From 3c9b261a4f855022b5e9ec44c2d6e3eee5630cd5 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 5 Sep 2024 14:09:34 +0200 Subject: [PATCH 001/211] feat: guide user for calling a contract --- Cargo.lock | 2 + Cargo.toml | 2 + crates/pop-cli/src/commands/call/contract.rs | 154 +++++++++++++++--- crates/pop-contracts/Cargo.toml | 3 +- crates/pop-contracts/src/call/metadata.rs | 48 ++++++ .../src/{call.rs => call/mod.rs} | 2 + crates/pop-contracts/src/lib.rs | 4 +- 7 files changed, 188 insertions(+), 27 deletions(-) create mode 100644 crates/pop-contracts/src/call/metadata.rs rename crates/pop-contracts/src/{call.rs => call/mod.rs} (99%) diff --git a/Cargo.lock b/Cargo.lock index 34d1d888f..f61d7fe1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4695,6 +4695,7 @@ dependencies = [ "anyhow", "contract-build", "contract-extrinsics", + "contract-transcode", "dirs", "duct", "flate2", @@ -4703,6 +4704,7 @@ dependencies = [ "mockito", "pop-common", "reqwest 0.12.5", + "scale-info", "sp-core", "sp-weights", "strum 0.26.3", diff --git a/Cargo.toml b/Cargo.toml index 02ec5aad0..78147f504 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,8 @@ sp-core = "31" sp-weights = "30" contract-build = "4.1" contract-extrinsics = "4.1" +contract-transcode = "4.1" +scale-info = { version = "2.11.3", default-features = false, features = ["derive"] } heck = "0.5.0" # parachains diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index d43689c9a..ab361fa1c 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -1,31 +1,35 @@ // SPDX-License-Identifier: GPL-3.0 -use crate::style::Theme; -use anyhow::anyhow; +use crate::{ + cli::{traits::Cli as _, Cli}, + style::Theme, +}; +use anyhow::{anyhow, Result}; use clap::Args; -use cliclack::{clear_screen, intro, log, outro, outro_cancel, set_theme}; +use cliclack::{clear_screen, confirm, input, intro, log, outro, outro_cancel, set_theme}; use console::style; use pop_contracts::{ - call_smart_contract, dry_run_call, dry_run_gas_estimate_call, set_up_call, CallOpts, + call_smart_contract, dry_run_call, dry_run_gas_estimate_call, get_messages, set_up_call, + CallOpts, Message, }; use sp_weights::Weight; use std::path::PathBuf; -#[derive(Args)] +#[derive(Args, Clone)] pub struct CallContractCommand { /// Path to the contract build directory. #[arg(short = 'p', long)] path: Option, /// The address of the contract to call. #[clap(name = "contract", long, env = "CONTRACT")] - contract: String, + contract: Option, /// The name of the contract message to call. #[clap(long, short)] - message: String, + message: Option, /// The constructor arguments, encoded as strings. #[clap(long, num_args = 0..)] args: Vec, - /// Transfers an initial balance to the instantiated contract. + /// Transfers an initial balance to the contract. #[clap(name = "value", long, default_value = "0")] value: String, /// Maximum amount of gas to be used for this command. @@ -40,7 +44,7 @@ pub struct CallContractCommand { /// Websocket endpoint of a node. #[clap(name = "url", long, value_parser, default_value = "ws://localhost:9944")] url: url::Url, - /// Secret key URI for the account deploying the contract. + /// Secret key URI for the account calling the contract. /// /// e.g. /// - for a dev account "//Alice" @@ -57,26 +61,38 @@ pub struct CallContractCommand { impl CallContractCommand { /// Executes the command. - pub(crate) async fn execute(self) -> anyhow::Result<()> { + pub(crate) async fn execute(self) -> Result<()> { clear_screen()?; intro(format!("{}: Calling a contract", style(" Pop CLI ").black().on_magenta()))?; set_theme(Theme); + let call_config = if self.contract.is_none() { + guide_user_to_call_contract().await? + } else { + self.clone() + }; + let contract = call_config + .contract + .expect("contract can not be none as fallback above is interactive input; qed"); + let message = call_config + .message + .expect("message can not be none as fallback above is interactive input; qed"); + let call_exec = set_up_call(CallOpts { - path: self.path.clone(), - contract: self.contract.clone(), - message: self.message.clone(), - args: self.args.clone(), - value: self.value.clone(), - gas_limit: self.gas_limit, - proof_size: self.proof_size, - url: self.url.clone(), - suri: self.suri.clone(), - execute: self.execute, + path: call_config.path, + contract, + message, + args: call_config.args, + value: call_config.value, + gas_limit: call_config.gas_limit, + proof_size: call_config.proof_size, + url: call_config.url, + suri: call_config.suri, + execute: call_config.execute, }) .await?; - if self.dry_run { + if call_config.dry_run { let spinner = cliclack::spinner(); spinner.start("Doing a dry run to estimate the gas..."); match dry_run_gas_estimate_call(&call_exec).await { @@ -92,16 +108,16 @@ impl CallContractCommand { return Ok(()); } - if !self.execute { + if !call_config.execute { let spinner = cliclack::spinner(); spinner.start("Calling the contract..."); let call_dry_run_result = dry_run_call(&call_exec).await?; log::info(format!("Result: {}", call_dry_run_result))?; log::warning("Your call has not been executed.")?; log::warning(format!( - "To submit the transaction and execute the call on chain, add {} flag to the command.", - "-x/--execute" - ))?; + "To submit the transaction and execute the call on chain, add {} flag to the command.", + "-x/--execute" + ))?; } else { let weight_limit; if self.gas_limit.is_some() && self.proof_size.is_some() { @@ -136,3 +152,91 @@ impl CallContractCommand { Ok(()) } } + +/// Guide the user to call the contract. +async fn guide_user_to_call_contract() -> anyhow::Result { + Cli.intro("Calling a contract")?; + + // Prompt for location of your contract. + let path: String = input("Path to your contract") + .placeholder("./my_contract") + .default_input("./") + .interact()?; + let contract_path = PathBuf::from(&path); + + // Prompt for contract address. + let contract_address: String = input("Paste the on-chain contract address:") + .placeholder("5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") + .default_input("5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") + .interact()?; + + // TODO: Guess the metadata path from the contract path. + let metadata_path = contract_path.join("target/ink/metadata.json"); + let messages = get_messages(metadata_path)?; + let message = display_select_options(&messages)?; + let mut contract_args = Vec::new(); + for arg in &message.args { + contract_args.push(input(arg).placeholder(arg).interact()?); + } + let mut value = "0".to_string(); + if message.payable { + value = input("Value to transfer to the contract: ") + .placeholder("0") + .default_input("0") + .interact()?; + } + // Prompt for gas limit of the call. + let gas_limit_input: u64 = input("Gas Limit:") + .required(false) + .placeholder("By default it will use an Estimation") + .default_input("0") + .interact()?; + let gas_limit: Option = (gas_limit_input != 0).then_some(gas_limit_input); + + // Prompt for proof_size of your contract. + let proof_size_input: u64 = input("Proof size:") + .required(false) + .placeholder("By default it will use an Estimation") + .default_input("0") + .interact()?; + let proof_size: Option = (proof_size_input != 0).then_some(proof_size_input); + + // Prompt for contract location. + let url: String = input("Where is your contract?") + .placeholder("ws://localhost:9944") + .default_input("ws://localhost:9944") + .interact()?; + + // Who is calling the contract. + let suri: String = input("Secret key URI for the account calling the contract:") + .placeholder("//Alice") + .default_input("//Alice") + .interact()?; + + let is_call_confirmed: bool = + confirm("Do you want to execute the call? (Selecting 'No' will perform a dry run)") + .initial_value(true) + .interact()?; + + Ok(CallContractCommand { + path: Some(contract_path), + contract: Some(contract_address), + message: Some(message.label.clone()), + args: contract_args, + value, + gas_limit, + proof_size, + url: url::Url::parse(&url)?, + suri, + execute: message.mutates, + dry_run: is_call_confirmed, + }) +} + +fn display_select_options(messages: &Vec) -> Result<&Message> { + let mut prompt = cliclack::select("Select the call:".to_string()); + for message in messages { + prompt = prompt.item(message, &message.label, &message.docs); + } + Ok(prompt.interact()?) +} diff --git a/crates/pop-contracts/Cargo.toml b/crates/pop-contracts/Cargo.toml index 94e3a6d79..19bcbdf32 100644 --- a/crates/pop-contracts/Cargo.toml +++ b/crates/pop-contracts/Cargo.toml @@ -33,7 +33,8 @@ subxt.workspace = true # cargo-contracts contract-build.workspace = true contract-extrinsics.workspace = true - +contract-transcode.workspace = true +scale-info.workspace = true # pop pop-common = { path = "../pop-common", version = "0.3.0" } diff --git a/crates/pop-contracts/src/call/metadata.rs b/crates/pop-contracts/src/call/metadata.rs new file mode 100644 index 000000000..e026b522f --- /dev/null +++ b/crates/pop-contracts/src/call/metadata.rs @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-3.0 + +use crate::errors::Error; +use contract_transcode::{ink_metadata::MessageParamSpec, ContractMessageTranscoder}; +use scale_info::form::PortableForm; +use std::path::PathBuf; + +#[derive(Clone, PartialEq, Eq)] +// TODO: We are ignoring selector, return type for now. +/// Describes a contract message. +pub struct Message { + /// The label of the message. + pub label: String, + /// If the message is allowed to mutate the contract state. + pub mutates: bool, + /// If the message accepts any `value` from the caller. + pub payable: bool, + /// The parameters of the deployment handler. + pub args: Vec, + /// The message documentation. + pub docs: String, + /// If the message is the default for off-chain consumers (e.g UIs). + pub default: bool, +} + +pub fn get_messages(metadata_path: PathBuf) -> Result, Error> { + let transcoder = ContractMessageTranscoder::load(metadata_path)?; + let mut messages: Vec = Vec::new(); + for message in transcoder.metadata().spec().messages() { + messages.push(Message { + label: message.label().to_string(), + mutates: message.mutates(), + payable: message.payable(), + args: process_args(message.args()), + docs: message.docs().join("."), + default: *message.default(), + }); + } + Ok(messages) +} +//TODO: We are ignoring the type of the argument. +fn process_args(message_params: &[MessageParamSpec]) -> Vec { + let mut args: Vec = Vec::new(); + for arg in message_params { + args.push(arg.label().to_string()); + } + args +} diff --git a/crates/pop-contracts/src/call.rs b/crates/pop-contracts/src/call/mod.rs similarity index 99% rename from crates/pop-contracts/src/call.rs rename to crates/pop-contracts/src/call/mod.rs index 88a3a65b8..b5ee9a9dc 100644 --- a/crates/pop-contracts/src/call.rs +++ b/crates/pop-contracts/src/call/mod.rs @@ -20,6 +20,8 @@ use subxt::{Config, PolkadotConfig as DefaultConfig}; use subxt_signer::sr25519::Keypair; use url::Url; +pub mod metadata; + /// Attributes for the `call` command. pub struct CallOpts { /// Path to the contract build directory. diff --git a/crates/pop-contracts/src/lib.rs b/crates/pop-contracts/src/lib.rs index 1d558de30..36272e23a 100644 --- a/crates/pop-contracts/src/lib.rs +++ b/crates/pop-contracts/src/lib.rs @@ -13,7 +13,9 @@ mod utils; pub use build::{build_smart_contract, is_supported, Verbosity}; pub use call::{ - call_smart_contract, dry_run_call, dry_run_gas_estimate_call, set_up_call, CallOpts, + call_smart_contract, dry_run_call, dry_run_gas_estimate_call, + metadata::{get_messages, Message}, + set_up_call, CallOpts, }; pub use new::{create_smart_contract, is_valid_contract_name}; pub use node::{contracts_node_generator, is_chain_alive, run_contracts_node}; From f9e305a8da006f7d770fb21fd12d4f07722bc061 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 5 Sep 2024 17:03:06 +0200 Subject: [PATCH 002/211] feat: get metadata contract from the contract path --- crates/pop-cli/src/commands/call/contract.rs | 12 +++++------- crates/pop-contracts/src/call/metadata.rs | 15 +++++++++++---- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index ab361fa1c..a1b6b9556 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -13,7 +13,7 @@ use pop_contracts::{ CallOpts, Message, }; use sp_weights::Weight; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; #[derive(Args, Clone)] pub struct CallContractCommand { @@ -158,11 +158,11 @@ async fn guide_user_to_call_contract() -> anyhow::Result { Cli.intro("Calling a contract")?; // Prompt for location of your contract. - let path: String = input("Path to your contract") + let input_path: String = input("Path to your contract") .placeholder("./my_contract") .default_input("./") .interact()?; - let contract_path = PathBuf::from(&path); + let contract_path = Path::new(&input_path); // Prompt for contract address. let contract_address: String = input("Paste the on-chain contract address:") @@ -170,9 +170,7 @@ async fn guide_user_to_call_contract() -> anyhow::Result { .default_input("5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") .interact()?; - // TODO: Guess the metadata path from the contract path. - let metadata_path = contract_path.join("target/ink/metadata.json"); - let messages = get_messages(metadata_path)?; + let messages = get_messages(contract_path)?; let message = display_select_options(&messages)?; let mut contract_args = Vec::new(); for arg in &message.args { @@ -219,7 +217,7 @@ async fn guide_user_to_call_contract() -> anyhow::Result { .interact()?; Ok(CallContractCommand { - path: Some(contract_path), + path: Some(contract_path.to_path_buf()), contract: Some(contract_address), message: Some(message.label.clone()), args: contract_args, diff --git a/crates/pop-contracts/src/call/metadata.rs b/crates/pop-contracts/src/call/metadata.rs index e026b522f..651c6040e 100644 --- a/crates/pop-contracts/src/call/metadata.rs +++ b/crates/pop-contracts/src/call/metadata.rs @@ -1,9 +1,10 @@ // SPDX-License-Identifier: GPL-3.0 use crate::errors::Error; -use contract_transcode::{ink_metadata::MessageParamSpec, ContractMessageTranscoder}; +use contract_extrinsics::ContractArtifacts; +use contract_transcode::ink_metadata::MessageParamSpec; use scale_info::form::PortableForm; -use std::path::PathBuf; +use std::path::Path; #[derive(Clone, PartialEq, Eq)] // TODO: We are ignoring selector, return type for now. @@ -23,8 +24,14 @@ pub struct Message { pub default: bool, } -pub fn get_messages(metadata_path: PathBuf) -> Result, Error> { - let transcoder = ContractMessageTranscoder::load(metadata_path)?; +pub fn get_messages(path: &Path) -> Result, Error> { + let cargo_toml_path = match path.ends_with("Cargo.toml") { + true => path.to_path_buf(), + false => path.join("Cargo.toml"), + }; + let contract_artifacts = + ContractArtifacts::from_manifest_or_file(Some(&cargo_toml_path), None)?; + let transcoder = contract_artifacts.contract_transcoder()?; let mut messages: Vec = Vec::new(); for message in transcoder.metadata().spec().messages() { messages.push(Message { From 5327bf9612ddb5fcb4408730195099d88e68b464 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 5 Sep 2024 18:25:24 +0200 Subject: [PATCH 003/211] refactor: refactor test and validate address input --- crates/pop-cli/src/commands/call/contract.rs | 44 ++++++++++---------- crates/pop-contracts/src/call/metadata.rs | 2 +- crates/pop-contracts/src/lib.rs | 2 +- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index a1b6b9556..bd219bed8 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -9,8 +9,8 @@ use clap::Args; use cliclack::{clear_screen, confirm, input, intro, log, outro, outro_cancel, set_theme}; use console::style; use pop_contracts::{ - call_smart_contract, dry_run_call, dry_run_gas_estimate_call, get_messages, set_up_call, - CallOpts, Message, + call_smart_contract, dry_run_call, dry_run_gas_estimate_call, get_messages, parse_account, + set_up_call, CallOpts, Message, }; use sp_weights::Weight; use std::path::{Path, PathBuf}; @@ -155,11 +155,11 @@ impl CallContractCommand { /// Guide the user to call the contract. async fn guide_user_to_call_contract() -> anyhow::Result { - Cli.intro("Calling a contract")?; + Cli.intro("Call a contract")?; // Prompt for location of your contract. - let input_path: String = input("Path to your contract") - .placeholder("./my_contract") + let input_path: String = input("Where is your project located?") + .placeholder("./") .default_input("./") .interact()?; let contract_path = Path::new(&input_path); @@ -167,6 +167,10 @@ async fn guide_user_to_call_contract() -> anyhow::Result { // Prompt for contract address. let contract_address: String = input("Paste the on-chain contract address:") .placeholder("5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") + .validate(|input: &String| match parse_account(input) { + Ok(_) => Ok(()), + Err(_) => Err("Invalid address."), + }) .default_input("5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") .interact()?; @@ -178,35 +182,33 @@ async fn guide_user_to_call_contract() -> anyhow::Result { } let mut value = "0".to_string(); if message.payable { - value = input("Value to transfer to the contract: ") + value = input("Value to transfer to the call:") .placeholder("0") .default_input("0") .interact()?; } - // Prompt for gas limit of the call. - let gas_limit_input: u64 = input("Gas Limit:") + // Prompt for gas limit and proof_size of the call. + let gas_limit_input: String = input("Enter the gas limit:") .required(false) - .placeholder("By default it will use an Estimation") - .default_input("0") + .default_input("") + .placeholder("if left blank, an estimation will be used") .interact()?; - let gas_limit: Option = (gas_limit_input != 0).then_some(gas_limit_input); - - // Prompt for proof_size of your contract. - let proof_size_input: u64 = input("Proof size:") + let gas_limit: Option = gas_limit_input.parse::().ok(); // If blank or bad input, estimate it. + let proof_size_input: String = input("Enter the proof size limit:") .required(false) - .placeholder("By default it will use an Estimation") - .default_input("0") + .placeholder("if left blank, an estimation will be used") + .default_input("") .interact()?; - let proof_size: Option = (proof_size_input != 0).then_some(proof_size_input); + let proof_size: Option = proof_size_input.parse::().ok(); // If blank or bad input, estimate it. // Prompt for contract location. - let url: String = input("Where is your contract?") + let url: String = input("Where is your contract deployed?") .placeholder("ws://localhost:9944") .default_input("ws://localhost:9944") .interact()?; // Who is calling the contract. - let suri: String = input("Secret key URI for the account calling the contract:") + let suri: String = input("Signer calling the contract:") .placeholder("//Alice") .default_input("//Alice") .interact()?; @@ -227,12 +229,12 @@ async fn guide_user_to_call_contract() -> anyhow::Result { url: url::Url::parse(&url)?, suri, execute: message.mutates, - dry_run: is_call_confirmed, + dry_run: !is_call_confirmed, }) } fn display_select_options(messages: &Vec) -> Result<&Message> { - let mut prompt = cliclack::select("Select the call:".to_string()); + let mut prompt = cliclack::select("Select the message to call:"); for message in messages { prompt = prompt.item(message, &message.label, &message.docs); } diff --git a/crates/pop-contracts/src/call/metadata.rs b/crates/pop-contracts/src/call/metadata.rs index 651c6040e..0de02cf90 100644 --- a/crates/pop-contracts/src/call/metadata.rs +++ b/crates/pop-contracts/src/call/metadata.rs @@ -39,7 +39,7 @@ pub fn get_messages(path: &Path) -> Result, Error> { mutates: message.mutates(), payable: message.payable(), args: process_args(message.args()), - docs: message.docs().join("."), + docs: message.docs().join(" "), default: *message.default(), }); } diff --git a/crates/pop-contracts/src/lib.rs b/crates/pop-contracts/src/lib.rs index 36272e23a..c883c165c 100644 --- a/crates/pop-contracts/src/lib.rs +++ b/crates/pop-contracts/src/lib.rs @@ -25,4 +25,4 @@ pub use up::{ dry_run_gas_estimate_instantiate, dry_run_upload, instantiate_smart_contract, set_up_deployment, set_up_upload, upload_smart_contract, UpOpts, }; -pub use utils::signer::parse_hex_bytes; +pub use utils::{helpers::parse_account, signer::parse_hex_bytes}; From 83e236cc5daeb6aed405a3b545a376f651ef15f1 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 6 Sep 2024 11:53:49 +0200 Subject: [PATCH 004/211] fix: apply feedback --- crates/pop-cli/src/commands/call/contract.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index bd219bed8..5dc91aa4a 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -166,7 +166,7 @@ async fn guide_user_to_call_contract() -> anyhow::Result { // Prompt for contract address. let contract_address: String = input("Paste the on-chain contract address:") - .placeholder("5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") + .placeholder("e.g. 5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") .validate(|input: &String| match parse_account(input) { Ok(_) => Ok(()), Err(_) => Err("Invalid address."), @@ -174,7 +174,13 @@ async fn guide_user_to_call_contract() -> anyhow::Result { .default_input("5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") .interact()?; - let messages = get_messages(contract_path)?; + let messages = match get_messages(contract_path) { + Ok(messages) => messages, + Err(e) => { + outro_cancel("Unable to fetch contract metadata.")?; + return Err(anyhow!(format!("{}", e.to_string()))); + }, + }; let message = display_select_options(&messages)?; let mut contract_args = Vec::new(); for arg in &message.args { @@ -191,12 +197,12 @@ async fn guide_user_to_call_contract() -> anyhow::Result { let gas_limit_input: String = input("Enter the gas limit:") .required(false) .default_input("") - .placeholder("if left blank, an estimation will be used") + .placeholder("If left blank, an estimation will be used") .interact()?; let gas_limit: Option = gas_limit_input.parse::().ok(); // If blank or bad input, estimate it. let proof_size_input: String = input("Enter the proof size limit:") .required(false) - .placeholder("if left blank, an estimation will be used") + .placeholder("If left blank, an estimation will be used") .default_input("") .interact()?; let proof_size: Option = proof_size_input.parse::().ok(); // If blank or bad input, estimate it. From 7cef633a6712f827046a8d7c7199400314962238 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 6 Sep 2024 14:48:46 +0200 Subject: [PATCH 005/211] feat: prompt to have another call and skip questions for queries --- crates/pop-cli/src/commands/call/contract.rs | 56 ++++++++++++-------- crates/pop-cli/src/commands/mod.rs | 2 +- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 5dc91aa4a..68f681afc 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -61,7 +61,7 @@ pub struct CallContractCommand { impl CallContractCommand { /// Executes the command. - pub(crate) async fn execute(self) -> Result<()> { + pub(crate) async fn execute(self: Box) -> Result<()> { clear_screen()?; intro(format!("{}: Calling a contract", style(" Pop CLI ").black().on_magenta()))?; set_theme(Theme); @@ -69,7 +69,7 @@ impl CallContractCommand { let call_config = if self.contract.is_none() { guide_user_to_call_contract().await? } else { - self.clone() + *self.clone() }; let contract = call_config .contract @@ -114,10 +114,6 @@ impl CallContractCommand { let call_dry_run_result = dry_run_call(&call_exec).await?; log::info(format!("Result: {}", call_dry_run_result))?; log::warning("Your call has not been executed.")?; - log::warning(format!( - "To submit the transaction and execute the call on chain, add {} flag to the command.", - "-x/--execute" - ))?; } else { let weight_limit; if self.gas_limit.is_some() && self.proof_size.is_some() { @@ -147,6 +143,13 @@ impl CallContractCommand { log::info(call_result)?; } + if self.contract.is_none() { + let another_call: bool = + confirm("Do you want to do another call?").initial_value(false).interact()?; + if another_call { + Box::pin(self.execute()).await?; + } + } outro("Call completed successfully!")?; Ok(()) @@ -193,19 +196,23 @@ async fn guide_user_to_call_contract() -> anyhow::Result { .default_input("0") .interact()?; } - // Prompt for gas limit and proof_size of the call. - let gas_limit_input: String = input("Enter the gas limit:") - .required(false) - .default_input("") - .placeholder("If left blank, an estimation will be used") - .interact()?; - let gas_limit: Option = gas_limit_input.parse::().ok(); // If blank or bad input, estimate it. - let proof_size_input: String = input("Enter the proof size limit:") - .required(false) - .placeholder("If left blank, an estimation will be used") - .default_input("") - .interact()?; - let proof_size: Option = proof_size_input.parse::().ok(); // If blank or bad input, estimate it. + let mut gas_limit: Option = None; + let mut proof_size: Option = None; + if message.mutates { + // Prompt for gas limit and proof_size of the call. + let gas_limit_input: String = input("Enter the gas limit:") + .required(false) + .default_input("") + .placeholder("If left blank, an estimation will be used") + .interact()?; + gas_limit = gas_limit_input.parse::().ok(); // If blank or bad input, estimate it. + let proof_size_input: String = input("Enter the proof size limit:") + .required(false) + .placeholder("If left blank, an estimation will be used") + .default_input("") + .interact()?; + proof_size = proof_size_input.parse::().ok(); // If blank or bad input, estimate it. + } // Prompt for contract location. let url: String = input("Where is your contract deployed?") @@ -219,10 +226,13 @@ async fn guide_user_to_call_contract() -> anyhow::Result { .default_input("//Alice") .interact()?; - let is_call_confirmed: bool = - confirm("Do you want to execute the call? (Selecting 'No' will perform a dry run)") - .initial_value(true) - .interact()?; + let mut is_call_confirmed: bool = true; + if message.mutates { + is_call_confirmed = + confirm("Do you want to execute the call? (Selecting 'No' will perform a dry run)") + .initial_value(true) + .interact()?; + } Ok(CallContractCommand { path: Some(contract_path.to_path_buf()), diff --git a/crates/pop-cli/src/commands/mod.rs b/crates/pop-cli/src/commands/mod.rs index 34c2f10b3..8a2941c95 100644 --- a/crates/pop-cli/src/commands/mod.rs +++ b/crates/pop-cli/src/commands/mod.rs @@ -98,7 +98,7 @@ impl Command { }, #[cfg(feature = "contract")] Self::Call(args) => match args.command { - call::Command::Contract(cmd) => cmd.execute().await.map(|_| Value::Null), + call::Command::Contract(cmd) => Box::new(cmd).execute().await.map(|_| Value::Null), }, #[cfg(any(feature = "parachain", feature = "contract"))] Self::Up(args) => match args.command { From 62eefcfd1d15550ba68b3a0881fdfe78b1164796 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Sun, 8 Sep 2024 22:14:33 +0200 Subject: [PATCH 006/211] refactor: use Cli module instead of cliclack --- crates/pop-cli/src/cli.rs | 94 ++++++++++++++++++++ crates/pop-cli/src/commands/call/contract.rs | 71 ++++++++------- 2 files changed, 132 insertions(+), 33 deletions(-) diff --git a/crates/pop-cli/src/cli.rs b/crates/pop-cli/src/cli.rs index 353a4704d..d11f6a473 100644 --- a/crates/pop-cli/src/cli.rs +++ b/crates/pop-cli/src/cli.rs @@ -13,6 +13,8 @@ pub(crate) mod traits { fn confirm(&mut self, prompt: impl Display) -> impl Confirm; /// Prints an info message. fn info(&mut self, text: impl Display) -> Result<()>; + /// Constructs a new [`Input`] prompt. + fn input(&mut self, prompt: impl Display) -> impl Input; /// Prints a header of the prompt sequence. fn intro(&mut self, title: impl Display) -> Result<()>; /// Constructs a new [`MultiSelect`] prompt. @@ -21,6 +23,8 @@ pub(crate) mod traits { fn outro(&mut self, message: impl Display) -> Result<()>; /// Prints a footer of the prompt sequence with a failure style. fn outro_cancel(&mut self, message: impl Display) -> Result<()>; + /// Constructs a new [`Select`] prompt. + fn select(&mut self, prompt: impl Display) -> impl Select; /// Prints a success message. fn success(&mut self, message: impl Display) -> Result<()>; /// Prints a warning message. @@ -29,10 +33,29 @@ pub(crate) mod traits { /// A confirmation prompt. pub trait Confirm { + /// Sets the initially selected value. + fn initial_value(self, initial_value: bool) -> Self; /// Starts the prompt interaction. fn interact(&mut self) -> Result; } + /// A text input prompt. + pub trait Input { + /// Sets the default value for the input. + fn default_input(self, value: &str) -> Self; + /// Starts the prompt interaction. + fn interact(&mut self) -> Result; + /// Sets the placeholder (hint) text for the input. + fn placeholder(self, value: &str) -> Self; + /// Sets whether the input is required. + fn required(self, required: bool) -> Self; + /// Sets a validation callback for the input that is called when the user submits. + fn validate( + self, + validator: impl Fn(&String) -> std::result::Result<(), &'static str> + 'static, + ) -> Self; + } + /// A multi-select prompt. pub trait MultiSelect { /// Starts the prompt interaction. @@ -42,6 +65,14 @@ pub(crate) mod traits { /// Sets whether the input is required. fn required(self, required: bool) -> Self; } + + /// A select prompt. + pub trait Select { + /// Starts the prompt interaction. + fn interact(&mut self) -> Result; + /// Adds an item to the selection prompt. + fn item(self, value: T, label: impl Display, hint: impl Display) -> Self; + } } /// A command line interface using cliclack. @@ -57,6 +88,11 @@ impl traits::Cli for Cli { cliclack::log::info(text) } + /// Constructs a new [`Input`] prompt. + fn input(&mut self, prompt: impl Display) -> impl traits::Input { + Input(cliclack::input(prompt)) + } + /// Prints a header of the prompt sequence. fn intro(&mut self, title: impl Display) -> Result<()> { cliclack::clear_screen()?; @@ -79,6 +115,11 @@ impl traits::Cli for Cli { cliclack::outro_cancel(message) } + /// Constructs a new [`Select`] prompt. + fn select(&mut self, prompt: impl Display) -> impl traits::Select { + Select::(cliclack::select(prompt)) + } + /// Prints a success message. fn success(&mut self, message: impl Display) -> Result<()> { cliclack::log::success(message) @@ -97,6 +138,43 @@ impl traits::Confirm for Confirm { fn interact(&mut self) -> Result { self.0.interact() } + /// Sets the initially selected value. + fn initial_value(mut self, initial_value: bool) -> Self { + self.0 = self.0.initial_value(initial_value); + self + } +} + +/// A input prompt using cliclack. +struct Input(cliclack::Input); +impl traits::Input for Input { + /// Sets the default value for the input. + fn default_input(mut self, value: &str) -> Self { + self.0 = self.0.default_input(value); + self + } + /// Starts the prompt interaction. + fn interact(&mut self) -> Result { + self.0.interact() + } + /// Sets the placeholder (hint) text for the input. + fn placeholder(mut self, placeholder: &str) -> Self { + self.0 = self.0.placeholder(placeholder); + self + } + /// Sets whether the input is required. + fn required(mut self, required: bool) -> Self { + self.0 = self.0.required(required); + self + } + /// Sets a validation callback for the input that is called when the user submits. + fn validate( + mut self, + validator: impl Fn(&String) -> std::result::Result<(), &'static str> + 'static, + ) -> Self { + self.0 = self.0.validate(validator); + self + } } /// A multi-select prompt using cliclack. @@ -121,6 +199,22 @@ impl traits::MultiSelect for MultiSelect { } } +/// A select prompt using cliclack. +struct Select(cliclack::Select); + +impl traits::Select for Select { + /// Starts the prompt interaction. + fn interact(&mut self) -> Result { + self.0.interact() + } + + /// Adds an item to the selection prompt. + fn item(mut self, value: T, label: impl Display, hint: impl Display) -> Self { + self.0 = self.0.item(value, label, hint); + self + } +} + #[cfg(test)] pub(crate) mod tests { use super::traits::*; diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 68f681afc..0af0fb43c 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -1,13 +1,11 @@ // SPDX-License-Identifier: GPL-3.0 -use crate::{ - cli::{traits::Cli as _, Cli}, - style::Theme, +use crate::cli::{ + traits::{Cli as _, Confirm, Input, Select}, + Cli, }; use anyhow::{anyhow, Result}; use clap::Args; -use cliclack::{clear_screen, confirm, input, intro, log, outro, outro_cancel, set_theme}; -use console::style; use pop_contracts::{ call_smart_contract, dry_run_call, dry_run_gas_estimate_call, get_messages, parse_account, set_up_call, CallOpts, Message, @@ -62,9 +60,7 @@ pub struct CallContractCommand { impl CallContractCommand { /// Executes the command. pub(crate) async fn execute(self: Box) -> Result<()> { - clear_screen()?; - intro(format!("{}: Calling a contract", style(" Pop CLI ").black().on_magenta()))?; - set_theme(Theme); + Cli.intro("Calling a contract")?; let call_config = if self.contract.is_none() { guide_user_to_call_contract().await? @@ -97,12 +93,12 @@ impl CallContractCommand { spinner.start("Doing a dry run to estimate the gas..."); match dry_run_gas_estimate_call(&call_exec).await { Ok(w) => { - log::info(format!("Gas limit: {:?}", w))?; - log::warning("Your call has not been executed.")?; + Cli.info(format!("Gas limit: {:?}", w))?; + Cli.warning("Your call has not been executed.")?; }, Err(e) => { spinner.error(format!("{e}")); - outro_cancel("Call failed.")?; + Cli.outro_cancel("Call failed.")?; }, }; return Ok(()); @@ -112,8 +108,8 @@ impl CallContractCommand { let spinner = cliclack::spinner(); spinner.start("Calling the contract..."); let call_dry_run_result = dry_run_call(&call_exec).await?; - log::info(format!("Result: {}", call_dry_run_result))?; - log::warning("Your call has not been executed.")?; + Cli.info(format!("Result: {}", call_dry_run_result))?; + Cli.warning("Your call has not been executed.")?; } else { let weight_limit; if self.gas_limit.is_some() && self.proof_size.is_some() { @@ -124,12 +120,12 @@ impl CallContractCommand { spinner.start("Doing a dry run to estimate the gas..."); weight_limit = match dry_run_gas_estimate_call(&call_exec).await { Ok(w) => { - log::info(format!("Gas limit: {:?}", w))?; + Cli.info(format!("Gas limit: {:?}", w))?; w }, Err(e) => { spinner.error(format!("{e}")); - outro_cancel("Call failed.")?; + Cli.outro_cancel("Call failed.")?; return Ok(()); }, }; @@ -141,17 +137,17 @@ impl CallContractCommand { .await .map_err(|err| anyhow!("{} {}", "ERROR:", format!("{err:?}")))?; - log::info(call_result)?; + Cli.info(call_result)?; } if self.contract.is_none() { let another_call: bool = - confirm("Do you want to do another call?").initial_value(false).interact()?; + Cli.confirm("Do you want to do another call?").initial_value(false).interact()?; if another_call { Box::pin(self.execute()).await?; } } - outro("Call completed successfully!")?; + Cli.outro("Call completed successfully!")?; Ok(()) } } @@ -161,14 +157,16 @@ async fn guide_user_to_call_contract() -> anyhow::Result { Cli.intro("Call a contract")?; // Prompt for location of your contract. - let input_path: String = input("Where is your project located?") + let input_path: String = Cli + .input("Where is your project located?") .placeholder("./") .default_input("./") .interact()?; let contract_path = Path::new(&input_path); // Prompt for contract address. - let contract_address: String = input("Paste the on-chain contract address:") + let contract_address: String = Cli + .input("Paste the on-chain contract address:") .placeholder("e.g. 5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") .validate(|input: &String| match parse_account(input) { Ok(_) => Ok(()), @@ -180,18 +178,19 @@ async fn guide_user_to_call_contract() -> anyhow::Result { let messages = match get_messages(contract_path) { Ok(messages) => messages, Err(e) => { - outro_cancel("Unable to fetch contract metadata.")?; + Cli.outro_cancel("Unable to fetch contract metadata.")?; return Err(anyhow!(format!("{}", e.to_string()))); }, }; let message = display_select_options(&messages)?; let mut contract_args = Vec::new(); for arg in &message.args { - contract_args.push(input(arg).placeholder(arg).interact()?); + contract_args.push(Cli.input(arg).placeholder(arg).interact()?); } let mut value = "0".to_string(); if message.payable { - value = input("Value to transfer to the call:") + value = Cli + .input("Value to transfer to the call:") .placeholder("0") .default_input("0") .interact()?; @@ -200,13 +199,15 @@ async fn guide_user_to_call_contract() -> anyhow::Result { let mut proof_size: Option = None; if message.mutates { // Prompt for gas limit and proof_size of the call. - let gas_limit_input: String = input("Enter the gas limit:") + let gas_limit_input: String = Cli + .input("Enter the gas limit:") .required(false) .default_input("") .placeholder("If left blank, an estimation will be used") .interact()?; gas_limit = gas_limit_input.parse::().ok(); // If blank or bad input, estimate it. - let proof_size_input: String = input("Enter the proof size limit:") + let proof_size_input: String = Cli + .input("Enter the proof size limit:") .required(false) .placeholder("If left blank, an estimation will be used") .default_input("") @@ -215,23 +216,25 @@ async fn guide_user_to_call_contract() -> anyhow::Result { } // Prompt for contract location. - let url: String = input("Where is your contract deployed?") + let url: String = Cli + .input("Where is your contract deployed?") .placeholder("ws://localhost:9944") .default_input("ws://localhost:9944") .interact()?; // Who is calling the contract. - let suri: String = input("Signer calling the contract:") + let suri: String = Cli + .input("Signer calling the contract:") .placeholder("//Alice") .default_input("//Alice") .interact()?; let mut is_call_confirmed: bool = true; if message.mutates { - is_call_confirmed = - confirm("Do you want to execute the call? (Selecting 'No' will perform a dry run)") - .initial_value(true) - .interact()?; + is_call_confirmed = Cli + .confirm("Do you want to execute the call? (Selecting 'No' will perform a dry run)") + .initial_value(true) + .interact()?; } Ok(CallContractCommand { @@ -250,9 +253,11 @@ async fn guide_user_to_call_contract() -> anyhow::Result { } fn display_select_options(messages: &Vec) -> Result<&Message> { - let mut prompt = cliclack::select("Select the message to call:"); + let mut cli = Cli; + let mut prompt = cli.select("Select the message to call:"); for message in messages { prompt = prompt.item(message, &message.label, &message.docs); } - Ok(prompt.interact()?) + let selected_message = prompt.interact()?; + Ok(selected_message) } From fee1e26f54b89f318f495a8e1286a66a9c5dff74 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Mon, 9 Sep 2024 17:21:11 +0200 Subject: [PATCH 007/211] test: unit test pop-cli crate --- crates/pop-cli/src/cli.rs | 131 +++++- crates/pop-cli/src/commands/call/contract.rs | 321 +++++++++++-- crates/pop-cli/src/commands/mod.rs | 7 +- crates/pop-contracts/src/call/mod.rs | 2 +- crates/pop-contracts/src/up.rs | 2 +- .../tests => tests}/files/testing.contract | 0 tests/files/testing.json | 424 ++++++++++++++++++ 7 files changed, 834 insertions(+), 53 deletions(-) rename {crates/pop-contracts/tests => tests}/files/testing.contract (100%) create mode 100644 tests/files/testing.json diff --git a/crates/pop-cli/src/cli.rs b/crates/pop-cli/src/cli.rs index d11f6a473..65999288d 100644 --- a/crates/pop-cli/src/cli.rs +++ b/crates/pop-cli/src/cli.rs @@ -218,18 +218,21 @@ impl traits::Select for Select { #[cfg(test)] pub(crate) mod tests { use super::traits::*; - use std::{fmt::Display, io::Result}; + use std::{fmt::Display, io::Result, usize}; /// Mock Cli with optional expectations #[derive(Default)] pub(crate) struct MockCli { confirm_expectation: Option<(String, bool)>, info_expectations: Vec, + input_expectations: Vec<(String, String)>, intro_expectation: Option, outro_expectation: Option, multiselect_expectation: Option<(String, Option, bool, Option>)>, outro_cancel_expectation: Option, + select_expectation: + Option<(String, Option, bool, Option>, usize)>, success_expectations: Vec, warning_expectations: Vec, } @@ -244,6 +247,11 @@ pub(crate) mod tests { self } + pub(crate) fn expect_input(mut self, prompt: impl Display, input: String) -> Self { + self.input_expectations.push((prompt.to_string(), input)); + self + } + pub(crate) fn expect_info(mut self, message: impl Display) -> Self { self.info_expectations.push(message.to_string()); self @@ -275,6 +283,18 @@ pub(crate) mod tests { self } + pub(crate) fn expect_select( + mut self, + prompt: impl Display, + required: Option, + collect: bool, + items: Option>, + item: usize, + ) -> Self { + self.select_expectation = Some((prompt.to_string(), required, collect, items, item)); + self + } + pub(crate) fn expect_success(mut self, message: impl Display) -> Self { self.success_expectations.push(message.to_string()); self @@ -292,6 +312,9 @@ pub(crate) mod tests { if !self.info_expectations.is_empty() { panic!("`{}` info log expectations not satisfied", self.info_expectations.join(",")) } + if !self.input_expectations.is_empty() { + panic!("`{:?}` input expectation not satisfied", self.input_expectations) + } if let Some(expectation) = self.intro_expectation { panic!("`{expectation}` intro expectation not satisfied") } @@ -304,6 +327,9 @@ pub(crate) mod tests { if let Some(expectation) = self.outro_cancel_expectation { panic!("`{expectation}` outro cancel expectation not satisfied") } + if let Some((prompt, _, _, _, _)) = self.select_expectation { + panic!("`{prompt}` select prompt expectation not satisfied") + } if !self.success_expectations.is_empty() { panic!( "`{}` success log expectations not satisfied", @@ -336,6 +362,20 @@ pub(crate) mod tests { Ok(()) } + fn input(&mut self, prompt: impl Display) -> impl Input { + let prompt = prompt.to_string(); + if let Some((expectation, input)) = self.input_expectations.pop() { + assert_eq!(expectation, prompt, "prompt does not satisfy expectation"); + return MockInput { + prompt: input.clone(), + input, + placeholder: "".to_string(), + required: false, + }; + } + MockInput::default() + } + fn intro(&mut self, title: impl Display) -> Result<()> { if let Some(expectation) = self.intro_expectation.take() { assert_eq!(expectation, title.to_string(), "intro does not satisfy expectation"); @@ -382,6 +422,21 @@ pub(crate) mod tests { Ok(()) } + fn select(&mut self, prompt: impl Display) -> impl Select { + let prompt = prompt.to_string(); + println!("prompt: {}", prompt); + if let Some((expectation, _, collect, items_expectation, item)) = + self.select_expectation.take() + { + println!("expectation: {}", expectation); + println!("items_expectation: {:?}", items_expectation); + assert_eq!(expectation, prompt, "prompt does not satisfy expectation"); + return MockSelect { items_expectation, collect, items: vec![], item }; + } + + MockSelect::default() + } + fn success(&mut self, message: impl Display) -> Result<()> { let message = message.to_string(); self.success_expectations.retain(|x| *x != message); @@ -405,6 +460,46 @@ pub(crate) mod tests { fn interact(&mut self) -> Result { Ok(self.confirm) } + fn initial_value(mut self, initial_value: bool) -> Self { + self.confirm = initial_value; + self + } + } + + /// Mock input prompt + #[derive(Default)] + struct MockInput { + prompt: String, + input: String, + placeholder: String, + required: bool, + } + + impl Input for MockInput { + fn interact(&mut self) -> Result { + Ok(self.prompt.clone()) + } + fn default_input(mut self, value: &str) -> Self { + self.input = value.to_string(); + self + } + + fn placeholder(mut self, value: &str) -> Self { + self.placeholder = value.to_string(); + self + } + + fn required(mut self, value: bool) -> Self { + self.required = value; + self + } + + fn validate( + self, + _validator: impl Fn(&String) -> std::result::Result<(), &'static str> + 'static, + ) -> Self { + self + } } /// Mock multi-select prompt @@ -454,4 +549,38 @@ pub(crate) mod tests { self } } + + /// Mock select prompt + pub(crate) struct MockSelect { + items_expectation: Option>, + collect: bool, + items: Vec, + item: usize, + } + + impl MockSelect { + pub(crate) fn default() -> Self { + Self { items_expectation: None, collect: false, items: vec![], item: 0 } + } + } + + impl Select for MockSelect { + fn interact(&mut self) -> Result { + Ok(self.items[self.item].clone()) + } + + fn item(mut self, value: T, label: impl Display, hint: impl Display) -> Self { + // Check expectations + if let Some(items) = self.items_expectation.as_mut() { + let item = (label.to_string(), hint.to_string()); + assert!(items.contains(&item), "`{item:?}` item does not satisfy any expectations"); + items.retain(|x| *x != item); + } + // Collect if specified + if self.collect { + self.items.push(value); + } + self + } + } } diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 0af0fb43c..efaff7db9 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -1,14 +1,11 @@ // SPDX-License-Identifier: GPL-3.0 -use crate::cli::{ - traits::{Cli as _, Confirm, Input, Select}, - Cli, -}; +use crate::cli::traits::*; use anyhow::{anyhow, Result}; use clap::Args; use pop_contracts::{ call_smart_contract, dry_run_call, dry_run_gas_estimate_call, get_messages, parse_account, - set_up_call, CallOpts, Message, + set_up_call, CallOpts, }; use sp_weights::Weight; use std::path::{Path, PathBuf}; @@ -57,19 +54,27 @@ pub struct CallContractCommand { dry_run: bool, } -impl CallContractCommand { +pub(crate) struct CallContract<'a, CLI: Cli> { + /// The cli to be used. + pub(crate) cli: &'a mut CLI, + /// The args to call. + pub(crate) args: CallContractCommand, +} + +impl<'a, CLI: Cli> CallContract<'a, CLI> { /// Executes the command. - pub(crate) async fn execute(self: Box) -> Result<()> { - Cli.intro("Calling a contract")?; + pub(crate) async fn execute(mut self: Box) -> Result<()> { + self.cli.intro("Call a contract")?; - let call_config = if self.contract.is_none() { - guide_user_to_call_contract().await? + let call_config = if self.args.contract.is_none() { + guide_user_to_call_contract(&mut self).await? } else { - *self.clone() + self.args.clone() }; let contract = call_config .contract .expect("contract can not be none as fallback above is interactive input; qed"); + // TODO: Can be nill pop call contract --contract let message = call_config .message .expect("message can not be none as fallback above is interactive input; qed"); @@ -82,7 +87,7 @@ impl CallContractCommand { value: call_config.value, gas_limit: call_config.gas_limit, proof_size: call_config.proof_size, - url: call_config.url, + url: call_config.url.clone(), suri: call_config.suri, execute: call_config.execute, }) @@ -93,12 +98,12 @@ impl CallContractCommand { spinner.start("Doing a dry run to estimate the gas..."); match dry_run_gas_estimate_call(&call_exec).await { Ok(w) => { - Cli.info(format!("Gas limit: {:?}", w))?; - Cli.warning("Your call has not been executed.")?; + self.cli.info(format!("Gas limit: {:?}", w))?; + self.cli.warning("Your call has not been executed.")?; }, Err(e) => { spinner.error(format!("{e}")); - Cli.outro_cancel("Call failed.")?; + self.cli.outro_cancel("Call failed.")?; }, }; return Ok(()); @@ -108,24 +113,26 @@ impl CallContractCommand { let spinner = cliclack::spinner(); spinner.start("Calling the contract..."); let call_dry_run_result = dry_run_call(&call_exec).await?; - Cli.info(format!("Result: {}", call_dry_run_result))?; - Cli.warning("Your call has not been executed.")?; + self.cli.info(format!("Result: {}", call_dry_run_result))?; + self.cli.warning("Your call has not been executed.")?; } else { let weight_limit; - if self.gas_limit.is_some() && self.proof_size.is_some() { - weight_limit = - Weight::from_parts(self.gas_limit.unwrap(), self.proof_size.unwrap()); + if call_config.gas_limit.is_some() && call_config.proof_size.is_some() { + weight_limit = Weight::from_parts( + call_config.gas_limit.unwrap(), + call_config.proof_size.unwrap(), + ); } else { let spinner = cliclack::spinner(); spinner.start("Doing a dry run to estimate the gas..."); weight_limit = match dry_run_gas_estimate_call(&call_exec).await { Ok(w) => { - Cli.info(format!("Gas limit: {:?}", w))?; + self.cli.info(format!("Gas limit: {:?}", w))?; w }, Err(e) => { spinner.error(format!("{e}")); - Cli.outro_cancel("Call failed.")?; + self.cli.outro_cancel("Call failed.")?; return Ok(()); }, }; @@ -133,39 +140,49 @@ impl CallContractCommand { let spinner = cliclack::spinner(); spinner.start("Calling the contract..."); - let call_result = call_smart_contract(call_exec, weight_limit, &self.url) + let call_result = call_smart_contract(call_exec, weight_limit, &call_config.url) .await .map_err(|err| anyhow!("{} {}", "ERROR:", format!("{err:?}")))?; - Cli.info(call_result)?; + self.cli.info(call_result)?; } - if self.contract.is_none() { - let another_call: bool = - Cli.confirm("Do you want to do another call?").initial_value(false).interact()?; + if self.args.contract.is_none() { + let another_call: bool = self + .cli + .confirm("Do you want to do another call?") + .initial_value(false) + .interact()?; if another_call { Box::pin(self.execute()).await?; + } else { + self.cli.outro("Call completed successfully!")?; } + } else { + self.cli.outro("Call completed successfully!")?; } - - Cli.outro("Call completed successfully!")?; Ok(()) } } /// Guide the user to call the contract. -async fn guide_user_to_call_contract() -> anyhow::Result { - Cli.intro("Call a contract")?; +async fn guide_user_to_call_contract<'a, CLI: Cli>( + command: &mut CallContract<'a, CLI>, +) -> anyhow::Result { + command.cli.intro("Call a contract")?; // Prompt for location of your contract. - let input_path: String = Cli + let input_path: String = command + .cli .input("Where is your project located?") .placeholder("./") .default_input("./") .interact()?; let contract_path = Path::new(&input_path); + println!("path: {:?}", contract_path); // Prompt for contract address. - let contract_address: String = Cli + let contract_address: String = command + .cli .input("Paste the on-chain contract address:") .placeholder("e.g. 5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") .validate(|input: &String| match parse_account(input) { @@ -178,18 +195,27 @@ async fn guide_user_to_call_contract() -> anyhow::Result { let messages = match get_messages(contract_path) { Ok(messages) => messages, Err(e) => { - Cli.outro_cancel("Unable to fetch contract metadata.")?; + command.cli.outro_cancel("Unable to fetch contract metadata.")?; return Err(anyhow!(format!("{}", e.to_string()))); }, }; - let message = display_select_options(&messages)?; + let message = { + let mut prompt = command.cli.select("Select the message to call:"); + for select_message in messages { + prompt = + prompt.item(select_message.clone(), &select_message.label, &select_message.docs); + } + prompt.interact()? + }; + let mut contract_args = Vec::new(); for arg in &message.args { - contract_args.push(Cli.input(arg).placeholder(arg).interact()?); + contract_args.push(command.cli.input(arg).placeholder(arg).interact()?); } let mut value = "0".to_string(); if message.payable { - value = Cli + value = command + .cli .input("Value to transfer to the call:") .placeholder("0") .default_input("0") @@ -199,14 +225,16 @@ async fn guide_user_to_call_contract() -> anyhow::Result { let mut proof_size: Option = None; if message.mutates { // Prompt for gas limit and proof_size of the call. - let gas_limit_input: String = Cli + let gas_limit_input: String = command + .cli .input("Enter the gas limit:") .required(false) .default_input("") .placeholder("If left blank, an estimation will be used") .interact()?; gas_limit = gas_limit_input.parse::().ok(); // If blank or bad input, estimate it. - let proof_size_input: String = Cli + let proof_size_input: String = command + .cli .input("Enter the proof size limit:") .required(false) .placeholder("If left blank, an estimation will be used") @@ -216,14 +244,16 @@ async fn guide_user_to_call_contract() -> anyhow::Result { } // Prompt for contract location. - let url: String = Cli + let url: String = command + .cli .input("Where is your contract deployed?") .placeholder("ws://localhost:9944") .default_input("ws://localhost:9944") .interact()?; // Who is calling the contract. - let suri: String = Cli + let suri: String = command + .cli .input("Signer calling the contract:") .placeholder("//Alice") .default_input("//Alice") @@ -231,7 +261,8 @@ async fn guide_user_to_call_contract() -> anyhow::Result { let mut is_call_confirmed: bool = true; if message.mutates { - is_call_confirmed = Cli + is_call_confirmed = command + .cli .confirm("Do you want to execute the call? (Selecting 'No' will perform a dry run)") .initial_value(true) .interact()?; @@ -252,12 +283,204 @@ async fn guide_user_to_call_contract() -> anyhow::Result { }) } -fn display_select_options(messages: &Vec) -> Result<&Message> { - let mut cli = Cli; - let mut prompt = cli.select("Select the message to call:"); - for message in messages { - prompt = prompt.item(message, &message.label, &message.docs); +#[cfg(test)] +mod tests { + use super::*; + use crate::cli::MockCli; + use pop_contracts::{create_smart_contract, Contract}; + use std::{env, fs}; + use url::Url; + + fn generate_smart_contract_test_environment() -> Result { + let temp_dir = tempfile::tempdir().expect("Could not create temp dir"); + let temp_contract_dir = temp_dir.path().join("testing"); + fs::create_dir(&temp_contract_dir)?; + create_smart_contract("testing", temp_contract_dir.as_path(), &Contract::Standard)?; + Ok(temp_dir) + } + // Function that mocks the build process generating the contract artifacts. + fn mock_build_process(temp_contract_dir: PathBuf) -> Result<()> { + // Create a target directory + let target_contract_dir = temp_contract_dir.join("target"); + fs::create_dir(&target_contract_dir)?; + fs::create_dir(&target_contract_dir.join("ink"))?; + // Copy a mocked testing.contract and testing.json files inside the target directory + let current_dir = env::current_dir().expect("Failed to get current directory"); + let contract_file = current_dir.join("../../tests/files/testing.contract"); + fs::copy(contract_file, &target_contract_dir.join("ink/testing.contract"))?; + let metadata_file = current_dir.join("../../tests/files/testing.json"); + fs::copy(metadata_file, &target_contract_dir.join("ink/testing.json"))?; + Ok(()) + } + + #[tokio::test] + async fn call_contract_messages_are_ok() -> Result<()> { + let temp_dir = generate_smart_contract_test_environment()?; + mock_build_process(temp_dir.path().join("testing"))?; + + let mut cli = MockCli::new() + .expect_intro(&"Call a contract") + .expect_warning("Your call has not been executed.") + .expect_outro("Call completed successfully!"); + + // Contract deployed on Pop Network testnet, test get + Box::new(CallContract { + cli: &mut cli, + args: CallContractCommand { + path: Some(temp_dir.path().join("testing")), + contract: Some("14BfwaXddoarT9Z6LcfXBmunutk6Ssmy1kBdWX6sy9KRskJz".to_string()), + message: Some("get".to_string()), + args: vec![].to_vec(), + value: "0".to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, + suri: "//Alice".to_string(), + dry_run: false, + execute: false, + }, + }) + .execute() + .await?; + + cli.verify() + } + + // This test only covers the interactive portion of the call contract command, without actually calling the contract. + #[tokio::test] + async fn guide_user_to_query_contract_works() -> Result<()> { + let temp_dir = generate_smart_contract_test_environment()?; + mock_build_process(temp_dir.path().join("testing"))?; + + let items = vec![ + ("flip".into(), " A message that can be called on instantiated contracts. This one flips the value of the stored `bool` from `true` to `false` and vice versa.".into()), + ("get".into(), " Simply returns the current value of our `bool`.".into()), + ]; + // The inputs are processed in reverse order. + let mut cli = MockCli::new() + .expect_intro(&"Call a contract") + .expect_input("Signer calling the contract:", "//Alice".into()) + .expect_input( + "Where is your contract deployed?", + "wss://rpc1.paseo.popnetwork.xyz".into(), + ) + .expect_select::( + "Select the message to call:", + Some(false), + true, + Some(items), + 1, // "get" message + ) + .expect_input( + "Paste the on-chain contract address:", + "14BfwaXddoarT9Z6LcfXBmunutk6Ssmy1kBdWX6sy9KRskJz".into(), + ) + .expect_input( + "Where is your project located?", + temp_dir.path().join("testing").display().to_string(), + ); + + let call_config = guide_user_to_call_contract(&mut CallContract { + cli: &mut cli, + args: CallContractCommand { + path: Some(temp_dir.path().join("testing")), + contract: None, + message: None, + args: vec![].to_vec(), + value: "0".to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse("ws://localhost:9944")?, + suri: "//Alice".to_string(), + dry_run: false, + execute: false, + }, + }) + .await?; + assert_eq!( + call_config.contract, + Some("14BfwaXddoarT9Z6LcfXBmunutk6Ssmy1kBdWX6sy9KRskJz".to_string()) + ); + assert_eq!(call_config.message, Some("get".to_string())); + assert_eq!(call_config.args.len(), 0); + assert_eq!(call_config.value, "0".to_string()); + assert_eq!(call_config.gas_limit, None); + assert_eq!(call_config.proof_size, None); + assert_eq!(call_config.url.to_string(), "wss://rpc1.paseo.popnetwork.xyz/"); + assert_eq!(call_config.suri, "//Alice"); + assert!(!call_config.execute); + assert!(!call_config.dry_run); + + cli.verify() + } + + // This test only covers the interactive portion of the call contract command, without actually calling the contract. + #[tokio::test] + async fn guide_user_to_call_contract_works() -> Result<()> { + let temp_dir = generate_smart_contract_test_environment()?; + mock_build_process(temp_dir.path().join("testing"))?; + + let items = vec![ + ("flip".into(), " A message that can be called on instantiated contracts. This one flips the value of the stored `bool` from `true` to `false` and vice versa.".into()), + ("get".into(), " Simply returns the current value of our `bool`.".into()), + ]; + // The inputs are processed in reverse order. + let mut cli = MockCli::new() + .expect_intro(&"Call a contract") + .expect_input("Signer calling the contract:", "//Alice".into()) + .expect_input( + "Where is your contract deployed?", + "wss://rpc1.paseo.popnetwork.xyz".into(), + ) + .expect_select::( + "Select the message to call:", + Some(false), + true, + Some(items), + 0, // "flip" message + ) + .expect_input("Enter the proof size limit:", "".into()) + .expect_input("Enter the gas limit:", "".into()) + .expect_input( + "Paste the on-chain contract address:", + "14BfwaXddoarT9Z6LcfXBmunutk6Ssmy1kBdWX6sy9KRskJz".into(), + ) + .expect_input( + "Where is your project located?", + temp_dir.path().join("testing").display().to_string(), + ); + + let call_config = guide_user_to_call_contract(&mut CallContract { + cli: &mut cli, + args: CallContractCommand { + path: Some(temp_dir.path().join("testing")), + contract: None, + message: None, + args: vec![].to_vec(), + value: "0".to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse("ws://localhost:9944")?, + suri: "//Alice".to_string(), + dry_run: false, + execute: false, + }, + }) + .await?; + assert_eq!( + call_config.contract, + Some("14BfwaXddoarT9Z6LcfXBmunutk6Ssmy1kBdWX6sy9KRskJz".to_string()) + ); + assert_eq!(call_config.message, Some("flip".to_string())); + assert_eq!(call_config.args.len(), 0); + assert_eq!(call_config.value, "0".to_string()); + assert_eq!(call_config.gas_limit, None); + assert_eq!(call_config.proof_size, None); + assert_eq!(call_config.url.to_string(), "wss://rpc1.paseo.popnetwork.xyz/"); + assert_eq!(call_config.suri, "//Alice"); + assert!(call_config.execute); + assert!(!call_config.dry_run); + + cli.verify() } - let selected_message = prompt.interact()?; - Ok(selected_message) } diff --git a/crates/pop-cli/src/commands/mod.rs b/crates/pop-cli/src/commands/mod.rs index 8a2941c95..6e8967449 100644 --- a/crates/pop-cli/src/commands/mod.rs +++ b/crates/pop-cli/src/commands/mod.rs @@ -98,7 +98,12 @@ impl Command { }, #[cfg(feature = "contract")] Self::Call(args) => match args.command { - call::Command::Contract(cmd) => Box::new(cmd).execute().await.map(|_| Value::Null), + call::Command::Contract(cmd) => { + Box::new(call::contract::CallContract { cli: &mut Cli, args: cmd }) + .execute() + .await + .map(|_| Value::Null) + }, }, #[cfg(any(feature = "parachain", feature = "contract"))] Self::Up(args) => match args.command { diff --git a/crates/pop-contracts/src/call/mod.rs b/crates/pop-contracts/src/call/mod.rs index b5ee9a9dc..591b202c3 100644 --- a/crates/pop-contracts/src/call/mod.rs +++ b/crates/pop-contracts/src/call/mod.rs @@ -185,7 +185,7 @@ mod tests { fs::create_dir(&target_contract_dir.join("ink"))?; // Copy a mocked testing.contract file inside the target directory let current_dir = env::current_dir().expect("Failed to get current directory"); - let contract_file = current_dir.join("tests/files/testing.contract"); + let contract_file = current_dir.join("../../tests/files/testing.contract"); fs::copy(contract_file, &target_contract_dir.join("ink/testing.contract"))?; Ok(()) } diff --git a/crates/pop-contracts/src/up.rs b/crates/pop-contracts/src/up.rs index f4cc65bdf..e387dbe15 100644 --- a/crates/pop-contracts/src/up.rs +++ b/crates/pop-contracts/src/up.rs @@ -225,7 +225,7 @@ mod tests { fs::create_dir(&target_contract_dir.join("ink"))?; // Copy a mocked testing.contract file inside the target directory let current_dir = env::current_dir().expect("Failed to get current directory"); - let contract_file = current_dir.join("tests/files/testing.contract"); + let contract_file = current_dir.join("../../tests/files/testing.contract"); fs::copy(contract_file, &target_contract_dir.join("ink/testing.contract"))?; Ok(()) } diff --git a/crates/pop-contracts/tests/files/testing.contract b/tests/files/testing.contract similarity index 100% rename from crates/pop-contracts/tests/files/testing.contract rename to tests/files/testing.contract diff --git a/tests/files/testing.json b/tests/files/testing.json new file mode 100644 index 000000000..78cff4b6b --- /dev/null +++ b/tests/files/testing.json @@ -0,0 +1,424 @@ +{ + "source": { + "hash": "0x29972a7ea06ca802e5d857dfb7cb6455220679f4e9314a533840b9865ccbb799", + "language": "ink! 5.0.0", + "compiler": "rustc 1.78.0", + "build_info": { + "rust_toolchain": "stable-aarch64-apple-darwin", + "cargo_contract_version": "4.1.1", + "build_mode": "Release", + "wasm_opt_settings": { + "optimization_passes": "Z", + "keep_debug_symbols": false + } + } + }, + "contract": { + "name": "testing", + "version": "0.1.0", + "authors": [ + "[your_name] <[your_email]>" + ] + }, + "image": null, + "version": 5, + "types": [ + { + "id": 0, + "type": { + "def": { + "primitive": "bool" + } + } + }, + { + "id": 1, + "type": { + "path": [ + "testing", + "testing", + "Testing" + ], + "def": { + "composite": { + "fields": [ + { + "name": "value", + "type": 0, + "typeName": ",>>::Type" + } + ] + } + } + } + }, + { + "id": 2, + "type": { + "path": [ + "Result" + ], + "params": [ + { + "name": "T", + "type": 3 + }, + { + "name": "E", + "type": 4 + } + ], + "def": { + "variant": { + "variants": [ + { + "name": "Ok", + "fields": [ + { + "type": 3 + } + ], + "index": 0 + }, + { + "name": "Err", + "fields": [ + { + "type": 4 + } + ], + "index": 1 + } + ] + } + } + } + }, + { + "id": 3, + "type": { + "def": { + "tuple": [] + } + } + }, + { + "id": 4, + "type": { + "path": [ + "ink_primitives", + "LangError" + ], + "def": { + "variant": { + "variants": [ + { + "name": "CouldNotReadInput", + "index": 1 + } + ] + } + } + } + }, + { + "id": 5, + "type": { + "path": [ + "Result" + ], + "params": [ + { + "name": "T", + "type": 0 + }, + { + "name": "E", + "type": 4 + } + ], + "def": { + "variant": { + "variants": [ + { + "name": "Ok", + "fields": [ + { + "type": 0 + } + ], + "index": 0 + }, + { + "name": "Err", + "fields": [ + { + "type": 4 + } + ], + "index": 1 + } + ] + } + } + } + }, + { + "id": 6, + "type": { + "path": [ + "ink_primitives", + "types", + "AccountId" + ], + "def": { + "composite": { + "fields": [ + { + "type": 7, + "typeName": "[u8; 32]" + } + ] + } + } + } + }, + { + "id": 7, + "type": { + "def": { + "array": { + "len": 32, + "type": 8 + } + } + } + }, + { + "id": 8, + "type": { + "def": { + "primitive": "u8" + } + } + }, + { + "id": 9, + "type": { + "def": { + "primitive": "u128" + } + } + }, + { + "id": 10, + "type": { + "path": [ + "ink_primitives", + "types", + "Hash" + ], + "def": { + "composite": { + "fields": [ + { + "type": 7, + "typeName": "[u8; 32]" + } + ] + } + } + } + }, + { + "id": 11, + "type": { + "def": { + "primitive": "u64" + } + } + }, + { + "id": 12, + "type": { + "def": { + "primitive": "u32" + } + } + }, + { + "id": 13, + "type": { + "path": [ + "ink_env", + "types", + "NoChainExtension" + ], + "def": { + "variant": {} + } + } + } + ], + "storage": { + "root": { + "root_key": "0x00000000", + "layout": { + "struct": { + "name": "Testing", + "fields": [ + { + "name": "value", + "layout": { + "leaf": { + "key": "0x00000000", + "ty": 0 + } + } + } + ] + } + }, + "ty": 1 + } + }, + "spec": { + "constructors": [ + { + "label": "new", + "selector": "0x9bae9d5e", + "payable": false, + "args": [ + { + "label": "init_value", + "type": { + "type": 0, + "displayName": [ + "bool" + ] + } + } + ], + "returnType": { + "type": 2, + "displayName": [ + "ink_primitives", + "ConstructorResult" + ] + }, + "docs": [ + "Constructor that initializes the `bool` value to the given `init_value`." + ], + "default": false + }, + { + "label": "default", + "selector": "0xed4b9d1b", + "payable": false, + "args": [], + "returnType": { + "type": 2, + "displayName": [ + "ink_primitives", + "ConstructorResult" + ] + }, + "docs": [ + "Constructor that initializes the `bool` value to `false`.", + "", + "Constructors can delegate to other constructors." + ], + "default": false + } + ], + "messages": [ + { + "label": "flip", + "selector": "0x633aa551", + "mutates": true, + "payable": false, + "args": [], + "returnType": { + "type": 2, + "displayName": [ + "ink", + "MessageResult" + ] + }, + "docs": [ + " A message that can be called on instantiated contracts.", + " This one flips the value of the stored `bool` from `true`", + " to `false` and vice versa." + ], + "default": false + }, + { + "label": "get", + "selector": "0x2f865bd9", + "mutates": false, + "payable": false, + "args": [], + "returnType": { + "type": 5, + "displayName": [ + "ink", + "MessageResult" + ] + }, + "docs": [ + " Simply returns the current value of our `bool`." + ], + "default": false + } + ], + "events": [], + "docs": [], + "lang_error": { + "type": 4, + "displayName": [ + "ink", + "LangError" + ] + }, + "environment": { + "accountId": { + "type": 6, + "displayName": [ + "AccountId" + ] + }, + "balance": { + "type": 9, + "displayName": [ + "Balance" + ] + }, + "hash": { + "type": 10, + "displayName": [ + "Hash" + ] + }, + "timestamp": { + "type": 11, + "displayName": [ + "Timestamp" + ] + }, + "blockNumber": { + "type": 12, + "displayName": [ + "BlockNumber" + ] + }, + "chainExtension": { + "type": 13, + "displayName": [ + "ChainExtension" + ] + }, + "maxEventTopics": 4, + "staticBufferSize": 16384 + } + } +} \ No newline at end of file From 6c9aa7711d39ebb6059bae563b8ac846273e7d99 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Mon, 9 Sep 2024 17:36:46 +0200 Subject: [PATCH 008/211] test: unit contracts crate --- crates/pop-contracts/src/call/metadata.rs | 40 +++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/crates/pop-contracts/src/call/metadata.rs b/crates/pop-contracts/src/call/metadata.rs index 0de02cf90..cdc1071a2 100644 --- a/crates/pop-contracts/src/call/metadata.rs +++ b/crates/pop-contracts/src/call/metadata.rs @@ -53,3 +53,43 @@ fn process_args(message_params: &[MessageParamSpec]) -> Vec Result { + let temp_dir = tempfile::tempdir().expect("Could not create temp dir"); + let temp_contract_dir = temp_dir.path().join("testing"); + fs::create_dir(&temp_contract_dir)?; + create_smart_contract("testing", temp_contract_dir.as_path(), &Contract::Standard)?; + Ok(temp_dir) + } + // Function that mocks the build process generating the contract artifacts. + fn mock_build_process(temp_contract_dir: PathBuf) -> Result<(), Error> { + // Create a target directory + let target_contract_dir = temp_contract_dir.join("target"); + fs::create_dir(&target_contract_dir)?; + fs::create_dir(&target_contract_dir.join("ink"))?; + // Copy a mocked testing.contract file inside the target directory + let current_dir = env::current_dir().expect("Failed to get current directory"); + let contract_file = current_dir.join("../../tests/files/testing.contract"); + fs::copy(contract_file, &target_contract_dir.join("ink/testing.contract"))?; + Ok(()) + } + #[test] + fn get_messages_work() -> Result<()> { + let temp_dir = generate_smart_contract_test_environment()?; + mock_build_process(temp_dir.path().join("testing"))?; + let message = get_messages(&temp_dir.path().join("testing"))?; + assert_eq!(message.len(), 2); + assert_eq!(message[0].label, "flip"); + assert_eq!(message[0].docs, " A message that can be called on instantiated contracts. This one flips the value of the stored `bool` from `true` to `false` and vice versa."); + assert_eq!(message[1].label, "get"); + assert_eq!(message[1].docs, " Simply returns the current value of our `bool`."); + Ok(()) + } +} From cd4c20fa3517b32dde18f97a06142a8644181d84 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Mon, 9 Sep 2024 22:14:50 +0200 Subject: [PATCH 009/211] chore: format --- crates/pop-cli/src/commands/call/contract.rs | 6 ++++-- crates/pop-cli/src/commands/mod.rs | 5 ++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index efaff7db9..32ec5698d 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -346,7 +346,8 @@ mod tests { cli.verify() } - // This test only covers the interactive portion of the call contract command, without actually calling the contract. + // This test only covers the interactive portion of the call contract command, without actually + // calling the contract. #[tokio::test] async fn guide_user_to_query_contract_works() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; @@ -414,7 +415,8 @@ mod tests { cli.verify() } - // This test only covers the interactive portion of the call contract command, without actually calling the contract. + // This test only covers the interactive portion of the call contract command, without actually + // calling the contract. #[tokio::test] async fn guide_user_to_call_contract_works() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; diff --git a/crates/pop-cli/src/commands/mod.rs b/crates/pop-cli/src/commands/mod.rs index 6e8967449..4530ce4cb 100644 --- a/crates/pop-cli/src/commands/mod.rs +++ b/crates/pop-cli/src/commands/mod.rs @@ -98,12 +98,11 @@ impl Command { }, #[cfg(feature = "contract")] Self::Call(args) => match args.command { - call::Command::Contract(cmd) => { + call::Command::Contract(cmd) => Box::new(call::contract::CallContract { cli: &mut Cli, args: cmd }) .execute() .await - .map(|_| Value::Null) - }, + .map(|_| Value::Null), }, #[cfg(any(feature = "parachain", feature = "contract"))] Self::Up(args) => match args.command { From d59259c39210e8d3bbc6ffa5020d15f88bef0232 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 10 Sep 2024 11:00:55 +0200 Subject: [PATCH 010/211] test: refactor and improve test cases --- crates/pop-cli/src/cli.rs | 3 - crates/pop-cli/src/commands/call/contract.rs | 77 +++++++++---------- crates/pop-contracts/src/call/metadata.rs | 38 ++++----- crates/pop-contracts/src/call/mod.rs | 56 +++++++------- crates/pop-contracts/src/init_tests.rs | 29 +++++++ crates/pop-contracts/src/lib.rs | 2 + crates/pop-contracts/src/up.rs | 68 +++++++++------- .../tests/files/testing.contract | 1 + .../pop-contracts/tests}/files/testing.json | 33 +++++++- tests/files/testing.contract | 1 - 10 files changed, 182 insertions(+), 126 deletions(-) create mode 100644 crates/pop-contracts/src/init_tests.rs create mode 100644 crates/pop-contracts/tests/files/testing.contract rename {tests => crates/pop-contracts/tests}/files/testing.json (90%) delete mode 100644 tests/files/testing.contract diff --git a/crates/pop-cli/src/cli.rs b/crates/pop-cli/src/cli.rs index 65999288d..29598c7e7 100644 --- a/crates/pop-cli/src/cli.rs +++ b/crates/pop-cli/src/cli.rs @@ -424,12 +424,9 @@ pub(crate) mod tests { fn select(&mut self, prompt: impl Display) -> impl Select { let prompt = prompt.to_string(); - println!("prompt: {}", prompt); if let Some((expectation, _, collect, items_expectation, item)) = self.select_expectation.take() { - println!("expectation: {}", expectation); - println!("items_expectation: {:?}", items_expectation); assert_eq!(expectation, prompt, "prompt does not satisfy expectation"); return MockSelect { items_expectation, collect, items: vec![], item }; } diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 32ec5698d..e8312fe09 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -178,7 +178,6 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( .default_input("./") .interact()?; let contract_path = Path::new(&input_path); - println!("path: {:?}", contract_path); // Prompt for contract address. let contract_address: String = command @@ -287,36 +286,20 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( mod tests { use super::*; use crate::cli::MockCli; - use pop_contracts::{create_smart_contract, Contract}; - use std::{env, fs}; + use pop_contracts::{generate_smart_contract_test_environment, mock_build_process}; + use std::env; use url::Url; - fn generate_smart_contract_test_environment() -> Result { - let temp_dir = tempfile::tempdir().expect("Could not create temp dir"); - let temp_contract_dir = temp_dir.path().join("testing"); - fs::create_dir(&temp_contract_dir)?; - create_smart_contract("testing", temp_contract_dir.as_path(), &Contract::Standard)?; - Ok(temp_dir) - } - // Function that mocks the build process generating the contract artifacts. - fn mock_build_process(temp_contract_dir: PathBuf) -> Result<()> { - // Create a target directory - let target_contract_dir = temp_contract_dir.join("target"); - fs::create_dir(&target_contract_dir)?; - fs::create_dir(&target_contract_dir.join("ink"))?; - // Copy a mocked testing.contract and testing.json files inside the target directory - let current_dir = env::current_dir().expect("Failed to get current directory"); - let contract_file = current_dir.join("../../tests/files/testing.contract"); - fs::copy(contract_file, &target_contract_dir.join("ink/testing.contract"))?; - let metadata_file = current_dir.join("../../tests/files/testing.json"); - fs::copy(metadata_file, &target_contract_dir.join("ink/testing.json"))?; - Ok(()) - } - #[tokio::test] async fn call_contract_messages_are_ok() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; - mock_build_process(temp_dir.path().join("testing"))?; + let mut current_dir = env::current_dir().expect("Failed to get current directory"); + current_dir.pop(); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("pop-contracts/tests/files/testing.contract"), + current_dir.join("pop-contracts/tests/files/testing.json"), + )?; let mut cli = MockCli::new() .expect_intro(&"Call a contract") @@ -328,7 +311,7 @@ mod tests { cli: &mut cli, args: CallContractCommand { path: Some(temp_dir.path().join("testing")), - contract: Some("14BfwaXddoarT9Z6LcfXBmunutk6Ssmy1kBdWX6sy9KRskJz".to_string()), + contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), message: Some("get".to_string()), args: vec![].to_vec(), value: "0".to_string(), @@ -351,7 +334,13 @@ mod tests { #[tokio::test] async fn guide_user_to_query_contract_works() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; - mock_build_process(temp_dir.path().join("testing"))?; + let mut current_dir = env::current_dir().expect("Failed to get current directory"); + current_dir.pop(); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("pop-contracts/tests/files/testing.contract"), + current_dir.join("pop-contracts/tests/files/testing.json"), + )?; let items = vec![ ("flip".into(), " A message that can be called on instantiated contracts. This one flips the value of the stored `bool` from `true` to `false` and vice versa.".into()), @@ -374,7 +363,7 @@ mod tests { ) .expect_input( "Paste the on-chain contract address:", - "14BfwaXddoarT9Z6LcfXBmunutk6Ssmy1kBdWX6sy9KRskJz".into(), + "15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".into(), ) .expect_input( "Where is your project located?", @@ -400,7 +389,7 @@ mod tests { .await?; assert_eq!( call_config.contract, - Some("14BfwaXddoarT9Z6LcfXBmunutk6Ssmy1kBdWX6sy9KRskJz".to_string()) + Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) ); assert_eq!(call_config.message, Some("get".to_string())); assert_eq!(call_config.args.len(), 0); @@ -420,11 +409,18 @@ mod tests { #[tokio::test] async fn guide_user_to_call_contract_works() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; - mock_build_process(temp_dir.path().join("testing"))?; + let mut current_dir = env::current_dir().expect("Failed to get current directory"); + current_dir.pop(); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("pop-contracts/tests/files/testing.contract"), + current_dir.join("pop-contracts/tests/files/testing.json"), + )?; let items = vec![ ("flip".into(), " A message that can be called on instantiated contracts. This one flips the value of the stored `bool` from `true` to `false` and vice versa.".into()), ("get".into(), " Simply returns the current value of our `bool`.".into()), + ("specific_flip".into(), " A message for testing, flips the value of the stored `bool` with `new_value` and is payable".into()) ]; // The inputs are processed in reverse order. let mut cli = MockCli::new() @@ -434,18 +430,20 @@ mod tests { "Where is your contract deployed?", "wss://rpc1.paseo.popnetwork.xyz".into(), ) + .expect_input("Enter the proof size limit:", "".into()) // Only if call + .expect_input("Enter the gas limit:", "".into()) // Only if call + .expect_input("Value to transfer to the call:", "50".into()) // Only if payable + .expect_input("new_value", "true".into()) // Args for specific_flip .expect_select::( "Select the message to call:", Some(false), true, Some(items), - 0, // "flip" message + 2, // "specific_flip" message ) - .expect_input("Enter the proof size limit:", "".into()) - .expect_input("Enter the gas limit:", "".into()) .expect_input( "Paste the on-chain contract address:", - "14BfwaXddoarT9Z6LcfXBmunutk6Ssmy1kBdWX6sy9KRskJz".into(), + "15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".into(), ) .expect_input( "Where is your project located?", @@ -471,11 +469,12 @@ mod tests { .await?; assert_eq!( call_config.contract, - Some("14BfwaXddoarT9Z6LcfXBmunutk6Ssmy1kBdWX6sy9KRskJz".to_string()) + Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) ); - assert_eq!(call_config.message, Some("flip".to_string())); - assert_eq!(call_config.args.len(), 0); - assert_eq!(call_config.value, "0".to_string()); + assert_eq!(call_config.message, Some("specific_flip".to_string())); + assert_eq!(call_config.args.len(), 1); + assert_eq!(call_config.args[0], "true".to_string()); + assert_eq!(call_config.value, "50".to_string()); assert_eq!(call_config.gas_limit, None); assert_eq!(call_config.proof_size, None); assert_eq!(call_config.url.to_string(), "wss://rpc1.paseo.popnetwork.xyz/"); diff --git a/crates/pop-contracts/src/call/metadata.rs b/crates/pop-contracts/src/call/metadata.rs index cdc1071a2..a2b378e63 100644 --- a/crates/pop-contracts/src/call/metadata.rs +++ b/crates/pop-contracts/src/call/metadata.rs @@ -7,7 +7,6 @@ use scale_info::form::PortableForm; use std::path::Path; #[derive(Clone, PartialEq, Eq)] -// TODO: We are ignoring selector, return type for now. /// Describes a contract message. pub struct Message { /// The label of the message. @@ -56,40 +55,31 @@ fn process_args(message_params: &[MessageParamSpec]) -> Vec Result { - let temp_dir = tempfile::tempdir().expect("Could not create temp dir"); - let temp_contract_dir = temp_dir.path().join("testing"); - fs::create_dir(&temp_contract_dir)?; - create_smart_contract("testing", temp_contract_dir.as_path(), &Contract::Standard)?; - Ok(temp_dir) - } - // Function that mocks the build process generating the contract artifacts. - fn mock_build_process(temp_contract_dir: PathBuf) -> Result<(), Error> { - // Create a target directory - let target_contract_dir = temp_contract_dir.join("target"); - fs::create_dir(&target_contract_dir)?; - fs::create_dir(&target_contract_dir.join("ink"))?; - // Copy a mocked testing.contract file inside the target directory - let current_dir = env::current_dir().expect("Failed to get current directory"); - let contract_file = current_dir.join("../../tests/files/testing.contract"); - fs::copy(contract_file, &target_contract_dir.join("ink/testing.contract"))?; - Ok(()) - } #[test] fn get_messages_work() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; - mock_build_process(temp_dir.path().join("testing"))?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; let message = get_messages(&temp_dir.path().join("testing"))?; - assert_eq!(message.len(), 2); + assert_eq!(message.len(), 3); assert_eq!(message[0].label, "flip"); assert_eq!(message[0].docs, " A message that can be called on instantiated contracts. This one flips the value of the stored `bool` from `true` to `false` and vice versa."); assert_eq!(message[1].label, "get"); assert_eq!(message[1].docs, " Simply returns the current value of our `bool`."); + assert_eq!(message[2].label, "specific_flip"); + assert_eq!(message[2].docs, " A message for testing, flips the value of the stored `bool` with `new_value` and is payable"); + // assert parsed arguments + assert_eq!(message[2].args, vec!["new_value".to_string()]); Ok(()) } } diff --git a/crates/pop-contracts/src/call/mod.rs b/crates/pop-contracts/src/call/mod.rs index 591b202c3..fdac5fd02 100644 --- a/crates/pop-contracts/src/call/mod.rs +++ b/crates/pop-contracts/src/call/mod.rs @@ -160,40 +160,25 @@ pub async fn call_smart_contract( mod tests { use super::*; use crate::{ - contracts_node_generator, create_smart_contract, dry_run_gas_estimate_instantiate, - errors::Error, instantiate_smart_contract, run_contracts_node, set_up_deployment, Contract, - UpOpts, + contracts_node_generator, dry_run_gas_estimate_instantiate, errors::Error, + generate_smart_contract_test_environment, instantiate_smart_contract, mock_build_process, + run_contracts_node, set_up_deployment, UpOpts, }; use anyhow::Result; use sp_core::Bytes; - use std::{env, fs, process::Command}; + use std::{env, process::Command}; const CONTRACTS_NETWORK_URL: &str = "wss://rpc2.paseo.popnetwork.xyz"; - fn generate_smart_contract_test_environment() -> Result { - let temp_dir = tempfile::tempdir().expect("Could not create temp dir"); - let temp_contract_dir = temp_dir.path().join("testing"); - fs::create_dir(&temp_contract_dir)?; - create_smart_contract("testing", temp_contract_dir.as_path(), &Contract::Standard)?; - Ok(temp_dir) - } - // Function that mocks the build process generating the contract artifacts. - fn mock_build_process(temp_contract_dir: PathBuf) -> Result<(), Error> { - // Create a target directory - let target_contract_dir = temp_contract_dir.join("target"); - fs::create_dir(&target_contract_dir)?; - fs::create_dir(&target_contract_dir.join("ink"))?; - // Copy a mocked testing.contract file inside the target directory - let current_dir = env::current_dir().expect("Failed to get current directory"); - let contract_file = current_dir.join("../../tests/files/testing.contract"); - fs::copy(contract_file, &target_contract_dir.join("ink/testing.contract"))?; - Ok(()) - } - #[tokio::test] async fn test_set_up_call() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; - mock_build_process(temp_dir.path().join("testing"))?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; let call_opts = CallOpts { path: Some(temp_dir.path().join("testing")), @@ -259,7 +244,12 @@ mod tests { #[tokio::test] async fn test_dry_run_call_error_contract_not_deployed() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; - mock_build_process(temp_dir.path().join("testing"))?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; let call_opts = CallOpts { path: Some(temp_dir.path().join("testing")), @@ -281,7 +271,12 @@ mod tests { #[tokio::test] async fn test_dry_run_estimate_call_error_contract_not_deployed() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; - mock_build_process(temp_dir.path().join("testing"))?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; let call_opts = CallOpts { path: Some(temp_dir.path().join("testing")), @@ -307,7 +302,12 @@ mod tests { async fn call_works() -> Result<()> { const LOCALHOST_URL: &str = "ws://127.0.0.1:9944"; let temp_dir = generate_smart_contract_test_environment()?; - mock_build_process(temp_dir.path().join("testing"))?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; let cache = temp_dir.path().join(""); diff --git a/crates/pop-contracts/src/init_tests.rs b/crates/pop-contracts/src/init_tests.rs new file mode 100644 index 000000000..f1c24c7b9 --- /dev/null +++ b/crates/pop-contracts/src/init_tests.rs @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-3.0 + +use crate::{create_smart_contract, Contract}; +use anyhow::Result; +use std::{fs, path::PathBuf}; + +pub fn generate_smart_contract_test_environment() -> Result { + let temp_dir = tempfile::tempdir().expect("Could not create temp dir"); + let temp_contract_dir = temp_dir.path().join("testing"); + fs::create_dir(&temp_contract_dir)?; + create_smart_contract("testing", temp_contract_dir.as_path(), &Contract::Standard)?; + Ok(temp_dir) +} + +// Function that mocks the build process generating the contract artifacts. +pub fn mock_build_process( + temp_contract_dir: PathBuf, + contract_file: PathBuf, + metadata_file: PathBuf, +) -> Result<()> { + // Create a target directory + let target_contract_dir = temp_contract_dir.join("target"); + fs::create_dir(&target_contract_dir)?; + fs::create_dir(&target_contract_dir.join("ink"))?; + // Copy a mocked testing.contract and testing.json files inside the target directory + fs::copy(contract_file, &target_contract_dir.join("ink/testing.contract"))?; + fs::copy(metadata_file, &target_contract_dir.join("ink/testing.json"))?; + Ok(()) +} diff --git a/crates/pop-contracts/src/lib.rs b/crates/pop-contracts/src/lib.rs index c883c165c..4fd24bd4e 100644 --- a/crates/pop-contracts/src/lib.rs +++ b/crates/pop-contracts/src/lib.rs @@ -4,6 +4,7 @@ mod build; mod call; mod errors; +mod init_tests; mod new; mod node; mod templates; @@ -17,6 +18,7 @@ pub use call::{ metadata::{get_messages, Message}, set_up_call, CallOpts, }; +pub use init_tests::{generate_smart_contract_test_environment, mock_build_process}; pub use new::{create_smart_contract, is_valid_contract_name}; pub use node::{contracts_node_generator, is_chain_alive, run_contracts_node}; pub use templates::{Contract, ContractType}; diff --git a/crates/pop-contracts/src/up.rs b/crates/pop-contracts/src/up.rs index e387dbe15..a903c0b49 100644 --- a/crates/pop-contracts/src/up.rs +++ b/crates/pop-contracts/src/up.rs @@ -201,39 +201,24 @@ pub async fn upload_smart_contract( mod tests { use super::*; use crate::{ - contracts_node_generator, create_smart_contract, errors::Error, run_contracts_node, - templates::Contract, + contracts_node_generator, errors::Error, generate_smart_contract_test_environment, + mock_build_process, run_contracts_node, }; use anyhow::Result; - use std::{env, fs, process::Command}; + use std::{env, process::Command}; use url::Url; const CONTRACTS_NETWORK_URL: &str = "wss://rpc2.paseo.popnetwork.xyz"; - fn generate_smart_contract_test_environment() -> Result { - let temp_dir = tempfile::tempdir().expect("Could not create temp dir"); - let temp_contract_dir = temp_dir.path().join("testing"); - fs::create_dir(&temp_contract_dir)?; - create_smart_contract("testing", temp_contract_dir.as_path(), &Contract::Standard)?; - Ok(temp_dir) - } - // Function that mocks the build process generating the contract artifacts. - fn mock_build_process(temp_contract_dir: PathBuf) -> Result<(), Error> { - // Create a target directory - let target_contract_dir = temp_contract_dir.join("target"); - fs::create_dir(&target_contract_dir)?; - fs::create_dir(&target_contract_dir.join("ink"))?; - // Copy a mocked testing.contract file inside the target directory - let current_dir = env::current_dir().expect("Failed to get current directory"); - let contract_file = current_dir.join("../../tests/files/testing.contract"); - fs::copy(contract_file, &target_contract_dir.join("ink/testing.contract"))?; - Ok(()) - } - #[tokio::test] async fn set_up_deployment_works() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; - mock_build_process(temp_dir.path().join("testing"))?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; let up_opts = UpOpts { path: Some(temp_dir.path().join("testing")), constructor: "new".to_string(), @@ -252,7 +237,12 @@ mod tests { #[tokio::test] async fn set_up_upload_works() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; - mock_build_process(temp_dir.path().join("testing"))?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; let up_opts = UpOpts { path: Some(temp_dir.path().join("testing")), constructor: "new".to_string(), @@ -271,7 +261,12 @@ mod tests { #[tokio::test] async fn dry_run_gas_estimate_instantiate_works() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; - mock_build_process(temp_dir.path().join("testing"))?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; let up_opts = UpOpts { path: Some(temp_dir.path().join("testing")), constructor: "new".to_string(), @@ -293,7 +288,12 @@ mod tests { #[tokio::test] async fn dry_run_gas_estimate_instantiate_throw_custom_error() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; - mock_build_process(temp_dir.path().join("testing"))?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; let up_opts = UpOpts { path: Some(temp_dir.path().join("testing")), constructor: "new".to_string(), @@ -316,7 +316,12 @@ mod tests { #[tokio::test] async fn dry_run_upload_throw_custom_error() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; - mock_build_process(temp_dir.path().join("testing"))?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; let up_opts = UpOpts { path: Some(temp_dir.path().join("testing")), constructor: "new".to_string(), @@ -339,7 +344,12 @@ mod tests { async fn instantiate_and_upload() -> Result<()> { const LOCALHOST_URL: &str = "ws://127.0.0.1:9944"; let temp_dir = generate_smart_contract_test_environment()?; - mock_build_process(temp_dir.path().join("testing"))?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; let cache = temp_dir.path().join(""); diff --git a/crates/pop-contracts/tests/files/testing.contract b/crates/pop-contracts/tests/files/testing.contract new file mode 100644 index 000000000..5fb54f3ce --- /dev/null +++ b/crates/pop-contracts/tests/files/testing.contract @@ -0,0 +1 @@ +{"source":{"hash":"0x80776e58b218850d7d86447b2edea78d827ed0ed2499ff3a92b7ea10e4f95eb5","language":"ink! 5.0.0","compiler":"rustc 1.78.0","wasm":"0x0061736d01000000012b0860027f7f0060037f7f7f017f60000060047f7f7f7f017f60037f7f7f0060017f006000017f60017f017f027406057365616c310b6765745f73746f726167650003057365616c3005696e7075740000057365616c320b7365745f73746f726167650003057365616c300b7365616c5f72657475726e0004057365616c301176616c75655f7472616e73666572726564000003656e76066d656d6f72790201021003100f0101010105040006070002050002020616037f01418080040b7f00418080050b7f00418080050b0711020463616c6c0012066465706c6f7900130aa80c0f2b01017f037f2002200346047f200005200020036a200120036a2d00003a0000200341016a21030c010b0b0b6f01017f0240200020014d04402000210303402002450d02200320012d00003a0000200341016a2103200141016a2101200241016b21020c000b000b200041016b2103200141016b210103402002450d01200220036a200120026a2d00003a0000200241016b21020c000b000b20000b2501017f037f2002200346047f200005200020036a20013a0000200341016a21030c010b0b0b3f01027f0340200245044041000f0b200241016b210220012d0000210320002d00002104200141016a2101200041016a210020032004460d000b200420036b0b2601017f230041106b220124002001410036020c20002001410c6a4104100a200141106a24000b4801027f024002402000280208220320026a22042003490d00200420002802044b0d00200420036b2002470d01200028020020036a2001200210051a200020043602080f0b000b000b2601017f230041106b22022400200220003a000f20012002410f6a4101100a200241106a24000b6102027f027e230041206b22002400200041106a22014200370300200042003703082000411036021c200041086a2000411c6a1004200028021c41114f0440000b2001290300210220002903082103200041206a2400410541042002200384501b0b3f01017f2000280204220145044041020f0b2000200141016b36020420002000280200220041016a3602004101410220002d000022004101461b410020001b0b3c01027f027f200145044041808004210141010c010b410121024180800441013a000041818004210141020b2103200120023a0000200020031011000b12004180800441003b0100410041021011000b8a0101057f230041106b22012400200142808001370208200141808004360204200141046a22041009024020012802082205200128020c2202490d00200128020421032001410036020c2001200520026b3602082001200220036a36020420002004100b200128020c220020012802084b0d00200320022001280204200010021a200141106a24000f0b000b0d0020004180800420011003000b990401067f230041106b2200240020004180800136020441808004200041046a10010240024020002802042202418180014f0d000240024020024104490d002000418480043602042000200241046b360208418380042d00002101418280042d00002104418180042d00002103418080042d00002202412f470440200241ec00470440200241e300470d02410221022003413a46200441a5014671200141d10046710d030c020b2003410f472004411d4772200141f70147720d01200041046a100d220241ff01714102460d010c020b41032102200341860146200441db004671200141d90146710d010b41014101100e000b200042808001370208200041808004360204200041046a2204100920002802082205200028020c2201490d00200028020421032000200520016b220536020420032001200120036a2201200410002000280204220320054b720d0020002003360208200020013602042004100d220141ff01714102460d0020002802080d000240024002404102200241026b41ff01712200200041024f1b41016b0e020100020b2002410171101041004100100e000b100c41ff01714105470d01230041106b220024002000418080043602044180800441003a00002000428080818010370208200141ff0171410047200041046a100b200028020c2200418180014f0440000b410020001011000b100c41ff01714105460d010b000b200141ff017145101041004100100e000be50101057f230041106b2200240002400240100c41ff01714105470d0020004180800136020c418080042000410c6a1001200028020c2201418180014f0d0020014104490d012000418480043602042000200141046b360208418380042d00002101418280042d00002102418180042d000021030240418080042d0000220441ed014704402004419b0147200341ae0147722002419d0147200141de004772720d03200041046a100d220041ff01714102470d010c030b200341cb00462002419d0146712001411b4671450d0241001010100f000b20001010100f000b000b41014101100e000b","build_info":{"rust_toolchain":"stable-aarch64-apple-darwin","cargo_contract_version":"5.0.0-alpha","build_mode":"Release","wasm_opt_settings":{"optimization_passes":"Z","keep_debug_symbols":false}}},"contract":{"name":"testing","version":"0.1.0","authors":["[your_name] <[your_email]>"]},"image":null,"version":5,"types":[{"id":0,"type":{"def":{"primitive":"bool"}}},{"id":1,"type":{"path":["testing","testing","Testing"],"def":{"composite":{"fields":[{"name":"value","type":0,"typeName":",>>::Type"}]}}}},{"id":2,"type":{"path":["Result"],"params":[{"name":"T","type":3},{"name":"E","type":4}],"def":{"variant":{"variants":[{"name":"Ok","fields":[{"type":3}],"index":0},{"name":"Err","fields":[{"type":4}],"index":1}]}}}},{"id":3,"type":{"def":{"tuple":[]}}},{"id":4,"type":{"path":["ink_primitives","LangError"],"def":{"variant":{"variants":[{"name":"CouldNotReadInput","index":1}]}}}},{"id":5,"type":{"path":["Result"],"params":[{"name":"T","type":0},{"name":"E","type":4}],"def":{"variant":{"variants":[{"name":"Ok","fields":[{"type":0}],"index":0},{"name":"Err","fields":[{"type":4}],"index":1}]}}}},{"id":6,"type":{"path":["ink_primitives","types","AccountId"],"def":{"composite":{"fields":[{"type":7,"typeName":"[u8; 32]"}]}}}},{"id":7,"type":{"def":{"array":{"len":32,"type":8}}}},{"id":8,"type":{"def":{"primitive":"u8"}}},{"id":9,"type":{"def":{"primitive":"u128"}}},{"id":10,"type":{"path":["ink_primitives","types","Hash"],"def":{"composite":{"fields":[{"type":7,"typeName":"[u8; 32]"}]}}}},{"id":11,"type":{"def":{"primitive":"u64"}}},{"id":12,"type":{"def":{"primitive":"u32"}}},{"id":13,"type":{"path":["ink_env","types","NoChainExtension"],"def":{"variant":{}}}}],"storage":{"root":{"root_key":"0x00000000","layout":{"struct":{"name":"Testing","fields":[{"name":"value","layout":{"leaf":{"key":"0x00000000","ty":0}}}]}},"ty":1}},"spec":{"constructors":[{"label":"new","selector":"0x9bae9d5e","payable":false,"args":[{"label":"init_value","type":{"type":0,"displayName":["bool"]}}],"returnType":{"type":2,"displayName":["ink_primitives","ConstructorResult"]},"docs":["Constructor that initializes the `bool` value to the given `init_value`."],"default":false},{"label":"default","selector":"0xed4b9d1b","payable":false,"args":[],"returnType":{"type":2,"displayName":["ink_primitives","ConstructorResult"]},"docs":["Constructor that initializes the `bool` value to `false`.","","Constructors can delegate to other constructors."],"default":false}],"messages":[{"label":"flip","selector":"0x633aa551","mutates":true,"payable":false,"args":[],"returnType":{"type":2,"displayName":["ink","MessageResult"]},"docs":[" A message that can be called on instantiated contracts."," This one flips the value of the stored `bool` from `true`"," to `false` and vice versa."],"default":false},{"label":"get","selector":"0x2f865bd9","mutates":false,"payable":false,"args":[],"returnType":{"type":5,"displayName":["ink","MessageResult"]},"docs":[" Simply returns the current value of our `bool`."],"default":false},{"label":"specific_flip","selector":"0x6c0f1df7","mutates":true,"payable":true,"args":[{"label":"new_value","type":{"type":0,"displayName":["bool"]}}],"returnType":{"type":2,"displayName":["ink","MessageResult"]},"docs":[" A message for testing, flips the value of the stored `bool` with `new_value`"," and is payable"],"default":false}],"events":[],"docs":[],"lang_error":{"type":4,"displayName":["ink","LangError"]},"environment":{"accountId":{"type":6,"displayName":["AccountId"]},"balance":{"type":9,"displayName":["Balance"]},"hash":{"type":10,"displayName":["Hash"]},"timestamp":{"type":11,"displayName":["Timestamp"]},"blockNumber":{"type":12,"displayName":["BlockNumber"]},"chainExtension":{"type":13,"displayName":["ChainExtension"]},"maxEventTopics":4,"staticBufferSize":16384}}} \ No newline at end of file diff --git a/tests/files/testing.json b/crates/pop-contracts/tests/files/testing.json similarity index 90% rename from tests/files/testing.json rename to crates/pop-contracts/tests/files/testing.json index 78cff4b6b..ed230c98f 100644 --- a/tests/files/testing.json +++ b/crates/pop-contracts/tests/files/testing.json @@ -1,11 +1,11 @@ { "source": { - "hash": "0x29972a7ea06ca802e5d857dfb7cb6455220679f4e9314a533840b9865ccbb799", + "hash": "0x80776e58b218850d7d86447b2edea78d827ed0ed2499ff3a92b7ea10e4f95eb5", "language": "ink! 5.0.0", "compiler": "rustc 1.78.0", "build_info": { "rust_toolchain": "stable-aarch64-apple-darwin", - "cargo_contract_version": "4.1.1", + "cargo_contract_version": "5.0.0-alpha", "build_mode": "Release", "wasm_opt_settings": { "optimization_passes": "Z", @@ -369,6 +369,35 @@ " Simply returns the current value of our `bool`." ], "default": false + }, + { + "label": "specific_flip", + "selector": "0x6c0f1df7", + "mutates": true, + "payable": true, + "args": [ + { + "label": "new_value", + "type": { + "type": 0, + "displayName": [ + "bool" + ] + } + } + ], + "returnType": { + "type": 2, + "displayName": [ + "ink", + "MessageResult" + ] + }, + "docs": [ + " A message for testing, flips the value of the stored `bool` with `new_value`", + " and is payable" + ], + "default": false } ], "events": [], diff --git a/tests/files/testing.contract b/tests/files/testing.contract deleted file mode 100644 index 8701e6560..000000000 --- a/tests/files/testing.contract +++ /dev/null @@ -1 +0,0 @@ -{"source":{"hash":"0xb15348075722f8ac92352b8fcfd6fa3506e2a3f430adadcc79fa73cf23bfe9e7","language":"ink! 5.0.0","compiler":"rustc 1.78.0","wasm":"0x0061736d0100000001400b60037f7f7f017f60027f7f017f60027f7f0060037f7f7f0060017f0060047f7f7f7f017f60047f7f7f7f0060000060057f7f7f7f7f006000017f60017f017f028a0107057365616c310b6765745f73746f726167650005057365616c3005696e7075740002057365616c320b7365745f73746f726167650005057365616c300d64656275675f6d6573736167650001057365616c300b7365616c5f72657475726e0003057365616c301176616c75655f7472616e73666572726564000203656e76066d656d6f7279020102100335340000000006030403020901020a00010702040302020704070306020301010004000101010104020101080506050802010103000104050170010e0e0616037f01418080040b7f0041c092050b7f0041ba92050b0711020463616c6c001b066465706c6f79001d0913010041010b0d103528392a362529252627232c0ae63b342b01017f037f2002200346047f200005200020036a200120036a2d00003a0000200341016a21030c010b0b0b6f01017f0240200020014d04402000210303402002450d02200320012d00003a0000200341016a2103200141016a2101200241016b21020c000b000b200041016b2103200141016b210103402002450d01200220036a200120026a2d00003a0000200241016b21020c000b000b20000b2501017f037f2002200346047f200005200020036a20013a0000200341016a21030c010b0b0b3f01027f0340200245044041000f0b200241016b210220012d0000210320002d00002104200141016a2101200041016a210020032004460d000b200420036b0b2300200120034b04402001200341f49004100b000b20002001360204200020023602000b6801017f230041306b2203240020032001360204200320003602002003412c6a41033602002003410236020c200341a88a0436020820034202370214200341033602242003200341206a3602102003200341046a36022820032003360220200341086a20021011000b2601017f230041106b220124002001410036020c20002001410c6a4104100d200141106a24000b920101037f02402000280208220420026a220320044f04402003200028020422054b0d01200028020020046a200320046b2001200241d08f041033200020033602080f0b230041206b22002400200041013602042000420037020c200041988e043602082000412b36021c200041f286043602182000200041186a360200200041b08f041011000b2003200541c08f04100b000b2601017f230041106b22022400200220003a000f20012002410f6a4101100d200241106a24000b6d02037f027e230041206b22002400200041106a22014200370300200042003703082000411036021c200041086a2000411c6a1005200028021c220241114f04402002411041f49004100b000b2001290300210320002903082104200041206a2400410541042003200484501b0b810101017f230041306b220224002002410136020c200241988d043602082002420137021420024102360224200220002d00004102742200418092046a28020036022c20022000419492046a2802003602282002200241206a3602102002200241286a36022020012802142001280218200241086a10242100200241306a240020000b3c01017f230041206b22022400200241013b011c2002200136021820022000360214200241b88704360210200241988e0436020c2002410c6a102b000b4901017f230041106b22012400200141003a000f027f20002001410f6a410110134504404101410220012d000f22004101461b410020001b0c010b41020b2100200141106a240020000b3d01027f2000280204220320024922044504402001200220002802002201200241f0910410332000200320026b3602042000200120026a3602000b20040b11002001410036000020002001410410130b5301027f230041106b22002400200042808001370208200041ba9204360204200041046a220141001019200141001019200028020c2200418180014f044020004180800141f08004100b000b41002000101a000b5b01027f230041106b22022400200242808001370208200241ba9204360204200241046a22032001047f20034101101941010541000b1019200228020c2201418180014f044020014180800141f08004100b000b20002001101a000ba70102057f017e230041306b2201240020014100360220200142808001370228200141ba9204360224200141246a2202100c20012001290224370218200141106a200141186a2203200128022c10182001280214210420012802102105200129021821062001410036022c2001200637022420002002100e20012001290224370218200141086a2003200128022c1018200520042001280208200128020c10021a200141306a24000b7401027f230041206b220324002002200128020422044b04402003410136020c200341a48e0436020820034200370214200341988e04360210200341086a41f08f041011000b2001200420026b36020420012001280200220120026a3602002000200236020420002001360200200341206a24000b940101027f20002802082202200028020422034904402000200241016a360208200028020020026a20013a00000f0b230041306b2200240020002003360204200020023602002000412c6a41033602002000410236020c20004188880436020820004202370214200041033602242000200041206a360210200020003602282000200041046a360220200041086a41e08f041011000b0d00200041ba920420011004000bef0401077f230041406a220024000240024002400240100f41ff0171410546044020004180800136022841ba9204200041286a22011001200041106a200028022841ba920441808001100a2000200029031037022820012000411c6a10140d0320002d001f210120002d001e210220002d001d2103024020002d001c2204412f470440200441e300470d05410121042003413a47200241a5014772200141d1004772450d010c050b41002104200341860147200241db004772200141d90147720d040b2000410036022420004280800137022c200041ba9204360228200041286a2202100c2000200029022837021c200041086a2000411c6a20002802301018200028020c210320002802082105200028021c21012000200028022022063602282005200320012002100021022000200028022820012006100a02400240024020020e0400040401040b200028020021012000200028020436022c20002001360228200041286a1012220141ff01714102470440200028022c450d020b2000410136022c200041bc82043602280c060b2000410136022c2000418c82043602280c050b20040d02230041106b22002400200042808001370208200041ba9204360204200041046a220241001019200141ff01714100472002100e200028020c2200418180014f044020004180800141f08004100b000b41002000101a000b200041043a0028200041286a101c000b2000410136022c2000419c810436022820004200370234200041988e04360230200041286a41a481041011000b200141ff0171451017410041001016000b410141011016000b20004200370234200041988e04360230200041286a41e481041011000b4501017f230041206b2201240020014101360204200141988d043602002001420137020c2001410136021c200120003602182001200141186a360208200141e481041011000be90101057f230041206b220024000240100f220141ff0171410546044020004180800136021441ba9204200041146a22011001200041086a200028021441ba920441808001100a2000200029030837021420012000411c6a10140d0120002d001f210120002d001e210220002d001d210320002d001c2204419b01470440200341cb00462002419d0146712001411b467145200441ed0147720d02410010171015000b200341ae01472002419d014772200141de0047720d01200041146a1012220041ff01714102460d01200010171015000b200020013a0014200041146a101c000b410141011016000b6001027f230041106b2203240020022000280200200028020822046b4b0440200341086a200020042002101f2003280208200328020c1020200028020821040b200028020420046a2001200210061a2000200220046a360208200341106a24000b9a0301077f230041206b220624000240200220036a22032002490d00410121044108200128020022024101742205200320032005491b2203200341084d1b2203417f73411f76210502402002450440410021040c010b2006200236021c200620012802043602140b20062004360218200641086a210820032102200641146a2104230041106b22072400027f027f024020050440200241004e0d01410121054100210241040c030b2008410036020441010c010b027f2004280204044020042802082209450440200741086a20052002102120072802082104200728020c0c020b2004280200210a02402005200210222204450440410021040c010b2004200a200910061a0b20020c010b20072005200210212007280200210420072802040b210920082004200520041b3602042009200220041b21022004450b210541080b20086a200236020020082005360200200741106a2400200628020c21042006280208450440200120033602002001200436020441818080807821040c010b200628021021030b2000200336020420002004360200200641206a24000bcb0100024020004181808080784704402000450d01230041306b220024002000200136020c20004102360214200041a085043602102000420137021c2000410336022c2000200041286a36021820002000410c6a360228230041206b22012400200141003b011c200141b085043602182001200041106a360214200141b88704360210200141988e0436020c2001410c6a102b000b0f0b230041206b220024002000410136020c20004188830436020820004200370214200041988e04360210200041086a418084041011000b200041a892042d00001a200120021022210120002002360204200020013602000bc50101017f027f41ac92042d0000044041b092042802000c010b3f00210241b0920441c0920536020041ac920441013a000041b49204200241107436020041c092050b21020240027f4100200020026a41016b410020006b71220020016a22022000490d001a41b492042802002002490440200141ffff036a220241107640002200417f460d022000411074220020024180807c716a22022000490d0241b4920420023602004100200020016a22022000490d011a0b41b09204200236020020000b0f0b41000b0c00200041dc8204200110240b850401077f230041406a22032400200341033a003c2003412036022c200341003602382003200136023420032000360230200341003602242003410036021c027f0240024020022802102201450440200228020c22004103742105200041ffffffff01712106200228020421082002280200210720022802082101034020042005460d02200420076a220041046a28020022020440200328023020002802002002200328023428020c1100000d040b200441086a21042001280200210020012802042102200141086a210120002003411c6a2002110100450d000b0c020b200228021422044105742100200441ffffff3f712106200228020c2109200228020821052002280204210820022802002207210403402000450d01200441046a28020022020440200328023020042802002002200328023428020c1100000d030b2003200128021036022c200320012d001c3a003c20032001280218360238200341106a2005200141086a10372003200329031037021c200341086a20052001103720032003290308370224200441086a2104200041206b210020012802142102200141206a2101200520024103746a22022802002003411c6a2002280204110100450d000b0c010b200620084904402003280230200720064103746a22002802002000280204200328023428020c1100000d010b41000c010b41010b2101200341406b240020010b0300010b0c00200020012002101e41000bb20201047f230041106b220224000240027f0240024020014180014f04402002410036020c2001418010490d012001418080044f0d0220022001410c7641e001723a000c20022001410676413f71418001723a000d4102210341030c030b200028020822032000280200460440230041106b22042400200441086a200020034101101f2004280208200428020c1020200441106a2400200028020821030b2000200341016a360208200028020420036a20013a00000c030b2002200141067641c001723a000c4101210341020c010b20022001410676413f71418001723a000e20022001410c76413f71418001723a000d2002200141127641077141f001723a000c4103210341040b210420032002410c6a2205722001413f71418001723a0000200020052004101e0b200241106a240041000bdb05020b7f027e230041406a220324004127210202402000350200220d4290ce00540440200d210e0c010b0340200341196a20026a220041046b200d4290ce0080220e42f0b1037e200d7ca7220441ffff037141e4006e220641017441ac88046a2f00003b0000200041026b2006419c7f6c20046a41ffff037141017441ac88046a2f00003b0000200241046b2102200d42ffc1d72f562100200e210d20000d000b0b200ea7220041e3004b0440200241026b2202200341196a6a200ea7220441ffff037141e4006e2200419c7f6c20046a41ffff037141017441ac88046a2f00003b00000b02402000410a4f0440200241026b2202200341196a6a200041017441ac88046a2f00003b00000c010b200241016b2202200341196a6a20004130723a00000b200128021c22054101712207412720026b22066a2100410021042005410471044041988e04210441988e0441988e04102d20006a21000b412b418080c40020071b2107200341196a20026a2108024020012802004504404101210220012802142200200128021822012007200410300d01200020082006200128020c11000021020c010b2000200128020422094f04404101210220012802142200200128021822012007200410300d01200020082006200128020c11000021020c010b200541087104402001280210210b2001413036021020012d0020210c41012102200141013a0020200128021422052001280218220a2007200410300d01200341106a2001200920006b4101103120032802102200418080c400460d0120032802142104200520082006200a28020c1100000d01200020042005200a10320d012001200c3a00202001200b360210410021020c010b41012102200341086a2001200920006b4101103120032802082205418080c400460d00200328020c210920012802142200200128021822012007200410300d00200020082006200128020c1100000d002005200920002001103221020b200341406b240020020b1800200128021441d482044105200128021828020c1100000b0e0020002802001a03400c000b000baa0201017f230041406a220124002001200036020c20014102360214200141b08e043602102001420137021c2001410436022c2001200141286a36021820012001410c6a360228200141003602382001428080808010370230200141306a200141106a10234504402001280234210020012802382101024041b892042d000045044041b992042d00000d010b200020011003410947044041b8920441013a00000b41b9920441013a00000b000b230041406a220024002000413336020c200041c08504360208200041c4820436021420002001413f6a3602102000413c6a41063602002000410236021c2000419c880436021820004202370224200041023602342000200041306a3602202000200041106a3602382000200041086a360230200041186a41e086041011000b2200200042eeb4d39ded9bae93907f370308200042d4ce8f88d3c5f6dba47f3703000ba10301067f230041106b220224000240200120006b220141104f04402000200041036a417c71220520006b2200102e2005200120006b2200417c716a2000410371102e6a21042000410276210303402003450d0220022005200341c0012003200341c0014f1b41ac8b04102f200228020c21032002280208210520022002280200200228020422002000417c7141888d04102f024020022802042200450440410021010c010b2002280200220620004102746a21074100210103404100210003402001200020066a2802002201417f734107762001410676724181828408716a2101200041046a22004110470d000b200641106a22062007470d000b0b200141087641ff81fc0771200141ff81fc07716a418180046c41107620046a2104200228020c2201450d000b2002280208210020014102742103410021010340200120002802002201417f734107762001410676724181828408716a2101200041046a2100200341046b22030d000b200141087641ff81fc0771200141ff81fc07716a418180046c41107620046a21040c010b20002001102e21040b200241106a240020040b2c01017f200104400340200220002c000041bf7f4a6a2102200041016a2100200141016b22010d000b0b20020b6b01017f230041206b22052400200220034904402005410136020c200541a48e0436020820054200370214200541988e04360210200541086a20041011000b20002003360204200020013602002000200220036b36020c2000200120034102746a360208200541206a24000b39000240027f2002418080c40047044041012000200220012802101101000d011a0b20030d0141000b0f0b200020034100200128020c1100000b990101027f024002400240024020012d0020220441016b0e03010200030b200341ff01710d00410021040c020b20022104410021020c010b20024101762104200241016a41017621020b200441016a210420012802102103200128021821052001280214210102400340200441016b2204450d01200120032005280210110100450d000b418080c40021030b20002002360204200020033602000b3201017f027f0340200120012004460d011a200441016a2104200220002003280210110100450d000b200441016b0b2001490b78002001200346044020002002200110061a0f0b230041306b2200240020002003360204200020013602002000412c6a41033602002000410336020c200041fc8b0436020820004202370214200041033602242000200041206a360210200020003602282000200041046a360220200041086a20041011000bf60101067f2000027f418080c400200128020022022001280204460d001a2001200241016a2205360200024020022d0000220341187441187541004e0d002001200241026a220536020020022d0001413f7121042003411f712106200341df014d0440200641067420047221030c010b2001200241036a220536020020022d0002413f712004410674722104200341f00149044020042006410c747221030c010b2001200241046a2205360200418080c4002006411274418080f0007120022d0003413f71200441067472722203418080c400460d011a0b200120012802082207200520026b6a36020820030b360204200020073602000baa0301067f230041306b22022400200028020421042000280200210302400240200128020022062001280208220072044002402000450d00200128020c21002002410036022c200220033602242002200320046a360228200041016a21000340200041016b22000440200241186a200241246a1034200228021c418080c400470d010c020b0b200241106a200241246a10342002280214418080c400460d000240024020022802102205450d00200420054d04404100210020042005460d010c020b41002100200320056a2c00004140480d010b200321000b2005200420001b21042000200320001b21030b2006450440200128021420032004200128021828020c11000021000c030b200128020422002003200320046a102d22054d0d01200241086a2001200020056b410010314101210020022802082205418080c400460d02200228020c210620012802142207200320042001280218220128020c1100000d022005200620072001103221000c020b200128021420032004200128021828020c11000021000c010b200128021420032004200128021828020c11000021000b200241306a240020000b140020002802002001200028020428020c1101000b5501027f0240027f02400240200228020041016b0e020103000b200241046a0c010b200120022802044103746a22012802044105470d0120012802000b2802002104410121030b20002004360204200020033602000b0a0020002001200210240be00201067f230041406a22022400200028020021054101210002402001280214220441c88704410c2001280218220628020c22011100000d00200528020c21032002413c6a4103360200200241346a410336020020024103360214200241a087043602102002420337021c20022003410c6a3602382002200341086a3602302002410236022c200220033602282002200241286a220736021820042006200241106a10380d00200528020822030440200441d48704410220011100000d01200241386a200341106a290200370300200241306a200341086a29020037030020022003290200370328200420062007103821000c010b200220052802002203200528020428020c11020041002100200229030042e4dec78590d085de7d520d00200229030842c1f7f9e8cc93b2d141520d0041012100200441d48704410220011100000d00200420032802002003280204200111000021000b200241406b240020000b0bb0120100418080040ba7122f55736572732f616c65786265616e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f696e6b5f656e762d352e302e302f7372632f656e67696e652f6f6e5f636861696e2f696d706c732e727300000001006f0000001a01000032000000656e636f756e746572656420756e6578706563746564206572726f72800001001c000000000001006f000000e3000000170000002f55736572732f616c65786265616e2f446f63756d656e74732f726f6775652f74657374696e672f6c69622e72730000b40001002e000000060000000500000073746f7261676520656e7472792077617320656d70747900f400010017000000636f756c64206e6f742070726f7065726c79206465636f64652073746f7261676520656e747279001401010027000000070000000000000001000000080000004572726f72000000090000000c000000040000000a0000000b0000000c0000006361706163697479206f766572666c6f7700000074010100110000002f55736572732f616c65786265616e2f2e7275737475702f746f6f6c636861696e732f737461626c652d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f7261775f7665632e7273900101007000000019000000050000002f55736572732f616c65786265616e2f2e7275737475702f746f6f6c636861696e732f737461626c652d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f616c6c6f632e72736d656d6f727920616c6c6f636174696f6e206f6620206279746573206661696c65647e02010015000000930201000d000000100201006e000000a50100000d0000006120666f726d617474696e6720747261697420696d706c656d656e746174696f6e2072657475726e656420616e206572726f722f55736572732f616c65786265616e2f2e7275737475702f746f6f6c636861696e732f737461626c652d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f666d742e727300f30201006c0000007902000020000000293a63616c6c656420604f7074696f6e3a3a756e77726170282960206f6e206120604e6f6e65602076616c75650000001807010000000000710301000100000071030100010000000700000000000000010000000d00000070616e69636b6564206174203a0a696e646578206f7574206f6620626f756e64733a20746865206c656e20697320206275742074686520696e64657820697320d603010020000000f6030100120000003a200000180701000000000018040100020000003030303130323033303430353036303730383039313031313132313331343135313631373138313932303231323232333234323532363237323832393330333133323333333433353336333733383339343034313432343334343435343634373438343935303531353235333534353535363537353835393630363136323633363436353636363736383639373037313732373337343735373637373738373938303831383238333834383538363837383838393930393139323933393439353936393739383939206f7574206f662072616e676520666f7220736c696365206f66206c656e6774682072616e676520656e6420696e6465782000001605010010000000f4040100220000002f55736572732f616c65786265616e2f2e7275737475702f746f6f6c636861696e732f737461626c652d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f736c6963652f697465722e727300003805010072000000ce05000025000000736f7572636520736c696365206c656e67746820282920646f6573206e6f74206d617463682064657374696e6174696f6e20736c696365206c656e6774682028bc05010015000000d10501002b00000070030100010000002f55736572732f616c65786265616e2f2e7275737475702f746f6f6c636861696e732f737461626c652d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f7374722f636f756e742e727300000014060100710000004f000000320000001807010000000000756e61626c6520746f206465636f64652073656c6563746f72656e636f756e746572656420756e6b6e6f776e2073656c6563746f72756e61626c6520746f206465636f646520696e707574636f756c64206e6f74207265616420696e7075747061696420616e20756e70617961626c65206d6573736167656d6964203e206c656e00000018070100090000000a00000018070100000000002c070100010000002f55736572732f616c65786265616e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f696e6b5f656e762d352e302e302f7372632f656e67696e652f6f6e5f636861696e2f6275666665722e727340070100700000005c0000003b00000040070100700000005c0000001400000040070100700000005d0000000e00000040070100700000006800000009000000400701007000000090000000210000002f55736572732f616c65786265616e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f70616c6c65742d636f6e7472616374732d756170692d6e6578742d362e302e332f7372632f686f73742e727300000000080100710000002d000000170000002f55736572732f616c65786265616e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f7061726974792d7363616c652d636f6465632d332e362e31322f7372632f636f6465632e727300840801006b000000770000000e000000190000001c000000160000001400000019000000a0060100b9060100d5060100eb060100ff0601","build_info":{"build_mode":"Debug","cargo_contract_version":"4.1.1","rust_toolchain":"stable-aarch64-apple-darwin","wasm_opt_settings":{"keep_debug_symbols":false,"optimization_passes":"Z"}}},"contract":{"name":"testing","version":"0.1.0","authors":["[your_name] <[your_email]>"]},"image":null,"spec":{"constructors":[{"args":[{"label":"init_value","type":{"displayName":["bool"],"type":0}}],"default":false,"docs":["Constructor that initializes the `bool` value to the given `init_value`."],"label":"new","payable":false,"returnType":{"displayName":["ink_primitives","ConstructorResult"],"type":2},"selector":"0x9bae9d5e"},{"args":[],"default":false,"docs":["Constructor that initializes the `bool` value to `false`.","","Constructors can delegate to other constructors."],"label":"default","payable":false,"returnType":{"displayName":["ink_primitives","ConstructorResult"],"type":2},"selector":"0xed4b9d1b"}],"docs":[],"environment":{"accountId":{"displayName":["AccountId"],"type":6},"balance":{"displayName":["Balance"],"type":9},"blockNumber":{"displayName":["BlockNumber"],"type":12},"chainExtension":{"displayName":["ChainExtension"],"type":13},"hash":{"displayName":["Hash"],"type":10},"maxEventTopics":4,"staticBufferSize":16384,"timestamp":{"displayName":["Timestamp"],"type":11}},"events":[],"lang_error":{"displayName":["ink","LangError"],"type":4},"messages":[{"args":[],"default":false,"docs":[" A message that can be called on instantiated contracts."," This one flips the value of the stored `bool` from `true`"," to `false` and vice versa."],"label":"flip","mutates":true,"payable":false,"returnType":{"displayName":["ink","MessageResult"],"type":2},"selector":"0x633aa551"},{"args":[],"default":false,"docs":[" Simply returns the current value of our `bool`."],"label":"get","mutates":false,"payable":false,"returnType":{"displayName":["ink","MessageResult"],"type":5},"selector":"0x2f865bd9"}]},"storage":{"root":{"layout":{"struct":{"fields":[{"layout":{"leaf":{"key":"0x00000000","ty":0}},"name":"value"}],"name":"Testing"}},"root_key":"0x00000000","ty":1}},"types":[{"id":0,"type":{"def":{"primitive":"bool"}}},{"id":1,"type":{"def":{"composite":{"fields":[{"name":"value","type":0,"typeName":",>>::Type"}]}},"path":["testing","testing","Testing"]}},{"id":2,"type":{"def":{"variant":{"variants":[{"fields":[{"type":3}],"index":0,"name":"Ok"},{"fields":[{"type":4}],"index":1,"name":"Err"}]}},"params":[{"name":"T","type":3},{"name":"E","type":4}],"path":["Result"]}},{"id":3,"type":{"def":{"tuple":[]}}},{"id":4,"type":{"def":{"variant":{"variants":[{"index":1,"name":"CouldNotReadInput"}]}},"path":["ink_primitives","LangError"]}},{"id":5,"type":{"def":{"variant":{"variants":[{"fields":[{"type":0}],"index":0,"name":"Ok"},{"fields":[{"type":4}],"index":1,"name":"Err"}]}},"params":[{"name":"T","type":0},{"name":"E","type":4}],"path":["Result"]}},{"id":6,"type":{"def":{"composite":{"fields":[{"type":7,"typeName":"[u8; 32]"}]}},"path":["ink_primitives","types","AccountId"]}},{"id":7,"type":{"def":{"array":{"len":32,"type":8}}}},{"id":8,"type":{"def":{"primitive":"u8"}}},{"id":9,"type":{"def":{"primitive":"u128"}}},{"id":10,"type":{"def":{"composite":{"fields":[{"type":7,"typeName":"[u8; 32]"}]}},"path":["ink_primitives","types","Hash"]}},{"id":11,"type":{"def":{"primitive":"u64"}}},{"id":12,"type":{"def":{"primitive":"u32"}}},{"id":13,"type":{"def":{"variant":{}},"path":["ink_env","types","NoChainExtension"]}}],"version":5} \ No newline at end of file From c4221921bfced361edde5632904eeadedb261e12 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 10 Sep 2024 11:13:36 +0200 Subject: [PATCH 011/211] fix: fix todos and refactor --- crates/pop-cli/src/commands/call/contract.rs | 49 ++++++++++++++++++-- crates/pop-contracts/src/call/metadata.rs | 6 ++- 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index e8312fe09..f6908f9bd 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -74,10 +74,13 @@ impl<'a, CLI: Cli> CallContract<'a, CLI> { let contract = call_config .contract .expect("contract can not be none as fallback above is interactive input; qed"); - // TODO: Can be nill pop call contract --contract - let message = call_config - .message - .expect("message can not be none as fallback above is interactive input; qed"); + let message = match call_config.message { + Some(m) => m, + None => { + self.cli.outro_cancel("Please specify the message to call.")?; + return Ok(()); + }, + }; let call_exec = set_up_call(CallOpts { path: call_config.path, @@ -484,4 +487,42 @@ mod tests { cli.verify() } + + #[tokio::test] + async fn call_contract_messages_fails_no_message() -> Result<()> { + let temp_dir = generate_smart_contract_test_environment()?; + let mut current_dir = env::current_dir().expect("Failed to get current directory"); + current_dir.pop(); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("pop-contracts/tests/files/testing.contract"), + current_dir.join("pop-contracts/tests/files/testing.json"), + )?; + + let mut cli = MockCli::new() + .expect_intro(&"Call a contract") + .expect_outro_cancel("Please specify the message to call."); + + // Contract deployed on Pop Network testnet, test get + Box::new(CallContract { + cli: &mut cli, + args: CallContractCommand { + path: Some(temp_dir.path().join("testing")), + contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), + message: None, + args: vec![].to_vec(), + value: "0".to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, + suri: "//Alice".to_string(), + dry_run: false, + execute: false, + }, + }) + .execute() + .await?; + + cli.verify() + } } diff --git a/crates/pop-contracts/src/call/metadata.rs b/crates/pop-contracts/src/call/metadata.rs index a2b378e63..43c6997be 100644 --- a/crates/pop-contracts/src/call/metadata.rs +++ b/crates/pop-contracts/src/call/metadata.rs @@ -23,6 +23,10 @@ pub struct Message { pub default: bool, } +/// Extracts a list of smart contract messages parsing the metadata file. +/// +/// # Arguments +/// * `path` - Location path of the project. pub fn get_messages(path: &Path) -> Result, Error> { let cargo_toml_path = match path.ends_with("Cargo.toml") { true => path.to_path_buf(), @@ -44,7 +48,7 @@ pub fn get_messages(path: &Path) -> Result, Error> { } Ok(messages) } -//TODO: We are ignoring the type of the argument. +// Parse the message parameters into a vector of argument labels. fn process_args(message_params: &[MessageParamSpec]) -> Vec { let mut args: Vec = Vec::new(); for arg in message_params { From 22dd4f6b0e9c0afb8ecd4c9310a7504988b76d0f Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 10 Sep 2024 11:50:01 +0200 Subject: [PATCH 012/211] test: fix unit test --- crates/pop-cli/src/commands/call/contract.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index f6908f9bd..7ba1e0dcc 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -348,6 +348,7 @@ mod tests { let items = vec![ ("flip".into(), " A message that can be called on instantiated contracts. This one flips the value of the stored `bool` from `true` to `false` and vice versa.".into()), ("get".into(), " Simply returns the current value of our `bool`.".into()), + ("specific_flip".into(), " A message for testing, flips the value of the stored `bool` with `new_value` and is payable".into()) ]; // The inputs are processed in reverse order. let mut cli = MockCli::new() From c8fedef18f4e23bb27931991ae96ce07d8a9b5ad Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Wed, 11 Sep 2024 18:51:30 +0200 Subject: [PATCH 013/211] feat: parse types of parameters and display it to the user in the placeholder --- crates/pop-cli/src/commands/call/contract.rs | 10 +++++++-- crates/pop-contracts/src/call/metadata.rs | 23 +++++++++++++++----- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 7ba1e0dcc..dc13d1d50 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -212,7 +212,13 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( let mut contract_args = Vec::new(); for arg in &message.args { - contract_args.push(command.cli.input(arg).placeholder(arg).interact()?); + contract_args.push( + command + .cli + .input(format!("Enter the value for the parameter: {}", arg.label)) + .placeholder(&format!("Type required: {}", &arg.type_name)) + .interact()?, + ); } let mut value = "0".to_string(); if message.payable { @@ -437,7 +443,7 @@ mod tests { .expect_input("Enter the proof size limit:", "".into()) // Only if call .expect_input("Enter the gas limit:", "".into()) // Only if call .expect_input("Value to transfer to the call:", "50".into()) // Only if payable - .expect_input("new_value", "true".into()) // Args for specific_flip + .expect_input("Enter the value for the parameter: new_value", "true".into()) // Args for specific_flip .expect_select::( "Select the message to call:", Some(false), diff --git a/crates/pop-contracts/src/call/metadata.rs b/crates/pop-contracts/src/call/metadata.rs index 43c6997be..82e7589b7 100644 --- a/crates/pop-contracts/src/call/metadata.rs +++ b/crates/pop-contracts/src/call/metadata.rs @@ -6,6 +6,14 @@ use contract_transcode::ink_metadata::MessageParamSpec; use scale_info::form::PortableForm; use std::path::Path; +#[derive(Clone, PartialEq, Eq)] +/// Describes a contract message. +pub struct Param { + /// The label of the parameter. + pub label: String, + /// The type name of the parameter. + pub type_name: String, +} #[derive(Clone, PartialEq, Eq)] /// Describes a contract message. pub struct Message { @@ -16,7 +24,7 @@ pub struct Message { /// If the message accepts any `value` from the caller. pub payable: bool, /// The parameters of the deployment handler. - pub args: Vec, + pub args: Vec, /// The message documentation. pub docs: String, /// If the message is the default for off-chain consumers (e.g UIs). @@ -49,10 +57,13 @@ pub fn get_messages(path: &Path) -> Result, Error> { Ok(messages) } // Parse the message parameters into a vector of argument labels. -fn process_args(message_params: &[MessageParamSpec]) -> Vec { - let mut args: Vec = Vec::new(); +fn process_args(message_params: &[MessageParamSpec]) -> Vec { + let mut args: Vec = Vec::new(); for arg in message_params { - args.push(arg.label().to_string()); + args.push(Param { + label: arg.label().to_string(), + type_name: arg.ty().display_name().to_string(), + }); } args } @@ -83,7 +94,9 @@ mod tests { assert_eq!(message[2].label, "specific_flip"); assert_eq!(message[2].docs, " A message for testing, flips the value of the stored `bool` with `new_value` and is payable"); // assert parsed arguments - assert_eq!(message[2].args, vec!["new_value".to_string()]); + assert_eq!(message[2].args.len(), 1); + assert_eq!(message[2].args[0].label, "new_value".to_string()); + assert_eq!(message[2].args[0].type_name, "bool".to_string()); Ok(()) } } From b0b3573db230dbca387bf52cdb20fd2d1cef85fd Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 12 Sep 2024 11:30:06 +0200 Subject: [PATCH 014/211] refactor: error handling for pop call --- crates/pop-cli/src/commands/call/contract.rs | 29 ++++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index dc13d1d50..748614f37 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -67,7 +67,13 @@ impl<'a, CLI: Cli> CallContract<'a, CLI> { self.cli.intro("Call a contract")?; let call_config = if self.args.contract.is_none() { - guide_user_to_call_contract(&mut self).await? + match guide_user_to_call_contract(&mut self).await { + Ok(config) => config, + Err(e) => { + self.cli.outro_cancel(format!("{}", e.to_string()))?; + return Ok(()); + }, + } } else { self.args.clone() }; @@ -82,7 +88,7 @@ impl<'a, CLI: Cli> CallContract<'a, CLI> { }, }; - let call_exec = set_up_call(CallOpts { + let call_exec = match set_up_call(CallOpts { path: call_config.path, contract, message, @@ -94,7 +100,14 @@ impl<'a, CLI: Cli> CallContract<'a, CLI> { suri: call_config.suri, execute: call_config.execute, }) - .await?; + .await + { + Ok(call_exec) => call_exec, + Err(e) => { + self.cli.outro_cancel(format!("{}", e.root_cause().to_string()))?; + return Ok(()); + }, + }; if call_config.dry_run { let spinner = cliclack::spinner(); @@ -197,8 +210,10 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( let messages = match get_messages(contract_path) { Ok(messages) => messages, Err(e) => { - command.cli.outro_cancel("Unable to fetch contract metadata.")?; - return Err(anyhow!(format!("{}", e.to_string()))); + return Err(anyhow!(format!( + "Unable to fetch contract metadata: {}", + e.to_string().replace("Anyhow error: ", "") + ))); }, }; let message = { @@ -227,6 +242,10 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( .input("Value to transfer to the call:") .placeholder("0") .default_input("0") + .validate(|input: &String| match input.parse::() { + Ok(_) => Ok(()), + Err(_) => Err("Invalid value."), + }) .interact()?; } let mut gas_limit: Option = None; From 8e60da84e3244e4720b92a22412927e1720b6ff9 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 12 Sep 2024 12:57:55 +0200 Subject: [PATCH 015/211] refactor: display call to be executed after guide and reorder --- crates/pop-cli/src/commands/call/contract.rs | 149 +++++++++++++++---- 1 file changed, 117 insertions(+), 32 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 748614f37..743dc3fc9 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -53,6 +53,40 @@ pub struct CallContractCommand { #[clap(long, conflicts_with = "execute")] dry_run: bool, } +impl CallContractCommand { + fn display(&self) -> String { + let mut full_message = format!("pop call contract"); + if let Some(path) = &self.path { + full_message.push_str(&format!(" --path {}", path.display().to_string())); + } + if let Some(contract) = &self.contract { + full_message.push_str(&format!(" --contract {}", contract)); + } + if let Some(message) = &self.message { + full_message.push_str(&format!(" --message {}", message)); + } + if !self.args.is_empty() { + full_message.push_str(&format!(" --args {}", self.args.join(" "))); + } + if self.value != "0" { + full_message.push_str(&format!(" --value {}", self.value)); + } + if let Some(gas_limit) = self.gas_limit { + full_message.push_str(&format!(" --gas {}", gas_limit)); + } + if let Some(proof_size) = self.proof_size { + full_message.push_str(&format!(" --proof_size {}", proof_size)); + } + full_message.push_str(&format!(" --url {} --suri {}", self.url, self.suri)); + if self.execute { + full_message.push_str(" --execute"); + } + if self.dry_run { + full_message.push_str(" --dry_run"); + } + full_message + } +} pub(crate) struct CallContract<'a, CLI: Cli> { /// The cli to be used. @@ -65,7 +99,6 @@ impl<'a, CLI: Cli> CallContract<'a, CLI> { /// Executes the command. pub(crate) async fn execute(mut self: Box) -> Result<()> { self.cli.intro("Call a contract")?; - let call_config = if self.args.contract.is_none() { match guide_user_to_call_contract(&mut self).await { Ok(config) => config, @@ -195,6 +228,24 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( .interact()?; let contract_path = Path::new(&input_path); + let messages = match get_messages(contract_path) { + Ok(messages) => messages, + Err(e) => { + return Err(anyhow!(format!( + "Unable to fetch contract metadata: {}", + e.to_string().replace("Anyhow error: ", "") + ))); + }, + }; + + // Prompt for contract location. + let url: String = command + .cli + .input("Where is your contract deployed?") + .placeholder("ws://localhost:9944") + .default_input("ws://localhost:9944") + .interact()?; + // Prompt for contract address. let contract_address: String = command .cli @@ -207,15 +258,6 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( .default_input("5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") .interact()?; - let messages = match get_messages(contract_path) { - Ok(messages) => messages, - Err(e) => { - return Err(anyhow!(format!( - "Unable to fetch contract metadata: {}", - e.to_string().replace("Anyhow error: ", "") - ))); - }, - }; let message = { let mut prompt = command.cli.select("Select the message to call:"); for select_message in messages { @@ -270,14 +312,6 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( proof_size = proof_size_input.parse::().ok(); // If blank or bad input, estimate it. } - // Prompt for contract location. - let url: String = command - .cli - .input("Where is your contract deployed?") - .placeholder("ws://localhost:9944") - .default_input("ws://localhost:9944") - .interact()?; - // Who is calling the contract. let suri: String = command .cli @@ -294,8 +328,7 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( .initial_value(true) .interact()?; } - - Ok(CallContractCommand { + let call_command = CallContractCommand { path: Some(contract_path.to_path_buf()), contract: Some(contract_address), message: Some(message.label.clone()), @@ -307,7 +340,9 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( suri, execute: message.mutates, dry_run: !is_call_confirmed, - }) + }; + command.cli.info(call_command.display())?; + Ok(call_command) } #[cfg(test)] @@ -379,10 +414,6 @@ mod tests { let mut cli = MockCli::new() .expect_intro(&"Call a contract") .expect_input("Signer calling the contract:", "//Alice".into()) - .expect_input( - "Where is your contract deployed?", - "wss://rpc1.paseo.popnetwork.xyz".into(), - ) .expect_select::( "Select the message to call:", Some(false), @@ -394,10 +425,17 @@ mod tests { "Paste the on-chain contract address:", "15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".into(), ) + .expect_input( + "Where is your contract deployed?", + "wss://rpc1.paseo.popnetwork.xyz".into(), + ) .expect_input( "Where is your project located?", temp_dir.path().join("testing").display().to_string(), - ); + ).expect_info(format!( + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message get --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice", + temp_dir.path().join("testing").display().to_string(), + )); let call_config = guide_user_to_call_contract(&mut CallContract { cli: &mut cli, @@ -429,6 +467,10 @@ mod tests { assert_eq!(call_config.suri, "//Alice"); assert!(!call_config.execute); assert!(!call_config.dry_run); + assert_eq!(call_config.display(), format!( + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message get --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice", + temp_dir.path().join("testing").display().to_string(), + )); cli.verify() } @@ -455,10 +497,6 @@ mod tests { let mut cli = MockCli::new() .expect_intro(&"Call a contract") .expect_input("Signer calling the contract:", "//Alice".into()) - .expect_input( - "Where is your contract deployed?", - "wss://rpc1.paseo.popnetwork.xyz".into(), - ) .expect_input("Enter the proof size limit:", "".into()) // Only if call .expect_input("Enter the gas limit:", "".into()) // Only if call .expect_input("Value to transfer to the call:", "50".into()) // Only if payable @@ -474,10 +512,17 @@ mod tests { "Paste the on-chain contract address:", "15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".into(), ) + .expect_input( + "Where is your contract deployed?", + "wss://rpc1.paseo.popnetwork.xyz".into(), + ) .expect_input( "Where is your project located?", temp_dir.path().join("testing").display().to_string(), - ); + ).expect_info(format!( + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + temp_dir.path().join("testing").display().to_string(), + )); let call_config = guide_user_to_call_contract(&mut CallContract { cli: &mut cli, @@ -510,6 +555,10 @@ mod tests { assert_eq!(call_config.suri, "//Alice"); assert!(call_config.execute); assert!(!call_config.dry_run); + assert_eq!(call_config.display(), format!( + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + temp_dir.path().join("testing").display().to_string(), + )); cli.verify() } @@ -529,7 +578,6 @@ mod tests { .expect_intro(&"Call a contract") .expect_outro_cancel("Please specify the message to call."); - // Contract deployed on Pop Network testnet, test get Box::new(CallContract { cli: &mut cli, args: CallContractCommand { @@ -551,4 +599,41 @@ mod tests { cli.verify() } + + #[tokio::test] + async fn call_contract_messages_fails_parse_metadata() -> Result<()> { + let temp_dir = generate_smart_contract_test_environment()?; + let mut current_dir = env::current_dir().expect("Failed to get current directory"); + current_dir.pop(); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("pop-contracts/tests/files/testing.contract"), + current_dir.join("pop-contracts/tests/files/testing.json"), + )?; + + let mut cli = MockCli::new() + .expect_intro(&"Call a contract") + .expect_outro_cancel("Unable to fetch contract metadata: No 'ink' dependency found"); + + Box::new(CallContract { + cli: &mut cli, + args: CallContractCommand { + path: Some(temp_dir.path().join("wrong-testing")), + contract: None, + message: None, + args: vec![].to_vec(), + value: "0".to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse("ws://localhost:9944")?, + suri: "//Alice".to_string(), + dry_run: false, + execute: false, + }, + }) + .execute() + .await?; + + cli.verify() + } } From 08f24548f2e758a04698212809b50dfbe788e5ba Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 12 Sep 2024 21:55:11 +0200 Subject: [PATCH 016/211] refactor: when repeat call use same contract values and dont clean screen --- crates/pop-cli/src/commands/call/contract.rs | 259 ++++++++++--------- crates/pop-cli/src/commands/mod.rs | 7 +- 2 files changed, 137 insertions(+), 129 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 743dc3fc9..0a7b71f73 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -8,7 +8,7 @@ use pop_contracts::{ set_up_call, CallOpts, }; use sp_weights::Weight; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; #[derive(Args, Clone)] pub struct CallContractCommand { @@ -97,10 +97,10 @@ pub(crate) struct CallContract<'a, CLI: Cli> { impl<'a, CLI: Cli> CallContract<'a, CLI> { /// Executes the command. - pub(crate) async fn execute(mut self: Box) -> Result<()> { + pub(crate) async fn execute(mut self: Self) -> Result<()> { self.cli.intro("Call a contract")?; let call_config = if self.args.contract.is_none() { - match guide_user_to_call_contract(&mut self).await { + match guide_user_to_call_contract(&mut self, None, None, None).await { Ok(config) => config, Err(e) => { self.cli.outro_cancel(format!("{}", e.to_string()))?; @@ -110,19 +110,29 @@ impl<'a, CLI: Cli> CallContract<'a, CLI> { } else { self.args.clone() }; + match self.execute_call(call_config.clone()).await { + Ok(_) => Ok(()), + Err(e) => { + self.cli.outro_cancel(format!("{}", e.to_string()))?; + return Ok(()); + }, + } + } + /// Executes the call. + async fn execute_call(&mut self, call_config: CallContractCommand) -> Result<()> { let contract = call_config .contract + .clone() .expect("contract can not be none as fallback above is interactive input; qed"); let message = match call_config.message { Some(m) => m, None => { - self.cli.outro_cancel("Please specify the message to call.")?; - return Ok(()); + return Err(anyhow!("Please specify the message to call.")); }, }; let call_exec = match set_up_call(CallOpts { - path: call_config.path, + path: call_config.path.clone(), contract, message, args: call_config.args, @@ -137,8 +147,7 @@ impl<'a, CLI: Cli> CallContract<'a, CLI> { { Ok(call_exec) => call_exec, Err(e) => { - self.cli.outro_cancel(format!("{}", e.root_cause().to_string()))?; - return Ok(()); + return Err(anyhow!(format!("{}", e.root_cause().to_string()))); }, }; @@ -181,8 +190,7 @@ impl<'a, CLI: Cli> CallContract<'a, CLI> { }, Err(e) => { spinner.error(format!("{e}")); - self.cli.outro_cancel("Call failed.")?; - return Ok(()); + return Err(anyhow!("Call failed.")); }, }; } @@ -198,37 +206,52 @@ impl<'a, CLI: Cli> CallContract<'a, CLI> { if self.args.contract.is_none() { let another_call: bool = self .cli - .confirm("Do you want to do another call?") + .confirm("Do you want to do another call using the existing smart contract?") .initial_value(false) .interact()?; if another_call { - Box::pin(self.execute()).await?; + // Remove only the prompt asking for another call. + console::Term::stderr().clear_last_lines(2)?; + let new_call_config = guide_user_to_call_contract( + self, + call_config.path, + Some(call_config.url), + call_config.contract, + ) + .await?; + Box::pin(self.execute_call(new_call_config)).await?; } else { self.cli.outro("Call completed successfully!")?; } } else { self.cli.outro("Call completed successfully!")?; } - Ok(()) + return Ok(()); } } /// Guide the user to call the contract. async fn guide_user_to_call_contract<'a, CLI: Cli>( command: &mut CallContract<'a, CLI>, + contract_path: Option, + url: Option, + contract_address: Option, ) -> anyhow::Result { - command.cli.intro("Call a contract")?; - - // Prompt for location of your contract. - let input_path: String = command - .cli - .input("Where is your project located?") - .placeholder("./") - .default_input("./") - .interact()?; - let contract_path = Path::new(&input_path); - - let messages = match get_messages(contract_path) { + let contract_path: PathBuf = match contract_path { + Some(path) => path, + None => { + // Prompt for path. + let input_path: String = command + .cli + .input("Where is your project located?") + .placeholder("./") + .default_input("./") + .interact()?; + PathBuf::from(input_path) + }, + }; + // Parse the contract metadata provided. If there error, do not prompt for more. + let messages = match get_messages(&contract_path) { Ok(messages) => messages, Err(e) => { return Err(anyhow!(format!( @@ -237,32 +260,45 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( ))); }, }; - - // Prompt for contract location. - let url: String = command - .cli - .input("Where is your contract deployed?") - .placeholder("ws://localhost:9944") - .default_input("ws://localhost:9944") - .interact()?; - - // Prompt for contract address. - let contract_address: String = command - .cli - .input("Paste the on-chain contract address:") - .placeholder("e.g. 5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") - .validate(|input: &String| match parse_account(input) { - Ok(_) => Ok(()), - Err(_) => Err("Invalid address."), - }) - .default_input("5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") - .interact()?; + let url: url::Url = match url { + Some(url) => url, + None => { + // Prompt for url. + let url: String = command + .cli + .input("Where is your contract deployed?") + .placeholder("ws://localhost:9944") + .default_input("ws://localhost:9944") + .interact()?; + url::Url::parse(&url)? + }, + }; + let contract_address: String = match contract_address { + Some(contract_address) => contract_address, + None => { + // Prompt for contract address. + let contract_address: String = command + .cli + .input("Paste the on-chain contract address:") + .placeholder("e.g. 5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") + .validate(|input: &String| match parse_account(input) { + Ok(_) => Ok(()), + Err(_) => Err("Invalid address."), + }) + .default_input("5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") + .interact()?; + contract_address + }, + }; let message = { let mut prompt = command.cli.select("Select the message to call:"); for select_message in messages { - prompt = - prompt.item(select_message.clone(), &select_message.label, &select_message.docs); + prompt = prompt.item( + select_message.clone(), + format!("{}\n", &select_message.label), + &select_message.docs, + ); } prompt.interact()? }; @@ -329,14 +365,14 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( .interact()?; } let call_command = CallContractCommand { - path: Some(contract_path.to_path_buf()), + path: Some(contract_path), contract: Some(contract_address), message: Some(message.label.clone()), args: contract_args, value, gas_limit, proof_size, - url: url::Url::parse(&url)?, + url, suri, execute: message.mutates, dry_run: !is_call_confirmed, @@ -406,13 +442,12 @@ mod tests { )?; let items = vec![ - ("flip".into(), " A message that can be called on instantiated contracts. This one flips the value of the stored `bool` from `true` to `false` and vice versa.".into()), - ("get".into(), " Simply returns the current value of our `bool`.".into()), - ("specific_flip".into(), " A message for testing, flips the value of the stored `bool` with `new_value` and is payable".into()) + ("flip\n".into(), " A message that can be called on instantiated contracts. This one flips the value of the stored `bool` from `true` to `false` and vice versa.".into()), + ("get\n".into(), " Simply returns the current value of our `bool`.".into()), + ("specific_flip\n".into(), " A message for testing, flips the value of the stored `bool` with `new_value` and is payable".into()) ]; // The inputs are processed in reverse order. let mut cli = MockCli::new() - .expect_intro(&"Call a contract") .expect_input("Signer calling the contract:", "//Alice".into()) .expect_select::( "Select the message to call:", @@ -437,22 +472,27 @@ mod tests { temp_dir.path().join("testing").display().to_string(), )); - let call_config = guide_user_to_call_contract(&mut CallContract { - cli: &mut cli, - args: CallContractCommand { - path: Some(temp_dir.path().join("testing")), - contract: None, - message: None, - args: vec![].to_vec(), - value: "0".to_string(), - gas_limit: None, - proof_size: None, - url: Url::parse("ws://localhost:9944")?, - suri: "//Alice".to_string(), - dry_run: false, - execute: false, + let call_config = guide_user_to_call_contract( + &mut CallContract { + cli: &mut cli, + args: CallContractCommand { + path: Some(temp_dir.path().join("testing")), + contract: None, + message: None, + args: vec![].to_vec(), + value: "0".to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse("ws://localhost:9944")?, + suri: "//Alice".to_string(), + dry_run: false, + execute: false, + }, }, - }) + None, + None, + None, + ) .await?; assert_eq!( call_config.contract, @@ -489,13 +529,12 @@ mod tests { )?; let items = vec![ - ("flip".into(), " A message that can be called on instantiated contracts. This one flips the value of the stored `bool` from `true` to `false` and vice versa.".into()), - ("get".into(), " Simply returns the current value of our `bool`.".into()), - ("specific_flip".into(), " A message for testing, flips the value of the stored `bool` with `new_value` and is payable".into()) + ("flip\n".into(), " A message that can be called on instantiated contracts. This one flips the value of the stored `bool` from `true` to `false` and vice versa.".into()), + ("get\n".into(), " Simply returns the current value of our `bool`.".into()), + ("specific_flip\n".into(), " A message for testing, flips the value of the stored `bool` with `new_value` and is payable".into()) ]; // The inputs are processed in reverse order. let mut cli = MockCli::new() - .expect_intro(&"Call a contract") .expect_input("Signer calling the contract:", "//Alice".into()) .expect_input("Enter the proof size limit:", "".into()) // Only if call .expect_input("Enter the gas limit:", "".into()) // Only if call @@ -524,22 +563,27 @@ mod tests { temp_dir.path().join("testing").display().to_string(), )); - let call_config = guide_user_to_call_contract(&mut CallContract { - cli: &mut cli, - args: CallContractCommand { - path: Some(temp_dir.path().join("testing")), - contract: None, - message: None, - args: vec![].to_vec(), - value: "0".to_string(), - gas_limit: None, - proof_size: None, - url: Url::parse("ws://localhost:9944")?, - suri: "//Alice".to_string(), - dry_run: false, - execute: false, + let call_config = guide_user_to_call_contract( + &mut CallContract { + cli: &mut cli, + args: CallContractCommand { + path: Some(temp_dir.path().join("testing")), + contract: None, + message: None, + args: vec![].to_vec(), + value: "0".to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse("ws://localhost:9944")?, + suri: "//Alice".to_string(), + dry_run: false, + execute: false, + }, }, - }) + None, + None, + None, + ) .await?; assert_eq!( call_config.contract, @@ -578,7 +622,7 @@ mod tests { .expect_intro(&"Call a contract") .expect_outro_cancel("Please specify the message to call."); - Box::new(CallContract { + CallContract { cli: &mut cli, args: CallContractCommand { path: Some(temp_dir.path().join("testing")), @@ -593,44 +637,7 @@ mod tests { dry_run: false, execute: false, }, - }) - .execute() - .await?; - - cli.verify() - } - - #[tokio::test] - async fn call_contract_messages_fails_parse_metadata() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; - let mut current_dir = env::current_dir().expect("Failed to get current directory"); - current_dir.pop(); - mock_build_process( - temp_dir.path().join("testing"), - current_dir.join("pop-contracts/tests/files/testing.contract"), - current_dir.join("pop-contracts/tests/files/testing.json"), - )?; - - let mut cli = MockCli::new() - .expect_intro(&"Call a contract") - .expect_outro_cancel("Unable to fetch contract metadata: No 'ink' dependency found"); - - Box::new(CallContract { - cli: &mut cli, - args: CallContractCommand { - path: Some(temp_dir.path().join("wrong-testing")), - contract: None, - message: None, - args: vec![].to_vec(), - value: "0".to_string(), - gas_limit: None, - proof_size: None, - url: Url::parse("ws://localhost:9944")?, - suri: "//Alice".to_string(), - dry_run: false, - execute: false, - }, - }) + } .execute() .await?; diff --git a/crates/pop-cli/src/commands/mod.rs b/crates/pop-cli/src/commands/mod.rs index 4530ce4cb..a7797845b 100644 --- a/crates/pop-cli/src/commands/mod.rs +++ b/crates/pop-cli/src/commands/mod.rs @@ -98,11 +98,12 @@ impl Command { }, #[cfg(feature = "contract")] Self::Call(args) => match args.command { - call::Command::Contract(cmd) => - Box::new(call::contract::CallContract { cli: &mut Cli, args: cmd }) + call::Command::Contract(cmd) => { + call::contract::CallContract { cli: &mut Cli, args: cmd } .execute() .await - .map(|_| Value::Null), + .map(|_| Value::Null) + }, }, #[cfg(any(feature = "parachain", feature = "contract"))] Self::Up(args) => match args.command { From 68848beedcb01416515174fdbf352e7359843c8d Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 13 Sep 2024 11:23:06 +0200 Subject: [PATCH 017/211] test: add dry-run test --- crates/pop-cli/src/commands/call/contract.rs | 49 ++++++++++++++++++-- crates/pop-cli/src/commands/mod.rs | 5 +- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 0a7b71f73..9782af737 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -24,12 +24,12 @@ pub struct CallContractCommand { /// The constructor arguments, encoded as strings. #[clap(long, num_args = 0..)] args: Vec, - /// Transfers an initial balance to the contract. + /// The value to be transferred as part of the call. #[clap(name = "value", long, default_value = "0")] value: String, /// Maximum amount of gas to be used for this command. /// If not specified it will perform a dry-run to estimate the gas consumed for the - /// instantiation. + /// call. #[clap(name = "gas", long)] gas_limit: Option, /// Maximum proof size for this command. @@ -374,7 +374,7 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( proof_size, url, suri, - execute: message.mutates, + execute: if is_call_confirmed { message.mutates } else { false }, dry_run: !is_call_confirmed, }; command.cli.info(call_command.display())?; @@ -406,7 +406,7 @@ mod tests { .expect_outro("Call completed successfully!"); // Contract deployed on Pop Network testnet, test get - Box::new(CallContract { + CallContract { cli: &mut cli, args: CallContractCommand { path: Some(temp_dir.path().join("testing")), @@ -421,13 +421,52 @@ mod tests { dry_run: false, execute: false, }, - }) + } .execute() .await?; cli.verify() } + #[tokio::test] + async fn call_contract_dry_run_works() -> Result<()> { + let temp_dir = generate_smart_contract_test_environment()?; + let mut current_dir = env::current_dir().expect("Failed to get current directory"); + current_dir.pop(); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("pop-contracts/tests/files/testing.contract"), + current_dir.join("pop-contracts/tests/files/testing.json"), + )?; + + let mut cli = MockCli::new() + .expect_intro(&"Call a contract") + .expect_warning("Your call has not been executed.") + .expect_info("Gas limit: Weight { ref_time: 100, proof_size: 10 }"); + + let call_config = CallContractCommand { + path: Some(temp_dir.path().join("testing")), + contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), + message: Some("flip".to_string()), + args: vec![].to_vec(), + value: "0".to_string(), + gas_limit: Some(100), + proof_size: Some(10), + url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, + suri: "//Alice".to_string(), + dry_run: true, + execute: false, + }; + assert_eq!(call_config.display(), format!( + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message flip --gas 100 --proof_size 10 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --dry_run", + temp_dir.path().join("testing").display().to_string(), + )); + // Contract deployed on Pop Network testnet, test dry-run + CallContract { cli: &mut cli, args: call_config }.execute().await?; + + cli.verify() + } + // This test only covers the interactive portion of the call contract command, without actually // calling the contract. #[tokio::test] diff --git a/crates/pop-cli/src/commands/mod.rs b/crates/pop-cli/src/commands/mod.rs index a7797845b..234b8d57b 100644 --- a/crates/pop-cli/src/commands/mod.rs +++ b/crates/pop-cli/src/commands/mod.rs @@ -98,12 +98,11 @@ impl Command { }, #[cfg(feature = "contract")] Self::Call(args) => match args.command { - call::Command::Contract(cmd) => { + call::Command::Contract(cmd) => call::contract::CallContract { cli: &mut Cli, args: cmd } .execute() .await - .map(|_| Value::Null) - }, + .map(|_| Value::Null), }, #[cfg(any(feature = "parachain", feature = "contract"))] Self::Up(args) => match args.command { From 1aaea92060dc27dd5c71463fbedfa64c1d5d525e Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 19 Sep 2024 21:15:01 +0200 Subject: [PATCH 018/211] test: refactor and add more test coverage --- crates/pop-cli/src/commands/call/contract.rs | 468 +++++++++---------- crates/pop-cli/src/commands/mod.rs | 6 +- 2 files changed, 224 insertions(+), 250 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 9782af737..a3a0f2403 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -use crate::cli::traits::*; +use crate::cli::{self, traits::*}; use anyhow::{anyhow, Result}; use clap::Args; use pop_contracts::{ @@ -54,6 +54,24 @@ pub struct CallContractCommand { dry_run: bool, } impl CallContractCommand { + /// Executes the command. + pub(crate) async fn execute(self) -> Result<()> { + let call_config: CallContractCommand = match self.set_up_call_config(&mut cli::Cli).await { + Ok(call_config) => call_config, + Err(e) => { + display_message(&format!("{}", e.to_string()), false, &mut cli::Cli)?; + return Ok(()); + }, + }; + match execute_call(call_config, self.contract.is_none(), &mut cli::Cli).await { + Ok(_) => Ok(()), + Err(e) => { + display_message(&format!("{}", e.to_string()), false, &mut cli::Cli)?; + Ok(()) + }, + } + } + fn display(&self) -> String { let mut full_message = format!("pop call contract"); if let Some(path) = &self.path { @@ -86,163 +104,39 @@ impl CallContractCommand { } full_message } -} -pub(crate) struct CallContract<'a, CLI: Cli> { - /// The cli to be used. - pub(crate) cli: &'a mut CLI, - /// The args to call. - pub(crate) args: CallContractCommand, -} - -impl<'a, CLI: Cli> CallContract<'a, CLI> { - /// Executes the command. - pub(crate) async fn execute(mut self: Self) -> Result<()> { - self.cli.intro("Call a contract")?; - let call_config = if self.args.contract.is_none() { - match guide_user_to_call_contract(&mut self, None, None, None).await { + /// Set up the config call. + async fn set_up_call_config( + &self, + cli: &mut impl cli::traits::Cli, + ) -> anyhow::Result { + cli.intro("Call a contract")?; + let call_config = if self.contract.is_none() { + match guide_user_to_call_contract(None, None, None, cli).await { Ok(config) => config, Err(e) => { - self.cli.outro_cancel(format!("{}", e.to_string()))?; - return Ok(()); + return Err(anyhow!(format!("{}", e.to_string()))); }, } } else { - self.args.clone() + self.clone() }; - match self.execute_call(call_config.clone()).await { - Ok(_) => Ok(()), - Err(e) => { - self.cli.outro_cancel(format!("{}", e.to_string()))?; - return Ok(()); - }, - } - } - /// Executes the call. - async fn execute_call(&mut self, call_config: CallContractCommand) -> Result<()> { - let contract = call_config - .contract - .clone() - .expect("contract can not be none as fallback above is interactive input; qed"); - let message = match call_config.message { - Some(m) => m, - None => { - return Err(anyhow!("Please specify the message to call.")); - }, - }; - - let call_exec = match set_up_call(CallOpts { - path: call_config.path.clone(), - contract, - message, - args: call_config.args, - value: call_config.value, - gas_limit: call_config.gas_limit, - proof_size: call_config.proof_size, - url: call_config.url.clone(), - suri: call_config.suri, - execute: call_config.execute, - }) - .await - { - Ok(call_exec) => call_exec, - Err(e) => { - return Err(anyhow!(format!("{}", e.root_cause().to_string()))); - }, - }; - - if call_config.dry_run { - let spinner = cliclack::spinner(); - spinner.start("Doing a dry run to estimate the gas..."); - match dry_run_gas_estimate_call(&call_exec).await { - Ok(w) => { - self.cli.info(format!("Gas limit: {:?}", w))?; - self.cli.warning("Your call has not been executed.")?; - }, - Err(e) => { - spinner.error(format!("{e}")); - self.cli.outro_cancel("Call failed.")?; - }, - }; - return Ok(()); - } - - if !call_config.execute { - let spinner = cliclack::spinner(); - spinner.start("Calling the contract..."); - let call_dry_run_result = dry_run_call(&call_exec).await?; - self.cli.info(format!("Result: {}", call_dry_run_result))?; - self.cli.warning("Your call has not been executed.")?; - } else { - let weight_limit; - if call_config.gas_limit.is_some() && call_config.proof_size.is_some() { - weight_limit = Weight::from_parts( - call_config.gas_limit.unwrap(), - call_config.proof_size.unwrap(), - ); - } else { - let spinner = cliclack::spinner(); - spinner.start("Doing a dry run to estimate the gas..."); - weight_limit = match dry_run_gas_estimate_call(&call_exec).await { - Ok(w) => { - self.cli.info(format!("Gas limit: {:?}", w))?; - w - }, - Err(e) => { - spinner.error(format!("{e}")); - return Err(anyhow!("Call failed.")); - }, - }; - } - let spinner = cliclack::spinner(); - spinner.start("Calling the contract..."); - - let call_result = call_smart_contract(call_exec, weight_limit, &call_config.url) - .await - .map_err(|err| anyhow!("{} {}", "ERROR:", format!("{err:?}")))?; - - self.cli.info(call_result)?; - } - if self.args.contract.is_none() { - let another_call: bool = self - .cli - .confirm("Do you want to do another call using the existing smart contract?") - .initial_value(false) - .interact()?; - if another_call { - // Remove only the prompt asking for another call. - console::Term::stderr().clear_last_lines(2)?; - let new_call_config = guide_user_to_call_contract( - self, - call_config.path, - Some(call_config.url), - call_config.contract, - ) - .await?; - Box::pin(self.execute_call(new_call_config)).await?; - } else { - self.cli.outro("Call completed successfully!")?; - } - } else { - self.cli.outro("Call completed successfully!")?; - } - return Ok(()); + Ok(call_config) } } /// Guide the user to call the contract. -async fn guide_user_to_call_contract<'a, CLI: Cli>( - command: &mut CallContract<'a, CLI>, +async fn guide_user_to_call_contract( contract_path: Option, url: Option, contract_address: Option, + cli: &mut impl cli::traits::Cli, ) -> anyhow::Result { let contract_path: PathBuf = match contract_path { Some(path) => path, None => { // Prompt for path. - let input_path: String = command - .cli + let input_path: String = cli .input("Where is your project located?") .placeholder("./") .default_input("./") @@ -264,8 +158,7 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( Some(url) => url, None => { // Prompt for url. - let url: String = command - .cli + let url: String = cli .input("Where is your contract deployed?") .placeholder("ws://localhost:9944") .default_input("ws://localhost:9944") @@ -277,8 +170,7 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( Some(contract_address) => contract_address, None => { // Prompt for contract address. - let contract_address: String = command - .cli + let contract_address: String = cli .input("Paste the on-chain contract address:") .placeholder("e.g. 5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") .validate(|input: &String| match parse_account(input) { @@ -292,7 +184,7 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( }; let message = { - let mut prompt = command.cli.select("Select the message to call:"); + let mut prompt = cli.select("Select the message to call:"); for select_message in messages { prompt = prompt.item( select_message.clone(), @@ -306,17 +198,14 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( let mut contract_args = Vec::new(); for arg in &message.args { contract_args.push( - command - .cli - .input(format!("Enter the value for the parameter: {}", arg.label)) + cli.input(format!("Enter the value for the parameter: {}", arg.label)) .placeholder(&format!("Type required: {}", &arg.type_name)) .interact()?, ); } let mut value = "0".to_string(); if message.payable { - value = command - .cli + value = cli .input("Value to transfer to the call:") .placeholder("0") .default_input("0") @@ -330,16 +219,14 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( let mut proof_size: Option = None; if message.mutates { // Prompt for gas limit and proof_size of the call. - let gas_limit_input: String = command - .cli + let gas_limit_input: String = cli .input("Enter the gas limit:") .required(false) .default_input("") .placeholder("If left blank, an estimation will be used") .interact()?; gas_limit = gas_limit_input.parse::().ok(); // If blank or bad input, estimate it. - let proof_size_input: String = command - .cli + let proof_size_input: String = cli .input("Enter the proof size limit:") .required(false) .placeholder("If left blank, an estimation will be used") @@ -349,8 +236,7 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( } // Who is calling the contract. - let suri: String = command - .cli + let suri: String = cli .input("Signer calling the contract:") .placeholder("//Alice") .default_input("//Alice") @@ -358,8 +244,7 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( let mut is_call_confirmed: bool = true; if message.mutates { - is_call_confirmed = command - .cli + is_call_confirmed = cli .confirm("Do you want to execute the call? (Selecting 'No' will perform a dry run)") .initial_value(true) .interact()?; @@ -377,10 +262,131 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( execute: if is_call_confirmed { message.mutates } else { false }, dry_run: !is_call_confirmed, }; - command.cli.info(call_command.display())?; + cli.info(call_command.display())?; Ok(call_command) } +/// Executes the call. +async fn execute_call( + call_config: CallContractCommand, + prompt_to_repeat_call: bool, + cli: &mut impl cli::traits::Cli, +) -> anyhow::Result<()> { + let contract = call_config + .contract + .clone() + .expect("contract can not be none as fallback above is interactive input; qed"); + let message = match call_config.message { + Some(m) => m, + None => { + return Err(anyhow!("Please specify the message to call.")); + }, + }; + + let call_exec = match set_up_call(CallOpts { + path: call_config.path.clone(), + contract, + message, + args: call_config.args, + value: call_config.value, + gas_limit: call_config.gas_limit, + proof_size: call_config.proof_size, + url: call_config.url.clone(), + suri: call_config.suri, + execute: call_config.execute, + }) + .await + { + Ok(call_exec) => call_exec, + Err(e) => { + return Err(anyhow!(format!("{}", e.root_cause().to_string()))); + }, + }; + + if call_config.dry_run { + let spinner = cliclack::spinner(); + spinner.start("Doing a dry run to estimate the gas..."); + match dry_run_gas_estimate_call(&call_exec).await { + Ok(w) => { + cli.info(format!("Gas limit: {:?}", w))?; + cli.warning("Your call has not been executed.")?; + }, + Err(e) => { + spinner.error(format!("{e}")); + display_message("Call failed.", false, cli)?; + }, + }; + return Ok(()); + } + + if !call_config.execute { + let spinner = cliclack::spinner(); + spinner.start("Calling the contract..."); + let call_dry_run_result = dry_run_call(&call_exec).await?; + cli.info(format!("Result: {}", call_dry_run_result))?; + cli.warning("Your call has not been executed.")?; + } else { + let weight_limit; + if call_config.gas_limit.is_some() && call_config.proof_size.is_some() { + weight_limit = + Weight::from_parts(call_config.gas_limit.unwrap(), call_config.proof_size.unwrap()); + } else { + let spinner = cliclack::spinner(); + spinner.start("Doing a dry run to estimate the gas..."); + weight_limit = match dry_run_gas_estimate_call(&call_exec).await { + Ok(w) => { + cli.info(format!("Gas limit: {:?}", w))?; + w + }, + Err(e) => { + spinner.error(format!("{e}")); + return Err(anyhow!("Call failed.")); + }, + }; + } + let spinner = cliclack::spinner(); + spinner.start("Calling the contract..."); + + let call_result = call_smart_contract(call_exec, weight_limit, &call_config.url) + .await + .map_err(|err| anyhow!("{} {}", "ERROR:", format!("{err:?}")))?; + + cli.info(call_result)?; + } + if prompt_to_repeat_call { + let another_call: bool = cli + .confirm("Do you want to do another call using the existing smart contract?") + .initial_value(false) + .interact()?; + if another_call { + // Remove only the prompt asking for another call. + console::Term::stderr().clear_last_lines(2)?; + let new_call_config = guide_user_to_call_contract( + call_config.path, + Some(call_config.url), + call_config.contract, + cli, + ) + .await?; + Box::pin(execute_call(new_call_config, prompt_to_repeat_call, cli)).await?; + } else { + display_message("Call completed successfully!", true, cli)?; + } + } else { + display_message("Call completed successfully!", true, cli)?; + } + return Ok(()); +} + +fn display_message(message: &str, success: bool, cli: &mut impl cli::traits::Cli) -> Result<()> { + if success { + cli.outro(message)?; + } else { + cli.outro_cancel(message)?; + } + Ok(()) +} + #[cfg(test)] mod tests { use super::*; @@ -390,7 +396,7 @@ mod tests { use url::Url; #[tokio::test] - async fn call_contract_messages_are_ok() -> Result<()> { + async fn call_contract_query_works() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; let mut current_dir = env::current_dir().expect("Failed to get current directory"); current_dir.pop(); @@ -403,27 +409,30 @@ mod tests { let mut cli = MockCli::new() .expect_intro(&"Call a contract") .expect_warning("Your call has not been executed.") + .expect_confirm( + "Do you want to do another call using the existing smart contract?", + false, + ) .expect_outro("Call completed successfully!"); // Contract deployed on Pop Network testnet, test get - CallContract { - cli: &mut cli, - args: CallContractCommand { - path: Some(temp_dir.path().join("testing")), - contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), - message: Some("get".to_string()), - args: vec![].to_vec(), - value: "0".to_string(), - gas_limit: None, - proof_size: None, - url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, - suri: "//Alice".to_string(), - dry_run: false, - execute: false, - }, + let config_call = CallContractCommand { + path: Some(temp_dir.path().join("testing")), + contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), + message: Some("get".to_string()), + args: vec![].to_vec(), + value: "0".to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, + suri: "//Alice".to_string(), + dry_run: false, + execute: false, } - .execute() + .set_up_call_config(&mut cli) .await?; + // Test the query. With true, it will prompt for another call. + execute_call(config_call, true, &mut cli).await?; cli.verify() } @@ -456,13 +465,15 @@ mod tests { suri: "//Alice".to_string(), dry_run: true, execute: false, - }; + } + .set_up_call_config(&mut cli) + .await?; assert_eq!(call_config.display(), format!( "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message flip --gas 100 --proof_size 10 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --dry_run", temp_dir.path().join("testing").display().to_string(), )); // Contract deployed on Pop Network testnet, test dry-run - CallContract { cli: &mut cli, args: call_config }.execute().await?; + execute_call(call_config, false, &mut cli).await?; cli.verify() } @@ -507,32 +518,11 @@ mod tests { "Where is your project located?", temp_dir.path().join("testing").display().to_string(), ).expect_info(format!( - "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message get --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice", - temp_dir.path().join("testing").display().to_string(), - )); - - let call_config = guide_user_to_call_contract( - &mut CallContract { - cli: &mut cli, - args: CallContractCommand { - path: Some(temp_dir.path().join("testing")), - contract: None, - message: None, - args: vec![].to_vec(), - value: "0".to_string(), - gas_limit: None, - proof_size: None, - url: Url::parse("ws://localhost:9944")?, - suri: "//Alice".to_string(), - dry_run: false, - execute: false, - }, - }, - None, - None, - None, - ) - .await?; + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message get --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice", + temp_dir.path().join("testing").display().to_string(), + )); + + let call_config = guide_user_to_call_contract(None, None, None, &mut cli).await?; assert_eq!( call_config.contract, Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) @@ -602,28 +592,7 @@ mod tests { temp_dir.path().join("testing").display().to_string(), )); - let call_config = guide_user_to_call_contract( - &mut CallContract { - cli: &mut cli, - args: CallContractCommand { - path: Some(temp_dir.path().join("testing")), - contract: None, - message: None, - args: vec![].to_vec(), - value: "0".to_string(), - gas_limit: None, - proof_size: None, - url: Url::parse("ws://localhost:9944")?, - suri: "//Alice".to_string(), - dry_run: false, - execute: false, - }, - }, - None, - None, - None, - ) - .await?; + let call_config = guide_user_to_call_contract(None, None, None, &mut cli).await?; assert_eq!( call_config.contract, Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) @@ -657,29 +626,38 @@ mod tests { current_dir.join("pop-contracts/tests/files/testing.json"), )?; - let mut cli = MockCli::new() - .expect_intro(&"Call a contract") - .expect_outro_cancel("Please specify the message to call."); - - CallContract { - cli: &mut cli, - args: CallContractCommand { - path: Some(temp_dir.path().join("testing")), - contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), - message: None, - args: vec![].to_vec(), - value: "0".to_string(), - gas_limit: None, - proof_size: None, - url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, - suri: "//Alice".to_string(), - dry_run: false, - execute: false, - }, + let mut cli = MockCli::new().expect_intro(&"Call a contract"); + + let call_config = CallContractCommand { + path: Some(temp_dir.path().join("testing")), + contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), + message: None, + args: vec![].to_vec(), + value: "0".to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, + suri: "//Alice".to_string(), + dry_run: false, + execute: false, } - .execute() + .set_up_call_config(&mut cli) .await?; + assert!(matches!( + execute_call(call_config, false, &mut cli).await, + anyhow::Result::Err(message) if message.to_string() == "Please specify the message to call." + )); + + cli.verify() + } + #[test] + fn test_display_message() -> Result<()> { + let mut cli = MockCli::new().expect_outro(&"Call completed successfully!"); + display_message("Call completed successfully!", true, &mut cli)?; + cli.verify()?; + let mut cli = MockCli::new().expect_outro_cancel("Call failed."); + display_message("Call failed.", false, &mut cli)?; cli.verify() } } diff --git a/crates/pop-cli/src/commands/mod.rs b/crates/pop-cli/src/commands/mod.rs index 234b8d57b..34c2f10b3 100644 --- a/crates/pop-cli/src/commands/mod.rs +++ b/crates/pop-cli/src/commands/mod.rs @@ -98,11 +98,7 @@ impl Command { }, #[cfg(feature = "contract")] Self::Call(args) => match args.command { - call::Command::Contract(cmd) => - call::contract::CallContract { cli: &mut Cli, args: cmd } - .execute() - .await - .map(|_| Value::Null), + call::Command::Contract(cmd) => cmd.execute().await.map(|_| Value::Null), }, #[cfg(any(feature = "parachain", feature = "contract"))] Self::Up(args) => match args.command { From 05db96fb4a68dcc09092a4019ef5d71814948683 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 20 Sep 2024 09:21:49 +0200 Subject: [PATCH 019/211] test: more coverage --- crates/pop-cli/src/cli.rs | 18 +-- crates/pop-cli/src/commands/call/contract.rs | 120 ++++++++++++++----- crates/pop-contracts/src/init_tests.rs | 6 +- 3 files changed, 101 insertions(+), 43 deletions(-) diff --git a/crates/pop-cli/src/cli.rs b/crates/pop-cli/src/cli.rs index 29598c7e7..968c1a4f9 100644 --- a/crates/pop-cli/src/cli.rs +++ b/crates/pop-cli/src/cli.rs @@ -223,7 +223,7 @@ pub(crate) mod tests { /// Mock Cli with optional expectations #[derive(Default)] pub(crate) struct MockCli { - confirm_expectation: Option<(String, bool)>, + confirm_expectation: Vec<(String, bool)>, info_expectations: Vec, input_expectations: Vec<(String, String)>, intro_expectation: Option, @@ -243,7 +243,7 @@ pub(crate) mod tests { } pub(crate) fn expect_confirm(mut self, prompt: impl Display, confirm: bool) -> Self { - self.confirm_expectation = Some((prompt.to_string(), confirm)); + self.confirm_expectation.push((prompt.to_string(), confirm)); self } @@ -306,8 +306,8 @@ pub(crate) mod tests { } pub(crate) fn verify(self) -> anyhow::Result<()> { - if let Some((expectation, _)) = self.confirm_expectation { - panic!("`{expectation}` confirm expectation not satisfied") + if !self.confirm_expectation.is_empty() { + panic!("`{:?}` confirm expectations not satisfied", self.confirm_expectation) } if !self.info_expectations.is_empty() { panic!("`{}` info log expectations not satisfied", self.info_expectations.join(",")) @@ -349,7 +349,7 @@ pub(crate) mod tests { impl Cli for MockCli { fn confirm(&mut self, prompt: impl Display) -> impl Confirm { let prompt = prompt.to_string(); - if let Some((expectation, confirm)) = self.confirm_expectation.take() { + if let Some((expectation, confirm)) = self.confirm_expectation.pop() { assert_eq!(expectation, prompt, "prompt does not satisfy expectation"); return MockConfirm { confirm }; } @@ -454,13 +454,13 @@ pub(crate) mod tests { } impl Confirm for MockConfirm { + fn initial_value(mut self, _initial_value: bool) -> Self { + self.confirm = self.confirm; // Ignore initial value and always return mock value + self + } fn interact(&mut self) -> Result { Ok(self.confirm) } - fn initial_value(mut self, initial_value: bool) -> Self { - self.confirm = initial_value; - self - } } /// Mock input prompt diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index a3a0f2403..897e41ab3 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -59,23 +59,23 @@ impl CallContractCommand { let call_config: CallContractCommand = match self.set_up_call_config(&mut cli::Cli).await { Ok(call_config) => call_config, Err(e) => { - display_message(&format!("{}", e.to_string()), false, &mut cli::Cli)?; + display_message(&e.to_string(), false, &mut cli::Cli)?; return Ok(()); }, }; match execute_call(call_config, self.contract.is_none(), &mut cli::Cli).await { Ok(_) => Ok(()), Err(e) => { - display_message(&format!("{}", e.to_string()), false, &mut cli::Cli)?; + display_message(&e.to_string(), false, &mut cli::Cli)?; Ok(()) }, } } fn display(&self) -> String { - let mut full_message = format!("pop call contract"); + let mut full_message = "pop call contract".to_string(); if let Some(path) = &self.path { - full_message.push_str(&format!(" --path {}", path.display().to_string())); + full_message.push_str(&format!(" --path {}", path.display())); } if let Some(contract) = &self.contract { full_message.push_str(&format!(" --contract {}", contract)); @@ -326,14 +326,12 @@ async fn execute_call( cli.info(format!("Result: {}", call_dry_run_result))?; cli.warning("Your call has not been executed.")?; } else { - let weight_limit; - if call_config.gas_limit.is_some() && call_config.proof_size.is_some() { - weight_limit = - Weight::from_parts(call_config.gas_limit.unwrap(), call_config.proof_size.unwrap()); + let weight_limit = if call_config.gas_limit.is_some() && call_config.proof_size.is_some() { + Weight::from_parts(call_config.gas_limit.unwrap(), call_config.proof_size.unwrap()) } else { let spinner = cliclack::spinner(); spinner.start("Doing a dry run to estimate the gas..."); - weight_limit = match dry_run_gas_estimate_call(&call_exec).await { + match dry_run_gas_estimate_call(&call_exec).await { Ok(w) => { cli.info(format!("Gas limit: {:?}", w))?; w @@ -342,8 +340,8 @@ async fn execute_call( spinner.error(format!("{e}")); return Err(anyhow!("Call failed.")); }, - }; - } + } + }; let spinner = cliclack::spinner(); spinner.start("Calling the contract..."); @@ -375,7 +373,7 @@ async fn execute_call( } else { display_message("Call completed successfully!", true, cli)?; } - return Ok(()); + Ok(()) } fn display_message(message: &str, success: bool, cli: &mut impl cli::traits::Cli) -> Result<()> { @@ -396,7 +394,7 @@ mod tests { use url::Url; #[tokio::test] - async fn call_contract_query_works() -> Result<()> { + async fn execute_query_works() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; let mut current_dir = env::current_dir().expect("Failed to get current directory"); current_dir.pop(); @@ -405,18 +403,8 @@ mod tests { current_dir.join("pop-contracts/tests/files/testing.contract"), current_dir.join("pop-contracts/tests/files/testing.json"), )?; - - let mut cli = MockCli::new() - .expect_intro(&"Call a contract") - .expect_warning("Your call has not been executed.") - .expect_confirm( - "Do you want to do another call using the existing smart contract?", - false, - ) - .expect_outro("Call completed successfully!"); - // Contract deployed on Pop Network testnet, test get - let config_call = CallContractCommand { + CallContractCommand { path: Some(temp_dir.path().join("testing")), contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), message: Some("get".to_string()), @@ -429,12 +417,9 @@ mod tests { dry_run: false, execute: false, } - .set_up_call_config(&mut cli) + .execute() .await?; - // Test the query. With true, it will prompt for another call. - execute_call(config_call, true, &mut cli).await?; - - cli.verify() + Ok(()) } #[tokio::test] @@ -478,6 +463,69 @@ mod tests { cli.verify() } + #[tokio::test] + async fn call_contract_query_duplicate_call_works() -> Result<()> { + let temp_dir = generate_smart_contract_test_environment()?; + let mut current_dir = env::current_dir().expect("Failed to get current directory"); + current_dir.pop(); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("pop-contracts/tests/files/testing.contract"), + current_dir.join("pop-contracts/tests/files/testing.json"), + )?; + let items = vec![ + ("flip\n".into(), " A message that can be called on instantiated contracts. This one flips the value of the stored `bool` from `true` to `false` and vice versa.".into()), + ("get\n".into(), " Simply returns the current value of our `bool`.".into()), + ("specific_flip\n".into(), " A message for testing, flips the value of the stored `bool` with `new_value` and is payable".into()) + ]; + let mut cli = MockCli::new() + .expect_intro(&"Call a contract") + .expect_warning("Your call has not been executed.") + .expect_confirm( + "Do you want to do another call using the existing smart contract?", + false, + ) + .expect_confirm( + "Do you want to do another call using the existing smart contract?", + true, + ) + .expect_select::( + "Select the message to call:", + Some(false), + true, + Some(items), + 1, // "get" message + ) + .expect_input("Signer calling the contract:", "//Alice".into()) + .expect_info(format!( + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message get --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice", + temp_dir.path().join("testing").display().to_string(), + )) + .expect_warning("Your call has not been executed.") + .expect_outro("Call completed successfully!"); + + // Contract deployed on Pop Network testnet, test get + let config_call = CallContractCommand { + path: Some(temp_dir.path().join("testing")), + contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), + message: Some("get".to_string()), + args: vec![].to_vec(), + value: "0".to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, + suri: "//Alice".to_string(), + dry_run: false, + execute: false, + } + .set_up_call_config(&mut cli) + .await?; + // Test the query. With true, it will prompt for another call. + execute_call(config_call, true, &mut cli).await?; + + cli.verify() + } + // This test only covers the interactive portion of the call contract command, without actually // calling the contract. #[tokio::test] @@ -616,7 +664,17 @@ mod tests { } #[tokio::test] - async fn call_contract_messages_fails_no_message() -> Result<()> { + async fn guide_user_to_call_contract_fails_not_build() -> Result<()> { + let temp_dir = generate_smart_contract_test_environment()?; + let mut cli = MockCli::new(); + assert!( + matches!(guide_user_to_call_contract(Some(temp_dir.path().join("testing")), None, None, &mut cli).await, anyhow::Result::Err(message) if message.to_string().contains("Unable to fetch contract metadata: Failed to find any contract artifacts in target directory.")) + ); + cli.verify() + } + + #[tokio::test] + async fn call_contract_fails_no_message() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; let mut current_dir = env::current_dir().expect("Failed to get current directory"); current_dir.pop(); @@ -652,7 +710,7 @@ mod tests { } #[test] - fn test_display_message() -> Result<()> { + fn display_message_works() -> Result<()> { let mut cli = MockCli::new().expect_outro(&"Call completed successfully!"); display_message("Call completed successfully!", true, &mut cli)?; cli.verify()?; diff --git a/crates/pop-contracts/src/init_tests.rs b/crates/pop-contracts/src/init_tests.rs index f1c24c7b9..0541220f8 100644 --- a/crates/pop-contracts/src/init_tests.rs +++ b/crates/pop-contracts/src/init_tests.rs @@ -21,9 +21,9 @@ pub fn mock_build_process( // Create a target directory let target_contract_dir = temp_contract_dir.join("target"); fs::create_dir(&target_contract_dir)?; - fs::create_dir(&target_contract_dir.join("ink"))?; + fs::create_dir(target_contract_dir.join("ink"))?; // Copy a mocked testing.contract and testing.json files inside the target directory - fs::copy(contract_file, &target_contract_dir.join("ink/testing.contract"))?; - fs::copy(metadata_file, &target_contract_dir.join("ink/testing.json"))?; + fs::copy(contract_file, target_contract_dir.join("ink/testing.contract"))?; + fs::copy(metadata_file, target_contract_dir.join("ink/testing.json"))?; Ok(()) } From 8df2058910af6b48d9f9c2c09a125cb0c019830e Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 20 Sep 2024 10:21:06 +0200 Subject: [PATCH 020/211] fix: unit test --- crates/pop-cli/src/commands/call/contract.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 897e41ab3..2219c88e4 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -612,6 +612,7 @@ mod tests { ]; // The inputs are processed in reverse order. let mut cli = MockCli::new() + .expect_confirm("Do you want to execute the call? (Selecting 'No' will perform a dry run)", true) .expect_input("Signer calling the contract:", "//Alice".into()) .expect_input("Enter the proof size limit:", "".into()) // Only if call .expect_input("Enter the gas limit:", "".into()) // Only if call From 96ed5f173205d3ca3cfad24d5b14f95b5ab46e87 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 5 Nov 2024 11:09:26 +0100 Subject: [PATCH 021/211] feat: dev mode to skip certain user prompts --- crates/pop-cli/src/commands/call/contract.rs | 93 ++++++++++++++++++-- 1 file changed, 87 insertions(+), 6 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 2219c88e4..06c96d5bf 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -52,6 +52,10 @@ pub struct CallContractCommand { /// Perform a dry-run via RPC to estimate the gas usage. This does not submit a transaction. #[clap(long, conflicts_with = "execute")] dry_run: bool, + /// Enables developer mode, bypassing certain user prompts for faster testing. + /// Recommended for testing and local development only. + #[clap(name = "dev", long, short, default_value = "false")] + dev_mode: bool, } impl CallContractCommand { /// Executes the command. @@ -112,7 +116,7 @@ impl CallContractCommand { ) -> anyhow::Result { cli.intro("Call a contract")?; let call_config = if self.contract.is_none() { - match guide_user_to_call_contract(None, None, None, cli).await { + match guide_user_to_call_contract(None, None, None, self.dev_mode, cli).await { Ok(config) => config, Err(e) => { return Err(anyhow!(format!("{}", e.to_string()))); @@ -130,6 +134,7 @@ async fn guide_user_to_call_contract( contract_path: Option, url: Option, contract_address: Option, + dev_mode: bool, cli: &mut impl cli::traits::Cli, ) -> anyhow::Result { let contract_path: PathBuf = match contract_path { @@ -217,7 +222,7 @@ async fn guide_user_to_call_contract( } let mut gas_limit: Option = None; let mut proof_size: Option = None; - if message.mutates { + if message.mutates && !dev_mode { // Prompt for gas limit and proof_size of the call. let gas_limit_input: String = cli .input("Enter the gas limit:") @@ -243,7 +248,7 @@ async fn guide_user_to_call_contract( .interact()?; let mut is_call_confirmed: bool = true; - if message.mutates { + if message.mutates && !dev_mode { is_call_confirmed = cli .confirm("Do you want to execute the call? (Selecting 'No' will perform a dry run)") .initial_value(true) @@ -261,6 +266,7 @@ async fn guide_user_to_call_contract( suri, execute: if is_call_confirmed { message.mutates } else { false }, dry_run: !is_call_confirmed, + dev_mode, }; cli.info(call_command.display())?; Ok(call_command) @@ -363,6 +369,7 @@ async fn execute_call( call_config.path, Some(call_config.url), call_config.contract, + call_config.dev_mode, cli, ) .await?; @@ -416,6 +423,7 @@ mod tests { suri: "//Alice".to_string(), dry_run: false, execute: false, + dev_mode: false, } .execute() .await?; @@ -450,6 +458,7 @@ mod tests { suri: "//Alice".to_string(), dry_run: true, execute: false, + dev_mode: false, } .set_up_call_config(&mut cli) .await?; @@ -517,6 +526,7 @@ mod tests { suri: "//Alice".to_string(), dry_run: false, execute: false, + dev_mode: false, } .set_up_call_config(&mut cli) .await?; @@ -570,7 +580,7 @@ mod tests { temp_dir.path().join("testing").display().to_string(), )); - let call_config = guide_user_to_call_contract(None, None, None, &mut cli).await?; + let call_config = guide_user_to_call_contract(None, None, None, false, &mut cli).await?; assert_eq!( call_config.contract, Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) @@ -641,7 +651,7 @@ mod tests { temp_dir.path().join("testing").display().to_string(), )); - let call_config = guide_user_to_call_contract(None, None, None, &mut cli).await?; + let call_config = guide_user_to_call_contract(None, None, None, false, &mut cli).await?; assert_eq!( call_config.contract, Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) @@ -664,12 +674,82 @@ mod tests { cli.verify() } + // This test only covers the interactive portion of the call contract command, without actually + // calling the contract. + #[tokio::test] + async fn guide_user_to_call_contract_in_dev_mode_works() -> Result<()> { + let temp_dir = generate_smart_contract_test_environment()?; + let mut current_dir = env::current_dir().expect("Failed to get current directory"); + current_dir.pop(); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("pop-contracts/tests/files/testing.contract"), + current_dir.join("pop-contracts/tests/files/testing.json"), + )?; + + let items = vec![ + ("flip\n".into(), " A message that can be called on instantiated contracts. This one flips the value of the stored `bool` from `true` to `false` and vice versa.".into()), + ("get\n".into(), " Simply returns the current value of our `bool`.".into()), + ("specific_flip\n".into(), " A message for testing, flips the value of the stored `bool` with `new_value` and is payable".into()) + ]; + // The inputs are processed in reverse order. + let mut cli = MockCli::new() + .expect_input("Signer calling the contract:", "//Alice".into()) + .expect_input("Value to transfer to the call:", "50".into()) // Only if payable + .expect_input("Enter the value for the parameter: new_value", "true".into()) // Args for specific_flip + .expect_select::( + "Select the message to call:", + Some(false), + true, + Some(items), + 2, // "specific_flip" message + ) + .expect_input( + "Paste the on-chain contract address:", + "15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".into(), + ) + .expect_input( + "Where is your contract deployed?", + "wss://rpc1.paseo.popnetwork.xyz".into(), + ) + .expect_input( + "Where is your project located?", + temp_dir.path().join("testing").display().to_string(), + ).expect_info(format!( + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + temp_dir.path().join("testing").display().to_string(), + )); + + let call_config = guide_user_to_call_contract(None, None, None, true, &mut cli).await?; + assert_eq!( + call_config.contract, + Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) + ); + assert_eq!(call_config.message, Some("specific_flip".to_string())); + assert_eq!(call_config.args.len(), 1); + assert_eq!(call_config.args[0], "true".to_string()); + assert_eq!(call_config.value, "50".to_string()); + assert_eq!(call_config.gas_limit, None); + assert_eq!(call_config.proof_size, None); + assert_eq!(call_config.url.to_string(), "wss://rpc1.paseo.popnetwork.xyz/"); + assert_eq!(call_config.suri, "//Alice"); + assert!(call_config.execute); + assert!(!call_config.dry_run); + assert!(call_config.dev_mode); + assert_eq!(call_config.display(), format!( + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + temp_dir.path().join("testing").display().to_string(), + )); + + cli.verify() + } + #[tokio::test] async fn guide_user_to_call_contract_fails_not_build() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; let mut cli = MockCli::new(); assert!( - matches!(guide_user_to_call_contract(Some(temp_dir.path().join("testing")), None, None, &mut cli).await, anyhow::Result::Err(message) if message.to_string().contains("Unable to fetch contract metadata: Failed to find any contract artifacts in target directory.")) + matches!(guide_user_to_call_contract(Some(temp_dir.path().join("testing")), None, None, false, &mut cli).await, anyhow::Result::Err(message) if message.to_string().contains("Unable to fetch contract metadata: Failed to find any contract artifacts in target directory.")) ); cli.verify() } @@ -699,6 +779,7 @@ mod tests { suri: "//Alice".to_string(), dry_run: false, execute: false, + dev_mode: false, } .set_up_call_config(&mut cli) .await?; From 0e5bf32f1111a829c23fb4f9befe28607c09be3e Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 5 Nov 2024 15:39:49 +0100 Subject: [PATCH 022/211] refactor: test functions, renaming and fix clippy --- crates/pop-cli/src/commands/call/contract.rs | 18 ++++----- crates/pop-contracts/src/call/metadata.rs | 4 +- crates/pop-contracts/src/call/mod.rs | 14 +++---- crates/pop-contracts/src/init_tests.rs | 29 --------------- crates/pop-contracts/src/lib.rs | 4 +- crates/pop-contracts/src/testing.rs | 39 ++++++++++++++++++++ crates/pop-contracts/src/up.rs | 16 ++++---- 7 files changed, 67 insertions(+), 57 deletions(-) delete mode 100644 crates/pop-contracts/src/init_tests.rs create mode 100644 crates/pop-contracts/src/testing.rs diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 06c96d5bf..85edaf173 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -396,13 +396,13 @@ fn display_message(message: &str, success: bool, cli: &mut impl cli::traits::Cli mod tests { use super::*; use crate::cli::MockCli; - use pop_contracts::{generate_smart_contract_test_environment, mock_build_process}; + use pop_contracts::{mock_build_process, new_environment}; use std::env; use url::Url; #[tokio::test] async fn execute_query_works() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let mut current_dir = env::current_dir().expect("Failed to get current directory"); current_dir.pop(); mock_build_process( @@ -432,7 +432,7 @@ mod tests { #[tokio::test] async fn call_contract_dry_run_works() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let mut current_dir = env::current_dir().expect("Failed to get current directory"); current_dir.pop(); mock_build_process( @@ -474,7 +474,7 @@ mod tests { #[tokio::test] async fn call_contract_query_duplicate_call_works() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let mut current_dir = env::current_dir().expect("Failed to get current directory"); current_dir.pop(); mock_build_process( @@ -540,7 +540,7 @@ mod tests { // calling the contract. #[tokio::test] async fn guide_user_to_query_contract_works() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let mut current_dir = env::current_dir().expect("Failed to get current directory"); current_dir.pop(); mock_build_process( @@ -606,7 +606,7 @@ mod tests { // calling the contract. #[tokio::test] async fn guide_user_to_call_contract_works() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let mut current_dir = env::current_dir().expect("Failed to get current directory"); current_dir.pop(); mock_build_process( @@ -678,7 +678,7 @@ mod tests { // calling the contract. #[tokio::test] async fn guide_user_to_call_contract_in_dev_mode_works() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let mut current_dir = env::current_dir().expect("Failed to get current directory"); current_dir.pop(); mock_build_process( @@ -746,7 +746,7 @@ mod tests { #[tokio::test] async fn guide_user_to_call_contract_fails_not_build() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let mut cli = MockCli::new(); assert!( matches!(guide_user_to_call_contract(Some(temp_dir.path().join("testing")), None, None, false, &mut cli).await, anyhow::Result::Err(message) if message.to_string().contains("Unable to fetch contract metadata: Failed to find any contract artifacts in target directory.")) @@ -756,7 +756,7 @@ mod tests { #[tokio::test] async fn call_contract_fails_no_message() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let mut current_dir = env::current_dir().expect("Failed to get current directory"); current_dir.pop(); mock_build_process( diff --git a/crates/pop-contracts/src/call/metadata.rs b/crates/pop-contracts/src/call/metadata.rs index 82e7589b7..605d06206 100644 --- a/crates/pop-contracts/src/call/metadata.rs +++ b/crates/pop-contracts/src/call/metadata.rs @@ -73,12 +73,12 @@ mod tests { use std::env; use super::*; - use crate::{generate_smart_contract_test_environment, mock_build_process}; + use crate::{mock_build_process, new_environment}; use anyhow::Result; #[test] fn get_messages_work() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let current_dir = env::current_dir().expect("Failed to get current directory"); mock_build_process( temp_dir.path().join("testing"), diff --git a/crates/pop-contracts/src/call/mod.rs b/crates/pop-contracts/src/call/mod.rs index 3b97220c6..2c363301b 100644 --- a/crates/pop-contracts/src/call/mod.rs +++ b/crates/pop-contracts/src/call/mod.rs @@ -161,8 +161,8 @@ mod tests { use super::*; use crate::{ contracts_node_generator, dry_run_gas_estimate_instantiate, errors::Error, - generate_smart_contract_test_environment, instantiate_smart_contract, mock_build_process, - run_contracts_node, set_up_deployment, UpOpts, + instantiate_smart_contract, mock_build_process, new_environment, run_contracts_node, + set_up_deployment, UpOpts, }; use anyhow::Result; use sp_core::Bytes; @@ -172,7 +172,7 @@ mod tests { #[tokio::test] async fn test_set_up_call() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let current_dir = env::current_dir().expect("Failed to get current directory"); mock_build_process( temp_dir.path().join("testing"), @@ -199,7 +199,7 @@ mod tests { #[tokio::test] async fn test_set_up_call_error_contract_not_build() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let call_opts = CallOpts { path: Some(temp_dir.path().join("testing")), contract: "5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A".to_string(), @@ -243,7 +243,7 @@ mod tests { #[tokio::test] async fn test_dry_run_call_error_contract_not_deployed() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let current_dir = env::current_dir().expect("Failed to get current directory"); mock_build_process( temp_dir.path().join("testing"), @@ -270,7 +270,7 @@ mod tests { #[tokio::test] async fn test_dry_run_estimate_call_error_contract_not_deployed() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let current_dir = env::current_dir().expect("Failed to get current directory"); mock_build_process( temp_dir.path().join("testing"), @@ -301,7 +301,7 @@ mod tests { #[tokio::test] async fn call_works() -> Result<()> { const LOCALHOST_URL: &str = "ws://127.0.0.1:9944"; - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let current_dir = env::current_dir().expect("Failed to get current directory"); mock_build_process( temp_dir.path().join("testing"), diff --git a/crates/pop-contracts/src/init_tests.rs b/crates/pop-contracts/src/init_tests.rs deleted file mode 100644 index 0541220f8..000000000 --- a/crates/pop-contracts/src/init_tests.rs +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 - -use crate::{create_smart_contract, Contract}; -use anyhow::Result; -use std::{fs, path::PathBuf}; - -pub fn generate_smart_contract_test_environment() -> Result { - let temp_dir = tempfile::tempdir().expect("Could not create temp dir"); - let temp_contract_dir = temp_dir.path().join("testing"); - fs::create_dir(&temp_contract_dir)?; - create_smart_contract("testing", temp_contract_dir.as_path(), &Contract::Standard)?; - Ok(temp_dir) -} - -// Function that mocks the build process generating the contract artifacts. -pub fn mock_build_process( - temp_contract_dir: PathBuf, - contract_file: PathBuf, - metadata_file: PathBuf, -) -> Result<()> { - // Create a target directory - let target_contract_dir = temp_contract_dir.join("target"); - fs::create_dir(&target_contract_dir)?; - fs::create_dir(target_contract_dir.join("ink"))?; - // Copy a mocked testing.contract and testing.json files inside the target directory - fs::copy(contract_file, target_contract_dir.join("ink/testing.contract"))?; - fs::copy(metadata_file, target_contract_dir.join("ink/testing.json"))?; - Ok(()) -} diff --git a/crates/pop-contracts/src/lib.rs b/crates/pop-contracts/src/lib.rs index 4fd24bd4e..a3ff0175c 100644 --- a/crates/pop-contracts/src/lib.rs +++ b/crates/pop-contracts/src/lib.rs @@ -4,11 +4,11 @@ mod build; mod call; mod errors; -mod init_tests; mod new; mod node; mod templates; mod test; +mod testing; mod up; mod utils; @@ -18,11 +18,11 @@ pub use call::{ metadata::{get_messages, Message}, set_up_call, CallOpts, }; -pub use init_tests::{generate_smart_contract_test_environment, mock_build_process}; pub use new::{create_smart_contract, is_valid_contract_name}; pub use node::{contracts_node_generator, is_chain_alive, run_contracts_node}; pub use templates::{Contract, ContractType}; pub use test::{test_e2e_smart_contract, test_smart_contract}; +pub use testing::{mock_build_process, new_environment}; pub use up::{ dry_run_gas_estimate_instantiate, dry_run_upload, instantiate_smart_contract, set_up_deployment, set_up_upload, upload_smart_contract, UpOpts, diff --git a/crates/pop-contracts/src/testing.rs b/crates/pop-contracts/src/testing.rs new file mode 100644 index 000000000..6a8abfcd9 --- /dev/null +++ b/crates/pop-contracts/src/testing.rs @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0 + +use crate::{create_smart_contract, Contract}; +use anyhow::Result; +use std::{ + fs::{copy, create_dir}, + path::Path, +}; + +/// Generates a smart contract test environment. +/// +/// * `name` - The name of the contract to be created. +pub fn new_environment(name: &str) -> Result { + let temp_dir = tempfile::tempdir().expect("Could not create temp dir"); + let temp_contract_dir = temp_dir.path().join(name); + create_dir(&temp_contract_dir)?; + create_smart_contract(name, temp_contract_dir.as_path(), &Contract::Standard)?; + Ok(temp_dir) +} + +/// Mocks the build process by generating contract artifacts in a specified temporary directory. +/// +/// * `temp_contract_dir` - The root directory where the `target` folder and artifacts will be +/// created. +/// * `contract_file` - The path to the mocked contract file to be copied. +/// * `metadata_file` - The path to the mocked metadata file to be copied. +pub fn mock_build_process

(temp_contract_dir: P, contract_file: P, metadata_file: P) -> Result<()> +where + P: AsRef, +{ + // Create a target directory + let target_contract_dir = temp_contract_dir.as_ref().join("target"); + create_dir(&target_contract_dir)?; + create_dir(target_contract_dir.join("ink"))?; + // Copy a mocked testing.contract and testing.json files inside the target directory + copy(contract_file, target_contract_dir.join("ink/testing.contract"))?; + copy(metadata_file, target_contract_dir.join("ink/testing.json"))?; + Ok(()) +} diff --git a/crates/pop-contracts/src/up.rs b/crates/pop-contracts/src/up.rs index b0c0edc5a..6d2f0ec03 100644 --- a/crates/pop-contracts/src/up.rs +++ b/crates/pop-contracts/src/up.rs @@ -204,8 +204,8 @@ pub async fn upload_smart_contract( mod tests { use super::*; use crate::{ - contracts_node_generator, errors::Error, generate_smart_contract_test_environment, - mock_build_process, run_contracts_node, + contracts_node_generator, errors::Error, mock_build_process, new_environment, + run_contracts_node, }; use anyhow::Result; use std::{env, process::Command}; @@ -215,7 +215,7 @@ mod tests { #[tokio::test] async fn set_up_deployment_works() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let current_dir = env::current_dir().expect("Failed to get current directory"); mock_build_process( temp_dir.path().join("testing"), @@ -239,7 +239,7 @@ mod tests { #[tokio::test] async fn set_up_upload_works() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let current_dir = env::current_dir().expect("Failed to get current directory"); mock_build_process( temp_dir.path().join("testing"), @@ -263,7 +263,7 @@ mod tests { #[tokio::test] async fn dry_run_gas_estimate_instantiate_works() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let current_dir = env::current_dir().expect("Failed to get current directory"); mock_build_process( temp_dir.path().join("testing"), @@ -290,7 +290,7 @@ mod tests { #[tokio::test] async fn dry_run_gas_estimate_instantiate_throw_custom_error() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let current_dir = env::current_dir().expect("Failed to get current directory"); mock_build_process( temp_dir.path().join("testing"), @@ -318,7 +318,7 @@ mod tests { #[tokio::test] async fn dry_run_upload_throw_custom_error() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let current_dir = env::current_dir().expect("Failed to get current directory"); mock_build_process( temp_dir.path().join("testing"), @@ -346,7 +346,7 @@ mod tests { #[tokio::test] async fn instantiate_and_upload() -> Result<()> { const LOCALHOST_URL: &str = "ws://127.0.0.1:9944"; - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let current_dir = env::current_dir().expect("Failed to get current directory"); mock_build_process( temp_dir.path().join("testing"), From 29848b42b6d8d9c93a9e3f1929291b83a3762b1f Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 5 Nov 2024 16:30:58 +0100 Subject: [PATCH 023/211] refactor: improve devex of pop call contract --- crates/pop-cli/src/commands/call/contract.rs | 446 +++++++++---------- 1 file changed, 221 insertions(+), 225 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 85edaf173..a7dbbf1ca 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -10,6 +10,10 @@ use pop_contracts::{ use sp_weights::Weight; use std::path::PathBuf; +const DEFAULT_URL: &str = "ws://localhost:9944"; +const DEFAULT_URI: &str = "//Alice"; +const DEFAULT_PAYABLE_VALUE: &str = "0"; + #[derive(Args, Clone)] pub struct CallContractCommand { /// Path to the contract build directory. @@ -25,7 +29,7 @@ pub struct CallContractCommand { #[clap(long, num_args = 0..)] args: Vec, /// The value to be transferred as part of the call. - #[clap(name = "value", long, default_value = "0")] + #[clap(name = "value", long, default_value = DEFAULT_PAYABLE_VALUE)] value: String, /// Maximum amount of gas to be used for this command. /// If not specified it will perform a dry-run to estimate the gas consumed for the @@ -37,14 +41,14 @@ pub struct CallContractCommand { #[clap(long)] proof_size: Option, /// Websocket endpoint of a node. - #[clap(name = "url", long, value_parser, default_value = "ws://localhost:9944")] + #[clap(name = "url", long, value_parser, default_value = DEFAULT_URL)] url: url::Url, /// Secret key URI for the account calling the contract. /// /// e.g. /// - for a dev account "//Alice" /// - with a password "//Alice///SECRET_PASSWORD" - #[clap(name = "suri", long, short, default_value = "//Alice")] + #[clap(name = "suri", long, short, default_value = DEFAULT_URI)] suri: String, /// Submit an extrinsic for on-chain execution. #[clap(short('x'), long)] @@ -67,7 +71,7 @@ impl CallContractCommand { return Ok(()); }, }; - match execute_call(call_config, self.contract.is_none(), &mut cli::Cli).await { + match call_config.execute_call(self.message.is_none(), &mut cli::Cli).await { Ok(_) => Ok(()), Err(e) => { display_message(&e.to_string(), false, &mut cli::Cli)?; @@ -115,8 +119,8 @@ impl CallContractCommand { cli: &mut impl cli::traits::Cli, ) -> anyhow::Result { cli.intro("Call a contract")?; - let call_config = if self.contract.is_none() { - match guide_user_to_call_contract(None, None, None, self.dev_mode, cli).await { + let call_config = if self.message.is_none() { + match self.guide_user_to_call_contract(cli).await { Ok(config) => config, Err(e) => { return Err(anyhow!(format!("{}", e.to_string()))); @@ -127,41 +131,35 @@ impl CallContractCommand { }; Ok(call_config) } -} -/// Guide the user to call the contract. -async fn guide_user_to_call_contract( - contract_path: Option, - url: Option, - contract_address: Option, - dev_mode: bool, - cli: &mut impl cli::traits::Cli, -) -> anyhow::Result { - let contract_path: PathBuf = match contract_path { - Some(path) => path, - None => { - // Prompt for path. - let input_path: String = cli - .input("Where is your project located?") - .placeholder("./") - .default_input("./") - .interact()?; - PathBuf::from(input_path) - }, - }; - // Parse the contract metadata provided. If there error, do not prompt for more. - let messages = match get_messages(&contract_path) { - Ok(messages) => messages, - Err(e) => { - return Err(anyhow!(format!( - "Unable to fetch contract metadata: {}", - e.to_string().replace("Anyhow error: ", "") - ))); - }, - }; - let url: url::Url = match url { - Some(url) => url, - None => { + /// Guide the user to call the contract. + async fn guide_user_to_call_contract( + &self, + cli: &mut impl cli::traits::Cli, + ) -> anyhow::Result { + let contract_path: PathBuf = match &self.path { + Some(path) => path.to_path_buf(), + None => { + // Prompt for path. + let input_path: String = cli + .input("Where is your project located?") + .placeholder("./") + .default_input("./") + .interact()?; + PathBuf::from(input_path) + }, + }; + // Parse the contract metadata provided. If there is an error, do not prompt for more. + let messages = match get_messages(&contract_path) { + Ok(messages) => messages, + Err(e) => { + return Err(anyhow!(format!( + "Unable to fetch contract metadata: {}", + e.to_string().replace("Anyhow error: ", "") + ))); + }, + }; + let url = if self.url.as_str() == DEFAULT_URL { // Prompt for url. let url: String = cli .input("Where is your contract deployed?") @@ -169,218 +167,216 @@ async fn guide_user_to_call_contract( .default_input("ws://localhost:9944") .interact()?; url::Url::parse(&url)? - }, - }; - let contract_address: String = match contract_address { - Some(contract_address) => contract_address, - None => { - // Prompt for contract address. - let contract_address: String = cli - .input("Paste the on-chain contract address:") - .placeholder("e.g. 5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") - .validate(|input: &String| match parse_account(input) { + } else { + self.url.clone() + }; + let contract_address: String = match &self.contract { + Some(contract_address) => contract_address.to_string(), + None => { + // Prompt for contract address. + let contract_address: String = cli + .input("Paste the on-chain contract address:") + .placeholder("e.g. 5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") + .validate(|input: &String| match parse_account(input) { + Ok(_) => Ok(()), + Err(_) => Err("Invalid address."), + }) + .default_input("5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") + .interact()?; + contract_address + }, + }; + + let message = { + let mut prompt = cli.select("Select the message to call:"); + for select_message in messages { + prompt = prompt.item( + select_message.clone(), + format!("{}\n", &select_message.label), + &select_message.docs, + ); + } + prompt.interact()? + }; + + let mut contract_args = Vec::new(); + for arg in &message.args { + contract_args.push( + cli.input(format!("Enter the value for the parameter: {}", arg.label)) + .placeholder(&format!("Type required: {}", &arg.type_name)) + .interact()?, + ); + } + let mut value = "0".to_string(); + if message.payable { + value = cli + .input("Value to transfer to the call:") + .placeholder("0") + .default_input("0") + .validate(|input: &String| match input.parse::() { Ok(_) => Ok(()), - Err(_) => Err("Invalid address."), + Err(_) => Err("Invalid value."), }) - .default_input("5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") .interact()?; - contract_address - }, - }; - - let message = { - let mut prompt = cli.select("Select the message to call:"); - for select_message in messages { - prompt = prompt.item( - select_message.clone(), - format!("{}\n", &select_message.label), - &select_message.docs, - ); } - prompt.interact()? - }; - - let mut contract_args = Vec::new(); - for arg in &message.args { - contract_args.push( - cli.input(format!("Enter the value for the parameter: {}", arg.label)) - .placeholder(&format!("Type required: {}", &arg.type_name)) - .interact()?, - ); - } - let mut value = "0".to_string(); - if message.payable { - value = cli - .input("Value to transfer to the call:") - .placeholder("0") - .default_input("0") - .validate(|input: &String| match input.parse::() { - Ok(_) => Ok(()), - Err(_) => Err("Invalid value."), - }) - .interact()?; - } - let mut gas_limit: Option = None; - let mut proof_size: Option = None; - if message.mutates && !dev_mode { - // Prompt for gas limit and proof_size of the call. - let gas_limit_input: String = cli - .input("Enter the gas limit:") - .required(false) - .default_input("") - .placeholder("If left blank, an estimation will be used") - .interact()?; - gas_limit = gas_limit_input.parse::().ok(); // If blank or bad input, estimate it. - let proof_size_input: String = cli - .input("Enter the proof size limit:") - .required(false) - .placeholder("If left blank, an estimation will be used") - .default_input("") - .interact()?; - proof_size = proof_size_input.parse::().ok(); // If blank or bad input, estimate it. - } + let mut gas_limit: Option = None; + let mut proof_size: Option = None; + if message.mutates && !self.dev_mode { + // Prompt for gas limit and proof_size of the call. + let gas_limit_input: String = cli + .input("Enter the gas limit:") + .required(false) + .default_input("") + .placeholder("If left blank, an estimation will be used") + .interact()?; + gas_limit = gas_limit_input.parse::().ok(); // If blank or bad input, estimate it. + let proof_size_input: String = cli + .input("Enter the proof size limit:") + .required(false) + .placeholder("If left blank, an estimation will be used") + .default_input("") + .interact()?; + proof_size = proof_size_input.parse::().ok(); // If blank or bad input, estimate it. + } - // Who is calling the contract. - let suri: String = cli - .input("Signer calling the contract:") - .placeholder("//Alice") - .default_input("//Alice") - .interact()?; - - let mut is_call_confirmed: bool = true; - if message.mutates && !dev_mode { - is_call_confirmed = cli - .confirm("Do you want to execute the call? (Selecting 'No' will perform a dry run)") - .initial_value(true) - .interact()?; + // Who is calling the contract. + let suri = if self.suri == DEFAULT_URI { + // Prompt for uri. + cli.input("Signer calling the contract:") + .placeholder("//Alice") + .default_input("//Alice") + .interact()? + } else { + self.suri.clone() + }; + + let mut is_call_confirmed: bool = true; + if message.mutates && !self.dev_mode { + is_call_confirmed = cli + .confirm("Do you want to execute the call? (Selecting 'No' will perform a dry run)") + .initial_value(true) + .interact()?; + } + let call_command = CallContractCommand { + path: Some(contract_path), + contract: Some(contract_address), + message: Some(message.label.clone()), + args: contract_args, + value, + gas_limit, + proof_size, + url, + suri, + execute: if is_call_confirmed { message.mutates } else { false }, + dry_run: !is_call_confirmed, + dev_mode: self.dev_mode, + }; + cli.info(call_command.display())?; + Ok(call_command) } - let call_command = CallContractCommand { - path: Some(contract_path), - contract: Some(contract_address), - message: Some(message.label.clone()), - args: contract_args, - value, - gas_limit, - proof_size, - url, - suri, - execute: if is_call_confirmed { message.mutates } else { false }, - dry_run: !is_call_confirmed, - dev_mode, - }; - cli.info(call_command.display())?; - Ok(call_command) -} -/// Executes the call. -async fn execute_call( - call_config: CallContractCommand, - prompt_to_repeat_call: bool, - cli: &mut impl cli::traits::Cli, -) -> anyhow::Result<()> { - let contract = call_config - .contract - .clone() - .expect("contract can not be none as fallback above is interactive input; qed"); - let message = match call_config.message { - Some(m) => m, - None => { - return Err(anyhow!("Please specify the message to call.")); - }, - }; - - let call_exec = match set_up_call(CallOpts { - path: call_config.path.clone(), - contract, - message, - args: call_config.args, - value: call_config.value, - gas_limit: call_config.gas_limit, - proof_size: call_config.proof_size, - url: call_config.url.clone(), - suri: call_config.suri, - execute: call_config.execute, - }) - .await - { - Ok(call_exec) => call_exec, - Err(e) => { - return Err(anyhow!(format!("{}", e.root_cause().to_string()))); - }, - }; - - if call_config.dry_run { - let spinner = cliclack::spinner(); - spinner.start("Doing a dry run to estimate the gas..."); - match dry_run_gas_estimate_call(&call_exec).await { - Ok(w) => { - cli.info(format!("Gas limit: {:?}", w))?; - cli.warning("Your call has not been executed.")?; + /// Executes the call. + async fn execute_call( + &self, + prompt_to_repeat_call: bool, + cli: &mut impl cli::traits::Cli, + ) -> anyhow::Result<()> { + let message = self + .message + .clone() + .expect("message can not be none as fallback above is interactive input; qed"); + let contract = match &self.contract { + Some(contract) => contract.to_string(), + None => { + return Err(anyhow!("Please specify the contract address.")); }, + }; + let call_exec = match set_up_call(CallOpts { + path: self.path.clone(), + contract, + message, + args: self.args.clone(), + value: self.value.clone(), + gas_limit: self.gas_limit, + proof_size: self.proof_size, + url: self.url.clone(), + suri: self.suri.clone(), + execute: self.execute, + }) + .await + { + Ok(call_exec) => call_exec, Err(e) => { - spinner.error(format!("{e}")); - display_message("Call failed.", false, cli)?; + return Err(anyhow!(format!("{}", e.root_cause().to_string()))); }, }; - return Ok(()); - } - if !call_config.execute { - let spinner = cliclack::spinner(); - spinner.start("Calling the contract..."); - let call_dry_run_result = dry_run_call(&call_exec).await?; - cli.info(format!("Result: {}", call_dry_run_result))?; - cli.warning("Your call has not been executed.")?; - } else { - let weight_limit = if call_config.gas_limit.is_some() && call_config.proof_size.is_some() { - Weight::from_parts(call_config.gas_limit.unwrap(), call_config.proof_size.unwrap()) - } else { + if self.dry_run { let spinner = cliclack::spinner(); spinner.start("Doing a dry run to estimate the gas..."); match dry_run_gas_estimate_call(&call_exec).await { Ok(w) => { cli.info(format!("Gas limit: {:?}", w))?; - w + cli.warning("Your call has not been executed.")?; }, Err(e) => { spinner.error(format!("{e}")); - return Err(anyhow!("Call failed.")); + display_message("Call failed.", false, cli)?; }, - } - }; - let spinner = cliclack::spinner(); - spinner.start("Calling the contract..."); + }; + return Ok(()); + } - let call_result = call_smart_contract(call_exec, weight_limit, &call_config.url) - .await - .map_err(|err| anyhow!("{} {}", "ERROR:", format!("{err:?}")))?; + if !self.execute { + let spinner = cliclack::spinner(); + spinner.start("Calling the contract..."); + let call_dry_run_result = dry_run_call(&call_exec).await?; + cli.info(format!("Result: {}", call_dry_run_result))?; + cli.warning("Your call has not been executed.")?; + } else { + let weight_limit = if self.gas_limit.is_some() && self.proof_size.is_some() { + Weight::from_parts(self.gas_limit.unwrap(), self.proof_size.unwrap()) + } else { + let spinner = cliclack::spinner(); + spinner.start("Doing a dry run to estimate the gas..."); + match dry_run_gas_estimate_call(&call_exec).await { + Ok(w) => { + cli.info(format!("Gas limit: {:?}", w))?; + w + }, + Err(e) => { + spinner.error(format!("{e}")); + return Err(anyhow!("Call failed.")); + }, + } + }; + let spinner = cliclack::spinner(); + spinner.start("Calling the contract..."); - cli.info(call_result)?; - } - if prompt_to_repeat_call { - let another_call: bool = cli - .confirm("Do you want to do another call using the existing smart contract?") - .initial_value(false) - .interact()?; - if another_call { - // Remove only the prompt asking for another call. - console::Term::stderr().clear_last_lines(2)?; - let new_call_config = guide_user_to_call_contract( - call_config.path, - Some(call_config.url), - call_config.contract, - call_config.dev_mode, - cli, - ) - .await?; - Box::pin(execute_call(new_call_config, prompt_to_repeat_call, cli)).await?; + let call_result = call_smart_contract(call_exec, weight_limit, &self.url) + .await + .map_err(|err| anyhow!("{} {}", "ERROR:", format!("{err:?}")))?; + + cli.info(call_result)?; + } + if prompt_to_repeat_call { + let another_call: bool = cli + .confirm("Do you want to do another call using the existing smart contract?") + .initial_value(false) + .interact()?; + if another_call { + // Remove only the prompt asking for another call. + console::Term::stderr().clear_last_lines(2)?; + let new_call_config = self.guide_user_to_call_contract(cli).await?; + Box::pin(new_call_config.execute_call(prompt_to_repeat_call, cli)).await?; + } else { + display_message("Call completed successfully!", true, cli)?; + } } else { display_message("Call completed successfully!", true, cli)?; } - } else { - display_message("Call completed successfully!", true, cli)?; + Ok(()) } - Ok(()) } fn display_message(message: &str, success: bool, cli: &mut impl cli::traits::Cli) -> Result<()> { From 5f479fa15bfb5835bffaf2f693766990d66e27df Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 5 Nov 2024 17:30:57 +0100 Subject: [PATCH 024/211] test: adjust tests to refactor --- crates/pop-cli/src/commands/call/contract.rs | 183 +++++++++++++------ 1 file changed, 125 insertions(+), 58 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index a7dbbf1ca..3a8ccf349 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -10,7 +10,7 @@ use pop_contracts::{ use sp_weights::Weight; use std::path::PathBuf; -const DEFAULT_URL: &str = "ws://localhost:9944"; +const DEFAULT_URL: &str = "ws://localhost:9944/"; const DEFAULT_URI: &str = "//Alice"; const DEFAULT_PAYABLE_VALUE: &str = "0"; @@ -94,7 +94,7 @@ impl CallContractCommand { if !self.args.is_empty() { full_message.push_str(&format!(" --args {}", self.args.join(" "))); } - if self.value != "0" { + if self.value != DEFAULT_PAYABLE_VALUE { full_message.push_str(&format!(" --value {}", self.value)); } if let Some(gas_limit) = self.gas_limit { @@ -119,17 +119,11 @@ impl CallContractCommand { cli: &mut impl cli::traits::Cli, ) -> anyhow::Result { cli.intro("Call a contract")?; - let call_config = if self.message.is_none() { - match self.guide_user_to_call_contract(cli).await { - Ok(config) => config, - Err(e) => { - return Err(anyhow!(format!("{}", e.to_string()))); - }, - } + if self.message.is_none() { + self.guide_user_to_call_contract(cli).await } else { - self.clone() - }; - Ok(call_config) + Ok(self.clone()) + } } /// Guide the user to call the contract. @@ -138,7 +132,7 @@ impl CallContractCommand { cli: &mut impl cli::traits::Cli, ) -> anyhow::Result { let contract_path: PathBuf = match &self.path { - Some(path) => path.to_path_buf(), + Some(path) => path.clone(), None => { // Prompt for path. let input_path: String = cli @@ -171,7 +165,7 @@ impl CallContractCommand { self.url.clone() }; let contract_address: String = match &self.contract { - Some(contract_address) => contract_address.to_string(), + Some(contract_address) => contract_address.clone(), None => { // Prompt for contract address. let contract_address: String = cli @@ -189,9 +183,9 @@ impl CallContractCommand { let message = { let mut prompt = cli.select("Select the message to call:"); - for select_message in messages { + for select_message in &messages { prompt = prompt.item( - select_message.clone(), + select_message, format!("{}\n", &select_message.label), &select_message.docs, ); @@ -250,13 +244,14 @@ impl CallContractCommand { self.suri.clone() }; - let mut is_call_confirmed: bool = true; - if message.mutates && !self.dev_mode { - is_call_confirmed = cli - .confirm("Do you want to execute the call? (Selecting 'No' will perform a dry run)") + let is_call_confirmed = if message.mutates && !self.dev_mode { + cli.confirm("Do you want to execute the call? (Selecting 'No' will perform a dry run)") .initial_value(true) - .interact()?; - } + .interact()? + } else { + true + }; + let call_command = CallContractCommand { path: Some(contract_path), contract: Some(contract_address), @@ -281,10 +276,12 @@ impl CallContractCommand { prompt_to_repeat_call: bool, cli: &mut impl cli::traits::Cli, ) -> anyhow::Result<()> { - let message = self - .message - .clone() - .expect("message can not be none as fallback above is interactive input; qed"); + let message = match &self.message { + Some(message) => message.to_string(), + None => { + return Err(anyhow!("Please specify the message to call.")); + }, + }; let contract = match &self.contract { Some(contract) => contract.to_string(), None => { @@ -360,11 +357,11 @@ impl CallContractCommand { cli.info(call_result)?; } if prompt_to_repeat_call { - let another_call: bool = cli + if cli .confirm("Do you want to do another call using the existing smart contract?") .initial_value(false) - .interact()?; - if another_call { + .interact()? + { // Remove only the prompt asking for another call. console::Term::stderr().clear_last_lines(2)?; let new_call_config = self.guide_user_to_call_contract(cli).await?; @@ -463,7 +460,7 @@ mod tests { temp_dir.path().join("testing").display().to_string(), )); // Contract deployed on Pop Network testnet, test dry-run - execute_call(call_config, false, &mut cli).await?; + call_config.execute_call(false, &mut cli).await?; cli.verify() } @@ -510,7 +507,7 @@ mod tests { .expect_outro("Call completed successfully!"); // Contract deployed on Pop Network testnet, test get - let config_call = CallContractCommand { + let call_config = CallContractCommand { path: Some(temp_dir.path().join("testing")), contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), message: Some("get".to_string()), @@ -527,7 +524,7 @@ mod tests { .set_up_call_config(&mut cli) .await?; // Test the query. With true, it will prompt for another call. - execute_call(config_call, true, &mut cli).await?; + call_config.execute_call(true, &mut cli).await?; cli.verify() } @@ -576,7 +573,22 @@ mod tests { temp_dir.path().join("testing").display().to_string(), )); - let call_config = guide_user_to_call_contract(None, None, None, false, &mut cli).await?; + let call_config = CallContractCommand { + path: None, + contract: None, + message: None, + args: vec![].to_vec(), + value: DEFAULT_PAYABLE_VALUE.to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse(DEFAULT_URL)?, + suri: DEFAULT_URI.to_string(), + dry_run: false, + execute: false, + dev_mode: false, + } + .guide_user_to_call_contract(&mut cli) + .await?; assert_eq!( call_config.contract, Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) @@ -647,7 +659,22 @@ mod tests { temp_dir.path().join("testing").display().to_string(), )); - let call_config = guide_user_to_call_contract(None, None, None, false, &mut cli).await?; + let call_config = CallContractCommand { + path: None, + contract: None, + message: None, + args: vec![].to_vec(), + value: DEFAULT_PAYABLE_VALUE.to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse(DEFAULT_URL)?, + suri: DEFAULT_URI.to_string(), + dry_run: false, + execute: false, + dev_mode: false, + } + .guide_user_to_call_contract(&mut cli) + .await?; assert_eq!( call_config.contract, Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) @@ -716,7 +743,22 @@ mod tests { temp_dir.path().join("testing").display().to_string(), )); - let call_config = guide_user_to_call_contract(None, None, None, true, &mut cli).await?; + let call_config = CallContractCommand { + path: None, + contract: None, + message: None, + args: vec![].to_vec(), + value: DEFAULT_PAYABLE_VALUE.to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse(DEFAULT_URL)?, + suri: DEFAULT_URI.to_string(), + dry_run: false, + execute: false, + dev_mode: true, + } + .guide_user_to_call_contract(&mut cli) + .await?; assert_eq!( call_config.contract, Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) @@ -744,14 +786,25 @@ mod tests { async fn guide_user_to_call_contract_fails_not_build() -> Result<()> { let temp_dir = new_environment("testing")?; let mut cli = MockCli::new(); - assert!( - matches!(guide_user_to_call_contract(Some(temp_dir.path().join("testing")), None, None, false, &mut cli).await, anyhow::Result::Err(message) if message.to_string().contains("Unable to fetch contract metadata: Failed to find any contract artifacts in target directory.")) - ); + assert!(matches!(CallContractCommand { + path: Some(temp_dir.path().join("testing")), + contract: None, + message: None, + args: vec![].to_vec(), + value: "0".to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, + suri: "//Alice".to_string(), + dry_run: false, + execute: false, + dev_mode: false, + }.guide_user_to_call_contract(&mut cli).await, anyhow::Result::Err(message) if message.to_string().contains("Unable to fetch contract metadata: Failed to find any contract artifacts in target directory."))); cli.verify() } #[tokio::test] - async fn call_contract_fails_no_message() -> Result<()> { + async fn execute_contract_fails_no_message_or_contract() -> Result<()> { let temp_dir = new_environment("testing")?; let mut current_dir = env::current_dir().expect("Failed to get current directory"); current_dir.pop(); @@ -761,29 +814,43 @@ mod tests { current_dir.join("pop-contracts/tests/files/testing.json"), )?; - let mut cli = MockCli::new().expect_intro(&"Call a contract"); - - let call_config = CallContractCommand { - path: Some(temp_dir.path().join("testing")), - contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), - message: None, - args: vec![].to_vec(), - value: "0".to_string(), - gas_limit: None, - proof_size: None, - url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, - suri: "//Alice".to_string(), - dry_run: false, - execute: false, - dev_mode: false, - } - .set_up_call_config(&mut cli) - .await?; + let mut cli = MockCli::new(); assert!(matches!( - execute_call(call_config, false, &mut cli).await, + CallContractCommand { + path: Some(temp_dir.path().join("testing")), + contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), + message: None, + args: vec![].to_vec(), + value: "0".to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, + suri: "//Alice".to_string(), + dry_run: false, + execute: false, + dev_mode: false, + }.execute_call(false, &mut cli).await, anyhow::Result::Err(message) if message.to_string() == "Please specify the message to call." )); + assert!(matches!( + CallContractCommand { + path: Some(temp_dir.path().join("testing")), + contract: None, + message: Some("get".to_string()), + args: vec![].to_vec(), + value: "0".to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, + suri: "//Alice".to_string(), + dry_run: false, + execute: false, + dev_mode: false, + }.execute_call(false, &mut cli).await, + anyhow::Result::Err(message) if message.to_string() == "Please specify the contract address." + )); + cli.verify() } From 5be201b233311a8fd509199954d047cdb6607932 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Wed, 6 Nov 2024 11:39:24 +0100 Subject: [PATCH 025/211] chore: reset_for_new_call fields --- crates/pop-cli/src/commands/call/contract.rs | 46 ++++++++++++-------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 3a8ccf349..132966e74 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -64,13 +64,14 @@ pub struct CallContractCommand { impl CallContractCommand { /// Executes the command. pub(crate) async fn execute(self) -> Result<()> { - let call_config: CallContractCommand = match self.set_up_call_config(&mut cli::Cli).await { - Ok(call_config) => call_config, - Err(e) => { - display_message(&e.to_string(), false, &mut cli::Cli)?; - return Ok(()); - }, - }; + let mut call_config: CallContractCommand = + match self.set_up_call_config(&mut cli::Cli).await { + Ok(call_config) => call_config, + Err(e) => { + display_message(&e.to_string(), false, &mut cli::Cli)?; + return Ok(()); + }, + }; match call_config.execute_call(self.message.is_none(), &mut cli::Cli).await { Ok(_) => Ok(()), Err(e) => { @@ -201,8 +202,8 @@ impl CallContractCommand { .interact()?, ); } - let mut value = "0".to_string(); - if message.payable { + let mut value = self.value.clone(); + if message.payable && value == DEFAULT_PAYABLE_VALUE { value = cli .input("Value to transfer to the call:") .placeholder("0") @@ -213,9 +214,9 @@ impl CallContractCommand { }) .interact()?; } - let mut gas_limit: Option = None; - let mut proof_size: Option = None; - if message.mutates && !self.dev_mode { + let mut gas_limit: Option = self.gas_limit; + let mut proof_size: Option = self.proof_size; + if message.mutates && !self.dev_mode && gas_limit.is_none() { // Prompt for gas limit and proof_size of the call. let gas_limit_input: String = cli .input("Enter the gas limit:") @@ -224,6 +225,9 @@ impl CallContractCommand { .placeholder("If left blank, an estimation will be used") .interact()?; gas_limit = gas_limit_input.parse::().ok(); // If blank or bad input, estimate it. + } + + if message.mutates && !self.dev_mode && proof_size.is_none() { let proof_size_input: String = cli .input("Enter the proof size limit:") .required(false) @@ -272,7 +276,7 @@ impl CallContractCommand { /// Executes the call. async fn execute_call( - &self, + &mut self, prompt_to_repeat_call: bool, cli: &mut impl cli::traits::Cli, ) -> anyhow::Result<()> { @@ -304,7 +308,7 @@ impl CallContractCommand { { Ok(call_exec) => call_exec, Err(e) => { - return Err(anyhow!(format!("{}", e.root_cause().to_string()))); + return Err(anyhow!(format!("{}", e.to_string()))); }, }; @@ -364,7 +368,8 @@ impl CallContractCommand { { // Remove only the prompt asking for another call. console::Term::stderr().clear_last_lines(2)?; - let new_call_config = self.guide_user_to_call_contract(cli).await?; + self.reset_for_new_call(); + let mut new_call_config = self.guide_user_to_call_contract(cli).await?; Box::pin(new_call_config.execute_call(prompt_to_repeat_call, cli)).await?; } else { display_message("Call completed successfully!", true, cli)?; @@ -374,6 +379,13 @@ impl CallContractCommand { } Ok(()) } + + /// Resets message specific fields to default values for a new call. + fn reset_for_new_call(&mut self) { + self.value = DEFAULT_PAYABLE_VALUE.to_string(); + self.gas_limit = None; + self.proof_size = None; + } } fn display_message(message: &str, success: bool, cli: &mut impl cli::traits::Cli) -> Result<()> { @@ -439,7 +451,7 @@ mod tests { .expect_warning("Your call has not been executed.") .expect_info("Gas limit: Weight { ref_time: 100, proof_size: 10 }"); - let call_config = CallContractCommand { + let mut call_config = CallContractCommand { path: Some(temp_dir.path().join("testing")), contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), message: Some("flip".to_string()), @@ -507,7 +519,7 @@ mod tests { .expect_outro("Call completed successfully!"); // Contract deployed on Pop Network testnet, test get - let call_config = CallContractCommand { + let mut call_config = CallContractCommand { path: Some(temp_dir.path().join("testing")), contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), message: Some("get".to_string()), From e55d1a645185a90ea6c6a172130915f7c54abb84 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Wed, 6 Nov 2024 19:44:30 +0100 Subject: [PATCH 026/211] fix: build contract if has not been built --- crates/pop-cli/src/commands/call/contract.rs | 36 +++++++++++++-- crates/pop-cli/src/commands/up/contract.rs | 45 +----------------- crates/pop-cli/src/common/build.rs | 48 ++++++++++++++++++++ crates/pop-cli/src/common/mod.rs | 2 + 4 files changed, 85 insertions(+), 46 deletions(-) create mode 100644 crates/pop-cli/src/common/build.rs diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 132966e74..24bfc06ad 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -1,11 +1,15 @@ // SPDX-License-Identifier: GPL-3.0 -use crate::cli::{self, traits::*}; +use crate::{ + cli::{self, traits::*}, + common::build::has_contract_been_built, +}; use anyhow::{anyhow, Result}; use clap::Args; +use cliclack::spinner; use pop_contracts::{ - call_smart_contract, dry_run_call, dry_run_gas_estimate_call, get_messages, parse_account, - set_up_call, CallOpts, + build_smart_contract, call_smart_contract, dry_run_call, dry_run_gas_estimate_call, + get_messages, parse_account, set_up_call, CallOpts, Verbosity, }; use sp_weights::Weight; use std::path::PathBuf; @@ -64,6 +68,7 @@ pub struct CallContractCommand { impl CallContractCommand { /// Executes the command. pub(crate) async fn execute(self) -> Result<()> { + self.ensure_contract_built(&mut cli::Cli).await?; let mut call_config: CallContractCommand = match self.set_up_call_config(&mut cli::Cli).await { Ok(call_config) => call_config, @@ -114,6 +119,31 @@ impl CallContractCommand { full_message } + /// Checks if the contract has been built; if not, builds it. + async fn ensure_contract_built(&self, cli: &mut impl cli::traits::Cli) -> Result<()> { + // Check if build exists in the specified "Contract build directory" + if !has_contract_been_built(self.path.as_deref()) { + // Build the contract in release mode + cli.warning("NOTE: contract has not yet been built.")?; + let spinner = spinner(); + spinner.start("Building contract in RELEASE mode..."); + let result = match build_smart_contract(self.path.as_deref(), true, Verbosity::Quiet) { + Ok(result) => result, + Err(e) => { + return Err(anyhow!(format!( + "🚫 An error occurred building your contract: {}\nUse `pop build` to retry with build output.", + e.to_string() + ))); + }, + }; + spinner.stop(format!( + "Your contract artifacts are ready. You can find them in: {}", + result.target_directory.display() + )); + } + Ok(()) + } + /// Set up the config call. async fn set_up_call_config( &self, diff --git a/crates/pop-cli/src/commands/up/contract.rs b/crates/pop-cli/src/commands/up/contract.rs index 72a096973..d357d958b 100644 --- a/crates/pop-cli/src/commands/up/contract.rs +++ b/crates/pop-cli/src/commands/up/contract.rs @@ -2,13 +2,12 @@ use crate::{ cli::{traits::Cli as _, Cli}, - common::contracts::check_contracts_node_and_prompt, + common::{build::has_contract_been_built, contracts::check_contracts_node_and_prompt}, style::style, }; use clap::Args; use cliclack::{confirm, log, log::error, spinner}; use console::{Emoji, Style}; -use pop_common::manifest::from_path; use pop_contracts::{ build_smart_contract, dry_run_gas_estimate_instantiate, dry_run_upload, instantiate_smart_contract, is_chain_alive, parse_hex_bytes, run_contracts_node, @@ -17,7 +16,7 @@ use pop_contracts::{ use sp_core::Bytes; use sp_weights::Weight; use std::{ - path::{Path, PathBuf}, + path::PathBuf, process::{Child, Command}, }; use tempfile::NamedTempFile; @@ -312,28 +311,9 @@ impl From for UpOpts { } } -/// Checks if a contract has been built by verifying the existence of the build directory and the -/// .contract file. -/// -/// # Arguments -/// * `path` - An optional path to the project directory. If no path is provided, the current -/// directory is used. -pub fn has_contract_been_built(path: Option<&Path>) -> bool { - let project_path = path.unwrap_or_else(|| Path::new("./")); - let manifest = match from_path(Some(project_path)) { - Ok(manifest) => manifest, - Err(_) => return false, - }; - let contract_name = manifest.package().name(); - project_path.join("target/ink").exists() && - project_path.join(format!("target/ink/{}.contract", contract_name)).exists() -} - #[cfg(test)] mod tests { use super::*; - use duct::cmd; - use std::fs::{self, File}; use url::Url; #[test] @@ -369,25 +349,4 @@ mod tests { ); Ok(()) } - - #[test] - fn has_contract_been_built_works() -> anyhow::Result<()> { - let temp_dir = tempfile::tempdir()?; - let path = temp_dir.path(); - - // Standard rust project - let name = "hello_world"; - cmd("cargo", ["new", name]).dir(&path).run()?; - let contract_path = path.join(name); - assert!(!has_contract_been_built(Some(&contract_path))); - - cmd("cargo", ["build"]).dir(&contract_path).run()?; - // Mock build directory - fs::create_dir(&contract_path.join("target/ink"))?; - assert!(!has_contract_been_built(Some(&path.join(name)))); - // Create a mocked .contract file inside the target directory - File::create(contract_path.join(format!("target/ink/{}.contract", name)))?; - assert!(has_contract_been_built(Some(&path.join(name)))); - Ok(()) - } } diff --git a/crates/pop-cli/src/common/build.rs b/crates/pop-cli/src/common/build.rs new file mode 100644 index 000000000..6bc7fd4d2 --- /dev/null +++ b/crates/pop-cli/src/common/build.rs @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-3.0 + +use pop_common::manifest::from_path; +use std::path::Path; +/// Checks if a contract has been built by verifying the existence of the build directory and the +/// .contract file. +/// +/// # Arguments +/// * `path` - An optional path to the project directory. If no path is provided, the current +/// directory is used. +pub fn has_contract_been_built(path: Option<&Path>) -> bool { + let project_path = path.unwrap_or_else(|| Path::new("./")); + let manifest = match from_path(Some(project_path)) { + Ok(manifest) => manifest, + Err(_) => return false, + }; + let contract_name = manifest.package().name(); + project_path.join("target/ink").exists() && + project_path.join(format!("target/ink/{}.contract", contract_name)).exists() +} + +#[cfg(test)] +mod tests { + use super::*; + use duct::cmd; + use std::fs::{self, File}; + + #[test] + fn has_contract_been_built_works() -> anyhow::Result<()> { + let temp_dir = tempfile::tempdir()?; + let path = temp_dir.path(); + + // Standard rust project + let name = "hello_world"; + cmd("cargo", ["new", name]).dir(&path).run()?; + let contract_path = path.join(name); + assert!(!has_contract_been_built(Some(&contract_path))); + + cmd("cargo", ["build"]).dir(&contract_path).run()?; + // Mock build directory + fs::create_dir(&contract_path.join("target/ink"))?; + assert!(!has_contract_been_built(Some(&path.join(name)))); + // Create a mocked .contract file inside the target directory + File::create(contract_path.join(format!("target/ink/{}.contract", name)))?; + assert!(has_contract_been_built(Some(&path.join(name)))); + Ok(()) + } +} diff --git a/crates/pop-cli/src/common/mod.rs b/crates/pop-cli/src/common/mod.rs index 1cb3ee579..0469db60a 100644 --- a/crates/pop-cli/src/common/mod.rs +++ b/crates/pop-cli/src/common/mod.rs @@ -1,5 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 +#[cfg(feature = "contract")] +pub mod build; #[cfg(feature = "contract")] pub mod contracts; pub mod helpers; From eaad4be11e7798d8169f3839fed8c41e57026eba Mon Sep 17 00:00:00 2001 From: Frank Bell <60948618+evilrobot-01@users.noreply.github.com> Date: Wed, 6 Nov 2024 22:03:35 +0000 Subject: [PATCH 027/211] refactor: use command state (#338) Merged set_up_call_config and guide_user_to_call_contract into a single function. Also adds short symbols for arguments. --- crates/pop-cli/src/commands/call/contract.rs | 312 +++++++++---------- 1 file changed, 152 insertions(+), 160 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 24bfc06ad..1a559ebc7 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -24,7 +24,7 @@ pub struct CallContractCommand { #[arg(short = 'p', long)] path: Option, /// The address of the contract to call. - #[clap(name = "contract", long, env = "CONTRACT")] + #[clap(name = "contract", short = 'c', long, env = "CONTRACT")] contract: Option, /// The name of the contract message to call. #[clap(long, short)] @@ -33,19 +33,19 @@ pub struct CallContractCommand { #[clap(long, num_args = 0..)] args: Vec, /// The value to be transferred as part of the call. - #[clap(name = "value", long, default_value = DEFAULT_PAYABLE_VALUE)] + #[clap(name = "value", short = 'v', long, default_value = DEFAULT_PAYABLE_VALUE)] value: String, /// Maximum amount of gas to be used for this command. /// If not specified it will perform a dry-run to estimate the gas consumed for the /// call. - #[clap(name = "gas", long)] + #[clap(name = "gas", short = 'g', long)] gas_limit: Option, /// Maximum proof size for this command. /// If not specified it will perform a dry-run to estimate the proof size required. - #[clap(long)] + #[clap(short = 'P', long)] proof_size: Option, /// Websocket endpoint of a node. - #[clap(name = "url", long, value_parser, default_value = DEFAULT_URL)] + #[clap(name = "url", short = 'u', long, value_parser, default_value = DEFAULT_URL)] url: url::Url, /// Secret key URI for the account calling the contract. /// @@ -58,7 +58,7 @@ pub struct CallContractCommand { #[clap(short('x'), long)] execute: bool, /// Perform a dry-run via RPC to estimate the gas usage. This does not submit a transaction. - #[clap(long, conflicts_with = "execute")] + #[clap(short = 'D', long, conflicts_with = "execute")] dry_run: bool, /// Enables developer mode, bypassing certain user prompts for faster testing. /// Recommended for testing and local development only. @@ -67,23 +67,21 @@ pub struct CallContractCommand { } impl CallContractCommand { /// Executes the command. - pub(crate) async fn execute(self) -> Result<()> { + pub(crate) async fn execute(mut self) -> Result<()> { + // Ensure contract is built. self.ensure_contract_built(&mut cli::Cli).await?; - let mut call_config: CallContractCommand = - match self.set_up_call_config(&mut cli::Cli).await { - Ok(call_config) => call_config, - Err(e) => { - display_message(&e.to_string(), false, &mut cli::Cli)?; - return Ok(()); - }, - }; - match call_config.execute_call(self.message.is_none(), &mut cli::Cli).await { - Ok(_) => Ok(()), - Err(e) => { - display_message(&e.to_string(), false, &mut cli::Cli)?; - Ok(()) - }, + // Check if message specified via command line argument. + let prompt_to_repeat_call = self.message.is_none(); + // Configure the call based on command line arguments/call UI. + if let Err(e) = self.configure(&mut cli::Cli, false).await { + display_message(&e.to_string(), false, &mut cli::Cli)?; + return Ok(()); + }; + // Finally execute the call. + if let Err(e) = self.execute_call(&mut cli::Cli, prompt_to_repeat_call).await { + display_message(&e.to_string(), false, &mut cli::Cli)?; } + Ok(()) } fn display(&self) -> String { @@ -120,7 +118,7 @@ impl CallContractCommand { } /// Checks if the contract has been built; if not, builds it. - async fn ensure_contract_built(&self, cli: &mut impl cli::traits::Cli) -> Result<()> { + async fn ensure_contract_built(&self, cli: &mut impl Cli) -> Result<()> { // Check if build exists in the specified "Contract build directory" if !has_contract_been_built(self.path.as_deref()) { // Build the contract in release mode @@ -144,36 +142,39 @@ impl CallContractCommand { Ok(()) } - /// Set up the config call. - async fn set_up_call_config( - &self, - cli: &mut impl cli::traits::Cli, - ) -> anyhow::Result { - cli.intro("Call a contract")?; - if self.message.is_none() { - self.guide_user_to_call_contract(cli).await - } else { - Ok(self.clone()) + /// Configure the call based on command line arguments/call UI. + async fn configure(&mut self, cli: &mut impl Cli, repeat: bool) -> Result<()> { + // Show intro on first run. + if !repeat { + cli.intro("Call a contract")?; + } + + // If message has been specified via command line arguments, return early. + if self.message.is_some() { + return Ok(()); } - } - /// Guide the user to call the contract. - async fn guide_user_to_call_contract( - &self, - cli: &mut impl cli::traits::Cli, - ) -> anyhow::Result { - let contract_path: PathBuf = match &self.path { - Some(path) => path.clone(), + // Resolve path. + let contract_path = match self.path.as_ref() { None => { - // Prompt for path. - let input_path: String = cli - .input("Where is your project located?") - .placeholder("./") - .default_input("./") - .interact()?; - PathBuf::from(input_path) + let path = Some(PathBuf::from("./")); + if has_contract_been_built(path.as_deref()) { + self.path = path; + } else { + // Prompt for path. + let input_path: String = cli + .input("Where is your project located?") + .placeholder("./") + .default_input("./") + .interact()?; + self.path = Some(PathBuf::from(input_path)); + } + + self.path.as_ref().unwrap() }, + Some(p) => p, }; + // Parse the contract metadata provided. If there is an error, do not prompt for more. let messages = match get_messages(&contract_path) { Ok(messages) => messages, @@ -184,34 +185,34 @@ impl CallContractCommand { ))); }, }; - let url = if self.url.as_str() == DEFAULT_URL { + + // Resolve url. + if !repeat && self.url.as_str() == DEFAULT_URL { // Prompt for url. let url: String = cli .input("Where is your contract deployed?") .placeholder("ws://localhost:9944") .default_input("ws://localhost:9944") .interact()?; - url::Url::parse(&url)? - } else { - self.url.clone() + self.url = url::Url::parse(&url)? }; - let contract_address: String = match &self.contract { - Some(contract_address) => contract_address.clone(), - None => { - // Prompt for contract address. - let contract_address: String = cli - .input("Paste the on-chain contract address:") - .placeholder("e.g. 5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") - .validate(|input: &String| match parse_account(input) { - Ok(_) => Ok(()), - Err(_) => Err("Invalid address."), - }) - .default_input("5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") - .interact()?; - contract_address - }, + + // Resolve contract address. + if let None = self.contract { + // Prompt for contract address. + let contract_address: String = cli + .input("Paste the on-chain contract address:") + .placeholder("e.g. 5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") + .validate(|input: &String| match parse_account(input) { + Ok(_) => Ok(()), + Err(_) => Err("Invalid address."), + }) + .default_input("5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") + .interact()?; + self.contract = Some(contract_address); }; + // Resolve message. let message = { let mut prompt = cli.select("Select the message to call:"); for select_message in &messages { @@ -221,9 +222,12 @@ impl CallContractCommand { &select_message.docs, ); } - prompt.interact()? + let message = prompt.interact()?; + self.message = Some(message.label.clone()); + message }; + // Resolve message arguments. let mut contract_args = Vec::new(); for arg in &message.args { contract_args.push( @@ -232,9 +236,11 @@ impl CallContractCommand { .interact()?, ); } - let mut value = self.value.clone(); - if message.payable && value == DEFAULT_PAYABLE_VALUE { - value = cli + self.args = contract_args; + + // Resolve value. + if message.payable && self.value == DEFAULT_PAYABLE_VALUE { + self.value = cli .input("Value to transfer to the call:") .placeholder("0") .default_input("0") @@ -244,9 +250,9 @@ impl CallContractCommand { }) .interact()?; } - let mut gas_limit: Option = self.gas_limit; - let mut proof_size: Option = self.proof_size; - if message.mutates && !self.dev_mode && gas_limit.is_none() { + + // Resolve gas limit. + if message.mutates && !self.dev_mode && self.gas_limit.is_none() { // Prompt for gas limit and proof_size of the call. let gas_limit_input: String = cli .input("Enter the gas limit:") @@ -254,30 +260,31 @@ impl CallContractCommand { .default_input("") .placeholder("If left blank, an estimation will be used") .interact()?; - gas_limit = gas_limit_input.parse::().ok(); // If blank or bad input, estimate it. + self.gas_limit = gas_limit_input.parse::().ok(); // If blank or bad input, estimate it. } - if message.mutates && !self.dev_mode && proof_size.is_none() { + // Resolve proof size. + if message.mutates && !self.dev_mode && self.proof_size.is_none() { let proof_size_input: String = cli .input("Enter the proof size limit:") .required(false) .placeholder("If left blank, an estimation will be used") .default_input("") .interact()?; - proof_size = proof_size_input.parse::().ok(); // If blank or bad input, estimate it. + self.proof_size = proof_size_input.parse::().ok(); // If blank or bad input, estimate it. } - // Who is calling the contract. - let suri = if self.suri == DEFAULT_URI { + // Resolve who is calling the contract. + if self.suri == DEFAULT_URI { // Prompt for uri. - cli.input("Signer calling the contract:") + self.suri = cli + .input("Signer calling the contract:") .placeholder("//Alice") .default_input("//Alice") - .interact()? - } else { - self.suri.clone() + .interact()?; }; + // Finally prompt for confirmation. let is_call_confirmed = if message.mutates && !self.dev_mode { cli.confirm("Do you want to execute the call? (Selecting 'No' will perform a dry run)") .initial_value(true) @@ -285,31 +292,19 @@ impl CallContractCommand { } else { true }; + self.execute = is_call_confirmed && message.mutates; + self.dry_run = !is_call_confirmed; - let call_command = CallContractCommand { - path: Some(contract_path), - contract: Some(contract_address), - message: Some(message.label.clone()), - args: contract_args, - value, - gas_limit, - proof_size, - url, - suri, - execute: if is_call_confirmed { message.mutates } else { false }, - dry_run: !is_call_confirmed, - dev_mode: self.dev_mode, - }; - cli.info(call_command.display())?; - Ok(call_command) + cli.info(self.display())?; + Ok(()) } - /// Executes the call. + /// Execute the call. async fn execute_call( &mut self, + cli: &mut impl Cli, prompt_to_repeat_call: bool, - cli: &mut impl cli::traits::Cli, - ) -> anyhow::Result<()> { + ) -> Result<()> { let message = match &self.message { Some(message) => message.to_string(), None => { @@ -343,7 +338,7 @@ impl CallContractCommand { }; if self.dry_run { - let spinner = cliclack::spinner(); + let spinner = spinner(); spinner.start("Doing a dry run to estimate the gas..."); match dry_run_gas_estimate_call(&call_exec).await { Ok(w) => { @@ -359,7 +354,7 @@ impl CallContractCommand { } if !self.execute { - let spinner = cliclack::spinner(); + let spinner = spinner(); spinner.start("Calling the contract..."); let call_dry_run_result = dry_run_call(&call_exec).await?; cli.info(format!("Result: {}", call_dry_run_result))?; @@ -368,7 +363,7 @@ impl CallContractCommand { let weight_limit = if self.gas_limit.is_some() && self.proof_size.is_some() { Weight::from_parts(self.gas_limit.unwrap(), self.proof_size.unwrap()) } else { - let spinner = cliclack::spinner(); + let spinner = spinner(); spinner.start("Doing a dry run to estimate the gas..."); match dry_run_gas_estimate_call(&call_exec).await { Ok(w) => { @@ -381,7 +376,7 @@ impl CallContractCommand { }, } }; - let spinner = cliclack::spinner(); + let spinner = spinner(); spinner.start("Calling the contract..."); let call_result = call_smart_contract(call_exec, weight_limit, &self.url) @@ -390,35 +385,37 @@ impl CallContractCommand { cli.info(call_result)?; } - if prompt_to_repeat_call { - if cli - .confirm("Do you want to do another call using the existing smart contract?") - .initial_value(false) - .interact()? - { - // Remove only the prompt asking for another call. - console::Term::stderr().clear_last_lines(2)?; - self.reset_for_new_call(); - let mut new_call_config = self.guide_user_to_call_contract(cli).await?; - Box::pin(new_call_config.execute_call(prompt_to_repeat_call, cli)).await?; - } else { - display_message("Call completed successfully!", true, cli)?; - } - } else { + + // Prompt for any additional calls. + if !prompt_to_repeat_call { display_message("Call completed successfully!", true, cli)?; + return Ok(()); + } + if cli + .confirm("Do you want to perform another call using the existing smart contract?") + .initial_value(false) + .interact()? + { + // Reset specific items from the last call and repeat. + self.reset_for_new_call(); + self.configure(cli, true).await?; + Box::pin(self.execute_call(cli, prompt_to_repeat_call)).await + } else { + display_message("Contract calling complete.", true, cli)?; + Ok(()) } - Ok(()) } /// Resets message specific fields to default values for a new call. fn reset_for_new_call(&mut self) { + self.message = None; self.value = DEFAULT_PAYABLE_VALUE.to_string(); self.gas_limit = None; self.proof_size = None; } } -fn display_message(message: &str, success: bool, cli: &mut impl cli::traits::Cli) -> Result<()> { +fn display_message(message: &str, success: bool, cli: &mut impl Cli) -> Result<()> { if success { cli.outro(message)?; } else { @@ -494,15 +491,14 @@ mod tests { dry_run: true, execute: false, dev_mode: false, - } - .set_up_call_config(&mut cli) - .await?; + }; + call_config.configure(&mut cli, false).await?; assert_eq!(call_config.display(), format!( "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message flip --gas 100 --proof_size 10 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --dry_run", temp_dir.path().join("testing").display().to_string(), )); // Contract deployed on Pop Network testnet, test dry-run - call_config.execute_call(false, &mut cli).await?; + call_config.execute_call(&mut cli, false).await?; cli.verify() } @@ -526,11 +522,11 @@ mod tests { .expect_intro(&"Call a contract") .expect_warning("Your call has not been executed.") .expect_confirm( - "Do you want to do another call using the existing smart contract?", + "Do you want to perform another call using the existing smart contract?", false, ) .expect_confirm( - "Do you want to do another call using the existing smart contract?", + "Do you want to perform another call using the existing smart contract?", true, ) .expect_select::( @@ -546,7 +542,7 @@ mod tests { temp_dir.path().join("testing").display().to_string(), )) .expect_warning("Your call has not been executed.") - .expect_outro("Call completed successfully!"); + .expect_outro("Contract calling complete."); // Contract deployed on Pop Network testnet, test get let mut call_config = CallContractCommand { @@ -562,11 +558,10 @@ mod tests { dry_run: false, execute: false, dev_mode: false, - } - .set_up_call_config(&mut cli) - .await?; + }; + call_config.configure(&mut cli, false).await?; // Test the query. With true, it will prompt for another call. - call_config.execute_call(true, &mut cli).await?; + call_config.execute_call(&mut cli, true).await?; cli.verify() } @@ -615,7 +610,7 @@ mod tests { temp_dir.path().join("testing").display().to_string(), )); - let call_config = CallContractCommand { + let mut call_config = CallContractCommand { path: None, contract: None, message: None, @@ -628,9 +623,8 @@ mod tests { dry_run: false, execute: false, dev_mode: false, - } - .guide_user_to_call_contract(&mut cli) - .await?; + }; + call_config.configure(&mut cli, false).await?; assert_eq!( call_config.contract, Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) @@ -701,7 +695,7 @@ mod tests { temp_dir.path().join("testing").display().to_string(), )); - let call_config = CallContractCommand { + let mut call_config = CallContractCommand { path: None, contract: None, message: None, @@ -714,9 +708,8 @@ mod tests { dry_run: false, execute: false, dev_mode: false, - } - .guide_user_to_call_contract(&mut cli) - .await?; + }; + call_config.configure(&mut cli, false).await?; assert_eq!( call_config.contract, Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) @@ -785,7 +778,7 @@ mod tests { temp_dir.path().join("testing").display().to_string(), )); - let call_config = CallContractCommand { + let mut call_config = CallContractCommand { path: None, contract: None, message: None, @@ -798,9 +791,8 @@ mod tests { dry_run: false, execute: false, dev_mode: true, - } - .guide_user_to_call_contract(&mut cli) - .await?; + }; + call_config.configure(&mut cli, false).await?; assert_eq!( call_config.contract, Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) @@ -829,19 +821,19 @@ mod tests { let temp_dir = new_environment("testing")?; let mut cli = MockCli::new(); assert!(matches!(CallContractCommand { - path: Some(temp_dir.path().join("testing")), - contract: None, - message: None, - args: vec![].to_vec(), - value: "0".to_string(), - gas_limit: None, - proof_size: None, - url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, - suri: "//Alice".to_string(), - dry_run: false, - execute: false, - dev_mode: false, - }.guide_user_to_call_contract(&mut cli).await, anyhow::Result::Err(message) if message.to_string().contains("Unable to fetch contract metadata: Failed to find any contract artifacts in target directory."))); + path: Some(temp_dir.path().join("testing")), + contract: None, + message: None, + args: vec![].to_vec(), + value: "0".to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, + suri: "//Alice".to_string(), + dry_run: false, + execute: false, + dev_mode: false, + }.configure(&mut cli, false).await, Err(message) if message.to_string().contains("Unable to fetch contract metadata: Failed to find any contract artifacts in target directory."))); cli.verify() } @@ -871,7 +863,7 @@ mod tests { dry_run: false, execute: false, dev_mode: false, - }.execute_call(false, &mut cli).await, + }.execute_call(&mut cli, false).await, anyhow::Result::Err(message) if message.to_string() == "Please specify the message to call." )); @@ -889,7 +881,7 @@ mod tests { dry_run: false, execute: false, dev_mode: false, - }.execute_call(false, &mut cli).await, + }.execute_call(&mut cli, false).await, anyhow::Result::Err(message) if message.to_string() == "Please specify the contract address." )); From 4d54652fc1f9c3f090e11a17211c512268c717ac Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Mon, 4 Nov 2024 18:26:58 +0100 Subject: [PATCH 028/211] fix: automatically add some or none to Option argument --- crates/pop-cli/src/commands/call/contract.rs | 4 +-- crates/pop-contracts/src/call/metadata.rs | 35 ++++++++++++++++++++ crates/pop-contracts/src/errors.rs | 2 ++ crates/pop-contracts/src/lib.rs | 2 +- 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 1a559ebc7..fc9b7787a 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -30,7 +30,7 @@ pub struct CallContractCommand { #[clap(long, short)] message: Option, /// The constructor arguments, encoded as strings. - #[clap(long, num_args = 0..)] + #[clap(long, num_args = 0.., value_delimiter = ',')] args: Vec, /// The value to be transferred as part of the call. #[clap(name = "value", short = 'v', long, default_value = DEFAULT_PAYABLE_VALUE)] @@ -96,7 +96,7 @@ impl CallContractCommand { full_message.push_str(&format!(" --message {}", message)); } if !self.args.is_empty() { - full_message.push_str(&format!(" --args {}", self.args.join(" "))); + full_message.push_str(&format!(" --args {}", self.args.join(","))); } if self.value != DEFAULT_PAYABLE_VALUE { full_message.push_str(&format!(" --value {}", self.value)); diff --git a/crates/pop-contracts/src/call/metadata.rs b/crates/pop-contracts/src/call/metadata.rs index 605d06206..31c2a07d2 100644 --- a/crates/pop-contracts/src/call/metadata.rs +++ b/crates/pop-contracts/src/call/metadata.rs @@ -56,6 +56,19 @@ pub fn get_messages(path: &Path) -> Result, Error> { } Ok(messages) } + +/// Extracts the information of a smart contract message parsing the metadata file. +/// +/// # Arguments +/// * `path` - Location path of the project. +/// * `message` - The label of the contract message. +pub fn get_message(path: &Path, message: &str) -> Result { + get_messages(path)? + .into_iter() + .find(|msg| msg.label == message) + .ok_or_else(|| Error::InvalidMessageName(message.to_string())) +} + // Parse the message parameters into a vector of argument labels. fn process_args(message_params: &[MessageParamSpec]) -> Vec { let mut args: Vec = Vec::new(); @@ -99,4 +112,26 @@ mod tests { assert_eq!(message[2].args[0].type_name, "bool".to_string()); Ok(()) } + + #[test] + fn get_message_work() -> Result<()> { + let temp_dir = generate_smart_contract_test_environment()?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; + assert!(matches!( + get_message(&temp_dir.path().join("testing"), "wrong_flip"), + Err(Error::InvalidMessageName(name)) if name == "wrong_flip".to_string())); + let message = get_message(&temp_dir.path().join("testing"), "specific_flip")?; + assert_eq!(message.label, "specific_flip"); + assert_eq!(message.docs, " A message for testing, flips the value of the stored `bool` with `new_value` and is payable"); + // assert parsed arguments + assert_eq!(message.args.len(), 1); + assert_eq!(message.args[0].label, "new_value".to_string()); + assert_eq!(message.args[0].type_name, "bool".to_string()); + Ok(()) + } } diff --git a/crates/pop-contracts/src/errors.rs b/crates/pop-contracts/src/errors.rs index a0ea14ade..066e35e16 100644 --- a/crates/pop-contracts/src/errors.rs +++ b/crates/pop-contracts/src/errors.rs @@ -28,6 +28,8 @@ pub enum Error { InstallContractsNode(String), #[error("{0}")] InstantiateContractError(String), + #[error("Invalid message name: {0}")] + InvalidMessageName(String), #[error("Invalid name: {0}")] InvalidName(String), #[error("IO error: {0}")] diff --git a/crates/pop-contracts/src/lib.rs b/crates/pop-contracts/src/lib.rs index a3ff0175c..521833be1 100644 --- a/crates/pop-contracts/src/lib.rs +++ b/crates/pop-contracts/src/lib.rs @@ -15,7 +15,7 @@ mod utils; pub use build::{build_smart_contract, is_supported, Verbosity}; pub use call::{ call_smart_contract, dry_run_call, dry_run_gas_estimate_call, - metadata::{get_messages, Message}, + metadata::{get_message, get_messages, Message}, set_up_call, CallOpts, }; pub use new::{create_smart_contract, is_valid_contract_name}; From 781ebc7a273c5ddfc8f0c50dd2e59aba5878ce26 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Mon, 4 Nov 2024 20:25:36 +0100 Subject: [PATCH 029/211] test: refactor and tests --- crates/pop-cli/src/commands/call/contract.rs | 8 +- crates/pop-contracts/src/call/metadata.rs | 84 ++++++++++- crates/pop-contracts/src/call/mod.rs | 26 ++-- crates/pop-contracts/src/errors.rs | 8 +- crates/pop-contracts/src/lib.rs | 2 +- .../tests/files/testing.contract | 2 +- crates/pop-contracts/tests/files/testing.json | 140 ++++++++++++------ 7 files changed, 206 insertions(+), 64 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index fc9b7787a..3b22d014c 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -671,6 +671,7 @@ mod tests { .expect_input("Enter the proof size limit:", "".into()) // Only if call .expect_input("Enter the gas limit:", "".into()) // Only if call .expect_input("Value to transfer to the call:", "50".into()) // Only if payable + .expect_input("Enter the value for the parameter: number", "2".into()) // Args for specific_flip .expect_input("Enter the value for the parameter: new_value", "true".into()) // Args for specific_flip .expect_select::( "Select the message to call:", @@ -691,7 +692,7 @@ mod tests { "Where is your project located?", temp_dir.path().join("testing").display().to_string(), ).expect_info(format!( - "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true,2 --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", temp_dir.path().join("testing").display().to_string(), )); @@ -715,8 +716,9 @@ mod tests { Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) ); assert_eq!(call_config.message, Some("specific_flip".to_string())); - assert_eq!(call_config.args.len(), 1); + assert_eq!(call_config.args.len(), 2); assert_eq!(call_config.args[0], "true".to_string()); + assert_eq!(call_config.args[1], "2".to_string()); assert_eq!(call_config.value, "50".to_string()); assert_eq!(call_config.gas_limit, None); assert_eq!(call_config.proof_size, None); @@ -725,7 +727,7 @@ mod tests { assert!(call_config.execute); assert!(!call_config.dry_run); assert_eq!(call_config.display(), format!( - "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true,2 --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", temp_dir.path().join("testing").display().to_string(), )); diff --git a/crates/pop-contracts/src/call/metadata.rs b/crates/pop-contracts/src/call/metadata.rs index 31c2a07d2..6c4b58db3 100644 --- a/crates/pop-contracts/src/call/metadata.rs +++ b/crates/pop-contracts/src/call/metadata.rs @@ -62,7 +62,7 @@ pub fn get_messages(path: &Path) -> Result, Error> { /// # Arguments /// * `path` - Location path of the project. /// * `message` - The label of the contract message. -pub fn get_message(path: &Path, message: &str) -> Result { +fn get_message(path: &Path, message: &str) -> Result { get_messages(path)? .into_iter() .find(|msg| msg.label == message) @@ -81,6 +81,41 @@ fn process_args(message_params: &[MessageParamSpec]) -> Vec args } +/// Generates a list of processed argument values for a specified contract message, +/// wrapping each value in `Some(...)` or replacing it with `None` if the argument is optional +/// +/// # Arguments +/// * `path` - Location path of the project. +/// * `message_label` - Label of the contract message to retrieve. +/// * `args` - Argument values provided by the user. +pub fn generate_message_args( + path: Option<&Path>, + message_label: &str, + args: Vec, +) -> Result, Error> { + let contract_path = path.unwrap_or_else(|| Path::new("./")); + let message = get_message(&contract_path, message_label)?; + if args.len() != message.args.len() { + return Err(Error::WrongNumberArguments { + expected: message.args.len(), + provided: args.len(), + }); + } + Ok(args + .into_iter() + .zip(&message.args) + .map(|(arg, param)| { + if param.type_name == "Option" && arg.is_empty() { + "None".to_string() + } else if param.type_name == "Option" { + format!("Some({})", arg) + } else { + arg + } + }) + .collect::>()) +} + #[cfg(test)] mod tests { use std::env; @@ -107,9 +142,11 @@ mod tests { assert_eq!(message[2].label, "specific_flip"); assert_eq!(message[2].docs, " A message for testing, flips the value of the stored `bool` with `new_value` and is payable"); // assert parsed arguments - assert_eq!(message[2].args.len(), 1); + assert_eq!(message[2].args.len(), 2); assert_eq!(message[2].args[0].label, "new_value".to_string()); assert_eq!(message[2].args[0].type_name, "bool".to_string()); + assert_eq!(message[2].args[1].label, "number".to_string()); + assert_eq!(message[2].args[1].type_name, "Option".to_string()); Ok(()) } @@ -129,9 +166,50 @@ mod tests { assert_eq!(message.label, "specific_flip"); assert_eq!(message.docs, " A message for testing, flips the value of the stored `bool` with `new_value` and is payable"); // assert parsed arguments - assert_eq!(message.args.len(), 1); + assert_eq!(message.args.len(), 2); assert_eq!(message.args[0].label, "new_value".to_string()); assert_eq!(message.args[0].type_name, "bool".to_string()); + assert_eq!(message.args[1].label, "number".to_string()); + assert_eq!(message.args[1].type_name, "Option".to_string()); + Ok(()) + } + + #[test] + fn generate_message_args_work() -> Result<()> { + let temp_dir = generate_smart_contract_test_environment()?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; + assert!(matches!( + generate_message_args(Some(&temp_dir.path().join("testing")),"wrong_flip", Vec::new()), + Err(Error::InvalidMessageName(error)) if error == "wrong_flip".to_string())); + assert!(matches!( + generate_message_args( + Some(&temp_dir.path().join("testing")), + "specific_flip", + Vec::new() + ), + Err(Error::WrongNumberArguments {expected, provided }) if expected == 2 && provided == 0 + )); + assert_eq!( + generate_message_args( + Some(&temp_dir.path().join("testing")), + "specific_flip", + ["true".to_string(), "2".to_string()].to_vec() + )?, + ["true".to_string(), "Some(2)".to_string()] + ); + assert_eq!( + generate_message_args( + Some(&temp_dir.path().join("testing")), + "specific_flip", + ["true".to_string(), "".to_string()].to_vec() + )?, + ["true".to_string(), "None".to_string()] + ); Ok(()) } } diff --git a/crates/pop-contracts/src/call/mod.rs b/crates/pop-contracts/src/call/mod.rs index 2c363301b..6683541e6 100644 --- a/crates/pop-contracts/src/call/mod.rs +++ b/crates/pop-contracts/src/call/mod.rs @@ -53,7 +53,7 @@ pub struct CallOpts { /// * `call_opts` - options for the `call` command. pub async fn set_up_call( call_opts: CallOpts, -) -> anyhow::Result> { +) -> Result, Error> { let token_metadata = TokenMetadata::query::(&call_opts.url).await?; let manifest_path = get_manifest_path(call_opts.path.as_deref())?; let signer = create_signer(&call_opts.suri)?; @@ -67,10 +67,16 @@ pub async fn set_up_call( parse_balance(&call_opts.value)?; let contract: ::AccountId = parse_account(&call_opts.contract)?; + // Parse the argument values input by the user. + let args = metadata::generate_message_args( + call_opts.path.as_deref(), + &call_opts.message, + call_opts.args, + )?; let call_exec: CallExec = CallCommandBuilder::new(contract.clone(), &call_opts.message, extrinsic_opts) - .args(call_opts.args.clone()) + .args(args) .value(value.denominate_balance(&token_metadata)?) .gas_limit(call_opts.gas_limit) .proof_size(call_opts.proof_size) @@ -212,11 +218,9 @@ mod tests { suri: "//Alice".to_string(), execute: false, }; - let call = set_up_call(call_opts).await; - assert!(call.is_err()); - let error = call.err().unwrap(); - assert_eq!(error.root_cause().to_string(), "Failed to find any contract artifacts in target directory. \nRun `cargo contract build --release` to generate the artifacts."); - + assert!( + matches!(set_up_call(call_opts).await, Err(Error::AnyhowError(message)) if message.root_cause().to_string() == "Failed to find any contract artifacts in target directory. \nRun `cargo contract build --release` to generate the artifacts.") + ); Ok(()) } #[tokio::test] @@ -233,11 +237,9 @@ mod tests { suri: "//Alice".to_string(), execute: false, }; - let call = set_up_call(call_opts).await; - assert!(call.is_err()); - let error = call.err().unwrap(); - assert_eq!(error.root_cause().to_string(), "No 'ink' dependency found"); - + assert!( + matches!(set_up_call(call_opts).await, Err(Error::AnyhowError(message)) if message.root_cause().to_string() == "No 'ink' dependency found") + ); Ok(()) } diff --git a/crates/pop-contracts/src/errors.rs b/crates/pop-contracts/src/errors.rs index 066e35e16..a22a0dcb7 100644 --- a/crates/pop-contracts/src/errors.rs +++ b/crates/pop-contracts/src/errors.rs @@ -38,6 +38,8 @@ pub enum Error { KeyPairCreation(String), #[error("Failed to get manifest path: {0}")] ManifestPath(String), + #[error("Argument {0} is required")] + MissingArgument(String), #[error("Failed to create new contract project: {0}")] NewContract(String), #[error("ParseError error: {0}")] @@ -46,12 +48,14 @@ pub enum Error { ParseSecretURI(String), #[error("The `Repository` property is missing from the template variant")] RepositoryMissing, + #[error("Sourcing error {0}")] + SourcingError(SourcingError), #[error("Failed to execute test command: {0}")] TestCommand(String), #[error("Unsupported platform: {os}")] UnsupportedPlatform { os: &'static str }, #[error("{0}")] UploadContractError(String), - #[error("Sourcing error {0}")] - SourcingError(SourcingError), + #[error("Wrong number of arguments provided. Expecting {expected}, {provided} provided")] + WrongNumberArguments { expected: usize, provided: usize }, } diff --git a/crates/pop-contracts/src/lib.rs b/crates/pop-contracts/src/lib.rs index 521833be1..a3ff0175c 100644 --- a/crates/pop-contracts/src/lib.rs +++ b/crates/pop-contracts/src/lib.rs @@ -15,7 +15,7 @@ mod utils; pub use build::{build_smart_contract, is_supported, Verbosity}; pub use call::{ call_smart_contract, dry_run_call, dry_run_gas_estimate_call, - metadata::{get_message, get_messages, Message}, + metadata::{get_messages, Message}, set_up_call, CallOpts, }; pub use new::{create_smart_contract, is_valid_contract_name}; diff --git a/crates/pop-contracts/tests/files/testing.contract b/crates/pop-contracts/tests/files/testing.contract index 5fb54f3ce..73ff479a5 100644 --- a/crates/pop-contracts/tests/files/testing.contract +++ b/crates/pop-contracts/tests/files/testing.contract @@ -1 +1 @@ -{"source":{"hash":"0x80776e58b218850d7d86447b2edea78d827ed0ed2499ff3a92b7ea10e4f95eb5","language":"ink! 5.0.0","compiler":"rustc 1.78.0","wasm":"0x0061736d01000000012b0860027f7f0060037f7f7f017f60000060047f7f7f7f017f60037f7f7f0060017f006000017f60017f017f027406057365616c310b6765745f73746f726167650003057365616c3005696e7075740000057365616c320b7365745f73746f726167650003057365616c300b7365616c5f72657475726e0004057365616c301176616c75655f7472616e73666572726564000003656e76066d656d6f72790201021003100f0101010105040006070002050002020616037f01418080040b7f00418080050b7f00418080050b0711020463616c6c0012066465706c6f7900130aa80c0f2b01017f037f2002200346047f200005200020036a200120036a2d00003a0000200341016a21030c010b0b0b6f01017f0240200020014d04402000210303402002450d02200320012d00003a0000200341016a2103200141016a2101200241016b21020c000b000b200041016b2103200141016b210103402002450d01200220036a200120026a2d00003a0000200241016b21020c000b000b20000b2501017f037f2002200346047f200005200020036a20013a0000200341016a21030c010b0b0b3f01027f0340200245044041000f0b200241016b210220012d0000210320002d00002104200141016a2101200041016a210020032004460d000b200420036b0b2601017f230041106b220124002001410036020c20002001410c6a4104100a200141106a24000b4801027f024002402000280208220320026a22042003490d00200420002802044b0d00200420036b2002470d01200028020020036a2001200210051a200020043602080f0b000b000b2601017f230041106b22022400200220003a000f20012002410f6a4101100a200241106a24000b6102027f027e230041206b22002400200041106a22014200370300200042003703082000411036021c200041086a2000411c6a1004200028021c41114f0440000b2001290300210220002903082103200041206a2400410541042002200384501b0b3f01017f2000280204220145044041020f0b2000200141016b36020420002000280200220041016a3602004101410220002d000022004101461b410020001b0b3c01027f027f200145044041808004210141010c010b410121024180800441013a000041818004210141020b2103200120023a0000200020031011000b12004180800441003b0100410041021011000b8a0101057f230041106b22012400200142808001370208200141808004360204200141046a22041009024020012802082205200128020c2202490d00200128020421032001410036020c2001200520026b3602082001200220036a36020420002004100b200128020c220020012802084b0d00200320022001280204200010021a200141106a24000f0b000b0d0020004180800420011003000b990401067f230041106b2200240020004180800136020441808004200041046a10010240024020002802042202418180014f0d000240024020024104490d002000418480043602042000200241046b360208418380042d00002101418280042d00002104418180042d00002103418080042d00002202412f470440200241ec00470440200241e300470d02410221022003413a46200441a5014671200141d10046710d030c020b2003410f472004411d4772200141f70147720d01200041046a100d220241ff01714102460d010c020b41032102200341860146200441db004671200141d90146710d010b41014101100e000b200042808001370208200041808004360204200041046a2204100920002802082205200028020c2201490d00200028020421032000200520016b220536020420032001200120036a2201200410002000280204220320054b720d0020002003360208200020013602042004100d220141ff01714102460d0020002802080d000240024002404102200241026b41ff01712200200041024f1b41016b0e020100020b2002410171101041004100100e000b100c41ff01714105470d01230041106b220024002000418080043602044180800441003a00002000428080818010370208200141ff0171410047200041046a100b200028020c2200418180014f0440000b410020001011000b100c41ff01714105460d010b000b200141ff017145101041004100100e000be50101057f230041106b2200240002400240100c41ff01714105470d0020004180800136020c418080042000410c6a1001200028020c2201418180014f0d0020014104490d012000418480043602042000200141046b360208418380042d00002101418280042d00002102418180042d000021030240418080042d0000220441ed014704402004419b0147200341ae0147722002419d0147200141de004772720d03200041046a100d220041ff01714102470d010c030b200341cb00462002419d0146712001411b4671450d0241001010100f000b20001010100f000b000b41014101100e000b","build_info":{"rust_toolchain":"stable-aarch64-apple-darwin","cargo_contract_version":"5.0.0-alpha","build_mode":"Release","wasm_opt_settings":{"optimization_passes":"Z","keep_debug_symbols":false}}},"contract":{"name":"testing","version":"0.1.0","authors":["[your_name] <[your_email]>"]},"image":null,"version":5,"types":[{"id":0,"type":{"def":{"primitive":"bool"}}},{"id":1,"type":{"path":["testing","testing","Testing"],"def":{"composite":{"fields":[{"name":"value","type":0,"typeName":",>>::Type"}]}}}},{"id":2,"type":{"path":["Result"],"params":[{"name":"T","type":3},{"name":"E","type":4}],"def":{"variant":{"variants":[{"name":"Ok","fields":[{"type":3}],"index":0},{"name":"Err","fields":[{"type":4}],"index":1}]}}}},{"id":3,"type":{"def":{"tuple":[]}}},{"id":4,"type":{"path":["ink_primitives","LangError"],"def":{"variant":{"variants":[{"name":"CouldNotReadInput","index":1}]}}}},{"id":5,"type":{"path":["Result"],"params":[{"name":"T","type":0},{"name":"E","type":4}],"def":{"variant":{"variants":[{"name":"Ok","fields":[{"type":0}],"index":0},{"name":"Err","fields":[{"type":4}],"index":1}]}}}},{"id":6,"type":{"path":["ink_primitives","types","AccountId"],"def":{"composite":{"fields":[{"type":7,"typeName":"[u8; 32]"}]}}}},{"id":7,"type":{"def":{"array":{"len":32,"type":8}}}},{"id":8,"type":{"def":{"primitive":"u8"}}},{"id":9,"type":{"def":{"primitive":"u128"}}},{"id":10,"type":{"path":["ink_primitives","types","Hash"],"def":{"composite":{"fields":[{"type":7,"typeName":"[u8; 32]"}]}}}},{"id":11,"type":{"def":{"primitive":"u64"}}},{"id":12,"type":{"def":{"primitive":"u32"}}},{"id":13,"type":{"path":["ink_env","types","NoChainExtension"],"def":{"variant":{}}}}],"storage":{"root":{"root_key":"0x00000000","layout":{"struct":{"name":"Testing","fields":[{"name":"value","layout":{"leaf":{"key":"0x00000000","ty":0}}}]}},"ty":1}},"spec":{"constructors":[{"label":"new","selector":"0x9bae9d5e","payable":false,"args":[{"label":"init_value","type":{"type":0,"displayName":["bool"]}}],"returnType":{"type":2,"displayName":["ink_primitives","ConstructorResult"]},"docs":["Constructor that initializes the `bool` value to the given `init_value`."],"default":false},{"label":"default","selector":"0xed4b9d1b","payable":false,"args":[],"returnType":{"type":2,"displayName":["ink_primitives","ConstructorResult"]},"docs":["Constructor that initializes the `bool` value to `false`.","","Constructors can delegate to other constructors."],"default":false}],"messages":[{"label":"flip","selector":"0x633aa551","mutates":true,"payable":false,"args":[],"returnType":{"type":2,"displayName":["ink","MessageResult"]},"docs":[" A message that can be called on instantiated contracts."," This one flips the value of the stored `bool` from `true`"," to `false` and vice versa."],"default":false},{"label":"get","selector":"0x2f865bd9","mutates":false,"payable":false,"args":[],"returnType":{"type":5,"displayName":["ink","MessageResult"]},"docs":[" Simply returns the current value of our `bool`."],"default":false},{"label":"specific_flip","selector":"0x6c0f1df7","mutates":true,"payable":true,"args":[{"label":"new_value","type":{"type":0,"displayName":["bool"]}}],"returnType":{"type":2,"displayName":["ink","MessageResult"]},"docs":[" A message for testing, flips the value of the stored `bool` with `new_value`"," and is payable"],"default":false}],"events":[],"docs":[],"lang_error":{"type":4,"displayName":["ink","LangError"]},"environment":{"accountId":{"type":6,"displayName":["AccountId"]},"balance":{"type":9,"displayName":["Balance"]},"hash":{"type":10,"displayName":["Hash"]},"timestamp":{"type":11,"displayName":["Timestamp"]},"blockNumber":{"type":12,"displayName":["BlockNumber"]},"chainExtension":{"type":13,"displayName":["ChainExtension"]},"maxEventTopics":4,"staticBufferSize":16384}}} \ No newline at end of file +{"source":{"hash":"0xd35166baa65bf10eb07c3d30c052d0fe752d6b98a864b61062e189be1a1ed550","language":"ink! 5.0.0","compiler":"rustc 1.78.0","wasm":"0x0061736d0100000001270760027f7f0060037f7f7f017f60000060047f7f7f7f017f60037f7f7f006000017f60017f017f027406057365616c310b6765745f73746f726167650003057365616c3005696e7075740000057365616c320b7365745f73746f726167650003057365616c300b7365616c5f72657475726e0004057365616c301176616c75655f7472616e73666572726564000003656e76066d656d6f727902010210031110010101010004000506020000000002020616037f01418080040b7f00418080050b7f00418080050b0711020463616c6c0013066465706c6f7900140aff0d102b01017f037f2002200346047f200005200020036a200120036a2d00003a0000200341016a21030c010b0b0b6f01017f0240200020014d04402000210303402002450d02200320012d00003a0000200341016a2103200141016a2101200241016b21020c000b000b200041016b2103200141016b210103402002450d01200220036a200120026a2d00003a0000200241016b21020c000b000b20000b2501017f037f2002200346047f200005200020036a20013a0000200341016a21030c010b0b0b3f01027f0340200245044041000f0b200241016b210220012d0000210320002d00002104200141016a2101200041016a210020032004460d000b200420036b0b2601017f230041106b220224002002200036020c20012002410c6a4104100a200241106a24000b4801027f024002402000280208220320026a22042003490d00200420002802044b0d00200420036b2002470d01200028020020036a2001200210051a200020043602080f0b000b000b2601017f230041106b22022400200220003a000f20012002410f6a4101100a200241106a24000b6102027f027e230041206b22002400200041106a22014200370300200042003703082000411036021c200041086a2000411c6a1004200028021c41114f0440000b2001290300210220002903082103200041206a2400410541042002200384501b0b7301037f230041106b22012400200141086a220320002802042202047f2000200241016b36020420002000280200220041016a36020020002d00000520000b3a000120032002453a000020012d0009210020012d00082102200141106a240041024101410220004101461b410020001b20021b0b12004180800441003b0100410041021011000b3c01027f027f200145044041808004210141010c010b410121024180800441013a000041818004210141020b2103200120023a0000200020031011000b920101057f230041106b220224002002428080013702082002418080043602044100200241046a22041009024020022802082206200228020c2203490d00200228020421052002410036020c2002200620036b3602082002200320056a36020420012004100b200020041009200228020c220020022802084b0d00200520032002280204200010021a200241106a24000f0b000b0d0020004180800420011003000b08002000200110100ba70501087f230041206b2200240020004180800136021441808004200041146a100102400240024020002802142202418180014f0d004104210541042106024020024104490d0020004184800436020c2000200241046b360210418380042d00002101418280042d00002104418180042d0000210302400240418080042d00002202412f470440200241ec00460d01200241e300472003413a4772200441a50147200141d1004772720d03410221010c020b200341860147200441db004772200141d90147720d02410321010c010b2003410f472004411d4772200141f70147720d012000410c6a100d220241ff01714102460d0120002802102204450d010240200028020c22032d000022010e020100020b20044105490d0120032800012106410121010b20002001360218200020023a0014410821050b200041146a220220056a2006360200200028021822044104460d01200028021c210520002d0014210620004280800137021820004180800436021441002002100920002802182207200028021c2201490d00200028021421032000200720016b220736021420032001200120036a2201200210002000280214220320074b720d0020002003360218200020013602142002100d220241ff01714102460d002000280218220141034d2001410447720d00200028021428000021000240024002404102200441026b2201200141024f1b41016b0e020100020b2005200020041b2006410047101241004100100f000b100c41ff01714105470d01230041106b220024002000418080043602044180800441003a00002000428080818010370208200241ff0171410047200041046a100b200028020c2200418180014f0440000b410020001011000b100c41ff01714105460d020b000b41014101100f000b2000200241ff017145101241004100100f000be90101057f230041106b2200240002400240100c41ff01714105470d0020004180800136020c418080042000410c6a1001200028020c2201418180014f0d0020014104490d012000418480043602042000200141046b360208418380042d00002101418280042d00002102418180042d000021030240418080042d0000220441ed014704402004419b0147200341ae0147722002419d0147200141de004772720d03200041046a100d220041ff01714102470d010c030b200341cb00462002419d0146712001411b4671450d02410041001010100e000b410020001010100e000b000b41014101100f000b","build_info":{"rust_toolchain":"stable-aarch64-apple-darwin","cargo_contract_version":"5.0.0-alpha","build_mode":"Release","wasm_opt_settings":{"optimization_passes":"Z","keep_debug_symbols":false}}},"contract":{"name":"testing","version":"0.1.0","authors":["[your_name] <[your_email]>"]},"image":null,"version":5,"types":[{"id":0,"type":{"def":{"primitive":"bool"}}},{"id":1,"type":{"def":{"primitive":"u32"}}},{"id":2,"type":{"path":["testing","testing","Testing"],"def":{"composite":{"fields":[{"name":"value","type":0,"typeName":",>>::Type"},{"name":"number","type":1,"typeName":",>>::Type"}]}}}},{"id":3,"type":{"path":["Result"],"params":[{"name":"T","type":4},{"name":"E","type":5}],"def":{"variant":{"variants":[{"name":"Ok","fields":[{"type":4}],"index":0},{"name":"Err","fields":[{"type":5}],"index":1}]}}}},{"id":4,"type":{"def":{"tuple":[]}}},{"id":5,"type":{"path":["ink_primitives","LangError"],"def":{"variant":{"variants":[{"name":"CouldNotReadInput","index":1}]}}}},{"id":6,"type":{"path":["Result"],"params":[{"name":"T","type":0},{"name":"E","type":5}],"def":{"variant":{"variants":[{"name":"Ok","fields":[{"type":0}],"index":0},{"name":"Err","fields":[{"type":5}],"index":1}]}}}},{"id":7,"type":{"path":["Option"],"params":[{"name":"T","type":1}],"def":{"variant":{"variants":[{"name":"None","index":0},{"name":"Some","fields":[{"type":1}],"index":1}]}}}},{"id":8,"type":{"path":["ink_primitives","types","AccountId"],"def":{"composite":{"fields":[{"type":9,"typeName":"[u8; 32]"}]}}}},{"id":9,"type":{"def":{"array":{"len":32,"type":10}}}},{"id":10,"type":{"def":{"primitive":"u8"}}},{"id":11,"type":{"def":{"primitive":"u128"}}},{"id":12,"type":{"path":["ink_primitives","types","Hash"],"def":{"composite":{"fields":[{"type":9,"typeName":"[u8; 32]"}]}}}},{"id":13,"type":{"def":{"primitive":"u64"}}},{"id":14,"type":{"path":["ink_env","types","NoChainExtension"],"def":{"variant":{}}}}],"storage":{"root":{"root_key":"0x00000000","layout":{"struct":{"name":"Testing","fields":[{"name":"value","layout":{"leaf":{"key":"0x00000000","ty":0}}},{"name":"number","layout":{"leaf":{"key":"0x00000000","ty":1}}}]}},"ty":2}},"spec":{"constructors":[{"label":"new","selector":"0x9bae9d5e","payable":false,"args":[{"label":"init_value","type":{"type":0,"displayName":["bool"]}}],"returnType":{"type":3,"displayName":["ink_primitives","ConstructorResult"]},"docs":["Constructor that initializes the `bool` value to the given `init_value`."],"default":false},{"label":"default","selector":"0xed4b9d1b","payable":false,"args":[],"returnType":{"type":3,"displayName":["ink_primitives","ConstructorResult"]},"docs":["Constructor that initializes the `bool` value to `false`.","","Constructors can delegate to other constructors."],"default":false}],"messages":[{"label":"flip","selector":"0x633aa551","mutates":true,"payable":false,"args":[],"returnType":{"type":3,"displayName":["ink","MessageResult"]},"docs":[" A message that can be called on instantiated contracts."," This one flips the value of the stored `bool` from `true`"," to `false` and vice versa."],"default":false},{"label":"get","selector":"0x2f865bd9","mutates":false,"payable":false,"args":[],"returnType":{"type":6,"displayName":["ink","MessageResult"]},"docs":[" Simply returns the current value of our `bool`."],"default":false},{"label":"specific_flip","selector":"0x6c0f1df7","mutates":true,"payable":true,"args":[{"label":"new_value","type":{"type":0,"displayName":["bool"]}},{"label":"number","type":{"type":7,"displayName":["Option"]}}],"returnType":{"type":3,"displayName":["ink","MessageResult"]},"docs":[" A message for testing, flips the value of the stored `bool` with `new_value`"," and is payable"],"default":false}],"events":[],"docs":[],"lang_error":{"type":5,"displayName":["ink","LangError"]},"environment":{"accountId":{"type":8,"displayName":["AccountId"]},"balance":{"type":11,"displayName":["Balance"]},"hash":{"type":12,"displayName":["Hash"]},"timestamp":{"type":13,"displayName":["Timestamp"]},"blockNumber":{"type":1,"displayName":["BlockNumber"]},"chainExtension":{"type":14,"displayName":["ChainExtension"]},"maxEventTopics":4,"staticBufferSize":16384}}} \ No newline at end of file diff --git a/crates/pop-contracts/tests/files/testing.json b/crates/pop-contracts/tests/files/testing.json index ed230c98f..8f28780d5 100644 --- a/crates/pop-contracts/tests/files/testing.json +++ b/crates/pop-contracts/tests/files/testing.json @@ -1,6 +1,6 @@ { "source": { - "hash": "0x80776e58b218850d7d86447b2edea78d827ed0ed2499ff3a92b7ea10e4f95eb5", + "hash": "0xd35166baa65bf10eb07c3d30c052d0fe752d6b98a864b61062e189be1a1ed550", "language": "ink! 5.0.0", "compiler": "rustc 1.78.0", "build_info": { @@ -33,6 +33,14 @@ }, { "id": 1, + "type": { + "def": { + "primitive": "u32" + } + } + }, + { + "id": 2, "type": { "path": [ "testing", @@ -46,6 +54,11 @@ "name": "value", "type": 0, "typeName": ",>>::Type" + }, + { + "name": "number", + "type": 1, + "typeName": ",>>::Type" } ] } @@ -53,7 +66,7 @@ } }, { - "id": 2, + "id": 3, "type": { "path": [ "Result" @@ -61,11 +74,11 @@ "params": [ { "name": "T", - "type": 3 + "type": 4 }, { "name": "E", - "type": 4 + "type": 5 } ], "def": { @@ -75,7 +88,7 @@ "name": "Ok", "fields": [ { - "type": 3 + "type": 4 } ], "index": 0 @@ -84,7 +97,7 @@ "name": "Err", "fields": [ { - "type": 4 + "type": 5 } ], "index": 1 @@ -95,7 +108,7 @@ } }, { - "id": 3, + "id": 4, "type": { "def": { "tuple": [] @@ -103,7 +116,7 @@ } }, { - "id": 4, + "id": 5, "type": { "path": [ "ink_primitives", @@ -122,7 +135,7 @@ } }, { - "id": 5, + "id": 6, "type": { "path": [ "Result" @@ -134,7 +147,7 @@ }, { "name": "E", - "type": 4 + "type": 5 } ], "def": { @@ -153,7 +166,7 @@ "name": "Err", "fields": [ { - "type": 4 + "type": 5 } ], "index": 1 @@ -164,7 +177,40 @@ } }, { - "id": 6, + "id": 7, + "type": { + "path": [ + "Option" + ], + "params": [ + { + "name": "T", + "type": 1 + } + ], + "def": { + "variant": { + "variants": [ + { + "name": "None", + "index": 0 + }, + { + "name": "Some", + "fields": [ + { + "type": 1 + } + ], + "index": 1 + } + ] + } + } + } + }, + { + "id": 8, "type": { "path": [ "ink_primitives", @@ -175,7 +221,7 @@ "composite": { "fields": [ { - "type": 7, + "type": 9, "typeName": "[u8; 32]" } ] @@ -184,18 +230,18 @@ } }, { - "id": 7, + "id": 9, "type": { "def": { "array": { "len": 32, - "type": 8 + "type": 10 } } } }, { - "id": 8, + "id": 10, "type": { "def": { "primitive": "u8" @@ -203,7 +249,7 @@ } }, { - "id": 9, + "id": 11, "type": { "def": { "primitive": "u128" @@ -211,7 +257,7 @@ } }, { - "id": 10, + "id": 12, "type": { "path": [ "ink_primitives", @@ -222,7 +268,7 @@ "composite": { "fields": [ { - "type": 7, + "type": 9, "typeName": "[u8; 32]" } ] @@ -231,7 +277,7 @@ } }, { - "id": 11, + "id": 13, "type": { "def": { "primitive": "u64" @@ -239,15 +285,7 @@ } }, { - "id": 12, - "type": { - "def": { - "primitive": "u32" - } - } - }, - { - "id": 13, + "id": 14, "type": { "path": [ "ink_env", @@ -275,11 +313,20 @@ "ty": 0 } } + }, + { + "name": "number", + "layout": { + "leaf": { + "key": "0x00000000", + "ty": 1 + } + } } ] } }, - "ty": 1 + "ty": 2 } }, "spec": { @@ -300,7 +347,7 @@ } ], "returnType": { - "type": 2, + "type": 3, "displayName": [ "ink_primitives", "ConstructorResult" @@ -317,7 +364,7 @@ "payable": false, "args": [], "returnType": { - "type": 2, + "type": 3, "displayName": [ "ink_primitives", "ConstructorResult" @@ -339,7 +386,7 @@ "payable": false, "args": [], "returnType": { - "type": 2, + "type": 3, "displayName": [ "ink", "MessageResult" @@ -359,7 +406,7 @@ "payable": false, "args": [], "returnType": { - "type": 5, + "type": 6, "displayName": [ "ink", "MessageResult" @@ -384,10 +431,19 @@ "bool" ] } + }, + { + "label": "number", + "type": { + "type": 7, + "displayName": [ + "Option" + ] + } } ], "returnType": { - "type": 2, + "type": 3, "displayName": [ "ink", "MessageResult" @@ -403,7 +459,7 @@ "events": [], "docs": [], "lang_error": { - "type": 4, + "type": 5, "displayName": [ "ink", "LangError" @@ -411,37 +467,37 @@ }, "environment": { "accountId": { - "type": 6, + "type": 8, "displayName": [ "AccountId" ] }, "balance": { - "type": 9, + "type": 11, "displayName": [ "Balance" ] }, "hash": { - "type": 10, + "type": 12, "displayName": [ "Hash" ] }, "timestamp": { - "type": 11, + "type": 13, "displayName": [ "Timestamp" ] }, "blockNumber": { - "type": 12, + "type": 1, "displayName": [ "BlockNumber" ] }, "chainExtension": { - "type": 13, + "type": 14, "displayName": [ "ChainExtension" ] From d361160418ce5a8d3863044540ae58500750c1e9 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 5 Nov 2024 09:04:57 +0100 Subject: [PATCH 030/211] refactor: improve code and comments --- crates/pop-contracts/src/call/metadata.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/crates/pop-contracts/src/call/metadata.rs b/crates/pop-contracts/src/call/metadata.rs index 6c4b58db3..76737a0af 100644 --- a/crates/pop-contracts/src/call/metadata.rs +++ b/crates/pop-contracts/src/call/metadata.rs @@ -94,7 +94,7 @@ pub fn generate_message_args( args: Vec, ) -> Result, Error> { let contract_path = path.unwrap_or_else(|| Path::new("./")); - let message = get_message(&contract_path, message_label)?; + let message = get_message(contract_path, message_label)?; if args.len() != message.args.len() { return Err(Error::WrongNumberArguments { expected: message.args.len(), @@ -104,14 +104,12 @@ pub fn generate_message_args( Ok(args .into_iter() .zip(&message.args) - .map(|(arg, param)| { - if param.type_name == "Option" && arg.is_empty() { - "None".to_string() - } else if param.type_name == "Option" { - format!("Some({})", arg) - } else { - arg - } + .map(|(arg, param)| match (param.type_name.as_str(), arg.is_empty()) { + ("Option", true) => "None".to_string(), /* If the argument is Option and empty, + * replace it with `None` */ + ("Option", false) => format!("Some({})", arg), /* If the argument is Option and not + * empty, wrap it in `Some(...)` */ + _ => arg, // If the argument is not Option, return it as is }) .collect::>()) } From 9b5b6d8e630c4028911f036da3df00c9c059516a Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 5 Nov 2024 22:15:50 +0100 Subject: [PATCH 031/211] fix: renaming and clean code --- crates/pop-contracts/src/call/metadata.rs | 51 +++++++++++++---------- crates/pop-contracts/src/call/mod.rs | 6 +-- crates/pop-contracts/src/errors.rs | 4 +- 3 files changed, 33 insertions(+), 28 deletions(-) diff --git a/crates/pop-contracts/src/call/metadata.rs b/crates/pop-contracts/src/call/metadata.rs index 76737a0af..d6c3ccb5f 100644 --- a/crates/pop-contracts/src/call/metadata.rs +++ b/crates/pop-contracts/src/call/metadata.rs @@ -62,8 +62,11 @@ pub fn get_messages(path: &Path) -> Result, Error> { /// # Arguments /// * `path` - Location path of the project. /// * `message` - The label of the contract message. -fn get_message(path: &Path, message: &str) -> Result { - get_messages(path)? +fn get_message

(path: P, message: &str) -> Result +where + P: AsRef, +{ + get_messages(path.as_ref())? .into_iter() .find(|msg| msg.label == message) .ok_or_else(|| Error::InvalidMessageName(message.to_string())) @@ -81,22 +84,24 @@ fn process_args(message_params: &[MessageParamSpec]) -> Vec args } -/// Generates a list of processed argument values for a specified contract message, +/// Processes a list of argument values for a specified contract message, /// wrapping each value in `Some(...)` or replacing it with `None` if the argument is optional /// /// # Arguments /// * `path` - Location path of the project. /// * `message_label` - Label of the contract message to retrieve. /// * `args` - Argument values provided by the user. -pub fn generate_message_args( - path: Option<&Path>, - message_label: &str, +pub fn process_message_args

( + path: P, + message: &str, args: Vec, -) -> Result, Error> { - let contract_path = path.unwrap_or_else(|| Path::new("./")); - let message = get_message(contract_path, message_label)?; +) -> Result, Error> +where + P: AsRef, +{ + let message = get_message(path, message)?; if args.len() != message.args.len() { - return Err(Error::WrongNumberArguments { + return Err(Error::IncorrectArguments { expected: message.args.len(), provided: args.len(), }); @@ -105,10 +110,10 @@ pub fn generate_message_args( .into_iter() .zip(&message.args) .map(|(arg, param)| match (param.type_name.as_str(), arg.is_empty()) { - ("Option", true) => "None".to_string(), /* If the argument is Option and empty, - * replace it with `None` */ - ("Option", false) => format!("Some({})", arg), /* If the argument is Option and not - * empty, wrap it in `Some(...)` */ + ("Option", true) => "None".to_string(), /* If the argument is Option and empty, */ + // replace it with `None` + ("Option", false) => format!("Some({})", arg), /* If the argument is Option and not */ + // empty, wrap it in `Some(...)` _ => arg, // If the argument is not Option, return it as is }) .collect::>()) @@ -173,7 +178,7 @@ mod tests { } #[test] - fn generate_message_args_work() -> Result<()> { + fn process_message_args_work() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; let current_dir = env::current_dir().expect("Failed to get current directory"); mock_build_process( @@ -182,27 +187,27 @@ mod tests { current_dir.join("./tests/files/testing.json"), )?; assert!(matches!( - generate_message_args(Some(&temp_dir.path().join("testing")),"wrong_flip", Vec::new()), + process_message_args(temp_dir.path().join("testing"),"wrong_flip", Vec::new()), Err(Error::InvalidMessageName(error)) if error == "wrong_flip".to_string())); assert!(matches!( - generate_message_args( - Some(&temp_dir.path().join("testing")), + process_message_args( + temp_dir.path().join("testing"), "specific_flip", Vec::new() ), - Err(Error::WrongNumberArguments {expected, provided }) if expected == 2 && provided == 0 + Err(Error::IncorrectArguments {expected, provided }) if expected == 2 && provided == 0 )); assert_eq!( - generate_message_args( - Some(&temp_dir.path().join("testing")), + process_message_args( + temp_dir.path().join("testing"), "specific_flip", ["true".to_string(), "2".to_string()].to_vec() )?, ["true".to_string(), "Some(2)".to_string()] ); assert_eq!( - generate_message_args( - Some(&temp_dir.path().join("testing")), + process_message_args( + temp_dir.path().join("testing"), "specific_flip", ["true".to_string(), "".to_string()].to_vec() )?, diff --git a/crates/pop-contracts/src/call/mod.rs b/crates/pop-contracts/src/call/mod.rs index 6683541e6..69d0f04e5 100644 --- a/crates/pop-contracts/src/call/mod.rs +++ b/crates/pop-contracts/src/call/mod.rs @@ -67,9 +67,9 @@ pub async fn set_up_call( parse_balance(&call_opts.value)?; let contract: ::AccountId = parse_account(&call_opts.contract)?; - // Parse the argument values input by the user. - let args = metadata::generate_message_args( - call_opts.path.as_deref(), + // Process the argument values input by the user. + let args = metadata::process_message_args( + call_opts.path.unwrap_or_else(|| PathBuf::from("./")), &call_opts.message, call_opts.args, )?; diff --git a/crates/pop-contracts/src/errors.rs b/crates/pop-contracts/src/errors.rs index a22a0dcb7..f0e019f1c 100644 --- a/crates/pop-contracts/src/errors.rs +++ b/crates/pop-contracts/src/errors.rs @@ -56,6 +56,6 @@ pub enum Error { UnsupportedPlatform { os: &'static str }, #[error("{0}")] UploadContractError(String), - #[error("Wrong number of arguments provided. Expecting {expected}, {provided} provided")] - WrongNumberArguments { expected: usize, provided: usize }, + #[error("Incorrect number of arguments provided. Expecting {expected}, {provided} provided")] + IncorrectArguments { expected: usize, provided: usize }, } From 5b27b37c9702ad5ab01b7ebaec43ffe2811a3414 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Wed, 6 Nov 2024 09:57:13 +0100 Subject: [PATCH 032/211] chore: option params not mandatory --- crates/pop-cli/src/commands/call/contract.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 3b22d014c..204aee356 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -230,11 +230,15 @@ impl CallContractCommand { // Resolve message arguments. let mut contract_args = Vec::new(); for arg in &message.args { - contract_args.push( - cli.input(format!("Enter the value for the parameter: {}", arg.label)) - .placeholder(&format!("Type required: {}", &arg.type_name)) - .interact()?, - ); + let mut input = cli + .input(format!("Enter the value for the parameter: {}", arg.label)) + .placeholder(&format!("Type required: {}", arg.type_name)); + + // Set default input only if the parameter type is `Option` (Not mandatory) + if arg.type_name == "Option" { + input = input.default_input(""); + } + contract_args.push(input.interact()?); } self.args = contract_args; From b0c9478cf0a302d8cb0b23fc6d6d83a75365a9b7 Mon Sep 17 00:00:00 2001 From: Alex Bean Date: Wed, 6 Nov 2024 09:59:43 +0100 Subject: [PATCH 033/211] fix: parse user inputs for Option arguments in constructor (#335) * fix: automatically add some or none to Option argument * fix: tests --- crates/pop-cli/src/commands/call/contract.rs | 8 +- crates/pop-cli/src/commands/up/contract.rs | 2 +- .../src/{call/mod.rs => call.rs} | 3 +- crates/pop-contracts/src/errors.rs | 2 + crates/pop-contracts/src/lib.rs | 10 +- crates/pop-contracts/src/up.rs | 9 +- .../src/{call => utils}/metadata.rs | 193 +++++++++++++++++- crates/pop-contracts/src/utils/mod.rs | 1 + .../tests/files/testing.contract | 2 +- crates/pop-contracts/tests/files/testing.json | 109 +++++----- 10 files changed, 274 insertions(+), 65 deletions(-) rename crates/pop-contracts/src/{call/mod.rs => call.rs} (99%) rename crates/pop-contracts/src/{call => utils}/metadata.rs (52%) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 204aee356..d0c3e8b13 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -760,6 +760,7 @@ mod tests { let mut cli = MockCli::new() .expect_input("Signer calling the contract:", "//Alice".into()) .expect_input("Value to transfer to the call:", "50".into()) // Only if payable + .expect_input("Enter the value for the parameter: number", "2".into()) // Args for specific_flip .expect_input("Enter the value for the parameter: new_value", "true".into()) // Args for specific_flip .expect_select::( "Select the message to call:", @@ -780,7 +781,7 @@ mod tests { "Where is your project located?", temp_dir.path().join("testing").display().to_string(), ).expect_info(format!( - "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true,2 --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", temp_dir.path().join("testing").display().to_string(), )); @@ -804,8 +805,9 @@ mod tests { Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) ); assert_eq!(call_config.message, Some("specific_flip".to_string())); - assert_eq!(call_config.args.len(), 1); + assert_eq!(call_config.args.len(), 2); assert_eq!(call_config.args[0], "true".to_string()); + assert_eq!(call_config.args[1], "2".to_string()); assert_eq!(call_config.value, "50".to_string()); assert_eq!(call_config.gas_limit, None); assert_eq!(call_config.proof_size, None); @@ -815,7 +817,7 @@ mod tests { assert!(!call_config.dry_run); assert!(call_config.dev_mode); assert_eq!(call_config.display(), format!( - "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true,2 --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", temp_dir.path().join("testing").display().to_string(), )); diff --git a/crates/pop-cli/src/commands/up/contract.rs b/crates/pop-cli/src/commands/up/contract.rs index d357d958b..ca8112b28 100644 --- a/crates/pop-cli/src/commands/up/contract.rs +++ b/crates/pop-cli/src/commands/up/contract.rs @@ -35,7 +35,7 @@ pub struct UpContractCommand { #[clap(name = "constructor", long, default_value = "new")] constructor: String, /// The constructor arguments, encoded as strings. - #[clap(long, num_args = 0..)] + #[clap(long, num_args = 0.., value_delimiter = ',')] args: Vec, /// Transfers an initial balance to the instantiated contract. #[clap(name = "value", long, default_value = "0")] diff --git a/crates/pop-contracts/src/call/mod.rs b/crates/pop-contracts/src/call.rs similarity index 99% rename from crates/pop-contracts/src/call/mod.rs rename to crates/pop-contracts/src/call.rs index 69d0f04e5..42cb70823 100644 --- a/crates/pop-contracts/src/call/mod.rs +++ b/crates/pop-contracts/src/call.rs @@ -4,6 +4,7 @@ use crate::{ errors::Error, utils::{ helpers::{get_manifest_path, parse_account, parse_balance}, + metadata, signer::create_signer, }, }; @@ -20,8 +21,6 @@ use subxt::{Config, PolkadotConfig as DefaultConfig}; use subxt_signer::sr25519::Keypair; use url::Url; -pub mod metadata; - /// Attributes for the `call` command. pub struct CallOpts { /// Path to the contract build directory. diff --git a/crates/pop-contracts/src/errors.rs b/crates/pop-contracts/src/errors.rs index f0e019f1c..3d19d679b 100644 --- a/crates/pop-contracts/src/errors.rs +++ b/crates/pop-contracts/src/errors.rs @@ -28,6 +28,8 @@ pub enum Error { InstallContractsNode(String), #[error("{0}")] InstantiateContractError(String), + #[error("Invalid constructor name: {0}")] + InvalidConstructorName(String), #[error("Invalid message name: {0}")] InvalidMessageName(String), #[error("Invalid name: {0}")] diff --git a/crates/pop-contracts/src/lib.rs b/crates/pop-contracts/src/lib.rs index a3ff0175c..2fbb4a639 100644 --- a/crates/pop-contracts/src/lib.rs +++ b/crates/pop-contracts/src/lib.rs @@ -14,9 +14,7 @@ mod utils; pub use build::{build_smart_contract, is_supported, Verbosity}; pub use call::{ - call_smart_contract, dry_run_call, dry_run_gas_estimate_call, - metadata::{get_messages, Message}, - set_up_call, CallOpts, + call_smart_contract, dry_run_call, dry_run_gas_estimate_call, set_up_call, CallOpts, }; pub use new::{create_smart_contract, is_valid_contract_name}; pub use node::{contracts_node_generator, is_chain_alive, run_contracts_node}; @@ -27,4 +25,8 @@ pub use up::{ dry_run_gas_estimate_instantiate, dry_run_upload, instantiate_smart_contract, set_up_deployment, set_up_upload, upload_smart_contract, UpOpts, }; -pub use utils::{helpers::parse_account, signer::parse_hex_bytes}; +pub use utils::{ + helpers::parse_account, + metadata::{get_messages, Message}, + signer::parse_hex_bytes, +}; diff --git a/crates/pop-contracts/src/up.rs b/crates/pop-contracts/src/up.rs index 6d2f0ec03..4542211e0 100644 --- a/crates/pop-contracts/src/up.rs +++ b/crates/pop-contracts/src/up.rs @@ -3,6 +3,7 @@ use crate::{ errors::Error, utils::{ helpers::{get_manifest_path, parse_balance}, + metadata, signer::create_signer, }, }; @@ -62,10 +63,16 @@ pub async fn set_up_deployment( let value: BalanceVariant<::Balance> = parse_balance(&up_opts.value)?; + // Process the argument values input by the user. + let args = metadata::process_constructor_args( + up_opts.path.unwrap_or_else(|| PathBuf::from("./")), + &up_opts.constructor, + up_opts.args, + )?; let instantiate_exec: InstantiateExec = InstantiateCommandBuilder::new(extrinsic_opts) .constructor(up_opts.constructor.clone()) - .args(up_opts.args.clone()) + .args(args) .value(value.denominate_balance(&token_metadata)?) .gas_limit(up_opts.gas_limit) .proof_size(up_opts.proof_size) diff --git a/crates/pop-contracts/src/call/metadata.rs b/crates/pop-contracts/src/utils/metadata.rs similarity index 52% rename from crates/pop-contracts/src/call/metadata.rs rename to crates/pop-contracts/src/utils/metadata.rs index d6c3ccb5f..89f887329 100644 --- a/crates/pop-contracts/src/call/metadata.rs +++ b/crates/pop-contracts/src/utils/metadata.rs @@ -14,6 +14,22 @@ pub struct Param { /// The type name of the parameter. pub type_name: String, } + +#[derive(Clone, PartialEq, Eq)] +/// Describes a contract message. +pub struct Constructor { + /// The label of the constructor. + pub label: String, + /// If the message accepts any `value` from the caller. + pub payable: bool, + /// The parameters of the deployment handler. + pub args: Vec, + /// The constructor documentation. + pub docs: String, + /// If the constructor is the default for off-chain consumers (e.g UIs). + pub default: bool, +} + #[derive(Clone, PartialEq, Eq)] /// Describes a contract message. pub struct Message { @@ -72,10 +88,50 @@ where .ok_or_else(|| Error::InvalidMessageName(message.to_string())) } -// Parse the message parameters into a vector of argument labels. -fn process_args(message_params: &[MessageParamSpec]) -> Vec { +/// Extracts a list of smart contract contructors parsing the metadata file. +/// +/// # Arguments +/// * `path` - Location path of the project. +pub fn get_constructors(path: &Path) -> Result, Error> { + let cargo_toml_path = match path.ends_with("Cargo.toml") { + true => path.to_path_buf(), + false => path.join("Cargo.toml"), + }; + let contract_artifacts = + ContractArtifacts::from_manifest_or_file(Some(&cargo_toml_path), None)?; + let transcoder = contract_artifacts.contract_transcoder()?; + let mut constructors: Vec = Vec::new(); + for constructor in transcoder.metadata().spec().constructors() { + constructors.push(Constructor { + label: constructor.label().to_string(), + payable: *constructor.payable(), + args: process_args(constructor.args()), + docs: constructor.docs().join(" "), + default: *constructor.default(), + }); + } + Ok(constructors) +} + +/// Extracts the information of a smart contract constructor parsing the metadata file. +/// +/// # Arguments +/// * `path` - Location path of the project. +/// * `constructor` - The label of the constructor. +fn get_constructor

(path: P, constructor: &str) -> Result +where + P: AsRef, +{ + get_constructors(path.as_ref())? + .into_iter() + .find(|c: &Constructor| c.label == constructor) + .ok_or_else(|| Error::InvalidConstructorName(constructor.to_string())) +} + +// Parse the parameters into a vector of argument labels. +fn process_args(params: &[MessageParamSpec]) -> Vec { let mut args: Vec = Vec::new(); - for arg in message_params { + for arg in params { args.push(Param { label: arg.label().to_string(), type_name: arg.ty().display_name().to_string(), @@ -119,6 +175,41 @@ where .collect::>()) } +/// Processes a list of argument values for a specified contract constructor, +/// wrapping each value in `Some(...)` or replacing it with `None` if the argument is optional +/// +/// # Arguments +/// * `path` - Location path of the project. +/// * `constructor` - Label of the contract constructor to retrieve. +/// * `args` - Argument values provided by the user. +pub fn process_constructor_args

( + path: P, + constructor: &str, + args: Vec, +) -> Result, Error> +where + P: AsRef, +{ + let constructor = get_constructor(path, constructor)?; + if args.len() != constructor.args.len() { + return Err(Error::IncorrectArguments { + expected: constructor.args.len(), + provided: args.len(), + }); + } + Ok(args + .into_iter() + .zip(&constructor.args) + .map(|(arg, param)| match (param.type_name.as_str(), arg.is_empty()) { + ("Option", true) => "None".to_string(), /* If the argument is Option and empty, */ + // replace it with `None` + ("Option", false) => format!("Some({})", arg), /* If the argument is Option and not */ + // empty, wrap it in `Some(...)` + _ => arg, // If the argument is not Option, return it as is + }) + .collect::>()) +} + #[cfg(test)] mod tests { use std::env; @@ -177,6 +268,63 @@ mod tests { Ok(()) } + #[test] + fn get_constructors_work() -> Result<()> { + let temp_dir = new_environment("testing")?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; + let constructor = get_constructors(&temp_dir.path().join("testing"))?; + assert_eq!(constructor.len(), 2); + assert_eq!(constructor[0].label, "new"); + assert_eq!( + constructor[0].docs, + "Constructor that initializes the `bool` value to the given `init_value`." + ); + assert_eq!(constructor[1].label, "default"); + assert_eq!( + constructor[1].docs, + "Constructor that initializes the `bool` value to `false`. Constructors can delegate to other constructors." + ); + // assert parsed arguments + assert_eq!(constructor[0].args.len(), 2); + assert_eq!(constructor[0].args[0].label, "init_value".to_string()); + assert_eq!(constructor[0].args[0].type_name, "bool".to_string()); + assert_eq!(constructor[0].args[1].label, "number".to_string()); + assert_eq!(constructor[0].args[1].type_name, "Option".to_string()); + Ok(()) + } + + #[test] + fn get_constructor_work() -> Result<()> { + let temp_dir = new_environment("testing")?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; + assert!(matches!( + get_constructor(&temp_dir.path().join("testing"), "wrong_constructor"), + Err(Error::InvalidConstructorName(name)) if name == "wrong_constructor".to_string())); + let constructor = get_constructor(&temp_dir.path().join("testing"), "new")?; + assert_eq!(constructor.label, "new"); + assert_eq!( + constructor.docs, + "Constructor that initializes the `bool` value to the given `init_value`." + ); + // assert parsed arguments + assert_eq!(constructor.args.len(), 2); + assert_eq!(constructor.args[0].label, "init_value".to_string()); + assert_eq!(constructor.args[0].type_name, "bool".to_string()); + assert_eq!(constructor.args[1].label, "number".to_string()); + assert_eq!(constructor.args[1].type_name, "Option".to_string()); + Ok(()) + } + #[test] fn process_message_args_work() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; @@ -215,4 +363,43 @@ mod tests { ); Ok(()) } + + #[test] + fn process_constructor_args_work() -> Result<()> { + let temp_dir = new_environment("testing")?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; + assert!(matches!( + process_constructor_args(temp_dir.path().join("testing"),"wrong_constructor", Vec::new()), + Err(Error::InvalidConstructorName(error)) if error == "wrong_constructor".to_string())); + assert!(matches!( + process_constructor_args( + temp_dir.path().join("testing"), + "new", + Vec::new() + ), + Err(Error::IncorrectArguments {expected, provided }) if expected == 2 && provided == 0 + )); + assert_eq!( + process_constructor_args( + temp_dir.path().join("testing"), + "new", + ["true".to_string(), "2".to_string()].to_vec() + )?, + ["true".to_string(), "Some(2)".to_string()] + ); + assert_eq!( + process_constructor_args( + temp_dir.path().join("testing"), + "new", + ["true".to_string(), "".to_string()].to_vec() + )?, + ["true".to_string(), "None".to_string()] + ); + Ok(()) + } } diff --git a/crates/pop-contracts/src/utils/mod.rs b/crates/pop-contracts/src/utils/mod.rs index 357c66082..ad49e2dc6 100644 --- a/crates/pop-contracts/src/utils/mod.rs +++ b/crates/pop-contracts/src/utils/mod.rs @@ -1,4 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 pub mod helpers; +pub mod metadata; pub mod signer; diff --git a/crates/pop-contracts/tests/files/testing.contract b/crates/pop-contracts/tests/files/testing.contract index 73ff479a5..7918b7184 100644 --- a/crates/pop-contracts/tests/files/testing.contract +++ b/crates/pop-contracts/tests/files/testing.contract @@ -1 +1 @@ -{"source":{"hash":"0xd35166baa65bf10eb07c3d30c052d0fe752d6b98a864b61062e189be1a1ed550","language":"ink! 5.0.0","compiler":"rustc 1.78.0","wasm":"0x0061736d0100000001270760027f7f0060037f7f7f017f60000060047f7f7f7f017f60037f7f7f006000017f60017f017f027406057365616c310b6765745f73746f726167650003057365616c3005696e7075740000057365616c320b7365745f73746f726167650003057365616c300b7365616c5f72657475726e0004057365616c301176616c75655f7472616e73666572726564000003656e76066d656d6f727902010210031110010101010004000506020000000002020616037f01418080040b7f00418080050b7f00418080050b0711020463616c6c0013066465706c6f7900140aff0d102b01017f037f2002200346047f200005200020036a200120036a2d00003a0000200341016a21030c010b0b0b6f01017f0240200020014d04402000210303402002450d02200320012d00003a0000200341016a2103200141016a2101200241016b21020c000b000b200041016b2103200141016b210103402002450d01200220036a200120026a2d00003a0000200241016b21020c000b000b20000b2501017f037f2002200346047f200005200020036a20013a0000200341016a21030c010b0b0b3f01027f0340200245044041000f0b200241016b210220012d0000210320002d00002104200141016a2101200041016a210020032004460d000b200420036b0b2601017f230041106b220224002002200036020c20012002410c6a4104100a200241106a24000b4801027f024002402000280208220320026a22042003490d00200420002802044b0d00200420036b2002470d01200028020020036a2001200210051a200020043602080f0b000b000b2601017f230041106b22022400200220003a000f20012002410f6a4101100a200241106a24000b6102027f027e230041206b22002400200041106a22014200370300200042003703082000411036021c200041086a2000411c6a1004200028021c41114f0440000b2001290300210220002903082103200041206a2400410541042002200384501b0b7301037f230041106b22012400200141086a220320002802042202047f2000200241016b36020420002000280200220041016a36020020002d00000520000b3a000120032002453a000020012d0009210020012d00082102200141106a240041024101410220004101461b410020001b20021b0b12004180800441003b0100410041021011000b3c01027f027f200145044041808004210141010c010b410121024180800441013a000041818004210141020b2103200120023a0000200020031011000b920101057f230041106b220224002002428080013702082002418080043602044100200241046a22041009024020022802082206200228020c2203490d00200228020421052002410036020c2002200620036b3602082002200320056a36020420012004100b200020041009200228020c220020022802084b0d00200520032002280204200010021a200241106a24000f0b000b0d0020004180800420011003000b08002000200110100ba70501087f230041206b2200240020004180800136021441808004200041146a100102400240024020002802142202418180014f0d004104210541042106024020024104490d0020004184800436020c2000200241046b360210418380042d00002101418280042d00002104418180042d0000210302400240418080042d00002202412f470440200241ec00460d01200241e300472003413a4772200441a50147200141d1004772720d03410221010c020b200341860147200441db004772200141d90147720d02410321010c010b2003410f472004411d4772200141f70147720d012000410c6a100d220241ff01714102460d0120002802102204450d010240200028020c22032d000022010e020100020b20044105490d0120032800012106410121010b20002001360218200020023a0014410821050b200041146a220220056a2006360200200028021822044104460d01200028021c210520002d0014210620004280800137021820004180800436021441002002100920002802182207200028021c2201490d00200028021421032000200720016b220736021420032001200120036a2201200210002000280214220320074b720d0020002003360218200020013602142002100d220241ff01714102460d002000280218220141034d2001410447720d00200028021428000021000240024002404102200441026b2201200141024f1b41016b0e020100020b2005200020041b2006410047101241004100100f000b100c41ff01714105470d01230041106b220024002000418080043602044180800441003a00002000428080818010370208200241ff0171410047200041046a100b200028020c2200418180014f0440000b410020001011000b100c41ff01714105460d020b000b41014101100f000b2000200241ff017145101241004100100f000be90101057f230041106b2200240002400240100c41ff01714105470d0020004180800136020c418080042000410c6a1001200028020c2201418180014f0d0020014104490d012000418480043602042000200141046b360208418380042d00002101418280042d00002102418180042d000021030240418080042d0000220441ed014704402004419b0147200341ae0147722002419d0147200141de004772720d03200041046a100d220041ff01714102470d010c030b200341cb00462002419d0146712001411b4671450d02410041001010100e000b410020001010100e000b000b41014101100f000b","build_info":{"rust_toolchain":"stable-aarch64-apple-darwin","cargo_contract_version":"5.0.0-alpha","build_mode":"Release","wasm_opt_settings":{"optimization_passes":"Z","keep_debug_symbols":false}}},"contract":{"name":"testing","version":"0.1.0","authors":["[your_name] <[your_email]>"]},"image":null,"version":5,"types":[{"id":0,"type":{"def":{"primitive":"bool"}}},{"id":1,"type":{"def":{"primitive":"u32"}}},{"id":2,"type":{"path":["testing","testing","Testing"],"def":{"composite":{"fields":[{"name":"value","type":0,"typeName":",>>::Type"},{"name":"number","type":1,"typeName":",>>::Type"}]}}}},{"id":3,"type":{"path":["Result"],"params":[{"name":"T","type":4},{"name":"E","type":5}],"def":{"variant":{"variants":[{"name":"Ok","fields":[{"type":4}],"index":0},{"name":"Err","fields":[{"type":5}],"index":1}]}}}},{"id":4,"type":{"def":{"tuple":[]}}},{"id":5,"type":{"path":["ink_primitives","LangError"],"def":{"variant":{"variants":[{"name":"CouldNotReadInput","index":1}]}}}},{"id":6,"type":{"path":["Result"],"params":[{"name":"T","type":0},{"name":"E","type":5}],"def":{"variant":{"variants":[{"name":"Ok","fields":[{"type":0}],"index":0},{"name":"Err","fields":[{"type":5}],"index":1}]}}}},{"id":7,"type":{"path":["Option"],"params":[{"name":"T","type":1}],"def":{"variant":{"variants":[{"name":"None","index":0},{"name":"Some","fields":[{"type":1}],"index":1}]}}}},{"id":8,"type":{"path":["ink_primitives","types","AccountId"],"def":{"composite":{"fields":[{"type":9,"typeName":"[u8; 32]"}]}}}},{"id":9,"type":{"def":{"array":{"len":32,"type":10}}}},{"id":10,"type":{"def":{"primitive":"u8"}}},{"id":11,"type":{"def":{"primitive":"u128"}}},{"id":12,"type":{"path":["ink_primitives","types","Hash"],"def":{"composite":{"fields":[{"type":9,"typeName":"[u8; 32]"}]}}}},{"id":13,"type":{"def":{"primitive":"u64"}}},{"id":14,"type":{"path":["ink_env","types","NoChainExtension"],"def":{"variant":{}}}}],"storage":{"root":{"root_key":"0x00000000","layout":{"struct":{"name":"Testing","fields":[{"name":"value","layout":{"leaf":{"key":"0x00000000","ty":0}}},{"name":"number","layout":{"leaf":{"key":"0x00000000","ty":1}}}]}},"ty":2}},"spec":{"constructors":[{"label":"new","selector":"0x9bae9d5e","payable":false,"args":[{"label":"init_value","type":{"type":0,"displayName":["bool"]}}],"returnType":{"type":3,"displayName":["ink_primitives","ConstructorResult"]},"docs":["Constructor that initializes the `bool` value to the given `init_value`."],"default":false},{"label":"default","selector":"0xed4b9d1b","payable":false,"args":[],"returnType":{"type":3,"displayName":["ink_primitives","ConstructorResult"]},"docs":["Constructor that initializes the `bool` value to `false`.","","Constructors can delegate to other constructors."],"default":false}],"messages":[{"label":"flip","selector":"0x633aa551","mutates":true,"payable":false,"args":[],"returnType":{"type":3,"displayName":["ink","MessageResult"]},"docs":[" A message that can be called on instantiated contracts."," This one flips the value of the stored `bool` from `true`"," to `false` and vice versa."],"default":false},{"label":"get","selector":"0x2f865bd9","mutates":false,"payable":false,"args":[],"returnType":{"type":6,"displayName":["ink","MessageResult"]},"docs":[" Simply returns the current value of our `bool`."],"default":false},{"label":"specific_flip","selector":"0x6c0f1df7","mutates":true,"payable":true,"args":[{"label":"new_value","type":{"type":0,"displayName":["bool"]}},{"label":"number","type":{"type":7,"displayName":["Option"]}}],"returnType":{"type":3,"displayName":["ink","MessageResult"]},"docs":[" A message for testing, flips the value of the stored `bool` with `new_value`"," and is payable"],"default":false}],"events":[],"docs":[],"lang_error":{"type":5,"displayName":["ink","LangError"]},"environment":{"accountId":{"type":8,"displayName":["AccountId"]},"balance":{"type":11,"displayName":["Balance"]},"hash":{"type":12,"displayName":["Hash"]},"timestamp":{"type":13,"displayName":["Timestamp"]},"blockNumber":{"type":1,"displayName":["BlockNumber"]},"chainExtension":{"type":14,"displayName":["ChainExtension"]},"maxEventTopics":4,"staticBufferSize":16384}}} \ No newline at end of file +{"source":{"hash":"0xea3a2d4428c3717ab229f1822014375d42a0497a0b86eb8fbb405ec6733899e9","language":"ink! 5.0.0","compiler":"rustc 1.78.0","wasm":"0x0061736d0100000001270760027f7f0060037f7f7f017f60000060047f7f7f7f017f60037f7f7f0060017f017f6000017f027406057365616c310b6765745f73746f726167650003057365616c3005696e7075740000057365616c320b7365745f73746f726167650003057365616c300b7365616c5f72657475726e0004057365616c301176616c75655f7472616e73666572726564000003656e76066d656d6f7279020102100313120101010100050000040006020000000002020616037f01418080040b7f00418080050b7f00418080050b0711020463616c6c0015066465706c6f7900160ac910122b01017f037f2002200346047f200005200020036a200120036a2d00003a0000200341016a21030c010b0b0b6f01017f0240200020014d04402000210303402002450d02200320012d00003a0000200341016a2103200141016a2101200241016b21020c000b000b200041016b2103200141016b210103402002450d01200220036a200120026a2d00003a0000200241016b21020c000b000b20000b2501017f037f2002200346047f200005200020036a20013a0000200341016a21030c010b0b0b3f01027f0340200245044041000f0b200241016b210220012d0000210320002d00002104200141016a2101200041016a210020032004460d000b200420036b0bc00101057f230041106b22022400410221044104210502402001100a220641ff01714102460d00200241086a2001100b20022d00080d000240024020022d000922030e020100020b20012802042203410449047f4101052001200341046b36020420012001280200220141046a3602002001280000210341000b2101200220033602042002200136020020022802000d0141012103200228020421040b20002003360204200020063a0000410821050b200020056a2004360200200241106a24000b3f01027f230041106b22012400200141086a2000100b20012d0009210020012d00082102200141106a240041024101410220004101461b410020001b20021b0b3c01017f200020012802042202047f2001200241016b36020420012001280200220141016a36020020012d00000520010b3a000120002002453a00000b2601017f230041106b220224002002200036020c20012002410c6a4104100d200241106a24000b4801027f024002402000280208220320026a22042003490d00200420002802044b0d00200420036b2002470d01200028020020036a2001200210051a200020043602080f0b000b000b2601017f230041106b22022400200220003a000f20012002410f6a4101100d200241106a24000b6102027f027e230041206b22002400200041106a22014200370300200042003703082000411036021c200041086a2000411c6a1004200028021c41114f0440000b2001290300210220002903082103200041206a2400410541042002200384501b0b12004180800441003b0100410041021013000b3c01027f027f200145044041808004210141010c010b410121024180800441013a000041818004210141020b2103200120023a0000200020031013000b920101057f230041106b220224002002428080013702082002418080043602044100200241046a2204100c024020022802082206200228020c2203490d00200228020421052002410036020c2002200620036b3602082002200320056a36020420012004100e20002004100c200228020c220020022802084b0d00200520032002280204200010021a200241106a24000f0b000b0d0020004180800420011003000b08002000200110120bb60501087f230041206b2200240020004180800136021441808004200041146a100102400240024020002802142201418180014f0d0041042104027f410420014104490d001a20004184800436020c2000200141046b360210418380042d00002101418280042d00002102418180042d00002103024002400240418080042d00002205412f470440200541ec00460d014104200541e300470d041a41042003413a470d041a4104200241a501470d041a41042202200141d100470d041a410221010c020b41042003418601470d031a4104200241db00470d031a41042202200141d901470d031a410321010c010b41042003410f470d021a41042002411d470d021a4104200141f701470d021a200041146a2000410c6a1009200028021822014102460d01200028021c210420002d001421020b20002001360204200020023a000041080c010b41040b20006a2004360200200028020422024104460d012000280208210520002d000021072000428080013702182000418080043602144100200041146a2203100c20002802182206200028021c2201490d00200028021421042000200620016b220636021420042001200120046a2201200310002000280214220420064b720d0020002004360218200020013602142003100a220141ff01714102460d002000280218220341034d2003410447720d00200028021428000021000240024002404102200241026b2203200341024f1b41016b0e020100020b2005200020021b20074100471014410041001011000b100f41ff01714105470d01230041106b220024002000418080043602044180800441003a00002000428080818010370208200141ff0171410047200041046a100e200028020c2200418180014f0440000b410020001013000b100f41ff01714105460d020b000b410141011011000b2000200141ff0171451014410041001011000bd90201067f230041206b22002400024002400240100f41ff01714105470d0020004180800136021441808004200041146a100120002802142201418180014f0d004104210220014104490d0120004184800436020c2000200141046b360210418380042d00002103418280042d00002101418180042d0000210402400240418080042d0000220541ed014704402005419b0147200441ae0147722001419d0147720d04200341de00460d010c040b200441cb00472001419d0147720d03410321012003411b470d04410221030c010b200041146a2000410c6a1009200028021822034102460d02200028021c210120002d001421020b20002003360204200020023a0000410821020c020b000b410321010b200020026a2001360200024020002802042202410347044020024102470d014100410010121010000b410141011011000b2000280208410020021b20002d000041004710121010000b","build_info":{"rust_toolchain":"stable-aarch64-apple-darwin","cargo_contract_version":"5.0.0-alpha","build_mode":"Release","wasm_opt_settings":{"optimization_passes":"Z","keep_debug_symbols":false}}},"contract":{"name":"testing","version":"0.1.0","authors":["[your_name] <[your_email]>"]},"image":null,"version":5,"types":[{"id":0,"type":{"def":{"primitive":"bool"}}},{"id":1,"type":{"def":{"primitive":"u32"}}},{"id":2,"type":{"path":["testing","testing","Testing"],"def":{"composite":{"fields":[{"name":"value","type":0,"typeName":",>>::Type"},{"name":"number","type":1,"typeName":",>>::Type"}]}}}},{"id":3,"type":{"path":["Option"],"params":[{"name":"T","type":1}],"def":{"variant":{"variants":[{"name":"None","index":0},{"name":"Some","fields":[{"type":1}],"index":1}]}}}},{"id":4,"type":{"path":["Result"],"params":[{"name":"T","type":5},{"name":"E","type":6}],"def":{"variant":{"variants":[{"name":"Ok","fields":[{"type":5}],"index":0},{"name":"Err","fields":[{"type":6}],"index":1}]}}}},{"id":5,"type":{"def":{"tuple":[]}}},{"id":6,"type":{"path":["ink_primitives","LangError"],"def":{"variant":{"variants":[{"name":"CouldNotReadInput","index":1}]}}}},{"id":7,"type":{"path":["Result"],"params":[{"name":"T","type":0},{"name":"E","type":6}],"def":{"variant":{"variants":[{"name":"Ok","fields":[{"type":0}],"index":0},{"name":"Err","fields":[{"type":6}],"index":1}]}}}},{"id":8,"type":{"path":["ink_primitives","types","AccountId"],"def":{"composite":{"fields":[{"type":9,"typeName":"[u8; 32]"}]}}}},{"id":9,"type":{"def":{"array":{"len":32,"type":10}}}},{"id":10,"type":{"def":{"primitive":"u8"}}},{"id":11,"type":{"def":{"primitive":"u128"}}},{"id":12,"type":{"path":["ink_primitives","types","Hash"],"def":{"composite":{"fields":[{"type":9,"typeName":"[u8; 32]"}]}}}},{"id":13,"type":{"def":{"primitive":"u64"}}},{"id":14,"type":{"path":["ink_env","types","NoChainExtension"],"def":{"variant":{}}}}],"storage":{"root":{"root_key":"0x00000000","layout":{"struct":{"name":"Testing","fields":[{"name":"value","layout":{"leaf":{"key":"0x00000000","ty":0}}},{"name":"number","layout":{"leaf":{"key":"0x00000000","ty":1}}}]}},"ty":2}},"spec":{"constructors":[{"label":"new","selector":"0x9bae9d5e","payable":false,"args":[{"label":"init_value","type":{"type":0,"displayName":["bool"]}},{"label":"number","type":{"type":3,"displayName":["Option"]}}],"returnType":{"type":4,"displayName":["ink_primitives","ConstructorResult"]},"docs":["Constructor that initializes the `bool` value to the given `init_value`."],"default":false},{"label":"default","selector":"0xed4b9d1b","payable":false,"args":[],"returnType":{"type":4,"displayName":["ink_primitives","ConstructorResult"]},"docs":["Constructor that initializes the `bool` value to `false`.","","Constructors can delegate to other constructors."],"default":false}],"messages":[{"label":"flip","selector":"0x633aa551","mutates":true,"payable":false,"args":[],"returnType":{"type":4,"displayName":["ink","MessageResult"]},"docs":[" A message that can be called on instantiated contracts."," This one flips the value of the stored `bool` from `true`"," to `false` and vice versa."],"default":false},{"label":"get","selector":"0x2f865bd9","mutates":false,"payable":false,"args":[],"returnType":{"type":7,"displayName":["ink","MessageResult"]},"docs":[" Simply returns the current value of our `bool`."],"default":false},{"label":"specific_flip","selector":"0x6c0f1df7","mutates":true,"payable":true,"args":[{"label":"new_value","type":{"type":0,"displayName":["bool"]}},{"label":"number","type":{"type":3,"displayName":["Option"]}}],"returnType":{"type":4,"displayName":["ink","MessageResult"]},"docs":[" A message for testing, flips the value of the stored `bool` with `new_value`"," and is payable"],"default":false}],"events":[],"docs":[],"lang_error":{"type":6,"displayName":["ink","LangError"]},"environment":{"accountId":{"type":8,"displayName":["AccountId"]},"balance":{"type":11,"displayName":["Balance"]},"hash":{"type":12,"displayName":["Hash"]},"timestamp":{"type":13,"displayName":["Timestamp"]},"blockNumber":{"type":1,"displayName":["BlockNumber"]},"chainExtension":{"type":14,"displayName":["ChainExtension"]},"maxEventTopics":4,"staticBufferSize":16384}}} \ No newline at end of file diff --git a/crates/pop-contracts/tests/files/testing.json b/crates/pop-contracts/tests/files/testing.json index 8f28780d5..a5bad9291 100644 --- a/crates/pop-contracts/tests/files/testing.json +++ b/crates/pop-contracts/tests/files/testing.json @@ -1,6 +1,6 @@ { "source": { - "hash": "0xd35166baa65bf10eb07c3d30c052d0fe752d6b98a864b61062e189be1a1ed550", + "hash": "0xea3a2d4428c3717ab229f1822014375d42a0497a0b86eb8fbb405ec6733899e9", "language": "ink! 5.0.0", "compiler": "rustc 1.78.0", "build_info": { @@ -67,6 +67,39 @@ }, { "id": 3, + "type": { + "path": [ + "Option" + ], + "params": [ + { + "name": "T", + "type": 1 + } + ], + "def": { + "variant": { + "variants": [ + { + "name": "None", + "index": 0 + }, + { + "name": "Some", + "fields": [ + { + "type": 1 + } + ], + "index": 1 + } + ] + } + } + } + }, + { + "id": 4, "type": { "path": [ "Result" @@ -74,11 +107,11 @@ "params": [ { "name": "T", - "type": 4 + "type": 5 }, { "name": "E", - "type": 5 + "type": 6 } ], "def": { @@ -88,7 +121,7 @@ "name": "Ok", "fields": [ { - "type": 4 + "type": 5 } ], "index": 0 @@ -97,7 +130,7 @@ "name": "Err", "fields": [ { - "type": 5 + "type": 6 } ], "index": 1 @@ -108,7 +141,7 @@ } }, { - "id": 4, + "id": 5, "type": { "def": { "tuple": [] @@ -116,7 +149,7 @@ } }, { - "id": 5, + "id": 6, "type": { "path": [ "ink_primitives", @@ -135,7 +168,7 @@ } }, { - "id": 6, + "id": 7, "type": { "path": [ "Result" @@ -147,7 +180,7 @@ }, { "name": "E", - "type": 5 + "type": 6 } ], "def": { @@ -166,40 +199,7 @@ "name": "Err", "fields": [ { - "type": 5 - } - ], - "index": 1 - } - ] - } - } - } - }, - { - "id": 7, - "type": { - "path": [ - "Option" - ], - "params": [ - { - "name": "T", - "type": 1 - } - ], - "def": { - "variant": { - "variants": [ - { - "name": "None", - "index": 0 - }, - { - "name": "Some", - "fields": [ - { - "type": 1 + "type": 6 } ], "index": 1 @@ -344,10 +344,19 @@ "bool" ] } + }, + { + "label": "number", + "type": { + "type": 3, + "displayName": [ + "Option" + ] + } } ], "returnType": { - "type": 3, + "type": 4, "displayName": [ "ink_primitives", "ConstructorResult" @@ -364,7 +373,7 @@ "payable": false, "args": [], "returnType": { - "type": 3, + "type": 4, "displayName": [ "ink_primitives", "ConstructorResult" @@ -386,7 +395,7 @@ "payable": false, "args": [], "returnType": { - "type": 3, + "type": 4, "displayName": [ "ink", "MessageResult" @@ -406,7 +415,7 @@ "payable": false, "args": [], "returnType": { - "type": 6, + "type": 7, "displayName": [ "ink", "MessageResult" @@ -435,7 +444,7 @@ { "label": "number", "type": { - "type": 7, + "type": 3, "displayName": [ "Option" ] @@ -443,7 +452,7 @@ } ], "returnType": { - "type": 3, + "type": 4, "displayName": [ "ink", "MessageResult" @@ -459,7 +468,7 @@ "events": [], "docs": [], "lang_error": { - "type": 5, + "type": 6, "displayName": [ "ink", "LangError" From cf09406568b8a54c67edddead4e5896cbca65778 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Wed, 6 Nov 2024 12:50:28 +0100 Subject: [PATCH 034/211] refactor: process_function_args --- crates/pop-contracts/src/call.rs | 5 +- crates/pop-contracts/src/lib.rs | 2 +- crates/pop-contracts/src/up.rs | 5 +- crates/pop-contracts/src/utils/metadata.rs | 148 ++++++++------------- 4 files changed, 60 insertions(+), 100 deletions(-) diff --git a/crates/pop-contracts/src/call.rs b/crates/pop-contracts/src/call.rs index 42cb70823..7a964c387 100644 --- a/crates/pop-contracts/src/call.rs +++ b/crates/pop-contracts/src/call.rs @@ -4,7 +4,7 @@ use crate::{ errors::Error, utils::{ helpers::{get_manifest_path, parse_account, parse_balance}, - metadata, + metadata::{process_function_args, FunctionType}, signer::create_signer, }, }; @@ -67,10 +67,11 @@ pub async fn set_up_call( let contract: ::AccountId = parse_account(&call_opts.contract)?; // Process the argument values input by the user. - let args = metadata::process_message_args( + let args = process_function_args( call_opts.path.unwrap_or_else(|| PathBuf::from("./")), &call_opts.message, call_opts.args, + FunctionType::Message, )?; let call_exec: CallExec = diff --git a/crates/pop-contracts/src/lib.rs b/crates/pop-contracts/src/lib.rs index 2fbb4a639..142283998 100644 --- a/crates/pop-contracts/src/lib.rs +++ b/crates/pop-contracts/src/lib.rs @@ -27,6 +27,6 @@ pub use up::{ }; pub use utils::{ helpers::parse_account, - metadata::{get_messages, Message}, + metadata::{get_messages, ContractFunction}, signer::parse_hex_bytes, }; diff --git a/crates/pop-contracts/src/up.rs b/crates/pop-contracts/src/up.rs index 4542211e0..2b304123b 100644 --- a/crates/pop-contracts/src/up.rs +++ b/crates/pop-contracts/src/up.rs @@ -3,7 +3,7 @@ use crate::{ errors::Error, utils::{ helpers::{get_manifest_path, parse_balance}, - metadata, + metadata::{process_function_args, FunctionType}, signer::create_signer, }, }; @@ -64,10 +64,11 @@ pub async fn set_up_deployment( parse_balance(&up_opts.value)?; // Process the argument values input by the user. - let args = metadata::process_constructor_args( + let args = process_function_args( up_opts.path.unwrap_or_else(|| PathBuf::from("./")), &up_opts.constructor, up_opts.args, + FunctionType::Constructor, )?; let instantiate_exec: InstantiateExec = InstantiateCommandBuilder::new(extrinsic_opts) diff --git a/crates/pop-contracts/src/utils/metadata.rs b/crates/pop-contracts/src/utils/metadata.rs index 89f887329..bf3458313 100644 --- a/crates/pop-contracts/src/utils/metadata.rs +++ b/crates/pop-contracts/src/utils/metadata.rs @@ -16,8 +16,8 @@ pub struct Param { } #[derive(Clone, PartialEq, Eq)] -/// Describes a contract message. -pub struct Constructor { +/// Describes a contract function. +pub struct ContractFunction { /// The label of the constructor. pub label: String, /// If the message accepts any `value` from the caller. @@ -28,30 +28,22 @@ pub struct Constructor { pub docs: String, /// If the constructor is the default for off-chain consumers (e.g UIs). pub default: bool, + /// If the message is allowed to mutate the contract state. true for constructors. + pub mutates: bool, } #[derive(Clone, PartialEq, Eq)] -/// Describes a contract message. -pub struct Message { - /// The label of the message. - pub label: String, - /// If the message is allowed to mutate the contract state. - pub mutates: bool, - /// If the message accepts any `value` from the caller. - pub payable: bool, - /// The parameters of the deployment handler. - pub args: Vec, - /// The message documentation. - pub docs: String, - /// If the message is the default for off-chain consumers (e.g UIs). - pub default: bool, +/// Specifies the type of contract funtion, either a constructor or a message. +pub enum FunctionType { + Constructor, + Message, } /// Extracts a list of smart contract messages parsing the metadata file. /// /// # Arguments /// * `path` - Location path of the project. -pub fn get_messages(path: &Path) -> Result, Error> { +pub fn get_messages(path: &Path) -> Result, Error> { let cargo_toml_path = match path.ends_with("Cargo.toml") { true => path.to_path_buf(), false => path.join("Cargo.toml"), @@ -59,9 +51,9 @@ pub fn get_messages(path: &Path) -> Result, Error> { let contract_artifacts = ContractArtifacts::from_manifest_or_file(Some(&cargo_toml_path), None)?; let transcoder = contract_artifacts.contract_transcoder()?; - let mut messages: Vec = Vec::new(); + let mut messages: Vec = Vec::new(); for message in transcoder.metadata().spec().messages() { - messages.push(Message { + messages.push(ContractFunction { label: message.label().to_string(), mutates: message.mutates(), payable: message.payable(), @@ -78,7 +70,7 @@ pub fn get_messages(path: &Path) -> Result, Error> { /// # Arguments /// * `path` - Location path of the project. /// * `message` - The label of the contract message. -fn get_message

(path: P, message: &str) -> Result +fn get_message

(path: P, message: &str) -> Result where P: AsRef, { @@ -92,7 +84,7 @@ where /// /// # Arguments /// * `path` - Location path of the project. -pub fn get_constructors(path: &Path) -> Result, Error> { +pub fn get_constructors(path: &Path) -> Result, Error> { let cargo_toml_path = match path.ends_with("Cargo.toml") { true => path.to_path_buf(), false => path.join("Cargo.toml"), @@ -100,14 +92,15 @@ pub fn get_constructors(path: &Path) -> Result, Error> { let contract_artifacts = ContractArtifacts::from_manifest_or_file(Some(&cargo_toml_path), None)?; let transcoder = contract_artifacts.contract_transcoder()?; - let mut constructors: Vec = Vec::new(); + let mut constructors: Vec = Vec::new(); for constructor in transcoder.metadata().spec().constructors() { - constructors.push(Constructor { + constructors.push(ContractFunction { label: constructor.label().to_string(), payable: *constructor.payable(), args: process_args(constructor.args()), docs: constructor.docs().join(" "), default: *constructor.default(), + mutates: true, }); } Ok(constructors) @@ -118,13 +111,13 @@ pub fn get_constructors(path: &Path) -> Result, Error> { /// # Arguments /// * `path` - Location path of the project. /// * `constructor` - The label of the constructor. -fn get_constructor

(path: P, constructor: &str) -> Result +fn get_constructor

(path: P, constructor: &str) -> Result where P: AsRef, { get_constructors(path.as_ref())? .into_iter() - .find(|c: &Constructor| c.label == constructor) + .find(|c| c.label == constructor) .ok_or_else(|| Error::InvalidConstructorName(constructor.to_string())) } @@ -140,66 +133,35 @@ fn process_args(params: &[MessageParamSpec]) -> Vec { args } -/// Processes a list of argument values for a specified contract message, +/// Processes a list of argument values for a specified contract function, /// wrapping each value in `Some(...)` or replacing it with `None` if the argument is optional /// /// # Arguments /// * `path` - Location path of the project. -/// * `message_label` - Label of the contract message to retrieve. +/// * `label` - Label of the contract message to retrieve. /// * `args` - Argument values provided by the user. -pub fn process_message_args

( +pub fn process_function_args

( path: P, - message: &str, + label: &str, args: Vec, + function_type: FunctionType, ) -> Result, Error> where P: AsRef, { - let message = get_message(path, message)?; - if args.len() != message.args.len() { - return Err(Error::IncorrectArguments { - expected: message.args.len(), - provided: args.len(), - }); - } - Ok(args - .into_iter() - .zip(&message.args) - .map(|(arg, param)| match (param.type_name.as_str(), arg.is_empty()) { - ("Option", true) => "None".to_string(), /* If the argument is Option and empty, */ - // replace it with `None` - ("Option", false) => format!("Some({})", arg), /* If the argument is Option and not */ - // empty, wrap it in `Some(...)` - _ => arg, // If the argument is not Option, return it as is - }) - .collect::>()) -} - -/// Processes a list of argument values for a specified contract constructor, -/// wrapping each value in `Some(...)` or replacing it with `None` if the argument is optional -/// -/// # Arguments -/// * `path` - Location path of the project. -/// * `constructor` - Label of the contract constructor to retrieve. -/// * `args` - Argument values provided by the user. -pub fn process_constructor_args

( - path: P, - constructor: &str, - args: Vec, -) -> Result, Error> -where - P: AsRef, -{ - let constructor = get_constructor(path, constructor)?; - if args.len() != constructor.args.len() { + let function = match function_type { + FunctionType::Message => get_message(path, label)?, + FunctionType::Constructor => get_constructor(path, label)?, + }; + if args.len() != function.args.len() { return Err(Error::IncorrectArguments { - expected: constructor.args.len(), + expected: function.args.len(), provided: args.len(), }); } Ok(args .into_iter() - .zip(&constructor.args) + .zip(&function.args) .map(|(arg, param)| match (param.type_name.as_str(), arg.is_empty()) { ("Option", true) => "None".to_string(), /* If the argument is Option and empty, */ // replace it with `None` @@ -326,8 +288,8 @@ mod tests { } #[test] - fn process_message_args_work() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + fn process_function_args_work() -> Result<()> { + let temp_dir = new_environment("testing")?; let current_dir = env::current_dir().expect("Failed to get current directory"); mock_build_process( temp_dir.path().join("testing"), @@ -335,68 +297,64 @@ mod tests { current_dir.join("./tests/files/testing.json"), )?; assert!(matches!( - process_message_args(temp_dir.path().join("testing"),"wrong_flip", Vec::new()), + process_function_args(temp_dir.path().join("testing"),"wrong_flip", Vec::new(), FunctionType::Message), Err(Error::InvalidMessageName(error)) if error == "wrong_flip".to_string())); assert!(matches!( - process_message_args( + process_function_args( temp_dir.path().join("testing"), "specific_flip", - Vec::new() + Vec::new(), + FunctionType::Message ), Err(Error::IncorrectArguments {expected, provided }) if expected == 2 && provided == 0 )); assert_eq!( - process_message_args( + process_function_args( temp_dir.path().join("testing"), "specific_flip", - ["true".to_string(), "2".to_string()].to_vec() + ["true".to_string(), "2".to_string()].to_vec(), + FunctionType::Message )?, ["true".to_string(), "Some(2)".to_string()] ); assert_eq!( - process_message_args( + process_function_args( temp_dir.path().join("testing"), "specific_flip", - ["true".to_string(), "".to_string()].to_vec() + ["true".to_string(), "".to_string()].to_vec(), + FunctionType::Message )?, ["true".to_string(), "None".to_string()] ); - Ok(()) - } - #[test] - fn process_constructor_args_work() -> Result<()> { - let temp_dir = new_environment("testing")?; - let current_dir = env::current_dir().expect("Failed to get current directory"); - mock_build_process( - temp_dir.path().join("testing"), - current_dir.join("./tests/files/testing.contract"), - current_dir.join("./tests/files/testing.json"), - )?; + // Test constructors assert!(matches!( - process_constructor_args(temp_dir.path().join("testing"),"wrong_constructor", Vec::new()), + process_function_args(temp_dir.path().join("testing"),"wrong_constructor", Vec::new(), FunctionType::Constructor), Err(Error::InvalidConstructorName(error)) if error == "wrong_constructor".to_string())); assert!(matches!( - process_constructor_args( + process_function_args( temp_dir.path().join("testing"), "new", - Vec::new() + Vec::new(), + FunctionType::Constructor ), Err(Error::IncorrectArguments {expected, provided }) if expected == 2 && provided == 0 )); assert_eq!( - process_constructor_args( + process_function_args( temp_dir.path().join("testing"), "new", - ["true".to_string(), "2".to_string()].to_vec() + ["true".to_string(), "2".to_string()].to_vec(), + FunctionType::Constructor )?, ["true".to_string(), "Some(2)".to_string()] ); assert_eq!( - process_constructor_args( + process_function_args( temp_dir.path().join("testing"), "new", - ["true".to_string(), "".to_string()].to_vec() + ["true".to_string(), "".to_string()].to_vec(), + FunctionType::Constructor )?, ["true".to_string(), "None".to_string()] ); From ea538bf340521e2c102871b7ccd4de1a7b0e45e7 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Wed, 6 Nov 2024 13:00:43 +0100 Subject: [PATCH 035/211] test: update tests accordingly last changes --- crates/pop-cli/src/commands/up/contract.rs | 2 +- crates/pop-contracts/src/utils/metadata.rs | 21 +-- .../tests/files/testing.contract | 2 +- crates/pop-contracts/tests/files/testing.json | 126 ++++++++++-------- 4 files changed, 82 insertions(+), 69 deletions(-) diff --git a/crates/pop-cli/src/commands/up/contract.rs b/crates/pop-cli/src/commands/up/contract.rs index ca8112b28..e7853144c 100644 --- a/crates/pop-cli/src/commands/up/contract.rs +++ b/crates/pop-cli/src/commands/up/contract.rs @@ -321,7 +321,7 @@ mod tests { let command = UpContractCommand { path: None, constructor: "new".to_string(), - args: vec!["false".to_string()].to_vec(), + args: vec![], value: "0".to_string(), gas_limit: None, proof_size: None, diff --git a/crates/pop-contracts/src/utils/metadata.rs b/crates/pop-contracts/src/utils/metadata.rs index bf3458313..cf20d8787 100644 --- a/crates/pop-contracts/src/utils/metadata.rs +++ b/crates/pop-contracts/src/utils/metadata.rs @@ -252,11 +252,14 @@ mod tests { "Constructor that initializes the `bool` value to `false`. Constructors can delegate to other constructors." ); // assert parsed arguments - assert_eq!(constructor[0].args.len(), 2); + assert_eq!(constructor[0].args.len(), 1); assert_eq!(constructor[0].args[0].label, "init_value".to_string()); assert_eq!(constructor[0].args[0].type_name, "bool".to_string()); - assert_eq!(constructor[0].args[1].label, "number".to_string()); - assert_eq!(constructor[0].args[1].type_name, "Option".to_string()); + assert_eq!(constructor[1].args.len(), 2); + assert_eq!(constructor[1].args[0].label, "init_value".to_string()); + assert_eq!(constructor[1].args[0].type_name, "bool".to_string()); + assert_eq!(constructor[1].args[1].label, "number".to_string()); + assert_eq!(constructor[1].args[1].type_name, "Option".to_string()); Ok(()) } @@ -272,11 +275,11 @@ mod tests { assert!(matches!( get_constructor(&temp_dir.path().join("testing"), "wrong_constructor"), Err(Error::InvalidConstructorName(name)) if name == "wrong_constructor".to_string())); - let constructor = get_constructor(&temp_dir.path().join("testing"), "new")?; - assert_eq!(constructor.label, "new"); + let constructor = get_constructor(&temp_dir.path().join("testing"), "default")?; + assert_eq!(constructor.label, "default"); assert_eq!( constructor.docs, - "Constructor that initializes the `bool` value to the given `init_value`." + "Constructor that initializes the `bool` value to `false`. Constructors can delegate to other constructors." ); // assert parsed arguments assert_eq!(constructor.args.len(), 2); @@ -334,7 +337,7 @@ mod tests { assert!(matches!( process_function_args( temp_dir.path().join("testing"), - "new", + "default", Vec::new(), FunctionType::Constructor ), @@ -343,7 +346,7 @@ mod tests { assert_eq!( process_function_args( temp_dir.path().join("testing"), - "new", + "default", ["true".to_string(), "2".to_string()].to_vec(), FunctionType::Constructor )?, @@ -352,7 +355,7 @@ mod tests { assert_eq!( process_function_args( temp_dir.path().join("testing"), - "new", + "default", ["true".to_string(), "".to_string()].to_vec(), FunctionType::Constructor )?, diff --git a/crates/pop-contracts/tests/files/testing.contract b/crates/pop-contracts/tests/files/testing.contract index 7918b7184..fbd8e6fba 100644 --- a/crates/pop-contracts/tests/files/testing.contract +++ b/crates/pop-contracts/tests/files/testing.contract @@ -1 +1 @@ -{"source":{"hash":"0xea3a2d4428c3717ab229f1822014375d42a0497a0b86eb8fbb405ec6733899e9","language":"ink! 5.0.0","compiler":"rustc 1.78.0","wasm":"0x0061736d0100000001270760027f7f0060037f7f7f017f60000060047f7f7f7f017f60037f7f7f0060017f017f6000017f027406057365616c310b6765745f73746f726167650003057365616c3005696e7075740000057365616c320b7365745f73746f726167650003057365616c300b7365616c5f72657475726e0004057365616c301176616c75655f7472616e73666572726564000003656e76066d656d6f7279020102100313120101010100050000040006020000000002020616037f01418080040b7f00418080050b7f00418080050b0711020463616c6c0015066465706c6f7900160ac910122b01017f037f2002200346047f200005200020036a200120036a2d00003a0000200341016a21030c010b0b0b6f01017f0240200020014d04402000210303402002450d02200320012d00003a0000200341016a2103200141016a2101200241016b21020c000b000b200041016b2103200141016b210103402002450d01200220036a200120026a2d00003a0000200241016b21020c000b000b20000b2501017f037f2002200346047f200005200020036a20013a0000200341016a21030c010b0b0b3f01027f0340200245044041000f0b200241016b210220012d0000210320002d00002104200141016a2101200041016a210020032004460d000b200420036b0bc00101057f230041106b22022400410221044104210502402001100a220641ff01714102460d00200241086a2001100b20022d00080d000240024020022d000922030e020100020b20012802042203410449047f4101052001200341046b36020420012001280200220141046a3602002001280000210341000b2101200220033602042002200136020020022802000d0141012103200228020421040b20002003360204200020063a0000410821050b200020056a2004360200200241106a24000b3f01027f230041106b22012400200141086a2000100b20012d0009210020012d00082102200141106a240041024101410220004101461b410020001b20021b0b3c01017f200020012802042202047f2001200241016b36020420012001280200220141016a36020020012d00000520010b3a000120002002453a00000b2601017f230041106b220224002002200036020c20012002410c6a4104100d200241106a24000b4801027f024002402000280208220320026a22042003490d00200420002802044b0d00200420036b2002470d01200028020020036a2001200210051a200020043602080f0b000b000b2601017f230041106b22022400200220003a000f20012002410f6a4101100d200241106a24000b6102027f027e230041206b22002400200041106a22014200370300200042003703082000411036021c200041086a2000411c6a1004200028021c41114f0440000b2001290300210220002903082103200041206a2400410541042002200384501b0b12004180800441003b0100410041021013000b3c01027f027f200145044041808004210141010c010b410121024180800441013a000041818004210141020b2103200120023a0000200020031013000b920101057f230041106b220224002002428080013702082002418080043602044100200241046a2204100c024020022802082206200228020c2203490d00200228020421052002410036020c2002200620036b3602082002200320056a36020420012004100e20002004100c200228020c220020022802084b0d00200520032002280204200010021a200241106a24000f0b000b0d0020004180800420011003000b08002000200110120bb60501087f230041206b2200240020004180800136021441808004200041146a100102400240024020002802142201418180014f0d0041042104027f410420014104490d001a20004184800436020c2000200141046b360210418380042d00002101418280042d00002102418180042d00002103024002400240418080042d00002205412f470440200541ec00460d014104200541e300470d041a41042003413a470d041a4104200241a501470d041a41042202200141d100470d041a410221010c020b41042003418601470d031a4104200241db00470d031a41042202200141d901470d031a410321010c010b41042003410f470d021a41042002411d470d021a4104200141f701470d021a200041146a2000410c6a1009200028021822014102460d01200028021c210420002d001421020b20002001360204200020023a000041080c010b41040b20006a2004360200200028020422024104460d012000280208210520002d000021072000428080013702182000418080043602144100200041146a2203100c20002802182206200028021c2201490d00200028021421042000200620016b220636021420042001200120046a2201200310002000280214220420064b720d0020002004360218200020013602142003100a220141ff01714102460d002000280218220341034d2003410447720d00200028021428000021000240024002404102200241026b2203200341024f1b41016b0e020100020b2005200020021b20074100471014410041001011000b100f41ff01714105470d01230041106b220024002000418080043602044180800441003a00002000428080818010370208200141ff0171410047200041046a100e200028020c2200418180014f0440000b410020001013000b100f41ff01714105460d020b000b410141011011000b2000200141ff0171451014410041001011000bd90201067f230041206b22002400024002400240100f41ff01714105470d0020004180800136021441808004200041146a100120002802142201418180014f0d004104210220014104490d0120004184800436020c2000200141046b360210418380042d00002103418280042d00002101418180042d0000210402400240418080042d0000220541ed014704402005419b0147200441ae0147722001419d0147720d04200341de00460d010c040b200441cb00472001419d0147720d03410321012003411b470d04410221030c010b200041146a2000410c6a1009200028021822034102460d02200028021c210120002d001421020b20002003360204200020023a0000410821020c020b000b410321010b200020026a2001360200024020002802042202410347044020024102470d014100410010121010000b410141011011000b2000280208410020021b20002d000041004710121010000b","build_info":{"rust_toolchain":"stable-aarch64-apple-darwin","cargo_contract_version":"5.0.0-alpha","build_mode":"Release","wasm_opt_settings":{"optimization_passes":"Z","keep_debug_symbols":false}}},"contract":{"name":"testing","version":"0.1.0","authors":["[your_name] <[your_email]>"]},"image":null,"version":5,"types":[{"id":0,"type":{"def":{"primitive":"bool"}}},{"id":1,"type":{"def":{"primitive":"u32"}}},{"id":2,"type":{"path":["testing","testing","Testing"],"def":{"composite":{"fields":[{"name":"value","type":0,"typeName":",>>::Type"},{"name":"number","type":1,"typeName":",>>::Type"}]}}}},{"id":3,"type":{"path":["Option"],"params":[{"name":"T","type":1}],"def":{"variant":{"variants":[{"name":"None","index":0},{"name":"Some","fields":[{"type":1}],"index":1}]}}}},{"id":4,"type":{"path":["Result"],"params":[{"name":"T","type":5},{"name":"E","type":6}],"def":{"variant":{"variants":[{"name":"Ok","fields":[{"type":5}],"index":0},{"name":"Err","fields":[{"type":6}],"index":1}]}}}},{"id":5,"type":{"def":{"tuple":[]}}},{"id":6,"type":{"path":["ink_primitives","LangError"],"def":{"variant":{"variants":[{"name":"CouldNotReadInput","index":1}]}}}},{"id":7,"type":{"path":["Result"],"params":[{"name":"T","type":0},{"name":"E","type":6}],"def":{"variant":{"variants":[{"name":"Ok","fields":[{"type":0}],"index":0},{"name":"Err","fields":[{"type":6}],"index":1}]}}}},{"id":8,"type":{"path":["ink_primitives","types","AccountId"],"def":{"composite":{"fields":[{"type":9,"typeName":"[u8; 32]"}]}}}},{"id":9,"type":{"def":{"array":{"len":32,"type":10}}}},{"id":10,"type":{"def":{"primitive":"u8"}}},{"id":11,"type":{"def":{"primitive":"u128"}}},{"id":12,"type":{"path":["ink_primitives","types","Hash"],"def":{"composite":{"fields":[{"type":9,"typeName":"[u8; 32]"}]}}}},{"id":13,"type":{"def":{"primitive":"u64"}}},{"id":14,"type":{"path":["ink_env","types","NoChainExtension"],"def":{"variant":{}}}}],"storage":{"root":{"root_key":"0x00000000","layout":{"struct":{"name":"Testing","fields":[{"name":"value","layout":{"leaf":{"key":"0x00000000","ty":0}}},{"name":"number","layout":{"leaf":{"key":"0x00000000","ty":1}}}]}},"ty":2}},"spec":{"constructors":[{"label":"new","selector":"0x9bae9d5e","payable":false,"args":[{"label":"init_value","type":{"type":0,"displayName":["bool"]}},{"label":"number","type":{"type":3,"displayName":["Option"]}}],"returnType":{"type":4,"displayName":["ink_primitives","ConstructorResult"]},"docs":["Constructor that initializes the `bool` value to the given `init_value`."],"default":false},{"label":"default","selector":"0xed4b9d1b","payable":false,"args":[],"returnType":{"type":4,"displayName":["ink_primitives","ConstructorResult"]},"docs":["Constructor that initializes the `bool` value to `false`.","","Constructors can delegate to other constructors."],"default":false}],"messages":[{"label":"flip","selector":"0x633aa551","mutates":true,"payable":false,"args":[],"returnType":{"type":4,"displayName":["ink","MessageResult"]},"docs":[" A message that can be called on instantiated contracts."," This one flips the value of the stored `bool` from `true`"," to `false` and vice versa."],"default":false},{"label":"get","selector":"0x2f865bd9","mutates":false,"payable":false,"args":[],"returnType":{"type":7,"displayName":["ink","MessageResult"]},"docs":[" Simply returns the current value of our `bool`."],"default":false},{"label":"specific_flip","selector":"0x6c0f1df7","mutates":true,"payable":true,"args":[{"label":"new_value","type":{"type":0,"displayName":["bool"]}},{"label":"number","type":{"type":3,"displayName":["Option"]}}],"returnType":{"type":4,"displayName":["ink","MessageResult"]},"docs":[" A message for testing, flips the value of the stored `bool` with `new_value`"," and is payable"],"default":false}],"events":[],"docs":[],"lang_error":{"type":6,"displayName":["ink","LangError"]},"environment":{"accountId":{"type":8,"displayName":["AccountId"]},"balance":{"type":11,"displayName":["Balance"]},"hash":{"type":12,"displayName":["Hash"]},"timestamp":{"type":13,"displayName":["Timestamp"]},"blockNumber":{"type":1,"displayName":["BlockNumber"]},"chainExtension":{"type":14,"displayName":["ChainExtension"]},"maxEventTopics":4,"staticBufferSize":16384}}} \ No newline at end of file +{"source":{"hash":"0xf4f0cafd08d8e362141b3c64e3c651ad6a38225dbf0b66f691c15bb0ea00eac3","language":"ink! 5.0.0","compiler":"rustc 1.78.0","wasm":"0x0061736d0100000001270760027f7f0060037f7f7f017f60000060047f7f7f7f017f60037f7f7f0060017f017f6000017f027406057365616c310b6765745f73746f726167650003057365616c3005696e7075740000057365616c320b7365745f73746f726167650003057365616c300b7365616c5f72657475726e0004057365616c301176616c75655f7472616e73666572726564000003656e76066d656d6f7279020102100313120101010100050000040006000200000002020616037f01418080040b7f00418080050b7f00418080050b0711020463616c6c0015066465706c6f7900160ad910122b01017f037f2002200346047f200005200020036a200120036a2d00003a0000200341016a21030c010b0b0b6f01017f0240200020014d04402000210303402002450d02200320012d00003a0000200341016a2103200141016a2101200241016b21020c000b000b200041016b2103200141016b210103402002450d01200220036a200120026a2d00003a0000200241016b21020c000b000b20000b2501017f037f2002200346047f200005200020036a20013a0000200341016a21030c010b0b0b3f01027f0340200245044041000f0b200241016b210220012d0000210320002d00002104200141016a2101200041016a210020032004460d000b200420036b0bc00101057f230041106b22022400410221044104210502402001100a220641ff01714102460d00200241086a2001100b20022d00080d000240024020022d000922030e020100020b20012802042203410449047f4101052001200341046b36020420012001280200220141046a3602002001280000210341000b2101200220033602042002200136020020022802000d0141012103200228020421040b20002003360204200020063a0000410821050b200020056a2004360200200241106a24000b3f01027f230041106b22012400200141086a2000100b20012d0009210020012d00082102200141106a240041024101410220004101461b410020001b20021b0b3c01017f200020012802042202047f2001200241016b36020420012001280200220141016a36020020012d00000520010b3a000120002002453a00000b2601017f230041106b220224002002200036020c20012002410c6a4104100d200241106a24000b4801027f024002402000280208220320026a22042003490d00200420002802044b0d00200420036b2002470d01200028020020036a2001200210051a200020043602080f0b000b000b2601017f230041106b22022400200220003a000f20012002410f6a4101100d200241106a24000b6102027f027e230041206b22002400200041106a22014200370300200042003703082000411036021c200041086a2000411c6a1004200028021c41114f0440000b2001290300210220002903082103200041206a2400410541042002200384501b0b3c01027f027f200145044041808004210141010c010b410121024180800441013a000041818004210141020b2103200120023a0000200020031013000b12004180800441003b0100410041021013000b920101057f230041106b220224002002428080013702082002418080043602044100200241046a2204100c024020022802082206200228020c2203490d00200228020421052002410036020c2002200620036b3602082002200320056a36020420012004100e20002004100c200228020c220020022802084b0d00200520032002280204200010021a200241106a24000f0b000b0d0020004180800420011003000b08002000200110120bb60501087f230041206b2200240020004180800136021441808004200041146a100102400240024020002802142201418180014f0d0041042104027f410420014104490d001a20004184800436020c2000200141046b360210418380042d00002101418280042d00002102418180042d00002103024002400240418080042d00002205412f470440200541ec00460d014104200541e300470d041a41042003413a470d041a4104200241a501470d041a41042202200141d100470d041a410221010c020b41042003418601470d031a4104200241db00470d031a41042202200141d901470d031a410321010c010b41042003410f470d021a41042002411d470d021a4104200141f701470d021a200041146a2000410c6a1009200028021822014102460d01200028021c210420002d001421020b20002001360204200020023a000041080c010b41040b20006a2004360200200028020422024104460d012000280208210520002d000021072000428080013702182000418080043602144100200041146a2203100c20002802182206200028021c2201490d00200028021421042000200620016b220636021420042001200120046a2201200310002000280214220420064b720d0020002004360218200020013602142003100a220141ff01714102460d002000280218220341034d2003410447720d00200028021428000021000240024002404102200241026b2203200341024f1b41016b0e020100020b2005200020021b20074100471014410041001010000b100f41ff01714105470d01230041106b220024002000418080043602044180800441003a00002000428080818010370208200141ff0171410047200041046a100e200028020c2200418180014f0440000b410020001013000b100f41ff01714105460d020b000b410141011010000b2000200141ff0171451014410041001010000be90201067f230041206b22002400024002400240100f41ff01714105470d0020004180800136021441808004200041146a100120002802142201418180014f0d004104210220014104490d0120004184800436020c2000200141046b360210418380042d00002101418280042d00002103418180042d00002104027f418080042d0000220541ed014704402005419b0147200441ae0147722003419d0147200141de004772720d0341022103410322012000410c6a100a220441ff01714102470d011a0c040b200441cb00472003419d0147722001411b47720d02200041146a2000410c6a1009200028021822034102460d0220002d00142104200028021c0b210120002003360204200020043a0000410821020c020b000b410321010b200020026a2001360200024020002802042201410347044020002d0000210220014102460d012000280208410020011b200241004710121011000b410141011010000b4100200210121011000b","build_info":{"rust_toolchain":"stable-aarch64-apple-darwin","cargo_contract_version":"5.0.0-alpha","build_mode":"Release","wasm_opt_settings":{"optimization_passes":"Z","keep_debug_symbols":false}}},"contract":{"name":"testing","version":"0.1.0","authors":["[your_name] <[your_email]>"]},"image":null,"version":5,"types":[{"id":0,"type":{"def":{"primitive":"bool"}}},{"id":1,"type":{"def":{"primitive":"u32"}}},{"id":2,"type":{"path":["testing","testing","Testing"],"def":{"composite":{"fields":[{"name":"value","type":0,"typeName":",>>::Type"},{"name":"number","type":1,"typeName":",>>::Type"}]}}}},{"id":3,"type":{"path":["Result"],"params":[{"name":"T","type":4},{"name":"E","type":5}],"def":{"variant":{"variants":[{"name":"Ok","fields":[{"type":4}],"index":0},{"name":"Err","fields":[{"type":5}],"index":1}]}}}},{"id":4,"type":{"def":{"tuple":[]}}},{"id":5,"type":{"path":["ink_primitives","LangError"],"def":{"variant":{"variants":[{"name":"CouldNotReadInput","index":1}]}}}},{"id":6,"type":{"path":["Option"],"params":[{"name":"T","type":1}],"def":{"variant":{"variants":[{"name":"None","index":0},{"name":"Some","fields":[{"type":1}],"index":1}]}}}},{"id":7,"type":{"path":["Result"],"params":[{"name":"T","type":0},{"name":"E","type":5}],"def":{"variant":{"variants":[{"name":"Ok","fields":[{"type":0}],"index":0},{"name":"Err","fields":[{"type":5}],"index":1}]}}}},{"id":8,"type":{"path":["ink_primitives","types","AccountId"],"def":{"composite":{"fields":[{"type":9,"typeName":"[u8; 32]"}]}}}},{"id":9,"type":{"def":{"array":{"len":32,"type":10}}}},{"id":10,"type":{"def":{"primitive":"u8"}}},{"id":11,"type":{"def":{"primitive":"u128"}}},{"id":12,"type":{"path":["ink_primitives","types","Hash"],"def":{"composite":{"fields":[{"type":9,"typeName":"[u8; 32]"}]}}}},{"id":13,"type":{"def":{"primitive":"u64"}}},{"id":14,"type":{"path":["ink_env","types","NoChainExtension"],"def":{"variant":{}}}}],"storage":{"root":{"root_key":"0x00000000","layout":{"struct":{"name":"Testing","fields":[{"name":"value","layout":{"leaf":{"key":"0x00000000","ty":0}}},{"name":"number","layout":{"leaf":{"key":"0x00000000","ty":1}}}]}},"ty":2}},"spec":{"constructors":[{"label":"new","selector":"0x9bae9d5e","payable":false,"args":[{"label":"init_value","type":{"type":0,"displayName":["bool"]}}],"returnType":{"type":3,"displayName":["ink_primitives","ConstructorResult"]},"docs":["Constructor that initializes the `bool` value to the given `init_value`."],"default":false},{"label":"default","selector":"0xed4b9d1b","payable":false,"args":[{"label":"init_value","type":{"type":0,"displayName":["bool"]}},{"label":"number","type":{"type":6,"displayName":["Option"]}}],"returnType":{"type":3,"displayName":["ink_primitives","ConstructorResult"]},"docs":["Constructor that initializes the `bool` value to `false`.","","Constructors can delegate to other constructors."],"default":false}],"messages":[{"label":"flip","selector":"0x633aa551","mutates":true,"payable":false,"args":[],"returnType":{"type":3,"displayName":["ink","MessageResult"]},"docs":[" A message that can be called on instantiated contracts."," This one flips the value of the stored `bool` from `true`"," to `false` and vice versa."],"default":false},{"label":"get","selector":"0x2f865bd9","mutates":false,"payable":false,"args":[],"returnType":{"type":7,"displayName":["ink","MessageResult"]},"docs":[" Simply returns the current value of our `bool`."],"default":false},{"label":"specific_flip","selector":"0x6c0f1df7","mutates":true,"payable":true,"args":[{"label":"new_value","type":{"type":0,"displayName":["bool"]}},{"label":"number","type":{"type":6,"displayName":["Option"]}}],"returnType":{"type":3,"displayName":["ink","MessageResult"]},"docs":[" A message for testing, flips the value of the stored `bool` with `new_value`"," and is payable"],"default":false}],"events":[],"docs":[],"lang_error":{"type":5,"displayName":["ink","LangError"]},"environment":{"accountId":{"type":8,"displayName":["AccountId"]},"balance":{"type":11,"displayName":["Balance"]},"hash":{"type":12,"displayName":["Hash"]},"timestamp":{"type":13,"displayName":["Timestamp"]},"blockNumber":{"type":1,"displayName":["BlockNumber"]},"chainExtension":{"type":14,"displayName":["ChainExtension"]},"maxEventTopics":4,"staticBufferSize":16384}}} \ No newline at end of file diff --git a/crates/pop-contracts/tests/files/testing.json b/crates/pop-contracts/tests/files/testing.json index a5bad9291..61921986b 100644 --- a/crates/pop-contracts/tests/files/testing.json +++ b/crates/pop-contracts/tests/files/testing.json @@ -1,6 +1,6 @@ { "source": { - "hash": "0xea3a2d4428c3717ab229f1822014375d42a0497a0b86eb8fbb405ec6733899e9", + "hash": "0xf4f0cafd08d8e362141b3c64e3c651ad6a38225dbf0b66f691c15bb0ea00eac3", "language": "ink! 5.0.0", "compiler": "rustc 1.78.0", "build_info": { @@ -67,39 +67,6 @@ }, { "id": 3, - "type": { - "path": [ - "Option" - ], - "params": [ - { - "name": "T", - "type": 1 - } - ], - "def": { - "variant": { - "variants": [ - { - "name": "None", - "index": 0 - }, - { - "name": "Some", - "fields": [ - { - "type": 1 - } - ], - "index": 1 - } - ] - } - } - } - }, - { - "id": 4, "type": { "path": [ "Result" @@ -107,11 +74,11 @@ "params": [ { "name": "T", - "type": 5 + "type": 4 }, { "name": "E", - "type": 6 + "type": 5 } ], "def": { @@ -121,7 +88,7 @@ "name": "Ok", "fields": [ { - "type": 5 + "type": 4 } ], "index": 0 @@ -130,7 +97,7 @@ "name": "Err", "fields": [ { - "type": 6 + "type": 5 } ], "index": 1 @@ -141,7 +108,7 @@ } }, { - "id": 5, + "id": 4, "type": { "def": { "tuple": [] @@ -149,7 +116,7 @@ } }, { - "id": 6, + "id": 5, "type": { "path": [ "ink_primitives", @@ -167,6 +134,39 @@ } } }, + { + "id": 6, + "type": { + "path": [ + "Option" + ], + "params": [ + { + "name": "T", + "type": 1 + } + ], + "def": { + "variant": { + "variants": [ + { + "name": "None", + "index": 0 + }, + { + "name": "Some", + "fields": [ + { + "type": 1 + } + ], + "index": 1 + } + ] + } + } + } + }, { "id": 7, "type": { @@ -180,7 +180,7 @@ }, { "name": "E", - "type": 6 + "type": 5 } ], "def": { @@ -199,7 +199,7 @@ "name": "Err", "fields": [ { - "type": 6 + "type": 5 } ], "index": 1 @@ -344,19 +344,10 @@ "bool" ] } - }, - { - "label": "number", - "type": { - "type": 3, - "displayName": [ - "Option" - ] - } } ], "returnType": { - "type": 4, + "type": 3, "displayName": [ "ink_primitives", "ConstructorResult" @@ -371,9 +362,28 @@ "label": "default", "selector": "0xed4b9d1b", "payable": false, - "args": [], + "args": [ + { + "label": "init_value", + "type": { + "type": 0, + "displayName": [ + "bool" + ] + } + }, + { + "label": "number", + "type": { + "type": 6, + "displayName": [ + "Option" + ] + } + } + ], "returnType": { - "type": 4, + "type": 3, "displayName": [ "ink_primitives", "ConstructorResult" @@ -395,7 +405,7 @@ "payable": false, "args": [], "returnType": { - "type": 4, + "type": 3, "displayName": [ "ink", "MessageResult" @@ -444,7 +454,7 @@ { "label": "number", "type": { - "type": 3, + "type": 6, "displayName": [ "Option" ] @@ -452,7 +462,7 @@ } ], "returnType": { - "type": 4, + "type": 3, "displayName": [ "ink", "MessageResult" @@ -468,7 +478,7 @@ "events": [], "docs": [], "lang_error": { - "type": 6, + "type": 5, "displayName": [ "ink", "LangError" From 314fa45af21cefab0e3d689b9438998fdc68a5fe Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Wed, 6 Nov 2024 14:53:04 +0100 Subject: [PATCH 036/211] fix: issue with delimiter --- crates/pop-cli/src/commands/call/contract.rs | 14 ++++++-------- crates/pop-cli/src/commands/up/contract.rs | 2 +- crates/pop-contracts/src/utils/metadata.rs | 7 ++++--- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index d0c3e8b13..9a5a61c38 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -30,7 +30,7 @@ pub struct CallContractCommand { #[clap(long, short)] message: Option, /// The constructor arguments, encoded as strings. - #[clap(long, num_args = 0.., value_delimiter = ',')] + #[clap(long, num_args = 0..,)] args: Vec, /// The value to be transferred as part of the call. #[clap(name = "value", short = 'v', long, default_value = DEFAULT_PAYABLE_VALUE)] @@ -482,7 +482,7 @@ mod tests { .expect_warning("Your call has not been executed.") .expect_info("Gas limit: Weight { ref_time: 100, proof_size: 10 }"); - let mut call_config = CallContractCommand { + let call_config = CallContractCommand { path: Some(temp_dir.path().join("testing")), contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), message: Some("flip".to_string()), @@ -549,7 +549,7 @@ mod tests { .expect_outro("Contract calling complete."); // Contract deployed on Pop Network testnet, test get - let mut call_config = CallContractCommand { + let call_config = CallContractCommand { path: Some(temp_dir.path().join("testing")), contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), message: Some("get".to_string()), @@ -760,7 +760,6 @@ mod tests { let mut cli = MockCli::new() .expect_input("Signer calling the contract:", "//Alice".into()) .expect_input("Value to transfer to the call:", "50".into()) // Only if payable - .expect_input("Enter the value for the parameter: number", "2".into()) // Args for specific_flip .expect_input("Enter the value for the parameter: new_value", "true".into()) // Args for specific_flip .expect_select::( "Select the message to call:", @@ -781,7 +780,7 @@ mod tests { "Where is your project located?", temp_dir.path().join("testing").display().to_string(), ).expect_info(format!( - "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true,2 --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", temp_dir.path().join("testing").display().to_string(), )); @@ -805,9 +804,8 @@ mod tests { Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) ); assert_eq!(call_config.message, Some("specific_flip".to_string())); - assert_eq!(call_config.args.len(), 2); + assert_eq!(call_config.args.len(), 1); assert_eq!(call_config.args[0], "true".to_string()); - assert_eq!(call_config.args[1], "2".to_string()); assert_eq!(call_config.value, "50".to_string()); assert_eq!(call_config.gas_limit, None); assert_eq!(call_config.proof_size, None); @@ -817,7 +815,7 @@ mod tests { assert!(!call_config.dry_run); assert!(call_config.dev_mode); assert_eq!(call_config.display(), format!( - "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true,2 --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", temp_dir.path().join("testing").display().to_string(), )); diff --git a/crates/pop-cli/src/commands/up/contract.rs b/crates/pop-cli/src/commands/up/contract.rs index e7853144c..42166a695 100644 --- a/crates/pop-cli/src/commands/up/contract.rs +++ b/crates/pop-cli/src/commands/up/contract.rs @@ -35,7 +35,7 @@ pub struct UpContractCommand { #[clap(name = "constructor", long, default_value = "new")] constructor: String, /// The constructor arguments, encoded as strings. - #[clap(long, num_args = 0.., value_delimiter = ',')] + #[clap(long, num_args = 0..,)] args: Vec, /// Transfers an initial balance to the instantiated contract. #[clap(name = "value", long, default_value = "0")] diff --git a/crates/pop-contracts/src/utils/metadata.rs b/crates/pop-contracts/src/utils/metadata.rs index cf20d8787..4b84fa257 100644 --- a/crates/pop-contracts/src/utils/metadata.rs +++ b/crates/pop-contracts/src/utils/metadata.rs @@ -149,17 +149,18 @@ pub fn process_function_args

( where P: AsRef, { + let parsed_args = args.into_iter().map(|arg| arg.replace(",", "")).collect::>(); let function = match function_type { FunctionType::Message => get_message(path, label)?, FunctionType::Constructor => get_constructor(path, label)?, }; - if args.len() != function.args.len() { + if parsed_args.len() != function.args.len() { return Err(Error::IncorrectArguments { expected: function.args.len(), - provided: args.len(), + provided: parsed_args.len(), }); } - Ok(args + Ok(parsed_args .into_iter() .zip(&function.args) .map(|(arg, param)| match (param.type_name.as_str(), arg.is_empty()) { From 069fc378bb4e316eb75a23733e06c5dadb38c6ae Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Wed, 6 Nov 2024 23:37:44 +0100 Subject: [PATCH 037/211] test: fix unit test --- crates/pop-contracts/src/utils/metadata.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pop-contracts/src/utils/metadata.rs b/crates/pop-contracts/src/utils/metadata.rs index 4b84fa257..eb9c7b6de 100644 --- a/crates/pop-contracts/src/utils/metadata.rs +++ b/crates/pop-contracts/src/utils/metadata.rs @@ -209,7 +209,7 @@ mod tests { #[test] fn get_message_work() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let current_dir = env::current_dir().expect("Failed to get current directory"); mock_build_process( temp_dir.path().join("testing"), From 2defc640ebce78f7817e9e9c6153195302f0af81 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 7 Nov 2024 15:22:36 +0100 Subject: [PATCH 038/211] refactor: renaming and fix comments --- crates/pop-cli/src/commands/call/contract.rs | 2 +- crates/pop-contracts/src/utils/metadata.rs | 44 +++++++++++--------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 9a5a61c38..a0f28dc43 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -176,7 +176,7 @@ impl CallContractCommand { }; // Parse the contract metadata provided. If there is an error, do not prompt for more. - let messages = match get_messages(&contract_path) { + let messages = match get_messages(contract_path) { Ok(messages) => messages, Err(e) => { return Err(anyhow!(format!( diff --git a/crates/pop-contracts/src/utils/metadata.rs b/crates/pop-contracts/src/utils/metadata.rs index eb9c7b6de..a16e7876a 100644 --- a/crates/pop-contracts/src/utils/metadata.rs +++ b/crates/pop-contracts/src/utils/metadata.rs @@ -6,8 +6,8 @@ use contract_transcode::ink_metadata::MessageParamSpec; use scale_info::form::PortableForm; use std::path::Path; +/// Describes a parameter. #[derive(Clone, PartialEq, Eq)] -/// Describes a contract message. pub struct Param { /// The label of the parameter. pub label: String, @@ -15,25 +15,25 @@ pub struct Param { pub type_name: String, } -#[derive(Clone, PartialEq, Eq)] /// Describes a contract function. +#[derive(Clone, PartialEq, Eq)] pub struct ContractFunction { - /// The label of the constructor. + /// The label of the function. pub label: String, - /// If the message accepts any `value` from the caller. + /// If the function accepts any `value` from the caller. pub payable: bool, /// The parameters of the deployment handler. pub args: Vec, - /// The constructor documentation. + /// The function documentation. pub docs: String, - /// If the constructor is the default for off-chain consumers (e.g UIs). + /// If the message/constructor is the default for off-chain consumers (e.g UIs). pub default: bool, /// If the message is allowed to mutate the contract state. true for constructors. pub mutates: bool, } -#[derive(Clone, PartialEq, Eq)] /// Specifies the type of contract funtion, either a constructor or a message. +#[derive(Clone, PartialEq, Eq)] pub enum FunctionType { Constructor, Message, @@ -51,18 +51,20 @@ pub fn get_messages(path: &Path) -> Result, Error> { let contract_artifacts = ContractArtifacts::from_manifest_or_file(Some(&cargo_toml_path), None)?; let transcoder = contract_artifacts.contract_transcoder()?; - let mut messages: Vec = Vec::new(); - for message in transcoder.metadata().spec().messages() { - messages.push(ContractFunction { + Ok(transcoder + .metadata() + .spec() + .messages() + .iter() + .map(|message| ContractFunction { label: message.label().to_string(), mutates: message.mutates(), payable: message.payable(), args: process_args(message.args()), docs: message.docs().join(" "), default: *message.default(), - }); - } - Ok(messages) + }) + .collect()) } /// Extracts the information of a smart contract message parsing the metadata file. @@ -92,18 +94,20 @@ pub fn get_constructors(path: &Path) -> Result, Error> { let contract_artifacts = ContractArtifacts::from_manifest_or_file(Some(&cargo_toml_path), None)?; let transcoder = contract_artifacts.contract_transcoder()?; - let mut constructors: Vec = Vec::new(); - for constructor in transcoder.metadata().spec().constructors() { - constructors.push(ContractFunction { + Ok(transcoder + .metadata() + .spec() + .constructors() + .iter() + .map(|constructor| ContractFunction { label: constructor.label().to_string(), payable: *constructor.payable(), args: process_args(constructor.args()), docs: constructor.docs().join(" "), default: *constructor.default(), mutates: true, - }); - } - Ok(constructors) + }) + .collect()) } /// Extracts the information of a smart contract constructor parsing the metadata file. @@ -149,7 +153,7 @@ pub fn process_function_args

( where P: AsRef, { - let parsed_args = args.into_iter().map(|arg| arg.replace(",", "")).collect::>(); + let parsed_args = args.into_iter().map(|arg| arg.replace(",", "")).collect::>(); let function = match function_type { FunctionType::Message => get_message(path, label)?, FunctionType::Constructor => get_constructor(path, label)?, From c69ca1679498b4ae883150d58b703bef9db59ddf Mon Sep 17 00:00:00 2001 From: Frank Bell <60948618+evilrobot-01@users.noreply.github.com> Date: Fri, 8 Nov 2024 08:35:46 +0000 Subject: [PATCH 039/211] refactor: format types (#339) Shows the full type representation, making it easier to see the entry format of parameter values. --- crates/pop-cli/src/commands/call/contract.rs | 21 +- crates/pop-contracts/src/utils/metadata.rs | 201 ++++++++++++++++--- 2 files changed, 186 insertions(+), 36 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index a0f28dc43..8db1cc63c 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -96,7 +96,8 @@ impl CallContractCommand { full_message.push_str(&format!(" --message {}", message)); } if !self.args.is_empty() { - full_message.push_str(&format!(" --args {}", self.args.join(","))); + let args: Vec<_> = self.args.iter().map(|a| format!("\"{a}\"")).collect(); + full_message.push_str(&format!(" --args {}", args.join(", "))); } if self.value != DEFAULT_PAYABLE_VALUE { full_message.push_str(&format!(" --value {}", self.value)); @@ -235,7 +236,7 @@ impl CallContractCommand { .placeholder(&format!("Type required: {}", arg.type_name)); // Set default input only if the parameter type is `Option` (Not mandatory) - if arg.type_name == "Option" { + if arg.type_name.starts_with("Option<") { input = input.default_input(""); } contract_args.push(input.interact()?); @@ -482,7 +483,7 @@ mod tests { .expect_warning("Your call has not been executed.") .expect_info("Gas limit: Weight { ref_time: 100, proof_size: 10 }"); - let call_config = CallContractCommand { + let mut call_config = CallContractCommand { path: Some(temp_dir.path().join("testing")), contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), message: Some("flip".to_string()), @@ -549,7 +550,7 @@ mod tests { .expect_outro("Contract calling complete."); // Contract deployed on Pop Network testnet, test get - let call_config = CallContractCommand { + let mut call_config = CallContractCommand { path: Some(temp_dir.path().join("testing")), contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), message: Some("get".to_string()), @@ -696,7 +697,7 @@ mod tests { "Where is your project located?", temp_dir.path().join("testing").display().to_string(), ).expect_info(format!( - "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true,2 --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args \"true\", \"2\" --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", temp_dir.path().join("testing").display().to_string(), )); @@ -731,7 +732,7 @@ mod tests { assert!(call_config.execute); assert!(!call_config.dry_run); assert_eq!(call_config.display(), format!( - "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true,2 --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args \"true\", \"2\" --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", temp_dir.path().join("testing").display().to_string(), )); @@ -760,6 +761,7 @@ mod tests { let mut cli = MockCli::new() .expect_input("Signer calling the contract:", "//Alice".into()) .expect_input("Value to transfer to the call:", "50".into()) // Only if payable + .expect_input("Enter the value for the parameter: number", "2".into()) // Args for specific_flip .expect_input("Enter the value for the parameter: new_value", "true".into()) // Args for specific_flip .expect_select::( "Select the message to call:", @@ -780,7 +782,7 @@ mod tests { "Where is your project located?", temp_dir.path().join("testing").display().to_string(), ).expect_info(format!( - "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args \"true\", \"2\" --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", temp_dir.path().join("testing").display().to_string(), )); @@ -804,8 +806,9 @@ mod tests { Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) ); assert_eq!(call_config.message, Some("specific_flip".to_string())); - assert_eq!(call_config.args.len(), 1); + assert_eq!(call_config.args.len(), 2); assert_eq!(call_config.args[0], "true".to_string()); + assert_eq!(call_config.args[1], "2".to_string()); assert_eq!(call_config.value, "50".to_string()); assert_eq!(call_config.gas_limit, None); assert_eq!(call_config.proof_size, None); @@ -815,7 +818,7 @@ mod tests { assert!(!call_config.dry_run); assert!(call_config.dev_mode); assert_eq!(call_config.display(), format!( - "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args \"true\", \"2\" --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", temp_dir.path().join("testing").display().to_string(), )); diff --git a/crates/pop-contracts/src/utils/metadata.rs b/crates/pop-contracts/src/utils/metadata.rs index a16e7876a..f0dd2b8e1 100644 --- a/crates/pop-contracts/src/utils/metadata.rs +++ b/crates/pop-contracts/src/utils/metadata.rs @@ -3,11 +3,11 @@ use crate::errors::Error; use contract_extrinsics::ContractArtifacts; use contract_transcode::ink_metadata::MessageParamSpec; -use scale_info::form::PortableForm; +use scale_info::{form::PortableForm, PortableRegistry, Type, TypeDef, TypeDefPrimitive}; use std::path::Path; /// Describes a parameter. -#[derive(Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Param { /// The label of the parameter. pub label: String, @@ -51,8 +51,8 @@ pub fn get_messages(path: &Path) -> Result, Error> { let contract_artifacts = ContractArtifacts::from_manifest_or_file(Some(&cargo_toml_path), None)?; let transcoder = contract_artifacts.contract_transcoder()?; - Ok(transcoder - .metadata() + let metadata = transcoder.metadata(); + Ok(metadata .spec() .messages() .iter() @@ -60,7 +60,7 @@ pub fn get_messages(path: &Path) -> Result, Error> { label: message.label().to_string(), mutates: message.mutates(), payable: message.payable(), - args: process_args(message.args()), + args: process_args(message.args(), metadata.registry()), docs: message.docs().join(" "), default: *message.default(), }) @@ -94,15 +94,15 @@ pub fn get_constructors(path: &Path) -> Result, Error> { let contract_artifacts = ContractArtifacts::from_manifest_or_file(Some(&cargo_toml_path), None)?; let transcoder = contract_artifacts.contract_transcoder()?; - Ok(transcoder - .metadata() + let metadata = transcoder.metadata(); + Ok(metadata .spec() .constructors() .iter() .map(|constructor| ContractFunction { label: constructor.label().to_string(), payable: *constructor.payable(), - args: process_args(constructor.args()), + args: process_args(constructor.args(), metadata.registry()), docs: constructor.docs().join(" "), default: *constructor.default(), mutates: true, @@ -126,17 +126,164 @@ where } // Parse the parameters into a vector of argument labels. -fn process_args(params: &[MessageParamSpec]) -> Vec { +fn process_args( + params: &[MessageParamSpec], + registry: &PortableRegistry, +) -> Vec { let mut args: Vec = Vec::new(); for arg in params { - args.push(Param { - label: arg.label().to_string(), - type_name: arg.ty().display_name().to_string(), - }); + // Resolve type from registry to provide full type representation. + let type_name = + format_type(registry.resolve(arg.ty().ty().id).expect("type not found"), registry); + args.push(Param { label: arg.label().to_string(), type_name }); } args } +// Formats a specified type, using the registry to output its full type representation. +fn format_type(ty: &Type, registry: &PortableRegistry) -> String { + let mut name = ty + .path + .segments + .last() + .map(|s| s.to_owned()) + .unwrap_or_else(|| ty.path.to_string()); + + if !ty.type_params.is_empty() { + let params: Vec<_> = ty + .type_params + .iter() + .filter_map(|p| registry.resolve(p.ty.unwrap().id)) + .map(|t| format_type(t, registry)) + .collect(); + name = format!("{name}<{}>", params.join(",")); + } + + name = format!( + "{name}{}", + match &ty.type_def { + TypeDef::Composite(composite) => { + if composite.fields.is_empty() { + return "".to_string(); + } + + let mut named = false; + let fields: Vec<_> = composite + .fields + .iter() + .filter_map(|f| match f.name.as_ref() { + None => registry.resolve(f.ty.id).map(|t| format_type(t, registry)), + Some(field) => { + named = true; + f.type_name.as_ref().map(|t| format!("{field}: {t}")) + }, + }) + .collect(); + match named { + true => format!(" {{ {} }}", fields.join(", ")), + false => format!(" ({})", fields.join(", ")), + } + }, + TypeDef::Variant(variant) => { + let variants: Vec<_> = variant + .variants + .iter() + .map(|v| { + if v.fields.is_empty() { + return v.name.clone(); + } + + let name = v.name.as_str(); + let mut named = false; + let fields: Vec<_> = v + .fields + .iter() + .filter_map(|f| match f.name.as_ref() { + None => registry.resolve(f.ty.id).map(|t| format_type(t, registry)), + Some(field) => { + named = true; + f.type_name.as_ref().map(|t| format!("{field}: {t}")) + }, + }) + .collect(); + format!( + "{name}{}", + match named { + true => format!("{{ {} }}", fields.join(", ")), + false => format!("({})", fields.join(", ")), + } + ) + }) + .collect(); + format!(": {}", variants.join(", ")) + }, + TypeDef::Sequence(sequence) => { + format!( + "[{}]", + format_type( + registry.resolve(sequence.type_param.id).expect("sequence type not found"), + registry + ) + ) + }, + TypeDef::Array(array) => { + format!( + "[{};{}]", + format_type( + registry.resolve(array.type_param.id).expect("array type not found"), + registry + ), + array.len + ) + }, + TypeDef::Tuple(tuple) => { + let fields: Vec<_> = tuple + .fields + .iter() + .filter_map(|p| registry.resolve(p.id)) + .map(|t| format_type(t, registry)) + .collect(); + format!("({})", fields.join(",")) + }, + TypeDef::Primitive(primitive) => { + use TypeDefPrimitive::*; + match primitive { + Bool => "bool", + Char => "char", + Str => "str", + U8 => "u8", + U16 => "u16", + U32 => "u32", + U64 => "u64", + U128 => "u128", + U256 => "u256", + I8 => "i8", + I16 => "i16", + I32 => "i32", + I64 => "i64", + I128 => "i128", + I256 => "i256", + } + .to_string() + }, + TypeDef::Compact(compact) => { + format!( + "Compact<{}>", + format_type( + registry.resolve(compact.type_param.id).expect("compact type not found"), + registry + ) + ) + }, + TypeDef::BitSequence(_) => { + unimplemented!("bit sequence not currently supported") + }, + } + ); + + name +} + /// Processes a list of argument values for a specified contract function, /// wrapping each value in `Some(...)` or replacing it with `None` if the argument is optional /// @@ -153,26 +300,26 @@ pub fn process_function_args

( where P: AsRef, { - let parsed_args = args.into_iter().map(|arg| arg.replace(",", "")).collect::>(); let function = match function_type { FunctionType::Message => get_message(path, label)?, FunctionType::Constructor => get_constructor(path, label)?, }; - if parsed_args.len() != function.args.len() { + if args.len() != function.args.len() { return Err(Error::IncorrectArguments { expected: function.args.len(), - provided: parsed_args.len(), + provided: args.len(), }); } - Ok(parsed_args + Ok(args .into_iter() .zip(&function.args) - .map(|(arg, param)| match (param.type_name.as_str(), arg.is_empty()) { - ("Option", true) => "None".to_string(), /* If the argument is Option and empty, */ - // replace it with `None` - ("Option", false) => format!("Some({})", arg), /* If the argument is Option and not */ - // empty, wrap it in `Some(...)` - _ => arg, // If the argument is not Option, return it as is + .map(|(arg, param)| match (param.type_name.starts_with("Option<"), arg.is_empty()) { + // If the argument is Option and empty, replace it with `None` + (true, true) => "None".to_string(), + // If the argument is Option and not empty, wrap it in `Some(...)` + (true, false) => format!("Some({})", arg), + // If the argument is not Option, return it as is + _ => arg, }) .collect::>()) } @@ -207,7 +354,7 @@ mod tests { assert_eq!(message[2].args[0].label, "new_value".to_string()); assert_eq!(message[2].args[0].type_name, "bool".to_string()); assert_eq!(message[2].args[1].label, "number".to_string()); - assert_eq!(message[2].args[1].type_name, "Option".to_string()); + assert_eq!(message[2].args[1].type_name, "Option: None, Some(u32)".to_string()); Ok(()) } @@ -231,7 +378,7 @@ mod tests { assert_eq!(message.args[0].label, "new_value".to_string()); assert_eq!(message.args[0].type_name, "bool".to_string()); assert_eq!(message.args[1].label, "number".to_string()); - assert_eq!(message.args[1].type_name, "Option".to_string()); + assert_eq!(message.args[1].type_name, "Option: None, Some(u32)".to_string()); Ok(()) } @@ -264,7 +411,7 @@ mod tests { assert_eq!(constructor[1].args[0].label, "init_value".to_string()); assert_eq!(constructor[1].args[0].type_name, "bool".to_string()); assert_eq!(constructor[1].args[1].label, "number".to_string()); - assert_eq!(constructor[1].args[1].type_name, "Option".to_string()); + assert_eq!(constructor[1].args[1].type_name, "Option: None, Some(u32)".to_string()); Ok(()) } @@ -291,7 +438,7 @@ mod tests { assert_eq!(constructor.args[0].label, "init_value".to_string()); assert_eq!(constructor.args[0].type_name, "bool".to_string()); assert_eq!(constructor.args[1].label, "number".to_string()); - assert_eq!(constructor.args[1].type_name, "Option".to_string()); + assert_eq!(constructor.args[1].type_name, "Option: None, Some(u32)".to_string()); Ok(()) } From 5d76f1aaa6351fa00cc96c5946b37576ec617b9d Mon Sep 17 00:00:00 2001 From: Alejandro Martinez Andres <11448715+al3mart@users.noreply.github.com> Date: Fri, 8 Nov 2024 11:30:30 +0100 Subject: [PATCH 040/211] fix: logo doesn't show in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index df7f72a99..a3a63f945 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Pop CLI - +

From d1b5b9fa61843936cf656853b9928e3ef89bc1f6 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Wed, 11 Sep 2024 13:14:43 +0200 Subject: [PATCH 041/211] feat: pop call parachain prototype --- Cargo.lock | 2 + crates/pop-cli/src/commands/call/mod.rs | 6 + crates/pop-cli/src/commands/call/parachain.rs | 134 ++++++++++++++++++ crates/pop-cli/src/commands/mod.rs | 9 +- crates/pop-parachains/Cargo.toml | 2 + crates/pop-parachains/src/call.rs | 104 ++++++++++++++ crates/pop-parachains/src/errors.rs | 8 ++ crates/pop-parachains/src/lib.rs | 4 + 8 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 crates/pop-cli/src/commands/call/parachain.rs create mode 100644 crates/pop-parachains/src/call.rs diff --git a/Cargo.lock b/Cargo.lock index a5ca08051..a43de4663 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4765,9 +4765,11 @@ dependencies = [ "mockito", "pop-common", "reqwest 0.12.5", + "scale-info", "serde_json", "strum 0.26.3", "strum_macros 0.26.4", + "subxt", "symlink", "tar", "tempfile", diff --git a/crates/pop-cli/src/commands/call/mod.rs b/crates/pop-cli/src/commands/call/mod.rs index 6cb137f4b..241ebbdb9 100644 --- a/crates/pop-cli/src/commands/call/mod.rs +++ b/crates/pop-cli/src/commands/call/mod.rs @@ -4,6 +4,8 @@ use clap::{Args, Subcommand}; #[cfg(feature = "contract")] pub(crate) mod contract; +#[cfg(feature = "parachain")] +pub(crate) mod parachain; /// Arguments for calling a smart contract. #[derive(Args)] @@ -16,6 +18,10 @@ pub(crate) struct CallArgs { /// Call a smart contract. #[derive(Subcommand)] pub(crate) enum Command { + /// Call a parachain + #[cfg(feature = "parachain")] + #[clap(alias = "p")] + Parachain(parachain::CallParachainCommand), /// Call a contract #[cfg(feature = "contract")] #[clap(alias = "c")] diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs new file mode 100644 index 000000000..537e7c565 --- /dev/null +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: GPL-3.0 + +use crate::cli::traits::*; +use anyhow::{anyhow, Result}; +use clap::Args; +use pop_parachains::{fetch_metadata, parse_chain_metadata, query, storage_info, Metadata}; + +#[derive(Args, Clone)] +pub struct CallParachainCommand { + /// The name of the pallet to call. + #[clap(long, short)] + pallet: Option, + /// The name of extrinsic to call. + #[clap(long, short)] + extrinsic: Option, + /// The constructor arguments, encoded as strings. + #[clap(long, num_args = 0..)] + args: Vec, + /// Transfers an initial balance to the contract. + #[clap(name = "value", long, default_value = "0")] + value: String, + /// Maximum amount of gas to be used for this command. + /// If not specified it will perform a dry-run to estimate the gas consumed for the + /// instantiation. + #[clap(name = "gas", long)] + gas_limit: Option, + /// Maximum proof size for this command. + /// If not specified it will perform a dry-run to estimate the proof size required. + #[clap(long)] + proof_size: Option, + /// Websocket endpoint of a node. + #[clap(name = "url", long, value_parser, default_value = "ws://localhost:9944")] + url: String, + /// Secret key URI for the account calling the contract. + /// + /// e.g. + /// - for a dev account "//Alice" + /// - with a password "//Alice///SECRET_PASSWORD" + #[clap(name = "suri", long, short, default_value = "//Alice")] + suri: String, +} + +pub(crate) struct CallParachain<'a, CLI: Cli> { + /// The cli to be used. + pub(crate) cli: &'a mut CLI, + /// The args to call. + pub(crate) args: CallParachainCommand, +} + +impl<'a, CLI: Cli> CallParachain<'a, CLI> { + /// Executes the command. + pub(crate) async fn execute(mut self: Box) -> Result<()> { + self.cli.intro("Call a parachain")?; + let metadata = fetch_metadata("wss://rpc1.paseo.popnetwork.xyz").await?; + let call_config = if self.args.pallet.is_none() && self.args.extrinsic.is_none() { + guide_user_to_call_chain(&mut self, metadata).await? + } else { + self.args.clone() + }; + Ok(()) + } +} + +#[derive(Clone, Eq, PartialEq)] +enum Action { + Extrinsic, + Query, +} + +/// Guide the user to call the contract. +async fn guide_user_to_call_chain<'a, CLI: Cli>( + command: &mut CallParachain<'a, CLI>, + metadata: Metadata, +) -> anyhow::Result { + command.cli.intro("Call a contract")?; + + let pallets = match parse_chain_metadata(metadata.clone()).await { + Ok(pallets) => pallets, + Err(e) => { + command.cli.outro_cancel("Unable to fetch the chain metadata.")?; + return Err(anyhow!(format!("{}", e.to_string()))); + }, + }; + let pallet = { + let mut prompt = command.cli.select("Select the pallet to call:"); + for pallet_item in pallets { + prompt = prompt.item(pallet_item.clone(), &pallet_item.label, &pallet_item.docs); + } + prompt.interact()? + }; + let action = command + .cli + .select("What do you want to do?") + .item(Action::Extrinsic, "Submit an extrinsic", "hint") + .item(Action::Query, "Query storage", "hint") + .interact()?; + + if action == Action::Extrinsic { + let extrinsic = { + let mut prompt_extrinsic = command.cli.select("Select the extrinsic to call:"); + for extrinsic in pallet.extrinsics { + prompt_extrinsic = prompt_extrinsic.item( + extrinsic.clone(), + &extrinsic.name, + &extrinsic.docs.concat(), + ); + } + prompt_extrinsic.interact()? + }; + } else { + let storage = { + let mut prompt_storage = command.cli.select("Select the storage to query:"); + for storage in pallet.storage { + prompt_storage = prompt_storage.item(storage.clone(), &storage.name, &storage.docs); + } + prompt_storage.interact()? + }; + let a = storage_info(&pallet.label, &storage.name, &metadata)?; + query(&pallet.label, &storage.name, vec![], "wss://rpc1.paseo.popnetwork.xyz").await?; + } + // println!("Selected pallet: {:?}", pallet.label); + // println!("ext: {:?}", pallet.extrinsics); + + Ok(CallParachainCommand { + pallet: Some("pallet".to_string()), + extrinsic: Some("extrinsic".to_string()), + args: vec!["".to_string()], + value: "0".to_string(), + gas_limit: None, + proof_size: None, + url: "wss://rpc2.paseo.popnetwork.xyz".to_string(), + suri: "//Alice".to_string(), + }) +} diff --git a/crates/pop-cli/src/commands/mod.rs b/crates/pop-cli/src/commands/mod.rs index 34c2f10b3..742c96342 100644 --- a/crates/pop-cli/src/commands/mod.rs +++ b/crates/pop-cli/src/commands/mod.rs @@ -96,8 +96,15 @@ impl Command { build::Command::Spec(cmd) => cmd.execute().await.map(|_| Value::Null), }, }, - #[cfg(feature = "contract")] Self::Call(args) => match args.command { + #[cfg(feature = "parachain")] + call::Command::Parachain(cmd) => { + Box::new(call::parachain::CallParachain { cli: &mut Cli, args: cmd }) + .execute() + .await + .map(|_| Value::Null) + }, + #[cfg(feature = "contract")] call::Command::Contract(cmd) => cmd.execute().await.map(|_| Value::Null), }, #[cfg(any(feature = "parachain", feature = "contract"))] diff --git a/crates/pop-parachains/Cargo.toml b/crates/pop-parachains/Cargo.toml index 1ac8166b0..ddf95056e 100644 --- a/crates/pop-parachains/Cargo.toml +++ b/crates/pop-parachains/Cargo.toml @@ -26,6 +26,8 @@ url.workspace = true askama.workspace = true indexmap.workspace = true reqwest.workspace = true +scale-info.workspace = true +subxt.workspace = true symlink.workspace = true toml_edit.workspace = true walkdir.workspace = true diff --git a/crates/pop-parachains/src/call.rs b/crates/pop-parachains/src/call.rs new file mode 100644 index 000000000..840541ca4 --- /dev/null +++ b/crates/pop-parachains/src/call.rs @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-3.0 + +// use subxt_codegen::fetch_metadata; +// use subxt_metadata::Metadata; +use crate::errors::Error; +use scale_info::{form::PortableForm, PortableRegistry, Variant}; +use subxt::{ + dynamic::Value, + error::MetadataError, + metadata::types::{PalletMetadata, StorageEntryMetadata, StorageMetadata}, + Metadata, OnlineClient, SubstrateConfig, +}; + +#[derive(Clone, PartialEq, Eq)] +pub struct Storage { + pub name: String, + pub docs: String, +} + +#[derive(Clone, PartialEq, Eq)] +/// Describes a contract message. +pub struct Pallet { + /// The label of the message. + pub label: String, + /// The message documentation. + pub docs: String, + // The extrinsics of the pallet. + pub extrinsics: Vec>, + // The storage of the pallet. + pub storage: Vec, + // /// The constants of the pallet. + // pub consts: Vec, +} + +pub async fn fetch_metadata(url: &str) -> Result { + let api = OnlineClient::::from_url(url).await?; + Ok(api.metadata()) +} + +pub async fn query( + pallet_name: &str, + entry_name: &str, + args: Vec, + url: &str, +) -> Result<(), Error> { + let api = OnlineClient::::from_url(url).await?; + println!("here"); + let storage_query = subxt::dynamic::storage(pallet_name, entry_name, args); + println!("here"); + let mut results = api.storage().at_latest().await?.iter(storage_query).await?; + println!("{:?}", results); + while let Some(Ok(kv)) = results.next().await { + println!("Keys decoded: {:?}", kv.keys); + println!("Value: {:?}", kv.value.to_value().map_err(|_| Error::ParsingResponseError)?); + } + Ok(()) +} + +pub async fn parse_chain_metadata(metadata: Metadata) -> Result, Error> { + let mut pallets: Vec = Vec::new(); + for pallet in metadata.pallets() { + let extrinsics = + pallet.call_variants().map(|variants| variants.to_vec()).unwrap_or_default(); // Return an empty Vec if Option is None + let storage: Vec = pallet + .storage() + .map(|metadata| { + metadata + .entries() + .iter() + .map(|entry| Storage { + name: entry.name().to_string(), + docs: entry.docs().concat(), + }) + .collect() + }) + .unwrap_or_default(); // Return an empty Vec if Option is None + + pallets.push(Pallet { + label: pallet.name().to_string(), + extrinsics, + docs: pallet.docs().join(" "), + storage, + }); + } + Ok(pallets) +} + +/// Return details about the given storage entry. +pub fn storage_info<'a>( + pallet_name: &str, + entry_name: &str, + metadata: &'a Metadata, +) -> Result<&'a StorageEntryMetadata, Error> { + let pallet_metadata = metadata + .pallet_by_name(pallet_name) + .ok_or(Error::PalletNotFound(pallet_name.to_string()))?; + let storage_metadata = pallet_metadata + .storage() + .ok_or_else(|| MetadataError::StorageNotFoundInPallet(pallet_name.to_owned()))?; + let storage_entry = storage_metadata + .entry_by_name(entry_name) + .ok_or_else(|| MetadataError::StorageEntryNotFound(entry_name.to_owned()))?; + Ok(storage_entry) +} diff --git a/crates/pop-parachains/src/errors.rs b/crates/pop-parachains/src/errors.rs index 3eff7bbfc..7e9e8e19d 100644 --- a/crates/pop-parachains/src/errors.rs +++ b/crates/pop-parachains/src/errors.rs @@ -21,6 +21,8 @@ pub enum Error { IO(#[from] std::io::Error), #[error("JSON error: {0}")] JsonError(#[from] serde_json::Error), + #[error("Metadata error: {0}")] + MetadataError(#[from] subxt::error::MetadataError), #[error("Missing binary: {0}")] MissingBinary(String), #[error("Missing chain spec file at: {0}")] @@ -31,12 +33,18 @@ pub enum Error { OrchestratorError(#[from] OrchestratorError), #[error("Failed to create pallet directory")] PalletDirCreation, + #[error("Failed to find the pallet {0}")] + PalletNotFound(String), + #[error("Failed to parse the response")] + ParsingResponseError, #[error("Invalid path")] PathError, #[error("Failed to execute rustfmt")] RustfmtError(std::io::Error), #[error("Template error: {0}")] SourcingError(#[from] pop_common::sourcing::Error), + #[error("{0}")] + SubxtError(#[from] subxt::Error), #[error("Toml error: {0}")] TomlError(#[from] toml_edit::de::Error), #[error("Unsupported command: {0}")] diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index 06bee8f4a..37f4c64e8 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -2,6 +2,7 @@ #![doc = include_str!("../README.md")] mod build; +mod call; mod errors; mod generator; mod new_pallet; @@ -14,10 +15,13 @@ pub use build::{ binary_path, build_parachain, export_wasm_file, generate_genesis_state_file, generate_plain_chain_spec, generate_raw_chain_spec, is_supported, ChainSpec, }; +pub use call::{fetch_metadata, parse_chain_metadata, query, storage_info}; pub use errors::Error; pub use indexmap::IndexSet; pub use new_pallet::{create_pallet_template, new_pallet_options::*, TemplatePalletConfig}; pub use new_parachain::instantiate_template_dir; +// External export from subxt. +pub use subxt::Metadata; pub use templates::{Config, Parachain, Provider}; pub use up::Zombienet; pub use utils::helpers::is_initial_endowment_valid; From 316255743b9ad54d36107467ff815d1c57d6c635 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Wed, 11 Sep 2024 18:00:13 +0200 Subject: [PATCH 042/211] feat: dispaly arguments of extrinsic --- crates/pop-cli/src/commands/call/parachain.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 537e7c565..21bc13795 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -73,6 +73,13 @@ async fn guide_user_to_call_chain<'a, CLI: Cli>( metadata: Metadata, ) -> anyhow::Result { command.cli.intro("Call a contract")?; + // Prompt for contract location. + let url: String = command + .cli + .input("Which chain would you like to interact with?") + .placeholder("wss://rpc1.paseo.popnetwork.xyz") + .default_input("wss://rpc1.paseo.popnetwork.xyz") + .interact()?; let pallets = match parse_chain_metadata(metadata.clone()).await { Ok(pallets) => pallets, @@ -107,6 +114,18 @@ async fn guide_user_to_call_chain<'a, CLI: Cli>( } prompt_extrinsic.interact()? }; + let mut args = Vec::new(); + for argument in extrinsic.fields { + let value = command + .cli + .input(&format!( + "Enter the value for the argument '{}':", + argument.name.unwrap_or_default() + )) + .interact()?; + args.push(value); + } + println!("Extrinsic to submit: {:?} with args {:?}", extrinsic.name, args); } else { let storage = { let mut prompt_storage = command.cli.select("Select the storage to query:"); From 7d7b3443e2148827a9b0fa96b8ba28c231681040 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 13 Sep 2024 17:30:02 +0200 Subject: [PATCH 043/211] refactor: structure similar to pop call contract --- crates/pop-cli/src/commands/call/parachain.rs | 68 ++++++++++++------- crates/pop-cli/src/commands/mod.rs | 10 ++- 2 files changed, 48 insertions(+), 30 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 21bc13795..68ecc2025 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -16,22 +16,10 @@ pub struct CallParachainCommand { /// The constructor arguments, encoded as strings. #[clap(long, num_args = 0..)] args: Vec, - /// Transfers an initial balance to the contract. - #[clap(name = "value", long, default_value = "0")] - value: String, - /// Maximum amount of gas to be used for this command. - /// If not specified it will perform a dry-run to estimate the gas consumed for the - /// instantiation. - #[clap(name = "gas", long)] - gas_limit: Option, - /// Maximum proof size for this command. - /// If not specified it will perform a dry-run to estimate the proof size required. - #[clap(long)] - proof_size: Option, /// Websocket endpoint of a node. #[clap(name = "url", long, value_parser, default_value = "ws://localhost:9944")] url: String, - /// Secret key URI for the account calling the contract. + /// Secret key URI for the account signing the extrinsic. /// /// e.g. /// - for a dev account "//Alice" @@ -39,6 +27,22 @@ pub struct CallParachainCommand { #[clap(name = "suri", long, short, default_value = "//Alice")] suri: String, } +impl CallParachainCommand { + fn display(&self) -> String { + let mut full_message = format!("pop call parachain"); + if let Some(pallet) = &self.pallet { + full_message.push_str(&format!(" --pallet {}", pallet)); + } + if let Some(extrinsic) = &self.extrinsic { + full_message.push_str(&format!(" --extrinsic {}", extrinsic)); + } + if !self.args.is_empty() { + full_message.push_str(&format!(" --args {}", self.args.join(" "))); + } + full_message.push_str(&format!(" --url {} --suri {}", self.url, self.suri)); + full_message + } +} pub(crate) struct CallParachain<'a, CLI: Cli> { /// The cli to be used. @@ -49,7 +53,7 @@ pub(crate) struct CallParachain<'a, CLI: Cli> { impl<'a, CLI: Cli> CallParachain<'a, CLI> { /// Executes the command. - pub(crate) async fn execute(mut self: Box) -> Result<()> { + pub(crate) async fn execute(mut self: Self) -> Result<()> { self.cli.intro("Call a parachain")?; let metadata = fetch_metadata("wss://rpc1.paseo.popnetwork.xyz").await?; let call_config = if self.args.pallet.is_none() && self.args.extrinsic.is_none() { @@ -57,6 +61,27 @@ impl<'a, CLI: Cli> CallParachain<'a, CLI> { } else { self.args.clone() }; + match self.execute_extrinsic(call_config.clone()).await { + Ok(_) => Ok(()), + Err(e) => { + self.cli.outro_cancel(format!("{}", e.to_string()))?; + return Ok(()); + }, + } + } + /// Executes the extrinsic or query. + async fn execute_extrinsic(&mut self, call_config: CallParachainCommand) -> Result<()> { + let pallet = call_config + .pallet + .clone() + .expect("pallet can not be none as fallback above is interactive input; qed"); + let extrinsic = call_config + .extrinsic + .clone() + .expect("extrinsic can not be none as fallback above is interactive input; qed"); + // TODO: Check if exists? + // println!("Extrinsic to submit: {:?} with args {:?}", extrinsic.name, args); + // query(&pallet.label, &storage.name, vec![], "wss://rpc1.paseo.popnetwork.xyz").await?; Ok(()) } } @@ -102,8 +127,10 @@ async fn guide_user_to_call_chain<'a, CLI: Cli>( .item(Action::Query, "Query storage", "hint") .interact()?; + let mut extrinsic; + let mut args = Vec::new(); if action == Action::Extrinsic { - let extrinsic = { + extrinsic = { let mut prompt_extrinsic = command.cli.select("Select the extrinsic to call:"); for extrinsic in pallet.extrinsics { prompt_extrinsic = prompt_extrinsic.item( @@ -114,7 +141,6 @@ async fn guide_user_to_call_chain<'a, CLI: Cli>( } prompt_extrinsic.interact()? }; - let mut args = Vec::new(); for argument in extrinsic.fields { let value = command .cli @@ -125,7 +151,6 @@ async fn guide_user_to_call_chain<'a, CLI: Cli>( .interact()?; args.push(value); } - println!("Extrinsic to submit: {:?} with args {:?}", extrinsic.name, args); } else { let storage = { let mut prompt_storage = command.cli.select("Select the storage to query:"); @@ -135,18 +160,13 @@ async fn guide_user_to_call_chain<'a, CLI: Cli>( prompt_storage.interact()? }; let a = storage_info(&pallet.label, &storage.name, &metadata)?; - query(&pallet.label, &storage.name, vec![], "wss://rpc1.paseo.popnetwork.xyz").await?; + println!("STORAGE {:?}", a); } - // println!("Selected pallet: {:?}", pallet.label); - // println!("ext: {:?}", pallet.extrinsics); Ok(CallParachainCommand { - pallet: Some("pallet".to_string()), + pallet: Some(pallet.label), extrinsic: Some("extrinsic".to_string()), args: vec!["".to_string()], - value: "0".to_string(), - gas_limit: None, - proof_size: None, url: "wss://rpc2.paseo.popnetwork.xyz".to_string(), suri: "//Alice".to_string(), }) diff --git a/crates/pop-cli/src/commands/mod.rs b/crates/pop-cli/src/commands/mod.rs index 742c96342..9e8b0c406 100644 --- a/crates/pop-cli/src/commands/mod.rs +++ b/crates/pop-cli/src/commands/mod.rs @@ -98,12 +98,10 @@ impl Command { }, Self::Call(args) => match args.command { #[cfg(feature = "parachain")] - call::Command::Parachain(cmd) => { - Box::new(call::parachain::CallParachain { cli: &mut Cli, args: cmd }) - .execute() - .await - .map(|_| Value::Null) - }, + call::Command::Parachain(cmd) => call::parachain::CallParachain { cli: &mut Cli, args: cmd } + .execute() + .await + .map(|_| Value::Null), #[cfg(feature = "contract")] call::Command::Contract(cmd) => cmd.execute().await.map(|_| Value::Null), }, From eb30ae2b07c1f1577e32e179a352df479d8aa950 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 19 Sep 2024 18:01:13 +0200 Subject: [PATCH 044/211] feat: parse all values for extrinsic/storage --- Cargo.lock | 25 +++ Cargo.toml | 1 + crates/pop-cli/src/commands/call/parachain.rs | 162 ++++++++++-------- crates/pop-cli/src/commands/mod.rs | 5 +- crates/pop-parachains/Cargo.toml | 1 + crates/pop-parachains/src/call.rs | 75 +++++--- crates/pop-parachains/src/lib.rs | 2 +- 7 files changed, 162 insertions(+), 109 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a43de4663..995689b49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4417,6 +4417,12 @@ dependencies = [ "password-hash", ] +[[package]] +name = "peekmore" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9163e1259760e83d528d1b3171e5100c1767f10c52e1c4d6afad26e63d47d758" + [[package]] name = "pem" version = "3.0.4" @@ -4766,6 +4772,7 @@ dependencies = [ "pop-common", "reqwest 0.12.5", "scale-info", + "scale-typegen-description", "serde_json", "strum 0.26.3", "strum_macros 0.26.4", @@ -5698,6 +5705,24 @@ dependencies = [ "thiserror", ] +[[package]] +name = "scale-typegen-description" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ff4cabffc407f6378bc7a164fab8280bfcd862b2dd063cc5c9914a520ea8566" +dependencies = [ + "anyhow", + "peekmore", + "proc-macro2", + "quote", + "rand", + "rand_chacha", + "scale-info", + "scale-typegen", + "scale-value", + "smallvec", +] + [[package]] name = "scale-value" version = "0.16.2" diff --git a/Cargo.toml b/Cargo.toml index 990706e95..f6fb08d86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,7 @@ contract-build = "5.0.0-alpha" contract-extrinsics = "5.0.0-alpha" contract-transcode = "5.0.0-alpha" scale-info = { version = "2.11.3", default-features = false, features = ["derive"] } +scale-typegen-description = "0.8.0" heck = "0.5.0" # parachains diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 68ecc2025..db2b25cf6 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-3.0 -use crate::cli::traits::*; +use crate::cli::{self, traits::*}; use anyhow::{anyhow, Result}; use clap::Args; -use pop_parachains::{fetch_metadata, parse_chain_metadata, query, storage_info, Metadata}; +use pop_parachains::{fetch_metadata, get_type_description, parse_chain_metadata, Metadata}; #[derive(Args, Clone)] pub struct CallParachainCommand { @@ -11,8 +11,11 @@ pub struct CallParachainCommand { #[clap(long, short)] pallet: Option, /// The name of extrinsic to call. - #[clap(long, short)] + #[clap(long, short, conflicts_with = "query")] extrinsic: Option, + /// The name of storage to query. + #[clap(long, short, conflicts_with = "extrinsic")] + query: Option, /// The constructor arguments, encoded as strings. #[clap(long, num_args = 0..)] args: Vec, @@ -28,6 +31,38 @@ pub struct CallParachainCommand { suri: String, } impl CallParachainCommand { + /// Executes the command. + pub(crate) async fn execute(mut self) -> Result<()> { + let metadata = self.query_metadata(&mut cli::Cli).await?; + let call_config = + if self.pallet.is_none() && (self.extrinsic.is_none() || self.query.is_none()) { + guide_user_to_call_chain(&mut cli::Cli, metadata).await? + } else { + self.clone() + }; + execute_extrinsic(call_config.clone(), &mut cli::Cli).await?; + Ok(()) + } + ///Parse metadata. + async fn query_metadata( + &mut self, + cli: &mut impl cli::traits::Cli, + ) -> anyhow::Result { + cli.intro("Call a parachain")?; + let url: String = + if self.pallet.is_none() && (self.extrinsic.is_none() || self.query.is_none()) { + // Prompt for contract location. + cli.input("Which chain would you like to interact with?") + .placeholder("wss://rpc1.paseo.popnetwork.xyz") + .default_input("wss://rpc1.paseo.popnetwork.xyz") + .interact()? + } else { + self.url.clone() + }; + let metadata = fetch_metadata(&url).await?; + Ok(metadata) + } + fn display(&self) -> String { let mut full_message = format!("pop call parachain"); if let Some(pallet) = &self.pallet { @@ -36,6 +71,9 @@ impl CallParachainCommand { if let Some(extrinsic) = &self.extrinsic { full_message.push_str(&format!(" --extrinsic {}", extrinsic)); } + if let Some(query) = &self.query { + full_message.push_str(&format!(" --query {}", query)); + } if !self.args.is_empty() { full_message.push_str(&format!(" --args {}", self.args.join(" "))); } @@ -44,48 +82,6 @@ impl CallParachainCommand { } } -pub(crate) struct CallParachain<'a, CLI: Cli> { - /// The cli to be used. - pub(crate) cli: &'a mut CLI, - /// The args to call. - pub(crate) args: CallParachainCommand, -} - -impl<'a, CLI: Cli> CallParachain<'a, CLI> { - /// Executes the command. - pub(crate) async fn execute(mut self: Self) -> Result<()> { - self.cli.intro("Call a parachain")?; - let metadata = fetch_metadata("wss://rpc1.paseo.popnetwork.xyz").await?; - let call_config = if self.args.pallet.is_none() && self.args.extrinsic.is_none() { - guide_user_to_call_chain(&mut self, metadata).await? - } else { - self.args.clone() - }; - match self.execute_extrinsic(call_config.clone()).await { - Ok(_) => Ok(()), - Err(e) => { - self.cli.outro_cancel(format!("{}", e.to_string()))?; - return Ok(()); - }, - } - } - /// Executes the extrinsic or query. - async fn execute_extrinsic(&mut self, call_config: CallParachainCommand) -> Result<()> { - let pallet = call_config - .pallet - .clone() - .expect("pallet can not be none as fallback above is interactive input; qed"); - let extrinsic = call_config - .extrinsic - .clone() - .expect("extrinsic can not be none as fallback above is interactive input; qed"); - // TODO: Check if exists? - // println!("Extrinsic to submit: {:?} with args {:?}", extrinsic.name, args); - // query(&pallet.label, &storage.name, vec![], "wss://rpc1.paseo.popnetwork.xyz").await?; - Ok(()) - } -} - #[derive(Clone, Eq, PartialEq)] enum Action { Extrinsic, @@ -93,45 +89,34 @@ enum Action { } /// Guide the user to call the contract. -async fn guide_user_to_call_chain<'a, CLI: Cli>( - command: &mut CallParachain<'a, CLI>, +async fn guide_user_to_call_chain( + cli: &mut impl cli::traits::Cli, metadata: Metadata, ) -> anyhow::Result { - command.cli.intro("Call a contract")?; - // Prompt for contract location. - let url: String = command - .cli - .input("Which chain would you like to interact with?") - .placeholder("wss://rpc1.paseo.popnetwork.xyz") - .default_input("wss://rpc1.paseo.popnetwork.xyz") - .interact()?; - let pallets = match parse_chain_metadata(metadata.clone()).await { Ok(pallets) => pallets, Err(e) => { - command.cli.outro_cancel("Unable to fetch the chain metadata.")?; + cli.outro_cancel("Unable to fetch the chain metadata.")?; return Err(anyhow!(format!("{}", e.to_string()))); }, }; let pallet = { - let mut prompt = command.cli.select("Select the pallet to call:"); + let mut prompt = cli.select("Select the pallet to call:"); for pallet_item in pallets { prompt = prompt.item(pallet_item.clone(), &pallet_item.label, &pallet_item.docs); } prompt.interact()? }; - let action = command - .cli + let action = cli .select("What do you want to do?") .item(Action::Extrinsic, "Submit an extrinsic", "hint") .item(Action::Query, "Query storage", "hint") .interact()?; - let mut extrinsic; let mut args = Vec::new(); if action == Action::Extrinsic { - extrinsic = { - let mut prompt_extrinsic = command.cli.select("Select the extrinsic to call:"); + let extrinsic = { + let mut prompt_extrinsic = cli.select("Select the extrinsic to call:"); for extrinsic in pallet.extrinsics { prompt_extrinsic = prompt_extrinsic.item( extrinsic.clone(), @@ -142,8 +127,7 @@ async fn guide_user_to_call_chain<'a, CLI: Cli>( prompt_extrinsic.interact()? }; for argument in extrinsic.fields { - let value = command - .cli + let value = cli .input(&format!( "Enter the value for the argument '{}':", argument.name.unwrap_or_default() @@ -151,23 +135,49 @@ async fn guide_user_to_call_chain<'a, CLI: Cli>( .interact()?; args.push(value); } + Ok(CallParachainCommand { + pallet: Some(pallet.label), + extrinsic: Some(extrinsic.name), + query: None, + args, + url: "wss://rpc2.paseo.popnetwork.xyz".to_string(), + suri: "//Alice".to_string(), + }) } else { - let storage = { - let mut prompt_storage = command.cli.select("Select the storage to query:"); + let query = { + let mut prompt_storage = cli.select("Select the storage to query:"); for storage in pallet.storage { prompt_storage = prompt_storage.item(storage.clone(), &storage.name, &storage.docs); } prompt_storage.interact()? }; - let a = storage_info(&pallet.label, &storage.name, &metadata)?; - println!("STORAGE {:?}", a); + let keys_needed = get_type_description(query.ty.1, &metadata)?; + for key in keys_needed { + let value = cli.input(&format!("Enter the key '{}':", key)).interact()?; + args.push(value); + } + Ok(CallParachainCommand { + pallet: Some(pallet.label), + extrinsic: None, + query: Some(query.name), + args, + url: "wss://rpc2.paseo.popnetwork.xyz".to_string(), + suri: "//Alice".to_string(), + }) } +} - Ok(CallParachainCommand { - pallet: Some(pallet.label), - extrinsic: Some("extrinsic".to_string()), - args: vec!["".to_string()], - url: "wss://rpc2.paseo.popnetwork.xyz".to_string(), - suri: "//Alice".to_string(), - }) +/// Executes the extrinsic or query. +async fn execute_extrinsic( + call_config: CallParachainCommand, + cli: &mut impl cli::traits::Cli, +) -> Result<()> { + cli.info(call_config.display())?; + // TODO: Check if exists? + if call_config.extrinsic.is_some() { + //self.execute_extrinsic(call_config.clone(), &mut cli::Cli).await?; + } else { + //self.execute_query(call_config.clone(), &mut cli::Cli).await?; + } + Ok(()) } diff --git a/crates/pop-cli/src/commands/mod.rs b/crates/pop-cli/src/commands/mod.rs index 9e8b0c406..9f00a8bdb 100644 --- a/crates/pop-cli/src/commands/mod.rs +++ b/crates/pop-cli/src/commands/mod.rs @@ -98,10 +98,7 @@ impl Command { }, Self::Call(args) => match args.command { #[cfg(feature = "parachain")] - call::Command::Parachain(cmd) => call::parachain::CallParachain { cli: &mut Cli, args: cmd } - .execute() - .await - .map(|_| Value::Null), + call::Command::Parachain(cmd) => cmd.execute().await.map(|_| Value::Null), #[cfg(feature = "contract")] call::Command::Contract(cmd) => cmd.execute().await.map(|_| Value::Null), }, diff --git a/crates/pop-parachains/Cargo.toml b/crates/pop-parachains/Cargo.toml index ddf95056e..4d8db2dde 100644 --- a/crates/pop-parachains/Cargo.toml +++ b/crates/pop-parachains/Cargo.toml @@ -27,6 +27,7 @@ askama.workspace = true indexmap.workspace = true reqwest.workspace = true scale-info.workspace = true +scale-typegen-description.workspace = true subxt.workspace = true symlink.workspace = true toml_edit.workspace = true diff --git a/crates/pop-parachains/src/call.rs b/crates/pop-parachains/src/call.rs index 840541ca4..d5c12099a 100644 --- a/crates/pop-parachains/src/call.rs +++ b/crates/pop-parachains/src/call.rs @@ -1,20 +1,17 @@ // SPDX-License-Identifier: GPL-3.0 -// use subxt_codegen::fetch_metadata; -// use subxt_metadata::Metadata; use crate::errors::Error; -use scale_info::{form::PortableForm, PortableRegistry, Variant}; +use scale_info::{form::PortableForm, Variant}; +use scale_typegen_description::type_description; use subxt::{ - dynamic::Value, - error::MetadataError, - metadata::types::{PalletMetadata, StorageEntryMetadata, StorageMetadata}, - Metadata, OnlineClient, SubstrateConfig, + dynamic::Value, metadata::types::StorageEntryType, Metadata, OnlineClient, SubstrateConfig, }; #[derive(Clone, PartialEq, Eq)] pub struct Storage { pub name: String, pub docs: String, + pub ty: (u32, Option), } #[derive(Clone, PartialEq, Eq)] @@ -28,8 +25,6 @@ pub struct Pallet { pub extrinsics: Vec>, // The storage of the pallet. pub storage: Vec, - // /// The constants of the pallet. - // pub consts: Vec, } pub async fn fetch_metadata(url: &str) -> Result { @@ -63,13 +58,17 @@ pub async fn parse_chain_metadata(metadata: Metadata) -> Result, Err pallet.call_variants().map(|variants| variants.to_vec()).unwrap_or_default(); // Return an empty Vec if Option is None let storage: Vec = pallet .storage() - .map(|metadata| { - metadata - .entries() + .map(|m| { + m.entries() .iter() .map(|entry| Storage { name: entry.name().to_string(), docs: entry.docs().concat(), + ty: match entry.entry_type() { + StorageEntryType::Plain(value) => (*value, None), + StorageEntryType::Map { value_ty, key_ty, .. } => + (*value_ty, Some(*key_ty)), + }, }) .collect() }) @@ -85,20 +84,40 @@ pub async fn parse_chain_metadata(metadata: Metadata) -> Result, Err Ok(pallets) } -/// Return details about the given storage entry. -pub fn storage_info<'a>( - pallet_name: &str, - entry_name: &str, - metadata: &'a Metadata, -) -> Result<&'a StorageEntryMetadata, Error> { - let pallet_metadata = metadata - .pallet_by_name(pallet_name) - .ok_or(Error::PalletNotFound(pallet_name.to_string()))?; - let storage_metadata = pallet_metadata - .storage() - .ok_or_else(|| MetadataError::StorageNotFoundInPallet(pallet_name.to_owned()))?; - let storage_entry = storage_metadata - .entry_by_name(entry_name) - .ok_or_else(|| MetadataError::StorageEntryNotFound(entry_name.to_owned()))?; - Ok(storage_entry) +pub fn get_type_description( + key_ty_id: Option, + metadata: &Metadata, +) -> Result, Error> { + if let Some(key_ty_id) = key_ty_id { + let key_ty_description = type_description(key_ty_id, metadata.types(), false)?; + let result = key_ty_description.trim().trim_matches(|c| c == '(' || c == ')'); + + let parsed_result: Vec = if result == "\"\"" { + vec![] + } else if !result.contains(',') { + vec![result.to_string()] + } else { + result.split(',').map(|s| s.trim().to_string()).collect() + }; + + Ok(parsed_result) + } else { + Ok(vec![]) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use anyhow::Result; + + #[tokio::test] + async fn storage_info_works() -> Result<()> { + let metadata = fetch_metadata("wss://rpc2.paseo.popnetwork.xyz").await?; + explore("Nfts", "Account", &metadata)?; + explore("Nfts", "Collection", &metadata)?; + explore("Nfts", "NextCollectionId", &metadata)?; + + Ok(()) + } } diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index 37f4c64e8..614aa377b 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -15,7 +15,7 @@ pub use build::{ binary_path, build_parachain, export_wasm_file, generate_genesis_state_file, generate_plain_chain_spec, generate_raw_chain_spec, is_supported, ChainSpec, }; -pub use call::{fetch_metadata, parse_chain_metadata, query, storage_info}; +pub use call::{fetch_metadata, get_type_description, parse_chain_metadata, query}; pub use errors::Error; pub use indexmap::IndexSet; pub use new_pallet::{create_pallet_template, new_pallet_options::*, TemplatePalletConfig}; From 9527a3915450994a0463bb531e2d45c1a7c56ec3 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Sat, 21 Sep 2024 09:29:54 +0200 Subject: [PATCH 045/211] refactor: signer in common --- Cargo.lock | 5 +- Cargo.toml | 1 + crates/pop-cli/src/commands/call/parachain.rs | 38 ++++- crates/pop-common/Cargo.toml | 2 + crates/pop-common/src/errors.rs | 4 + crates/pop-common/src/lib.rs | 5 + .../src/utils => pop-common/src}/signer.rs | 24 +--- crates/pop-contracts/Cargo.toml | 2 - crates/pop-contracts/src/build.rs | 2 +- crates/pop-contracts/src/call.rs | 7 +- crates/pop-contracts/src/errors.rs | 4 - crates/pop-contracts/src/lib.rs | 3 +- crates/pop-contracts/src/new.rs | 2 +- crates/pop-contracts/src/up.rs | 7 +- crates/pop-contracts/src/utils/helpers.rs | 112 --------------- crates/pop-contracts/src/utils/mod.rs | 134 +++++++++++++++++- crates/pop-parachains/Cargo.toml | 1 + crates/pop-parachains/src/call.rs | 80 ++++++++--- crates/pop-parachains/src/errors.rs | 3 +- crates/pop-parachains/src/lib.rs | 6 +- 20 files changed, 257 insertions(+), 185 deletions(-) rename crates/{pop-contracts/src/utils => pop-common/src}/signer.rs (55%) delete mode 100644 crates/pop-contracts/src/utils/helpers.rs diff --git a/Cargo.lock b/Cargo.lock index 995689b49..6709f2673 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4718,6 +4718,8 @@ dependencies = [ "serde_json", "strum 0.26.3", "strum_macros 0.26.4", + "subxt", + "subxt-signer", "tar", "tempfile", "thiserror", @@ -4747,8 +4749,6 @@ dependencies = [ "sp-weights", "strum 0.26.3", "strum_macros 0.26.4", - "subxt", - "subxt-signer", "tar", "tempfile", "thiserror", @@ -4773,6 +4773,7 @@ dependencies = [ "reqwest 0.12.5", "scale-info", "scale-typegen-description", + "scale-value", "serde_json", "strum 0.26.3", "strum_macros 0.26.4", diff --git a/Cargo.toml b/Cargo.toml index f6fb08d86..eed08daf2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,7 @@ contract-extrinsics = "5.0.0-alpha" contract-transcode = "5.0.0-alpha" scale-info = { version = "2.11.3", default-features = false, features = ["derive"] } scale-typegen-description = "0.8.0" +scale-value = { version = "0.16.2", default-features = false } heck = "0.5.0" # parachains diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index db2b25cf6..74dad9cc3 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -3,7 +3,9 @@ use crate::cli::{self, traits::*}; use anyhow::{anyhow, Result}; use clap::Args; -use pop_parachains::{fetch_metadata, get_type_description, parse_chain_metadata, Metadata}; +use pop_parachains::{ + fetch_metadata, get_type_description, parse_chain_metadata, query, submit_extrinsic, Metadata, +}; #[derive(Args, Clone)] pub struct CallParachainCommand { @@ -64,7 +66,7 @@ impl CallParachainCommand { } fn display(&self) -> String { - let mut full_message = format!("pop call parachain"); + let mut full_message = "pop call parachain".to_string(); if let Some(pallet) = &self.pallet { full_message.push_str(&format!(" --pallet {}", pallet)); } @@ -120,8 +122,8 @@ async fn guide_user_to_call_chain( for extrinsic in pallet.extrinsics { prompt_extrinsic = prompt_extrinsic.item( extrinsic.clone(), - &extrinsic.name, - &extrinsic.docs.concat(), + format!("{}\n", &extrinsic.name), + extrinsic.docs.concat(), ); } prompt_extrinsic.interact()? @@ -132,6 +134,12 @@ async fn guide_user_to_call_chain( "Enter the value for the argument '{}':", argument.name.unwrap_or_default() )) + .placeholder(&format!( + "{} - {}", + argument.docs.join(","), + argument.type_name.unwrap_or_default() + )) + .required(false) .interact()?; args.push(value); } @@ -173,11 +181,29 @@ async fn execute_extrinsic( cli: &mut impl cli::traits::Cli, ) -> Result<()> { cli.info(call_config.display())?; + let pallet = call_config + .pallet + .expect("pallet can not be none as fallback above is interactive input; qed"); // TODO: Check if exists? if call_config.extrinsic.is_some() { - //self.execute_extrinsic(call_config.clone(), &mut cli::Cli).await?; + let extrinsic = call_config + .extrinsic + .expect("storage can not be none as fallback above is interactive input; qed"); + let result = submit_extrinsic( + &pallet, + &extrinsic, + call_config.args, + &call_config.url, + &call_config.suri, + ) + .await?; + cli.outro(format!("Extrinsic submitted successfully with hash: {}", result))?; } else { - //self.execute_query(call_config.clone(), &mut cli::Cli).await?; + let storage = call_config + .query + .expect("storage can not be none as fallback above is interactive input; qed"); + let result = query(&pallet, &storage, call_config.args, &call_config.url).await?; + cli.outro(result)?; } Ok(()) } diff --git a/crates/pop-common/Cargo.toml b/crates/pop-common/Cargo.toml index 8a3afe616..8db41a09f 100644 --- a/crates/pop-common/Cargo.toml +++ b/crates/pop-common/Cargo.toml @@ -19,6 +19,8 @@ reqwest.workspace = true serde_json.workspace = true serde.workspace = true strum.workspace = true +subxt.workspace = true +subxt-signer.workspace = true tar.workspace = true tempfile.workspace = true thiserror.workspace = true diff --git a/crates/pop-common/src/errors.rs b/crates/pop-common/src/errors.rs index 02fb0c9b1..7598cd543 100644 --- a/crates/pop-common/src/errors.rs +++ b/crates/pop-common/src/errors.rs @@ -13,12 +13,16 @@ pub enum Error { Git(String), #[error("IO error: {0}")] IO(#[from] std::io::Error), + #[error("Failed to create keypair from URI: {0}")] + KeyPairCreation(String), #[error("Failed to get manifest path: {0}")] ManifestPath(String), #[error("Manifest error: {0}")] ManifestError(#[from] cargo_toml::Error), #[error("ParseError error: {0}")] ParseError(#[from] url::ParseError), + #[error("Failed to parse secret URI: {0}")] + ParseSecretURI(String), #[error("SourceError error: {0}")] SourceError(#[from] sourcing::Error), #[error("TemplateError error: {0}")] diff --git a/crates/pop-common/src/lib.rs b/crates/pop-common/src/lib.rs index 7206bc7ee..2f2cc2a4e 100644 --- a/crates/pop-common/src/lib.rs +++ b/crates/pop-common/src/lib.rs @@ -3,6 +3,7 @@ pub mod errors; pub mod git; pub mod helpers; pub mod manifest; +pub mod signer; pub mod sourcing; pub mod templates; @@ -11,7 +12,11 @@ pub use errors::Error; pub use git::{Git, GitHub, Release}; pub use helpers::{get_project_name_from_path, prefix_with_current_dir_if_needed, replace_in_file}; pub use manifest::{add_crate_to_workspace, find_workspace_toml}; +pub use signer::create_signer; pub use templates::extractor::extract_template_files; +// External exports +pub use subxt::{Config, PolkadotConfig as DefaultConfig}; +pub use subxt_signer::sr25519::Keypair; static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); diff --git a/crates/pop-contracts/src/utils/signer.rs b/crates/pop-common/src/signer.rs similarity index 55% rename from crates/pop-contracts/src/utils/signer.rs rename to crates/pop-common/src/signer.rs index 51fc44b0e..79da4a283 100644 --- a/crates/pop-contracts/src/utils/signer.rs +++ b/crates/pop-common/src/signer.rs @@ -1,24 +1,16 @@ // SPDX-License-Identifier: GPL-3.0 use crate::errors::Error; -use contract_build::util::decode_hex; -use sp_core::Bytes; use subxt_signer::{sr25519::Keypair, SecretUri}; /// Create a Signer from a secret URI. -pub(crate) fn create_signer(suri: &str) -> Result { +pub fn create_signer(suri: &str) -> Result { let uri = ::from_str(suri) .map_err(|e| Error::ParseSecretURI(format!("{}", e)))?; let keypair = Keypair::from_uri(&uri).map_err(|e| Error::KeyPairCreation(format!("{}", e)))?; Ok(keypair) } -/// Parse hex encoded bytes. -pub fn parse_hex_bytes(input: &str) -> Result { - let bytes = decode_hex(input).map_err(|e| Error::HexParsing(format!("{}", e)))?; - Ok(bytes.into()) -} - #[cfg(test)] mod tests { use super::*; @@ -39,18 +31,4 @@ mod tests { assert!(matches!(create_signer("11111"), Err(Error::KeyPairCreation(..)))); Ok(()) } - - #[test] - fn parse_hex_bytes_works() -> Result<(), Error> { - let input_in_hex = "48656c6c6f"; - let result = parse_hex_bytes(input_in_hex)?; - assert_eq!(result, Bytes(vec![72, 101, 108, 108, 111])); - Ok(()) - } - - #[test] - fn parse_hex_bytes_fails_wrong_input() -> Result<(), Error> { - assert!(matches!(parse_hex_bytes("wronghexvalue"), Err(Error::HexParsing(..)))); - Ok(()) - } } diff --git a/crates/pop-contracts/Cargo.toml b/crates/pop-contracts/Cargo.toml index 19bcbdf32..2b7aa1ab9 100644 --- a/crates/pop-contracts/Cargo.toml +++ b/crates/pop-contracts/Cargo.toml @@ -27,8 +27,6 @@ sp-core.workspace = true sp-weights.workspace = true strum.workspace = true strum_macros.workspace = true -subxt-signer.workspace = true -subxt.workspace = true # cargo-contracts contract-build.workspace = true diff --git a/crates/pop-contracts/src/build.rs b/crates/pop-contracts/src/build.rs index dcbec25a8..df9cc4a3f 100644 --- a/crates/pop-contracts/src/build.rs +++ b/crates/pop-contracts/src/build.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -use crate::{errors::Error, utils::helpers::get_manifest_path}; +use crate::{errors::Error, utils::get_manifest_path}; pub use contract_build::Verbosity; use contract_build::{execute, BuildMode, BuildResult, ExecuteArgs}; use std::path::Path; diff --git a/crates/pop-contracts/src/call.rs b/crates/pop-contracts/src/call.rs index 7a964c387..af988d54e 100644 --- a/crates/pop-contracts/src/call.rs +++ b/crates/pop-contracts/src/call.rs @@ -3,9 +3,9 @@ use crate::{ errors::Error, utils::{ - helpers::{get_manifest_path, parse_account, parse_balance}, + get_manifest_path, metadata::{process_function_args, FunctionType}, - signer::create_signer, + parse_account, parse_balance, }, }; use anyhow::Context; @@ -15,10 +15,9 @@ use contract_extrinsics::{ ExtrinsicOptsBuilder, TokenMetadata, }; use ink_env::{DefaultEnvironment, Environment}; +use pop_common::{create_signer, Config, DefaultConfig, Keypair}; use sp_weights::Weight; use std::path::PathBuf; -use subxt::{Config, PolkadotConfig as DefaultConfig}; -use subxt_signer::sr25519::Keypair; use url::Url; /// Attributes for the `call` command. diff --git a/crates/pop-contracts/src/errors.rs b/crates/pop-contracts/src/errors.rs index 3d19d679b..c3e5de535 100644 --- a/crates/pop-contracts/src/errors.rs +++ b/crates/pop-contracts/src/errors.rs @@ -36,8 +36,6 @@ pub enum Error { InvalidName(String), #[error("IO error: {0}")] IO(#[from] std::io::Error), - #[error("Failed to create keypair from URI: {0}")] - KeyPairCreation(String), #[error("Failed to get manifest path: {0}")] ManifestPath(String), #[error("Argument {0} is required")] @@ -46,8 +44,6 @@ pub enum Error { NewContract(String), #[error("ParseError error: {0}")] ParseError(#[from] url::ParseError), - #[error("Failed to parse secret URI: {0}")] - ParseSecretURI(String), #[error("The `Repository` property is missing from the template variant")] RepositoryMissing, #[error("Sourcing error {0}")] diff --git a/crates/pop-contracts/src/lib.rs b/crates/pop-contracts/src/lib.rs index 142283998..c5e76e2f1 100644 --- a/crates/pop-contracts/src/lib.rs +++ b/crates/pop-contracts/src/lib.rs @@ -26,7 +26,6 @@ pub use up::{ set_up_deployment, set_up_upload, upload_smart_contract, UpOpts, }; pub use utils::{ - helpers::parse_account, metadata::{get_messages, ContractFunction}, - signer::parse_hex_bytes, + parse_account, parse_hex_bytes, }; diff --git a/crates/pop-contracts/src/new.rs b/crates/pop-contracts/src/new.rs index 256c226e5..c0bd93f42 100644 --- a/crates/pop-contracts/src/new.rs +++ b/crates/pop-contracts/src/new.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -use crate::{errors::Error, utils::helpers::canonicalized_path, Contract}; +use crate::{errors::Error, utils::canonicalized_path, Contract}; use anyhow::Result; use contract_build::new_contract_project; use heck::ToUpperCamelCase; diff --git a/crates/pop-contracts/src/up.rs b/crates/pop-contracts/src/up.rs index 2b304123b..17a8e797b 100644 --- a/crates/pop-contracts/src/up.rs +++ b/crates/pop-contracts/src/up.rs @@ -2,9 +2,9 @@ use crate::{ errors::Error, utils::{ - helpers::{get_manifest_path, parse_balance}, + get_manifest_path, metadata::{process_function_args, FunctionType}, - signer::create_signer, + parse_balance, }, }; use contract_extrinsics::{ @@ -12,11 +12,10 @@ use contract_extrinsics::{ TokenMetadata, UploadCommandBuilder, UploadExec, }; use ink_env::{DefaultEnvironment, Environment}; +use pop_common::{create_signer, DefaultConfig, Keypair}; use sp_core::Bytes; use sp_weights::Weight; use std::{fmt::Write, path::PathBuf}; -use subxt::PolkadotConfig as DefaultConfig; -use subxt_signer::sr25519::Keypair; /// Attributes for the `up` command #[derive(Debug, PartialEq)] diff --git a/crates/pop-contracts/src/utils/helpers.rs b/crates/pop-contracts/src/utils/helpers.rs deleted file mode 100644 index d0797015c..000000000 --- a/crates/pop-contracts/src/utils/helpers.rs +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 - -use crate::errors::Error; -use contract_build::ManifestPath; -use contract_extrinsics::BalanceVariant; -use ink_env::{DefaultEnvironment, Environment}; -use std::{ - path::{Path, PathBuf}, - str::FromStr, -}; -use subxt::{Config, PolkadotConfig as DefaultConfig}; - -pub fn get_manifest_path(path: Option<&Path>) -> Result { - if let Some(path) = path { - let full_path = PathBuf::from(path.to_string_lossy().to_string() + "/Cargo.toml"); - ManifestPath::try_from(Some(full_path)) - .map_err(|e| Error::ManifestPath(format!("Failed to get manifest path: {}", e))) - } else { - ManifestPath::try_from(path.as_ref()) - .map_err(|e| Error::ManifestPath(format!("Failed to get manifest path: {}", e))) - } -} - -pub fn parse_balance( - balance: &str, -) -> Result::Balance>, Error> { - BalanceVariant::from_str(balance).map_err(|e| Error::BalanceParsing(format!("{}", e))) -} - -pub fn parse_account(account: &str) -> Result<::AccountId, Error> { - ::AccountId::from_str(account) - .map_err(|e| Error::AccountAddressParsing(format!("{}", e))) -} - -/// Canonicalizes the given path to ensure consistency and resolve any symbolic links. -/// -/// # Arguments -/// -/// * `target` - A reference to the `Path` to be canonicalized. -pub fn canonicalized_path(target: &Path) -> Result { - // Canonicalize the target path to ensure consistency and resolve any symbolic links. - target - .canonicalize() - // If an I/O error occurs during canonicalization, convert it into an Error enum variant. - .map_err(Error::IO) -} - -#[cfg(test)] -mod tests { - use super::*; - use anyhow::{Error, Result}; - use std::fs; - - fn setup_test_environment() -> Result { - let temp_dir = tempfile::tempdir().expect("Could not create temp dir"); - let temp_contract_dir = temp_dir.path().join("test_contract"); - fs::create_dir(&temp_contract_dir)?; - crate::create_smart_contract( - "test_contract", - temp_contract_dir.as_path(), - &crate::Contract::Standard, - )?; - Ok(temp_dir) - } - - #[test] - fn test_get_manifest_path() -> Result<(), Error> { - let temp_dir = setup_test_environment()?; - get_manifest_path(Some(&PathBuf::from(temp_dir.path().join("test_contract"))))?; - Ok(()) - } - - #[test] - fn test_canonicalized_path() -> Result<(), Error> { - let temp_dir = tempfile::tempdir()?; - // Error case - let error_directory = canonicalized_path(&temp_dir.path().join("my_directory")); - assert!(error_directory.is_err()); - // Success case - canonicalized_path(temp_dir.path())?; - Ok(()) - } - - #[test] - fn parse_balance_works() -> Result<(), Error> { - let balance = parse_balance("100000")?; - assert_eq!(balance, BalanceVariant::Default(100000)); - Ok(()) - } - - #[test] - fn parse_balance_fails_wrong_balance() -> Result<(), Error> { - assert!(matches!(parse_balance("wrongbalance"), Err(super::Error::BalanceParsing(..)))); - Ok(()) - } - - #[test] - fn parse_account_works() -> Result<(), Error> { - let account = parse_account("5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A")?; - assert_eq!(account.to_string(), "5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A"); - Ok(()) - } - - #[test] - fn parse_account_fails_wrong_value() -> Result<(), Error> { - assert!(matches!( - parse_account("wrongaccount"), - Err(super::Error::AccountAddressParsing(..)) - )); - Ok(()) - } -} diff --git a/crates/pop-contracts/src/utils/mod.rs b/crates/pop-contracts/src/utils/mod.rs index ad49e2dc6..17546b6ff 100644 --- a/crates/pop-contracts/src/utils/mod.rs +++ b/crates/pop-contracts/src/utils/mod.rs @@ -1,5 +1,135 @@ // SPDX-License-Identifier: GPL-3.0 -pub mod helpers; +use crate::errors::Error; +use contract_build::{util::decode_hex, ManifestPath}; +use contract_extrinsics::BalanceVariant; +use ink_env::{DefaultEnvironment, Environment}; +use pop_common::{Config, DefaultConfig}; +use sp_core::Bytes; +use std::{ + path::{Path, PathBuf}, + str::FromStr, +}; + pub mod metadata; -pub mod signer; + +pub fn get_manifest_path(path: Option<&Path>) -> Result { + if let Some(path) = path { + let full_path = PathBuf::from(path.to_string_lossy().to_string() + "/Cargo.toml"); + ManifestPath::try_from(Some(full_path)) + .map_err(|e| Error::ManifestPath(format!("Failed to get manifest path: {}", e))) + } else { + ManifestPath::try_from(path.as_ref()) + .map_err(|e| Error::ManifestPath(format!("Failed to get manifest path: {}", e))) + } +} + +pub fn parse_balance( + balance: &str, +) -> Result::Balance>, Error> { + BalanceVariant::from_str(balance).map_err(|e| Error::BalanceParsing(format!("{}", e))) +} + +pub fn parse_account(account: &str) -> Result<::AccountId, Error> { + ::AccountId::from_str(account) + .map_err(|e| Error::AccountAddressParsing(format!("{}", e))) +} + +/// Parse hex encoded bytes. +pub fn parse_hex_bytes(input: &str) -> Result { + let bytes = decode_hex(input).map_err(|e| Error::HexParsing(format!("{}", e)))?; + Ok(bytes.into()) +} + +/// Canonicalizes the given path to ensure consistency and resolve any symbolic links. +/// +/// # Arguments +/// +/// * `target` - A reference to the `Path` to be canonicalized. +pub fn canonicalized_path(target: &Path) -> Result { + // Canonicalize the target path to ensure consistency and resolve any symbolic links. + target + .canonicalize() + // If an I/O error occurs during canonicalization, convert it into an Error enum variant. + .map_err(Error::IO) +} + +#[cfg(test)] +mod tests { + use super::*; + use anyhow::{Error, Result}; + use std::fs; + + fn setup_test_environment() -> Result { + let temp_dir = tempfile::tempdir().expect("Could not create temp dir"); + let temp_contract_dir = temp_dir.path().join("test_contract"); + fs::create_dir(&temp_contract_dir)?; + crate::create_smart_contract( + "test_contract", + temp_contract_dir.as_path(), + &crate::Contract::Standard, + )?; + Ok(temp_dir) + } + + #[test] + fn test_get_manifest_path() -> Result<(), Error> { + let temp_dir = setup_test_environment()?; + get_manifest_path(Some(&PathBuf::from(temp_dir.path().join("test_contract"))))?; + Ok(()) + } + + #[test] + fn test_canonicalized_path() -> Result<(), Error> { + let temp_dir = tempfile::tempdir()?; + // Error case + let error_directory = canonicalized_path(&temp_dir.path().join("my_directory")); + assert!(error_directory.is_err()); + // Success case + canonicalized_path(temp_dir.path())?; + Ok(()) + } + + #[test] + fn parse_balance_works() -> Result<(), Error> { + let balance = parse_balance("100000")?; + assert_eq!(balance, BalanceVariant::Default(100000)); + Ok(()) + } + + #[test] + fn parse_balance_fails_wrong_balance() -> Result<(), Error> { + assert!(matches!(parse_balance("wrongbalance"), Err(super::Error::BalanceParsing(..)))); + Ok(()) + } + + #[test] + fn parse_account_works() -> Result<(), Error> { + let account = parse_account("5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A")?; + assert_eq!(account.to_string(), "5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A"); + Ok(()) + } + + #[test] + fn parse_account_fails_wrong_value() -> Result<(), Error> { + assert!(matches!( + parse_account("wrongaccount"), + Err(super::Error::AccountAddressParsing(..)) + )); + Ok(()) + } + + #[test] + fn parse_hex_bytes_works() -> Result<(), Error> { + let input_in_hex = "48656c6c6f"; + let result = parse_hex_bytes(input_in_hex)?; + assert_eq!(result, Bytes(vec![72, 101, 108, 108, 111])); + Ok(()) + } + + #[test] + fn parse_hex_bytes_fails_wrong_input() -> Result<(), Error> { + assert!(matches!(parse_hex_bytes("wronghexvalue"), Err(Error::HexParsing(..)))); + Ok(()) + } +} diff --git a/crates/pop-parachains/Cargo.toml b/crates/pop-parachains/Cargo.toml index 4d8db2dde..7f853abb0 100644 --- a/crates/pop-parachains/Cargo.toml +++ b/crates/pop-parachains/Cargo.toml @@ -28,6 +28,7 @@ indexmap.workspace = true reqwest.workspace = true scale-info.workspace = true scale-typegen-description.workspace = true +scale-value = { workspace = true } subxt.workspace = true symlink.workspace = true toml_edit.workspace = true diff --git a/crates/pop-parachains/src/call.rs b/crates/pop-parachains/src/call.rs index d5c12099a..c220e5cb6 100644 --- a/crates/pop-parachains/src/call.rs +++ b/crates/pop-parachains/src/call.rs @@ -1,11 +1,11 @@ // SPDX-License-Identifier: GPL-3.0 use crate::errors::Error; +use pop_common::create_signer; use scale_info::{form::PortableForm, Variant}; use scale_typegen_description::type_description; -use subxt::{ - dynamic::Value, metadata::types::StorageEntryType, Metadata, OnlineClient, SubstrateConfig, -}; +use scale_value::{stringify, Value}; +use subxt::{metadata::types::StorageEntryType, Metadata, OnlineClient, SubstrateConfig}; #[derive(Clone, PartialEq, Eq)] pub struct Storage { @@ -35,20 +35,45 @@ pub async fn fetch_metadata(url: &str) -> Result { pub async fn query( pallet_name: &str, entry_name: &str, - args: Vec, + args: Vec, url: &str, -) -> Result<(), Error> { +) -> Result { + let args_value: Vec = + args.into_iter().map(|v| stringify::from_str(&v).0.unwrap()).collect(); let api = OnlineClient::::from_url(url).await?; - println!("here"); - let storage_query = subxt::dynamic::storage(pallet_name, entry_name, args); - println!("here"); - let mut results = api.storage().at_latest().await?.iter(storage_query).await?; - println!("{:?}", results); - while let Some(Ok(kv)) = results.next().await { - println!("Keys decoded: {:?}", kv.keys); - println!("Value: {:?}", kv.value.to_value().map_err(|_| Error::ParsingResponseError)?); + let storage_query = subxt::dynamic::storage(pallet_name, entry_name, args_value); + let result = api.storage().at_latest().await?.fetch(&storage_query).await?; + if result.is_none() { + Ok("".to_string()) + } else { + Ok(result.unwrap().to_value()?.to_string()) } - Ok(()) +} + +pub async fn submit_extrinsic( + pallet_name: &str, + entry_name: &str, + args: Vec, + url: &str, + suri: &str, +) -> Result { + let args_value: Vec = args + .into_iter() + .filter_map(|v| match stringify::from_str(&v).0 { + Ok(value) => Some(value), + Err(_) => None, + }) + .collect(); + let api = OnlineClient::::from_url(url).await?; + let tx = subxt::dynamic::tx(pallet_name, entry_name, args_value); + let signer = create_signer(suri)?; + let result = api + .tx() + .sign_and_submit_then_watch_default(&tx, &signer) + .await? + .wait_for_finalized_success() + .await?; + Ok(result.extrinsic_hash().to_string()) } pub async fn parse_chain_metadata(metadata: Metadata) -> Result, Error> { @@ -112,11 +137,28 @@ mod tests { use anyhow::Result; #[tokio::test] - async fn storage_info_works() -> Result<()> { - let metadata = fetch_metadata("wss://rpc2.paseo.popnetwork.xyz").await?; - explore("Nfts", "Account", &metadata)?; - explore("Nfts", "Collection", &metadata)?; - explore("Nfts", "NextCollectionId", &metadata)?; + async fn query_works() -> Result<()> { + let result = + query("Assets", "Asset", vec!["50".into()], "wss://rpc2.paseo.popnetwork.xyz").await?; + println!("{:?}", result); + // query("Nfts", "Collection", &metadata)?; + // query("Nfts", "NextCollectionId", &metadata)?; + + Ok(()) + } + + #[tokio::test] + async fn extrinsic_works() -> Result<()> { + let result = query( + "Balances", + "TransferAllowDeath", + vec!["167Y1SbQrwQVNfkNUXtRkocfzVbaAHYjnZPkZRScWPQ46XDb".into(), "1".into()], + "wss://rpc2.paseo.popnetwork.xyz", + ) + .await?; + println!("{:?}", result); + // query("Nfts", "Collection", &metadata)?; + // query("Nfts", "NextCollectionId", &metadata)?; Ok(()) } diff --git a/crates/pop-parachains/src/errors.rs b/crates/pop-parachains/src/errors.rs index 7e9e8e19d..7ddb9dd75 100644 --- a/crates/pop-parachains/src/errors.rs +++ b/crates/pop-parachains/src/errors.rs @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 +use subxt::ext::scale_decode; use thiserror::Error; use zombienet_sdk::OrchestratorError; @@ -36,7 +37,7 @@ pub enum Error { #[error("Failed to find the pallet {0}")] PalletNotFound(String), #[error("Failed to parse the response")] - ParsingResponseError, + ParsingResponseError(#[from] scale_decode::Error), #[error("Invalid path")] PathError, #[error("Failed to execute rustfmt")] diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index 614aa377b..cb979b3fc 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -15,13 +15,15 @@ pub use build::{ binary_path, build_parachain, export_wasm_file, generate_genesis_state_file, generate_plain_chain_spec, generate_raw_chain_spec, is_supported, ChainSpec, }; -pub use call::{fetch_metadata, get_type_description, parse_chain_metadata, query}; +pub use call::{ + fetch_metadata, get_type_description, parse_chain_metadata, query, submit_extrinsic, +}; pub use errors::Error; pub use indexmap::IndexSet; pub use new_pallet::{create_pallet_template, new_pallet_options::*, TemplatePalletConfig}; pub use new_parachain::instantiate_template_dir; // External export from subxt. -pub use subxt::Metadata; +pub use subxt::{dynamic::Value, Metadata}; pub use templates::{Config, Parachain, Provider}; pub use up::Zombienet; pub use utils::helpers::is_initial_endowment_valid; From 9acf4bb9b18611dcfbf3e0ea9b5cb0e018188f87 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Sun, 22 Sep 2024 10:06:13 +0200 Subject: [PATCH 046/211] refactor: improve messages --- crates/pop-cli/src/commands/call/parachain.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 74dad9cc3..cd740cb7d 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -111,8 +111,8 @@ async fn guide_user_to_call_chain( }; let action = cli .select("What do you want to do?") - .item(Action::Extrinsic, "Submit an extrinsic", "hint") - .item(Action::Query, "Query storage", "hint") + .item(Action::Extrinsic, "Submit an extrinsic", "") + .item(Action::Query, "Query storage", "") .interact()?; let mut args = Vec::new(); @@ -134,11 +134,7 @@ async fn guide_user_to_call_chain( "Enter the value for the argument '{}':", argument.name.unwrap_or_default() )) - .placeholder(&format!( - "{} - {}", - argument.docs.join(","), - argument.type_name.unwrap_or_default() - )) + .placeholder(&argument.type_name.unwrap_or_default()) .required(false) .interact()?; args.push(value); From 8c836bd34ce18a0f660ce0f8413030c23f1ad7e5 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Mon, 23 Sep 2024 23:54:19 +0200 Subject: [PATCH 047/211] feat: call parachain ui --- Cargo.lock | 1 + Cargo.toml | 1 + crates/pop-cli/src/commands/call/mod.rs | 2 + crates/pop-cli/src/commands/call/parachain.rs | 194 +++++++--------- crates/pop-cli/src/commands/call/use_cases.rs | 113 +++++++++ crates/pop-common/src/errors.rs | 2 + crates/pop-common/src/lib.rs | 12 +- crates/pop-common/src/signer.rs | 24 +- crates/pop-contracts/src/call.rs | 6 +- crates/pop-contracts/src/errors.rs | 2 - crates/pop-contracts/src/lib.rs | 2 +- crates/pop-contracts/src/utils/mod.rs | 22 -- crates/pop-parachains/Cargo.toml | 1 + crates/pop-parachains/src/call.rs | 217 ++++++++++++------ crates/pop-parachains/src/errors.rs | 8 + crates/pop-parachains/src/lib.rs | 5 +- 16 files changed, 394 insertions(+), 218 deletions(-) create mode 100644 crates/pop-cli/src/commands/call/use_cases.rs diff --git a/Cargo.lock b/Cargo.lock index 6709f2673..4e6378f39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4767,6 +4767,7 @@ dependencies = [ "duct", "flate2", "glob", + "hex", "indexmap 2.4.0", "mockito", "pop-common", diff --git a/Cargo.toml b/Cargo.toml index eed08daf2..41310f037 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,6 +54,7 @@ scale-info = { version = "2.11.3", default-features = false, features = ["derive scale-typegen-description = "0.8.0" scale-value = { version = "0.16.2", default-features = false } heck = "0.5.0" +hex = { version = "0.4.3", default-features = false } # parachains askama = "0.12" diff --git a/crates/pop-cli/src/commands/call/mod.rs b/crates/pop-cli/src/commands/call/mod.rs index 241ebbdb9..04f70634b 100644 --- a/crates/pop-cli/src/commands/call/mod.rs +++ b/crates/pop-cli/src/commands/call/mod.rs @@ -6,6 +6,8 @@ use clap::{Args, Subcommand}; pub(crate) mod contract; #[cfg(feature = "parachain")] pub(crate) mod parachain; +#[cfg(feature = "parachain")] +pub(crate) mod use_cases; /// Arguments for calling a smart contract. #[derive(Args)] diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index cd740cb7d..fd974a877 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -1,26 +1,20 @@ // SPDX-License-Identifier: GPL-3.0 use crate::cli::{self, traits::*}; -use anyhow::{anyhow, Result}; +use anyhow::Result; use clap::Args; use pop_parachains::{ - fetch_metadata, get_type_description, parse_chain_metadata, query, submit_extrinsic, Metadata, + prepare_extrinsic, set_up_api, submit_extrinsic, OnlineClient, Pallet, SubstrateConfig, }; +use strum::VariantArray; + +use super::use_cases::prompt_arguments; #[derive(Args, Clone)] pub struct CallParachainCommand { - /// The name of the pallet to call. + /// The signed extrinsic to call. #[clap(long, short)] - pallet: Option, - /// The name of extrinsic to call. - #[clap(long, short, conflicts_with = "query")] extrinsic: Option, - /// The name of storage to query. - #[clap(long, short, conflicts_with = "extrinsic")] - query: Option, - /// The constructor arguments, encoded as strings. - #[clap(long, num_args = 0..)] - args: Vec, /// Websocket endpoint of a node. #[clap(name = "url", long, value_parser, default_value = "ws://localhost:9944")] url: String, @@ -32,112 +26,71 @@ pub struct CallParachainCommand { #[clap(name = "suri", long, short, default_value = "//Alice")] suri: String, } + impl CallParachainCommand { /// Executes the command. pub(crate) async fn execute(mut self) -> Result<()> { - let metadata = self.query_metadata(&mut cli::Cli).await?; - let call_config = - if self.pallet.is_none() && (self.extrinsic.is_none() || self.query.is_none()) { - guide_user_to_call_chain(&mut cli::Cli, metadata).await? - } else { - self.clone() - }; - execute_extrinsic(call_config.clone(), &mut cli::Cli).await?; + let (api, url) = self.set_up_api(&mut cli::Cli).await?; + let call_config = if self.extrinsic.is_none() { + guide_user_to_call_chain(&api, url, &mut cli::Cli).await? + } else { + self.clone() + }; + execute_extrinsic(api, call_config, self.extrinsic.is_none(), &mut cli::Cli).await?; Ok(()) } - ///Parse metadata. - async fn query_metadata( + /// Prompt the user for the chain to use if not indicated and fetch the metadata. + async fn set_up_api( &mut self, cli: &mut impl cli::traits::Cli, - ) -> anyhow::Result { + ) -> anyhow::Result<(OnlineClient, String)> { cli.intro("Call a parachain")?; - let url: String = - if self.pallet.is_none() && (self.extrinsic.is_none() || self.query.is_none()) { - // Prompt for contract location. - cli.input("Which chain would you like to interact with?") - .placeholder("wss://rpc1.paseo.popnetwork.xyz") - .default_input("wss://rpc1.paseo.popnetwork.xyz") - .interact()? - } else { - self.url.clone() - }; - let metadata = fetch_metadata(&url).await?; - Ok(metadata) + let url: String = if self.extrinsic.is_none() { + // Prompt for contract location. + cli.input("Which chain would you like to interact with?") + .placeholder("wss://rpc1.paseo.popnetwork.xyz") + .default_input("wss://rpc1.paseo.popnetwork.xyz") + .interact()? + } else { + self.url.clone() + }; + let api = set_up_api(&url).await?; + Ok((api, url)) } fn display(&self) -> String { let mut full_message = "pop call parachain".to_string(); - if let Some(pallet) = &self.pallet { - full_message.push_str(&format!(" --pallet {}", pallet)); - } if let Some(extrinsic) = &self.extrinsic { full_message.push_str(&format!(" --extrinsic {}", extrinsic)); } - if let Some(query) = &self.query { - full_message.push_str(&format!(" --query {}", query)); + full_message.push_str(&format!("--url {}", self.url)); + if !self.suri.is_empty() { + full_message.push_str(&format!(" --suri {}", self.suri)); } - if !self.args.is_empty() { - full_message.push_str(&format!(" --args {}", self.args.join(" "))); - } - full_message.push_str(&format!(" --url {} --suri {}", self.url, self.suri)); full_message } } -#[derive(Clone, Eq, PartialEq)] -enum Action { - Extrinsic, - Query, -} - /// Guide the user to call the contract. async fn guide_user_to_call_chain( + api: &OnlineClient, + url: String, cli: &mut impl cli::traits::Cli, - metadata: Metadata, ) -> anyhow::Result { - let pallets = match parse_chain_metadata(metadata.clone()).await { - Ok(pallets) => pallets, - Err(e) => { - cli.outro_cancel("Unable to fetch the chain metadata.")?; - return Err(anyhow!(format!("{}", e.to_string()))); - }, - }; + let pallets = Pallet::VARIANTS; let pallet = { let mut prompt = cli.select("Select the pallet to call:"); for pallet_item in pallets { - prompt = prompt.item(pallet_item.clone(), &pallet_item.label, &pallet_item.docs); + prompt = prompt.item(pallet_item.clone(), pallet_item.as_ref(), ""); } prompt.interact()? }; - let action = cli - .select("What do you want to do?") - .item(Action::Extrinsic, "Submit an extrinsic", "") - .item(Action::Query, "Query storage", "") - .interact()?; - let mut args = Vec::new(); - if action == Action::Extrinsic { - let extrinsic = { - let mut prompt_extrinsic = cli.select("Select the extrinsic to call:"); - for extrinsic in pallet.extrinsics { - prompt_extrinsic = prompt_extrinsic.item( - extrinsic.clone(), - format!("{}\n", &extrinsic.name), - extrinsic.docs.concat(), - ); - } - prompt_extrinsic.interact()? - }; - for argument in extrinsic.fields { - let value = cli - .input(&format!( - "Enter the value for the argument '{}':", - argument.name.unwrap_or_default() - )) - .placeholder(&argument.type_name.unwrap_or_default()) - .required(false) - .interact()?; - args.push(value); + let extrinsic = { + let mut prompt_extrinsic = cli.select("Select the extrinsic to call:"); + for extrinsic in pallet.extrinsics() { + prompt_extrinsic = + prompt_extrinsic.item(extrinsic.clone(), format!("{}\n", &extrinsic.as_ref()), ""); } Ok(CallParachainCommand { pallet: Some(pallet.label), @@ -155,7 +108,7 @@ async fn guide_user_to_call_chain( } prompt_storage.interact()? }; - let keys_needed = get_type_description(query.ty.1, &metadata)?; + let keys_needed = get_type_description(query.ty.1, metadata)?; for key in keys_needed { let value = cli.input(&format!("Enter the key '{}':", key)).interact()?; args.push(value); @@ -173,33 +126,54 @@ async fn guide_user_to_call_chain( /// Executes the extrinsic or query. async fn execute_extrinsic( + api: OnlineClient, call_config: CallParachainCommand, + prompt_to_repeat_call: bool, cli: &mut impl cli::traits::Cli, ) -> Result<()> { cli.info(call_config.display())?; - let pallet = call_config - .pallet - .expect("pallet can not be none as fallback above is interactive input; qed"); - // TODO: Check if exists? - if call_config.extrinsic.is_some() { - let extrinsic = call_config - .extrinsic - .expect("storage can not be none as fallback above is interactive input; qed"); - let result = submit_extrinsic( - &pallet, - &extrinsic, - call_config.args, - &call_config.url, - &call_config.suri, - ) - .await?; - cli.outro(format!("Extrinsic submitted successfully with hash: {}", result))?; + let extrinsic = call_config + .extrinsic + .expect("extrinsic can not be none as fallback above is interactive input; qed"); + if !cli.confirm("Do you want to sign and submit the call?").interact()? { + display_message(&format!("Extrinsic: {} not submitted", extrinsic), true, cli)?; + return Ok(()); + } + // TODO: Handle error + let result = submit_extrinsic(api.clone(), extrinsic).await?; + // Repeat call. + if prompt_to_repeat_call { + let another_call: bool = cli + .confirm("Do you want to do another call to the same chain?") + .initial_value(false) + .interact()?; + if another_call { + // Remove only the prompt asking for another call. + console::Term::stderr().clear_last_lines(2)?; + let new_call_config = guide_user_to_call_chain(&api, call_config.url, cli).await?; + Box::pin(execute_extrinsic(api, new_call_config, prompt_to_repeat_call, cli)).await?; + } else { + display_message( + &format!("Extrinsic submitted successfully with hash: {}", result), + true, + cli, + )?; + } + } else { + display_message( + &format!("Extrinsic submitted successfully with hash: {}", result), + true, + cli, + )?; + } + Ok(()) +} + +fn display_message(message: &str, success: bool, cli: &mut impl cli::traits::Cli) -> Result<()> { + if success { + cli.outro(message)?; } else { - let storage = call_config - .query - .expect("storage can not be none as fallback above is interactive input; qed"); - let result = query(&pallet, &storage, call_config.args, &call_config.url).await?; - cli.outro(result)?; + cli.outro_cancel(message)?; } Ok(()) } diff --git a/crates/pop-cli/src/commands/call/use_cases.rs b/crates/pop-cli/src/commands/call/use_cases.rs new file mode 100644 index 000000000..0723a0bb5 --- /dev/null +++ b/crates/pop-cli/src/commands/call/use_cases.rs @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-3.0 + +use crate::cli::{ + self, + traits::{Confirm as _, Input as _}, +}; +use anyhow::Result; +use pop_common::parse_account; +use pop_parachains::{parse_string_into_scale_value, Extrinsic, Pallet, Value}; + +/// Prompt the user to select an operation. +pub fn prompt_arguments( + extrinsic: &Extrinsic, + cli: &mut impl cli::traits::Cli, +) -> Result> { + match extrinsic { + Extrinsic::CreateAsset => prompt_query_params(&extrinsic, cli), + Extrinsic::MintAsset => prompt_query_params(&extrinsic, cli), + Extrinsic::CreateCollection => prompt_query_params(&extrinsic, cli), + Extrinsic::MintNFT => prompt_query_params(&extrinsic, cli), + Extrinsic::Transfer => prompt_query_params(&extrinsic, cli), + } +} +fn prompt_query_params( + extrinsic: &Extrinsic, + cli: &mut impl cli::traits::Cli, +) -> Result> { + let mut args: Vec = Vec::new(); + let id = cli + .input(&format!( + "Enter the {} id", + if extrinsic.pallet()? == Pallet::Assets.to_string() { "Asset" } else { "Collection" } + )) + .placeholder("0") + .required(false) + .interact()?; + args.push(parse_string_into_scale_value(&id)?); + // if call == &Call::NFTItem { + // let nft_id = cli + // .input("Enter the Nft id") + // .placeholder("0 or None") + // .required(false) + // .interact()?; + // args.push(parse_string_into_scale_value(&nft_id)?); + // } + Ok(args) +} +fn prompt_mint_params( + extrinsic: &Extrinsic, + cli: &mut impl cli::traits::Cli, +) -> Result> { + let mut args: Vec = Vec::new(); + let id = cli + .input(&format!( + "Enter the {} id", + if extrinsic.pallet()? == Pallet::Assets.to_string() { "Asset" } else { "Collection" } + )) + .placeholder("0") + .required(true) + .interact()?; + args.push(parse_string_into_scale_value(&id)?); + if extrinsic == &Extrinsic::MintNFT { + let nft_id = cli + .input(&format!( + "Enter the {} id", + if extrinsic.pallet()? == Pallet::Assets.to_string() { + "Asset" + } else { + "Collection" + } + )) + .placeholder("0") + .required(true) + .interact()?; + args.push(parse_string_into_scale_value(&nft_id)?); + } + // Prompt for beneficiary + let beneficiary: String = cli + .input("Enter the beneficiary address") + .placeholder("e.g. 5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") + .required(true) + .validate(|input: &String| match parse_account(input) { + Ok(_) => Ok(()), + Err(_) => Err("Invalid address."), + }) + .interact()?; + args.push(parse_string_into_scale_value(&beneficiary)?); + // if extrinsic == &Extrinsic::AssetItem { + // let amount = cli.input("Enter the amount").placeholder("0").required(true).interact()?; + // args.push(parse_string_into_scale_value(&amount)?); + // } + if extrinsic == &Extrinsic::MintNFT { + if cli + .confirm("Do you want to include witness data?") + .initial_value(false) + .interact()? + { + let config = {}; + let owned_item = cli + .input("Id of the item in a required collection:") + .placeholder("0 or None") + .required(false) + .interact()?; + let owned_item = cli + .input("The price specified in mint settings:") + .placeholder("0 or None") + .required(false) + .interact()?; + //args.push(Value::u128(id)); + } + } + Ok(args) +} diff --git a/crates/pop-common/src/errors.rs b/crates/pop-common/src/errors.rs index 7598cd543..5a0066add 100644 --- a/crates/pop-common/src/errors.rs +++ b/crates/pop-common/src/errors.rs @@ -7,6 +7,8 @@ use thiserror::Error; pub enum Error { #[error("Anyhow error: {0}")] AnyhowError(#[from] anyhow::Error), + #[error("Failed to parse account address: {0}")] + AccountAddressParsing(String), #[error("Configuration error: {0}")] Config(String), #[error("a git error occurred: {0}")] diff --git a/crates/pop-common/src/lib.rs b/crates/pop-common/src/lib.rs index 2f2cc2a4e..332fdb79a 100644 --- a/crates/pop-common/src/lib.rs +++ b/crates/pop-common/src/lib.rs @@ -12,7 +12,7 @@ pub use errors::Error; pub use git::{Git, GitHub, Release}; pub use helpers::{get_project_name_from_path, prefix_with_current_dir_if_needed, replace_in_file}; pub use manifest::{add_crate_to_workspace, find_workspace_toml}; -pub use signer::create_signer; +pub use signer::{create_signer, parse_account}; pub use templates::extractor::extract_template_files; // External exports pub use subxt::{Config, PolkadotConfig as DefaultConfig}; @@ -40,16 +40,18 @@ pub fn target() -> Result<&'static str, Error> { } match ARCH { - "aarch64" => + "aarch64" => { return match OS { "macos" => Ok("aarch64-apple-darwin"), _ => Ok("aarch64-unknown-linux-gnu"), - }, - "x86_64" | "x86" => + } + }, + "x86_64" | "x86" => { return match OS { "macos" => Ok("x86_64-apple-darwin"), _ => Ok("x86_64-unknown-linux-gnu"), - }, + } + }, &_ => {}, } Err(Error::UnsupportedPlatform { arch: ARCH, os: OS }) diff --git a/crates/pop-common/src/signer.rs b/crates/pop-common/src/signer.rs index 79da4a283..8cc3e9e25 100644 --- a/crates/pop-common/src/signer.rs +++ b/crates/pop-common/src/signer.rs @@ -1,8 +1,14 @@ // SPDX-License-Identifier: GPL-3.0 -use crate::errors::Error; +use crate::{errors::Error, Config, DefaultConfig}; +use std::str::FromStr; use subxt_signer::{sr25519::Keypair, SecretUri}; +pub fn parse_account(account: &str) -> Result<::AccountId, Error> { + ::AccountId::from_str(account) + .map_err(|e| Error::AccountAddressParsing(format!("{}", e))) +} + /// Create a Signer from a secret URI. pub fn create_signer(suri: &str) -> Result { let uri = ::from_str(suri) @@ -31,4 +37,20 @@ mod tests { assert!(matches!(create_signer("11111"), Err(Error::KeyPairCreation(..)))); Ok(()) } + + #[test] + fn parse_account_works() -> Result<(), Error> { + let account = parse_account("5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A")?; + assert_eq!(account.to_string(), "5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A"); + Ok(()) + } + + #[test] + fn parse_account_fails_wrong_value() -> Result<(), Error> { + assert!(matches!( + parse_account("wrongaccount"), + Err(super::Error::AccountAddressParsing(..)) + )); + Ok(()) + } } diff --git a/crates/pop-contracts/src/call.rs b/crates/pop-contracts/src/call.rs index af988d54e..0ca795738 100644 --- a/crates/pop-contracts/src/call.rs +++ b/crates/pop-contracts/src/call.rs @@ -4,8 +4,8 @@ use crate::{ errors::Error, utils::{ get_manifest_path, - metadata::{process_function_args, FunctionType}, - parse_account, parse_balance, + metadata::{get_messages, ContractFunction}, + parse_balance, }, }; use anyhow::Context; @@ -15,7 +15,7 @@ use contract_extrinsics::{ ExtrinsicOptsBuilder, TokenMetadata, }; use ink_env::{DefaultEnvironment, Environment}; -use pop_common::{create_signer, Config, DefaultConfig, Keypair}; +use pop_common::{create_signer, parse_account, Config, DefaultConfig, Keypair}; use sp_weights::Weight; use std::path::PathBuf; use url::Url; diff --git a/crates/pop-contracts/src/errors.rs b/crates/pop-contracts/src/errors.rs index c3e5de535..348f352c1 100644 --- a/crates/pop-contracts/src/errors.rs +++ b/crates/pop-contracts/src/errors.rs @@ -8,8 +8,6 @@ use thiserror::Error; pub enum Error { #[error("Anyhow error: {0}")] AnyhowError(#[from] anyhow::Error), - #[error("Failed to parse account address: {0}")] - AccountAddressParsing(String), #[error("Failed to parse balance: {0}")] BalanceParsing(String), #[error("{0}")] diff --git a/crates/pop-contracts/src/lib.rs b/crates/pop-contracts/src/lib.rs index c5e76e2f1..fe512a56f 100644 --- a/crates/pop-contracts/src/lib.rs +++ b/crates/pop-contracts/src/lib.rs @@ -27,5 +27,5 @@ pub use up::{ }; pub use utils::{ metadata::{get_messages, ContractFunction}, - parse_account, parse_hex_bytes, + parse_hex_bytes, }; diff --git a/crates/pop-contracts/src/utils/mod.rs b/crates/pop-contracts/src/utils/mod.rs index 17546b6ff..7192839b4 100644 --- a/crates/pop-contracts/src/utils/mod.rs +++ b/crates/pop-contracts/src/utils/mod.rs @@ -4,7 +4,6 @@ use crate::errors::Error; use contract_build::{util::decode_hex, ManifestPath}; use contract_extrinsics::BalanceVariant; use ink_env::{DefaultEnvironment, Environment}; -use pop_common::{Config, DefaultConfig}; use sp_core::Bytes; use std::{ path::{Path, PathBuf}, @@ -30,11 +29,6 @@ pub fn parse_balance( BalanceVariant::from_str(balance).map_err(|e| Error::BalanceParsing(format!("{}", e))) } -pub fn parse_account(account: &str) -> Result<::AccountId, Error> { - ::AccountId::from_str(account) - .map_err(|e| Error::AccountAddressParsing(format!("{}", e))) -} - /// Parse hex encoded bytes. pub fn parse_hex_bytes(input: &str) -> Result { let bytes = decode_hex(input).map_err(|e| Error::HexParsing(format!("{}", e)))?; @@ -103,22 +97,6 @@ mod tests { Ok(()) } - #[test] - fn parse_account_works() -> Result<(), Error> { - let account = parse_account("5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A")?; - assert_eq!(account.to_string(), "5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A"); - Ok(()) - } - - #[test] - fn parse_account_fails_wrong_value() -> Result<(), Error> { - assert!(matches!( - parse_account("wrongaccount"), - Err(super::Error::AccountAddressParsing(..)) - )); - Ok(()) - } - #[test] fn parse_hex_bytes_works() -> Result<(), Error> { let input_in_hex = "48656c6c6f"; diff --git a/crates/pop-parachains/Cargo.toml b/crates/pop-parachains/Cargo.toml index 7f853abb0..c590ad3dc 100644 --- a/crates/pop-parachains/Cargo.toml +++ b/crates/pop-parachains/Cargo.toml @@ -22,6 +22,7 @@ tempfile.workspace = true thiserror.workspace = true tokio.workspace = true url.workspace = true +hex.workspace = true askama.workspace = true indexmap.workspace = true diff --git a/crates/pop-parachains/src/call.rs b/crates/pop-parachains/src/call.rs index c220e5cb6..f7df6175a 100644 --- a/crates/pop-parachains/src/call.rs +++ b/crates/pop-parachains/src/call.rs @@ -2,77 +2,125 @@ use crate::errors::Error; use pop_common::create_signer; -use scale_info::{form::PortableForm, Variant}; -use scale_typegen_description::type_description; use scale_value::{stringify, Value}; -use subxt::{metadata::types::StorageEntryType, Metadata, OnlineClient, SubstrateConfig}; +use strum::{EnumMessage as _, EnumProperty as _, VariantArray as _}; +use strum_macros::{AsRefStr, Display, EnumMessage, EnumProperty, EnumString, VariantArray}; +use subxt::{ + config::DefaultExtrinsicParamsBuilder, tx::SubmittableExtrinsic, OnlineClient, SubstrateConfig, +}; +/// A supported pallet. +#[derive(AsRefStr, Clone, Debug, Display, EnumMessage, EnumString, Eq, PartialEq, VariantArray)] +pub enum Pallet { + //Assets. + #[strum(serialize = "Assets")] + Assets, + //Balances. + #[strum(serialize = "Balances")] + Balances, + /// NFT. + #[strum(serialize = "Nfts")] + Nfts, +} +impl Pallet { + /// Get the list of extrinsics available. + pub fn extrinsics(&self) -> Vec<&Extrinsic> { + Extrinsic::VARIANTS + .iter() + .filter(|t| t.get_str("Pallet") == Some(self.as_ref())) + .collect() + } +} -#[derive(Clone, PartialEq, Eq)] -pub struct Storage { - pub name: String, - pub docs: String, - pub ty: (u32, Option), +#[derive( + AsRefStr, + Clone, + Debug, + Display, + EnumMessage, + EnumProperty, + EnumString, + Eq, + PartialEq, + VariantArray, +)] +pub enum Extrinsic { + #[strum(serialize = "create_asset", message = "create", props(Pallet = "Assets"))] + CreateAsset, + #[strum(serialize = "mint_asset", message = "mint", props(Pallet = "Asset"))] + MintAsset, + #[strum(serialize = "create_nft", message = "create", props(Pallet = "Nfts"))] + CreateCollection, + #[strum(serialize = "mint", message = "mint", props(Pallet = "Nft"))] + MintNFT, + #[strum(serialize = "transfer", message = "transfer_allow_death", props(Pallet = "Balances"))] + Transfer, +} +impl Extrinsic { + /// Get the template's name. + fn extrinsic_name(&self) -> &str { + self.get_message().unwrap_or_default() + } + /// Get the pallet of the extrinsic. + pub fn pallet(&self) -> Result<&str, Error> { + self.get_str("Pallet").ok_or(Error::PalletMissing) + } } -#[derive(Clone, PartialEq, Eq)] -/// Describes a contract message. -pub struct Pallet { - /// The label of the message. - pub label: String, - /// The message documentation. - pub docs: String, - // The extrinsics of the pallet. - pub extrinsics: Vec>, - // The storage of the pallet. - pub storage: Vec, +pub fn parse_string_into_scale_value(str: &str) -> Result { + let value = stringify::from_str(str) + .0 + .map_err(|_| Error::ParsingValueError(str.to_string()))?; + Ok(value) } -pub async fn fetch_metadata(url: &str) -> Result { +pub async fn set_up_api(url: &str) -> Result, Error> { let api = OnlineClient::::from_url(url).await?; - Ok(api.metadata()) + Ok(api) } -pub async fn query( +// pub async fn prepare_query( +// api: &OnlineClient, +// pallet_name: &str, +// entry_name: &str, +// args: Vec, +// ) -> Result, Error> { +// //let args = convert_vec(args_value)?; +// let storage = subxt::dynamic::storage(pallet_name, entry_name, args); +// let addr_bytes = api.storage().address_bytes(&storage)?; +// let result = api.storage().at_latest().await?.fetch(&storage).await?; +// Ok(addr_bytes) +// } +fn encode_extrinsic(encoded_call_data: Vec) -> String { + format!("0x{}", hex::encode(encoded_call_data)) +} +fn decode_extrinsic(encoded_call_data: String) -> Result, Error> { + let hex_data = encoded_call_data.trim_start_matches("0x"); + Ok(hex::decode(hex_data)?) +} +pub async fn prepare_extrinsic( + api: &OnlineClient, pallet_name: &str, entry_name: &str, - args: Vec, - url: &str, + args_value: Vec, + suri: &str, ) -> Result { - let args_value: Vec = - args.into_iter().map(|v| stringify::from_str(&v).0.unwrap()).collect(); - let api = OnlineClient::::from_url(url).await?; - let storage_query = subxt::dynamic::storage(pallet_name, entry_name, args_value); - let result = api.storage().at_latest().await?.fetch(&storage_query).await?; - if result.is_none() { - Ok("".to_string()) - } else { - Ok(result.unwrap().to_value()?.to_string()) - } + let signer = create_signer(suri)?; + let tx = subxt::dynamic::tx(pallet_name, entry_name, args_value); + let signed_extrinsic: SubmittableExtrinsic> = + api.tx() + .create_signed(&tx, &signer, DefaultExtrinsicParamsBuilder::new().build()) + .await?; + Ok(encode_extrinsic(signed_extrinsic.encoded().to_vec())) } pub async fn submit_extrinsic( - pallet_name: &str, - entry_name: &str, - args: Vec, - url: &str, - suri: &str, + api: OnlineClient, + encoded_extrinsic: String, ) -> Result { - let args_value: Vec = args - .into_iter() - .filter_map(|v| match stringify::from_str(&v).0 { - Ok(value) => Some(value), - Err(_) => None, - }) - .collect(); - let api = OnlineClient::::from_url(url).await?; - let tx = subxt::dynamic::tx(pallet_name, entry_name, args_value); - let signer = create_signer(suri)?; - let result = api - .tx() - .sign_and_submit_then_watch_default(&tx, &signer) - .await? - .wait_for_finalized_success() - .await?; + let extrinsic = decode_extrinsic(encoded_extrinsic)?; + let signed_extrinsic: SubmittableExtrinsic> = + SubmittableExtrinsic::from_bytes(api, extrinsic); + let result = signed_extrinsic.submit_and_watch().await?.wait_for_finalized().await?; Ok(result.extrinsic_hash().to_string()) } @@ -91,8 +139,9 @@ pub async fn parse_chain_metadata(metadata: Metadata) -> Result, Err docs: entry.docs().concat(), ty: match entry.entry_type() { StorageEntryType::Plain(value) => (*value, None), - StorageEntryType::Map { value_ty, key_ty, .. } => - (*value_ty, Some(*key_ty)), + StorageEntryType::Map { value_ty, key_ty, .. } => { + (*value_ty, Some(*key_ty)) + }, }, }) .collect() @@ -135,28 +184,52 @@ pub fn get_type_description( mod tests { use super::*; use anyhow::Result; + use pop_common::parse_account; - #[tokio::test] - async fn query_works() -> Result<()> { - let result = - query("Assets", "Asset", vec!["50".into()], "wss://rpc2.paseo.popnetwork.xyz").await?; - println!("{:?}", result); - // query("Nfts", "Collection", &metadata)?; - // query("Nfts", "NextCollectionId", &metadata)?; + // #[tokio::test] + // async fn query_works() -> Result<()> { + // let api = set_up_api("wss://rpc2.paseo.popnetwork.xyz").await?; + // let result = prepare_query(&api, "Assets", "Asset", vec!["50".into()]).await?; + // println!("{:?}", result); + // // query("Nfts", "Collection", &metadata)?; + // // query("Nfts", "NextCollectionId", &metadata)?; - Ok(()) - } + // Ok(()) + // } #[tokio::test] async fn extrinsic_works() -> Result<()> { - let result = query( - "Balances", - "TransferAllowDeath", - vec!["167Y1SbQrwQVNfkNUXtRkocfzVbaAHYjnZPkZRScWPQ46XDb".into(), "1".into()], - "wss://rpc2.paseo.popnetwork.xyz", + let api = set_up_api("ws://127.0.0.1:53677").await?; + // let result = prepare_extrinsic( + // &api, + // "Nfts", + // "mint", + // vec![ + // "1".into(), + // "1".into(), + // "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty".into(), + // "None".into(), + // ], + // "//Alice", + // ) + // .await?; + let bob = parse_account("5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty")?; + let result = prepare_extrinsic( + &api, + "Assets", + "create", + vec![ + Value::u128(3), + Value::unnamed_variant("Id", vec![Value::from_bytes(bob)]), + Value::u128(1000000), + ], + "//Alice", ) .await?; - println!("{:?}", result); + //println!("{:?}", result); + println!("{:?}", format!("0x{}", hex::encode(result))); + // let rs = submit_extrinsic(api, result).await?; + // println!("{:?}", rs); // query("Nfts", "Collection", &metadata)?; // query("Nfts", "NextCollectionId", &metadata)?; diff --git a/crates/pop-parachains/src/errors.rs b/crates/pop-parachains/src/errors.rs index 7ddb9dd75..6c7b55b95 100644 --- a/crates/pop-parachains/src/errors.rs +++ b/crates/pop-parachains/src/errors.rs @@ -10,6 +10,8 @@ pub enum Error { Aborted, #[error("Anyhow error: {0}")] AnyhowError(#[from] anyhow::Error), + #[error("The `Call` property is missing from the call variant")] + CallMissing, #[error("{0}")] CommonError(#[from] pop_common::Error), #[error("Configuration error: {0}")] @@ -18,6 +20,8 @@ pub enum Error { CurrentDirAccess, #[error("Failed to parse the endowment value")] EndowmentError, + #[error("Failed decode a value")] + FromHexError(#[from] hex::FromHexError), #[error("IO error: {0}")] IO(#[from] std::io::Error), #[error("JSON error: {0}")] @@ -34,10 +38,14 @@ pub enum Error { OrchestratorError(#[from] OrchestratorError), #[error("Failed to create pallet directory")] PalletDirCreation, + #[error("The `Pallet` property is missing from the call variant")] + PalletMissing, #[error("Failed to find the pallet {0}")] PalletNotFound(String), #[error("Failed to parse the response")] ParsingResponseError(#[from] scale_decode::Error), + #[error("Failed to parse the argument {0}")] + ParsingValueError(String), #[error("Invalid path")] PathError, #[error("Failed to execute rustfmt")] diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index cb979b3fc..73604afff 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -16,14 +16,15 @@ pub use build::{ generate_plain_chain_spec, generate_raw_chain_spec, is_supported, ChainSpec, }; pub use call::{ - fetch_metadata, get_type_description, parse_chain_metadata, query, submit_extrinsic, + parse_string_into_scale_value, prepare_extrinsic, set_up_api, submit_extrinsic, Extrinsic, + Pallet, }; pub use errors::Error; pub use indexmap::IndexSet; pub use new_pallet::{create_pallet_template, new_pallet_options::*, TemplatePalletConfig}; pub use new_parachain::instantiate_template_dir; // External export from subxt. -pub use subxt::{dynamic::Value, Metadata}; +pub use subxt::{dynamic::Value, Metadata, OnlineClient, SubstrateConfig}; pub use templates::{Config, Parachain, Provider}; pub use up::Zombienet; pub use utils::helpers::is_initial_endowment_valid; From 1fb9cccc73f61fa2dec8f04e51aee8fcc8d84bf9 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 24 Sep 2024 16:17:05 +0200 Subject: [PATCH 048/211] fix: calls working --- Cargo.lock | 1 - Cargo.toml | 1 - crates/pop-cli/src/commands/call/mod.rs | 2 - crates/pop-cli/src/commands/call/parachain.rs | 244 +++++++++++++----- crates/pop-cli/src/commands/call/use_cases.rs | 113 -------- crates/pop-parachains/Cargo.toml | 1 - crates/pop-parachains/src/call.rs | 230 +++++++++-------- crates/pop-parachains/src/lib.rs | 5 +- 8 files changed, 308 insertions(+), 289 deletions(-) delete mode 100644 crates/pop-cli/src/commands/call/use_cases.rs diff --git a/Cargo.lock b/Cargo.lock index 4e6378f39..ad6441d00 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4774,7 +4774,6 @@ dependencies = [ "reqwest 0.12.5", "scale-info", "scale-typegen-description", - "scale-value", "serde_json", "strum 0.26.3", "strum_macros 0.26.4", diff --git a/Cargo.toml b/Cargo.toml index 41310f037..f8946eeb2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,6 @@ contract-extrinsics = "5.0.0-alpha" contract-transcode = "5.0.0-alpha" scale-info = { version = "2.11.3", default-features = false, features = ["derive"] } scale-typegen-description = "0.8.0" -scale-value = { version = "0.16.2", default-features = false } heck = "0.5.0" hex = { version = "0.4.3", default-features = false } diff --git a/crates/pop-cli/src/commands/call/mod.rs b/crates/pop-cli/src/commands/call/mod.rs index 04f70634b..241ebbdb9 100644 --- a/crates/pop-cli/src/commands/call/mod.rs +++ b/crates/pop-cli/src/commands/call/mod.rs @@ -6,8 +6,6 @@ use clap::{Args, Subcommand}; pub(crate) mod contract; #[cfg(feature = "parachain")] pub(crate) mod parachain; -#[cfg(feature = "parachain")] -pub(crate) mod use_cases; /// Arguments for calling a smart contract. #[derive(Args)] diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index fd974a877..846bc64d9 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -3,16 +3,16 @@ use crate::cli::{self, traits::*}; use anyhow::Result; use clap::Args; +use pop_common::parse_account; use pop_parachains::{ - prepare_extrinsic, set_up_api, submit_extrinsic, OnlineClient, Pallet, SubstrateConfig, + prepare_extrinsic, set_up_api, submit_extrinsic, Extrinsic, OnlineClient, Pallet, + SubstrateConfig, Value, }; use strum::VariantArray; -use super::use_cases::prompt_arguments; - #[derive(Args, Clone)] pub struct CallParachainCommand { - /// The signed extrinsic to call. + /// The signed extrinsic to submit. #[clap(long, short)] extrinsic: Option, /// Websocket endpoint of a node. @@ -25,6 +25,9 @@ pub struct CallParachainCommand { /// - with a password "//Alice///SECRET_PASSWORD" #[clap(name = "suri", long, short, default_value = "//Alice")] suri: String, + // pallet: Option, + // ext: Option, + // args: Option>, } impl CallParachainCommand { @@ -48,8 +51,8 @@ impl CallParachainCommand { let url: String = if self.extrinsic.is_none() { // Prompt for contract location. cli.input("Which chain would you like to interact with?") - .placeholder("wss://rpc1.paseo.popnetwork.xyz") - .default_input("wss://rpc1.paseo.popnetwork.xyz") + .placeholder("ws://127.0.0.1:53677") + .default_input("ws://127.0.0.1:53677") .interact()? } else { self.url.clone() @@ -63,7 +66,7 @@ impl CallParachainCommand { if let Some(extrinsic) = &self.extrinsic { full_message.push_str(&format!(" --extrinsic {}", extrinsic)); } - full_message.push_str(&format!("--url {}", self.url)); + full_message.push_str(&format!(" --url {}", self.url)); if !self.suri.is_empty() { full_message.push_str(&format!(" --suri {}", self.suri)); } @@ -77,51 +80,37 @@ async fn guide_user_to_call_chain( url: String, cli: &mut impl cli::traits::Cli, ) -> anyhow::Result { - let pallets = Pallet::VARIANTS; - let pallet = { - let mut prompt = cli.select("Select the pallet to call:"); - for pallet_item in pallets { - prompt = prompt.item(pallet_item.clone(), pallet_item.as_ref(), ""); - } - prompt.interact()? - }; + // let pallets = Pallet::VARIANTS; + // let pallet = { + // let mut prompt = cli.select("Select the pallet to call:"); + // for pallet_item in pallets { + // prompt = prompt.item(pallet_item.clone(), pallet_item.as_ref(), ""); + // } + // prompt.interact()? + // }; let extrinsic = { let mut prompt_extrinsic = cli.select("Select the extrinsic to call:"); - for extrinsic in pallet.extrinsics() { - prompt_extrinsic = - prompt_extrinsic.item(extrinsic.clone(), format!("{}\n", &extrinsic.as_ref()), ""); - } - Ok(CallParachainCommand { - pallet: Some(pallet.label), - extrinsic: Some(extrinsic.name), - query: None, - args, - url: "wss://rpc2.paseo.popnetwork.xyz".to_string(), - suri: "//Alice".to_string(), - }) - } else { - let query = { - let mut prompt_storage = cli.select("Select the storage to query:"); - for storage in pallet.storage { - prompt_storage = prompt_storage.item(storage.clone(), &storage.name, &storage.docs); - } - prompt_storage.interact()? - }; - let keys_needed = get_type_description(query.ty.1, metadata)?; - for key in keys_needed { - let value = cli.input(&format!("Enter the key '{}':", key)).interact()?; - args.push(value); + //for extrinsic in pallet.extrinsics() { + for extrinsic in Extrinsic::VARIANTS { + prompt_extrinsic = prompt_extrinsic.item( + extrinsic.clone(), + extrinsic.description(), + extrinsic.pallet(), + ); } - Ok(CallParachainCommand { - pallet: Some(pallet.label), - extrinsic: None, - query: Some(query.name), - args, - url: "wss://rpc2.paseo.popnetwork.xyz".to_string(), - suri: "//Alice".to_string(), - }) - } + prompt_extrinsic.interact()? + }; + let args = prompt_arguments(&extrinsic, cli)?; + let suri = cli::Cli + .input("Who is going to sign the extrinsic:") + .placeholder("//Alice") + .default_input("//Alice") + .interact()?; + // TODO: Handle error + let encoded_call_data = + prepare_extrinsic(api, extrinsic.pallet(), extrinsic.extrinsic_name(), args, &suri).await?; + Ok(CallParachainCommand { extrinsic: Some(encoded_call_data), url, suri }) } /// Executes the extrinsic or query. @@ -135,12 +124,26 @@ async fn execute_extrinsic( let extrinsic = call_config .extrinsic .expect("extrinsic can not be none as fallback above is interactive input; qed"); - if !cli.confirm("Do you want to sign and submit the call?").interact()? { + if !cli.confirm("Do you want to submit the call?").interact()? { display_message(&format!("Extrinsic: {} not submitted", extrinsic), true, cli)?; return Ok(()); } + let spinner = cliclack::spinner(); + spinner.start("Submitting the extrinsic..."); // TODO: Handle error - let result = submit_extrinsic(api.clone(), extrinsic).await?; + match submit_extrinsic(api.clone(), extrinsic).await { + Ok(result) => { + display_message( + &format!("Extrinsic submitted successfully with hash: {:?}", result), + true, + cli, + )?; + }, + Err(e) => { + display_message(&format!("Error submitting extrinsic: {}", e), false, cli)?; + }, + } + spinner.stop("message"); // Repeat call. if prompt_to_repeat_call { let another_call: bool = cli @@ -152,19 +155,7 @@ async fn execute_extrinsic( console::Term::stderr().clear_last_lines(2)?; let new_call_config = guide_user_to_call_chain(&api, call_config.url, cli).await?; Box::pin(execute_extrinsic(api, new_call_config, prompt_to_repeat_call, cli)).await?; - } else { - display_message( - &format!("Extrinsic submitted successfully with hash: {}", result), - true, - cli, - )?; } - } else { - display_message( - &format!("Extrinsic submitted successfully with hash: {}", result), - true, - cli, - )?; } Ok(()) } @@ -177,3 +168,132 @@ fn display_message(message: &str, success: bool, cli: &mut impl cli::traits::Cli } Ok(()) } +// Prompt the user to select an operation. +fn prompt_arguments(extrinsic: &Extrinsic, cli: &mut impl cli::traits::Cli) -> Result> { + let mut args: Vec = Vec::new(); + match extrinsic { + Extrinsic::CreateAsset => { + args.push(prompt_for_numeric_value("Enter the Asset ID", cli)?); + args.push(prompt_for_account("Enter the Admin Address", cli)?); + args.push(prompt_for_numeric_value("Enter the Minimum Balance", cli)?); + // TODO: ADD METEDATA + }, + Extrinsic::MintAsset => { + args.push(prompt_for_numeric_value("Enter the Asset ID", cli)?); + args.push(prompt_for_account("Enter the Beneficiary Address", cli)?); + args.push(prompt_for_numeric_value("Enter the Amount", cli)?); + }, + Extrinsic::CreateCollection => { + args.push(prompt_for_account("Enter the Admin Address", cli)?); + args.push(prompt_for_collection_config(cli)?); + }, + Extrinsic::MintNFT => { + args.push(prompt_for_numeric_value("Enter the Collection ID", cli)?); + args.push(prompt_for_numeric_value("Enter the Item ID", cli)?); + args.push(prompt_for_account("Enter the Beneficiary Address", cli)?); + args.push(prompt_for_witness_data(cli)?); + }, + Extrinsic::Transfer => { + args.push(prompt_for_account("Enter the Destination Address", cli)?); + args.push(prompt_for_numeric_value("Enter the Amount", cli)?); + }, + } + Ok(args) +} +fn prompt_for_numeric_value(message: &str, cli: &mut impl cli::traits::Cli) -> Result { + let id = cli + .input(message) + .placeholder("0") + .default_input("0") + .validate(|input: &String| match input.parse::() { + Ok(_) => Ok(()), + Err(_) => Err("Invalid value."), + }) + .required(true) + .interact()?; + Ok(Value::u128(id.parse::()?)) +} +fn prompt_for_account(message: &str, cli: &mut impl cli::traits::Cli) -> Result { + let account: String = cli + .input(message) + .placeholder("e.g. 5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") + .required(true) + .interact()?; + let account_id = parse_account(&account)?; + // TODO: Support other Adresses? Let the user pick Id, Address, or Index + Ok(Value::unnamed_variant("Id", vec![Value::from_bytes(account_id)])) +} +fn prompt_for_numeric_optional_value( + message: &str, + cli: &mut impl cli::traits::Cli, +) -> Result { + let value = cli + .input(message) + .placeholder("0 or (empty for None)") + .validate(|input: &String| match input.parse::() { + Ok(_) => Ok(()), + Err(_) => { + if input.is_empty() || input == "None" { + Ok(()) + } else { + Err("Invalid value.") + } + }, + }) + .required(false) + .interact()?; + if value.is_empty() || value == "None" { + Ok(Value::unnamed_variant("None", vec![])) + } else { + Ok(Value::unnamed_variant("Some", vec![Value::u128(value.parse::()?)])) + } +} +fn prompt_for_variant_value( + message: &str, + default_value: &str, + cli: &mut impl cli::traits::Cli, +) -> Result { + let mint_type: String = cli + .input(message) + .placeholder(&format!("e.g. {}", default_value)) + .default_input(default_value) + .required(true) + .interact()?; + Ok(Value::unnamed_variant(mint_type, vec![])) +} +fn prompt_for_collection_config(cli: &mut impl cli::traits::Cli) -> Result { + cli.info("Enter the Pallet NFT Collection Config:")?; + let settings = prompt_for_numeric_value("Collection's Settings", cli)?; + let max_supply = prompt_for_numeric_optional_value("Collection's Max Supply", cli)?; + cli.info("Enter the Mint Settings:")?; + let mint_type = prompt_for_variant_value("Who can mint?", "Issuer", cli)?; + let price_per_mint = prompt_for_numeric_optional_value("Price per mint", cli)?; + let start_block = prompt_for_numeric_optional_value("When the mint starts", cli)?; + let end_block = prompt_for_numeric_optional_value("When the mint ends", cli)?; + let default_item_settings = prompt_for_numeric_value("Default Item Settings", cli)?; + // mint settings + let mint_settings = Value::unnamed_composite(vec![ + mint_type, + price_per_mint, + start_block, + end_block, + default_item_settings, + ]); + let config_collection = Value::unnamed_composite(vec![settings, max_supply, mint_settings]); + + Ok(config_collection) +} +fn prompt_for_witness_data(cli: &mut impl cli::traits::Cli) -> Result { + if cli + .confirm("Do you want to enter witness data for mint") + .initial_value(false) + .interact()? + { + let owned_item = + prompt_for_numeric_optional_value("Id of the item in a required collection:", cli)?; + let mint_price = prompt_for_numeric_optional_value("Mint price:", cli)?; + Ok(Value::unnamed_variant("Some".to_string(), vec![owned_item, mint_price])) + } else { + Ok(Value::unnamed_variant("None", vec![])) + } +} diff --git a/crates/pop-cli/src/commands/call/use_cases.rs b/crates/pop-cli/src/commands/call/use_cases.rs deleted file mode 100644 index 0723a0bb5..000000000 --- a/crates/pop-cli/src/commands/call/use_cases.rs +++ /dev/null @@ -1,113 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 - -use crate::cli::{ - self, - traits::{Confirm as _, Input as _}, -}; -use anyhow::Result; -use pop_common::parse_account; -use pop_parachains::{parse_string_into_scale_value, Extrinsic, Pallet, Value}; - -/// Prompt the user to select an operation. -pub fn prompt_arguments( - extrinsic: &Extrinsic, - cli: &mut impl cli::traits::Cli, -) -> Result> { - match extrinsic { - Extrinsic::CreateAsset => prompt_query_params(&extrinsic, cli), - Extrinsic::MintAsset => prompt_query_params(&extrinsic, cli), - Extrinsic::CreateCollection => prompt_query_params(&extrinsic, cli), - Extrinsic::MintNFT => prompt_query_params(&extrinsic, cli), - Extrinsic::Transfer => prompt_query_params(&extrinsic, cli), - } -} -fn prompt_query_params( - extrinsic: &Extrinsic, - cli: &mut impl cli::traits::Cli, -) -> Result> { - let mut args: Vec = Vec::new(); - let id = cli - .input(&format!( - "Enter the {} id", - if extrinsic.pallet()? == Pallet::Assets.to_string() { "Asset" } else { "Collection" } - )) - .placeholder("0") - .required(false) - .interact()?; - args.push(parse_string_into_scale_value(&id)?); - // if call == &Call::NFTItem { - // let nft_id = cli - // .input("Enter the Nft id") - // .placeholder("0 or None") - // .required(false) - // .interact()?; - // args.push(parse_string_into_scale_value(&nft_id)?); - // } - Ok(args) -} -fn prompt_mint_params( - extrinsic: &Extrinsic, - cli: &mut impl cli::traits::Cli, -) -> Result> { - let mut args: Vec = Vec::new(); - let id = cli - .input(&format!( - "Enter the {} id", - if extrinsic.pallet()? == Pallet::Assets.to_string() { "Asset" } else { "Collection" } - )) - .placeholder("0") - .required(true) - .interact()?; - args.push(parse_string_into_scale_value(&id)?); - if extrinsic == &Extrinsic::MintNFT { - let nft_id = cli - .input(&format!( - "Enter the {} id", - if extrinsic.pallet()? == Pallet::Assets.to_string() { - "Asset" - } else { - "Collection" - } - )) - .placeholder("0") - .required(true) - .interact()?; - args.push(parse_string_into_scale_value(&nft_id)?); - } - // Prompt for beneficiary - let beneficiary: String = cli - .input("Enter the beneficiary address") - .placeholder("e.g. 5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") - .required(true) - .validate(|input: &String| match parse_account(input) { - Ok(_) => Ok(()), - Err(_) => Err("Invalid address."), - }) - .interact()?; - args.push(parse_string_into_scale_value(&beneficiary)?); - // if extrinsic == &Extrinsic::AssetItem { - // let amount = cli.input("Enter the amount").placeholder("0").required(true).interact()?; - // args.push(parse_string_into_scale_value(&amount)?); - // } - if extrinsic == &Extrinsic::MintNFT { - if cli - .confirm("Do you want to include witness data?") - .initial_value(false) - .interact()? - { - let config = {}; - let owned_item = cli - .input("Id of the item in a required collection:") - .placeholder("0 or None") - .required(false) - .interact()?; - let owned_item = cli - .input("The price specified in mint settings:") - .placeholder("0 or None") - .required(false) - .interact()?; - //args.push(Value::u128(id)); - } - } - Ok(args) -} diff --git a/crates/pop-parachains/Cargo.toml b/crates/pop-parachains/Cargo.toml index c590ad3dc..2c1d25fa7 100644 --- a/crates/pop-parachains/Cargo.toml +++ b/crates/pop-parachains/Cargo.toml @@ -29,7 +29,6 @@ indexmap.workspace = true reqwest.workspace = true scale-info.workspace = true scale-typegen-description.workspace = true -scale-value = { workspace = true } subxt.workspace = true symlink.workspace = true toml_edit.workspace = true diff --git a/crates/pop-parachains/src/call.rs b/crates/pop-parachains/src/call.rs index f7df6175a..e4aff734f 100644 --- a/crates/pop-parachains/src/call.rs +++ b/crates/pop-parachains/src/call.rs @@ -1,12 +1,16 @@ // SPDX-License-Identifier: GPL-3.0 use crate::errors::Error; +use clap::builder::Str; use pop_common::create_signer; -use scale_value::{stringify, Value}; +use scale_info::form::PortableForm; use strum::{EnumMessage as _, EnumProperty as _, VariantArray as _}; use strum_macros::{AsRefStr, Display, EnumMessage, EnumProperty, EnumString, VariantArray}; use subxt::{ - config::DefaultExtrinsicParamsBuilder, tx::SubmittableExtrinsic, OnlineClient, SubstrateConfig, + config::DefaultExtrinsicParamsBuilder, + dynamic::Value, + tx::{Payload, SubmittableExtrinsic}, + OnlineClient, SubstrateConfig, }; /// A supported pallet. #[derive(AsRefStr, Clone, Debug, Display, EnumMessage, EnumString, Eq, PartialEq, VariantArray)] @@ -44,59 +48,70 @@ impl Pallet { VariantArray, )] pub enum Extrinsic { - #[strum(serialize = "create_asset", message = "create", props(Pallet = "Assets"))] + #[strum( + serialize = "create_asset", + message = "create", + detailed_message = "Create an Asset", + props(Pallet = "Assets") + )] CreateAsset, - #[strum(serialize = "mint_asset", message = "mint", props(Pallet = "Asset"))] + #[strum( + serialize = "mint_asset", + message = "mint", + detailed_message = "Mint an Asset", + props(Pallet = "Assets") + )] MintAsset, - #[strum(serialize = "create_nft", message = "create", props(Pallet = "Nfts"))] + #[strum( + serialize = "create_nft", + message = "create", + detailed_message = "Create a NFT Collection", + props(Pallet = "Nfts") + )] CreateCollection, - #[strum(serialize = "mint", message = "mint", props(Pallet = "Nft"))] + #[strum( + serialize = "mint", + message = "mint", + detailed_message = "Mint a NFT", + props(Pallet = "Nfts") + )] MintNFT, - #[strum(serialize = "transfer", message = "transfer_allow_death", props(Pallet = "Balances"))] + #[strum( + serialize = "transfer", + message = "transfer_allow_death", + detailed_message = "Transfer", + props(Pallet = "Balances") + )] Transfer, } impl Extrinsic { /// Get the template's name. - fn extrinsic_name(&self) -> &str { + pub fn extrinsic_name(&self) -> &str { self.get_message().unwrap_or_default() } + /// Get the description of the extrinsic. + pub fn description(&self) -> &str { + self.get_detailed_message().unwrap_or_default() + } + /// Get the pallet of the extrinsic. - pub fn pallet(&self) -> Result<&str, Error> { - self.get_str("Pallet").ok_or(Error::PalletMissing) + pub fn pallet(&self) -> &str { + self.get_str("Pallet").unwrap_or_default() } } -pub fn parse_string_into_scale_value(str: &str) -> Result { - let value = stringify::from_str(str) - .0 - .map_err(|_| Error::ParsingValueError(str.to_string()))?; - Ok(value) -} +// pub fn parse_string_into_scale_value(str: &str) -> Result { +// let value = stringify::from_str(str) +// .0 +// .map_err(|_| Error::ParsingValueError(str.to_string()))?; +// Ok(value) +// } pub async fn set_up_api(url: &str) -> Result, Error> { let api = OnlineClient::::from_url(url).await?; Ok(api) } -// pub async fn prepare_query( -// api: &OnlineClient, -// pallet_name: &str, -// entry_name: &str, -// args: Vec, -// ) -> Result, Error> { -// //let args = convert_vec(args_value)?; -// let storage = subxt::dynamic::storage(pallet_name, entry_name, args); -// let addr_bytes = api.storage().address_bytes(&storage)?; -// let result = api.storage().at_latest().await?.fetch(&storage).await?; -// Ok(addr_bytes) -// } -fn encode_extrinsic(encoded_call_data: Vec) -> String { - format!("0x{}", hex::encode(encoded_call_data)) -} -fn decode_extrinsic(encoded_call_data: String) -> Result, Error> { - let hex_data = encoded_call_data.trim_start_matches("0x"); - Ok(hex::decode(hex_data)?) -} pub async fn prepare_extrinsic( api: &OnlineClient, pallet_name: &str, @@ -120,71 +135,55 @@ pub async fn submit_extrinsic( let extrinsic = decode_extrinsic(encoded_extrinsic)?; let signed_extrinsic: SubmittableExtrinsic> = SubmittableExtrinsic::from_bytes(api, extrinsic); - let result = signed_extrinsic.submit_and_watch().await?.wait_for_finalized().await?; + let result = signed_extrinsic.submit_and_watch().await?; Ok(result.extrinsic_hash().to_string()) } -pub async fn parse_chain_metadata(metadata: Metadata) -> Result, Error> { - let mut pallets: Vec = Vec::new(); - for pallet in metadata.pallets() { - let extrinsics = - pallet.call_variants().map(|variants| variants.to_vec()).unwrap_or_default(); // Return an empty Vec if Option is None - let storage: Vec = pallet - .storage() - .map(|m| { - m.entries() - .iter() - .map(|entry| Storage { - name: entry.name().to_string(), - docs: entry.docs().concat(), - ty: match entry.entry_type() { - StorageEntryType::Plain(value) => (*value, None), - StorageEntryType::Map { value_ty, key_ty, .. } => { - (*value_ty, Some(*key_ty)) - }, - }, - }) - .collect() - }) - .unwrap_or_default(); // Return an empty Vec if Option is None - - pallets.push(Pallet { - label: pallet.name().to_string(), - extrinsics, - docs: pallet.docs().join(" "), - storage, - }); - } - Ok(pallets) +fn encode_extrinsic(encoded_call_data: Vec) -> String { + format!("0x{}", hex::encode(encoded_call_data)) +} +fn decode_extrinsic(encoded_call_data: String) -> Result, Error> { + let hex_data = encoded_call_data.trim_start_matches("0x"); + Ok(hex::decode(hex_data)?) } -pub fn get_type_description( - key_ty_id: Option, - metadata: &Metadata, -) -> Result, Error> { - if let Some(key_ty_id) = key_ty_id { - let key_ty_description = type_description(key_ty_id, metadata.types(), false)?; - let result = key_ty_description.trim().trim_matches(|c| c == '(' || c == ')'); - - let parsed_result: Vec = if result == "\"\"" { - vec![] - } else if !result.contains(',') { - vec![result.to_string()] - } else { - result.split(',').map(|s| s.trim().to_string()).collect() - }; - - Ok(parsed_result) - } else { - Ok(vec![]) - } +pub fn fetch_types( + api: &OnlineClient, + pallet_name: &str, + extrinsic: &str, +) -> Result { + let metadata = api.metadata(); + let pallet_metadata = metadata + .pallet_by_name(pallet_name) + .ok_or(Error::PalletNotFound(pallet_name.to_string()))?; + let extrinsic_metadata = pallet_metadata + .call_variant_by_name(extrinsic) + .ok_or(Error::PalletNotFound(pallet_name.to_string()))?; + //println!("{:?}", extrinsic_metadata.fields); + Ok("".to_string()) } #[cfg(test)] mod tests { + use std::vec; + use super::*; use anyhow::Result; use pop_common::parse_account; + use subxt::ext::{ + scale_encode::EncodeAsType, + scale_value::{self, value, Composite, Variant}, + }; + + #[tokio::test] + async fn fetch_works() -> Result<()> { + let api = set_up_api("ws://127.0.0.1:53677").await?; + let a = fetch_types(&api, "Nfts", "mint")?; + let me = api.metadata(); + let ty = me.types().resolve(279); + println!("TYPE {:?}", ty); + Ok(()) + } // #[tokio::test] // async fn query_works() -> Result<()> { @@ -196,40 +195,61 @@ mod tests { // Ok(()) // } - #[tokio::test] async fn extrinsic_works() -> Result<()> { let api = set_up_api("ws://127.0.0.1:53677").await?; + let bob = parse_account("5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty")?; + let alice = parse_account("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY")?; + let owned_item = Value::unnamed_variant("Some".to_string(), vec![Value::u128(1)]); + let mint_price = Value::unnamed_variant("Some".to_string(), vec![Value::u128(1)]); + let mint_witness = Value::unnamed_composite(vec![owned_item, mint_price]); + + let some = Value::unnamed_variant("Some".to_string(), vec![mint_witness]); + + let ni = Value::unnamed_variant( + "None", + vec![], // No fields for `None` + ); // let result = prepare_extrinsic( // &api, // "Nfts", // "mint", // vec![ - // "1".into(), - // "1".into(), - // "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty".into(), - // "None".into(), + // Value::u128(1), + // Value::u128(1), + // Value::unnamed_variant("Id", vec![Value::from_bytes(bob)]), + // ni, // ], // "//Alice", // ) // .await?; - let bob = parse_account("5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty")?; - let result = prepare_extrinsic( + + let max_supply = Value::unnamed_variant("Some".to_string(), vec![Value::u128(1)]); + let mint_type = Value::unnamed_variant("Issuer".to_string(), vec![]); + let price = Value::unnamed_variant("Some".to_string(), vec![Value::u128(1)]); + let start_block = Value::unnamed_variant("Some".to_string(), vec![Value::u128(1)]); + let end_block = Value::unnamed_variant("Some".to_string(), vec![Value::u128(1)]); + let mint_settings = Value::unnamed_composite(vec![ + mint_type, + price, + start_block, + end_block, + Value::u128(1), + ]); + let config_collection = + Value::unnamed_composite(vec![Value::u128(1), max_supply, mint_settings]); + let result2 = prepare_extrinsic( &api, - "Assets", + "Nfts", "create", - vec![ - Value::u128(3), - Value::unnamed_variant("Id", vec![Value::from_bytes(bob)]), - Value::u128(1000000), - ], + vec![Value::unnamed_variant("Id", vec![Value::from_bytes(alice)]), config_collection], "//Alice", ) .await?; - //println!("{:?}", result); - println!("{:?}", format!("0x{}", hex::encode(result))); - // let rs = submit_extrinsic(api, result).await?; - // println!("{:?}", rs); + //println!("{:?}", format!("0x{}", hex::encode(result))); + println!("{:?}", format!("0x{}", hex::encode(result2))); + //let rs = submit_extrinsic(api, result).await?; + //println!("{:?}", rs); // query("Nfts", "Collection", &metadata)?; // query("Nfts", "NextCollectionId", &metadata)?; diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index 73604afff..1a4052851 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -15,10 +15,7 @@ pub use build::{ binary_path, build_parachain, export_wasm_file, generate_genesis_state_file, generate_plain_chain_spec, generate_raw_chain_spec, is_supported, ChainSpec, }; -pub use call::{ - parse_string_into_scale_value, prepare_extrinsic, set_up_api, submit_extrinsic, Extrinsic, - Pallet, -}; +pub use call::{prepare_extrinsic, set_up_api, submit_extrinsic, Extrinsic, Pallet}; pub use errors::Error; pub use indexmap::IndexSet; pub use new_pallet::{create_pallet_template, new_pallet_options::*, TemplatePalletConfig}; From ee819b018135b2ff8630b824d4ec543f391cd4a8 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 24 Sep 2024 17:31:59 +0200 Subject: [PATCH 049/211] refactor: remove unused code --- crates/pop-cli/src/commands/call/parachain.rs | 48 ++++--- crates/pop-parachains/src/call.rs | 120 ++++++------------ crates/pop-parachains/src/lib.rs | 4 +- 3 files changed, 63 insertions(+), 109 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 846bc64d9..a01c26c05 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -1,14 +1,13 @@ // SPDX-License-Identifier: GPL-3.0 use crate::cli::{self, traits::*}; -use anyhow::Result; +use anyhow::{anyhow, Result}; use clap::Args; use pop_common::parse_account; use pop_parachains::{ - prepare_extrinsic, set_up_api, submit_extrinsic, Extrinsic, OnlineClient, Pallet, + prepare_extrinsic, set_up_api, submit_extrinsic, supported_extrinsics, Extrinsic, OnlineClient, SubstrateConfig, Value, }; -use strum::VariantArray; #[derive(Args, Clone)] pub struct CallParachainCommand { @@ -16,7 +15,7 @@ pub struct CallParachainCommand { #[clap(long, short)] extrinsic: Option, /// Websocket endpoint of a node. - #[clap(name = "url", long, value_parser, default_value = "ws://localhost:9944")] + #[clap(name = "url", long, value_parser, default_value = "ws://127.0.0.1:9944")] url: String, /// Secret key URI for the account signing the extrinsic. /// @@ -25,9 +24,6 @@ pub struct CallParachainCommand { /// - with a password "//Alice///SECRET_PASSWORD" #[clap(name = "suri", long, short, default_value = "//Alice")] suri: String, - // pallet: Option, - // ext: Option, - // args: Option>, } impl CallParachainCommand { @@ -35,7 +31,13 @@ impl CallParachainCommand { pub(crate) async fn execute(mut self) -> Result<()> { let (api, url) = self.set_up_api(&mut cli::Cli).await?; let call_config = if self.extrinsic.is_none() { - guide_user_to_call_chain(&api, url, &mut cli::Cli).await? + match guide_user_to_call_chain(&api, url, &mut cli::Cli).await { + Ok(call_config) => call_config, + Err(e) => { + display_message(&format!("{}", e), false, &mut cli::Cli)?; + return Ok(()); + }, + } } else { self.clone() }; @@ -51,8 +53,8 @@ impl CallParachainCommand { let url: String = if self.extrinsic.is_none() { // Prompt for contract location. cli.input("Which chain would you like to interact with?") - .placeholder("ws://127.0.0.1:53677") - .default_input("ws://127.0.0.1:53677") + .placeholder("wss://rpc1.paseo.popnetwork.xyz") + .default_input("wss://rpc1.paseo.popnetwork.xyz") .interact()? } else { self.url.clone() @@ -80,19 +82,10 @@ async fn guide_user_to_call_chain( url: String, cli: &mut impl cli::traits::Cli, ) -> anyhow::Result { - // let pallets = Pallet::VARIANTS; - // let pallet = { - // let mut prompt = cli.select("Select the pallet to call:"); - // for pallet_item in pallets { - // prompt = prompt.item(pallet_item.clone(), pallet_item.as_ref(), ""); - // } - // prompt.interact()? - // }; - let extrinsic = { let mut prompt_extrinsic = cli.select("Select the extrinsic to call:"); //for extrinsic in pallet.extrinsics() { - for extrinsic in Extrinsic::VARIANTS { + for extrinsic in supported_extrinsics(api) { prompt_extrinsic = prompt_extrinsic.item( extrinsic.clone(), extrinsic.description(), @@ -107,10 +100,14 @@ async fn guide_user_to_call_chain( .placeholder("//Alice") .default_input("//Alice") .interact()?; - // TODO: Handle error - let encoded_call_data = - prepare_extrinsic(api, extrinsic.pallet(), extrinsic.extrinsic_name(), args, &suri).await?; - Ok(CallParachainCommand { extrinsic: Some(encoded_call_data), url, suri }) + + match prepare_extrinsic(api, extrinsic.pallet(), extrinsic.extrinsic_name(), args, &suri).await + { + Ok(encoded_call_data) => { + Ok(CallParachainCommand { extrinsic: Some(encoded_call_data), url, suri }) + }, + Err(e) => Err(anyhow!(format!("{}", e.to_string()))), + } } /// Executes the extrinsic or query. @@ -140,10 +137,9 @@ async fn execute_extrinsic( )?; }, Err(e) => { - display_message(&format!("Error submitting extrinsic: {}", e), false, cli)?; + display_message(&format!("{}", e), false, cli)?; }, } - spinner.stop("message"); // Repeat call. if prompt_to_repeat_call { let another_call: bool = cli diff --git a/crates/pop-parachains/src/call.rs b/crates/pop-parachains/src/call.rs index e4aff734f..b45846c73 100644 --- a/crates/pop-parachains/src/call.rs +++ b/crates/pop-parachains/src/call.rs @@ -1,39 +1,13 @@ // SPDX-License-Identifier: GPL-3.0 use crate::errors::Error; -use clap::builder::Str; use pop_common::create_signer; -use scale_info::form::PortableForm; use strum::{EnumMessage as _, EnumProperty as _, VariantArray as _}; use strum_macros::{AsRefStr, Display, EnumMessage, EnumProperty, EnumString, VariantArray}; use subxt::{ - config::DefaultExtrinsicParamsBuilder, - dynamic::Value, - tx::{Payload, SubmittableExtrinsic}, - OnlineClient, SubstrateConfig, + config::DefaultExtrinsicParamsBuilder, dynamic::Value, tx::SubmittableExtrinsic, OnlineClient, + SubstrateConfig, }; -/// A supported pallet. -#[derive(AsRefStr, Clone, Debug, Display, EnumMessage, EnumString, Eq, PartialEq, VariantArray)] -pub enum Pallet { - //Assets. - #[strum(serialize = "Assets")] - Assets, - //Balances. - #[strum(serialize = "Balances")] - Balances, - /// NFT. - #[strum(serialize = "Nfts")] - Nfts, -} -impl Pallet { - /// Get the list of extrinsics available. - pub fn extrinsics(&self) -> Vec<&Extrinsic> { - Extrinsic::VARIANTS - .iter() - .filter(|t| t.get_str("Pallet") == Some(self.as_ref())) - .collect() - } -} #[derive( AsRefStr, @@ -99,13 +73,12 @@ impl Extrinsic { self.get_str("Pallet").unwrap_or_default() } } - -// pub fn parse_string_into_scale_value(str: &str) -> Result { -// let value = stringify::from_str(str) -// .0 -// .map_err(|_| Error::ParsingValueError(str.to_string()))?; -// Ok(value) -// } +pub fn supported_extrinsics(api: &OnlineClient) -> Vec<&Extrinsic> { + Extrinsic::VARIANTS + .iter() + .filter(|t| extrinsic_is_supported(api, t.pallet(), t.extrinsic_name())) + .collect() +} pub async fn set_up_api(url: &str) -> Result, Error> { let api = OnlineClient::::from_url(url).await?; @@ -147,57 +120,42 @@ fn decode_extrinsic(encoded_call_data: String) -> Result, Error> { Ok(hex::decode(hex_data)?) } -pub fn fetch_types( +fn extrinsic_is_supported( api: &OnlineClient, pallet_name: &str, extrinsic: &str, -) -> Result { +) -> bool { let metadata = api.metadata(); - let pallet_metadata = metadata - .pallet_by_name(pallet_name) - .ok_or(Error::PalletNotFound(pallet_name.to_string()))?; - let extrinsic_metadata = pallet_metadata - .call_variant_by_name(extrinsic) - .ok_or(Error::PalletNotFound(pallet_name.to_string()))?; - //println!("{:?}", extrinsic_metadata.fields); - Ok("".to_string()) + // Try to get the pallet metadata by name + let pallet_metadata = match metadata.pallet_by_name(pallet_name) { + Some(pallet) => pallet, + None => return false, // Return false if pallet is not found + }; + // Try to get the extrinsic metadata by name from the pallet + match pallet_metadata.call_variant_by_name(extrinsic) { + Some(_) => true, // Return true if extrinsic is found + None => false, // Return false if extrinsic is not found + } } #[cfg(test)] mod tests { - use std::vec; - use super::*; use anyhow::Result; use pop_common::parse_account; - use subxt::ext::{ - scale_encode::EncodeAsType, - scale_value::{self, value, Composite, Variant}, - }; + use std::vec; #[tokio::test] - async fn fetch_works() -> Result<()> { - let api = set_up_api("ws://127.0.0.1:53677").await?; - let a = fetch_types(&api, "Nfts", "mint")?; - let me = api.metadata(); - let ty = me.types().resolve(279); - println!("TYPE {:?}", ty); + async fn extrinsic_is_supported_works() -> Result<()> { + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + assert!(extrinsic_is_supported(&api, "Nfts", "mint")); + assert!(!extrinsic_is_supported(&api, "Nfts", "mint_no_exist")); Ok(()) } - // #[tokio::test] - // async fn query_works() -> Result<()> { - // let api = set_up_api("wss://rpc2.paseo.popnetwork.xyz").await?; - // let result = prepare_query(&api, "Assets", "Asset", vec!["50".into()]).await?; - // println!("{:?}", result); - // // query("Nfts", "Collection", &metadata)?; - // // query("Nfts", "NextCollectionId", &metadata)?; - - // Ok(()) - // } #[tokio::test] async fn extrinsic_works() -> Result<()> { - let api = set_up_api("ws://127.0.0.1:53677").await?; + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; let bob = parse_account("5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty")?; let alice = parse_account("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY")?; let owned_item = Value::unnamed_variant("Some".to_string(), vec![Value::u128(1)]); @@ -210,19 +168,19 @@ mod tests { "None", vec![], // No fields for `None` ); - // let result = prepare_extrinsic( - // &api, - // "Nfts", - // "mint", - // vec![ - // Value::u128(1), - // Value::u128(1), - // Value::unnamed_variant("Id", vec![Value::from_bytes(bob)]), - // ni, - // ], - // "//Alice", - // ) - // .await?; + let result = prepare_extrinsic( + &api, + "Nfts", + "mint", + vec![ + Value::u128(1), + Value::u128(1), + Value::unnamed_variant("Id", vec![Value::from_bytes(bob)]), + ni, + ], + "//Alice", + ) + .await?; let max_supply = Value::unnamed_variant("Some".to_string(), vec![Value::u128(1)]); let mint_type = Value::unnamed_variant("Issuer".to_string(), vec![]); diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index 1a4052851..76c597692 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -15,13 +15,13 @@ pub use build::{ binary_path, build_parachain, export_wasm_file, generate_genesis_state_file, generate_plain_chain_spec, generate_raw_chain_spec, is_supported, ChainSpec, }; -pub use call::{prepare_extrinsic, set_up_api, submit_extrinsic, Extrinsic, Pallet}; +pub use call::{prepare_extrinsic, set_up_api, submit_extrinsic, supported_extrinsics, Extrinsic}; pub use errors::Error; pub use indexmap::IndexSet; pub use new_pallet::{create_pallet_template, new_pallet_options::*, TemplatePalletConfig}; pub use new_parachain::instantiate_template_dir; // External export from subxt. -pub use subxt::{dynamic::Value, Metadata, OnlineClient, SubstrateConfig}; +pub use subxt::{dynamic::Value, OnlineClient, SubstrateConfig}; pub use templates::{Config, Parachain, Provider}; pub use up::Zombienet; pub use utils::helpers::is_initial_endowment_valid; From aec3b29a653317c2b47a1a244805165887f2d7f8 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 24 Sep 2024 17:50:44 +0200 Subject: [PATCH 050/211] refactor: remove unused code --- crates/pop-parachains/src/call.rs | 61 ------------------------------- 1 file changed, 61 deletions(-) diff --git a/crates/pop-parachains/src/call.rs b/crates/pop-parachains/src/call.rs index b45846c73..76b47162a 100644 --- a/crates/pop-parachains/src/call.rs +++ b/crates/pop-parachains/src/call.rs @@ -152,65 +152,4 @@ mod tests { assert!(!extrinsic_is_supported(&api, "Nfts", "mint_no_exist")); Ok(()) } - - #[tokio::test] - async fn extrinsic_works() -> Result<()> { - let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; - let bob = parse_account("5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty")?; - let alice = parse_account("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY")?; - let owned_item = Value::unnamed_variant("Some".to_string(), vec![Value::u128(1)]); - let mint_price = Value::unnamed_variant("Some".to_string(), vec![Value::u128(1)]); - let mint_witness = Value::unnamed_composite(vec![owned_item, mint_price]); - - let some = Value::unnamed_variant("Some".to_string(), vec![mint_witness]); - - let ni = Value::unnamed_variant( - "None", - vec![], // No fields for `None` - ); - let result = prepare_extrinsic( - &api, - "Nfts", - "mint", - vec![ - Value::u128(1), - Value::u128(1), - Value::unnamed_variant("Id", vec![Value::from_bytes(bob)]), - ni, - ], - "//Alice", - ) - .await?; - - let max_supply = Value::unnamed_variant("Some".to_string(), vec![Value::u128(1)]); - let mint_type = Value::unnamed_variant("Issuer".to_string(), vec![]); - let price = Value::unnamed_variant("Some".to_string(), vec![Value::u128(1)]); - let start_block = Value::unnamed_variant("Some".to_string(), vec![Value::u128(1)]); - let end_block = Value::unnamed_variant("Some".to_string(), vec![Value::u128(1)]); - let mint_settings = Value::unnamed_composite(vec![ - mint_type, - price, - start_block, - end_block, - Value::u128(1), - ]); - let config_collection = - Value::unnamed_composite(vec![Value::u128(1), max_supply, mint_settings]); - let result2 = prepare_extrinsic( - &api, - "Nfts", - "create", - vec![Value::unnamed_variant("Id", vec![Value::from_bytes(alice)]), config_collection], - "//Alice", - ) - .await?; - //println!("{:?}", format!("0x{}", hex::encode(result))); - println!("{:?}", format!("0x{}", hex::encode(result2))); - //let rs = submit_extrinsic(api, result).await?; - //println!("{:?}", rs); - // query("Nfts", "Collection", &metadata)?; - // query("Nfts", "NextCollectionId", &metadata)?; - - Ok(()) - } } From 8e50d0b4c4dcfea8988702762c4ae71764510e87 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 24 Sep 2024 19:20:05 +0200 Subject: [PATCH 051/211] refactor: various fixes --- crates/pop-cli/src/commands/call/parachain.rs | 15 +++++++-------- crates/pop-common/src/lib.rs | 10 ++++------ crates/pop-parachains/src/call.rs | 10 ++++------ 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index a01c26c05..9d549ed19 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -103,9 +103,8 @@ async fn guide_user_to_call_chain( match prepare_extrinsic(api, extrinsic.pallet(), extrinsic.extrinsic_name(), args, &suri).await { - Ok(encoded_call_data) => { - Ok(CallParachainCommand { extrinsic: Some(encoded_call_data), url, suri }) - }, + Ok(encoded_call_data) => + Ok(CallParachainCommand { extrinsic: Some(encoded_call_data), url, suri }), Err(e) => Err(anyhow!(format!("{}", e.to_string()))), } } @@ -127,9 +126,9 @@ async fn execute_extrinsic( } let spinner = cliclack::spinner(); spinner.start("Submitting the extrinsic..."); - // TODO: Handle error match submit_extrinsic(api.clone(), extrinsic).await { Ok(result) => { + console::Term::stderr().clear_last_lines(1)?; display_message( &format!("Extrinsic submitted successfully with hash: {:?}", result), true, @@ -137,6 +136,7 @@ async fn execute_extrinsic( )?; }, Err(e) => { + console::Term::stderr().clear_last_lines(1)?; display_message(&format!("{}", e), false, cli)?; }, } @@ -172,7 +172,7 @@ fn prompt_arguments(extrinsic: &Extrinsic, cli: &mut impl cli::traits::Cli) -> R args.push(prompt_for_numeric_value("Enter the Asset ID", cli)?); args.push(prompt_for_account("Enter the Admin Address", cli)?); args.push(prompt_for_numeric_value("Enter the Minimum Balance", cli)?); - // TODO: ADD METEDATA + // TODO: ADD METADATA }, Extrinsic::MintAsset => { args.push(prompt_for_numeric_value("Enter the Asset ID", cli)?); @@ -228,13 +228,12 @@ fn prompt_for_numeric_optional_value( .placeholder("0 or (empty for None)") .validate(|input: &String| match input.parse::() { Ok(_) => Ok(()), - Err(_) => { + Err(_) => if input.is_empty() || input == "None" { Ok(()) } else { Err("Invalid value.") - } - }, + }, }) .required(false) .interact()?; diff --git a/crates/pop-common/src/lib.rs b/crates/pop-common/src/lib.rs index 332fdb79a..4e71a5794 100644 --- a/crates/pop-common/src/lib.rs +++ b/crates/pop-common/src/lib.rs @@ -40,18 +40,16 @@ pub fn target() -> Result<&'static str, Error> { } match ARCH { - "aarch64" => { + "aarch64" => return match OS { "macos" => Ok("aarch64-apple-darwin"), _ => Ok("aarch64-unknown-linux-gnu"), - } - }, - "x86_64" | "x86" => { + }, + "x86_64" | "x86" => return match OS { "macos" => Ok("x86_64-apple-darwin"), _ => Ok("x86_64-unknown-linux-gnu"), - } - }, + }, &_ => {}, } Err(Error::UnsupportedPlatform { arch: ARCH, os: OS }) diff --git a/crates/pop-parachains/src/call.rs b/crates/pop-parachains/src/call.rs index 76b47162a..066569883 100644 --- a/crates/pop-parachains/src/call.rs +++ b/crates/pop-parachains/src/call.rs @@ -109,7 +109,7 @@ pub async fn submit_extrinsic( let signed_extrinsic: SubmittableExtrinsic> = SubmittableExtrinsic::from_bytes(api, extrinsic); let result = signed_extrinsic.submit_and_watch().await?; - Ok(result.extrinsic_hash().to_string()) + Ok(format!("{:?}", result.extrinsic_hash())) } fn encode_extrinsic(encoded_call_data: Vec) -> String { @@ -132,18 +132,16 @@ fn extrinsic_is_supported( None => return false, // Return false if pallet is not found }; // Try to get the extrinsic metadata by name from the pallet - match pallet_metadata.call_variant_by_name(extrinsic) { - Some(_) => true, // Return true if extrinsic is found - None => false, // Return false if extrinsic is not found + if pallet_metadata.call_variant_by_name(extrinsic).is_some() { + return true; } + false } #[cfg(test)] mod tests { use super::*; use anyhow::Result; - use pop_common::parse_account; - use std::vec; #[tokio::test] async fn extrinsic_is_supported_works() -> Result<()> { From 5996ba2898ef792b5506d9d8c4a9cd0b361a0b30 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 24 Sep 2024 22:23:07 +0200 Subject: [PATCH 052/211] refactor: various fixes --- Cargo.lock | 25 ------------------------- Cargo.toml | 1 - crates/pop-parachains/Cargo.toml | 1 - crates/pop-parachains/src/call.rs | 2 +- 4 files changed, 1 insertion(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ad6441d00..aa0bf52c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4417,12 +4417,6 @@ dependencies = [ "password-hash", ] -[[package]] -name = "peekmore" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9163e1259760e83d528d1b3171e5100c1767f10c52e1c4d6afad26e63d47d758" - [[package]] name = "pem" version = "3.0.4" @@ -4773,7 +4767,6 @@ dependencies = [ "pop-common", "reqwest 0.12.5", "scale-info", - "scale-typegen-description", "serde_json", "strum 0.26.3", "strum_macros 0.26.4", @@ -5706,24 +5699,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "scale-typegen-description" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff4cabffc407f6378bc7a164fab8280bfcd862b2dd063cc5c9914a520ea8566" -dependencies = [ - "anyhow", - "peekmore", - "proc-macro2", - "quote", - "rand", - "rand_chacha", - "scale-info", - "scale-typegen", - "scale-value", - "smallvec", -] - [[package]] name = "scale-value" version = "0.16.2" diff --git a/Cargo.toml b/Cargo.toml index f8946eeb2..149be1bae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,7 +51,6 @@ contract-build = "5.0.0-alpha" contract-extrinsics = "5.0.0-alpha" contract-transcode = "5.0.0-alpha" scale-info = { version = "2.11.3", default-features = false, features = ["derive"] } -scale-typegen-description = "0.8.0" heck = "0.5.0" hex = { version = "0.4.3", default-features = false } diff --git a/crates/pop-parachains/Cargo.toml b/crates/pop-parachains/Cargo.toml index 2c1d25fa7..845944d65 100644 --- a/crates/pop-parachains/Cargo.toml +++ b/crates/pop-parachains/Cargo.toml @@ -28,7 +28,6 @@ askama.workspace = true indexmap.workspace = true reqwest.workspace = true scale-info.workspace = true -scale-typegen-description.workspace = true subxt.workspace = true symlink.workspace = true toml_edit.workspace = true diff --git a/crates/pop-parachains/src/call.rs b/crates/pop-parachains/src/call.rs index 066569883..efb964142 100644 --- a/crates/pop-parachains/src/call.rs +++ b/crates/pop-parachains/src/call.rs @@ -53,7 +53,7 @@ pub enum Extrinsic { #[strum( serialize = "transfer", message = "transfer_allow_death", - detailed_message = "Transfer", + detailed_message = "Transfer Balance", props(Pallet = "Balances") )] Transfer, From aef62b8453ef4751452227f9f479f78b815c49fe Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 27 Sep 2024 11:44:58 +0200 Subject: [PATCH 053/211] feat: add option to include params from command line --- crates/pop-cli/src/commands/call/parachain.rs | 245 +++++++++++------- crates/pop-contracts/src/utils/mod.rs | 4 +- crates/pop-parachains/src/call.rs | 233 ++++++++++++++--- crates/pop-parachains/src/errors.rs | 10 +- crates/pop-parachains/src/lib.rs | 7 +- 5 files changed, 364 insertions(+), 135 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 9d549ed19..884db4618 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -3,17 +3,22 @@ use crate::cli::{self, traits::*}; use anyhow::{anyhow, Result}; use clap::Args; -use pop_common::parse_account; use pop_parachains::{ - prepare_extrinsic, set_up_api, submit_extrinsic, supported_extrinsics, Extrinsic, OnlineClient, - SubstrateConfig, Value, + construct_extrinsic, encode_call_data, set_up_api, sign_and_submit_extrinsic, + supported_extrinsics, DynamicPayload, Extrinsic, OnlineClient, SubstrateConfig, }; #[derive(Args, Clone)] pub struct CallParachainCommand { - /// The signed extrinsic to submit. + /// The name of the pallet to call. + #[clap(long, short)] + pallet: Option, + /// The name of the extrinsic to submit. #[clap(long, short)] extrinsic: Option, + /// The constructor arguments, encoded as strings. + #[clap(long, num_args = 0..)] + args: Vec, /// Websocket endpoint of a node. #[clap(name = "url", long, value_parser, default_value = "ws://127.0.0.1:9944")] url: String, @@ -30,8 +35,9 @@ impl CallParachainCommand { /// Executes the command. pub(crate) async fn execute(mut self) -> Result<()> { let (api, url) = self.set_up_api(&mut cli::Cli).await?; - let call_config = if self.extrinsic.is_none() { - match guide_user_to_call_chain(&api, url, &mut cli::Cli).await { + // TODO: Option to send call_data, sign it and send it. + let mut call_config = if self.pallet.is_none() && self.extrinsic.is_none() { + match guide_user_to_call_chain(&api, "", &url, &mut cli::Cli).await { Ok(call_config) => call_config, Err(e) => { display_message(&format!("{}", e), false, &mut cli::Cli)?; @@ -41,7 +47,13 @@ impl CallParachainCommand { } else { self.clone() }; - execute_extrinsic(api, call_config, self.extrinsic.is_none(), &mut cli::Cli).await?; + prepare_and_submit_extrinsic( + api, + &mut call_config, + self.pallet.is_none() && self.extrinsic.is_none(), + &mut cli::Cli, + ) + .await?; Ok(()) } /// Prompt the user for the chain to use if not indicated and fetch the metadata. @@ -50,7 +62,7 @@ impl CallParachainCommand { cli: &mut impl cli::traits::Cli, ) -> anyhow::Result<(OnlineClient, String)> { cli.intro("Call a parachain")?; - let url: String = if self.extrinsic.is_none() { + let url: String = if self.pallet.is_none() && self.extrinsic.is_none() { // Prompt for contract location. cli.input("Which chain would you like to interact with?") .placeholder("wss://rpc1.paseo.popnetwork.xyz") @@ -65,9 +77,15 @@ impl CallParachainCommand { fn display(&self) -> String { let mut full_message = "pop call parachain".to_string(); + if let Some(pallet) = &self.pallet { + full_message.push_str(&format!(" --pallet {}", pallet)); + } if let Some(extrinsic) = &self.extrinsic { full_message.push_str(&format!(" --extrinsic {}", extrinsic)); } + if self.args.len() > 0 { + full_message.push_str(&format!(" --args {}", self.args.join(" "))); + } full_message.push_str(&format!(" --url {}", self.url)); if !self.suri.is_empty() { full_message.push_str(&format!(" --suri {}", self.suri)); @@ -79,11 +97,12 @@ impl CallParachainCommand { /// Guide the user to call the contract. async fn guide_user_to_call_chain( api: &OnlineClient, - url: String, + suri: &str, + url: &str, cli: &mut impl cli::traits::Cli, ) -> anyhow::Result { let extrinsic = { - let mut prompt_extrinsic = cli.select("Select the extrinsic to call:"); + let mut prompt_extrinsic = cli.select("What would you like to do on Polkadot?"); //for extrinsic in pallet.extrinsics() { for extrinsic in supported_extrinsics(api) { prompt_extrinsic = prompt_extrinsic.item( @@ -95,62 +114,102 @@ async fn guide_user_to_call_chain( prompt_extrinsic.interact()? }; let args = prompt_arguments(&extrinsic, cli)?; - let suri = cli::Cli - .input("Who is going to sign the extrinsic:") - .placeholder("//Alice") - .default_input("//Alice") - .interact()?; - match prepare_extrinsic(api, extrinsic.pallet(), extrinsic.extrinsic_name(), args, &suri).await - { - Ok(encoded_call_data) => - Ok(CallParachainCommand { extrinsic: Some(encoded_call_data), url, suri }), - Err(e) => Err(anyhow!(format!("{}", e.to_string()))), - } + Ok(CallParachainCommand { + pallet: Some(extrinsic.pallet().to_string()), + extrinsic: Some(extrinsic.extrinsic_name().to_string()), + args, + url: url.to_string(), + suri: suri.to_string(), + }) } -/// Executes the extrinsic or query. -async fn execute_extrinsic( +/// Prepares the extrinsic or query. +async fn prepare_and_submit_extrinsic( api: OnlineClient, - call_config: CallParachainCommand, + call_config: &mut CallParachainCommand, prompt_to_repeat_call: bool, cli: &mut impl cli::traits::Cli, ) -> Result<()> { - cli.info(call_config.display())?; - let extrinsic = call_config + let extrinsic: String = call_config .extrinsic + .clone() .expect("extrinsic can not be none as fallback above is interactive input; qed"); - if !cli.confirm("Do you want to submit the call?").interact()? { - display_message(&format!("Extrinsic: {} not submitted", extrinsic), true, cli)?; - return Ok(()); - } - let spinner = cliclack::spinner(); - spinner.start("Submitting the extrinsic..."); - match submit_extrinsic(api.clone(), extrinsic).await { - Ok(result) => { - console::Term::stderr().clear_last_lines(1)?; - display_message( - &format!("Extrinsic submitted successfully with hash: {:?}", result), - true, - cli, - )?; + let pallet = match call_config.pallet.clone() { + Some(m) => m, + None => { + return Err(anyhow!("Please specify the pallet to call.")); }, + }; + let tx = match construct_extrinsic(&pallet, &extrinsic, &call_config.args) { + Ok(tx) => tx, Err(e) => { - console::Term::stderr().clear_last_lines(1)?; - display_message(&format!("{}", e), false, cli)?; + display_message( + &format!("Error parsing the arguments: {}", e.to_string()), + false, + &mut cli::Cli, + )?; + return Ok(()); }, - } - // Repeat call. - if prompt_to_repeat_call { - let another_call: bool = cli - .confirm("Do you want to do another call to the same chain?") - .initial_value(false) + }; + cli.info(format!("Call data {}", encode_call_data(&api, &tx)?))?; + if call_config.suri.is_empty() { + call_config.suri = cli::Cli + .input("Who is going to sign the extrinsic:") + .placeholder("//Alice") + .default_input("//Alice") .interact()?; - if another_call { - // Remove only the prompt asking for another call. - console::Term::stderr().clear_last_lines(2)?; - let new_call_config = guide_user_to_call_chain(&api, call_config.url, cli).await?; - Box::pin(execute_extrinsic(api, new_call_config, prompt_to_repeat_call, cli)).await?; + } + cli.info(call_config.display())?; + if !cli.confirm("Do you want to submit the call?").initial_value(true).interact()? { + display_message( + &format!("Extrinsic: {} not submitted, user cancel the operation", extrinsic), + false, + cli, + )?; + return Ok(()); + } + send_extrinsic(api, tx, &call_config.url, &call_config.suri, prompt_to_repeat_call, cli) + .await?; + + Ok(()) +} + +async fn send_extrinsic( + api: OnlineClient, + tx: DynamicPayload, + url: &str, + suri: &str, + prompt_to_repeat_call: bool, + cli: &mut impl cli::traits::Cli, +) -> Result<()> { + let spinner = cliclack::spinner(); + spinner.start("Signing and submitting the extrinsic, please wait..."); + let result = sign_and_submit_extrinsic(api.clone(), tx, suri).await; + if let Err(e) = result { + console::Term::stderr().clear_last_lines(1)?; + display_message(&format!("{}", e), false, cli)?; + } else { + console::Term::stderr().clear_last_lines(1)?; + display_message(&format!("Extrinsic submitted with hash: {:?}", result?), true, cli)?; + // Repeat call. + if prompt_to_repeat_call { + let another_call: bool = cli + .confirm("Do you want to do another call to the same chain?") + .initial_value(false) + .interact()?; + if another_call { + // Remove only the prompt asking for another call. + console::Term::stderr().clear_last_lines(2)?; + let mut new_call_config = guide_user_to_call_chain(&api, suri, url, cli).await?; + Box::pin(prepare_and_submit_extrinsic( + api, + &mut new_call_config, + prompt_to_repeat_call, + cli, + )) + .await?; + } } } Ok(()) @@ -165,14 +224,13 @@ fn display_message(message: &str, success: bool, cli: &mut impl cli::traits::Cli Ok(()) } // Prompt the user to select an operation. -fn prompt_arguments(extrinsic: &Extrinsic, cli: &mut impl cli::traits::Cli) -> Result> { - let mut args: Vec = Vec::new(); +fn prompt_arguments(extrinsic: &Extrinsic, cli: &mut impl cli::traits::Cli) -> Result> { + let mut args: Vec = Vec::new(); match extrinsic { Extrinsic::CreateAsset => { args.push(prompt_for_numeric_value("Enter the Asset ID", cli)?); args.push(prompt_for_account("Enter the Admin Address", cli)?); args.push(prompt_for_numeric_value("Enter the Minimum Balance", cli)?); - // TODO: ADD METADATA }, Extrinsic::MintAsset => { args.push(prompt_for_numeric_value("Enter the Asset ID", cli)?); @@ -181,13 +239,13 @@ fn prompt_arguments(extrinsic: &Extrinsic, cli: &mut impl cli::traits::Cli) -> R }, Extrinsic::CreateCollection => { args.push(prompt_for_account("Enter the Admin Address", cli)?); - args.push(prompt_for_collection_config(cli)?); + args.extend(prompt_for_collection_config(cli)?); }, Extrinsic::MintNFT => { args.push(prompt_for_numeric_value("Enter the Collection ID", cli)?); args.push(prompt_for_numeric_value("Enter the Item ID", cli)?); args.push(prompt_for_account("Enter the Beneficiary Address", cli)?); - args.push(prompt_for_witness_data(cli)?); + args.extend(prompt_for_witness_data(cli)?); }, Extrinsic::Transfer => { args.push(prompt_for_account("Enter the Destination Address", cli)?); @@ -196,7 +254,7 @@ fn prompt_arguments(extrinsic: &Extrinsic, cli: &mut impl cli::traits::Cli) -> R } Ok(args) } -fn prompt_for_numeric_value(message: &str, cli: &mut impl cli::traits::Cli) -> Result { +fn prompt_for_numeric_value(message: &str, cli: &mut impl cli::traits::Cli) -> Result { let id = cli .input(message) .placeholder("0") @@ -207,88 +265,85 @@ fn prompt_for_numeric_value(message: &str, cli: &mut impl cli::traits::Cli) -> R }) .required(true) .interact()?; - Ok(Value::u128(id.parse::()?)) + Ok(id) } -fn prompt_for_account(message: &str, cli: &mut impl cli::traits::Cli) -> Result { +fn prompt_for_account(message: &str, cli: &mut impl cli::traits::Cli) -> Result { let account: String = cli .input(message) .placeholder("e.g. 5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") .required(true) .interact()?; - let account_id = parse_account(&account)?; + //let account_id = parse_account(&account)?; // TODO: Support other Adresses? Let the user pick Id, Address, or Index - Ok(Value::unnamed_variant("Id", vec![Value::from_bytes(account_id)])) + Ok(account) } fn prompt_for_numeric_optional_value( message: &str, cli: &mut impl cli::traits::Cli, -) -> Result { +) -> Result { let value = cli .input(message) .placeholder("0 or (empty for None)") .validate(|input: &String| match input.parse::() { Ok(_) => Ok(()), - Err(_) => + Err(_) => { if input.is_empty() || input == "None" { Ok(()) } else { Err("Invalid value.") - }, + } + }, }) .required(false) .interact()?; if value.is_empty() || value == "None" { - Ok(Value::unnamed_variant("None", vec![])) + Ok("None".to_string()) + //Ok(Value::unnamed_variant("None", vec![])) } else { - Ok(Value::unnamed_variant("Some", vec![Value::u128(value.parse::()?)])) + Ok(value) + //Ok(Value::unnamed_variant("Some", vec![Value::u128(value.parse::()?)])) } } fn prompt_for_variant_value( message: &str, default_value: &str, cli: &mut impl cli::traits::Cli, -) -> Result { +) -> Result { let mint_type: String = cli .input(message) .placeholder(&format!("e.g. {}", default_value)) .default_input(default_value) .required(true) .interact()?; - Ok(Value::unnamed_variant(mint_type, vec![])) + Ok(mint_type) } -fn prompt_for_collection_config(cli: &mut impl cli::traits::Cli) -> Result { +fn prompt_for_collection_config(cli: &mut impl cli::traits::Cli) -> Result> { + let mut args: Vec = Vec::new(); cli.info("Enter the Pallet NFT Collection Config:")?; - let settings = prompt_for_numeric_value("Collection's Settings", cli)?; - let max_supply = prompt_for_numeric_optional_value("Collection's Max Supply", cli)?; + args.push(prompt_for_numeric_value("Collection's Settings", cli)?); + args.push(prompt_for_numeric_optional_value("Collection's Max Supply", cli)?); cli.info("Enter the Mint Settings:")?; - let mint_type = prompt_for_variant_value("Who can mint?", "Issuer", cli)?; - let price_per_mint = prompt_for_numeric_optional_value("Price per mint", cli)?; - let start_block = prompt_for_numeric_optional_value("When the mint starts", cli)?; - let end_block = prompt_for_numeric_optional_value("When the mint ends", cli)?; - let default_item_settings = prompt_for_numeric_value("Default Item Settings", cli)?; - // mint settings - let mint_settings = Value::unnamed_composite(vec![ - mint_type, - price_per_mint, - start_block, - end_block, - default_item_settings, - ]); - let config_collection = Value::unnamed_composite(vec![settings, max_supply, mint_settings]); - - Ok(config_collection) + args.push(prompt_for_variant_value("Who can mint?", "Issuer", cli)?); + args.push(prompt_for_numeric_optional_value("Price per mint", cli)?); + args.push(prompt_for_numeric_optional_value("When the mint starts", cli)?); + args.push(prompt_for_numeric_optional_value("When the mint ends", cli)?); + args.push(prompt_for_numeric_value("Default Item Settings", cli)?); + Ok(args) } -fn prompt_for_witness_data(cli: &mut impl cli::traits::Cli) -> Result { +fn prompt_for_witness_data(cli: &mut impl cli::traits::Cli) -> Result> { + let mut args: Vec = Vec::new(); if cli .confirm("Do you want to enter witness data for mint") .initial_value(false) .interact()? { - let owned_item = - prompt_for_numeric_optional_value("Id of the item in a required collection:", cli)?; - let mint_price = prompt_for_numeric_optional_value("Mint price:", cli)?; - Ok(Value::unnamed_variant("Some".to_string(), vec![owned_item, mint_price])) + args.push(prompt_for_numeric_optional_value( + "Id of the item in a required collection:", + cli, + )?); + args.push(prompt_for_numeric_optional_value("Mint price:", cli)?); } else { - Ok(Value::unnamed_variant("None", vec![])) + args.push("None".to_string()); } + Ok(args) } diff --git a/crates/pop-contracts/src/utils/mod.rs b/crates/pop-contracts/src/utils/mod.rs index 7192839b4..12c7813ad 100644 --- a/crates/pop-contracts/src/utils/mod.rs +++ b/crates/pop-contracts/src/utils/mod.rs @@ -51,7 +51,7 @@ pub fn canonicalized_path(target: &Path) -> Result { #[cfg(test)] mod tests { use super::*; - use anyhow::{Error, Result}; + use anyhow::Result; use std::fs; fn setup_test_environment() -> Result { @@ -106,7 +106,7 @@ mod tests { } #[test] - fn parse_hex_bytes_fails_wrong_input() -> Result<(), Error> { + fn parse_hex_bytes_fails_wrong_input() -> Result<()> { assert!(matches!(parse_hex_bytes("wronghexvalue"), Err(Error::HexParsing(..)))); Ok(()) } diff --git a/crates/pop-parachains/src/call.rs b/crates/pop-parachains/src/call.rs index efb964142..c41b4d49a 100644 --- a/crates/pop-parachains/src/call.rs +++ b/crates/pop-parachains/src/call.rs @@ -1,36 +1,47 @@ // SPDX-License-Identifier: GPL-3.0 use crate::errors::Error; -use pop_common::create_signer; +use pop_common::{create_signer, parse_account}; use strum::{EnumMessage as _, EnumProperty as _, VariantArray as _}; use strum_macros::{AsRefStr, Display, EnumMessage, EnumProperty, EnumString, VariantArray}; use subxt::{ - config::DefaultExtrinsicParamsBuilder, dynamic::Value, tx::SubmittableExtrinsic, OnlineClient, - SubstrateConfig, + dynamic::Value, + tx::{DynamicPayload, Payload}, + OnlineClient, SubstrateConfig, }; +#[derive(AsRefStr, Clone, Debug, Display, EnumMessage, EnumString, Eq, PartialEq, VariantArray)] +pub enum Pallet { + #[strum(serialize = "Assets")] + Assets, + #[strum(serialize = "Balances")] + Balances, + #[strum(serialize = "Nfts")] + Nfts, +} + #[derive( AsRefStr, Clone, Debug, Display, EnumMessage, - EnumProperty, EnumString, + EnumProperty, Eq, PartialEq, VariantArray, )] pub enum Extrinsic { #[strum( - serialize = "create_asset", + serialize = "create", message = "create", detailed_message = "Create an Asset", props(Pallet = "Assets") )] CreateAsset, #[strum( - serialize = "mint_asset", + serialize = "mint", message = "mint", detailed_message = "Mint an Asset", props(Pallet = "Assets") @@ -44,7 +55,7 @@ pub enum Extrinsic { )] CreateCollection, #[strum( - serialize = "mint", + serialize = "mint_nft", message = "mint", detailed_message = "Mint a NFT", props(Pallet = "Nfts") @@ -73,6 +84,7 @@ impl Extrinsic { self.get_str("Pallet").unwrap_or_default() } } + pub fn supported_extrinsics(api: &OnlineClient) -> Vec<&Extrinsic> { Extrinsic::VARIANTS .iter() @@ -80,44 +92,183 @@ pub fn supported_extrinsics(api: &OnlineClient) -> Vec<&Extrins .collect() } +pub fn parse_args( + pallet_name: &str, + extrinsic_name: &str, + raw_args: &Vec, +) -> Result, Error> { + let mut args: Vec = Vec::new(); + let extrinsic = Extrinsic::VARIANTS + .iter() + .find(|t| t.pallet() == pallet_name && t.extrinsic_name() == extrinsic_name) + .ok_or(Error::ExtrinsicNotSupported(extrinsic_name.to_string()))?; + match extrinsic { + Extrinsic::CreateAsset => { + args.push(Value::u128( + raw_args[0].parse::().map_err(|_| Error::ParsingArgsError)?, + )); + args.push(Value::unnamed_variant( + "Id", + vec![Value::from_bytes(parse_account(&raw_args[1])?)], + )); + args.push(Value::u128( + raw_args[2].parse::().map_err(|_| Error::ParsingArgsError)?, + )); + }, + Extrinsic::MintAsset => { + args.push(Value::u128( + raw_args[0].parse::().map_err(|_| Error::ParsingArgsError)?, + )); + args.push(Value::unnamed_variant( + "Id", + vec![Value::from_bytes(parse_account(&raw_args[1])?)], + )); + args.push(Value::u128( + raw_args[2].parse::().map_err(|_| Error::ParsingArgsError)?, + )); + }, + Extrinsic::CreateCollection => { + args.push(Value::unnamed_variant( + "Id", + vec![Value::from_bytes(parse_account(&raw_args[0])?)], + )); + let mint_settings = Value::unnamed_composite(vec![ + Value::unnamed_variant(&raw_args[3], vec![]), + if raw_args[4] == "None" { + Value::unnamed_variant("None", vec![]) + } else { + Value::unnamed_variant( + "Some", + vec![Value::u128( + raw_args[4].parse::().map_err(|_| Error::ParsingArgsError)?, + )], + ) + }, + if raw_args[5] == "None" { + Value::unnamed_variant("None", vec![]) + } else { + Value::unnamed_variant( + "Some", + vec![Value::u128( + raw_args[5].parse::().map_err(|_| Error::ParsingArgsError)?, + )], + ) + }, + if raw_args[6] == "None" { + Value::unnamed_variant("None", vec![]) + } else { + Value::unnamed_variant( + "Some", + vec![Value::u128( + raw_args[6].parse::().map_err(|_| Error::ParsingArgsError)?, + )], + ) + }, + Value::u128(raw_args[7].parse::().map_err(|_| Error::ParsingArgsError)?), + ]); + let max_supply = if raw_args[2] == "None" { + Value::unnamed_variant("None", vec![]) + } else { + Value::unnamed_variant( + "Some", + vec![Value::u128( + raw_args[2].parse::().map_err(|_| Error::ParsingArgsError)?, + )], + ) + }; + args.push(Value::unnamed_composite(vec![ + Value::u128(raw_args[1].parse::().map_err(|_| Error::ParsingArgsError)?), + max_supply, + mint_settings, + ])) + }, + Extrinsic::MintNFT => { + args.push(Value::u128( + raw_args[0].parse::().map_err(|_| Error::ParsingArgsError)?, + )); + args.push(Value::u128( + raw_args[1].parse::().map_err(|_| Error::ParsingArgsError)?, + )); + args.push(Value::unnamed_variant( + "Id", + vec![Value::from_bytes(parse_account(&raw_args[2])?)], + )); + if raw_args[3] == "None" && raw_args.len() == 4 { + args.push(Value::unnamed_variant("None", vec![])); + } else { + let owned_item = if raw_args[3] == "None" { + Value::unnamed_variant("None", vec![]) + } else { + Value::unnamed_variant( + "Some", + vec![Value::u128( + raw_args[3].parse::().map_err(|_| Error::ParsingArgsError)?, + )], + ) + }; + let mint_price = if raw_args[4] == "None" { + Value::unnamed_variant("None", vec![]) + } else { + Value::unnamed_variant( + "Some", + vec![Value::u128( + raw_args[4].parse::().map_err(|_| Error::ParsingArgsError)?, + )], + ) + }; + args.push(Value::unnamed_variant("Some".to_string(), vec![owned_item, mint_price])); + } + }, + Extrinsic::Transfer => { + args.push(Value::unnamed_variant( + "Id", + vec![Value::from_bytes(parse_account(&raw_args[0])?)], + )); + args.push(Value::u128( + raw_args[1].parse::().map_err(|_| Error::ParsingArgsError)?, + )); + }, + } + Ok(args) +} + pub async fn set_up_api(url: &str) -> Result, Error> { let api = OnlineClient::::from_url(url).await?; Ok(api) } -pub async fn prepare_extrinsic( - api: &OnlineClient, +pub fn construct_extrinsic( pallet_name: &str, - entry_name: &str, - args_value: Vec, - suri: &str, -) -> Result { - let signer = create_signer(suri)?; - let tx = subxt::dynamic::tx(pallet_name, entry_name, args_value); - let signed_extrinsic: SubmittableExtrinsic> = - api.tx() - .create_signed(&tx, &signer, DefaultExtrinsicParamsBuilder::new().build()) - .await?; - Ok(encode_extrinsic(signed_extrinsic.encoded().to_vec())) + extrinsic_name: &str, + args: &Vec, +) -> Result { + let parsed_args: Vec = parse_args(pallet_name, extrinsic_name, &args)?; + Ok(subxt::dynamic::tx(pallet_name, extrinsic_name, parsed_args)) } -pub async fn submit_extrinsic( +pub async fn sign_and_submit_extrinsic( api: OnlineClient, - encoded_extrinsic: String, + tx: DynamicPayload, + suri: &str, ) -> Result { - let extrinsic = decode_extrinsic(encoded_extrinsic)?; - let signed_extrinsic: SubmittableExtrinsic> = - SubmittableExtrinsic::from_bytes(api, extrinsic); - let result = signed_extrinsic.submit_and_watch().await?; + let signer = create_signer(suri)?; + let result = api + .tx() + .sign_and_submit_then_watch_default(&tx, &signer) + .await? + .wait_for_finalized() + .await? + .wait_for_success() + .await?; Ok(format!("{:?}", result.extrinsic_hash())) } -fn encode_extrinsic(encoded_call_data: Vec) -> String { - format!("0x{}", hex::encode(encoded_call_data)) -} -fn decode_extrinsic(encoded_call_data: String) -> Result, Error> { - let hex_data = encoded_call_data.trim_start_matches("0x"); - Ok(hex::decode(hex_data)?) +pub fn encode_call_data( + api: &OnlineClient, + tx: &DynamicPayload, +) -> Result { + let call_data = tx.encode_call_data(&api.metadata())?; + Ok(format!("0x{}", hex::encode(call_data))) } fn extrinsic_is_supported( @@ -142,6 +293,7 @@ fn extrinsic_is_supported( mod tests { use super::*; use anyhow::Result; + use subxt::tx::Payload; #[tokio::test] async fn extrinsic_is_supported_works() -> Result<()> { @@ -150,4 +302,21 @@ mod tests { assert!(!extrinsic_is_supported(&api, "Nfts", "mint_no_exist")); Ok(()) } + + #[tokio::test] + async fn encode_call_data_works() -> Result<()> { + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let tx = construct_extrinsic( + "Assets", + "create", + &vec![ + "1000".to_string(), + "15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5".to_string(), + "1000000".to_string(), + ], + )?; + let call_data = tx.encode_call_data(&api.metadata())?; + assert_eq!("0x3400419c00d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d40420f00000000000000000000000000", encode_call_data(call_data)); + Ok(()) + } } diff --git a/crates/pop-parachains/src/errors.rs b/crates/pop-parachains/src/errors.rs index 6c7b55b95..2a54c4fed 100644 --- a/crates/pop-parachains/src/errors.rs +++ b/crates/pop-parachains/src/errors.rs @@ -10,8 +10,6 @@ pub enum Error { Aborted, #[error("Anyhow error: {0}")] AnyhowError(#[from] anyhow::Error), - #[error("The `Call` property is missing from the call variant")] - CallMissing, #[error("{0}")] CommonError(#[from] pop_common::Error), #[error("Configuration error: {0}")] @@ -20,6 +18,8 @@ pub enum Error { CurrentDirAccess, #[error("Failed to parse the endowment value")] EndowmentError, + #[error("The extrinsic {0} is not supported")] + ExtrinsicNotSupported(String), #[error("Failed decode a value")] FromHexError(#[from] hex::FromHexError), #[error("IO error: {0}")] @@ -42,10 +42,10 @@ pub enum Error { PalletMissing, #[error("Failed to find the pallet {0}")] PalletNotFound(String), + #[error("Failed to parse the arguments")] + ParsingArgsError, #[error("Failed to parse the response")] ParsingResponseError(#[from] scale_decode::Error), - #[error("Failed to parse the argument {0}")] - ParsingValueError(String), #[error("Invalid path")] PathError, #[error("Failed to execute rustfmt")] @@ -54,6 +54,8 @@ pub enum Error { SourcingError(#[from] pop_common::sourcing::Error), #[error("{0}")] SubxtError(#[from] subxt::Error), + #[error("{0}")] + SubxtExternalError(#[from] subxt::ext::subxt_core::Error), #[error("Toml error: {0}")] TomlError(#[from] toml_edit::de::Error), #[error("Unsupported command: {0}")] diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index 76c597692..53f603b11 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -15,13 +15,16 @@ pub use build::{ binary_path, build_parachain, export_wasm_file, generate_genesis_state_file, generate_plain_chain_spec, generate_raw_chain_spec, is_supported, ChainSpec, }; -pub use call::{prepare_extrinsic, set_up_api, submit_extrinsic, supported_extrinsics, Extrinsic}; +pub use call::{ + construct_extrinsic, encode_call_data, set_up_api, sign_and_submit_extrinsic, + supported_extrinsics, Extrinsic, Pallet, +}; pub use errors::Error; pub use indexmap::IndexSet; pub use new_pallet::{create_pallet_template, new_pallet_options::*, TemplatePalletConfig}; pub use new_parachain::instantiate_template_dir; // External export from subxt. -pub use subxt::{dynamic::Value, OnlineClient, SubstrateConfig}; +pub use subxt::{dynamic::Value, tx::DynamicPayload, OnlineClient, SubstrateConfig}; pub use templates::{Config, Parachain, Provider}; pub use up::Zombienet; pub use utils::helpers::is_initial_endowment_valid; From 24a53f158cab23f17f63fdf4b781fd10134eda2a Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 27 Sep 2024 13:22:49 +0200 Subject: [PATCH 054/211] refactor: clean docs and refactor code --- crates/pop-cli/src/commands/call/parachain.rs | 26 +- crates/pop-parachains/src/call.rs | 259 +++++++----------- 2 files changed, 106 insertions(+), 179 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 884db4618..5d6584d39 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -35,7 +35,6 @@ impl CallParachainCommand { /// Executes the command. pub(crate) async fn execute(mut self) -> Result<()> { let (api, url) = self.set_up_api(&mut cli::Cli).await?; - // TODO: Option to send call_data, sign it and send it. let mut call_config = if self.pallet.is_none() && self.extrinsic.is_none() { match guide_user_to_call_chain(&api, "", &url, &mut cli::Cli).await { Ok(call_config) => call_config, @@ -83,7 +82,7 @@ impl CallParachainCommand { if let Some(extrinsic) = &self.extrinsic { full_message.push_str(&format!(" --extrinsic {}", extrinsic)); } - if self.args.len() > 0 { + if !self.args.is_empty() { full_message.push_str(&format!(" --args {}", self.args.join(" "))); } full_message.push_str(&format!(" --url {}", self.url)); @@ -102,7 +101,7 @@ async fn guide_user_to_call_chain( cli: &mut impl cli::traits::Cli, ) -> anyhow::Result { let extrinsic = { - let mut prompt_extrinsic = cli.select("What would you like to do on Polkadot?"); + let mut prompt_extrinsic = cli.select("What would you like to do?"); //for extrinsic in pallet.extrinsics() { for extrinsic in supported_extrinsics(api) { prompt_extrinsic = prompt_extrinsic.item( @@ -141,18 +140,14 @@ async fn prepare_and_submit_extrinsic( return Err(anyhow!("Please specify the pallet to call.")); }, }; - let tx = match construct_extrinsic(&pallet, &extrinsic, &call_config.args) { + let tx = match construct_extrinsic(&pallet, &extrinsic, call_config.args.clone()) { Ok(tx) => tx, Err(e) => { - display_message( - &format!("Error parsing the arguments: {}", e.to_string()), - false, - &mut cli::Cli, - )?; + display_message(&format!("Error parsing the arguments: {}", e), false, &mut cli::Cli)?; return Ok(()); }, }; - cli.info(format!("Call data {}", encode_call_data(&api, &tx)?))?; + cli.info(format!("Encoded call data: {}", encode_call_data(&api, &tx)?))?; if call_config.suri.is_empty() { call_config.suri = cli::Cli .input("Who is going to sign the extrinsic:") @@ -163,7 +158,7 @@ async fn prepare_and_submit_extrinsic( cli.info(call_config.display())?; if !cli.confirm("Do you want to submit the call?").initial_value(true).interact()? { display_message( - &format!("Extrinsic: {} not submitted, user cancel the operation", extrinsic), + &format!("Extrinsic {} was not submitted. Operation canceled by the user.", extrinsic), false, cli, )?; @@ -273,8 +268,6 @@ fn prompt_for_account(message: &str, cli: &mut impl cli::traits::Cli) -> Result< .placeholder("e.g. 5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") .required(true) .interact()?; - //let account_id = parse_account(&account)?; - // TODO: Support other Adresses? Let the user pick Id, Address, or Index Ok(account) } fn prompt_for_numeric_optional_value( @@ -286,22 +279,19 @@ fn prompt_for_numeric_optional_value( .placeholder("0 or (empty for None)") .validate(|input: &String| match input.parse::() { Ok(_) => Ok(()), - Err(_) => { + Err(_) => if input.is_empty() || input == "None" { Ok(()) } else { Err("Invalid value.") - } - }, + }, }) .required(false) .interact()?; if value.is_empty() || value == "None" { Ok("None".to_string()) - //Ok(Value::unnamed_variant("None", vec![])) } else { Ok(value) - //Ok(Value::unnamed_variant("Some", vec![Value::u128(value.parse::()?)])) } } fn prompt_for_variant_value( diff --git a/crates/pop-parachains/src/call.rs b/crates/pop-parachains/src/call.rs index c41b4d49a..93452638c 100644 --- a/crates/pop-parachains/src/call.rs +++ b/crates/pop-parachains/src/call.rs @@ -70,7 +70,7 @@ pub enum Extrinsic { Transfer, } impl Extrinsic { - /// Get the template's name. + /// Get the extrinsic's name. pub fn extrinsic_name(&self) -> &str { self.get_message().unwrap_or_default() } @@ -78,13 +78,17 @@ impl Extrinsic { pub fn description(&self) -> &str { self.get_detailed_message().unwrap_or_default() } - /// Get the pallet of the extrinsic. pub fn pallet(&self) -> &str { self.get_str("Pallet").unwrap_or_default() } } +pub async fn set_up_api(url: &str) -> Result, Error> { + let api = OnlineClient::::from_url(url).await?; + Ok(api) +} + pub fn supported_extrinsics(api: &OnlineClient) -> Vec<&Extrinsic> { Extrinsic::VARIANTS .iter() @@ -92,10 +96,44 @@ pub fn supported_extrinsics(api: &OnlineClient) -> Vec<&Extrins .collect() } -pub fn parse_args( +pub fn construct_extrinsic( pallet_name: &str, extrinsic_name: &str, - raw_args: &Vec, + args: Vec, +) -> Result { + let parsed_args: Vec = parse_args(pallet_name, extrinsic_name, args)?; + Ok(subxt::dynamic::tx(pallet_name, extrinsic_name, parsed_args)) +} + +pub async fn sign_and_submit_extrinsic( + api: OnlineClient, + tx: DynamicPayload, + suri: &str, +) -> Result { + let signer = create_signer(suri)?; + let result = api + .tx() + .sign_and_submit_then_watch_default(&tx, &signer) + .await? + .wait_for_finalized() + .await? + .wait_for_success() + .await?; + Ok(format!("{:?}", result.extrinsic_hash())) +} + +pub fn encode_call_data( + api: &OnlineClient, + tx: &DynamicPayload, +) -> Result { + let call_data = tx.encode_call_data(&api.metadata())?; + Ok(format!("0x{}", hex::encode(call_data))) +} + +fn parse_args( + pallet_name: &str, + extrinsic_name: &str, + raw_args: Vec, ) -> Result, Error> { let mut args: Vec = Vec::new(); let extrinsic = Extrinsic::VARIANTS @@ -104,171 +142,88 @@ pub fn parse_args( .ok_or(Error::ExtrinsicNotSupported(extrinsic_name.to_string()))?; match extrinsic { Extrinsic::CreateAsset => { - args.push(Value::u128( - raw_args[0].parse::().map_err(|_| Error::ParsingArgsError)?, - )); - args.push(Value::unnamed_variant( - "Id", - vec![Value::from_bytes(parse_account(&raw_args[1])?)], - )); - args.push(Value::u128( - raw_args[2].parse::().map_err(|_| Error::ParsingArgsError)?, - )); + if raw_args.len() < 3 { + return Err(Error::ParsingArgsError); + } + args.push(parse_u128(&raw_args[0])?); + args.push(parse_account_id(&raw_args[1])?); + args.push(parse_u128(&raw_args[2])?); }, Extrinsic::MintAsset => { - args.push(Value::u128( - raw_args[0].parse::().map_err(|_| Error::ParsingArgsError)?, - )); - args.push(Value::unnamed_variant( - "Id", - vec![Value::from_bytes(parse_account(&raw_args[1])?)], - )); - args.push(Value::u128( - raw_args[2].parse::().map_err(|_| Error::ParsingArgsError)?, - )); + if raw_args.len() < 3 { + return Err(Error::ParsingArgsError); + } + args.push(parse_u128(&raw_args[0])?); + args.push(parse_account_id(&raw_args[1])?); + args.push(parse_u128(&raw_args[2])?); }, Extrinsic::CreateCollection => { - args.push(Value::unnamed_variant( - "Id", - vec![Value::from_bytes(parse_account(&raw_args[0])?)], - )); + if raw_args.len() < 7 { + return Err(Error::ParsingArgsError); + } + args.push(parse_account_id(&raw_args[0])?); let mint_settings = Value::unnamed_composite(vec![ Value::unnamed_variant(&raw_args[3], vec![]), - if raw_args[4] == "None" { - Value::unnamed_variant("None", vec![]) - } else { - Value::unnamed_variant( - "Some", - vec![Value::u128( - raw_args[4].parse::().map_err(|_| Error::ParsingArgsError)?, - )], - ) - }, - if raw_args[5] == "None" { - Value::unnamed_variant("None", vec![]) - } else { - Value::unnamed_variant( - "Some", - vec![Value::u128( - raw_args[5].parse::().map_err(|_| Error::ParsingArgsError)?, - )], - ) - }, - if raw_args[6] == "None" { - Value::unnamed_variant("None", vec![]) - } else { - Value::unnamed_variant( - "Some", - vec![Value::u128( - raw_args[6].parse::().map_err(|_| Error::ParsingArgsError)?, - )], - ) - }, - Value::u128(raw_args[7].parse::().map_err(|_| Error::ParsingArgsError)?), + parse_optional_u128(&raw_args[4])?, + parse_optional_u128(&raw_args[5])?, + parse_optional_u128(&raw_args[6])?, + parse_u128(&raw_args[7])?, ]); - let max_supply = if raw_args[2] == "None" { - Value::unnamed_variant("None", vec![]) - } else { - Value::unnamed_variant( - "Some", - vec![Value::u128( - raw_args[2].parse::().map_err(|_| Error::ParsingArgsError)?, - )], - ) - }; + let max_supply = parse_optional_u128(&raw_args[2])?; args.push(Value::unnamed_composite(vec![ - Value::u128(raw_args[1].parse::().map_err(|_| Error::ParsingArgsError)?), + parse_u128(&raw_args[1])?, max_supply, mint_settings, ])) }, Extrinsic::MintNFT => { - args.push(Value::u128( - raw_args[0].parse::().map_err(|_| Error::ParsingArgsError)?, - )); - args.push(Value::u128( - raw_args[1].parse::().map_err(|_| Error::ParsingArgsError)?, - )); - args.push(Value::unnamed_variant( - "Id", - vec![Value::from_bytes(parse_account(&raw_args[2])?)], - )); + println!("{:?}", raw_args.len()); + if raw_args.len() < 4 { + return Err(Error::ParsingArgsError); + } + args.push(parse_u128(&raw_args[0])?); + args.push(parse_u128(&raw_args[1])?); + args.push(parse_account_id(&raw_args[2])?); if raw_args[3] == "None" && raw_args.len() == 4 { args.push(Value::unnamed_variant("None", vec![])); } else { - let owned_item = if raw_args[3] == "None" { - Value::unnamed_variant("None", vec![]) - } else { - Value::unnamed_variant( - "Some", - vec![Value::u128( - raw_args[3].parse::().map_err(|_| Error::ParsingArgsError)?, - )], - ) - }; - let mint_price = if raw_args[4] == "None" { - Value::unnamed_variant("None", vec![]) - } else { - Value::unnamed_variant( - "Some", - vec![Value::u128( - raw_args[4].parse::().map_err(|_| Error::ParsingArgsError)?, - )], - ) - }; - args.push(Value::unnamed_variant("Some".to_string(), vec![owned_item, mint_price])); + if raw_args.len() < 5 { + return Err(Error::ParsingArgsError); + } + let owned_item = parse_optional_u128(&raw_args[3])?; + let mint_price = parse_optional_u128(&raw_args[4])?; + args.push(Value::unnamed_variant( + "Some", + vec![Value::unnamed_composite(vec![owned_item, mint_price])], + )); } }, Extrinsic::Transfer => { - args.push(Value::unnamed_variant( - "Id", - vec![Value::from_bytes(parse_account(&raw_args[0])?)], - )); - args.push(Value::u128( - raw_args[1].parse::().map_err(|_| Error::ParsingArgsError)?, - )); + if raw_args.len() < 2 { + return Err(Error::ParsingArgsError); + } + args.push(parse_account_id(&raw_args[0])?); + args.push(parse_u128(&raw_args[1])?); }, } Ok(args) } -pub async fn set_up_api(url: &str) -> Result, Error> { - let api = OnlineClient::::from_url(url).await?; - Ok(api) +// Helper function to parse u128 +fn parse_u128(arg: &str) -> Result { + Ok(Value::u128(arg.parse::().map_err(|_| Error::ParsingArgsError)?)) } - -pub fn construct_extrinsic( - pallet_name: &str, - extrinsic_name: &str, - args: &Vec, -) -> Result { - let parsed_args: Vec = parse_args(pallet_name, extrinsic_name, &args)?; - Ok(subxt::dynamic::tx(pallet_name, extrinsic_name, parsed_args)) -} - -pub async fn sign_and_submit_extrinsic( - api: OnlineClient, - tx: DynamicPayload, - suri: &str, -) -> Result { - let signer = create_signer(suri)?; - let result = api - .tx() - .sign_and_submit_then_watch_default(&tx, &signer) - .await? - .wait_for_finalized() - .await? - .wait_for_success() - .await?; - Ok(format!("{:?}", result.extrinsic_hash())) +// Helper function to parse account id +fn parse_account_id(arg: &str) -> Result { + Ok(Value::unnamed_variant("Id", vec![Value::from_bytes(parse_account(arg)?)])) } - -pub fn encode_call_data( - api: &OnlineClient, - tx: &DynamicPayload, -) -> Result { - let call_data = tx.encode_call_data(&api.metadata())?; - Ok(format!("0x{}", hex::encode(call_data))) +// Helper function to handle "None" or Some(u128) values +fn parse_optional_u128(arg: &str) -> Result { + if arg == "None" { + Ok(Value::unnamed_variant("None", vec![])) + } else { + Ok(Value::unnamed_variant("Some", vec![parse_u128(arg)?])) + } } fn extrinsic_is_supported( @@ -293,7 +248,6 @@ fn extrinsic_is_supported( mod tests { use super::*; use anyhow::Result; - use subxt::tx::Payload; #[tokio::test] async fn extrinsic_is_supported_works() -> Result<()> { @@ -302,21 +256,4 @@ mod tests { assert!(!extrinsic_is_supported(&api, "Nfts", "mint_no_exist")); Ok(()) } - - #[tokio::test] - async fn encode_call_data_works() -> Result<()> { - let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; - let tx = construct_extrinsic( - "Assets", - "create", - &vec![ - "1000".to_string(), - "15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5".to_string(), - "1000000".to_string(), - ], - )?; - let call_data = tx.encode_call_data(&api.metadata())?; - assert_eq!("0x3400419c00d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d40420f00000000000000000000000000", encode_call_data(call_data)); - Ok(()) - } } From ca2778af854050ff475816e5cc9b6ab5b0be8c89 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Mon, 11 Nov 2024 19:41:39 +0100 Subject: [PATCH 055/211] fix: tests --- crates/pop-cli/src/commands/call/contract.rs | 3 ++- crates/pop-contracts/src/call.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 8db1cc63c..d9e604b1e 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -7,9 +7,10 @@ use crate::{ use anyhow::{anyhow, Result}; use clap::Args; use cliclack::spinner; +use pop_common::parse_account; use pop_contracts::{ build_smart_contract, call_smart_contract, dry_run_call, dry_run_gas_estimate_call, - get_messages, parse_account, set_up_call, CallOpts, Verbosity, + get_messages, set_up_call, CallOpts, Verbosity, }; use sp_weights::Weight; use std::path::PathBuf; diff --git a/crates/pop-contracts/src/call.rs b/crates/pop-contracts/src/call.rs index 0ca795738..440b19cd1 100644 --- a/crates/pop-contracts/src/call.rs +++ b/crates/pop-contracts/src/call.rs @@ -4,7 +4,7 @@ use crate::{ errors::Error, utils::{ get_manifest_path, - metadata::{get_messages, ContractFunction}, + metadata::{process_function_args, FunctionType}, parse_balance, }, }; From 2c54c2b53660f83ad6d050622f8384e379b24115 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Sun, 17 Nov 2024 16:25:18 +0100 Subject: [PATCH 056/211] refactor: parse all the metadata again --- crates/pop-cli/src/commands/call/parachain.rs | 510 +++++++++--------- crates/pop-parachains/src/call.rs | 221 +------- crates/pop-parachains/src/lib.rs | 10 +- crates/pop-parachains/src/utils/metadata.rs | 508 +++++++++++++++++ crates/pop-parachains/src/utils/mod.rs | 1 + 5 files changed, 775 insertions(+), 475 deletions(-) create mode 100644 crates/pop-parachains/src/utils/metadata.rs diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 5d6584d39..30f54dc44 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -4,10 +4,14 @@ use crate::cli::{self, traits::*}; use anyhow::{anyhow, Result}; use clap::Args; use pop_parachains::{ - construct_extrinsic, encode_call_data, set_up_api, sign_and_submit_extrinsic, - supported_extrinsics, DynamicPayload, Extrinsic, OnlineClient, SubstrateConfig, + construct_extrinsic, encode_call_data, find_pallet_by_name, parse_chain_metadata, + process_prompt_arguments, set_up_api, sign_and_submit_extrinsic, Arg, DynamicPayload, + OnlineClient, SubstrateConfig, }; +const DEFAULT_URL: &str = "ws://localhost:9944/"; +const DEFAULT_URI: &str = "//Alice"; + #[derive(Args, Clone)] pub struct CallParachainCommand { /// The name of the pallet to call. @@ -20,58 +24,119 @@ pub struct CallParachainCommand { #[clap(long, num_args = 0..)] args: Vec, /// Websocket endpoint of a node. - #[clap(name = "url", long, value_parser, default_value = "ws://127.0.0.1:9944")] - url: String, + #[clap(name = "url", short = 'u', long, value_parser, default_value = DEFAULT_URL)] + url: url::Url, /// Secret key URI for the account signing the extrinsic. /// /// e.g. /// - for a dev account "//Alice" /// - with a password "//Alice///SECRET_PASSWORD" - #[clap(name = "suri", long, short, default_value = "//Alice")] + #[clap(name = "suri", long, short, default_value = DEFAULT_URI)] suri: String, } impl CallParachainCommand { /// Executes the command. pub(crate) async fn execute(mut self) -> Result<()> { - let (api, url) = self.set_up_api(&mut cli::Cli).await?; - let mut call_config = if self.pallet.is_none() && self.extrinsic.is_none() { - match guide_user_to_call_chain(&api, "", &url, &mut cli::Cli).await { - Ok(call_config) => call_config, - Err(e) => { - display_message(&format!("{}", e), false, &mut cli::Cli)?; - return Ok(()); - }, - } - } else { - self.clone() + // Check if message specified via command line argument. + let prompt_to_repeat_call = self.extrinsic.is_none(); + // Configure the call based on command line arguments/call UI. + let api = match self.configure(&mut cli::Cli, false).await { + Ok(api) => api, + Err(e) => { + display_message(&e.to_string(), false, &mut cli::Cli)?; + return Ok(()); + }, }; - prepare_and_submit_extrinsic( - api, - &mut call_config, - self.pallet.is_none() && self.extrinsic.is_none(), - &mut cli::Cli, - ) - .await?; + // Prepare Extrinsic. + let tx = match self.prepare_extrinsic(&api, &mut cli::Cli).await { + Ok(api) => api, + Err(e) => { + display_message(&e.to_string(), false, &mut cli::Cli)?; + return Ok(()); + }, + }; + // TODO: If call_data, go directly here. + // Finally execute the call. + if let Err(e) = self.send_extrinsic(api, tx, prompt_to_repeat_call, &mut cli::Cli).await { + display_message(&e.to_string(), false, &mut cli::Cli)?; + } Ok(()) } - /// Prompt the user for the chain to use if not indicated and fetch the metadata. - async fn set_up_api( + + /// Configure the call based on command line arguments/call UI. + async fn configure( &mut self, cli: &mut impl cli::traits::Cli, - ) -> anyhow::Result<(OnlineClient, String)> { - cli.intro("Call a parachain")?; - let url: String = if self.pallet.is_none() && self.extrinsic.is_none() { - // Prompt for contract location. - cli.input("Which chain would you like to interact with?") + repeat: bool, + ) -> Result> { + // Show intro on first run. + if !repeat { + cli.intro("Call a parachain")?; + } + // If extrinsic has been specified via command line arguments, return early. + // TODO: CALL DATA + // if self.extrinsic.is_some() { + // return Ok(()); + // } + + // Resolve url. + if !repeat && self.url.as_str() == DEFAULT_URL { + // Prompt for url. + let url: String = cli + .input("Which chain would you like to interact with?") .placeholder("wss://rpc1.paseo.popnetwork.xyz") .default_input("wss://rpc1.paseo.popnetwork.xyz") - .interact()? + .interact()?; + self.url = url::Url::parse(&url)? + }; + // Parse metadata from url chain. + let api = set_up_api(self.url.as_str()).await?; + let pallets = match parse_chain_metadata(api.clone()).await { + Ok(pallets) => pallets, + Err(e) => { + return Err(anyhow!(format!( + "Unable to fetch the chain metadata: {}", + e.to_string() + ))); + }, + }; + // Resolve pallet. + let pallet = if let Some(ref pallet_name) = self.pallet { + find_pallet_by_name(&api, pallet_name).await? } else { - self.url.clone() + let mut prompt = cli.select("Select the pallet to call:"); + for pallet_item in pallets { + prompt = prompt.item(pallet_item.clone(), &pallet_item.name, &pallet_item.docs); + } + let pallet_prompted = prompt.interact()?; + self.pallet = Some(pallet_prompted.name.clone()); + pallet_prompted }; - let api = set_up_api(&url).await?; - Ok((api, url)) + // Resolve extrinsic. + let extrinsic = { + let mut prompt_extrinsic = cli.select("Select the extrinsic to call:"); + for extrinsic in pallet.extrinsics { + prompt_extrinsic = prompt_extrinsic.item( + extrinsic.clone(), + &extrinsic.name, + &extrinsic.docs.concat(), + ); + } + prompt_extrinsic.interact()? + }; + self.extrinsic = Some(extrinsic.name); + // Resolve message arguments. + let mut contract_args = Vec::new(); + for arg in extrinsic.fields { + let arg_metadata = process_prompt_arguments(&api, &arg)?; + let input = prompt_argument(&api, &arg_metadata, cli)?; + contract_args.push(input); + } + self.args = contract_args; + + cli.info(self.display())?; + Ok(api) } fn display(&self) -> String { @@ -83,131 +148,100 @@ impl CallParachainCommand { full_message.push_str(&format!(" --extrinsic {}", extrinsic)); } if !self.args.is_empty() { - full_message.push_str(&format!(" --args {}", self.args.join(" "))); - } - full_message.push_str(&format!(" --url {}", self.url)); - if !self.suri.is_empty() { - full_message.push_str(&format!(" --suri {}", self.suri)); + let args: Vec<_> = self.args.iter().map(|a| format!("\"{a}\"")).collect(); + full_message.push_str(&format!(" --args {}", args.join(", "))); } + full_message.push_str(&format!(" --url {} --suri {}", self.url, self.suri)); full_message } -} - -/// Guide the user to call the contract. -async fn guide_user_to_call_chain( - api: &OnlineClient, - suri: &str, - url: &str, - cli: &mut impl cli::traits::Cli, -) -> anyhow::Result { - let extrinsic = { - let mut prompt_extrinsic = cli.select("What would you like to do?"); - //for extrinsic in pallet.extrinsics() { - for extrinsic in supported_extrinsics(api) { - prompt_extrinsic = prompt_extrinsic.item( - extrinsic.clone(), - extrinsic.description(), - extrinsic.pallet(), - ); - } - prompt_extrinsic.interact()? - }; - let args = prompt_arguments(&extrinsic, cli)?; - Ok(CallParachainCommand { - pallet: Some(extrinsic.pallet().to_string()), - extrinsic: Some(extrinsic.extrinsic_name().to_string()), - args, - url: url.to_string(), - suri: suri.to_string(), - }) -} + /// Prepares the extrinsic or query. + async fn prepare_extrinsic( + &self, + api: &OnlineClient, + cli: &mut impl cli::traits::Cli, + ) -> Result { + let extrinsic = match &self.extrinsic { + Some(extrinsic) => extrinsic.to_string(), + None => { + return Err(anyhow!("Please specify the extrinsic.")); + }, + }; + let pallet = match &self.pallet { + Some(pallet) => pallet.to_string(), + None => { + return Err(anyhow!("Please specify the pallet.")); + }, + }; + let tx = match construct_extrinsic(api, &pallet, &extrinsic, self.args.clone()).await { + Ok(tx) => tx, + Err(e) => { + return Err(anyhow!("Error parsing the arguments: {}", e)); + }, + }; + cli.info(format!("Encoded call data: {}", encode_call_data(api, &tx)?))?; + Ok(tx) + } -/// Prepares the extrinsic or query. -async fn prepare_and_submit_extrinsic( - api: OnlineClient, - call_config: &mut CallParachainCommand, - prompt_to_repeat_call: bool, - cli: &mut impl cli::traits::Cli, -) -> Result<()> { - let extrinsic: String = call_config - .extrinsic - .clone() - .expect("extrinsic can not be none as fallback above is interactive input; qed"); - let pallet = match call_config.pallet.clone() { - Some(m) => m, - None => { - return Err(anyhow!("Please specify the pallet to call.")); - }, - }; - let tx = match construct_extrinsic(&pallet, &extrinsic, call_config.args.clone()) { - Ok(tx) => tx, - Err(e) => { - display_message(&format!("Error parsing the arguments: {}", e), false, &mut cli::Cli)?; + async fn send_extrinsic( + &mut self, + api: OnlineClient, + tx: DynamicPayload, + prompt_to_repeat_call: bool, + cli: &mut impl cli::traits::Cli, + ) -> Result<()> { + if self.suri.is_empty() { + self.suri = cli::Cli + .input("Who is going to sign the extrinsic:") + .placeholder("//Alice") + .default_input("//Alice") + .interact()?; + } + cli.info(self.display())?; + if !cli.confirm("Do you want to submit the call?").initial_value(true).interact()? { + display_message( + &format!( + "Extrinsic {:?} was not submitted. Operation canceled by the user.", + self.extrinsic + ), + false, + cli, + )?; return Ok(()); - }, - }; - cli.info(format!("Encoded call data: {}", encode_call_data(&api, &tx)?))?; - if call_config.suri.is_empty() { - call_config.suri = cli::Cli - .input("Who is going to sign the extrinsic:") - .placeholder("//Alice") - .default_input("//Alice") - .interact()?; - } - cli.info(call_config.display())?; - if !cli.confirm("Do you want to submit the call?").initial_value(true).interact()? { - display_message( - &format!("Extrinsic {} was not submitted. Operation canceled by the user.", extrinsic), - false, - cli, - )?; - return Ok(()); - } - send_extrinsic(api, tx, &call_config.url, &call_config.suri, prompt_to_repeat_call, cli) - .await?; + } + let spinner = cliclack::spinner(); + spinner.start("Signing and submitting the extrinsic, please wait..."); + let result = sign_and_submit_extrinsic(api.clone(), tx, &self.suri) + .await + .map_err(|err| anyhow!("{} {}", "ERROR:", format!("{err:?}")))?; - Ok(()) -} + display_message(&format!("Extrinsic submitted with hash: {:?}", result), true, cli)?; -async fn send_extrinsic( - api: OnlineClient, - tx: DynamicPayload, - url: &str, - suri: &str, - prompt_to_repeat_call: bool, - cli: &mut impl cli::traits::Cli, -) -> Result<()> { - let spinner = cliclack::spinner(); - spinner.start("Signing and submitting the extrinsic, please wait..."); - let result = sign_and_submit_extrinsic(api.clone(), tx, suri).await; - if let Err(e) = result { - console::Term::stderr().clear_last_lines(1)?; - display_message(&format!("{}", e), false, cli)?; - } else { - console::Term::stderr().clear_last_lines(1)?; - display_message(&format!("Extrinsic submitted with hash: {:?}", result?), true, cli)?; - // Repeat call. - if prompt_to_repeat_call { - let another_call: bool = cli - .confirm("Do you want to do another call to the same chain?") - .initial_value(false) - .interact()?; - if another_call { - // Remove only the prompt asking for another call. - console::Term::stderr().clear_last_lines(2)?; - let mut new_call_config = guide_user_to_call_chain(&api, suri, url, cli).await?; - Box::pin(prepare_and_submit_extrinsic( - api, - &mut new_call_config, - prompt_to_repeat_call, - cli, - )) - .await?; - } + // Prompt for any additional calls. + if !prompt_to_repeat_call { + display_message("Call completed successfully!", true, cli)?; + return Ok(()); + } + if cli + .confirm("Do you want to perform another call to the same chain?") + .initial_value(false) + .interact()? + { + // Reset specific items from the last call and repeat. + self.reset_for_new_call(); + self.configure(cli, true).await?; + let tx = self.prepare_extrinsic(&api, &mut cli::Cli).await?; + Box::pin(self.send_extrinsic(api, tx, prompt_to_repeat_call, cli)).await + } else { + display_message("Parachain calling complete.", true, cli)?; + Ok(()) } } - Ok(()) + /// Resets specific fields to default values for a new call. + fn reset_for_new_call(&mut self) { + self.pallet = None; + self.extrinsic = None; + } } fn display_message(message: &str, success: bool, cli: &mut impl cli::traits::Cli) -> Result<()> { @@ -218,122 +252,88 @@ fn display_message(message: &str, success: bool, cli: &mut impl cli::traits::Cli } Ok(()) } -// Prompt the user to select an operation. -fn prompt_arguments(extrinsic: &Extrinsic, cli: &mut impl cli::traits::Cli) -> Result> { - let mut args: Vec = Vec::new(); - match extrinsic { - Extrinsic::CreateAsset => { - args.push(prompt_for_numeric_value("Enter the Asset ID", cli)?); - args.push(prompt_for_account("Enter the Admin Address", cli)?); - args.push(prompt_for_numeric_value("Enter the Minimum Balance", cli)?); - }, - Extrinsic::MintAsset => { - args.push(prompt_for_numeric_value("Enter the Asset ID", cli)?); - args.push(prompt_for_account("Enter the Beneficiary Address", cli)?); - args.push(prompt_for_numeric_value("Enter the Amount", cli)?); - }, - Extrinsic::CreateCollection => { - args.push(prompt_for_account("Enter the Admin Address", cli)?); - args.extend(prompt_for_collection_config(cli)?); - }, - Extrinsic::MintNFT => { - args.push(prompt_for_numeric_value("Enter the Collection ID", cli)?); - args.push(prompt_for_numeric_value("Enter the Item ID", cli)?); - args.push(prompt_for_account("Enter the Beneficiary Address", cli)?); - args.extend(prompt_for_witness_data(cli)?); - }, - Extrinsic::Transfer => { - args.push(prompt_for_account("Enter the Destination Address", cli)?); - args.push(prompt_for_numeric_value("Enter the Amount", cli)?); - }, - } - Ok(args) + +// Prompt the user for the proper arguments. +fn prompt_argument( + api: &OnlineClient, + arg: &Arg, + cli: &mut impl cli::traits::Cli, +) -> Result { + Ok(if arg.optional { + // The argument is optional; prompt the user to decide whether to provide a value. + if !cli + .confirm(format!( + "Do you want to provide a value for the optional parameter: {}?", + arg.name + )) + .interact()? + { + return Ok("None".to_string()); + } + let value = prompt_argument_value(api, arg, cli)?; + format!("Some({})", value) + } else { + // Non-optional argument. + prompt_argument_value(api, arg, cli)? + }) } -fn prompt_for_numeric_value(message: &str, cli: &mut impl cli::traits::Cli) -> Result { - let id = cli - .input(message) - .placeholder("0") - .default_input("0") - .validate(|input: &String| match input.parse::() { - Ok(_) => Ok(()), - Err(_) => Err("Invalid value."), - }) - .required(true) - .interact()?; - Ok(id) + +fn prompt_argument_value( + api: &OnlineClient, + arg: &Arg, + cli: &mut impl cli::traits::Cli, +) -> Result { + if arg.options.is_empty() { + prompt_for_primitive(arg, cli) + } else if arg.variant { + prompt_for_variant(api, arg, cli) + } else { + prompt_for_composite(api, arg, cli) + } } -fn prompt_for_account(message: &str, cli: &mut impl cli::traits::Cli) -> Result { - let account: String = cli - .input(message) - .placeholder("e.g. 5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") - .required(true) + +fn prompt_for_primitive(arg: &Arg, cli: &mut impl cli::traits::Cli) -> Result { + let user_input = cli + .input(format!("Enter the value for the parameter: {}", arg.name)) + .placeholder(&format!("Type required: {}", arg.type_input)) .interact()?; - Ok(account) + Ok(user_input) } -fn prompt_for_numeric_optional_value( - message: &str, + +fn prompt_for_variant( + api: &OnlineClient, + arg: &Arg, cli: &mut impl cli::traits::Cli, ) -> Result { - let value = cli - .input(message) - .placeholder("0 or (empty for None)") - .validate(|input: &String| match input.parse::() { - Ok(_) => Ok(()), - Err(_) => - if input.is_empty() || input == "None" { - Ok(()) - } else { - Err("Invalid value.") - }, - }) - .required(false) - .interact()?; - if value.is_empty() || value == "None" { - Ok("None".to_string()) + let selected_variant = { + let mut select = cli.select(format!("Select the value for the parameter: {}", arg.name)); + for option in &arg.options { + select = select.item(option, &option.name, &option.type_input); + } + select.interact()? + }; + + if !selected_variant.options.is_empty() { + let mut field_values = Vec::new(); + for field_arg in &selected_variant.options { + let field_value = prompt_argument(api, field_arg, cli)?; + field_values.push(field_value); + } + Ok(format!("{}({})", selected_variant.name, field_values.join(", "))) } else { - Ok(value) + Ok(selected_variant.name.clone()) } } -fn prompt_for_variant_value( - message: &str, - default_value: &str, + +fn prompt_for_composite( + api: &OnlineClient, + arg: &Arg, cli: &mut impl cli::traits::Cli, ) -> Result { - let mint_type: String = cli - .input(message) - .placeholder(&format!("e.g. {}", default_value)) - .default_input(default_value) - .required(true) - .interact()?; - Ok(mint_type) -} -fn prompt_for_collection_config(cli: &mut impl cli::traits::Cli) -> Result> { - let mut args: Vec = Vec::new(); - cli.info("Enter the Pallet NFT Collection Config:")?; - args.push(prompt_for_numeric_value("Collection's Settings", cli)?); - args.push(prompt_for_numeric_optional_value("Collection's Max Supply", cli)?); - cli.info("Enter the Mint Settings:")?; - args.push(prompt_for_variant_value("Who can mint?", "Issuer", cli)?); - args.push(prompt_for_numeric_optional_value("Price per mint", cli)?); - args.push(prompt_for_numeric_optional_value("When the mint starts", cli)?); - args.push(prompt_for_numeric_optional_value("When the mint ends", cli)?); - args.push(prompt_for_numeric_value("Default Item Settings", cli)?); - Ok(args) -} -fn prompt_for_witness_data(cli: &mut impl cli::traits::Cli) -> Result> { - let mut args: Vec = Vec::new(); - if cli - .confirm("Do you want to enter witness data for mint") - .initial_value(false) - .interact()? - { - args.push(prompt_for_numeric_optional_value( - "Id of the item in a required collection:", - cli, - )?); - args.push(prompt_for_numeric_optional_value("Mint price:", cli)?); - } else { - args.push("None".to_string()); + let mut field_values = Vec::new(); + for field_arg in &arg.options { + let field_value = prompt_argument(api, field_arg, cli)?; + field_values.push(field_value); } - Ok(args) + Ok(field_values.join(", ")) } diff --git a/crates/pop-parachains/src/call.rs b/crates/pop-parachains/src/call.rs index 93452638c..485234552 100644 --- a/crates/pop-parachains/src/call.rs +++ b/crates/pop-parachains/src/call.rs @@ -1,107 +1,26 @@ // SPDX-License-Identifier: GPL-3.0 -use crate::errors::Error; -use pop_common::{create_signer, parse_account}; -use strum::{EnumMessage as _, EnumProperty as _, VariantArray as _}; -use strum_macros::{AsRefStr, Display, EnumMessage, EnumProperty, EnumString, VariantArray}; +use crate::{errors::Error, utils::metadata::process_extrinsic_args}; +use pop_common::create_signer; use subxt::{ dynamic::Value, tx::{DynamicPayload, Payload}, OnlineClient, SubstrateConfig, }; -#[derive(AsRefStr, Clone, Debug, Display, EnumMessage, EnumString, Eq, PartialEq, VariantArray)] -pub enum Pallet { - #[strum(serialize = "Assets")] - Assets, - #[strum(serialize = "Balances")] - Balances, - #[strum(serialize = "Nfts")] - Nfts, -} - -#[derive( - AsRefStr, - Clone, - Debug, - Display, - EnumMessage, - EnumString, - EnumProperty, - Eq, - PartialEq, - VariantArray, -)] -pub enum Extrinsic { - #[strum( - serialize = "create", - message = "create", - detailed_message = "Create an Asset", - props(Pallet = "Assets") - )] - CreateAsset, - #[strum( - serialize = "mint", - message = "mint", - detailed_message = "Mint an Asset", - props(Pallet = "Assets") - )] - MintAsset, - #[strum( - serialize = "create_nft", - message = "create", - detailed_message = "Create a NFT Collection", - props(Pallet = "Nfts") - )] - CreateCollection, - #[strum( - serialize = "mint_nft", - message = "mint", - detailed_message = "Mint a NFT", - props(Pallet = "Nfts") - )] - MintNFT, - #[strum( - serialize = "transfer", - message = "transfer_allow_death", - detailed_message = "Transfer Balance", - props(Pallet = "Balances") - )] - Transfer, -} -impl Extrinsic { - /// Get the extrinsic's name. - pub fn extrinsic_name(&self) -> &str { - self.get_message().unwrap_or_default() - } - /// Get the description of the extrinsic. - pub fn description(&self) -> &str { - self.get_detailed_message().unwrap_or_default() - } - /// Get the pallet of the extrinsic. - pub fn pallet(&self) -> &str { - self.get_str("Pallet").unwrap_or_default() - } -} - pub async fn set_up_api(url: &str) -> Result, Error> { let api = OnlineClient::::from_url(url).await?; Ok(api) } -pub fn supported_extrinsics(api: &OnlineClient) -> Vec<&Extrinsic> { - Extrinsic::VARIANTS - .iter() - .filter(|t| extrinsic_is_supported(api, t.pallet(), t.extrinsic_name())) - .collect() -} - -pub fn construct_extrinsic( +pub async fn construct_extrinsic( + api: &OnlineClient, pallet_name: &str, extrinsic_name: &str, args: Vec, ) -> Result { - let parsed_args: Vec = parse_args(pallet_name, extrinsic_name, args)?; + let parsed_args: Vec = + process_extrinsic_args(api, pallet_name, extrinsic_name, args).await?; Ok(subxt::dynamic::tx(pallet_name, extrinsic_name, parsed_args)) } @@ -129,131 +48,3 @@ pub fn encode_call_data( let call_data = tx.encode_call_data(&api.metadata())?; Ok(format!("0x{}", hex::encode(call_data))) } - -fn parse_args( - pallet_name: &str, - extrinsic_name: &str, - raw_args: Vec, -) -> Result, Error> { - let mut args: Vec = Vec::new(); - let extrinsic = Extrinsic::VARIANTS - .iter() - .find(|t| t.pallet() == pallet_name && t.extrinsic_name() == extrinsic_name) - .ok_or(Error::ExtrinsicNotSupported(extrinsic_name.to_string()))?; - match extrinsic { - Extrinsic::CreateAsset => { - if raw_args.len() < 3 { - return Err(Error::ParsingArgsError); - } - args.push(parse_u128(&raw_args[0])?); - args.push(parse_account_id(&raw_args[1])?); - args.push(parse_u128(&raw_args[2])?); - }, - Extrinsic::MintAsset => { - if raw_args.len() < 3 { - return Err(Error::ParsingArgsError); - } - args.push(parse_u128(&raw_args[0])?); - args.push(parse_account_id(&raw_args[1])?); - args.push(parse_u128(&raw_args[2])?); - }, - Extrinsic::CreateCollection => { - if raw_args.len() < 7 { - return Err(Error::ParsingArgsError); - } - args.push(parse_account_id(&raw_args[0])?); - let mint_settings = Value::unnamed_composite(vec![ - Value::unnamed_variant(&raw_args[3], vec![]), - parse_optional_u128(&raw_args[4])?, - parse_optional_u128(&raw_args[5])?, - parse_optional_u128(&raw_args[6])?, - parse_u128(&raw_args[7])?, - ]); - let max_supply = parse_optional_u128(&raw_args[2])?; - args.push(Value::unnamed_composite(vec![ - parse_u128(&raw_args[1])?, - max_supply, - mint_settings, - ])) - }, - Extrinsic::MintNFT => { - println!("{:?}", raw_args.len()); - if raw_args.len() < 4 { - return Err(Error::ParsingArgsError); - } - args.push(parse_u128(&raw_args[0])?); - args.push(parse_u128(&raw_args[1])?); - args.push(parse_account_id(&raw_args[2])?); - if raw_args[3] == "None" && raw_args.len() == 4 { - args.push(Value::unnamed_variant("None", vec![])); - } else { - if raw_args.len() < 5 { - return Err(Error::ParsingArgsError); - } - let owned_item = parse_optional_u128(&raw_args[3])?; - let mint_price = parse_optional_u128(&raw_args[4])?; - args.push(Value::unnamed_variant( - "Some", - vec![Value::unnamed_composite(vec![owned_item, mint_price])], - )); - } - }, - Extrinsic::Transfer => { - if raw_args.len() < 2 { - return Err(Error::ParsingArgsError); - } - args.push(parse_account_id(&raw_args[0])?); - args.push(parse_u128(&raw_args[1])?); - }, - } - Ok(args) -} - -// Helper function to parse u128 -fn parse_u128(arg: &str) -> Result { - Ok(Value::u128(arg.parse::().map_err(|_| Error::ParsingArgsError)?)) -} -// Helper function to parse account id -fn parse_account_id(arg: &str) -> Result { - Ok(Value::unnamed_variant("Id", vec![Value::from_bytes(parse_account(arg)?)])) -} -// Helper function to handle "None" or Some(u128) values -fn parse_optional_u128(arg: &str) -> Result { - if arg == "None" { - Ok(Value::unnamed_variant("None", vec![])) - } else { - Ok(Value::unnamed_variant("Some", vec![parse_u128(arg)?])) - } -} - -fn extrinsic_is_supported( - api: &OnlineClient, - pallet_name: &str, - extrinsic: &str, -) -> bool { - let metadata = api.metadata(); - // Try to get the pallet metadata by name - let pallet_metadata = match metadata.pallet_by_name(pallet_name) { - Some(pallet) => pallet, - None => return false, // Return false if pallet is not found - }; - // Try to get the extrinsic metadata by name from the pallet - if pallet_metadata.call_variant_by_name(extrinsic).is_some() { - return true; - } - false -} - -#[cfg(test)] -mod tests { - use super::*; - use anyhow::Result; - - #[tokio::test] - async fn extrinsic_is_supported_works() -> Result<()> { - let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; - assert!(extrinsic_is_supported(&api, "Nfts", "mint")); - assert!(!extrinsic_is_supported(&api, "Nfts", "mint_no_exist")); - Ok(()) - } -} diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index 53f603b11..47ae30f5a 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -15,10 +15,7 @@ pub use build::{ binary_path, build_parachain, export_wasm_file, generate_genesis_state_file, generate_plain_chain_spec, generate_raw_chain_spec, is_supported, ChainSpec, }; -pub use call::{ - construct_extrinsic, encode_call_data, set_up_api, sign_and_submit_extrinsic, - supported_extrinsics, Extrinsic, Pallet, -}; +pub use call::{construct_extrinsic, encode_call_data, set_up_api, sign_and_submit_extrinsic}; pub use errors::Error; pub use indexmap::IndexSet; pub use new_pallet::{create_pallet_template, new_pallet_options::*, TemplatePalletConfig}; @@ -27,6 +24,9 @@ pub use new_parachain::instantiate_template_dir; pub use subxt::{dynamic::Value, tx::DynamicPayload, OnlineClient, SubstrateConfig}; pub use templates::{Config, Parachain, Provider}; pub use up::Zombienet; -pub use utils::helpers::is_initial_endowment_valid; +pub use utils::{ + helpers::is_initial_endowment_valid, + metadata::{find_pallet_by_name, parse_chain_metadata, process_prompt_arguments, Arg}, +}; /// Information about the Node. External export from Zombienet-SDK. pub use zombienet_sdk::NetworkNode; diff --git a/crates/pop-parachains/src/utils/metadata.rs b/crates/pop-parachains/src/utils/metadata.rs new file mode 100644 index 000000000..27b13f5a5 --- /dev/null +++ b/crates/pop-parachains/src/utils/metadata.rs @@ -0,0 +1,508 @@ +// SPDX-License-Identifier: GPL-3.0 + +use crate::errors::Error; +use pop_common::parse_account; +use scale_info::{ + form::PortableForm, Field, PortableRegistry, Type, TypeDef, TypeDefCompact, TypeDefComposite, + TypeDefPrimitive, TypeDefTuple, TypeDefVariant, Variant, +}; +use subxt::{dynamic::Value, Metadata, OnlineClient, SubstrateConfig}; + +#[derive(Clone, PartialEq, Eq)] +/// Describes a pallet with its extrinsics. +pub struct Pallet { + /// The name of the pallet. + pub name: String, + /// The documentation of the pallet. + pub docs: String, + // The extrinsics of the pallet. + pub extrinsics: Vec>, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Arg { + pub name: String, + pub type_input: String, + pub optional: bool, + pub options: Vec, + pub variant: bool, +} + +/// Parses the chain metadata to extract information about pallets and their extrinsics. +pub async fn parse_chain_metadata( + api: OnlineClient, +) -> Result, Error> { + let metadata: Metadata = api.metadata(); + Ok(metadata + .pallets() + .map(|pallet| { + let extrinsics = + pallet.call_variants().map(|variants| variants.to_vec()).unwrap_or_default(); + Pallet { name: pallet.name().to_string(), extrinsics, docs: pallet.docs().join(" ") } + }) + .collect()) +} + +pub async fn find_pallet_by_name( + api: &OnlineClient, + pallet_name: &str, +) -> Result { + let metadata: Metadata = api.metadata(); + for pallet in metadata.pallets() { + if pallet.name() == pallet_name { + let extrinsics = + pallet.call_variants().map(|variants| variants.to_vec()).unwrap_or_default(); + return Ok(Pallet { + name: pallet.name().to_string(), + extrinsics, + docs: pallet.docs().join(" "), + }); + } + } + Err(Error::PalletNotFound(pallet_name.to_string())) +} + +async fn find_extrinsic_by_name( + api: &OnlineClient, + pallet_name: &str, + extrinsic_name: &str, +) -> Result, Error> { + let pallet = find_pallet_by_name(api, pallet_name).await?; + // Check if the specified extrinsic exists within this pallet + if let Some(extrinsic) = pallet.extrinsics.iter().find(|&e| e.name == extrinsic_name) { + return Ok(extrinsic.clone()); + } else { + return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); + } +} + +pub async fn process_extrinsic_args( + api: &OnlineClient, + pallet_name: &str, + extrinsic_name: &str, + args: Vec, +) -> Result, Error> { + let metadata: Metadata = api.metadata(); + let registry = metadata.types(); + let extrinsic = find_extrinsic_by_name(&api, pallet_name, extrinsic_name).await?; + + let mut return_args: Vec = Vec::new(); + for (index, field) in extrinsic.fields.iter().enumerate() { + let arg_input = args.get(index).ok_or(Error::ParsingArgsError)?; + let type_info = registry.resolve(field.ty.id).ok_or(Error::ParsingArgsError)?; //Resolve with type_id + let arg_processed = process_value(arg_input, type_info, registry)?; + return_args.push(arg_processed); + } + Ok(return_args) +} + +pub fn process_value( + arg: &str, + ty: &Type, + registry: &PortableRegistry, +) -> Result { + let type_path = ty.path.segments.join("::"); + match type_path.as_str() { + "Option" => handle_option_type(arg, ty, registry), + "sp_core::crypto::AccountId32" => Ok(Value::from_bytes(parse_account(arg)?)), + _ => match &ty.type_def { + TypeDef::Primitive(primitive) => handle_primitive_type(arg, primitive), + TypeDef::Composite(composite) => handle_composite_type(arg, composite, registry), + TypeDef::Variant(variant_def) => handle_variant_type(arg, variant_def, registry), + TypeDef::Tuple(tuple) => handle_tuple_type(arg, tuple, registry), + TypeDef::Compact(compact) => handle_compact_type(arg, compact, registry), + TypeDef::Sequence(_) | TypeDef::Array(_) => Ok(Value::from_bytes(arg)), + _ => Err(Error::ParsingArgsError), + }, + } +} +fn handle_option_type( + arg: &str, + ty: &Type, + registry: &PortableRegistry, +) -> Result { + // Handle Option + if arg.trim() == "None" { + Ok(Value::unnamed_variant("None", vec![])) + } else if arg.trim().starts_with("Some(") && arg.trim().ends_with(')') { + let inner_arg = &arg.trim()[5..arg.trim().len() - 1]; + if let Some(inner_type_id) = ty.type_params.get(0).and_then(|param| param.ty) { + let inner_ty = registry.resolve(inner_type_id.id()).ok_or(Error::ParsingArgsError)?; + let inner_value = process_value(inner_arg.trim(), inner_ty, registry)?; + Ok(Value::unnamed_variant("Some", vec![inner_value])) + } else { + Err(Error::ParsingArgsError) + } + } else { + Err(Error::ParsingArgsError) + } +} + +fn handle_primitive_type(arg: &str, primitive: &TypeDefPrimitive) -> Result { + match primitive { + TypeDefPrimitive::Bool => { + Ok(Value::bool(arg.parse::().map_err(|_| Error::ParsingArgsError)?)) + }, + TypeDefPrimitive::Char => { + Ok(Value::char(arg.chars().next().ok_or(Error::ParsingArgsError)?)) + }, + TypeDefPrimitive::Str => Ok(Value::string(arg.to_string())), + TypeDefPrimitive::U8 + | TypeDefPrimitive::U16 + | TypeDefPrimitive::U32 + | TypeDefPrimitive::U64 + | TypeDefPrimitive::U128 + | TypeDefPrimitive::U256 => { + Ok(Value::u128(arg.parse::().map_err(|_| Error::ParsingArgsError)?)) + }, + TypeDefPrimitive::I8 + | TypeDefPrimitive::I16 + | TypeDefPrimitive::I32 + | TypeDefPrimitive::I64 + | TypeDefPrimitive::I128 + | TypeDefPrimitive::I256 => { + Ok(Value::i128(arg.parse::().map_err(|_| Error::ParsingArgsError)?)) + }, + } +} +fn handle_composite_type( + arg: &str, + composite: &TypeDefComposite, + registry: &PortableRegistry, +) -> Result { + let arg_trimmed = arg.trim(); + let inner = if arg_trimmed.starts_with('{') && arg_trimmed.ends_with('}') { + &arg_trimmed[1..arg_trimmed.len() - 1] + } else { + arg_trimmed + }; + let sub_args = split_top_level_commas(inner)?; + if sub_args.len() != composite.fields.len() { + return Err(Error::ParsingArgsError); + } + let mut values = Vec::new(); + for (field, sub_arg) in composite.fields.iter().zip(sub_args.iter()) { + let sub_ty = registry.resolve(field.ty.id).ok_or(Error::ParsingArgsError)?; + let value = process_value(sub_arg.trim(), sub_ty, registry)?; + let field_name = field.name.clone().unwrap_or_default(); + values.push((field_name, value)); + } + Ok(Value::named_composite(values)) +} +fn handle_variant_type( + arg: &str, + variant: &TypeDefVariant, + registry: &PortableRegistry, +) -> Result { + // Handle variants like Some(value1, value2, ...) + let input = arg.trim(); + let (variant_name, variant_data) = if let Some(start) = input.find('(') { + if !input.ends_with(')') { + return Err(Error::ParsingArgsError); + } + let name = input[..start].trim(); + let data_str = &input[start + 1..input.len() - 1]; + (name, Some(data_str)) + } else { + let name = input.trim(); + (name, None) + }; + + // Find the variant definition + let variant_def = variant + .variants + .iter() + .find(|v| v.name == variant_name) + .ok_or(Error::ParsingArgsError)?; + + // Handle variant fields + let fields_values = if let Some(data_str) = variant_data { + let inputs = split_top_level_commas(data_str)?; + if inputs.len() != variant_def.fields.len() { + return Err(Error::ParsingArgsError); + } + + let mut values = Vec::new(); + for (field_def, field_input) in variant_def.fields.iter().zip(inputs.iter()) { + let field_ty = registry.resolve(field_def.ty.id).ok_or(Error::ParsingArgsError)?; + let field_value = process_value(field_input.trim(), field_ty, registry)?; + values.push(field_value); + } + values + } else if variant_def.fields.is_empty() { + vec![] + } else { + // Variant has fields but no data provided + return Err(Error::ParsingArgsError); + }; + + Ok(Value::unnamed_variant(variant_name, fields_values)) +} +fn handle_tuple_type( + arg: &str, + tuple: &TypeDefTuple, + registry: &PortableRegistry, +) -> Result { + let arg_trimmed = arg.trim(); + let inner = if arg_trimmed.starts_with('(') && arg_trimmed.ends_with(')') { + &arg_trimmed[1..arg_trimmed.len() - 1] + } else { + arg_trimmed + }; + let sub_args = split_top_level_commas(inner)?; + if sub_args.len() != tuple.fields.len() { + return Err(Error::ParsingArgsError); + } + let mut values = Vec::new(); + for (sub_ty_id, sub_arg) in tuple.fields.iter().zip(sub_args.iter()) { + let sub_ty = registry.resolve(sub_ty_id.id()).ok_or(Error::ParsingArgsError)?; + let value = process_value(sub_arg.trim(), sub_ty, registry)?; + values.push(value); + } + Ok(Value::unnamed_composite(values)) +} +fn handle_compact_type( + arg: &str, + compact: &TypeDefCompact, + registry: &PortableRegistry, +) -> Result { + let inner_ty = registry.resolve(compact.type_param.id()).ok_or(Error::ParsingArgsError)?; + process_value(arg, inner_ty, registry) +} +fn split_top_level_commas(s: &str) -> Result, Error> { + let mut result = Vec::new(); + let mut brace_depth = 0; + let mut paren_depth = 0; + let mut last_index = 0; + for (i, c) in s.char_indices() { + match c { + '{' => brace_depth += 1, + '}' => brace_depth -= 1, + '(' => paren_depth += 1, + ')' => paren_depth -= 1, + ',' if brace_depth == 0 && paren_depth == 0 => { + result.push(&s[last_index..i]); + last_index = i + 1; + }, + _ => (), + } + } + if brace_depth != 0 || paren_depth != 0 { + return Err(Error::ParsingArgsError); + } + result.push(&s[last_index..]); + Ok(result) +} + +/// Processes an argument by constructing its `Arg` representation, including type information. +pub fn process_prompt_arguments( + api: &OnlineClient, + field: &Field, +) -> Result { + let type_id = field.ty().id(); + let metadata = api.metadata(); + let registry = metadata.types(); + let name = format!("{:?}", field.name()); + let type_name = field.type_name(); + parse_type(registry, type_id, name, type_name) +} + +fn parse_type( + registry: &PortableRegistry, + type_id: u32, + name: String, + type_name: Option<&String>, +) -> Result { + let type_info = registry.resolve(type_id).ok_or(Error::ParsingArgsError)?; + // Check if the type is Option by checking the path segments + if type_info.path.segments == ["Option"] { + // The type is Option + // Get the inner type T from type parameters + if let Some(inner_type_id) = type_info.type_params.get(0).and_then(|param| param.ty) { + let inner_arg = parse_type(registry, inner_type_id.id(), name.clone(), type_name)?; + Ok(Arg { + name, + type_input: inner_arg.type_input, + optional: true, + options: inner_arg.options, + variant: false, + }) + } else { + // Unable to get inner type + Err(Error::ParsingArgsError) + } + } else { + let type_input = get_type_name(registry, type_info); + match &type_info.type_def { + TypeDef::Primitive(_) => { + Ok(Arg { name, type_input, optional: false, options: vec![], variant: false }) + }, + TypeDef::Composite(composite) => { + let mut composite_fields = vec![]; + + for composite_field in composite.fields() { + if let Some(name) = composite_field.name() { + let field_name = format!("{:?}", composite_field.name()); + let field_type_id = composite_field.ty().id(); + let field_type_name = composite_field.type_name(); + + let field_arg = + parse_type(registry, field_type_id, field_name, field_type_name)?; + composite_fields.push(field_arg); + } + } + + Ok(Arg { + name, + type_input, + optional: false, + options: composite_fields, + variant: false, + }) + }, + TypeDef::Variant(variant) => { + // Regular enum handling for non-option variants + let mut variant_fields = vec![]; + for variant in variant.variants() { + let variant_name = variant.name().to_string(); + + let mut fields = vec![]; + for field in variant.fields() { + let field_name = format!("{:?}", field.name()); + let field_type_id = field.ty().id(); + let field_type_name = field.type_name(); + + let field_arg = parse_type( + registry, + field_type_id, + variant_name.clone(), + field_type_name, + )?; + fields.push(field_arg); + } + + variant_fields.push(Arg { + name: variant_name, + type_input: "".to_string(), + optional: false, + options: fields, + variant: false, + }); + } + + Ok(Arg { + name, + type_input, + optional: false, + options: variant_fields, + variant: true, + }) + }, + TypeDef::Array(_) | TypeDef::Sequence(_) | TypeDef::Tuple(_) | TypeDef::Compact(_) => { + Ok(Arg { name, type_input, optional: false, options: vec![], variant: false }) + }, + _ => Err(Error::ParsingArgsError), + } + } +} + +fn get_type_name(registry: &PortableRegistry, type_info: &Type) -> String { + if !type_info.path.segments.is_empty() { + type_info.path.segments.join("::") + } else { + match &type_info.type_def { + TypeDef::Primitive(primitive) => format!("{:?}", primitive), + TypeDef::Array(array) => { + // Get the inner type of Compact + if let Some(inner_type_info) = registry.resolve(array.type_param().id()) { + get_type_name(registry, inner_type_info) + } else { + "Compact".to_string() + } + }, + TypeDef::Sequence(sequence) => { + // Get the inner type of Compact + if let Some(inner_type_info) = registry.resolve(sequence.type_param().id()) { + get_type_name(registry, inner_type_info) + } else { + "Compact".to_string() + } + }, + TypeDef::Tuple(tuple) => { + let field_types: Vec = tuple + .fields() + .iter() + .map(|field| { + if let Some(field_type_info) = registry.resolve(field.id()) { + get_type_name(registry, field_type_info) + } else { + "Unknown".to_string() + } + }) + .collect(); + format!("({})", field_types.join(", ")) + }, + TypeDef::Compact(compact) => { + // Get the inner type of Compact + if let Some(inner_type_info) = registry.resolve(compact.type_param().id()) { + get_type_name(registry, inner_type_info) + } else { + "Compact".to_string() + } + }, + _ => "Unknown Type".to_string(), + } + } +} + +// #[cfg(test)] +// mod tests { +// use crate::set_up_api; + +// use super::*; +// use anyhow::Result; + +// #[tokio::test] +// async fn process_prompt_arguments_works() -> Result<()> { +// let api = set_up_api("ws://127.0.0.1:9944").await?; +// // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; +// let ex = find_extrinsic_by_name(&api, "Nfts", "mint").await?; +// let prompt_args1 = process_prompt_arguments(&api, &ex.fields()[2])?; + +// Ok(()) +// } + +// #[tokio::test] +// async fn process_extrinsic_args_works() -> Result<()> { +// let api = set_up_api("ws://127.0.0.1:9944").await?; +// // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; +// let ex = find_extrinsic_by_name(&api, "Nfts", "mint").await?; +// let args_parsed = process_extrinsic_args( +// &api, +// "Nfts", +// "mint", +// vec![ +// "1".to_string(), +// "1".to_string(), +// "Id(5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y)".to_string(), +// "Some(Some(1), Some(1))".to_string(), +// ], +// ) +// .await?; +// println!(" ARGS PARSER {:?}", args_parsed); + +// Ok(()) +// } + +// #[tokio::test] +// async fn process_extrinsic_args2_works() -> Result<()> { +// let api = set_up_api("ws://127.0.0.1:9944").await?; +// // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; +// let ex = find_extrinsic_by_name(&api, "Nfts", "mint").await?; +// let args_parsed = +// process_extrinsic_args(&api, "System", "remark", vec!["0x11".to_string()]).await?; +// println!(" ARGS PARSER {:?}", args_parsed); + +// Ok(()) +// } +// } diff --git a/crates/pop-parachains/src/utils/mod.rs b/crates/pop-parachains/src/utils/mod.rs index 265ebafd4..a9f3ad9a8 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 helpers; +pub mod metadata; From cd4625057b84d2ab3caa99ceaa59a92d1002c711 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Wed, 20 Nov 2024 10:06:28 +0100 Subject: [PATCH 057/211] refactor: reorganize and clean metadata functions --- Cargo.lock | 1 + crates/pop-cli/src/commands/call/parachain.rs | 86 +-- crates/pop-common/Cargo.toml | 1 + crates/pop-common/src/lib.rs | 2 + crates/pop-common/src/metadata.rs | 151 ++++++ crates/pop-contracts/src/utils/metadata.rs | 147 +---- crates/pop-parachains/src/lib.rs | 2 +- crates/pop-parachains/src/utils/metadata.rs | 508 ------------------ .../pop-parachains/src/utils/metadata/mod.rs | 296 ++++++++++ .../src/utils/metadata/type_parser.rs | 162 ++++++ 10 files changed, 659 insertions(+), 697 deletions(-) create mode 100644 crates/pop-common/src/metadata.rs delete mode 100644 crates/pop-parachains/src/utils/metadata.rs create mode 100644 crates/pop-parachains/src/utils/metadata/mod.rs create mode 100644 crates/pop-parachains/src/utils/metadata/type_parser.rs diff --git a/Cargo.lock b/Cargo.lock index aa0bf52c5..ec363647e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4708,6 +4708,7 @@ dependencies = [ "mockito", "regex", "reqwest 0.12.5", + "scale-info", "serde", "serde_json", "strum 0.26.3", diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 30f54dc44..cbff1f231 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -4,9 +4,9 @@ use crate::cli::{self, traits::*}; use anyhow::{anyhow, Result}; use clap::Args; use pop_parachains::{ - construct_extrinsic, encode_call_data, find_pallet_by_name, parse_chain_metadata, - process_prompt_arguments, set_up_api, sign_and_submit_extrinsic, Arg, DynamicPayload, - OnlineClient, SubstrateConfig, + construct_extrinsic, encode_call_data, field_to_param, find_pallet_by_name, + parse_chain_metadata, set_up_api, sign_and_submit_extrinsic, DynamicPayload, OnlineClient, + Param, SubstrateConfig, }; const DEFAULT_URL: &str = "ws://localhost:9944/"; @@ -92,7 +92,7 @@ impl CallParachainCommand { }; // Parse metadata from url chain. let api = set_up_api(self.url.as_str()).await?; - let pallets = match parse_chain_metadata(api.clone()).await { + let pallets = match parse_chain_metadata(&api).await { Ok(pallets) => pallets, Err(e) => { return Err(anyhow!(format!( @@ -128,9 +128,9 @@ impl CallParachainCommand { self.extrinsic = Some(extrinsic.name); // Resolve message arguments. let mut contract_args = Vec::new(); - for arg in extrinsic.fields { - let arg_metadata = process_prompt_arguments(&api, &arg)?; - let input = prompt_argument(&api, &arg_metadata, cli)?; + for field in extrinsic.fields { + let param = field_to_param(&api, &field)?; + let input = prompt_for_param(&api, cli, ¶m)?; contract_args.push(input); } self.args = contract_args; @@ -253,70 +253,70 @@ fn display_message(message: &str, success: bool, cli: &mut impl cli::traits::Cli Ok(()) } -// Prompt the user for the proper arguments. -fn prompt_argument( +// Prompts the user for the value of a parameter. +fn prompt_for_param( api: &OnlineClient, - arg: &Arg, cli: &mut impl cli::traits::Cli, + param: &Param, ) -> Result { - Ok(if arg.optional { - // The argument is optional; prompt the user to decide whether to provide a value. + if param.is_optional { + // Prompt user for optional parameter decision. if !cli .confirm(format!( "Do you want to provide a value for the optional parameter: {}?", - arg.name + param.name )) .interact()? { return Ok("None".to_string()); } - let value = prompt_argument_value(api, arg, cli)?; - format!("Some({})", value) + let value = get_param_value(api, cli, param)?; + Ok(format!("Some({})", value)) } else { - // Non-optional argument. - prompt_argument_value(api, arg, cli)? - }) + // Handle non-optional parameters. + get_param_value(api, cli, param) + } } -fn prompt_argument_value( +// Resolves the value of a parameter based on its type. +fn get_param_value( api: &OnlineClient, - arg: &Arg, cli: &mut impl cli::traits::Cli, + param: &Param, ) -> Result { - if arg.options.is_empty() { - prompt_for_primitive(arg, cli) - } else if arg.variant { - prompt_for_variant(api, arg, cli) + if param.sub_params.is_empty() { + prompt_for_primitive_param(cli, param) + } else if param.is_variant { + prompt_for_variant_param(api, cli, param) } else { - prompt_for_composite(api, arg, cli) + prompt_for_composite_param(api, cli, param) } } -fn prompt_for_primitive(arg: &Arg, cli: &mut impl cli::traits::Cli) -> Result { - let user_input = cli - .input(format!("Enter the value for the parameter: {}", arg.name)) - .placeholder(&format!("Type required: {}", arg.type_input)) - .interact()?; - Ok(user_input) +fn prompt_for_primitive_param(cli: &mut impl cli::traits::Cli, param: &Param) -> Result { + Ok(cli + .input(format!("Enter the value for the parameter: {}", param.name)) + .placeholder(&format!("Type required: {}", param.type_name)) + .interact()?) } -fn prompt_for_variant( +fn prompt_for_variant_param( api: &OnlineClient, - arg: &Arg, cli: &mut impl cli::traits::Cli, + param: &Param, ) -> Result { let selected_variant = { - let mut select = cli.select(format!("Select the value for the parameter: {}", arg.name)); - for option in &arg.options { - select = select.item(option, &option.name, &option.type_input); + let mut select = cli.select(format!("Select the value for the parameter: {}", param.name)); + for option in ¶m.sub_params { + select = select.item(option, &option.name, &option.type_name); } select.interact()? }; - if !selected_variant.options.is_empty() { + if !selected_variant.sub_params.is_empty() { let mut field_values = Vec::new(); - for field_arg in &selected_variant.options { - let field_value = prompt_argument(api, field_arg, cli)?; + for field_arg in &selected_variant.sub_params { + let field_value = prompt_for_param(api, cli, field_arg)?; field_values.push(field_value); } Ok(format!("{}({})", selected_variant.name, field_values.join(", "))) @@ -325,14 +325,14 @@ fn prompt_for_variant( } } -fn prompt_for_composite( +fn prompt_for_composite_param( api: &OnlineClient, - arg: &Arg, cli: &mut impl cli::traits::Cli, + param: &Param, ) -> Result { let mut field_values = Vec::new(); - for field_arg in &arg.options { - let field_value = prompt_argument(api, field_arg, cli)?; + for field_arg in ¶m.sub_params { + let field_value = prompt_for_param(api, cli, field_arg)?; field_values.push(field_value); } Ok(field_values.join(", ")) diff --git a/crates/pop-common/Cargo.toml b/crates/pop-common/Cargo.toml index 8db41a09f..bf50d4040 100644 --- a/crates/pop-common/Cargo.toml +++ b/crates/pop-common/Cargo.toml @@ -16,6 +16,7 @@ git2.workspace = true git2_credentials.workspace = true regex.workspace = true reqwest.workspace = true +scale-info.workspace = true serde_json.workspace = true serde.workspace = true strum.workspace = true diff --git a/crates/pop-common/src/lib.rs b/crates/pop-common/src/lib.rs index 4e71a5794..f34fcaa89 100644 --- a/crates/pop-common/src/lib.rs +++ b/crates/pop-common/src/lib.rs @@ -3,6 +3,7 @@ pub mod errors; pub mod git; pub mod helpers; pub mod manifest; +pub mod metadata; pub mod signer; pub mod sourcing; pub mod templates; @@ -12,6 +13,7 @@ pub use errors::Error; pub use git::{Git, GitHub, Release}; pub use helpers::{get_project_name_from_path, prefix_with_current_dir_if_needed, replace_in_file}; pub use manifest::{add_crate_to_workspace, find_workspace_toml}; +pub use metadata::format_type; pub use signer::{create_signer, parse_account}; pub use templates::extractor::extract_template_files; // External exports diff --git a/crates/pop-common/src/metadata.rs b/crates/pop-common/src/metadata.rs new file mode 100644 index 000000000..8c5675cf5 --- /dev/null +++ b/crates/pop-common/src/metadata.rs @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: GPL-3.0 + +use scale_info::{form::PortableForm, PortableRegistry, Type, TypeDef, TypeDefPrimitive}; + +/// Formats a specified type, using the registry to output its full type representation. +/// +/// # Arguments +/// * `ty`: A reference to the `Type` to be formatted. +/// * `registry`: A reference to the `PortableRegistry` to resolve type dependencies. +pub fn format_type(ty: &Type, registry: &PortableRegistry) -> String { + let mut name = ty + .path + .segments + .last() + .map(|s| s.to_owned()) + .unwrap_or_else(|| ty.path.to_string()); + + if !ty.type_params.is_empty() { + let params: Vec<_> = ty + .type_params + .iter() + .filter_map(|p| registry.resolve(p.ty.unwrap().id)) + .map(|t| format_type(t, registry)) + .collect(); + name = format!("{name}<{}>", params.join(",")); + } + + name = format!( + "{name}{}", + match &ty.type_def { + TypeDef::Composite(composite) => { + if composite.fields.is_empty() { + return "".to_string(); + } + + let mut named = false; + let fields: Vec<_> = composite + .fields + .iter() + .filter_map(|f| match f.name.as_ref() { + None => registry.resolve(f.ty.id).map(|t| format_type(t, registry)), + Some(field) => { + named = true; + f.type_name.as_ref().map(|t| format!("{field}: {t}")) + }, + }) + .collect(); + match named { + true => format!(" {{ {} }}", fields.join(", ")), + false => format!(" ({})", fields.join(", ")), + } + }, + TypeDef::Variant(variant) => { + let variants: Vec<_> = variant + .variants + .iter() + .map(|v| { + if v.fields.is_empty() { + return v.name.clone(); + } + + let name = v.name.as_str(); + let mut named = false; + let fields: Vec<_> = v + .fields + .iter() + .filter_map(|f| match f.name.as_ref() { + None => registry.resolve(f.ty.id).map(|t| format_type(t, registry)), + Some(field) => { + named = true; + f.type_name.as_ref().map(|t| format!("{field}: {t}")) + }, + }) + .collect(); + format!( + "{name}{}", + match named { + true => format!("{{ {} }}", fields.join(", ")), + false => format!("({})", fields.join(", ")), + } + ) + }) + .collect(); + format!(": {}", variants.join(", ")) + }, + TypeDef::Sequence(sequence) => { + format!( + "[{}]", + format_type( + registry.resolve(sequence.type_param.id).expect("sequence type not found"), + registry + ) + ) + }, + TypeDef::Array(array) => { + format!( + "[{};{}]", + format_type( + registry.resolve(array.type_param.id).expect("array type not found"), + registry + ), + array.len + ) + }, + TypeDef::Tuple(tuple) => { + let fields: Vec<_> = tuple + .fields + .iter() + .filter_map(|p| registry.resolve(p.id)) + .map(|t| format_type(t, registry)) + .collect(); + format!("({})", fields.join(",")) + }, + TypeDef::Primitive(primitive) => { + use TypeDefPrimitive::*; + match primitive { + Bool => "bool", + Char => "char", + Str => "str", + U8 => "u8", + U16 => "u16", + U32 => "u32", + U64 => "u64", + U128 => "u128", + U256 => "u256", + I8 => "i8", + I16 => "i16", + I32 => "i32", + I64 => "i64", + I128 => "i128", + I256 => "i256", + } + .to_string() + }, + TypeDef::Compact(compact) => { + format!( + "Compact<{}>", + format_type( + registry.resolve(compact.type_param.id).expect("compact type not found"), + registry + ) + ) + }, + TypeDef::BitSequence(_) => { + unimplemented!("bit sequence not currently supported") + }, + } + ); + + name +} diff --git a/crates/pop-contracts/src/utils/metadata.rs b/crates/pop-contracts/src/utils/metadata.rs index f0dd2b8e1..09c2d3220 100644 --- a/crates/pop-contracts/src/utils/metadata.rs +++ b/crates/pop-contracts/src/utils/metadata.rs @@ -3,7 +3,8 @@ use crate::errors::Error; use contract_extrinsics::ContractArtifacts; use contract_transcode::ink_metadata::MessageParamSpec; -use scale_info::{form::PortableForm, PortableRegistry, Type, TypeDef, TypeDefPrimitive}; +use pop_common::format_type; +use scale_info::{form::PortableForm, PortableRegistry}; use std::path::Path; /// Describes a parameter. @@ -140,150 +141,6 @@ fn process_args( args } -// Formats a specified type, using the registry to output its full type representation. -fn format_type(ty: &Type, registry: &PortableRegistry) -> String { - let mut name = ty - .path - .segments - .last() - .map(|s| s.to_owned()) - .unwrap_or_else(|| ty.path.to_string()); - - if !ty.type_params.is_empty() { - let params: Vec<_> = ty - .type_params - .iter() - .filter_map(|p| registry.resolve(p.ty.unwrap().id)) - .map(|t| format_type(t, registry)) - .collect(); - name = format!("{name}<{}>", params.join(",")); - } - - name = format!( - "{name}{}", - match &ty.type_def { - TypeDef::Composite(composite) => { - if composite.fields.is_empty() { - return "".to_string(); - } - - let mut named = false; - let fields: Vec<_> = composite - .fields - .iter() - .filter_map(|f| match f.name.as_ref() { - None => registry.resolve(f.ty.id).map(|t| format_type(t, registry)), - Some(field) => { - named = true; - f.type_name.as_ref().map(|t| format!("{field}: {t}")) - }, - }) - .collect(); - match named { - true => format!(" {{ {} }}", fields.join(", ")), - false => format!(" ({})", fields.join(", ")), - } - }, - TypeDef::Variant(variant) => { - let variants: Vec<_> = variant - .variants - .iter() - .map(|v| { - if v.fields.is_empty() { - return v.name.clone(); - } - - let name = v.name.as_str(); - let mut named = false; - let fields: Vec<_> = v - .fields - .iter() - .filter_map(|f| match f.name.as_ref() { - None => registry.resolve(f.ty.id).map(|t| format_type(t, registry)), - Some(field) => { - named = true; - f.type_name.as_ref().map(|t| format!("{field}: {t}")) - }, - }) - .collect(); - format!( - "{name}{}", - match named { - true => format!("{{ {} }}", fields.join(", ")), - false => format!("({})", fields.join(", ")), - } - ) - }) - .collect(); - format!(": {}", variants.join(", ")) - }, - TypeDef::Sequence(sequence) => { - format!( - "[{}]", - format_type( - registry.resolve(sequence.type_param.id).expect("sequence type not found"), - registry - ) - ) - }, - TypeDef::Array(array) => { - format!( - "[{};{}]", - format_type( - registry.resolve(array.type_param.id).expect("array type not found"), - registry - ), - array.len - ) - }, - TypeDef::Tuple(tuple) => { - let fields: Vec<_> = tuple - .fields - .iter() - .filter_map(|p| registry.resolve(p.id)) - .map(|t| format_type(t, registry)) - .collect(); - format!("({})", fields.join(",")) - }, - TypeDef::Primitive(primitive) => { - use TypeDefPrimitive::*; - match primitive { - Bool => "bool", - Char => "char", - Str => "str", - U8 => "u8", - U16 => "u16", - U32 => "u32", - U64 => "u64", - U128 => "u128", - U256 => "u256", - I8 => "i8", - I16 => "i16", - I32 => "i32", - I64 => "i64", - I128 => "i128", - I256 => "i256", - } - .to_string() - }, - TypeDef::Compact(compact) => { - format!( - "Compact<{}>", - format_type( - registry.resolve(compact.type_param.id).expect("compact type not found"), - registry - ) - ) - }, - TypeDef::BitSequence(_) => { - unimplemented!("bit sequence not currently supported") - }, - } - ); - - name -} - /// Processes a list of argument values for a specified contract function, /// wrapping each value in `Some(...)` or replacing it with `None` if the argument is optional /// diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index 47ae30f5a..d319c0097 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -26,7 +26,7 @@ pub use templates::{Config, Parachain, Provider}; pub use up::Zombienet; pub use utils::{ helpers::is_initial_endowment_valid, - metadata::{find_pallet_by_name, parse_chain_metadata, process_prompt_arguments, Arg}, + metadata::{field_to_param, find_pallet_by_name, parse_chain_metadata, Param}, }; /// Information about the Node. External export from Zombienet-SDK. pub use zombienet_sdk::NetworkNode; diff --git a/crates/pop-parachains/src/utils/metadata.rs b/crates/pop-parachains/src/utils/metadata.rs deleted file mode 100644 index 27b13f5a5..000000000 --- a/crates/pop-parachains/src/utils/metadata.rs +++ /dev/null @@ -1,508 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 - -use crate::errors::Error; -use pop_common::parse_account; -use scale_info::{ - form::PortableForm, Field, PortableRegistry, Type, TypeDef, TypeDefCompact, TypeDefComposite, - TypeDefPrimitive, TypeDefTuple, TypeDefVariant, Variant, -}; -use subxt::{dynamic::Value, Metadata, OnlineClient, SubstrateConfig}; - -#[derive(Clone, PartialEq, Eq)] -/// Describes a pallet with its extrinsics. -pub struct Pallet { - /// The name of the pallet. - pub name: String, - /// The documentation of the pallet. - pub docs: String, - // The extrinsics of the pallet. - pub extrinsics: Vec>, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Arg { - pub name: String, - pub type_input: String, - pub optional: bool, - pub options: Vec, - pub variant: bool, -} - -/// Parses the chain metadata to extract information about pallets and their extrinsics. -pub async fn parse_chain_metadata( - api: OnlineClient, -) -> Result, Error> { - let metadata: Metadata = api.metadata(); - Ok(metadata - .pallets() - .map(|pallet| { - let extrinsics = - pallet.call_variants().map(|variants| variants.to_vec()).unwrap_or_default(); - Pallet { name: pallet.name().to_string(), extrinsics, docs: pallet.docs().join(" ") } - }) - .collect()) -} - -pub async fn find_pallet_by_name( - api: &OnlineClient, - pallet_name: &str, -) -> Result { - let metadata: Metadata = api.metadata(); - for pallet in metadata.pallets() { - if pallet.name() == pallet_name { - let extrinsics = - pallet.call_variants().map(|variants| variants.to_vec()).unwrap_or_default(); - return Ok(Pallet { - name: pallet.name().to_string(), - extrinsics, - docs: pallet.docs().join(" "), - }); - } - } - Err(Error::PalletNotFound(pallet_name.to_string())) -} - -async fn find_extrinsic_by_name( - api: &OnlineClient, - pallet_name: &str, - extrinsic_name: &str, -) -> Result, Error> { - let pallet = find_pallet_by_name(api, pallet_name).await?; - // Check if the specified extrinsic exists within this pallet - if let Some(extrinsic) = pallet.extrinsics.iter().find(|&e| e.name == extrinsic_name) { - return Ok(extrinsic.clone()); - } else { - return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); - } -} - -pub async fn process_extrinsic_args( - api: &OnlineClient, - pallet_name: &str, - extrinsic_name: &str, - args: Vec, -) -> Result, Error> { - let metadata: Metadata = api.metadata(); - let registry = metadata.types(); - let extrinsic = find_extrinsic_by_name(&api, pallet_name, extrinsic_name).await?; - - let mut return_args: Vec = Vec::new(); - for (index, field) in extrinsic.fields.iter().enumerate() { - let arg_input = args.get(index).ok_or(Error::ParsingArgsError)?; - let type_info = registry.resolve(field.ty.id).ok_or(Error::ParsingArgsError)?; //Resolve with type_id - let arg_processed = process_value(arg_input, type_info, registry)?; - return_args.push(arg_processed); - } - Ok(return_args) -} - -pub fn process_value( - arg: &str, - ty: &Type, - registry: &PortableRegistry, -) -> Result { - let type_path = ty.path.segments.join("::"); - match type_path.as_str() { - "Option" => handle_option_type(arg, ty, registry), - "sp_core::crypto::AccountId32" => Ok(Value::from_bytes(parse_account(arg)?)), - _ => match &ty.type_def { - TypeDef::Primitive(primitive) => handle_primitive_type(arg, primitive), - TypeDef::Composite(composite) => handle_composite_type(arg, composite, registry), - TypeDef::Variant(variant_def) => handle_variant_type(arg, variant_def, registry), - TypeDef::Tuple(tuple) => handle_tuple_type(arg, tuple, registry), - TypeDef::Compact(compact) => handle_compact_type(arg, compact, registry), - TypeDef::Sequence(_) | TypeDef::Array(_) => Ok(Value::from_bytes(arg)), - _ => Err(Error::ParsingArgsError), - }, - } -} -fn handle_option_type( - arg: &str, - ty: &Type, - registry: &PortableRegistry, -) -> Result { - // Handle Option - if arg.trim() == "None" { - Ok(Value::unnamed_variant("None", vec![])) - } else if arg.trim().starts_with("Some(") && arg.trim().ends_with(')') { - let inner_arg = &arg.trim()[5..arg.trim().len() - 1]; - if let Some(inner_type_id) = ty.type_params.get(0).and_then(|param| param.ty) { - let inner_ty = registry.resolve(inner_type_id.id()).ok_or(Error::ParsingArgsError)?; - let inner_value = process_value(inner_arg.trim(), inner_ty, registry)?; - Ok(Value::unnamed_variant("Some", vec![inner_value])) - } else { - Err(Error::ParsingArgsError) - } - } else { - Err(Error::ParsingArgsError) - } -} - -fn handle_primitive_type(arg: &str, primitive: &TypeDefPrimitive) -> Result { - match primitive { - TypeDefPrimitive::Bool => { - Ok(Value::bool(arg.parse::().map_err(|_| Error::ParsingArgsError)?)) - }, - TypeDefPrimitive::Char => { - Ok(Value::char(arg.chars().next().ok_or(Error::ParsingArgsError)?)) - }, - TypeDefPrimitive::Str => Ok(Value::string(arg.to_string())), - TypeDefPrimitive::U8 - | TypeDefPrimitive::U16 - | TypeDefPrimitive::U32 - | TypeDefPrimitive::U64 - | TypeDefPrimitive::U128 - | TypeDefPrimitive::U256 => { - Ok(Value::u128(arg.parse::().map_err(|_| Error::ParsingArgsError)?)) - }, - TypeDefPrimitive::I8 - | TypeDefPrimitive::I16 - | TypeDefPrimitive::I32 - | TypeDefPrimitive::I64 - | TypeDefPrimitive::I128 - | TypeDefPrimitive::I256 => { - Ok(Value::i128(arg.parse::().map_err(|_| Error::ParsingArgsError)?)) - }, - } -} -fn handle_composite_type( - arg: &str, - composite: &TypeDefComposite, - registry: &PortableRegistry, -) -> Result { - let arg_trimmed = arg.trim(); - let inner = if arg_trimmed.starts_with('{') && arg_trimmed.ends_with('}') { - &arg_trimmed[1..arg_trimmed.len() - 1] - } else { - arg_trimmed - }; - let sub_args = split_top_level_commas(inner)?; - if sub_args.len() != composite.fields.len() { - return Err(Error::ParsingArgsError); - } - let mut values = Vec::new(); - for (field, sub_arg) in composite.fields.iter().zip(sub_args.iter()) { - let sub_ty = registry.resolve(field.ty.id).ok_or(Error::ParsingArgsError)?; - let value = process_value(sub_arg.trim(), sub_ty, registry)?; - let field_name = field.name.clone().unwrap_or_default(); - values.push((field_name, value)); - } - Ok(Value::named_composite(values)) -} -fn handle_variant_type( - arg: &str, - variant: &TypeDefVariant, - registry: &PortableRegistry, -) -> Result { - // Handle variants like Some(value1, value2, ...) - let input = arg.trim(); - let (variant_name, variant_data) = if let Some(start) = input.find('(') { - if !input.ends_with(')') { - return Err(Error::ParsingArgsError); - } - let name = input[..start].trim(); - let data_str = &input[start + 1..input.len() - 1]; - (name, Some(data_str)) - } else { - let name = input.trim(); - (name, None) - }; - - // Find the variant definition - let variant_def = variant - .variants - .iter() - .find(|v| v.name == variant_name) - .ok_or(Error::ParsingArgsError)?; - - // Handle variant fields - let fields_values = if let Some(data_str) = variant_data { - let inputs = split_top_level_commas(data_str)?; - if inputs.len() != variant_def.fields.len() { - return Err(Error::ParsingArgsError); - } - - let mut values = Vec::new(); - for (field_def, field_input) in variant_def.fields.iter().zip(inputs.iter()) { - let field_ty = registry.resolve(field_def.ty.id).ok_or(Error::ParsingArgsError)?; - let field_value = process_value(field_input.trim(), field_ty, registry)?; - values.push(field_value); - } - values - } else if variant_def.fields.is_empty() { - vec![] - } else { - // Variant has fields but no data provided - return Err(Error::ParsingArgsError); - }; - - Ok(Value::unnamed_variant(variant_name, fields_values)) -} -fn handle_tuple_type( - arg: &str, - tuple: &TypeDefTuple, - registry: &PortableRegistry, -) -> Result { - let arg_trimmed = arg.trim(); - let inner = if arg_trimmed.starts_with('(') && arg_trimmed.ends_with(')') { - &arg_trimmed[1..arg_trimmed.len() - 1] - } else { - arg_trimmed - }; - let sub_args = split_top_level_commas(inner)?; - if sub_args.len() != tuple.fields.len() { - return Err(Error::ParsingArgsError); - } - let mut values = Vec::new(); - for (sub_ty_id, sub_arg) in tuple.fields.iter().zip(sub_args.iter()) { - let sub_ty = registry.resolve(sub_ty_id.id()).ok_or(Error::ParsingArgsError)?; - let value = process_value(sub_arg.trim(), sub_ty, registry)?; - values.push(value); - } - Ok(Value::unnamed_composite(values)) -} -fn handle_compact_type( - arg: &str, - compact: &TypeDefCompact, - registry: &PortableRegistry, -) -> Result { - let inner_ty = registry.resolve(compact.type_param.id()).ok_or(Error::ParsingArgsError)?; - process_value(arg, inner_ty, registry) -} -fn split_top_level_commas(s: &str) -> Result, Error> { - let mut result = Vec::new(); - let mut brace_depth = 0; - let mut paren_depth = 0; - let mut last_index = 0; - for (i, c) in s.char_indices() { - match c { - '{' => brace_depth += 1, - '}' => brace_depth -= 1, - '(' => paren_depth += 1, - ')' => paren_depth -= 1, - ',' if brace_depth == 0 && paren_depth == 0 => { - result.push(&s[last_index..i]); - last_index = i + 1; - }, - _ => (), - } - } - if brace_depth != 0 || paren_depth != 0 { - return Err(Error::ParsingArgsError); - } - result.push(&s[last_index..]); - Ok(result) -} - -/// Processes an argument by constructing its `Arg` representation, including type information. -pub fn process_prompt_arguments( - api: &OnlineClient, - field: &Field, -) -> Result { - let type_id = field.ty().id(); - let metadata = api.metadata(); - let registry = metadata.types(); - let name = format!("{:?}", field.name()); - let type_name = field.type_name(); - parse_type(registry, type_id, name, type_name) -} - -fn parse_type( - registry: &PortableRegistry, - type_id: u32, - name: String, - type_name: Option<&String>, -) -> Result { - let type_info = registry.resolve(type_id).ok_or(Error::ParsingArgsError)?; - // Check if the type is Option by checking the path segments - if type_info.path.segments == ["Option"] { - // The type is Option - // Get the inner type T from type parameters - if let Some(inner_type_id) = type_info.type_params.get(0).and_then(|param| param.ty) { - let inner_arg = parse_type(registry, inner_type_id.id(), name.clone(), type_name)?; - Ok(Arg { - name, - type_input: inner_arg.type_input, - optional: true, - options: inner_arg.options, - variant: false, - }) - } else { - // Unable to get inner type - Err(Error::ParsingArgsError) - } - } else { - let type_input = get_type_name(registry, type_info); - match &type_info.type_def { - TypeDef::Primitive(_) => { - Ok(Arg { name, type_input, optional: false, options: vec![], variant: false }) - }, - TypeDef::Composite(composite) => { - let mut composite_fields = vec![]; - - for composite_field in composite.fields() { - if let Some(name) = composite_field.name() { - let field_name = format!("{:?}", composite_field.name()); - let field_type_id = composite_field.ty().id(); - let field_type_name = composite_field.type_name(); - - let field_arg = - parse_type(registry, field_type_id, field_name, field_type_name)?; - composite_fields.push(field_arg); - } - } - - Ok(Arg { - name, - type_input, - optional: false, - options: composite_fields, - variant: false, - }) - }, - TypeDef::Variant(variant) => { - // Regular enum handling for non-option variants - let mut variant_fields = vec![]; - for variant in variant.variants() { - let variant_name = variant.name().to_string(); - - let mut fields = vec![]; - for field in variant.fields() { - let field_name = format!("{:?}", field.name()); - let field_type_id = field.ty().id(); - let field_type_name = field.type_name(); - - let field_arg = parse_type( - registry, - field_type_id, - variant_name.clone(), - field_type_name, - )?; - fields.push(field_arg); - } - - variant_fields.push(Arg { - name: variant_name, - type_input: "".to_string(), - optional: false, - options: fields, - variant: false, - }); - } - - Ok(Arg { - name, - type_input, - optional: false, - options: variant_fields, - variant: true, - }) - }, - TypeDef::Array(_) | TypeDef::Sequence(_) | TypeDef::Tuple(_) | TypeDef::Compact(_) => { - Ok(Arg { name, type_input, optional: false, options: vec![], variant: false }) - }, - _ => Err(Error::ParsingArgsError), - } - } -} - -fn get_type_name(registry: &PortableRegistry, type_info: &Type) -> String { - if !type_info.path.segments.is_empty() { - type_info.path.segments.join("::") - } else { - match &type_info.type_def { - TypeDef::Primitive(primitive) => format!("{:?}", primitive), - TypeDef::Array(array) => { - // Get the inner type of Compact - if let Some(inner_type_info) = registry.resolve(array.type_param().id()) { - get_type_name(registry, inner_type_info) - } else { - "Compact".to_string() - } - }, - TypeDef::Sequence(sequence) => { - // Get the inner type of Compact - if let Some(inner_type_info) = registry.resolve(sequence.type_param().id()) { - get_type_name(registry, inner_type_info) - } else { - "Compact".to_string() - } - }, - TypeDef::Tuple(tuple) => { - let field_types: Vec = tuple - .fields() - .iter() - .map(|field| { - if let Some(field_type_info) = registry.resolve(field.id()) { - get_type_name(registry, field_type_info) - } else { - "Unknown".to_string() - } - }) - .collect(); - format!("({})", field_types.join(", ")) - }, - TypeDef::Compact(compact) => { - // Get the inner type of Compact - if let Some(inner_type_info) = registry.resolve(compact.type_param().id()) { - get_type_name(registry, inner_type_info) - } else { - "Compact".to_string() - } - }, - _ => "Unknown Type".to_string(), - } - } -} - -// #[cfg(test)] -// mod tests { -// use crate::set_up_api; - -// use super::*; -// use anyhow::Result; - -// #[tokio::test] -// async fn process_prompt_arguments_works() -> Result<()> { -// let api = set_up_api("ws://127.0.0.1:9944").await?; -// // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; -// let ex = find_extrinsic_by_name(&api, "Nfts", "mint").await?; -// let prompt_args1 = process_prompt_arguments(&api, &ex.fields()[2])?; - -// Ok(()) -// } - -// #[tokio::test] -// async fn process_extrinsic_args_works() -> Result<()> { -// let api = set_up_api("ws://127.0.0.1:9944").await?; -// // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; -// let ex = find_extrinsic_by_name(&api, "Nfts", "mint").await?; -// let args_parsed = process_extrinsic_args( -// &api, -// "Nfts", -// "mint", -// vec![ -// "1".to_string(), -// "1".to_string(), -// "Id(5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y)".to_string(), -// "Some(Some(1), Some(1))".to_string(), -// ], -// ) -// .await?; -// println!(" ARGS PARSER {:?}", args_parsed); - -// Ok(()) -// } - -// #[tokio::test] -// async fn process_extrinsic_args2_works() -> Result<()> { -// let api = set_up_api("ws://127.0.0.1:9944").await?; -// // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; -// let ex = find_extrinsic_by_name(&api, "Nfts", "mint").await?; -// let args_parsed = -// process_extrinsic_args(&api, "System", "remark", vec!["0x11".to_string()]).await?; -// println!(" ARGS PARSER {:?}", args_parsed); - -// Ok(()) -// } -// } diff --git a/crates/pop-parachains/src/utils/metadata/mod.rs b/crates/pop-parachains/src/utils/metadata/mod.rs new file mode 100644 index 000000000..12d494f2e --- /dev/null +++ b/crates/pop-parachains/src/utils/metadata/mod.rs @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-3.0 + +use crate::errors::Error; +use pop_common::{format_type, parse_account}; +use scale_info::{form::PortableForm, Field, PortableRegistry, TypeDef, Variant}; +use subxt::{dynamic::Value, Metadata, OnlineClient, SubstrateConfig}; +use type_parser::process_argument; + +mod type_parser; + +#[derive(Clone, PartialEq, Eq)] +/// Represents a pallet in the blockchain, including its extrinsics. +pub struct Pallet { + /// The name of the pallet. + pub name: String, + /// The documentation of the pallet. + pub docs: String, + // The extrinsics of the pallet. + pub extrinsics: Vec>, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +/// Describes a parameter of an extrinsic. +pub struct Param { + /// The name of the parameter. + pub name: String, + /// The type of the parameter. + pub type_name: String, + /// Indicates if the parameter is optional (`Option`). + pub is_optional: bool, + /// Nested parameters for composite or variants types. + pub sub_params: Vec, + /// Indicates if the parameter is a Variant. + pub is_variant: bool, +} + +/// Parses the chain metadata to extract information about pallets and their extrinsics. +/// +/// # Arguments +/// * `api`: Reference to an `OnlineClient` connected to the chain. +pub async fn parse_chain_metadata( + api: &OnlineClient, +) -> Result, Error> { + let metadata: Metadata = api.metadata(); + Ok(metadata + .pallets() + .map(|pallet| { + let extrinsics = + pallet.call_variants().map(|variants| variants.to_vec()).unwrap_or_default(); + Pallet { name: pallet.name().to_string(), extrinsics, docs: pallet.docs().join(" ") } + }) + .collect()) +} + +/// Finds a specific pallet by name and retrieves its details from metadata. +/// +/// # Arguments +/// * `api`: Reference to an `OnlineClient` connected to the chain. +/// * `pallet_name`: The name of the pallet to find. +pub async fn find_pallet_by_name( + api: &OnlineClient, + pallet_name: &str, +) -> Result { + let metadata: Metadata = api.metadata(); + for pallet in metadata.pallets() { + if pallet.name() == pallet_name { + let extrinsics = + pallet.call_variants().map(|variants| variants.to_vec()).unwrap_or_default(); + return Ok(Pallet { + name: pallet.name().to_string(), + extrinsics, + docs: pallet.docs().join(" "), + }); + } + } + Err(Error::PalletNotFound(pallet_name.to_string())) +} + +/// Transforms a metadata field into its `Param` representation. +/// +/// # Arguments +/// * `api`: Reference to an `OnlineClient` connected to the blockchain. +/// * `field`: A reference to a metadata field of the extrinsic. +pub fn field_to_param( + api: &OnlineClient, + field: &Field, +) -> Result { + let metadata: Metadata = api.metadata(); + let registry = metadata.types(); + let name = format!("{:?}", field.name); + type_to_param(name, registry, field.ty.id, &field.type_name) +} + +/// Converts a type's metadata into a `Param` representation. +/// +/// # Arguments +/// * `name`: The name of the parameter. +/// * `registry`: A reference to the `PortableRegistry` to resolve type dependencies. +/// * `type_id`: The ID of the type to be converted. +/// * `type_name`: An optional descriptive name for the type. +fn type_to_param( + name: String, + registry: &PortableRegistry, + type_id: u32, + type_name: &Option, +) -> Result { + let type_info = registry.resolve(type_id).ok_or(Error::ParsingArgsError)?; + if type_info.path.segments == ["Option"] { + if let Some(sub_type_id) = type_info.type_params.get(0).and_then(|param| param.ty) { + // Recursive for the sub parameters + let sub_param = type_to_param(name.clone(), registry, sub_type_id.id, type_name)?; + return Ok(Param { + name, + type_name: sub_param.type_name, + is_optional: true, + sub_params: sub_param.sub_params, + is_variant: false, + }); + } else { + Err(Error::ParsingArgsError) + } + } else { + // Determine the formatted type name. + let type_name = format_type(type_info, registry); + match &type_info.type_def { + TypeDef::Primitive(_) => Ok(Param { + name, + type_name, + is_optional: false, + sub_params: Vec::new(), + is_variant: false, + }), + TypeDef::Composite(composite) => { + let sub_params = composite + .fields + .iter() + .map(|field| { + // Recursive for the sub parameters of composite type. + type_to_param( + format!("{:?}", field.name), + registry, + field.ty.id, + &field.type_name, + ) + }) + .collect::, Error>>()?; + + Ok(Param { name, type_name, is_optional: false, sub_params, is_variant: false }) + }, + TypeDef::Variant(variant) => { + let variant_params = variant + .variants + .iter() + .map(|variant| { + let variant_sub_params = variant + .fields + .iter() + .map(|field| { + // Recursive for the sub parameters of variant type. + type_to_param( + format!("{:?}", field.name), + registry, + field.ty.id, + &field.type_name, + ) + }) + .collect::, Error>>()?; + Ok(Param { + name: variant.name.clone(), + type_name: "Variant".to_string(), + is_optional: false, + sub_params: variant_sub_params, + is_variant: true, + }) + }) + .collect::, Error>>()?; + + Ok(Param { + name, + type_name, + is_optional: false, + sub_params: variant_params, + is_variant: true, + }) + }, + TypeDef::Array(_) | TypeDef::Sequence(_) | TypeDef::Tuple(_) | TypeDef::Compact(_) => + Ok(Param { + name, + type_name, + is_optional: false, + sub_params: Vec::new(), + is_variant: false, + }), + _ => Err(Error::ParsingArgsError), + } + } +} + +/// Processes and maps parameters for a given pallet extrinsic based on its metadata. +/// +/// # Arguments +/// * `api`: Reference to an `OnlineClient` connected to the blockchain. +/// * `pallet_name`: Name of the pallet containing the extrinsic. +/// * `extrinsic_name`: Name of the extrinsic to process. +/// * `raw_params`: A vector of raw string arguments for the extrinsic. +pub async fn process_extrinsic_args( + api: &OnlineClient, + pallet_name: &str, + extrinsic_name: &str, + raw_params: Vec, +) -> Result, Error> { + let metadata: Metadata = api.metadata(); + let registry = metadata.types(); + let extrinsic = find_extrinsic_by_name(&api, pallet_name, extrinsic_name).await?; + + let mut processed_parameters: Vec = Vec::new(); + for (index, field) in extrinsic.fields.iter().enumerate() { + let raw_parameter = raw_params.get(index).ok_or(Error::ParsingArgsError)?; + let type_info = registry.resolve(field.ty.id).ok_or(Error::ParsingArgsError)?; //Resolve with type_id + let arg_processed = process_argument(raw_parameter, type_info, registry)?; + processed_parameters.push(arg_processed); + } + Ok(processed_parameters) +} + +/// Finds a specific extrinsic by name and retrieves its details from metadata. +/// +/// # Arguments +/// * `api`: Reference to an `OnlineClient` connected to the chain. +/// * `pallet_name`: The name of the pallet to find. +/// * `extrinsic_name`: Name of the extrinsic to locate. +async fn find_extrinsic_by_name( + api: &OnlineClient, + pallet_name: &str, + extrinsic_name: &str, +) -> Result, Error> { + let pallet = find_pallet_by_name(api, pallet_name).await?; + // Check if the specified extrinsic exists within this pallet + if let Some(extrinsic) = pallet.extrinsics.iter().find(|&e| e.name == extrinsic_name) { + return Ok(extrinsic.clone()); + } else { + return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); + } +} + +// #[cfg(test)] +// mod tests { +// use crate::set_up_api; + +// use super::*; +// use anyhow::Result; + +// #[tokio::test] +// async fn process_prompt_arguments_works() -> Result<()> { +// let api = set_up_api("ws://127.0.0.1:9944").await?; +// // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; +// let ex = find_extrinsic_by_name(&api, "Nfts", "mint").await?; +// let prompt_args1 = process_prompt_arguments(&api, &ex.fields()[2])?; + +// Ok(()) +// } + +// #[tokio::test] +// async fn process_extrinsic_args_works() -> Result<()> { +// let api = set_up_api("ws://127.0.0.1:9944").await?; +// // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; +// let ex = find_extrinsic_by_name(&api, "Nfts", "mint").await?; +// let args_parsed = process_extrinsic_args( +// &api, +// "Nfts", +// "mint", +// vec![ +// "1".to_string(), +// "1".to_string(), +// "Id(5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y)".to_string(), +// "Some(Some(1), Some(1))".to_string(), +// ], +// ) +// .await?; +// println!(" ARGS PARSER {:?}", args_parsed); + +// Ok(()) +// } + +// #[tokio::test] +// async fn process_extrinsic_args2_works() -> Result<()> { +// let api = set_up_api("ws://127.0.0.1:9944").await?; +// // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; +// let ex = find_extrinsic_by_name(&api, "Nfts", "mint").await?; +// let args_parsed = +// process_extrinsic_args(&api, "System", "remark", vec!["0x11".to_string()]).await?; +// println!(" ARGS PARSER {:?}", args_parsed); + +// Ok(()) +// } +// } diff --git a/crates/pop-parachains/src/utils/metadata/type_parser.rs b/crates/pop-parachains/src/utils/metadata/type_parser.rs new file mode 100644 index 000000000..0710fd2bf --- /dev/null +++ b/crates/pop-parachains/src/utils/metadata/type_parser.rs @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-3.0 + +use crate::errors::Error; +use scale_info::{ + form::PortableForm, PortableRegistry, Type, TypeDef, TypeDefArray, TypeDefCompact, + TypeDefComposite, TypeDefPrimitive, TypeDefSequence, TypeDefTuple, TypeDefVariant, +}; +use subxt::dynamic::Value; + +/// Parses an argument string into a `Value` based on its type definition. +/// +/// # Arguments +/// * `arg`: The string representation of the argument to parse. +/// * `ty`: A reference to the `Type` to be formatted. +/// * `registry`: A reference to the `PortableRegistry` to resolve type dependencies. +pub fn process_argument( + arg: &str, + ty: &Type, + registry: &PortableRegistry, +) -> Result { + match &ty.type_def { + TypeDef::Primitive(primitive) => primitive.parse(arg, registry), + TypeDef::Composite(composite) => composite.parse(arg, registry), + TypeDef::Variant(variant) => variant.parse(arg, registry), + TypeDef::Tuple(tuple) => tuple.parse(arg, registry), + TypeDef::Sequence(sequence) => sequence.parse(arg, registry), + TypeDef::Array(array) => array.parse(arg, registry), + TypeDef::Compact(compact) => compact.parse(arg, registry), + _ => Err(Error::ParsingArgsError), + } +} + +/// Trait to define how different type definitions parse a string argument into a `Value`. +pub trait TypeParser { + fn parse(&self, arg: &str, registry: &PortableRegistry) -> Result; +} + +impl TypeParser for TypeDefPrimitive { + fn parse(&self, arg: &str, _registry: &PortableRegistry) -> Result { + match self { + TypeDefPrimitive::Bool => + Ok(Value::bool(arg.parse::().map_err(|_| Error::ParsingArgsError)?)), + TypeDefPrimitive::Char => + Ok(Value::char(arg.chars().next().ok_or(Error::ParsingArgsError)?)), + TypeDefPrimitive::Str => Ok(Value::string(arg.to_string())), + TypeDefPrimitive::U8 | + TypeDefPrimitive::U16 | + TypeDefPrimitive::U32 | + TypeDefPrimitive::U64 | + TypeDefPrimitive::U128 | + TypeDefPrimitive::U256 => + Ok(Value::u128(arg.parse::().map_err(|_| Error::ParsingArgsError)?)), + TypeDefPrimitive::I8 | + TypeDefPrimitive::I16 | + TypeDefPrimitive::I32 | + TypeDefPrimitive::I64 | + TypeDefPrimitive::I128 | + TypeDefPrimitive::I256 => + Ok(Value::i128(arg.parse::().map_err(|_| Error::ParsingArgsError)?)), + } + } +} + +impl TypeParser for TypeDefVariant { + fn parse(&self, arg: &str, registry: &PortableRegistry) -> Result { + let input = arg.trim(); + // Parse variant with data (e.g., `Some(value1, value2, ...)`). + if let Some(start) = input.find('(') { + if !input.ends_with(')') { + return Err(Error::ParsingArgsError); + } + let name = input[..start].trim(); + let data_str = &input[start + 1..input.len() - 1]; + let variant_def = self + .variants + .iter() + .find(|v| v.name == name) + .ok_or_else(|| Error::ParsingArgsError)?; + + let mut values = Vec::new(); + for field in variant_def.fields.iter() { + let field_type_id = field.ty.id; + let field_ty = + registry.resolve(field_type_id).ok_or_else(|| Error::ParsingArgsError)?; + // Recursive for the sub parameters of variant type. + let field_value = process_argument(data_str, field_ty, registry)?; + values.push(field_value); + } + + Ok(Value::unnamed_variant(name.to_string(), values)) + } else { + // Parse variant without data (e.g., `None`). + let name = input.to_string(); + let variant_def = self + .variants + .iter() + .find(|v| v.name == name) + .ok_or_else(|| Error::ParsingArgsError)?; + if !variant_def.fields.is_empty() { + return Err(Error::ParsingArgsError); + } + Ok(Value::unnamed_variant(name, vec![])) + } + } +} + +impl TypeParser for TypeDefComposite { + // Example: {"a": true, "b": "hello"} + fn parse(&self, arg: &str, _registry: &PortableRegistry) -> Result { + let parsed: serde_json::Value = + serde_json::from_str(arg).map_err(|_| Error::ParsingArgsError)?; + let scale_val = + serde_json::from_value::>(parsed).map_err(|_| Error::ParsingArgsError)?; + Ok(scale_val) + } +} +impl TypeParser for TypeDefSequence { + // Example: [val1, val2, ...] + fn parse(&self, arg: &str, _registry: &PortableRegistry) -> Result { + Ok(Value::from_bytes(arg)) + } +} + +impl TypeParser for TypeDefArray { + // Example: [val1, val2, ...] + fn parse(&self, arg: &str, _registry: &PortableRegistry) -> Result { + Ok(Value::from_bytes(arg)) + } +} + +impl TypeParser for TypeDefTuple { + fn parse(&self, arg: &str, registry: &PortableRegistry) -> Result { + let input = arg.trim(); + // Extract tuple contents from parentheses (e.g., `(value1, value2, ...)`). + let tuple_content = if input.starts_with('(') && input.ends_with(')') { + &input[1..input.len() - 1] + } else { + input + }; + let tuple_values: Vec<&str> = tuple_content.split(',').map(|s| s.trim()).collect(); + if tuple_values.len() != self.fields.len() { + return Err(Error::ParsingArgsError); + } + let mut values = Vec::new(); + for (sub_ty_id, sub_arg) in self.fields.iter().zip(tuple_values.iter()) { + let sub_ty = registry.resolve(sub_ty_id.id).ok_or_else(|| Error::ParsingArgsError)?; + // Recursive for each value of the tuple. + let value = process_argument(sub_arg.trim(), sub_ty, registry)?; + values.push(value); + } + Ok(Value::unnamed_composite(values)) + } +} + +impl TypeParser for TypeDefCompact { + fn parse(&self, input: &str, registry: &PortableRegistry) -> Result { + // Parse compact types as their sub type (e.g., `Compact`). + let sub_ty = registry.resolve(self.type_param.id).ok_or_else(|| Error::ParsingArgsError)?; + // Recursive for the inner value. + process_argument(input, sub_ty, registry) + } +} From 17b02ab50dd6f3dd8350a9cbb96c7af4f1659841 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Wed, 20 Nov 2024 14:59:10 +0100 Subject: [PATCH 058/211] feat: display specific use cases to the user --- crates/pop-cli/src/commands/call/parachain.rs | 47 ++++++++--- .../src/call/metadata/action.rs | 84 +++++++++++++++++++ .../src/{utils => call}/metadata/mod.rs | 3 +- .../{utils => call}/metadata/type_parser.rs | 0 .../src/{call.rs => call/mod.rs} | 6 +- crates/pop-parachains/src/lib.rs | 14 ++-- crates/pop-parachains/src/utils/mod.rs | 1 - 7 files changed, 134 insertions(+), 21 deletions(-) create mode 100644 crates/pop-parachains/src/call/metadata/action.rs rename crates/pop-parachains/src/{utils => call}/metadata/mod.rs (99%) rename crates/pop-parachains/src/{utils => call}/metadata/type_parser.rs (100%) rename crates/pop-parachains/src/{call.rs => call/mod.rs} (89%) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index cbff1f231..5edb2166b 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -4,9 +4,9 @@ use crate::cli::{self, traits::*}; use anyhow::{anyhow, Result}; use clap::Args; use pop_parachains::{ - construct_extrinsic, encode_call_data, field_to_param, find_pallet_by_name, - parse_chain_metadata, set_up_api, sign_and_submit_extrinsic, DynamicPayload, OnlineClient, - Param, SubstrateConfig, + construct_extrinsic, encode_call_data, field_to_param, find_extrinsic_by_name, + find_pallet_by_name, parse_chain_metadata, set_up_api, sign_and_submit_extrinsic, + supported_actions, Action, DynamicPayload, OnlineClient, Param, SubstrateConfig, }; const DEFAULT_URL: &str = "ws://localhost:9944/"; @@ -105,16 +105,25 @@ impl CallParachainCommand { let pallet = if let Some(ref pallet_name) = self.pallet { find_pallet_by_name(&api, pallet_name).await? } else { - let mut prompt = cli.select("Select the pallet to call:"); - for pallet_item in pallets { - prompt = prompt.item(pallet_item.clone(), &pallet_item.name, &pallet_item.docs); + // Specific predefined actions first. + let picked_action: Option = prompt_predefined_actions(&api, cli).await?; + if let Some(action) = picked_action { + self.extrinsic = Some(action.action_name().to_string()); + find_pallet_by_name(&api, action.pallet()).await? + } else { + let mut prompt = cli.select("Select the pallet to call:"); + for pallet_item in pallets { + prompt = prompt.item(pallet_item.clone(), &pallet_item.name, &pallet_item.docs); + } + let pallet_prompted = prompt.interact()?; + self.pallet = Some(pallet_prompted.name.clone()); + pallet_prompted } - let pallet_prompted = prompt.interact()?; - self.pallet = Some(pallet_prompted.name.clone()); - pallet_prompted }; // Resolve extrinsic. - let extrinsic = { + let extrinsic = if let Some(ref extrinsic_name) = self.extrinsic { + find_extrinsic_by_name(&api, &pallet.name, extrinsic_name).await? + } else { let mut prompt_extrinsic = cli.select("Select the extrinsic to call:"); for extrinsic in pallet.extrinsics { prompt_extrinsic = prompt_extrinsic.item( @@ -123,9 +132,10 @@ impl CallParachainCommand { &extrinsic.docs.concat(), ); } - prompt_extrinsic.interact()? + let extrinsic_prompted = prompt_extrinsic.interact()?; + self.extrinsic = Some(extrinsic_prompted.name.clone()); + extrinsic_prompted }; - self.extrinsic = Some(extrinsic.name); // Resolve message arguments. let mut contract_args = Vec::new(); for field in extrinsic.fields { @@ -253,6 +263,19 @@ fn display_message(message: &str, success: bool, cli: &mut impl cli::traits::Cli Ok(()) } +async fn prompt_predefined_actions( + api: &OnlineClient, + cli: &mut impl cli::traits::Cli, +) -> Result> { + let mut predefined_action = cli.select("What would you like to do?"); + for action in supported_actions(&api).await { + predefined_action = + predefined_action.item(Some(action.clone()), action.description(), action.pallet()); + } + predefined_action = predefined_action.item(None, "All", "Explore all pallets and extrinsics"); + Ok(predefined_action.interact()?) +} + // Prompts the user for the value of a parameter. fn prompt_for_param( api: &OnlineClient, diff --git a/crates/pop-parachains/src/call/metadata/action.rs b/crates/pop-parachains/src/call/metadata/action.rs new file mode 100644 index 000000000..a86447cf9 --- /dev/null +++ b/crates/pop-parachains/src/call/metadata/action.rs @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-3.0 + +use strum::{EnumMessage as _, EnumProperty as _, VariantArray as _}; +use strum_macros::{AsRefStr, Display, EnumMessage, EnumProperty, EnumString, VariantArray}; +use subxt::{OnlineClient, SubstrateConfig}; + +use super::find_extrinsic_by_name; + +#[derive( + AsRefStr, + Clone, + Debug, + Display, + EnumMessage, + EnumString, + EnumProperty, + Eq, + PartialEq, + VariantArray, +)] +pub enum Action { + #[strum( + serialize = "create", + message = "create", + detailed_message = "Create an Asset", + props(Pallet = "Assets") + )] + CreateAsset, + #[strum( + serialize = "mint", + message = "mint", + detailed_message = "Mint an Asset", + props(Pallet = "Assets") + )] + MintAsset, + #[strum( + serialize = "create_nft", + message = "create", + detailed_message = "Create an NFT Collection", + props(Pallet = "Nfts") + )] + CreateCollection, + #[strum( + serialize = "mint_nft", + message = "mint", + detailed_message = "Mint an NFT", + props(Pallet = "Nfts") + )] + MintNFT, + #[strum( + serialize = "transfer", + message = "transfer_allow_death", + detailed_message = "Transfer Balance", + props(Pallet = "Balances") + )] + Transfer, +} + +impl Action { + /// Get the action's name. + pub fn action_name(&self) -> &str { + self.get_message().unwrap_or_default() + } + + /// Get the description of the action. + pub fn description(&self) -> &str { + self.get_detailed_message().unwrap_or_default() + } + + /// Get the associated pallet for the action. + pub fn pallet(&self) -> &str { + self.get_str("Pallet").unwrap_or_default() + } +} + +pub async fn supported_actions(api: &OnlineClient) -> Vec { + let mut actions = Vec::new(); + for action in Action::VARIANTS.iter() { + if find_extrinsic_by_name(api, action.pallet(), action.action_name()).await.is_ok() { + actions.push(action.clone()); + } + } + actions +} diff --git a/crates/pop-parachains/src/utils/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs similarity index 99% rename from crates/pop-parachains/src/utils/metadata/mod.rs rename to crates/pop-parachains/src/call/metadata/mod.rs index 12d494f2e..09d63d1f9 100644 --- a/crates/pop-parachains/src/utils/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -6,6 +6,7 @@ use scale_info::{form::PortableForm, Field, PortableRegistry, TypeDef, Variant}; use subxt::{dynamic::Value, Metadata, OnlineClient, SubstrateConfig}; use type_parser::process_argument; +pub mod action; mod type_parser; #[derive(Clone, PartialEq, Eq)] @@ -229,7 +230,7 @@ pub async fn process_extrinsic_args( /// * `api`: Reference to an `OnlineClient` connected to the chain. /// * `pallet_name`: The name of the pallet to find. /// * `extrinsic_name`: Name of the extrinsic to locate. -async fn find_extrinsic_by_name( +pub async fn find_extrinsic_by_name( api: &OnlineClient, pallet_name: &str, extrinsic_name: &str, diff --git a/crates/pop-parachains/src/utils/metadata/type_parser.rs b/crates/pop-parachains/src/call/metadata/type_parser.rs similarity index 100% rename from crates/pop-parachains/src/utils/metadata/type_parser.rs rename to crates/pop-parachains/src/call/metadata/type_parser.rs diff --git a/crates/pop-parachains/src/call.rs b/crates/pop-parachains/src/call/mod.rs similarity index 89% rename from crates/pop-parachains/src/call.rs rename to crates/pop-parachains/src/call/mod.rs index 485234552..a487674c8 100644 --- a/crates/pop-parachains/src/call.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -use crate::{errors::Error, utils::metadata::process_extrinsic_args}; +use crate::errors::Error; use pop_common::create_signer; use subxt::{ dynamic::Value, @@ -8,6 +8,8 @@ use subxt::{ OnlineClient, SubstrateConfig, }; +pub mod metadata; + pub async fn set_up_api(url: &str) -> Result, Error> { let api = OnlineClient::::from_url(url).await?; Ok(api) @@ -20,7 +22,7 @@ pub async fn construct_extrinsic( args: Vec, ) -> Result { let parsed_args: Vec = - process_extrinsic_args(api, pallet_name, extrinsic_name, args).await?; + metadata::process_extrinsic_args(api, pallet_name, extrinsic_name, args).await?; Ok(subxt::dynamic::tx(pallet_name, extrinsic_name, parsed_args)) } diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index d319c0097..98f26090a 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -15,7 +15,14 @@ pub use build::{ binary_path, build_parachain, export_wasm_file, generate_genesis_state_file, generate_plain_chain_spec, generate_raw_chain_spec, is_supported, ChainSpec, }; -pub use call::{construct_extrinsic, encode_call_data, set_up_api, sign_and_submit_extrinsic}; +pub use call::{ + construct_extrinsic, encode_call_data, + metadata::{ + action::{supported_actions, Action}, + field_to_param, find_extrinsic_by_name, find_pallet_by_name, parse_chain_metadata, Param, + }, + set_up_api, sign_and_submit_extrinsic, +}; pub use errors::Error; pub use indexmap::IndexSet; pub use new_pallet::{create_pallet_template, new_pallet_options::*, TemplatePalletConfig}; @@ -24,9 +31,6 @@ pub use new_parachain::instantiate_template_dir; pub use subxt::{dynamic::Value, tx::DynamicPayload, OnlineClient, SubstrateConfig}; pub use templates::{Config, Parachain, Provider}; pub use up::Zombienet; -pub use utils::{ - helpers::is_initial_endowment_valid, - metadata::{field_to_param, find_pallet_by_name, parse_chain_metadata, Param}, -}; +pub use utils::helpers::is_initial_endowment_valid; /// Information about the Node. External export from Zombienet-SDK. pub use zombienet_sdk::NetworkNode; diff --git a/crates/pop-parachains/src/utils/mod.rs b/crates/pop-parachains/src/utils/mod.rs index a9f3ad9a8..265ebafd4 100644 --- a/crates/pop-parachains/src/utils/mod.rs +++ b/crates/pop-parachains/src/utils/mod.rs @@ -1,4 +1,3 @@ // SPDX-License-Identifier: GPL-3.0 pub mod helpers; -pub mod metadata; From 8abb4803057d64af6fe4bc598f62c8c329bfa1c3 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Wed, 20 Nov 2024 15:17:25 +0100 Subject: [PATCH 059/211] refactor: predefined actions --- crates/pop-cli/src/commands/call/parachain.rs | 11 +++++---- .../src/call/metadata/action.rs | 23 +++++++++++-------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 5edb2166b..466f291e1 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -108,8 +108,8 @@ impl CallParachainCommand { // Specific predefined actions first. let picked_action: Option = prompt_predefined_actions(&api, cli).await?; if let Some(action) = picked_action { - self.extrinsic = Some(action.action_name().to_string()); - find_pallet_by_name(&api, action.pallet()).await? + self.extrinsic = Some(action.extrinsic_name().to_string()); + find_pallet_by_name(&api, action.pallet_name()).await? } else { let mut prompt = cli.select("Select the pallet to call:"); for pallet_item in pallets { @@ -269,8 +269,11 @@ async fn prompt_predefined_actions( ) -> Result> { let mut predefined_action = cli.select("What would you like to do?"); for action in supported_actions(&api).await { - predefined_action = - predefined_action.item(Some(action.clone()), action.description(), action.pallet()); + predefined_action = predefined_action.item( + Some(action.clone()), + action.description(), + action.pallet_name(), + ); } predefined_action = predefined_action.item(None, "All", "Explore all pallets and extrinsics"); Ok(predefined_action.interact()?) diff --git a/crates/pop-parachains/src/call/metadata/action.rs b/crates/pop-parachains/src/call/metadata/action.rs index a86447cf9..5b41650a1 100644 --- a/crates/pop-parachains/src/call/metadata/action.rs +++ b/crates/pop-parachains/src/call/metadata/action.rs @@ -1,11 +1,11 @@ // SPDX-License-Identifier: GPL-3.0 +use super::find_extrinsic_by_name; use strum::{EnumMessage as _, EnumProperty as _, VariantArray as _}; use strum_macros::{AsRefStr, Display, EnumMessage, EnumProperty, EnumString, VariantArray}; use subxt::{OnlineClient, SubstrateConfig}; -use super::find_extrinsic_by_name; - +/// Enum representing predefined actions. #[derive( AsRefStr, Clone, @@ -57,18 +57,18 @@ pub enum Action { } impl Action { - /// Get the action's name. - pub fn action_name(&self) -> &str { - self.get_message().unwrap_or_default() - } - /// Get the description of the action. pub fn description(&self) -> &str { self.get_detailed_message().unwrap_or_default() } - /// Get the associated pallet for the action. - pub fn pallet(&self) -> &str { + /// Get the the extrinsic name of the action. + pub fn extrinsic_name(&self) -> &str { + self.get_message().unwrap_or_default() + } + + /// Get the pallet name of the action. + pub fn pallet_name(&self) -> &str { self.get_str("Pallet").unwrap_or_default() } } @@ -76,7 +76,10 @@ impl Action { pub async fn supported_actions(api: &OnlineClient) -> Vec { let mut actions = Vec::new(); for action in Action::VARIANTS.iter() { - if find_extrinsic_by_name(api, action.pallet(), action.action_name()).await.is_ok() { + if find_extrinsic_by_name(api, action.pallet_name(), action.extrinsic_name()) + .await + .is_ok() + { actions.push(action.clone()); } } From 5f336c61654198d5c92844fb9d639a8d9b79fd69 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 21 Nov 2024 11:26:23 +0100 Subject: [PATCH 060/211] fix: various fixes --- crates/pop-cli/src/commands/call/parachain.rs | 21 +-- crates/pop-common/src/metadata.rs | 8 +- .../pop-parachains/src/call/metadata/mod.rs | 41 +++--- .../src/call/metadata/type_parser.rs | 128 ++++++++++++++---- crates/pop-parachains/src/errors.rs | 6 +- 5 files changed, 148 insertions(+), 56 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 466f291e1..d3f385cfc 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -21,7 +21,7 @@ pub struct CallParachainCommand { #[clap(long, short)] extrinsic: Option, /// The constructor arguments, encoded as strings. - #[clap(long, num_args = 0..)] + #[clap(long, num_args = 0..,)] args: Vec, /// Websocket endpoint of a node. #[clap(name = "url", short = 'u', long, value_parser, default_value = DEFAULT_URL)] @@ -108,6 +108,7 @@ impl CallParachainCommand { // Specific predefined actions first. let picked_action: Option = prompt_predefined_actions(&api, cli).await?; if let Some(action) = picked_action { + self.pallet = Some(action.pallet_name().to_string()); self.extrinsic = Some(action.extrinsic_name().to_string()); find_pallet_by_name(&api, action.pallet_name()).await? } else { @@ -137,13 +138,15 @@ impl CallParachainCommand { extrinsic_prompted }; // Resolve message arguments. - let mut contract_args = Vec::new(); - for field in extrinsic.fields { - let param = field_to_param(&api, &field)?; - let input = prompt_for_param(&api, cli, ¶m)?; - contract_args.push(input); + if self.args.is_empty() { + let mut contract_args = Vec::new(); + for field in extrinsic.fields { + let param = field_to_param(&api, &extrinsic.name, &field)?; + let input = prompt_for_param(&api, cli, ¶m)?; + contract_args.push(input); + } + self.args = contract_args; } - self.args = contract_args; cli.info(self.display())?; Ok(api) @@ -200,7 +203,7 @@ impl CallParachainCommand { prompt_to_repeat_call: bool, cli: &mut impl cli::traits::Cli, ) -> Result<()> { - if self.suri.is_empty() { + if self.suri == DEFAULT_URI { self.suri = cli::Cli .input("Who is going to sign the extrinsic:") .placeholder("//Alice") @@ -225,7 +228,7 @@ impl CallParachainCommand { .await .map_err(|err| anyhow!("{} {}", "ERROR:", format!("{err:?}")))?; - display_message(&format!("Extrinsic submitted with hash: {:?}", result), true, cli)?; + spinner.stop(&format!("Extrinsic submitted with hash: {:?}", result)); // Prompt for any additional calls. if !prompt_to_repeat_call { diff --git a/crates/pop-common/src/metadata.rs b/crates/pop-common/src/metadata.rs index 8c5675cf5..3cff32d72 100644 --- a/crates/pop-common/src/metadata.rs +++ b/crates/pop-common/src/metadata.rs @@ -19,7 +19,13 @@ pub fn format_type(ty: &Type, registry: &PortableRegistry) -> Stri let params: Vec<_> = ty .type_params .iter() - .filter_map(|p| registry.resolve(p.ty.unwrap().id)) + .filter_map(|p| { + if let Some(ty) = p.ty { + registry.resolve(ty.id) + } else { + None // Ignore if p.ty is None + } + }) .map(|t| format_type(t, registry)) .collect(); name = format!("{name}<{}>", params.join(",")); diff --git a/crates/pop-parachains/src/call/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs index 09d63d1f9..503acf03f 100644 --- a/crates/pop-parachains/src/call/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 use crate::errors::Error; -use pop_common::{format_type, parse_account}; +use pop_common::format_type; use scale_info::{form::PortableForm, Field, PortableRegistry, TypeDef, Variant}; use subxt::{dynamic::Value, Metadata, OnlineClient, SubstrateConfig}; use type_parser::process_argument; @@ -84,12 +84,13 @@ pub async fn find_pallet_by_name( /// * `field`: A reference to a metadata field of the extrinsic. pub fn field_to_param( api: &OnlineClient, + extrinsic_name: &str, field: &Field, ) -> Result { let metadata: Metadata = api.metadata(); let registry = metadata.types(); - let name = format!("{:?}", field.name); - type_to_param(name, registry, field.ty.id, &field.type_name) + let name = field.name.clone().unwrap_or("Unnamed".to_string()); //It can be unnamed field + type_to_param(extrinsic_name, name, registry, field.ty.id, &field.type_name) } /// Converts a type's metadata into a `Param` representation. @@ -100,16 +101,23 @@ pub fn field_to_param( /// * `type_id`: The ID of the type to be converted. /// * `type_name`: An optional descriptive name for the type. fn type_to_param( + extrinsic_name: &str, name: String, registry: &PortableRegistry, type_id: u32, type_name: &Option, ) -> Result { - let type_info = registry.resolve(type_id).ok_or(Error::ParsingArgsError)?; + let type_info = registry.resolve(type_id).ok_or(Error::MetadataParsingError(name.clone()))?; + if let Some(last_segment) = type_info.path.segments.last() { + if last_segment == "RuntimeCall" { + return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); + } + } if type_info.path.segments == ["Option"] { if let Some(sub_type_id) = type_info.type_params.get(0).and_then(|param| param.ty) { // Recursive for the sub parameters - let sub_param = type_to_param(name.clone(), registry, sub_type_id.id, type_name)?; + let sub_param = + type_to_param(extrinsic_name, name.clone(), registry, sub_type_id.id, type_name)?; return Ok(Param { name, type_name: sub_param.type_name, @@ -118,7 +126,7 @@ fn type_to_param( is_variant: false, }); } else { - Err(Error::ParsingArgsError) + Err(Error::MetadataParsingError(name)) } } else { // Determine the formatted type name. @@ -138,7 +146,8 @@ fn type_to_param( .map(|field| { // Recursive for the sub parameters of composite type. type_to_param( - format!("{:?}", field.name), + extrinsic_name, + field.name.clone().unwrap_or(name.clone()), registry, field.ty.id, &field.type_name, @@ -152,14 +161,15 @@ fn type_to_param( let variant_params = variant .variants .iter() - .map(|variant| { - let variant_sub_params = variant + .map(|variant_param| { + let variant_sub_params = variant_param .fields .iter() .map(|field| { // Recursive for the sub parameters of variant type. type_to_param( - format!("{:?}", field.name), + extrinsic_name, + field.name.clone().unwrap_or(variant_param.name.clone()), registry, field.ty.id, &field.type_name, @@ -167,8 +177,8 @@ fn type_to_param( }) .collect::, Error>>()?; Ok(Param { - name: variant.name.clone(), - type_name: "Variant".to_string(), + name: variant_param.name.clone(), + type_name: "".to_string(), is_optional: false, sub_params: variant_sub_params, is_variant: true, @@ -192,7 +202,7 @@ fn type_to_param( sub_params: Vec::new(), is_variant: false, }), - _ => Err(Error::ParsingArgsError), + _ => Err(Error::MetadataParsingError(name)), } } } @@ -213,11 +223,10 @@ pub async fn process_extrinsic_args( let metadata: Metadata = api.metadata(); let registry = metadata.types(); let extrinsic = find_extrinsic_by_name(&api, pallet_name, extrinsic_name).await?; - let mut processed_parameters: Vec = Vec::new(); for (index, field) in extrinsic.fields.iter().enumerate() { - let raw_parameter = raw_params.get(index).ok_or(Error::ParsingArgsError)?; - let type_info = registry.resolve(field.ty.id).ok_or(Error::ParsingArgsError)?; //Resolve with type_id + let raw_parameter = raw_params.get(index).ok_or(Error::ParamProcessingError)?; + let type_info = registry.resolve(field.ty.id).ok_or(Error::ParamProcessingError)?; //Resolve with type_id let arg_processed = process_argument(raw_parameter, type_info, registry)?; processed_parameters.push(arg_processed); } diff --git a/crates/pop-parachains/src/call/metadata/type_parser.rs b/crates/pop-parachains/src/call/metadata/type_parser.rs index 0710fd2bf..fb2c8bdea 100644 --- a/crates/pop-parachains/src/call/metadata/type_parser.rs +++ b/crates/pop-parachains/src/call/metadata/type_parser.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 use crate::errors::Error; +use pop_common::parse_account; use scale_info::{ form::PortableForm, PortableRegistry, Type, TypeDef, TypeDefArray, TypeDefCompact, TypeDefComposite, TypeDefPrimitive, TypeDefSequence, TypeDefTuple, TypeDefVariant, @@ -18,15 +19,45 @@ pub fn process_argument( ty: &Type, registry: &PortableRegistry, ) -> Result { - match &ty.type_def { - TypeDef::Primitive(primitive) => primitive.parse(arg, registry), - TypeDef::Composite(composite) => composite.parse(arg, registry), - TypeDef::Variant(variant) => variant.parse(arg, registry), - TypeDef::Tuple(tuple) => tuple.parse(arg, registry), - TypeDef::Sequence(sequence) => sequence.parse(arg, registry), - TypeDef::Array(array) => array.parse(arg, registry), - TypeDef::Compact(compact) => compact.parse(arg, registry), - _ => Err(Error::ParsingArgsError), + let type_path = ty.path.segments.join("::"); + match type_path.as_str() { + "Option" => handle_option_type(arg, ty, registry), + "sp_core::crypto::AccountId32" => Ok(Value::from_bytes(parse_account(arg)?)), /* Specifically parse AccountId */ + _ => match &ty.type_def { + TypeDef::Primitive(primitive) => primitive.parse(arg, registry), + TypeDef::Composite(composite) => composite.parse(arg, registry), + TypeDef::Variant(variant) => variant.parse(arg, registry), + TypeDef::Tuple(tuple) => tuple.parse(arg, registry), + TypeDef::Sequence(sequence) => sequence.parse(arg, registry), + TypeDef::Array(array) => array.parse(arg, registry), + TypeDef::Compact(compact) => compact.parse(arg, registry), + _ => Err(Error::ParamProcessingError), + }, + } +} + +fn handle_option_type( + arg: &str, + ty: &Type, + registry: &PortableRegistry, +) -> Result { + // Handle Option + if arg.trim() == "None" { + Ok(Value::unnamed_variant("None", vec![])) + } else if arg.trim().starts_with("Some(") && arg.trim().ends_with(')') { + let sub_arg = &arg.trim()[5..arg.trim().len() - 1]; + if let Some(sub_arg_type_id) = ty.type_params.get(0).and_then(|param| param.ty) { + let sub_arg_ty = + registry.resolve(sub_arg_type_id.id).ok_or(Error::ParamProcessingError)?; + Ok(Value::unnamed_variant( + "Some", + vec![process_argument(sub_arg.trim(), sub_arg_ty, registry)?], + )) + } else { + Err(Error::ParamProcessingError) + } + } else { + Err(Error::ParamProcessingError) } } @@ -39,9 +70,9 @@ impl TypeParser for TypeDefPrimitive { fn parse(&self, arg: &str, _registry: &PortableRegistry) -> Result { match self { TypeDefPrimitive::Bool => - Ok(Value::bool(arg.parse::().map_err(|_| Error::ParsingArgsError)?)), + Ok(Value::bool(arg.parse::().map_err(|_| Error::ParamProcessingError)?)), TypeDefPrimitive::Char => - Ok(Value::char(arg.chars().next().ok_or(Error::ParsingArgsError)?)), + Ok(Value::char(arg.chars().next().ok_or(Error::ParamProcessingError)?)), TypeDefPrimitive::Str => Ok(Value::string(arg.to_string())), TypeDefPrimitive::U8 | TypeDefPrimitive::U16 | @@ -49,14 +80,14 @@ impl TypeParser for TypeDefPrimitive { TypeDefPrimitive::U64 | TypeDefPrimitive::U128 | TypeDefPrimitive::U256 => - Ok(Value::u128(arg.parse::().map_err(|_| Error::ParsingArgsError)?)), + Ok(Value::u128(arg.parse::().map_err(|_| Error::ParamProcessingError)?)), TypeDefPrimitive::I8 | TypeDefPrimitive::I16 | TypeDefPrimitive::I32 | TypeDefPrimitive::I64 | TypeDefPrimitive::I128 | TypeDefPrimitive::I256 => - Ok(Value::i128(arg.parse::().map_err(|_| Error::ParsingArgsError)?)), + Ok(Value::i128(arg.parse::().map_err(|_| Error::ParamProcessingError)?)), } } } @@ -67,7 +98,7 @@ impl TypeParser for TypeDefVariant { // Parse variant with data (e.g., `Some(value1, value2, ...)`). if let Some(start) = input.find('(') { if !input.ends_with(')') { - return Err(Error::ParsingArgsError); + return Err(Error::ParamProcessingError); } let name = input[..start].trim(); let data_str = &input[start + 1..input.len() - 1]; @@ -75,13 +106,13 @@ impl TypeParser for TypeDefVariant { .variants .iter() .find(|v| v.name == name) - .ok_or_else(|| Error::ParsingArgsError)?; + .ok_or_else(|| Error::ParamProcessingError)?; let mut values = Vec::new(); for field in variant_def.fields.iter() { let field_type_id = field.ty.id; let field_ty = - registry.resolve(field_type_id).ok_or_else(|| Error::ParsingArgsError)?; + registry.resolve(field_type_id).ok_or_else(|| Error::ParamProcessingError)?; // Recursive for the sub parameters of variant type. let field_value = process_argument(data_str, field_ty, registry)?; values.push(field_value); @@ -95,9 +126,9 @@ impl TypeParser for TypeDefVariant { .variants .iter() .find(|v| v.name == name) - .ok_or_else(|| Error::ParsingArgsError)?; + .ok_or_else(|| Error::ParamProcessingError)?; if !variant_def.fields.is_empty() { - return Err(Error::ParsingArgsError); + return Err(Error::ParamProcessingError); } Ok(Value::unnamed_variant(name, vec![])) } @@ -105,15 +136,53 @@ impl TypeParser for TypeDefVariant { } impl TypeParser for TypeDefComposite { - // Example: {"a": true, "b": "hello"} - fn parse(&self, arg: &str, _registry: &PortableRegistry) -> Result { - let parsed: serde_json::Value = - serde_json::from_str(arg).map_err(|_| Error::ParsingArgsError)?; - let scale_val = - serde_json::from_value::>(parsed).map_err(|_| Error::ParsingArgsError)?; - Ok(scale_val) + // Example: {"a": true, "b": "hello", "c": { "d": 42, e: "world" }} + fn parse(&self, arg: &str, registry: &PortableRegistry) -> Result { + let mut values: Vec<&str> = arg.split(',').map(str::trim).collect(); + + let mut field_values = Vec::new(); + for (index, field) in self.fields.iter().enumerate() { + let field_name = field + .name + .clone() + .or_else(|| field.type_name.clone()) + .unwrap_or_else(|| format!("unnamed_field_{}", index)); + + let field_type = registry.resolve(field.ty.id).ok_or(Error::ParamProcessingError)?; + if values.is_empty() { + return Err(Error::ParamProcessingError); + } + let value = match &field_type.type_def { + TypeDef::Composite(nested_composite) => { + if nested_composite.fields.is_empty() { + // Unnamed composite resolving to a primitive type + let raw_value = values.remove(0); + process_argument(raw_value, field_type, registry)? + } else { + // Named or unnamed nested composite + let nested_args_count = nested_composite.fields.len(); + if values.len() < nested_args_count { + return Err(Error::ParamProcessingError); + } + + let nested_args: Vec = + values.drain(..nested_args_count).map(String::from).collect(); + let nested_arg_str = nested_args.join(","); + nested_composite.parse(&nested_arg_str, registry)? + } + }, + _ => { + // Parse a single argument for non-composite fields + let raw_value = values.remove(0); + process_argument(raw_value, field_type, registry)? + }, + }; + field_values.push((field_name, value)); + } + Ok(Value::named_composite(field_values)) } } + impl TypeParser for TypeDefSequence { // Example: [val1, val2, ...] fn parse(&self, arg: &str, _registry: &PortableRegistry) -> Result { @@ -139,11 +208,12 @@ impl TypeParser for TypeDefTuple { }; let tuple_values: Vec<&str> = tuple_content.split(',').map(|s| s.trim()).collect(); if tuple_values.len() != self.fields.len() { - return Err(Error::ParsingArgsError); + return Err(Error::ParamProcessingError); } let mut values = Vec::new(); for (sub_ty_id, sub_arg) in self.fields.iter().zip(tuple_values.iter()) { - let sub_ty = registry.resolve(sub_ty_id.id).ok_or_else(|| Error::ParsingArgsError)?; + let sub_ty = + registry.resolve(sub_ty_id.id).ok_or_else(|| Error::ParamProcessingError)?; // Recursive for each value of the tuple. let value = process_argument(sub_arg.trim(), sub_ty, registry)?; values.push(value); @@ -155,7 +225,9 @@ impl TypeParser for TypeDefTuple { impl TypeParser for TypeDefCompact { fn parse(&self, input: &str, registry: &PortableRegistry) -> Result { // Parse compact types as their sub type (e.g., `Compact`). - let sub_ty = registry.resolve(self.type_param.id).ok_or_else(|| Error::ParsingArgsError)?; + let sub_ty = registry + .resolve(self.type_param.id) + .ok_or_else(|| Error::ParamProcessingError)?; // Recursive for the inner value. process_argument(input, sub_ty, registry) } diff --git a/crates/pop-parachains/src/errors.rs b/crates/pop-parachains/src/errors.rs index 2a54c4fed..244b70aa2 100644 --- a/crates/pop-parachains/src/errors.rs +++ b/crates/pop-parachains/src/errors.rs @@ -28,6 +28,8 @@ pub enum Error { JsonError(#[from] serde_json::Error), #[error("Metadata error: {0}")] MetadataError(#[from] subxt::error::MetadataError), + #[error("Error parsing metadata for parameter {0} conversion")] + MetadataParsingError(String), #[error("Missing binary: {0}")] MissingBinary(String), #[error("Missing chain spec file at: {0}")] @@ -42,8 +44,8 @@ pub enum Error { PalletMissing, #[error("Failed to find the pallet {0}")] PalletNotFound(String), - #[error("Failed to parse the arguments")] - ParsingArgsError, + #[error("Failed to process the arguments provided by the user.")] + ParamProcessingError, #[error("Failed to parse the response")] ParsingResponseError(#[from] scale_decode::Error), #[error("Invalid path")] From f4fb1ab4ba1b5df938c561fe289e8377ec59de34 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 21 Nov 2024 11:54:49 +0100 Subject: [PATCH 061/211] fix: error message not supported for complex types --- crates/pop-parachains/src/call/metadata/mod.rs | 5 +++++ .../src/call/metadata/type_parser.rs | 14 +++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/crates/pop-parachains/src/call/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs index 503acf03f..16dcb3e8e 100644 --- a/crates/pop-parachains/src/call/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -113,6 +113,11 @@ fn type_to_param( return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); } } + for param in &type_info.type_params { + if param.name == "RuntimeCall" { + return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); + } + } if type_info.path.segments == ["Option"] { if let Some(sub_type_id) = type_info.type_params.get(0).and_then(|param| param.ty) { // Recursive for the sub parameters diff --git a/crates/pop-parachains/src/call/metadata/type_parser.rs b/crates/pop-parachains/src/call/metadata/type_parser.rs index fb2c8bdea..7f214f510 100644 --- a/crates/pop-parachains/src/call/metadata/type_parser.rs +++ b/crates/pop-parachains/src/call/metadata/type_parser.rs @@ -136,7 +136,8 @@ impl TypeParser for TypeDefVariant { } impl TypeParser for TypeDefComposite { - // Example: {"a": true, "b": "hello", "c": { "d": 42, e: "world" }} + // Example: A composite type is input by the user [true, hello, 42, world], convert into proper + // composite type: {"a": true, "b": "hello", "c": { "d": 42, e: "world" }} fn parse(&self, arg: &str, registry: &PortableRegistry) -> Result { let mut values: Vec<&str> = arg.split(',').map(str::trim).collect(); @@ -155,16 +156,15 @@ impl TypeParser for TypeDefComposite { let value = match &field_type.type_def { TypeDef::Composite(nested_composite) => { if nested_composite.fields.is_empty() { - // Unnamed composite resolving to a primitive type + // Recursive for the sub parameters of nested composite type. let raw_value = values.remove(0); process_argument(raw_value, field_type, registry)? } else { - // Named or unnamed nested composite + // Parse nested composite type. let nested_args_count = nested_composite.fields.len(); if values.len() < nested_args_count { return Err(Error::ParamProcessingError); } - let nested_args: Vec = values.drain(..nested_args_count).map(String::from).collect(); let nested_arg_str = nested_args.join(","); @@ -172,7 +172,7 @@ impl TypeParser for TypeDefComposite { } }, _ => { - // Parse a single argument for non-composite fields + // Recursive for the sub parameters of the composite type. let raw_value = values.remove(0); process_argument(raw_value, field_type, registry)? }, @@ -223,12 +223,12 @@ impl TypeParser for TypeDefTuple { } impl TypeParser for TypeDefCompact { - fn parse(&self, input: &str, registry: &PortableRegistry) -> Result { + fn parse(&self, arg: &str, registry: &PortableRegistry) -> Result { // Parse compact types as their sub type (e.g., `Compact`). let sub_ty = registry .resolve(self.type_param.id) .ok_or_else(|| Error::ParamProcessingError)?; // Recursive for the inner value. - process_argument(input, sub_ty, registry) + process_argument(arg, sub_ty, registry) } } From 9304b5e6c9b61fae84eca15378e8afee96f18f45 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 21 Nov 2024 14:57:26 +0100 Subject: [PATCH 062/211] refactor: parse all metadata, including parameters at once --- crates/pop-cli/src/commands/call/parachain.rs | 40 ++-- .../src/call/metadata/action.rs | 7 +- .../pop-parachains/src/call/metadata/mod.rs | 219 ++++++++++++------ crates/pop-parachains/src/lib.rs | 2 +- 4 files changed, 170 insertions(+), 98 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index d3f385cfc..dde9d2a51 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -4,9 +4,9 @@ use crate::cli::{self, traits::*}; use anyhow::{anyhow, Result}; use clap::Args; use pop_parachains::{ - construct_extrinsic, encode_call_data, field_to_param, find_extrinsic_by_name, - find_pallet_by_name, parse_chain_metadata, set_up_api, sign_and_submit_extrinsic, - supported_actions, Action, DynamicPayload, OnlineClient, Param, SubstrateConfig, + construct_extrinsic, encode_call_data, find_extrinsic_by_name, find_pallet_by_name, + parse_chain_metadata, set_up_api, sign_and_submit_extrinsic, supported_actions, Action, + DynamicPayload, OnlineClient, Pallet, Param, SubstrateConfig, }; const DEFAULT_URL: &str = "ws://localhost:9944/"; @@ -103,17 +103,17 @@ impl CallParachainCommand { }; // Resolve pallet. let pallet = if let Some(ref pallet_name) = self.pallet { - find_pallet_by_name(&api, pallet_name).await? + find_pallet_by_name(&pallets, pallet_name).await? } else { // Specific predefined actions first. - let picked_action: Option = prompt_predefined_actions(&api, cli).await?; + let picked_action: Option = prompt_predefined_actions(&pallets, cli).await?; if let Some(action) = picked_action { self.pallet = Some(action.pallet_name().to_string()); self.extrinsic = Some(action.extrinsic_name().to_string()); - find_pallet_by_name(&api, action.pallet_name()).await? + find_pallet_by_name(&pallets, action.pallet_name()).await? } else { let mut prompt = cli.select("Select the pallet to call:"); - for pallet_item in pallets { + for pallet_item in &pallets { prompt = prompt.item(pallet_item.clone(), &pallet_item.name, &pallet_item.docs); } let pallet_prompted = prompt.interact()?; @@ -123,25 +123,31 @@ impl CallParachainCommand { }; // Resolve extrinsic. let extrinsic = if let Some(ref extrinsic_name) = self.extrinsic { - find_extrinsic_by_name(&api, &pallet.name, extrinsic_name).await? + find_extrinsic_by_name(&pallets, &pallet.name, extrinsic_name).await? } else { let mut prompt_extrinsic = cli.select("Select the extrinsic to call:"); for extrinsic in pallet.extrinsics { - prompt_extrinsic = prompt_extrinsic.item( - extrinsic.clone(), - &extrinsic.name, - &extrinsic.docs.concat(), - ); + prompt_extrinsic = + prompt_extrinsic.item(extrinsic.clone(), &extrinsic.name, &extrinsic.docs); } let extrinsic_prompted = prompt_extrinsic.interact()?; self.extrinsic = Some(extrinsic_prompted.name.clone()); extrinsic_prompted }; + if !extrinsic.is_supported { + cli.outro_cancel( + "The selected extrinsic is not supported. Please choose another one.", + )?; + // Reset specific items from the last call and repeat. + self.reset_for_new_call(); + Box::pin(self.configure(cli, true)).await?; + } + // Resolve message arguments. if self.args.is_empty() { let mut contract_args = Vec::new(); - for field in extrinsic.fields { - let param = field_to_param(&api, &extrinsic.name, &field)?; + for param in extrinsic.params { + //let param = field_to_param(&api, &extrinsic.name, &field)?; let input = prompt_for_param(&api, cli, ¶m)?; contract_args.push(input); } @@ -267,11 +273,11 @@ fn display_message(message: &str, success: bool, cli: &mut impl cli::traits::Cli } async fn prompt_predefined_actions( - api: &OnlineClient, + pallets: &[Pallet], cli: &mut impl cli::traits::Cli, ) -> Result> { let mut predefined_action = cli.select("What would you like to do?"); - for action in supported_actions(&api).await { + for action in supported_actions(&pallets).await { predefined_action = predefined_action.item( Some(action.clone()), action.description(), diff --git a/crates/pop-parachains/src/call/metadata/action.rs b/crates/pop-parachains/src/call/metadata/action.rs index 5b41650a1..e27a95b20 100644 --- a/crates/pop-parachains/src/call/metadata/action.rs +++ b/crates/pop-parachains/src/call/metadata/action.rs @@ -1,9 +1,8 @@ // SPDX-License-Identifier: GPL-3.0 -use super::find_extrinsic_by_name; +use super::{find_extrinsic_by_name, Pallet}; use strum::{EnumMessage as _, EnumProperty as _, VariantArray as _}; use strum_macros::{AsRefStr, Display, EnumMessage, EnumProperty, EnumString, VariantArray}; -use subxt::{OnlineClient, SubstrateConfig}; /// Enum representing predefined actions. #[derive( @@ -73,10 +72,10 @@ impl Action { } } -pub async fn supported_actions(api: &OnlineClient) -> Vec { +pub async fn supported_actions(pallets: &[Pallet]) -> Vec { let mut actions = Vec::new(); for action in Action::VARIANTS.iter() { - if find_extrinsic_by_name(api, action.pallet_name(), action.extrinsic_name()) + if find_extrinsic_by_name(pallets, action.pallet_name(), action.extrinsic_name()) .await .is_ok() { diff --git a/crates/pop-parachains/src/call/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs index 16dcb3e8e..37d1f5f1e 100644 --- a/crates/pop-parachains/src/call/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -17,7 +17,20 @@ pub struct Pallet { /// The documentation of the pallet. pub docs: String, // The extrinsics of the pallet. - pub extrinsics: Vec>, + pub extrinsics: Vec, +} + +#[derive(Clone, PartialEq, Eq, Debug)] +/// Represents an extrinsic in a pallet. +pub struct Extrinsic { + /// The name of the extrinsic. + pub name: String, + /// The documentation of the extrinsic. + pub docs: String, + /// The parameters of the extrinsic. + pub params: Vec, + /// Whether this extrinsic is supported (no recursive or unsupported types like `RuntimeCall`). + pub is_supported: bool, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -43,14 +56,60 @@ pub async fn parse_chain_metadata( api: &OnlineClient, ) -> Result, Error> { let metadata: Metadata = api.metadata(); - Ok(metadata + let registry = metadata.types(); + + let pallets = metadata .pallets() .map(|pallet| { - let extrinsics = - pallet.call_variants().map(|variants| variants.to_vec()).unwrap_or_default(); - Pallet { name: pallet.name().to_string(), extrinsics, docs: pallet.docs().join(" ") } + let extrinsics = pallet + .call_variants() + .map(|variants| { + variants + .iter() + .map(|variant| { + let mut is_supported = true; + + // Parse parameters for the extrinsic + let params = { + let mut parsed_params = Vec::new(); + for field in &variant.fields { + match field_to_param(api, &variant.name, field) { + Ok(param) => parsed_params.push(param), + Err(Error::ExtrinsicNotSupported(_)) => { + is_supported = false; + parsed_params.clear(); // Discard any already-parsed params + break; // Stop processing further fields + }, + Err(e) => return Err(e), // Propagate other errors + } + } + parsed_params + }; + + Ok(Extrinsic { + name: variant.name.clone(), + docs: if is_supported { + variant.docs.concat() + } else { + "Extrinsic Not Supported".to_string() + }, + params, + is_supported, + }) + }) + .collect::, Error>>() + }) + .unwrap_or_else(|| Ok(vec![]))?; + + Ok(Pallet { + name: pallet.name().to_string(), + docs: pallet.docs().join(" "), + extrinsics, + }) }) - .collect()) + .collect::, Error>>()?; + + Ok(pallets) } /// Finds a specific pallet by name and retrieves its details from metadata. @@ -58,23 +117,32 @@ pub async fn parse_chain_metadata( /// # Arguments /// * `api`: Reference to an `OnlineClient` connected to the chain. /// * `pallet_name`: The name of the pallet to find. -pub async fn find_pallet_by_name( - api: &OnlineClient, +pub async fn find_pallet_by_name(pallets: &[Pallet], pallet_name: &str) -> Result { + if let Some(pallet) = pallets.iter().find(|p| p.name == pallet_name) { + Ok(pallet.clone()) + } else { + Err(Error::PalletNotFound(pallet_name.to_string())) + } +} + +/// Finds a specific extrinsic by name and retrieves its details from metadata. +/// +/// # Arguments +/// * `api`: Reference to an `OnlineClient` connected to the chain. +/// * `pallet_name`: The name of the pallet to find. +/// * `extrinsic_name`: Name of the extrinsic to locate. +pub async fn find_extrinsic_by_name( + pallets: &[Pallet], pallet_name: &str, -) -> Result { - let metadata: Metadata = api.metadata(); - for pallet in metadata.pallets() { - if pallet.name() == pallet_name { - let extrinsics = - pallet.call_variants().map(|variants| variants.to_vec()).unwrap_or_default(); - return Ok(Pallet { - name: pallet.name().to_string(), - extrinsics, - docs: pallet.docs().join(" "), - }); - } + extrinsic_name: &str, +) -> Result { + let pallet = find_pallet_by_name(pallets, pallet_name).await?; + // Check if the specified extrinsic exists within this pallet + if let Some(extrinsic) = pallet.extrinsics.iter().find(|&e| e.name == extrinsic_name) { + return Ok(extrinsic.clone()); + } else { + return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); } - Err(Error::PalletNotFound(pallet_name.to_string())) } /// Transforms a metadata field into its `Param` representation. @@ -82,7 +150,7 @@ pub async fn find_pallet_by_name( /// # Arguments /// * `api`: Reference to an `OnlineClient` connected to the blockchain. /// * `field`: A reference to a metadata field of the extrinsic. -pub fn field_to_param( +fn field_to_param( api: &OnlineClient, extrinsic_name: &str, field: &Field, @@ -114,7 +182,10 @@ fn type_to_param( } } for param in &type_info.type_params { - if param.name == "RuntimeCall" { + if param.name == "RuntimeCall" || + param.name == "Vec" || + param.name == "Vec<::RuntimeCall>" + { return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); } } @@ -227,7 +298,7 @@ pub async fn process_extrinsic_args( ) -> Result, Error> { let metadata: Metadata = api.metadata(); let registry = metadata.types(); - let extrinsic = find_extrinsic_by_name(&api, pallet_name, extrinsic_name).await?; + let extrinsic = parse_extrinsic_by_name(&api, pallet_name, extrinsic_name).await?; let mut processed_parameters: Vec = Vec::new(); for (index, field) in extrinsic.fields.iter().enumerate() { let raw_parameter = raw_params.get(index).ok_or(Error::ParamProcessingError)?; @@ -244,68 +315,64 @@ pub async fn process_extrinsic_args( /// * `api`: Reference to an `OnlineClient` connected to the chain. /// * `pallet_name`: The name of the pallet to find. /// * `extrinsic_name`: Name of the extrinsic to locate. -pub async fn find_extrinsic_by_name( +async fn parse_extrinsic_by_name( api: &OnlineClient, pallet_name: &str, extrinsic_name: &str, ) -> Result, Error> { - let pallet = find_pallet_by_name(api, pallet_name).await?; - // Check if the specified extrinsic exists within this pallet - if let Some(extrinsic) = pallet.extrinsics.iter().find(|&e| e.name == extrinsic_name) { - return Ok(extrinsic.clone()); - } else { - return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); - } + let metadata: Metadata = api.metadata(); + let pallet = metadata + .pallets() + .into_iter() + .find(|p| p.name() == pallet_name) + .ok_or_else(|| Error::PalletNotFound(pallet_name.to_string()))?; + // Retrieve and check for the extrinsic within the pallet + let extrinsic = pallet + .call_variants() + .map(|variants| variants.iter().find(|e| e.name == extrinsic_name)) + .flatten() + .ok_or_else(|| Error::ExtrinsicNotSupported(extrinsic_name.to_string()))?; + + Ok(extrinsic.clone()) } -// #[cfg(test)] -// mod tests { -// use crate::set_up_api; +#[cfg(test)] +mod tests { + use crate::set_up_api; -// use super::*; -// use anyhow::Result; + use super::*; + use anyhow::Result; -// #[tokio::test] -// async fn process_prompt_arguments_works() -> Result<()> { -// let api = set_up_api("ws://127.0.0.1:9944").await?; -// // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; -// let ex = find_extrinsic_by_name(&api, "Nfts", "mint").await?; -// let prompt_args1 = process_prompt_arguments(&api, &ex.fields()[2])?; + // #[tokio::test] + // async fn process_prompt_arguments_works() -> Result<()> { + // let api = set_up_api("ws://127.0.0.1:9944").await?; + // // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; + // let ex = find_extrinsic_by_name(&api, "Nfts", "mint").await?; + // let prompt_args1 = process_prompt_arguments(&api, &ex.fields()[2])?; -// Ok(()) -// } + // Ok(()) + // } -// #[tokio::test] -// async fn process_extrinsic_args_works() -> Result<()> { -// let api = set_up_api("ws://127.0.0.1:9944").await?; -// // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; -// let ex = find_extrinsic_by_name(&api, "Nfts", "mint").await?; -// let args_parsed = process_extrinsic_args( -// &api, -// "Nfts", -// "mint", -// vec![ -// "1".to_string(), -// "1".to_string(), -// "Id(5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y)".to_string(), -// "Some(Some(1), Some(1))".to_string(), -// ], -// ) -// .await?; -// println!(" ARGS PARSER {:?}", args_parsed); + #[tokio::test] + async fn process_extrinsic_args_works() -> Result<()> { + let api = set_up_api("ws://127.0.0.1:9944").await?; + // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; + let ex = parse_extrinsic_by_name(&api, "Utility", "batch").await?; + println!("EXTRINSIC {:?}", ex); + println!(" ARGS PARSER {:?}", ex.fields); -// Ok(()) -// } + Ok(()) + } -// #[tokio::test] -// async fn process_extrinsic_args2_works() -> Result<()> { -// let api = set_up_api("ws://127.0.0.1:9944").await?; -// // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; -// let ex = find_extrinsic_by_name(&api, "Nfts", "mint").await?; -// let args_parsed = -// process_extrinsic_args(&api, "System", "remark", vec!["0x11".to_string()]).await?; -// println!(" ARGS PARSER {:?}", args_parsed); + // #[tokio::test] + // async fn process_extrinsic_args2_works() -> Result<()> { + // let api = set_up_api("ws://127.0.0.1:9944").await?; + // // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; + // let ex = find_extrinsic_by_name(&api, "Nfts", "mint").await?; + // let args_parsed = + // process_extrinsic_args(&api, "System", "remark", vec!["0x11".to_string()]).await?; + // println!(" ARGS PARSER {:?}", args_parsed); -// Ok(()) -// } -// } + // Ok(()) + // } +} diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index 98f26090a..58c76cc49 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -19,7 +19,7 @@ pub use call::{ construct_extrinsic, encode_call_data, metadata::{ action::{supported_actions, Action}, - field_to_param, find_extrinsic_by_name, find_pallet_by_name, parse_chain_metadata, Param, + find_extrinsic_by_name, find_pallet_by_name, parse_chain_metadata, Pallet, Param, }, set_up_api, sign_and_submit_extrinsic, }; From d8a7ed8da006f8861cf4909a3a7cf33138445582 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 22 Nov 2024 10:22:21 +0100 Subject: [PATCH 063/211] refactor: clean docs and move code --- crates/pop-cli/src/commands/call/parachain.rs | 77 ++++---- .../src/call/metadata/action.rs | 11 +- .../pop-parachains/src/call/metadata/mod.rs | 179 ++---------------- .../src/call/metadata/params.rs | 144 ++++++++++++++ .../src/call/metadata/type_parser.rs | 3 +- 5 files changed, 216 insertions(+), 198 deletions(-) create mode 100644 crates/pop-parachains/src/call/metadata/params.rs diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index dde9d2a51..9b6f9e5b0 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -14,13 +14,13 @@ const DEFAULT_URI: &str = "//Alice"; #[derive(Args, Clone)] pub struct CallParachainCommand { - /// The name of the pallet to call. + /// The pallet containing the extrinsic to execute. #[clap(long, short)] pallet: Option, - /// The name of the extrinsic to submit. + /// The extrinsic to execute within the chosen pallet. #[clap(long, short)] extrinsic: Option, - /// The constructor arguments, encoded as strings. + /// The extrinsic arguments, encoded as strings. #[clap(long, num_args = 0..,)] args: Vec, /// Websocket endpoint of a node. @@ -64,6 +64,22 @@ impl CallParachainCommand { Ok(()) } + fn display(&self) -> String { + let mut full_message = "pop call parachain".to_string(); + if let Some(pallet) = &self.pallet { + full_message.push_str(&format!(" --pallet {}", pallet)); + } + if let Some(extrinsic) = &self.extrinsic { + full_message.push_str(&format!(" --extrinsic {}", extrinsic)); + } + if !self.args.is_empty() { + let args: Vec<_> = self.args.iter().map(|a| format!("\"{a}\"")).collect(); + full_message.push_str(&format!(" --args {}", args.join(" "))); + } + full_message.push_str(&format!(" --url {} --suri {}", self.url, self.suri)); + full_message + } + /// Configure the call based on command line arguments/call UI. async fn configure( &mut self, @@ -74,11 +90,6 @@ impl CallParachainCommand { if !repeat { cli.intro("Call a parachain")?; } - // If extrinsic has been specified via command line arguments, return early. - // TODO: CALL DATA - // if self.extrinsic.is_some() { - // return Ok(()); - // } // Resolve url. if !repeat && self.url.as_str() == DEFAULT_URL { @@ -90,6 +101,7 @@ impl CallParachainCommand { .interact()?; self.url = url::Url::parse(&url)? }; + // Parse metadata from url chain. let api = set_up_api(self.url.as_str()).await?; let pallets = match parse_chain_metadata(&api).await { @@ -103,11 +115,12 @@ impl CallParachainCommand { }; // Resolve pallet. let pallet = if let Some(ref pallet_name) = self.pallet { + // Specified by the user. find_pallet_by_name(&pallets, pallet_name).await? } else { // Specific predefined actions first. - let picked_action: Option = prompt_predefined_actions(&pallets, cli).await?; - if let Some(action) = picked_action { + let action: Option = prompt_predefined_actions(&pallets, cli).await?; + if let Some(action) = action { self.pallet = Some(action.pallet_name().to_string()); self.extrinsic = Some(action.extrinsic_name().to_string()); find_pallet_by_name(&pallets, action.pallet_name()).await? @@ -134,9 +147,10 @@ impl CallParachainCommand { self.extrinsic = Some(extrinsic_prompted.name.clone()); extrinsic_prompted }; + // Certain extrinsics are not supported yet due to complexity. if !extrinsic.is_supported { cli.outro_cancel( - "The selected extrinsic is not supported. Please choose another one.", + "The selected extrinsic is not supported yet. Please choose another one.", )?; // Reset specific items from the last call and repeat. self.reset_for_new_call(); @@ -147,7 +161,6 @@ impl CallParachainCommand { if self.args.is_empty() { let mut contract_args = Vec::new(); for param in extrinsic.params { - //let param = field_to_param(&api, &extrinsic.name, &field)?; let input = prompt_for_param(&api, cli, ¶m)?; contract_args.push(input); } @@ -158,22 +171,6 @@ impl CallParachainCommand { Ok(api) } - fn display(&self) -> String { - let mut full_message = "pop call parachain".to_string(); - if let Some(pallet) = &self.pallet { - full_message.push_str(&format!(" --pallet {}", pallet)); - } - if let Some(extrinsic) = &self.extrinsic { - full_message.push_str(&format!(" --extrinsic {}", extrinsic)); - } - if !self.args.is_empty() { - let args: Vec<_> = self.args.iter().map(|a| format!("\"{a}\"")).collect(); - full_message.push_str(&format!(" --args {}", args.join(", "))); - } - full_message.push_str(&format!(" --url {} --suri {}", self.url, self.suri)); - full_message - } - /// Prepares the extrinsic or query. async fn prepare_extrinsic( &self, @@ -195,7 +192,7 @@ impl CallParachainCommand { let tx = match construct_extrinsic(api, &pallet, &extrinsic, self.args.clone()).await { Ok(tx) => tx, Err(e) => { - return Err(anyhow!("Error parsing the arguments: {}", e)); + return Err(anyhow!("Error: {}", e)); }, }; cli.info(format!("Encoded call data: {}", encode_call_data(api, &tx)?))?; @@ -210,14 +207,18 @@ impl CallParachainCommand { cli: &mut impl cli::traits::Cli, ) -> Result<()> { if self.suri == DEFAULT_URI { - self.suri = cli::Cli - .input("Who is going to sign the extrinsic:") + self.suri = cli + .input("Signer of the extrinsic:") .placeholder("//Alice") .default_input("//Alice") .interact()?; } cli.info(self.display())?; - if !cli.confirm("Do you want to submit the call?").initial_value(true).interact()? { + if !cli + .confirm("Do you want to submit the extrinsic?") + .initial_value(true) + .interact()? + { display_message( &format!( "Extrinsic {:?} was not submitted. Operation canceled by the user.", @@ -232,13 +233,13 @@ impl CallParachainCommand { spinner.start("Signing and submitting the extrinsic, please wait..."); let result = sign_and_submit_extrinsic(api.clone(), tx, &self.suri) .await - .map_err(|err| anyhow!("{} {}", "ERROR:", format!("{err:?}")))?; + .map_err(|err| anyhow!("{}", format!("{err:?}")))?; spinner.stop(&format!("Extrinsic submitted with hash: {:?}", result)); // Prompt for any additional calls. if !prompt_to_repeat_call { - display_message("Call completed successfully!", true, cli)?; + display_message("Extrinsic submitted successfully!", true, cli)?; return Ok(()); } if cli @@ -260,6 +261,7 @@ impl CallParachainCommand { fn reset_for_new_call(&mut self) { self.pallet = None; self.extrinsic = None; + self.args.clear(); } } @@ -272,6 +274,7 @@ fn display_message(message: &str, success: bool, cli: &mut impl cli::traits::Cli Ok(()) } +// Prompts the user for some predefined actions. async fn prompt_predefined_actions( pallets: &[Pallet], cli: &mut impl cli::traits::Cli, @@ -295,7 +298,6 @@ fn prompt_for_param( param: &Param, ) -> Result { if param.is_optional { - // Prompt user for optional parameter decision. if !cli .confirm(format!( "Do you want to provide a value for the optional parameter: {}?", @@ -308,7 +310,6 @@ fn prompt_for_param( let value = get_param_value(api, cli, param)?; Ok(format!("Some({})", value)) } else { - // Handle non-optional parameters. get_param_value(api, cli, param) } } @@ -328,6 +329,7 @@ fn get_param_value( } } +// Prompt for the value when is a primitive. fn prompt_for_primitive_param(cli: &mut impl cli::traits::Cli, param: &Param) -> Result { Ok(cli .input(format!("Enter the value for the parameter: {}", param.name)) @@ -335,6 +337,8 @@ fn prompt_for_primitive_param(cli: &mut impl cli::traits::Cli, param: &Param) -> .interact()?) } +// Prompt the user to select the value of the Variant parameter and recursively prompt for nested +// fields. Output example: Id(5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY) for the Id variant. fn prompt_for_variant_param( api: &OnlineClient, cli: &mut impl cli::traits::Cli, @@ -360,6 +364,7 @@ fn prompt_for_variant_param( } } +// Recursively prompt the user for all the nested fields in a Composite type. fn prompt_for_composite_param( api: &OnlineClient, cli: &mut impl cli::traits::Cli, diff --git a/crates/pop-parachains/src/call/metadata/action.rs b/crates/pop-parachains/src/call/metadata/action.rs index e27a95b20..a26e5c9e3 100644 --- a/crates/pop-parachains/src/call/metadata/action.rs +++ b/crates/pop-parachains/src/call/metadata/action.rs @@ -4,7 +4,7 @@ use super::{find_extrinsic_by_name, Pallet}; use strum::{EnumMessage as _, EnumProperty as _, VariantArray as _}; use strum_macros::{AsRefStr, Display, EnumMessage, EnumProperty, EnumString, VariantArray}; -/// Enum representing predefined actions. +/// Enum representing various predefined actions supported. #[derive( AsRefStr, Clone, @@ -61,17 +61,22 @@ impl Action { self.get_detailed_message().unwrap_or_default() } - /// Get the the extrinsic name of the action. + /// Get the extrinsic name corresponding to the action. pub fn extrinsic_name(&self) -> &str { self.get_message().unwrap_or_default() } - /// Get the pallet name of the action. + /// Get the associated pallet name for the action. pub fn pallet_name(&self) -> &str { self.get_str("Pallet").unwrap_or_default() } } +/// Fetch the list of supported actions based on available pallets. +/// +/// # Arguments +/// +/// * `pallets`: List of pallets availables in the chain. pub async fn supported_actions(pallets: &[Pallet]) -> Vec { let mut actions = Vec::new(); for action in Action::VARIANTS.iter() { diff --git a/crates/pop-parachains/src/call/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs index 37d1f5f1e..2ae213eae 100644 --- a/crates/pop-parachains/src/call/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -1,12 +1,12 @@ // SPDX-License-Identifier: GPL-3.0 use crate::errors::Error; -use pop_common::format_type; -use scale_info::{form::PortableForm, Field, PortableRegistry, TypeDef, Variant}; +use scale_info::{form::PortableForm, Variant}; use subxt::{dynamic::Value, Metadata, OnlineClient, SubstrateConfig}; use type_parser::process_argument; pub mod action; +mod params; mod type_parser; #[derive(Clone, PartialEq, Eq)] @@ -48,7 +48,8 @@ pub struct Param { pub is_variant: bool, } -/// Parses the chain metadata to extract information about pallets and their extrinsics. +/// Parses the chain metadata to extract information about pallets and their extrinsics with its +/// parameters. /// /// # Arguments /// * `api`: Reference to an `OnlineClient` connected to the chain. @@ -56,7 +57,6 @@ pub async fn parse_chain_metadata( api: &OnlineClient, ) -> Result, Error> { let metadata: Metadata = api.metadata(); - let registry = metadata.types(); let pallets = metadata .pallets() @@ -73,14 +73,15 @@ pub async fn parse_chain_metadata( let params = { let mut parsed_params = Vec::new(); for field in &variant.fields { - match field_to_param(api, &variant.name, field) { + match params::field_to_param(api, &variant.name, field) { Ok(param) => parsed_params.push(param), Err(Error::ExtrinsicNotSupported(_)) => { + // Unsupported extrinsic due to complex types is_supported = false; - parsed_params.clear(); // Discard any already-parsed params - break; // Stop processing further fields + parsed_params.clear(); + break; }, - Err(e) => return Err(e), // Propagate other errors + Err(e) => return Err(e), } } parsed_params @@ -91,6 +92,7 @@ pub async fn parse_chain_metadata( docs: if is_supported { variant.docs.concat() } else { + // To display the message in the UI "Extrinsic Not Supported".to_string() }, params, @@ -115,7 +117,7 @@ pub async fn parse_chain_metadata( /// Finds a specific pallet by name and retrieves its details from metadata. /// /// # Arguments -/// * `api`: Reference to an `OnlineClient` connected to the chain. +/// * `pallets`: List of pallets availables in the chain. /// * `pallet_name`: The name of the pallet to find. pub async fn find_pallet_by_name(pallets: &[Pallet], pallet_name: &str) -> Result { if let Some(pallet) = pallets.iter().find(|p| p.name == pallet_name) { @@ -128,7 +130,7 @@ pub async fn find_pallet_by_name(pallets: &[Pallet], pallet_name: &str) -> Resul /// Finds a specific extrinsic by name and retrieves its details from metadata. /// /// # Arguments -/// * `api`: Reference to an `OnlineClient` connected to the chain. +/// * `pallets`: List of pallets availables in the chain. /// * `pallet_name`: The name of the pallet to find. /// * `extrinsic_name`: Name of the extrinsic to locate. pub async fn find_extrinsic_by_name( @@ -137,7 +139,6 @@ pub async fn find_extrinsic_by_name( extrinsic_name: &str, ) -> Result { let pallet = find_pallet_by_name(pallets, pallet_name).await?; - // Check if the specified extrinsic exists within this pallet if let Some(extrinsic) = pallet.extrinsics.iter().find(|&e| e.name == extrinsic_name) { return Ok(extrinsic.clone()); } else { @@ -145,144 +146,6 @@ pub async fn find_extrinsic_by_name( } } -/// Transforms a metadata field into its `Param` representation. -/// -/// # Arguments -/// * `api`: Reference to an `OnlineClient` connected to the blockchain. -/// * `field`: A reference to a metadata field of the extrinsic. -fn field_to_param( - api: &OnlineClient, - extrinsic_name: &str, - field: &Field, -) -> Result { - let metadata: Metadata = api.metadata(); - let registry = metadata.types(); - let name = field.name.clone().unwrap_or("Unnamed".to_string()); //It can be unnamed field - type_to_param(extrinsic_name, name, registry, field.ty.id, &field.type_name) -} - -/// Converts a type's metadata into a `Param` representation. -/// -/// # Arguments -/// * `name`: The name of the parameter. -/// * `registry`: A reference to the `PortableRegistry` to resolve type dependencies. -/// * `type_id`: The ID of the type to be converted. -/// * `type_name`: An optional descriptive name for the type. -fn type_to_param( - extrinsic_name: &str, - name: String, - registry: &PortableRegistry, - type_id: u32, - type_name: &Option, -) -> Result { - let type_info = registry.resolve(type_id).ok_or(Error::MetadataParsingError(name.clone()))?; - if let Some(last_segment) = type_info.path.segments.last() { - if last_segment == "RuntimeCall" { - return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); - } - } - for param in &type_info.type_params { - if param.name == "RuntimeCall" || - param.name == "Vec" || - param.name == "Vec<::RuntimeCall>" - { - return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); - } - } - if type_info.path.segments == ["Option"] { - if let Some(sub_type_id) = type_info.type_params.get(0).and_then(|param| param.ty) { - // Recursive for the sub parameters - let sub_param = - type_to_param(extrinsic_name, name.clone(), registry, sub_type_id.id, type_name)?; - return Ok(Param { - name, - type_name: sub_param.type_name, - is_optional: true, - sub_params: sub_param.sub_params, - is_variant: false, - }); - } else { - Err(Error::MetadataParsingError(name)) - } - } else { - // Determine the formatted type name. - let type_name = format_type(type_info, registry); - match &type_info.type_def { - TypeDef::Primitive(_) => Ok(Param { - name, - type_name, - is_optional: false, - sub_params: Vec::new(), - is_variant: false, - }), - TypeDef::Composite(composite) => { - let sub_params = composite - .fields - .iter() - .map(|field| { - // Recursive for the sub parameters of composite type. - type_to_param( - extrinsic_name, - field.name.clone().unwrap_or(name.clone()), - registry, - field.ty.id, - &field.type_name, - ) - }) - .collect::, Error>>()?; - - Ok(Param { name, type_name, is_optional: false, sub_params, is_variant: false }) - }, - TypeDef::Variant(variant) => { - let variant_params = variant - .variants - .iter() - .map(|variant_param| { - let variant_sub_params = variant_param - .fields - .iter() - .map(|field| { - // Recursive for the sub parameters of variant type. - type_to_param( - extrinsic_name, - field.name.clone().unwrap_or(variant_param.name.clone()), - registry, - field.ty.id, - &field.type_name, - ) - }) - .collect::, Error>>()?; - Ok(Param { - name: variant_param.name.clone(), - type_name: "".to_string(), - is_optional: false, - sub_params: variant_sub_params, - is_variant: true, - }) - }) - .collect::, Error>>()?; - - Ok(Param { - name, - type_name, - is_optional: false, - sub_params: variant_params, - is_variant: true, - }) - }, - TypeDef::Array(_) | TypeDef::Sequence(_) | TypeDef::Tuple(_) | TypeDef::Compact(_) => - Ok(Param { - name, - type_name, - is_optional: false, - sub_params: Vec::new(), - is_variant: false, - }), - _ => Err(Error::MetadataParsingError(name)), - } - } -} - /// Processes and maps parameters for a given pallet extrinsic based on its metadata. /// /// # Arguments @@ -353,16 +216,16 @@ mod tests { // Ok(()) // } - #[tokio::test] - async fn process_extrinsic_args_works() -> Result<()> { - let api = set_up_api("ws://127.0.0.1:9944").await?; - // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; - let ex = parse_extrinsic_by_name(&api, "Utility", "batch").await?; - println!("EXTRINSIC {:?}", ex); - println!(" ARGS PARSER {:?}", ex.fields); + // #[tokio::test] + // async fn process_extrinsic_args_works() -> Result<()> { + // let api = set_up_api("ws://127.0.0.1:9944").await?; + // // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; + // let ex = parse_extrinsic_by_name(&api, "Utility", "batch").await?; + // println!("EXTRINSIC {:?}", ex); + // println!(" ARGS PARSER {:?}", ex.fields); - Ok(()) - } + // Ok(()) + // } // #[tokio::test] // async fn process_extrinsic_args2_works() -> Result<()> { diff --git a/crates/pop-parachains/src/call/metadata/params.rs b/crates/pop-parachains/src/call/metadata/params.rs new file mode 100644 index 000000000..b85efd597 --- /dev/null +++ b/crates/pop-parachains/src/call/metadata/params.rs @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-3.0 + +use crate::{errors::Error, Param}; +use pop_common::format_type; +use scale_info::{form::PortableForm, Field, PortableRegistry, TypeDef}; +use subxt::{Metadata, OnlineClient, SubstrateConfig}; + +/// Transforms a metadata field into its `Param` representation. +/// +/// # Arguments +/// * `api`: Reference to an `OnlineClient` connected to the blockchain. +/// * `field`: A reference to a metadata field of the extrinsic. +pub fn field_to_param( + api: &OnlineClient, + extrinsic_name: &str, + field: &Field, +) -> Result { + let metadata: Metadata = api.metadata(); + let registry = metadata.types(); + let name = field.name.clone().unwrap_or("Unnamed".to_string()); //It can be unnamed field + type_to_param(extrinsic_name, name, registry, field.ty.id, &field.type_name) +} + +/// Converts a type's metadata into a `Param` representation. +/// +/// # Arguments +/// * `name`: The name of the parameter. +/// * `registry`: A reference to the `PortableRegistry` to resolve type dependencies. +/// * `type_id`: The ID of the type to be converted. +/// * `type_name`: An optional descriptive name for the type. +fn type_to_param( + extrinsic_name: &str, + name: String, + registry: &PortableRegistry, + type_id: u32, + type_name: &Option, +) -> Result { + let type_info = registry.resolve(type_id).ok_or(Error::MetadataParsingError(name.clone()))?; + if let Some(last_segment) = type_info.path.segments.last() { + if last_segment == "RuntimeCall" { + return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); + } + } + for param in &type_info.type_params { + if param.name == "RuntimeCall" || + param.name == "Vec" || + param.name == "Vec<::RuntimeCall>" + { + return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); + } + } + if type_info.path.segments == ["Option"] { + if let Some(sub_type_id) = type_info.type_params.get(0).and_then(|param| param.ty) { + // Recursive for the sub parameters + let sub_param = + type_to_param(extrinsic_name, name.clone(), registry, sub_type_id.id, type_name)?; + return Ok(Param { + name, + type_name: sub_param.type_name, + is_optional: true, + sub_params: sub_param.sub_params, + is_variant: false, + }); + } else { + Err(Error::MetadataParsingError(name)) + } + } else { + // Determine the formatted type name. + let type_name = format_type(type_info, registry); + match &type_info.type_def { + TypeDef::Primitive(_) => Ok(Param { + name, + type_name, + is_optional: false, + sub_params: Vec::new(), + is_variant: false, + }), + TypeDef::Composite(composite) => { + let sub_params = composite + .fields + .iter() + .map(|field| { + // Recursive for the sub parameters of composite type. + type_to_param( + extrinsic_name, + field.name.clone().unwrap_or(name.clone()), + registry, + field.ty.id, + &field.type_name, + ) + }) + .collect::, Error>>()?; + + Ok(Param { name, type_name, is_optional: false, sub_params, is_variant: false }) + }, + TypeDef::Variant(variant) => { + let variant_params = variant + .variants + .iter() + .map(|variant_param| { + let variant_sub_params = variant_param + .fields + .iter() + .map(|field| { + // Recursive for the sub parameters of variant type. + type_to_param( + extrinsic_name, + field.name.clone().unwrap_or(variant_param.name.clone()), + registry, + field.ty.id, + &field.type_name, + ) + }) + .collect::, Error>>()?; + Ok(Param { + name: variant_param.name.clone(), + type_name: "".to_string(), + is_optional: false, + sub_params: variant_sub_params, + is_variant: true, + }) + }) + .collect::, Error>>()?; + + Ok(Param { + name, + type_name, + is_optional: false, + sub_params: variant_params, + is_variant: true, + }) + }, + TypeDef::Array(_) | TypeDef::Sequence(_) | TypeDef::Tuple(_) | TypeDef::Compact(_) => + Ok(Param { + name, + type_name, + is_optional: false, + sub_params: Vec::new(), + is_variant: false, + }), + _ => Err(Error::MetadataParsingError(name)), + } + } +} diff --git a/crates/pop-parachains/src/call/metadata/type_parser.rs b/crates/pop-parachains/src/call/metadata/type_parser.rs index 7f214f510..63b500db0 100644 --- a/crates/pop-parachains/src/call/metadata/type_parser.rs +++ b/crates/pop-parachains/src/call/metadata/type_parser.rs @@ -22,7 +22,8 @@ pub fn process_argument( let type_path = ty.path.segments.join("::"); match type_path.as_str() { "Option" => handle_option_type(arg, ty, registry), - "sp_core::crypto::AccountId32" => Ok(Value::from_bytes(parse_account(arg)?)), /* Specifically parse AccountId */ + // TODO: Handle other account types. + "sp_core::crypto::AccountId32" => Ok(Value::from_bytes(parse_account(arg)?)), _ => match &ty.type_def { TypeDef::Primitive(primitive) => primitive.parse(arg, registry), TypeDef::Composite(composite) => composite.parse(arg, registry), From ad009f7df5f10623c3f6dedc30dfe617a2854748 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 22 Nov 2024 10:38:32 +0100 Subject: [PATCH 064/211] fix: format_type --- crates/pop-common/src/metadata.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pop-common/src/metadata.rs b/crates/pop-common/src/metadata.rs index 3cff32d72..4ea58d806 100644 --- a/crates/pop-common/src/metadata.rs +++ b/crates/pop-common/src/metadata.rs @@ -148,7 +148,7 @@ pub fn format_type(ty: &Type, registry: &PortableRegistry) -> Stri ) }, TypeDef::BitSequence(_) => { - unimplemented!("bit sequence not currently supported") + "BitSequence".to_string() }, } ); From 5998a7e5e2f219fbebe78b402ebbe32aef509516 Mon Sep 17 00:00:00 2001 From: Alex Bean Date: Fri, 22 Nov 2024 14:12:52 +0100 Subject: [PATCH 065/211] fix: parse user inputs for Option arguments (#332) * fix: automatically add some or none to Option argument * test: refactor and tests * refactor: improve code and comments * fix: renaming and clean code * chore: option params not mandatory * fix: parse user inputs for Option arguments in constructor (#335) * fix: automatically add some or none to Option argument * fix: tests * refactor: process_function_args * test: update tests accordingly last changes * fix: issue with delimiter * test: fix unit test * refactor: renaming and fix comments * refactor: format types (#339) Shows the full type representation, making it easier to see the entry format of parameter values. * fix: logo doesn't show in README --------- Co-authored-by: Frank Bell <60948618+evilrobot-01@users.noreply.github.com> Co-authored-by: Alejandro Martinez Andres <11448715+al3mart@users.noreply.github.com> --- README.md | 2 +- crates/pop-cli/src/commands/call/contract.rs | 37 +- crates/pop-cli/src/commands/up/contract.rs | 4 +- .../src/{call/mod.rs => call.rs} | 30 +- crates/pop-contracts/src/call/metadata.rs | 102 ---- crates/pop-contracts/src/errors.rs | 12 +- crates/pop-contracts/src/lib.rs | 10 +- crates/pop-contracts/src/up.rs | 10 +- crates/pop-contracts/src/utils/metadata.rs | 518 ++++++++++++++++++ crates/pop-contracts/src/utils/mod.rs | 1 + .../tests/files/testing.contract | 2 +- crates/pop-contracts/tests/files/testing.json | 161 ++++-- 12 files changed, 705 insertions(+), 184 deletions(-) rename crates/pop-contracts/src/{call/mod.rs => call.rs} (93%) delete mode 100644 crates/pop-contracts/src/call/metadata.rs create mode 100644 crates/pop-contracts/src/utils/metadata.rs diff --git a/README.md b/README.md index df7f72a99..a3a63f945 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Pop CLI - +
diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 1a559ebc7..8db1cc63c 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -30,7 +30,7 @@ pub struct CallContractCommand { #[clap(long, short)] message: Option, /// The constructor arguments, encoded as strings. - #[clap(long, num_args = 0..)] + #[clap(long, num_args = 0..,)] args: Vec, /// The value to be transferred as part of the call. #[clap(name = "value", short = 'v', long, default_value = DEFAULT_PAYABLE_VALUE)] @@ -96,7 +96,8 @@ impl CallContractCommand { full_message.push_str(&format!(" --message {}", message)); } if !self.args.is_empty() { - full_message.push_str(&format!(" --args {}", self.args.join(" "))); + let args: Vec<_> = self.args.iter().map(|a| format!("\"{a}\"")).collect(); + full_message.push_str(&format!(" --args {}", args.join(", "))); } if self.value != DEFAULT_PAYABLE_VALUE { full_message.push_str(&format!(" --value {}", self.value)); @@ -176,7 +177,7 @@ impl CallContractCommand { }; // Parse the contract metadata provided. If there is an error, do not prompt for more. - let messages = match get_messages(&contract_path) { + let messages = match get_messages(contract_path) { Ok(messages) => messages, Err(e) => { return Err(anyhow!(format!( @@ -230,11 +231,15 @@ impl CallContractCommand { // Resolve message arguments. let mut contract_args = Vec::new(); for arg in &message.args { - contract_args.push( - cli.input(format!("Enter the value for the parameter: {}", arg.label)) - .placeholder(&format!("Type required: {}", &arg.type_name)) - .interact()?, - ); + let mut input = cli + .input(format!("Enter the value for the parameter: {}", arg.label)) + .placeholder(&format!("Type required: {}", arg.type_name)); + + // Set default input only if the parameter type is `Option` (Not mandatory) + if arg.type_name.starts_with("Option<") { + input = input.default_input(""); + } + contract_args.push(input.interact()?); } self.args = contract_args; @@ -671,6 +676,7 @@ mod tests { .expect_input("Enter the proof size limit:", "".into()) // Only if call .expect_input("Enter the gas limit:", "".into()) // Only if call .expect_input("Value to transfer to the call:", "50".into()) // Only if payable + .expect_input("Enter the value for the parameter: number", "2".into()) // Args for specific_flip .expect_input("Enter the value for the parameter: new_value", "true".into()) // Args for specific_flip .expect_select::( "Select the message to call:", @@ -691,7 +697,7 @@ mod tests { "Where is your project located?", temp_dir.path().join("testing").display().to_string(), ).expect_info(format!( - "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args \"true\", \"2\" --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", temp_dir.path().join("testing").display().to_string(), )); @@ -715,8 +721,9 @@ mod tests { Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) ); assert_eq!(call_config.message, Some("specific_flip".to_string())); - assert_eq!(call_config.args.len(), 1); + assert_eq!(call_config.args.len(), 2); assert_eq!(call_config.args[0], "true".to_string()); + assert_eq!(call_config.args[1], "2".to_string()); assert_eq!(call_config.value, "50".to_string()); assert_eq!(call_config.gas_limit, None); assert_eq!(call_config.proof_size, None); @@ -725,7 +732,7 @@ mod tests { assert!(call_config.execute); assert!(!call_config.dry_run); assert_eq!(call_config.display(), format!( - "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args \"true\", \"2\" --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", temp_dir.path().join("testing").display().to_string(), )); @@ -754,6 +761,7 @@ mod tests { let mut cli = MockCli::new() .expect_input("Signer calling the contract:", "//Alice".into()) .expect_input("Value to transfer to the call:", "50".into()) // Only if payable + .expect_input("Enter the value for the parameter: number", "2".into()) // Args for specific_flip .expect_input("Enter the value for the parameter: new_value", "true".into()) // Args for specific_flip .expect_select::( "Select the message to call:", @@ -774,7 +782,7 @@ mod tests { "Where is your project located?", temp_dir.path().join("testing").display().to_string(), ).expect_info(format!( - "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args \"true\", \"2\" --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", temp_dir.path().join("testing").display().to_string(), )); @@ -798,8 +806,9 @@ mod tests { Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) ); assert_eq!(call_config.message, Some("specific_flip".to_string())); - assert_eq!(call_config.args.len(), 1); + assert_eq!(call_config.args.len(), 2); assert_eq!(call_config.args[0], "true".to_string()); + assert_eq!(call_config.args[1], "2".to_string()); assert_eq!(call_config.value, "50".to_string()); assert_eq!(call_config.gas_limit, None); assert_eq!(call_config.proof_size, None); @@ -809,7 +818,7 @@ mod tests { assert!(!call_config.dry_run); assert!(call_config.dev_mode); assert_eq!(call_config.display(), format!( - "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args \"true\", \"2\" --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", temp_dir.path().join("testing").display().to_string(), )); diff --git a/crates/pop-cli/src/commands/up/contract.rs b/crates/pop-cli/src/commands/up/contract.rs index d357d958b..42166a695 100644 --- a/crates/pop-cli/src/commands/up/contract.rs +++ b/crates/pop-cli/src/commands/up/contract.rs @@ -35,7 +35,7 @@ pub struct UpContractCommand { #[clap(name = "constructor", long, default_value = "new")] constructor: String, /// The constructor arguments, encoded as strings. - #[clap(long, num_args = 0..)] + #[clap(long, num_args = 0..,)] args: Vec, /// Transfers an initial balance to the instantiated contract. #[clap(name = "value", long, default_value = "0")] @@ -321,7 +321,7 @@ mod tests { let command = UpContractCommand { path: None, constructor: "new".to_string(), - args: vec!["false".to_string()].to_vec(), + args: vec![], value: "0".to_string(), gas_limit: None, proof_size: None, diff --git a/crates/pop-contracts/src/call/mod.rs b/crates/pop-contracts/src/call.rs similarity index 93% rename from crates/pop-contracts/src/call/mod.rs rename to crates/pop-contracts/src/call.rs index 2c363301b..7a964c387 100644 --- a/crates/pop-contracts/src/call/mod.rs +++ b/crates/pop-contracts/src/call.rs @@ -4,6 +4,7 @@ use crate::{ errors::Error, utils::{ helpers::{get_manifest_path, parse_account, parse_balance}, + metadata::{process_function_args, FunctionType}, signer::create_signer, }, }; @@ -20,8 +21,6 @@ use subxt::{Config, PolkadotConfig as DefaultConfig}; use subxt_signer::sr25519::Keypair; use url::Url; -pub mod metadata; - /// Attributes for the `call` command. pub struct CallOpts { /// Path to the contract build directory. @@ -53,7 +52,7 @@ pub struct CallOpts { /// * `call_opts` - options for the `call` command. pub async fn set_up_call( call_opts: CallOpts, -) -> anyhow::Result> { +) -> Result, Error> { let token_metadata = TokenMetadata::query::(&call_opts.url).await?; let manifest_path = get_manifest_path(call_opts.path.as_deref())?; let signer = create_signer(&call_opts.suri)?; @@ -67,10 +66,17 @@ pub async fn set_up_call( parse_balance(&call_opts.value)?; let contract: ::AccountId = parse_account(&call_opts.contract)?; + // Process the argument values input by the user. + let args = process_function_args( + call_opts.path.unwrap_or_else(|| PathBuf::from("./")), + &call_opts.message, + call_opts.args, + FunctionType::Message, + )?; let call_exec: CallExec = CallCommandBuilder::new(contract.clone(), &call_opts.message, extrinsic_opts) - .args(call_opts.args.clone()) + .args(args) .value(value.denominate_balance(&token_metadata)?) .gas_limit(call_opts.gas_limit) .proof_size(call_opts.proof_size) @@ -212,11 +218,9 @@ mod tests { suri: "//Alice".to_string(), execute: false, }; - let call = set_up_call(call_opts).await; - assert!(call.is_err()); - let error = call.err().unwrap(); - assert_eq!(error.root_cause().to_string(), "Failed to find any contract artifacts in target directory. \nRun `cargo contract build --release` to generate the artifacts."); - + assert!( + matches!(set_up_call(call_opts).await, Err(Error::AnyhowError(message)) if message.root_cause().to_string() == "Failed to find any contract artifacts in target directory. \nRun `cargo contract build --release` to generate the artifacts.") + ); Ok(()) } #[tokio::test] @@ -233,11 +237,9 @@ mod tests { suri: "//Alice".to_string(), execute: false, }; - let call = set_up_call(call_opts).await; - assert!(call.is_err()); - let error = call.err().unwrap(); - assert_eq!(error.root_cause().to_string(), "No 'ink' dependency found"); - + assert!( + matches!(set_up_call(call_opts).await, Err(Error::AnyhowError(message)) if message.root_cause().to_string() == "No 'ink' dependency found") + ); Ok(()) } diff --git a/crates/pop-contracts/src/call/metadata.rs b/crates/pop-contracts/src/call/metadata.rs deleted file mode 100644 index 605d06206..000000000 --- a/crates/pop-contracts/src/call/metadata.rs +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 - -use crate::errors::Error; -use contract_extrinsics::ContractArtifacts; -use contract_transcode::ink_metadata::MessageParamSpec; -use scale_info::form::PortableForm; -use std::path::Path; - -#[derive(Clone, PartialEq, Eq)] -/// Describes a contract message. -pub struct Param { - /// The label of the parameter. - pub label: String, - /// The type name of the parameter. - pub type_name: String, -} -#[derive(Clone, PartialEq, Eq)] -/// Describes a contract message. -pub struct Message { - /// The label of the message. - pub label: String, - /// If the message is allowed to mutate the contract state. - pub mutates: bool, - /// If the message accepts any `value` from the caller. - pub payable: bool, - /// The parameters of the deployment handler. - pub args: Vec, - /// The message documentation. - pub docs: String, - /// If the message is the default for off-chain consumers (e.g UIs). - pub default: bool, -} - -/// Extracts a list of smart contract messages parsing the metadata file. -/// -/// # Arguments -/// * `path` - Location path of the project. -pub fn get_messages(path: &Path) -> Result, Error> { - let cargo_toml_path = match path.ends_with("Cargo.toml") { - true => path.to_path_buf(), - false => path.join("Cargo.toml"), - }; - let contract_artifacts = - ContractArtifacts::from_manifest_or_file(Some(&cargo_toml_path), None)?; - let transcoder = contract_artifacts.contract_transcoder()?; - let mut messages: Vec = Vec::new(); - for message in transcoder.metadata().spec().messages() { - messages.push(Message { - label: message.label().to_string(), - mutates: message.mutates(), - payable: message.payable(), - args: process_args(message.args()), - docs: message.docs().join(" "), - default: *message.default(), - }); - } - Ok(messages) -} -// Parse the message parameters into a vector of argument labels. -fn process_args(message_params: &[MessageParamSpec]) -> Vec { - let mut args: Vec = Vec::new(); - for arg in message_params { - args.push(Param { - label: arg.label().to_string(), - type_name: arg.ty().display_name().to_string(), - }); - } - args -} - -#[cfg(test)] -mod tests { - use std::env; - - use super::*; - use crate::{mock_build_process, new_environment}; - use anyhow::Result; - - #[test] - fn get_messages_work() -> Result<()> { - let temp_dir = new_environment("testing")?; - let current_dir = env::current_dir().expect("Failed to get current directory"); - mock_build_process( - temp_dir.path().join("testing"), - current_dir.join("./tests/files/testing.contract"), - current_dir.join("./tests/files/testing.json"), - )?; - let message = get_messages(&temp_dir.path().join("testing"))?; - assert_eq!(message.len(), 3); - assert_eq!(message[0].label, "flip"); - assert_eq!(message[0].docs, " A message that can be called on instantiated contracts. This one flips the value of the stored `bool` from `true` to `false` and vice versa."); - assert_eq!(message[1].label, "get"); - assert_eq!(message[1].docs, " Simply returns the current value of our `bool`."); - assert_eq!(message[2].label, "specific_flip"); - assert_eq!(message[2].docs, " A message for testing, flips the value of the stored `bool` with `new_value` and is payable"); - // assert parsed arguments - assert_eq!(message[2].args.len(), 1); - assert_eq!(message[2].args[0].label, "new_value".to_string()); - assert_eq!(message[2].args[0].type_name, "bool".to_string()); - Ok(()) - } -} diff --git a/crates/pop-contracts/src/errors.rs b/crates/pop-contracts/src/errors.rs index a0ea14ade..3d19d679b 100644 --- a/crates/pop-contracts/src/errors.rs +++ b/crates/pop-contracts/src/errors.rs @@ -28,6 +28,10 @@ pub enum Error { InstallContractsNode(String), #[error("{0}")] InstantiateContractError(String), + #[error("Invalid constructor name: {0}")] + InvalidConstructorName(String), + #[error("Invalid message name: {0}")] + InvalidMessageName(String), #[error("Invalid name: {0}")] InvalidName(String), #[error("IO error: {0}")] @@ -36,6 +40,8 @@ pub enum Error { KeyPairCreation(String), #[error("Failed to get manifest path: {0}")] ManifestPath(String), + #[error("Argument {0} is required")] + MissingArgument(String), #[error("Failed to create new contract project: {0}")] NewContract(String), #[error("ParseError error: {0}")] @@ -44,12 +50,14 @@ pub enum Error { ParseSecretURI(String), #[error("The `Repository` property is missing from the template variant")] RepositoryMissing, + #[error("Sourcing error {0}")] + SourcingError(SourcingError), #[error("Failed to execute test command: {0}")] TestCommand(String), #[error("Unsupported platform: {os}")] UnsupportedPlatform { os: &'static str }, #[error("{0}")] UploadContractError(String), - #[error("Sourcing error {0}")] - SourcingError(SourcingError), + #[error("Incorrect number of arguments provided. Expecting {expected}, {provided} provided")] + IncorrectArguments { expected: usize, provided: usize }, } diff --git a/crates/pop-contracts/src/lib.rs b/crates/pop-contracts/src/lib.rs index a3ff0175c..142283998 100644 --- a/crates/pop-contracts/src/lib.rs +++ b/crates/pop-contracts/src/lib.rs @@ -14,9 +14,7 @@ mod utils; pub use build::{build_smart_contract, is_supported, Verbosity}; pub use call::{ - call_smart_contract, dry_run_call, dry_run_gas_estimate_call, - metadata::{get_messages, Message}, - set_up_call, CallOpts, + call_smart_contract, dry_run_call, dry_run_gas_estimate_call, set_up_call, CallOpts, }; pub use new::{create_smart_contract, is_valid_contract_name}; pub use node::{contracts_node_generator, is_chain_alive, run_contracts_node}; @@ -27,4 +25,8 @@ pub use up::{ dry_run_gas_estimate_instantiate, dry_run_upload, instantiate_smart_contract, set_up_deployment, set_up_upload, upload_smart_contract, UpOpts, }; -pub use utils::{helpers::parse_account, signer::parse_hex_bytes}; +pub use utils::{ + helpers::parse_account, + metadata::{get_messages, ContractFunction}, + signer::parse_hex_bytes, +}; diff --git a/crates/pop-contracts/src/up.rs b/crates/pop-contracts/src/up.rs index 6d2f0ec03..2b304123b 100644 --- a/crates/pop-contracts/src/up.rs +++ b/crates/pop-contracts/src/up.rs @@ -3,6 +3,7 @@ use crate::{ errors::Error, utils::{ helpers::{get_manifest_path, parse_balance}, + metadata::{process_function_args, FunctionType}, signer::create_signer, }, }; @@ -62,10 +63,17 @@ pub async fn set_up_deployment( let value: BalanceVariant<::Balance> = parse_balance(&up_opts.value)?; + // Process the argument values input by the user. + let args = process_function_args( + up_opts.path.unwrap_or_else(|| PathBuf::from("./")), + &up_opts.constructor, + up_opts.args, + FunctionType::Constructor, + )?; let instantiate_exec: InstantiateExec = InstantiateCommandBuilder::new(extrinsic_opts) .constructor(up_opts.constructor.clone()) - .args(up_opts.args.clone()) + .args(args) .value(value.denominate_balance(&token_metadata)?) .gas_limit(up_opts.gas_limit) .proof_size(up_opts.proof_size) diff --git a/crates/pop-contracts/src/utils/metadata.rs b/crates/pop-contracts/src/utils/metadata.rs new file mode 100644 index 000000000..f0dd2b8e1 --- /dev/null +++ b/crates/pop-contracts/src/utils/metadata.rs @@ -0,0 +1,518 @@ +// SPDX-License-Identifier: GPL-3.0 + +use crate::errors::Error; +use contract_extrinsics::ContractArtifacts; +use contract_transcode::ink_metadata::MessageParamSpec; +use scale_info::{form::PortableForm, PortableRegistry, Type, TypeDef, TypeDefPrimitive}; +use std::path::Path; + +/// Describes a parameter. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Param { + /// The label of the parameter. + pub label: String, + /// The type name of the parameter. + pub type_name: String, +} + +/// Describes a contract function. +#[derive(Clone, PartialEq, Eq)] +pub struct ContractFunction { + /// The label of the function. + pub label: String, + /// If the function accepts any `value` from the caller. + pub payable: bool, + /// The parameters of the deployment handler. + pub args: Vec, + /// The function documentation. + pub docs: String, + /// If the message/constructor is the default for off-chain consumers (e.g UIs). + pub default: bool, + /// If the message is allowed to mutate the contract state. true for constructors. + pub mutates: bool, +} + +/// Specifies the type of contract funtion, either a constructor or a message. +#[derive(Clone, PartialEq, Eq)] +pub enum FunctionType { + Constructor, + Message, +} + +/// Extracts a list of smart contract messages parsing the metadata file. +/// +/// # Arguments +/// * `path` - Location path of the project. +pub fn get_messages(path: &Path) -> Result, Error> { + let cargo_toml_path = match path.ends_with("Cargo.toml") { + true => path.to_path_buf(), + false => path.join("Cargo.toml"), + }; + let contract_artifacts = + ContractArtifacts::from_manifest_or_file(Some(&cargo_toml_path), None)?; + let transcoder = contract_artifacts.contract_transcoder()?; + let metadata = transcoder.metadata(); + Ok(metadata + .spec() + .messages() + .iter() + .map(|message| ContractFunction { + label: message.label().to_string(), + mutates: message.mutates(), + payable: message.payable(), + args: process_args(message.args(), metadata.registry()), + docs: message.docs().join(" "), + default: *message.default(), + }) + .collect()) +} + +/// Extracts the information of a smart contract message parsing the metadata file. +/// +/// # Arguments +/// * `path` - Location path of the project. +/// * `message` - The label of the contract message. +fn get_message

(path: P, message: &str) -> Result +where + P: AsRef, +{ + get_messages(path.as_ref())? + .into_iter() + .find(|msg| msg.label == message) + .ok_or_else(|| Error::InvalidMessageName(message.to_string())) +} + +/// Extracts a list of smart contract contructors parsing the metadata file. +/// +/// # Arguments +/// * `path` - Location path of the project. +pub fn get_constructors(path: &Path) -> Result, Error> { + let cargo_toml_path = match path.ends_with("Cargo.toml") { + true => path.to_path_buf(), + false => path.join("Cargo.toml"), + }; + let contract_artifacts = + ContractArtifacts::from_manifest_or_file(Some(&cargo_toml_path), None)?; + let transcoder = contract_artifacts.contract_transcoder()?; + let metadata = transcoder.metadata(); + Ok(metadata + .spec() + .constructors() + .iter() + .map(|constructor| ContractFunction { + label: constructor.label().to_string(), + payable: *constructor.payable(), + args: process_args(constructor.args(), metadata.registry()), + docs: constructor.docs().join(" "), + default: *constructor.default(), + mutates: true, + }) + .collect()) +} + +/// Extracts the information of a smart contract constructor parsing the metadata file. +/// +/// # Arguments +/// * `path` - Location path of the project. +/// * `constructor` - The label of the constructor. +fn get_constructor

(path: P, constructor: &str) -> Result +where + P: AsRef, +{ + get_constructors(path.as_ref())? + .into_iter() + .find(|c| c.label == constructor) + .ok_or_else(|| Error::InvalidConstructorName(constructor.to_string())) +} + +// Parse the parameters into a vector of argument labels. +fn process_args( + params: &[MessageParamSpec], + registry: &PortableRegistry, +) -> Vec { + let mut args: Vec = Vec::new(); + for arg in params { + // Resolve type from registry to provide full type representation. + let type_name = + format_type(registry.resolve(arg.ty().ty().id).expect("type not found"), registry); + args.push(Param { label: arg.label().to_string(), type_name }); + } + args +} + +// Formats a specified type, using the registry to output its full type representation. +fn format_type(ty: &Type, registry: &PortableRegistry) -> String { + let mut name = ty + .path + .segments + .last() + .map(|s| s.to_owned()) + .unwrap_or_else(|| ty.path.to_string()); + + if !ty.type_params.is_empty() { + let params: Vec<_> = ty + .type_params + .iter() + .filter_map(|p| registry.resolve(p.ty.unwrap().id)) + .map(|t| format_type(t, registry)) + .collect(); + name = format!("{name}<{}>", params.join(",")); + } + + name = format!( + "{name}{}", + match &ty.type_def { + TypeDef::Composite(composite) => { + if composite.fields.is_empty() { + return "".to_string(); + } + + let mut named = false; + let fields: Vec<_> = composite + .fields + .iter() + .filter_map(|f| match f.name.as_ref() { + None => registry.resolve(f.ty.id).map(|t| format_type(t, registry)), + Some(field) => { + named = true; + f.type_name.as_ref().map(|t| format!("{field}: {t}")) + }, + }) + .collect(); + match named { + true => format!(" {{ {} }}", fields.join(", ")), + false => format!(" ({})", fields.join(", ")), + } + }, + TypeDef::Variant(variant) => { + let variants: Vec<_> = variant + .variants + .iter() + .map(|v| { + if v.fields.is_empty() { + return v.name.clone(); + } + + let name = v.name.as_str(); + let mut named = false; + let fields: Vec<_> = v + .fields + .iter() + .filter_map(|f| match f.name.as_ref() { + None => registry.resolve(f.ty.id).map(|t| format_type(t, registry)), + Some(field) => { + named = true; + f.type_name.as_ref().map(|t| format!("{field}: {t}")) + }, + }) + .collect(); + format!( + "{name}{}", + match named { + true => format!("{{ {} }}", fields.join(", ")), + false => format!("({})", fields.join(", ")), + } + ) + }) + .collect(); + format!(": {}", variants.join(", ")) + }, + TypeDef::Sequence(sequence) => { + format!( + "[{}]", + format_type( + registry.resolve(sequence.type_param.id).expect("sequence type not found"), + registry + ) + ) + }, + TypeDef::Array(array) => { + format!( + "[{};{}]", + format_type( + registry.resolve(array.type_param.id).expect("array type not found"), + registry + ), + array.len + ) + }, + TypeDef::Tuple(tuple) => { + let fields: Vec<_> = tuple + .fields + .iter() + .filter_map(|p| registry.resolve(p.id)) + .map(|t| format_type(t, registry)) + .collect(); + format!("({})", fields.join(",")) + }, + TypeDef::Primitive(primitive) => { + use TypeDefPrimitive::*; + match primitive { + Bool => "bool", + Char => "char", + Str => "str", + U8 => "u8", + U16 => "u16", + U32 => "u32", + U64 => "u64", + U128 => "u128", + U256 => "u256", + I8 => "i8", + I16 => "i16", + I32 => "i32", + I64 => "i64", + I128 => "i128", + I256 => "i256", + } + .to_string() + }, + TypeDef::Compact(compact) => { + format!( + "Compact<{}>", + format_type( + registry.resolve(compact.type_param.id).expect("compact type not found"), + registry + ) + ) + }, + TypeDef::BitSequence(_) => { + unimplemented!("bit sequence not currently supported") + }, + } + ); + + name +} + +/// Processes a list of argument values for a specified contract function, +/// wrapping each value in `Some(...)` or replacing it with `None` if the argument is optional +/// +/// # Arguments +/// * `path` - Location path of the project. +/// * `label` - Label of the contract message to retrieve. +/// * `args` - Argument values provided by the user. +pub fn process_function_args

( + path: P, + label: &str, + args: Vec, + function_type: FunctionType, +) -> Result, Error> +where + P: AsRef, +{ + let function = match function_type { + FunctionType::Message => get_message(path, label)?, + FunctionType::Constructor => get_constructor(path, label)?, + }; + if args.len() != function.args.len() { + return Err(Error::IncorrectArguments { + expected: function.args.len(), + provided: args.len(), + }); + } + Ok(args + .into_iter() + .zip(&function.args) + .map(|(arg, param)| match (param.type_name.starts_with("Option<"), arg.is_empty()) { + // If the argument is Option and empty, replace it with `None` + (true, true) => "None".to_string(), + // If the argument is Option and not empty, wrap it in `Some(...)` + (true, false) => format!("Some({})", arg), + // If the argument is not Option, return it as is + _ => arg, + }) + .collect::>()) +} + +#[cfg(test)] +mod tests { + use std::env; + + use super::*; + use crate::{mock_build_process, new_environment}; + use anyhow::Result; + + #[test] + fn get_messages_work() -> Result<()> { + let temp_dir = new_environment("testing")?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; + let message = get_messages(&temp_dir.path().join("testing"))?; + assert_eq!(message.len(), 3); + assert_eq!(message[0].label, "flip"); + assert_eq!(message[0].docs, " A message that can be called on instantiated contracts. This one flips the value of the stored `bool` from `true` to `false` and vice versa."); + assert_eq!(message[1].label, "get"); + assert_eq!(message[1].docs, " Simply returns the current value of our `bool`."); + assert_eq!(message[2].label, "specific_flip"); + assert_eq!(message[2].docs, " A message for testing, flips the value of the stored `bool` with `new_value` and is payable"); + // assert parsed arguments + assert_eq!(message[2].args.len(), 2); + assert_eq!(message[2].args[0].label, "new_value".to_string()); + assert_eq!(message[2].args[0].type_name, "bool".to_string()); + assert_eq!(message[2].args[1].label, "number".to_string()); + assert_eq!(message[2].args[1].type_name, "Option: None, Some(u32)".to_string()); + Ok(()) + } + + #[test] + fn get_message_work() -> Result<()> { + let temp_dir = new_environment("testing")?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; + assert!(matches!( + get_message(&temp_dir.path().join("testing"), "wrong_flip"), + Err(Error::InvalidMessageName(name)) if name == "wrong_flip".to_string())); + let message = get_message(&temp_dir.path().join("testing"), "specific_flip")?; + assert_eq!(message.label, "specific_flip"); + assert_eq!(message.docs, " A message for testing, flips the value of the stored `bool` with `new_value` and is payable"); + // assert parsed arguments + assert_eq!(message.args.len(), 2); + assert_eq!(message.args[0].label, "new_value".to_string()); + assert_eq!(message.args[0].type_name, "bool".to_string()); + assert_eq!(message.args[1].label, "number".to_string()); + assert_eq!(message.args[1].type_name, "Option: None, Some(u32)".to_string()); + Ok(()) + } + + #[test] + fn get_constructors_work() -> Result<()> { + let temp_dir = new_environment("testing")?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; + let constructor = get_constructors(&temp_dir.path().join("testing"))?; + assert_eq!(constructor.len(), 2); + assert_eq!(constructor[0].label, "new"); + assert_eq!( + constructor[0].docs, + "Constructor that initializes the `bool` value to the given `init_value`." + ); + assert_eq!(constructor[1].label, "default"); + assert_eq!( + constructor[1].docs, + "Constructor that initializes the `bool` value to `false`. Constructors can delegate to other constructors." + ); + // assert parsed arguments + assert_eq!(constructor[0].args.len(), 1); + assert_eq!(constructor[0].args[0].label, "init_value".to_string()); + assert_eq!(constructor[0].args[0].type_name, "bool".to_string()); + assert_eq!(constructor[1].args.len(), 2); + assert_eq!(constructor[1].args[0].label, "init_value".to_string()); + assert_eq!(constructor[1].args[0].type_name, "bool".to_string()); + assert_eq!(constructor[1].args[1].label, "number".to_string()); + assert_eq!(constructor[1].args[1].type_name, "Option: None, Some(u32)".to_string()); + Ok(()) + } + + #[test] + fn get_constructor_work() -> Result<()> { + let temp_dir = new_environment("testing")?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; + assert!(matches!( + get_constructor(&temp_dir.path().join("testing"), "wrong_constructor"), + Err(Error::InvalidConstructorName(name)) if name == "wrong_constructor".to_string())); + let constructor = get_constructor(&temp_dir.path().join("testing"), "default")?; + assert_eq!(constructor.label, "default"); + assert_eq!( + constructor.docs, + "Constructor that initializes the `bool` value to `false`. Constructors can delegate to other constructors." + ); + // assert parsed arguments + assert_eq!(constructor.args.len(), 2); + assert_eq!(constructor.args[0].label, "init_value".to_string()); + assert_eq!(constructor.args[0].type_name, "bool".to_string()); + assert_eq!(constructor.args[1].label, "number".to_string()); + assert_eq!(constructor.args[1].type_name, "Option: None, Some(u32)".to_string()); + Ok(()) + } + + #[test] + fn process_function_args_work() -> Result<()> { + let temp_dir = new_environment("testing")?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; + assert!(matches!( + process_function_args(temp_dir.path().join("testing"),"wrong_flip", Vec::new(), FunctionType::Message), + Err(Error::InvalidMessageName(error)) if error == "wrong_flip".to_string())); + assert!(matches!( + process_function_args( + temp_dir.path().join("testing"), + "specific_flip", + Vec::new(), + FunctionType::Message + ), + Err(Error::IncorrectArguments {expected, provided }) if expected == 2 && provided == 0 + )); + assert_eq!( + process_function_args( + temp_dir.path().join("testing"), + "specific_flip", + ["true".to_string(), "2".to_string()].to_vec(), + FunctionType::Message + )?, + ["true".to_string(), "Some(2)".to_string()] + ); + assert_eq!( + process_function_args( + temp_dir.path().join("testing"), + "specific_flip", + ["true".to_string(), "".to_string()].to_vec(), + FunctionType::Message + )?, + ["true".to_string(), "None".to_string()] + ); + + // Test constructors + assert!(matches!( + process_function_args(temp_dir.path().join("testing"),"wrong_constructor", Vec::new(), FunctionType::Constructor), + Err(Error::InvalidConstructorName(error)) if error == "wrong_constructor".to_string())); + assert!(matches!( + process_function_args( + temp_dir.path().join("testing"), + "default", + Vec::new(), + FunctionType::Constructor + ), + Err(Error::IncorrectArguments {expected, provided }) if expected == 2 && provided == 0 + )); + assert_eq!( + process_function_args( + temp_dir.path().join("testing"), + "default", + ["true".to_string(), "2".to_string()].to_vec(), + FunctionType::Constructor + )?, + ["true".to_string(), "Some(2)".to_string()] + ); + assert_eq!( + process_function_args( + temp_dir.path().join("testing"), + "default", + ["true".to_string(), "".to_string()].to_vec(), + FunctionType::Constructor + )?, + ["true".to_string(), "None".to_string()] + ); + Ok(()) + } +} diff --git a/crates/pop-contracts/src/utils/mod.rs b/crates/pop-contracts/src/utils/mod.rs index 357c66082..ad49e2dc6 100644 --- a/crates/pop-contracts/src/utils/mod.rs +++ b/crates/pop-contracts/src/utils/mod.rs @@ -1,4 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 pub mod helpers; +pub mod metadata; pub mod signer; diff --git a/crates/pop-contracts/tests/files/testing.contract b/crates/pop-contracts/tests/files/testing.contract index 5fb54f3ce..fbd8e6fba 100644 --- a/crates/pop-contracts/tests/files/testing.contract +++ b/crates/pop-contracts/tests/files/testing.contract @@ -1 +1 @@ -{"source":{"hash":"0x80776e58b218850d7d86447b2edea78d827ed0ed2499ff3a92b7ea10e4f95eb5","language":"ink! 5.0.0","compiler":"rustc 1.78.0","wasm":"0x0061736d01000000012b0860027f7f0060037f7f7f017f60000060047f7f7f7f017f60037f7f7f0060017f006000017f60017f017f027406057365616c310b6765745f73746f726167650003057365616c3005696e7075740000057365616c320b7365745f73746f726167650003057365616c300b7365616c5f72657475726e0004057365616c301176616c75655f7472616e73666572726564000003656e76066d656d6f72790201021003100f0101010105040006070002050002020616037f01418080040b7f00418080050b7f00418080050b0711020463616c6c0012066465706c6f7900130aa80c0f2b01017f037f2002200346047f200005200020036a200120036a2d00003a0000200341016a21030c010b0b0b6f01017f0240200020014d04402000210303402002450d02200320012d00003a0000200341016a2103200141016a2101200241016b21020c000b000b200041016b2103200141016b210103402002450d01200220036a200120026a2d00003a0000200241016b21020c000b000b20000b2501017f037f2002200346047f200005200020036a20013a0000200341016a21030c010b0b0b3f01027f0340200245044041000f0b200241016b210220012d0000210320002d00002104200141016a2101200041016a210020032004460d000b200420036b0b2601017f230041106b220124002001410036020c20002001410c6a4104100a200141106a24000b4801027f024002402000280208220320026a22042003490d00200420002802044b0d00200420036b2002470d01200028020020036a2001200210051a200020043602080f0b000b000b2601017f230041106b22022400200220003a000f20012002410f6a4101100a200241106a24000b6102027f027e230041206b22002400200041106a22014200370300200042003703082000411036021c200041086a2000411c6a1004200028021c41114f0440000b2001290300210220002903082103200041206a2400410541042002200384501b0b3f01017f2000280204220145044041020f0b2000200141016b36020420002000280200220041016a3602004101410220002d000022004101461b410020001b0b3c01027f027f200145044041808004210141010c010b410121024180800441013a000041818004210141020b2103200120023a0000200020031011000b12004180800441003b0100410041021011000b8a0101057f230041106b22012400200142808001370208200141808004360204200141046a22041009024020012802082205200128020c2202490d00200128020421032001410036020c2001200520026b3602082001200220036a36020420002004100b200128020c220020012802084b0d00200320022001280204200010021a200141106a24000f0b000b0d0020004180800420011003000b990401067f230041106b2200240020004180800136020441808004200041046a10010240024020002802042202418180014f0d000240024020024104490d002000418480043602042000200241046b360208418380042d00002101418280042d00002104418180042d00002103418080042d00002202412f470440200241ec00470440200241e300470d02410221022003413a46200441a5014671200141d10046710d030c020b2003410f472004411d4772200141f70147720d01200041046a100d220241ff01714102460d010c020b41032102200341860146200441db004671200141d90146710d010b41014101100e000b200042808001370208200041808004360204200041046a2204100920002802082205200028020c2201490d00200028020421032000200520016b220536020420032001200120036a2201200410002000280204220320054b720d0020002003360208200020013602042004100d220141ff01714102460d0020002802080d000240024002404102200241026b41ff01712200200041024f1b41016b0e020100020b2002410171101041004100100e000b100c41ff01714105470d01230041106b220024002000418080043602044180800441003a00002000428080818010370208200141ff0171410047200041046a100b200028020c2200418180014f0440000b410020001011000b100c41ff01714105460d010b000b200141ff017145101041004100100e000be50101057f230041106b2200240002400240100c41ff01714105470d0020004180800136020c418080042000410c6a1001200028020c2201418180014f0d0020014104490d012000418480043602042000200141046b360208418380042d00002101418280042d00002102418180042d000021030240418080042d0000220441ed014704402004419b0147200341ae0147722002419d0147200141de004772720d03200041046a100d220041ff01714102470d010c030b200341cb00462002419d0146712001411b4671450d0241001010100f000b20001010100f000b000b41014101100e000b","build_info":{"rust_toolchain":"stable-aarch64-apple-darwin","cargo_contract_version":"5.0.0-alpha","build_mode":"Release","wasm_opt_settings":{"optimization_passes":"Z","keep_debug_symbols":false}}},"contract":{"name":"testing","version":"0.1.0","authors":["[your_name] <[your_email]>"]},"image":null,"version":5,"types":[{"id":0,"type":{"def":{"primitive":"bool"}}},{"id":1,"type":{"path":["testing","testing","Testing"],"def":{"composite":{"fields":[{"name":"value","type":0,"typeName":",>>::Type"}]}}}},{"id":2,"type":{"path":["Result"],"params":[{"name":"T","type":3},{"name":"E","type":4}],"def":{"variant":{"variants":[{"name":"Ok","fields":[{"type":3}],"index":0},{"name":"Err","fields":[{"type":4}],"index":1}]}}}},{"id":3,"type":{"def":{"tuple":[]}}},{"id":4,"type":{"path":["ink_primitives","LangError"],"def":{"variant":{"variants":[{"name":"CouldNotReadInput","index":1}]}}}},{"id":5,"type":{"path":["Result"],"params":[{"name":"T","type":0},{"name":"E","type":4}],"def":{"variant":{"variants":[{"name":"Ok","fields":[{"type":0}],"index":0},{"name":"Err","fields":[{"type":4}],"index":1}]}}}},{"id":6,"type":{"path":["ink_primitives","types","AccountId"],"def":{"composite":{"fields":[{"type":7,"typeName":"[u8; 32]"}]}}}},{"id":7,"type":{"def":{"array":{"len":32,"type":8}}}},{"id":8,"type":{"def":{"primitive":"u8"}}},{"id":9,"type":{"def":{"primitive":"u128"}}},{"id":10,"type":{"path":["ink_primitives","types","Hash"],"def":{"composite":{"fields":[{"type":7,"typeName":"[u8; 32]"}]}}}},{"id":11,"type":{"def":{"primitive":"u64"}}},{"id":12,"type":{"def":{"primitive":"u32"}}},{"id":13,"type":{"path":["ink_env","types","NoChainExtension"],"def":{"variant":{}}}}],"storage":{"root":{"root_key":"0x00000000","layout":{"struct":{"name":"Testing","fields":[{"name":"value","layout":{"leaf":{"key":"0x00000000","ty":0}}}]}},"ty":1}},"spec":{"constructors":[{"label":"new","selector":"0x9bae9d5e","payable":false,"args":[{"label":"init_value","type":{"type":0,"displayName":["bool"]}}],"returnType":{"type":2,"displayName":["ink_primitives","ConstructorResult"]},"docs":["Constructor that initializes the `bool` value to the given `init_value`."],"default":false},{"label":"default","selector":"0xed4b9d1b","payable":false,"args":[],"returnType":{"type":2,"displayName":["ink_primitives","ConstructorResult"]},"docs":["Constructor that initializes the `bool` value to `false`.","","Constructors can delegate to other constructors."],"default":false}],"messages":[{"label":"flip","selector":"0x633aa551","mutates":true,"payable":false,"args":[],"returnType":{"type":2,"displayName":["ink","MessageResult"]},"docs":[" A message that can be called on instantiated contracts."," This one flips the value of the stored `bool` from `true`"," to `false` and vice versa."],"default":false},{"label":"get","selector":"0x2f865bd9","mutates":false,"payable":false,"args":[],"returnType":{"type":5,"displayName":["ink","MessageResult"]},"docs":[" Simply returns the current value of our `bool`."],"default":false},{"label":"specific_flip","selector":"0x6c0f1df7","mutates":true,"payable":true,"args":[{"label":"new_value","type":{"type":0,"displayName":["bool"]}}],"returnType":{"type":2,"displayName":["ink","MessageResult"]},"docs":[" A message for testing, flips the value of the stored `bool` with `new_value`"," and is payable"],"default":false}],"events":[],"docs":[],"lang_error":{"type":4,"displayName":["ink","LangError"]},"environment":{"accountId":{"type":6,"displayName":["AccountId"]},"balance":{"type":9,"displayName":["Balance"]},"hash":{"type":10,"displayName":["Hash"]},"timestamp":{"type":11,"displayName":["Timestamp"]},"blockNumber":{"type":12,"displayName":["BlockNumber"]},"chainExtension":{"type":13,"displayName":["ChainExtension"]},"maxEventTopics":4,"staticBufferSize":16384}}} \ No newline at end of file +{"source":{"hash":"0xf4f0cafd08d8e362141b3c64e3c651ad6a38225dbf0b66f691c15bb0ea00eac3","language":"ink! 5.0.0","compiler":"rustc 1.78.0","wasm":"0x0061736d0100000001270760027f7f0060037f7f7f017f60000060047f7f7f7f017f60037f7f7f0060017f017f6000017f027406057365616c310b6765745f73746f726167650003057365616c3005696e7075740000057365616c320b7365745f73746f726167650003057365616c300b7365616c5f72657475726e0004057365616c301176616c75655f7472616e73666572726564000003656e76066d656d6f7279020102100313120101010100050000040006000200000002020616037f01418080040b7f00418080050b7f00418080050b0711020463616c6c0015066465706c6f7900160ad910122b01017f037f2002200346047f200005200020036a200120036a2d00003a0000200341016a21030c010b0b0b6f01017f0240200020014d04402000210303402002450d02200320012d00003a0000200341016a2103200141016a2101200241016b21020c000b000b200041016b2103200141016b210103402002450d01200220036a200120026a2d00003a0000200241016b21020c000b000b20000b2501017f037f2002200346047f200005200020036a20013a0000200341016a21030c010b0b0b3f01027f0340200245044041000f0b200241016b210220012d0000210320002d00002104200141016a2101200041016a210020032004460d000b200420036b0bc00101057f230041106b22022400410221044104210502402001100a220641ff01714102460d00200241086a2001100b20022d00080d000240024020022d000922030e020100020b20012802042203410449047f4101052001200341046b36020420012001280200220141046a3602002001280000210341000b2101200220033602042002200136020020022802000d0141012103200228020421040b20002003360204200020063a0000410821050b200020056a2004360200200241106a24000b3f01027f230041106b22012400200141086a2000100b20012d0009210020012d00082102200141106a240041024101410220004101461b410020001b20021b0b3c01017f200020012802042202047f2001200241016b36020420012001280200220141016a36020020012d00000520010b3a000120002002453a00000b2601017f230041106b220224002002200036020c20012002410c6a4104100d200241106a24000b4801027f024002402000280208220320026a22042003490d00200420002802044b0d00200420036b2002470d01200028020020036a2001200210051a200020043602080f0b000b000b2601017f230041106b22022400200220003a000f20012002410f6a4101100d200241106a24000b6102027f027e230041206b22002400200041106a22014200370300200042003703082000411036021c200041086a2000411c6a1004200028021c41114f0440000b2001290300210220002903082103200041206a2400410541042002200384501b0b3c01027f027f200145044041808004210141010c010b410121024180800441013a000041818004210141020b2103200120023a0000200020031013000b12004180800441003b0100410041021013000b920101057f230041106b220224002002428080013702082002418080043602044100200241046a2204100c024020022802082206200228020c2203490d00200228020421052002410036020c2002200620036b3602082002200320056a36020420012004100e20002004100c200228020c220020022802084b0d00200520032002280204200010021a200241106a24000f0b000b0d0020004180800420011003000b08002000200110120bb60501087f230041206b2200240020004180800136021441808004200041146a100102400240024020002802142201418180014f0d0041042104027f410420014104490d001a20004184800436020c2000200141046b360210418380042d00002101418280042d00002102418180042d00002103024002400240418080042d00002205412f470440200541ec00460d014104200541e300470d041a41042003413a470d041a4104200241a501470d041a41042202200141d100470d041a410221010c020b41042003418601470d031a4104200241db00470d031a41042202200141d901470d031a410321010c010b41042003410f470d021a41042002411d470d021a4104200141f701470d021a200041146a2000410c6a1009200028021822014102460d01200028021c210420002d001421020b20002001360204200020023a000041080c010b41040b20006a2004360200200028020422024104460d012000280208210520002d000021072000428080013702182000418080043602144100200041146a2203100c20002802182206200028021c2201490d00200028021421042000200620016b220636021420042001200120046a2201200310002000280214220420064b720d0020002004360218200020013602142003100a220141ff01714102460d002000280218220341034d2003410447720d00200028021428000021000240024002404102200241026b2203200341024f1b41016b0e020100020b2005200020021b20074100471014410041001010000b100f41ff01714105470d01230041106b220024002000418080043602044180800441003a00002000428080818010370208200141ff0171410047200041046a100e200028020c2200418180014f0440000b410020001013000b100f41ff01714105460d020b000b410141011010000b2000200141ff0171451014410041001010000be90201067f230041206b22002400024002400240100f41ff01714105470d0020004180800136021441808004200041146a100120002802142201418180014f0d004104210220014104490d0120004184800436020c2000200141046b360210418380042d00002101418280042d00002103418180042d00002104027f418080042d0000220541ed014704402005419b0147200441ae0147722003419d0147200141de004772720d0341022103410322012000410c6a100a220441ff01714102470d011a0c040b200441cb00472003419d0147722001411b47720d02200041146a2000410c6a1009200028021822034102460d0220002d00142104200028021c0b210120002003360204200020043a0000410821020c020b000b410321010b200020026a2001360200024020002802042201410347044020002d0000210220014102460d012000280208410020011b200241004710121011000b410141011010000b4100200210121011000b","build_info":{"rust_toolchain":"stable-aarch64-apple-darwin","cargo_contract_version":"5.0.0-alpha","build_mode":"Release","wasm_opt_settings":{"optimization_passes":"Z","keep_debug_symbols":false}}},"contract":{"name":"testing","version":"0.1.0","authors":["[your_name] <[your_email]>"]},"image":null,"version":5,"types":[{"id":0,"type":{"def":{"primitive":"bool"}}},{"id":1,"type":{"def":{"primitive":"u32"}}},{"id":2,"type":{"path":["testing","testing","Testing"],"def":{"composite":{"fields":[{"name":"value","type":0,"typeName":",>>::Type"},{"name":"number","type":1,"typeName":",>>::Type"}]}}}},{"id":3,"type":{"path":["Result"],"params":[{"name":"T","type":4},{"name":"E","type":5}],"def":{"variant":{"variants":[{"name":"Ok","fields":[{"type":4}],"index":0},{"name":"Err","fields":[{"type":5}],"index":1}]}}}},{"id":4,"type":{"def":{"tuple":[]}}},{"id":5,"type":{"path":["ink_primitives","LangError"],"def":{"variant":{"variants":[{"name":"CouldNotReadInput","index":1}]}}}},{"id":6,"type":{"path":["Option"],"params":[{"name":"T","type":1}],"def":{"variant":{"variants":[{"name":"None","index":0},{"name":"Some","fields":[{"type":1}],"index":1}]}}}},{"id":7,"type":{"path":["Result"],"params":[{"name":"T","type":0},{"name":"E","type":5}],"def":{"variant":{"variants":[{"name":"Ok","fields":[{"type":0}],"index":0},{"name":"Err","fields":[{"type":5}],"index":1}]}}}},{"id":8,"type":{"path":["ink_primitives","types","AccountId"],"def":{"composite":{"fields":[{"type":9,"typeName":"[u8; 32]"}]}}}},{"id":9,"type":{"def":{"array":{"len":32,"type":10}}}},{"id":10,"type":{"def":{"primitive":"u8"}}},{"id":11,"type":{"def":{"primitive":"u128"}}},{"id":12,"type":{"path":["ink_primitives","types","Hash"],"def":{"composite":{"fields":[{"type":9,"typeName":"[u8; 32]"}]}}}},{"id":13,"type":{"def":{"primitive":"u64"}}},{"id":14,"type":{"path":["ink_env","types","NoChainExtension"],"def":{"variant":{}}}}],"storage":{"root":{"root_key":"0x00000000","layout":{"struct":{"name":"Testing","fields":[{"name":"value","layout":{"leaf":{"key":"0x00000000","ty":0}}},{"name":"number","layout":{"leaf":{"key":"0x00000000","ty":1}}}]}},"ty":2}},"spec":{"constructors":[{"label":"new","selector":"0x9bae9d5e","payable":false,"args":[{"label":"init_value","type":{"type":0,"displayName":["bool"]}}],"returnType":{"type":3,"displayName":["ink_primitives","ConstructorResult"]},"docs":["Constructor that initializes the `bool` value to the given `init_value`."],"default":false},{"label":"default","selector":"0xed4b9d1b","payable":false,"args":[{"label":"init_value","type":{"type":0,"displayName":["bool"]}},{"label":"number","type":{"type":6,"displayName":["Option"]}}],"returnType":{"type":3,"displayName":["ink_primitives","ConstructorResult"]},"docs":["Constructor that initializes the `bool` value to `false`.","","Constructors can delegate to other constructors."],"default":false}],"messages":[{"label":"flip","selector":"0x633aa551","mutates":true,"payable":false,"args":[],"returnType":{"type":3,"displayName":["ink","MessageResult"]},"docs":[" A message that can be called on instantiated contracts."," This one flips the value of the stored `bool` from `true`"," to `false` and vice versa."],"default":false},{"label":"get","selector":"0x2f865bd9","mutates":false,"payable":false,"args":[],"returnType":{"type":7,"displayName":["ink","MessageResult"]},"docs":[" Simply returns the current value of our `bool`."],"default":false},{"label":"specific_flip","selector":"0x6c0f1df7","mutates":true,"payable":true,"args":[{"label":"new_value","type":{"type":0,"displayName":["bool"]}},{"label":"number","type":{"type":6,"displayName":["Option"]}}],"returnType":{"type":3,"displayName":["ink","MessageResult"]},"docs":[" A message for testing, flips the value of the stored `bool` with `new_value`"," and is payable"],"default":false}],"events":[],"docs":[],"lang_error":{"type":5,"displayName":["ink","LangError"]},"environment":{"accountId":{"type":8,"displayName":["AccountId"]},"balance":{"type":11,"displayName":["Balance"]},"hash":{"type":12,"displayName":["Hash"]},"timestamp":{"type":13,"displayName":["Timestamp"]},"blockNumber":{"type":1,"displayName":["BlockNumber"]},"chainExtension":{"type":14,"displayName":["ChainExtension"]},"maxEventTopics":4,"staticBufferSize":16384}}} \ No newline at end of file diff --git a/crates/pop-contracts/tests/files/testing.json b/crates/pop-contracts/tests/files/testing.json index ed230c98f..61921986b 100644 --- a/crates/pop-contracts/tests/files/testing.json +++ b/crates/pop-contracts/tests/files/testing.json @@ -1,6 +1,6 @@ { "source": { - "hash": "0x80776e58b218850d7d86447b2edea78d827ed0ed2499ff3a92b7ea10e4f95eb5", + "hash": "0xf4f0cafd08d8e362141b3c64e3c651ad6a38225dbf0b66f691c15bb0ea00eac3", "language": "ink! 5.0.0", "compiler": "rustc 1.78.0", "build_info": { @@ -33,6 +33,14 @@ }, { "id": 1, + "type": { + "def": { + "primitive": "u32" + } + } + }, + { + "id": 2, "type": { "path": [ "testing", @@ -46,6 +54,11 @@ "name": "value", "type": 0, "typeName": ",>>::Type" + }, + { + "name": "number", + "type": 1, + "typeName": ",>>::Type" } ] } @@ -53,7 +66,7 @@ } }, { - "id": 2, + "id": 3, "type": { "path": [ "Result" @@ -61,11 +74,11 @@ "params": [ { "name": "T", - "type": 3 + "type": 4 }, { "name": "E", - "type": 4 + "type": 5 } ], "def": { @@ -75,7 +88,7 @@ "name": "Ok", "fields": [ { - "type": 3 + "type": 4 } ], "index": 0 @@ -84,7 +97,7 @@ "name": "Err", "fields": [ { - "type": 4 + "type": 5 } ], "index": 1 @@ -95,7 +108,7 @@ } }, { - "id": 3, + "id": 4, "type": { "def": { "tuple": [] @@ -103,7 +116,7 @@ } }, { - "id": 4, + "id": 5, "type": { "path": [ "ink_primitives", @@ -122,7 +135,40 @@ } }, { - "id": 5, + "id": 6, + "type": { + "path": [ + "Option" + ], + "params": [ + { + "name": "T", + "type": 1 + } + ], + "def": { + "variant": { + "variants": [ + { + "name": "None", + "index": 0 + }, + { + "name": "Some", + "fields": [ + { + "type": 1 + } + ], + "index": 1 + } + ] + } + } + } + }, + { + "id": 7, "type": { "path": [ "Result" @@ -134,7 +180,7 @@ }, { "name": "E", - "type": 4 + "type": 5 } ], "def": { @@ -153,7 +199,7 @@ "name": "Err", "fields": [ { - "type": 4 + "type": 5 } ], "index": 1 @@ -164,7 +210,7 @@ } }, { - "id": 6, + "id": 8, "type": { "path": [ "ink_primitives", @@ -175,7 +221,7 @@ "composite": { "fields": [ { - "type": 7, + "type": 9, "typeName": "[u8; 32]" } ] @@ -184,18 +230,18 @@ } }, { - "id": 7, + "id": 9, "type": { "def": { "array": { "len": 32, - "type": 8 + "type": 10 } } } }, { - "id": 8, + "id": 10, "type": { "def": { "primitive": "u8" @@ -203,7 +249,7 @@ } }, { - "id": 9, + "id": 11, "type": { "def": { "primitive": "u128" @@ -211,7 +257,7 @@ } }, { - "id": 10, + "id": 12, "type": { "path": [ "ink_primitives", @@ -222,7 +268,7 @@ "composite": { "fields": [ { - "type": 7, + "type": 9, "typeName": "[u8; 32]" } ] @@ -231,7 +277,7 @@ } }, { - "id": 11, + "id": 13, "type": { "def": { "primitive": "u64" @@ -239,15 +285,7 @@ } }, { - "id": 12, - "type": { - "def": { - "primitive": "u32" - } - } - }, - { - "id": 13, + "id": 14, "type": { "path": [ "ink_env", @@ -275,11 +313,20 @@ "ty": 0 } } + }, + { + "name": "number", + "layout": { + "leaf": { + "key": "0x00000000", + "ty": 1 + } + } } ] } }, - "ty": 1 + "ty": 2 } }, "spec": { @@ -300,7 +347,7 @@ } ], "returnType": { - "type": 2, + "type": 3, "displayName": [ "ink_primitives", "ConstructorResult" @@ -315,9 +362,28 @@ "label": "default", "selector": "0xed4b9d1b", "payable": false, - "args": [], + "args": [ + { + "label": "init_value", + "type": { + "type": 0, + "displayName": [ + "bool" + ] + } + }, + { + "label": "number", + "type": { + "type": 6, + "displayName": [ + "Option" + ] + } + } + ], "returnType": { - "type": 2, + "type": 3, "displayName": [ "ink_primitives", "ConstructorResult" @@ -339,7 +405,7 @@ "payable": false, "args": [], "returnType": { - "type": 2, + "type": 3, "displayName": [ "ink", "MessageResult" @@ -359,7 +425,7 @@ "payable": false, "args": [], "returnType": { - "type": 5, + "type": 7, "displayName": [ "ink", "MessageResult" @@ -384,10 +450,19 @@ "bool" ] } + }, + { + "label": "number", + "type": { + "type": 6, + "displayName": [ + "Option" + ] + } } ], "returnType": { - "type": 2, + "type": 3, "displayName": [ "ink", "MessageResult" @@ -403,7 +478,7 @@ "events": [], "docs": [], "lang_error": { - "type": 4, + "type": 5, "displayName": [ "ink", "LangError" @@ -411,37 +486,37 @@ }, "environment": { "accountId": { - "type": 6, + "type": 8, "displayName": [ "AccountId" ] }, "balance": { - "type": 9, + "type": 11, "displayName": [ "Balance" ] }, "hash": { - "type": 10, + "type": 12, "displayName": [ "Hash" ] }, "timestamp": { - "type": 11, + "type": 13, "displayName": [ "Timestamp" ] }, "blockNumber": { - "type": 12, + "type": 1, "displayName": [ "BlockNumber" ] }, "chainExtension": { - "type": 13, + "type": 14, "displayName": [ "ChainExtension" ] From a6f8247316dd03bfd58936691990878f349db9ec Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 22 Nov 2024 22:35:53 +0100 Subject: [PATCH 066/211] test: fix unit test --- Cargo.lock | 11 ++++++----- crates/pop-cli/src/commands/up/contract.rs | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3ea832635..d273e0582 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4667,7 +4667,7 @@ dependencies = [ [[package]] name = "pop-cli" -version = "0.4.0" +version = "0.5.0" dependencies = [ "anyhow", "assert_cmd", @@ -4697,7 +4697,7 @@ dependencies = [ [[package]] name = "pop-common" -version = "0.4.0" +version = "0.5.0" dependencies = [ "anyhow", "cargo_toml", @@ -4722,7 +4722,7 @@ dependencies = [ [[package]] name = "pop-contracts" -version = "0.4.0" +version = "0.5.0" dependencies = [ "anyhow", "contract-build", @@ -4736,6 +4736,7 @@ dependencies = [ "mockito", "pop-common", "reqwest 0.12.7", + "scale-info", "sp-core", "sp-weights", "strum 0.26.3", @@ -4752,7 +4753,7 @@ dependencies = [ [[package]] name = "pop-parachains" -version = "0.4.0" +version = "0.5.0" dependencies = [ "anyhow", "askama", @@ -4781,7 +4782,7 @@ dependencies = [ [[package]] name = "pop-telemetry" -version = "0.4.0" +version = "0.5.0" dependencies = [ "dirs", "env_logger", diff --git a/crates/pop-cli/src/commands/up/contract.rs b/crates/pop-cli/src/commands/up/contract.rs index 9acbc2b63..435dab1d0 100644 --- a/crates/pop-cli/src/commands/up/contract.rs +++ b/crates/pop-cli/src/commands/up/contract.rs @@ -356,7 +356,7 @@ mod tests { UpOpts { path: None, constructor: "new".to_string(), - args: vec!["false".to_string()].to_vec(), + args: vec![].to_vec(), value: "0".to_string(), gas_limit: None, proof_size: None, From 4fe920d320d359b5361ffca2d44f1d621d867e6d Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Sun, 24 Nov 2024 17:23:13 +0100 Subject: [PATCH 067/211] refactor: clean the way to parse and prompt parameters --- Cargo.lock | 167 +++++++------ Cargo.toml | 1 + crates/pop-cli/src/commands/call/parachain.rs | 37 ++- crates/pop-common/src/lib.rs | 10 +- crates/pop-contracts/src/utils/metadata.rs | 2 +- crates/pop-parachains/Cargo.toml | 1 + .../pop-parachains/src/call/metadata/mod.rs | 122 ++------- .../src/call/metadata/params.rs | 68 ++++- .../src/call/metadata/type_parser.rs | 235 ------------------ crates/pop-parachains/src/call/mod.rs | 4 +- crates/pop-parachains/src/lib.rs | 4 +- 11 files changed, 210 insertions(+), 441 deletions(-) delete mode 100644 crates/pop-parachains/src/call/metadata/type_parser.rs diff --git a/Cargo.lock b/Cargo.lock index 2d296641a..220d5b221 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -355,7 +355,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -533,7 +533,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -550,7 +550,7 @@ checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -848,7 +848,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", "syn_derive", ] @@ -1075,7 +1075,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1505,7 +1505,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1532,7 +1532,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1549,7 +1549,7 @@ checksum = "98532a60dedaebc4848cb2cba5023337cc9ea3af16a5b062633fabfd9f18fb60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1597,7 +1597,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1619,7 +1619,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core 0.20.10", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1687,7 +1687,7 @@ checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1698,7 +1698,7 @@ checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1709,7 +1709,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1722,7 +1722,27 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.77", + "syn 2.0.89", +] + +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", ] [[package]] @@ -1793,7 +1813,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1823,7 +1843,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.77", + "syn 2.0.89", "termcolor", "toml 0.8.19", "walkdir", @@ -2078,7 +2098,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -2310,7 +2330,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -3005,7 +3025,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f357e2e867f4e222ffc4015a6e61d1073548de89f70a4e36a8b0385562777fa" dependencies = [ "blake2", - "derive_more", + "derive_more 0.99.18", "ink_primitives", "pallet-contracts-uapi-next", "parity-scale-codec", @@ -3023,7 +3043,7 @@ dependencies = [ "blake2", "cfg-if", "const_env", - "derive_more", + "derive_more 0.99.18", "ink_allocator", "ink_engine", "ink_prelude", @@ -3050,7 +3070,7 @@ version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a98fcc0ff9292ff68c7ee7b84c93533c9ff13859ec3b148faa822e2da9954fe6" dependencies = [ - "derive_more", + "derive_more 0.99.18", "impl-serde", "ink_prelude", "ink_primitives", @@ -3076,7 +3096,7 @@ version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11ec35ef7f45e67a53b6142d7e7f18e6d9292d76c3a2a1da14cf8423e481813d" dependencies = [ - "derive_more", + "derive_more 0.99.18", "ink_prelude", "parity-scale-codec", "scale-decode 0.10.0", @@ -3804,7 +3824,7 @@ checksum = "cb26336e6dc7cc76e7927d2c9e7e3bb376d7af65a6f56a0b16c47d18a9b1abc5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4242,7 +4262,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4464,7 +4484,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4495,7 +4515,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4562,7 +4582,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6380dbe1fb03ecc74ad55d841cfc75480222d153ba69ddcb00977866cbdabdb8" dependencies = [ "polkavm-derive-impl 0.5.0", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4592,7 +4612,7 @@ dependencies = [ "polkavm-common 0.5.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4604,7 +4624,7 @@ dependencies = [ "polkavm-common 0.8.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4616,7 +4636,7 @@ dependencies = [ "polkavm-common 0.9.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4626,7 +4646,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15e85319a0d5129dc9f021c62607e0804f5fb777a05cdda44d750ac0732def66" dependencies = [ "polkavm-derive-impl 0.8.0", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4636,7 +4656,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" dependencies = [ "polkavm-derive-impl 0.9.0", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4707,7 +4727,7 @@ dependencies = [ "git2_credentials", "mockito", "regex", - "reqwest 0.12.5", + "reqwest 0.12.7", "scale-info", "serde", "serde_json", @@ -4763,11 +4783,12 @@ dependencies = [ "flate2", "glob", "hex", - "indexmap 2.4.0", + "indexmap 2.5.0", "mockito", "pop-common", - "reqwest 0.12.5", + "reqwest 0.12.7", "scale-info", + "scale-value", "serde_json", "strum 0.26.3", "strum_macros 0.26.4", @@ -4858,7 +4879,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" dependencies = [ "proc-macro2", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4919,9 +4940,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -5068,7 +5089,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -5489,7 +5510,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58c4eb8a81997cf040a091d1f7e1938aeab6749d3a0dfa73af43cdc32393483d" dependencies = [ "byteorder", - "derive_more", + "derive_more 0.99.18", "twox-hash", ] @@ -5547,7 +5568,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7caaf753f8ed1ab4752c6afb20174f03598c664724e0e32628e161c21000ff76" dependencies = [ - "derive_more", + "derive_more 0.99.18", "parity-scale-codec", "scale-bits 0.4.0", "scale-decode-derive 0.10.0", @@ -5561,7 +5582,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e98f3262c250d90e700bb802eb704e1f841e03331c2eb815e46516c4edbf5b27" dependencies = [ - "derive_more", + "derive_more 0.99.18", "parity-scale-codec", "primitive-types", "scale-bits 0.6.0", @@ -5601,7 +5622,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d70cb4b29360105483fac1ed567ff95d65224a14dd275b6303ed0a654c78de5" dependencies = [ - "derive_more", + "derive_more 0.99.18", "parity-scale-codec", "scale-encode-derive 0.5.0", "scale-info", @@ -5614,7 +5635,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ba0b9c48dc0eb20c60b083c29447c0c4617cb7c4a4c9fef72aa5c5bc539e15e" dependencies = [ - "derive_more", + "derive_more 0.99.18", "parity-scale-codec", "primitive-types", "scale-bits 0.6.0", @@ -5651,13 +5672,13 @@ dependencies = [ [[package]] name = "scale-info" -version = "2.11.3" +version = "2.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca070c12893629e2cc820a9761bedf6ce1dcddc9852984d1dc734b8bd9bd024" +checksum = "346a3b32eba2640d17a9cb5927056b08f3de90f65b72fe09402c2ad07d684d0b" dependencies = [ "bitvec", "cfg-if", - "derive_more", + "derive_more 1.0.0", "parity-scale-codec", "scale-info-derive", "schemars", @@ -5666,14 +5687,14 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.11.3" +version = "2.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d35494501194174bda522a32605929eefc9ecf7e0a326c26db1fdd85881eb62" +checksum = "c6630024bf739e2179b91fb424b28898baf819414262c5d376677dbff1fe7ebf" dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.89", ] [[package]] @@ -5695,7 +5716,7 @@ dependencies = [ "proc-macro2", "quote", "scale-info", - "syn 2.0.77", + "syn 2.0.89", "thiserror", ] @@ -5707,7 +5728,7 @@ checksum = "ba4d772cfb7569e03868400344a1695d16560bf62b86b918604773607d39ec84" dependencies = [ "base58", "blake2", - "derive_more", + "derive_more 0.99.18", "either", "frame-metadata 15.1.0", "parity-scale-codec", @@ -5750,7 +5771,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -5923,7 +5944,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -5934,7 +5955,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -5958,7 +5979,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -6228,7 +6249,7 @@ dependencies = [ "bs58", "chacha20", "crossbeam-queue", - "derive_more", + "derive_more 0.99.18", "ed25519-zebra 4.0.3", "either", "event-listener 4.0.3", @@ -6278,7 +6299,7 @@ dependencies = [ "async-lock", "base64 0.21.7", "blake2-rfc", - "derive_more", + "derive_more 0.99.18", "either", "event-listener 4.0.3", "fnv", @@ -6442,7 +6463,7 @@ checksum = "48d09fa0a5f7299fb81ee25ae3853d26200f7a348148aed6de76be905c007dbe" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -6563,7 +6584,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -6769,7 +6790,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -6843,7 +6864,7 @@ dependencies = [ "scale-info", "scale-typegen", "subxt-metadata", - "syn 2.0.77", + "syn 2.0.89", "thiserror", "tokio", ] @@ -6906,7 +6927,7 @@ dependencies = [ "quote", "scale-typegen", "subxt-codegen", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -6963,9 +6984,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ "proc-macro2", "quote", @@ -6981,7 +7002,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -7130,7 +7151,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -7225,7 +7246,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -7450,7 +7471,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -7812,7 +7833,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", "wasm-bindgen-shared", ] @@ -7846,7 +7867,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -8513,7 +8534,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -8533,7 +8554,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 2e1b68ae5..43b517c44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,7 @@ contract-build = "5.0.0-alpha" contract-extrinsics = "5.0.0-alpha" contract-transcode = "5.0.0-alpha" scale-info = { version = "2.11.3", default-features = false, features = ["derive"] } +scale-value = { version = "0.16.2", default-features = false, features = ["from-string", "parser-ss58"] } heck = "0.5.0" hex = { version = "0.4.3", default-features = false } diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 9b6f9e5b0..86f44bc9c 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -189,7 +189,7 @@ impl CallParachainCommand { return Err(anyhow!("Please specify the pallet.")); }, }; - let tx = match construct_extrinsic(api, &pallet, &extrinsic, self.args.clone()).await { + let tx = match construct_extrinsic(&pallet, &extrinsic, self.args.clone()).await { Ok(tx) => tx, Err(e) => { return Err(anyhow!("Error: {}", e)); @@ -305,7 +305,7 @@ fn prompt_for_param( )) .interact()? { - return Ok("None".to_string()); + return Ok("None()".to_string()); } let value = get_param_value(api, cli, param)?; Ok(format!("Some({})", value)) @@ -324,6 +324,8 @@ fn get_param_value( prompt_for_primitive_param(cli, param) } else if param.is_variant { prompt_for_variant_param(api, cli, param) + } else if param.is_tuple { + prompt_for_tuple_param(api, cli, param) } else { prompt_for_composite_param(api, cli, param) } @@ -360,7 +362,7 @@ fn prompt_for_variant_param( } Ok(format!("{}({})", selected_variant.name, field_values.join(", "))) } else { - Ok(selected_variant.name.clone()) + Ok(format!("{}()", selected_variant.name.clone())) } } @@ -373,7 +375,32 @@ fn prompt_for_composite_param( let mut field_values = Vec::new(); for field_arg in ¶m.sub_params { let field_value = prompt_for_param(api, cli, field_arg)?; - field_values.push(field_value); + // Example: Param { name: "Id", type_name: "AccountId32 ([u8;32])", is_optional: false, + // sub_params: [Param { name: "Id", type_name: "[u8;32]", is_optional: false, sub_params: + // [], is_variant: false }], is_variant: false } + if param.sub_params.len() == 1 && param.name == param.sub_params[0].name { + field_values.push(format!("{}", field_value)); + } else { + field_values.push(format!("{}: {}", field_arg.name, field_value)); + } + } + if param.sub_params.len() == 1 && param.name == param.sub_params[0].name { + Ok(format!("{}", field_values.join(", "))) + } else { + Ok(format!("{{{}}}", field_values.join(", "))) + } +} + +// Recursively prompt the user for the tuple values. +fn prompt_for_tuple_param( + api: &OnlineClient, + cli: &mut impl cli::traits::Cli, + param: &Param, +) -> Result { + let mut tuple_values = Vec::new(); + for (_index, tuple_param) in param.sub_params.iter().enumerate() { + let tuple_value = prompt_for_param(api, cli, tuple_param)?; + tuple_values.push(tuple_value); } - Ok(field_values.join(", ")) + Ok(format!("({})", tuple_values.join(", "))) } diff --git a/crates/pop-common/src/lib.rs b/crates/pop-common/src/lib.rs index ebe88ff11..be843ec85 100644 --- a/crates/pop-common/src/lib.rs +++ b/crates/pop-common/src/lib.rs @@ -43,18 +43,16 @@ pub fn target() -> Result<&'static str, Error> { } match ARCH { - "aarch64" => { + "aarch64" => return match OS { "macos" => Ok("aarch64-apple-darwin"), _ => Ok("aarch64-unknown-linux-gnu"), - } - }, - "x86_64" | "x86" => { + }, + "x86_64" | "x86" => return match OS { "macos" => Ok("x86_64-apple-darwin"), _ => Ok("x86_64-unknown-linux-gnu"), - } - }, + }, &_ => {}, } Err(Error::UnsupportedPlatform { arch: ARCH, os: OS }) diff --git a/crates/pop-contracts/src/utils/metadata.rs b/crates/pop-contracts/src/utils/metadata.rs index 21d7433b8..09c2d3220 100644 --- a/crates/pop-contracts/src/utils/metadata.rs +++ b/crates/pop-contracts/src/utils/metadata.rs @@ -4,7 +4,7 @@ use crate::errors::Error; use contract_extrinsics::ContractArtifacts; use contract_transcode::ink_metadata::MessageParamSpec; use pop_common::format_type; -use scale_info::{form::PortableForm, PortableRegistry, Type, TypeDef, TypeDefPrimitive}; +use scale_info::{form::PortableForm, PortableRegistry}; use std::path::Path; /// Describes a parameter. diff --git a/crates/pop-parachains/Cargo.toml b/crates/pop-parachains/Cargo.toml index 7ec7be467..b6af7d0d7 100644 --- a/crates/pop-parachains/Cargo.toml +++ b/crates/pop-parachains/Cargo.toml @@ -28,6 +28,7 @@ askama.workspace = true indexmap.workspace = true reqwest.workspace = true scale-info.workspace = true +scale-value.workspace = true subxt.workspace = true symlink.workspace = true toml_edit.workspace = true diff --git a/crates/pop-parachains/src/call/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs index 2ae213eae..e7f863d0a 100644 --- a/crates/pop-parachains/src/call/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -1,13 +1,12 @@ // SPDX-License-Identifier: GPL-3.0 use crate::errors::Error; -use scale_info::{form::PortableForm, Variant}; +use params::Param; +use scale_value::stringify::custom_parsers; use subxt::{dynamic::Value, Metadata, OnlineClient, SubstrateConfig}; -use type_parser::process_argument; pub mod action; -mod params; -mod type_parser; +pub mod params; #[derive(Clone, PartialEq, Eq)] /// Represents a pallet in the blockchain, including its extrinsics. @@ -33,21 +32,6 @@ pub struct Extrinsic { pub is_supported: bool, } -#[derive(Debug, Clone, PartialEq, Eq)] -/// Describes a parameter of an extrinsic. -pub struct Param { - /// The name of the parameter. - pub name: String, - /// The type of the parameter. - pub type_name: String, - /// Indicates if the parameter is optional (`Option`). - pub is_optional: bool, - /// Nested parameters for composite or variants types. - pub sub_params: Vec, - /// Indicates if the parameter is a Variant. - pub is_variant: bool, -} - /// Parses the chain metadata to extract information about pallets and their extrinsics with its /// parameters. /// @@ -146,96 +130,20 @@ pub async fn find_extrinsic_by_name( } } -/// Processes and maps parameters for a given pallet extrinsic based on its metadata. +/// Parses and processes raw string parameters for an extrinsic, mapping them to `Value` types. /// /// # Arguments -/// * `api`: Reference to an `OnlineClient` connected to the blockchain. -/// * `pallet_name`: Name of the pallet containing the extrinsic. -/// * `extrinsic_name`: Name of the extrinsic to process. /// * `raw_params`: A vector of raw string arguments for the extrinsic. -pub async fn process_extrinsic_args( - api: &OnlineClient, - pallet_name: &str, - extrinsic_name: &str, - raw_params: Vec, -) -> Result, Error> { - let metadata: Metadata = api.metadata(); - let registry = metadata.types(); - let extrinsic = parse_extrinsic_by_name(&api, pallet_name, extrinsic_name).await?; - let mut processed_parameters: Vec = Vec::new(); - for (index, field) in extrinsic.fields.iter().enumerate() { - let raw_parameter = raw_params.get(index).ok_or(Error::ParamProcessingError)?; - let type_info = registry.resolve(field.ty.id).ok_or(Error::ParamProcessingError)?; //Resolve with type_id - let arg_processed = process_argument(raw_parameter, type_info, registry)?; - processed_parameters.push(arg_processed); +pub async fn parse_extrinsic_arguments(raw_params: Vec) -> Result, Error> { + let mut parsed_params: Vec = Vec::new(); + for raw_param in raw_params { + let parsed_value: Value = scale_value::stringify::from_str_custom() + .add_custom_parser(custom_parsers::parse_hex) + .add_custom_parser(custom_parsers::parse_ss58) + .parse(&raw_param) + .0 + .map_err(|_| Error::ParamProcessingError)?; + parsed_params.push(parsed_value); } - Ok(processed_parameters) -} - -/// Finds a specific extrinsic by name and retrieves its details from metadata. -/// -/// # Arguments -/// * `api`: Reference to an `OnlineClient` connected to the chain. -/// * `pallet_name`: The name of the pallet to find. -/// * `extrinsic_name`: Name of the extrinsic to locate. -async fn parse_extrinsic_by_name( - api: &OnlineClient, - pallet_name: &str, - extrinsic_name: &str, -) -> Result, Error> { - let metadata: Metadata = api.metadata(); - let pallet = metadata - .pallets() - .into_iter() - .find(|p| p.name() == pallet_name) - .ok_or_else(|| Error::PalletNotFound(pallet_name.to_string()))?; - // Retrieve and check for the extrinsic within the pallet - let extrinsic = pallet - .call_variants() - .map(|variants| variants.iter().find(|e| e.name == extrinsic_name)) - .flatten() - .ok_or_else(|| Error::ExtrinsicNotSupported(extrinsic_name.to_string()))?; - - Ok(extrinsic.clone()) -} - -#[cfg(test)] -mod tests { - use crate::set_up_api; - - use super::*; - use anyhow::Result; - - // #[tokio::test] - // async fn process_prompt_arguments_works() -> Result<()> { - // let api = set_up_api("ws://127.0.0.1:9944").await?; - // // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; - // let ex = find_extrinsic_by_name(&api, "Nfts", "mint").await?; - // let prompt_args1 = process_prompt_arguments(&api, &ex.fields()[2])?; - - // Ok(()) - // } - - // #[tokio::test] - // async fn process_extrinsic_args_works() -> Result<()> { - // let api = set_up_api("ws://127.0.0.1:9944").await?; - // // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; - // let ex = parse_extrinsic_by_name(&api, "Utility", "batch").await?; - // println!("EXTRINSIC {:?}", ex); - // println!(" ARGS PARSER {:?}", ex.fields); - - // Ok(()) - // } - - // #[tokio::test] - // async fn process_extrinsic_args2_works() -> Result<()> { - // let api = set_up_api("ws://127.0.0.1:9944").await?; - // // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; - // let ex = find_extrinsic_by_name(&api, "Nfts", "mint").await?; - // let args_parsed = - // process_extrinsic_args(&api, "System", "remark", vec!["0x11".to_string()]).await?; - // println!(" ARGS PARSER {:?}", args_parsed); - - // Ok(()) - // } + Ok(parsed_params) } diff --git a/crates/pop-parachains/src/call/metadata/params.rs b/crates/pop-parachains/src/call/metadata/params.rs index b85efd597..9c98287ac 100644 --- a/crates/pop-parachains/src/call/metadata/params.rs +++ b/crates/pop-parachains/src/call/metadata/params.rs @@ -1,10 +1,27 @@ // SPDX-License-Identifier: GPL-3.0 -use crate::{errors::Error, Param}; +use crate::errors::Error; use pop_common::format_type; use scale_info::{form::PortableForm, Field, PortableRegistry, TypeDef}; use subxt::{Metadata, OnlineClient, SubstrateConfig}; +/// Describes a parameter of an extrinsic. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Param { + /// The name of the parameter. + pub name: String, + /// The type of the parameter. + pub type_name: String, + /// Nested parameters for composite, variants types or tuples. + pub sub_params: Vec, + /// Indicates if the parameter is optional (`Option`). + pub is_optional: bool, + /// Indicates if the parameter is a Tuple. + pub is_tuple: bool, + /// Indicates if the parameter is a Variant. + pub is_variant: bool, +} + /// Transforms a metadata field into its `Param` representation. /// /// # Arguments @@ -57,8 +74,9 @@ fn type_to_param( return Ok(Param { name, type_name: sub_param.type_name, - is_optional: true, sub_params: sub_param.sub_params, + is_optional: true, + is_tuple: false, is_variant: false, }); } else { @@ -68,11 +86,15 @@ fn type_to_param( // Determine the formatted type name. let type_name = format_type(type_info, registry); match &type_info.type_def { - TypeDef::Primitive(_) => Ok(Param { + TypeDef::Primitive(_) | + TypeDef::Array(_) | + TypeDef::Sequence(_) | + TypeDef::Compact(_) => Ok(Param { name, type_name, - is_optional: false, sub_params: Vec::new(), + is_optional: false, + is_tuple: false, is_variant: false, }), TypeDef::Composite(composite) => { @@ -91,7 +113,14 @@ fn type_to_param( }) .collect::, Error>>()?; - Ok(Param { name, type_name, is_optional: false, sub_params, is_variant: false }) + Ok(Param { + name, + type_name, + sub_params, + is_optional: false, + is_tuple: false, + is_variant: false, + }) }, TypeDef::Variant(variant) => { let variant_params = variant @@ -115,8 +144,9 @@ fn type_to_param( Ok(Param { name: variant_param.name.clone(), type_name: "".to_string(), - is_optional: false, sub_params: variant_sub_params, + is_optional: false, + is_tuple: false, is_variant: true, }) }) @@ -125,19 +155,37 @@ fn type_to_param( Ok(Param { name, type_name, - is_optional: false, sub_params: variant_params, + is_optional: false, + is_tuple: false, is_variant: true, }) }, - TypeDef::Array(_) | TypeDef::Sequence(_) | TypeDef::Tuple(_) | TypeDef::Compact(_) => + TypeDef::Tuple(tuple) => { + let sub_params = tuple + .fields + .iter() + .enumerate() + .map(|(index, field_id)| { + type_to_param( + extrinsic_name, + format!("Index {} of the tuple {}", index.to_string(), name), + registry, + field_id.id, + &None, + ) + }) + .collect::, Error>>()?; + Ok(Param { name, type_name, + sub_params, is_optional: false, - sub_params: Vec::new(), + is_tuple: true, is_variant: false, - }), + }) + }, _ => Err(Error::MetadataParsingError(name)), } } diff --git a/crates/pop-parachains/src/call/metadata/type_parser.rs b/crates/pop-parachains/src/call/metadata/type_parser.rs deleted file mode 100644 index 63b500db0..000000000 --- a/crates/pop-parachains/src/call/metadata/type_parser.rs +++ /dev/null @@ -1,235 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 - -use crate::errors::Error; -use pop_common::parse_account; -use scale_info::{ - form::PortableForm, PortableRegistry, Type, TypeDef, TypeDefArray, TypeDefCompact, - TypeDefComposite, TypeDefPrimitive, TypeDefSequence, TypeDefTuple, TypeDefVariant, -}; -use subxt::dynamic::Value; - -/// Parses an argument string into a `Value` based on its type definition. -/// -/// # Arguments -/// * `arg`: The string representation of the argument to parse. -/// * `ty`: A reference to the `Type` to be formatted. -/// * `registry`: A reference to the `PortableRegistry` to resolve type dependencies. -pub fn process_argument( - arg: &str, - ty: &Type, - registry: &PortableRegistry, -) -> Result { - let type_path = ty.path.segments.join("::"); - match type_path.as_str() { - "Option" => handle_option_type(arg, ty, registry), - // TODO: Handle other account types. - "sp_core::crypto::AccountId32" => Ok(Value::from_bytes(parse_account(arg)?)), - _ => match &ty.type_def { - TypeDef::Primitive(primitive) => primitive.parse(arg, registry), - TypeDef::Composite(composite) => composite.parse(arg, registry), - TypeDef::Variant(variant) => variant.parse(arg, registry), - TypeDef::Tuple(tuple) => tuple.parse(arg, registry), - TypeDef::Sequence(sequence) => sequence.parse(arg, registry), - TypeDef::Array(array) => array.parse(arg, registry), - TypeDef::Compact(compact) => compact.parse(arg, registry), - _ => Err(Error::ParamProcessingError), - }, - } -} - -fn handle_option_type( - arg: &str, - ty: &Type, - registry: &PortableRegistry, -) -> Result { - // Handle Option - if arg.trim() == "None" { - Ok(Value::unnamed_variant("None", vec![])) - } else if arg.trim().starts_with("Some(") && arg.trim().ends_with(')') { - let sub_arg = &arg.trim()[5..arg.trim().len() - 1]; - if let Some(sub_arg_type_id) = ty.type_params.get(0).and_then(|param| param.ty) { - let sub_arg_ty = - registry.resolve(sub_arg_type_id.id).ok_or(Error::ParamProcessingError)?; - Ok(Value::unnamed_variant( - "Some", - vec![process_argument(sub_arg.trim(), sub_arg_ty, registry)?], - )) - } else { - Err(Error::ParamProcessingError) - } - } else { - Err(Error::ParamProcessingError) - } -} - -/// Trait to define how different type definitions parse a string argument into a `Value`. -pub trait TypeParser { - fn parse(&self, arg: &str, registry: &PortableRegistry) -> Result; -} - -impl TypeParser for TypeDefPrimitive { - fn parse(&self, arg: &str, _registry: &PortableRegistry) -> Result { - match self { - TypeDefPrimitive::Bool => - Ok(Value::bool(arg.parse::().map_err(|_| Error::ParamProcessingError)?)), - TypeDefPrimitive::Char => - Ok(Value::char(arg.chars().next().ok_or(Error::ParamProcessingError)?)), - TypeDefPrimitive::Str => Ok(Value::string(arg.to_string())), - TypeDefPrimitive::U8 | - TypeDefPrimitive::U16 | - TypeDefPrimitive::U32 | - TypeDefPrimitive::U64 | - TypeDefPrimitive::U128 | - TypeDefPrimitive::U256 => - Ok(Value::u128(arg.parse::().map_err(|_| Error::ParamProcessingError)?)), - TypeDefPrimitive::I8 | - TypeDefPrimitive::I16 | - TypeDefPrimitive::I32 | - TypeDefPrimitive::I64 | - TypeDefPrimitive::I128 | - TypeDefPrimitive::I256 => - Ok(Value::i128(arg.parse::().map_err(|_| Error::ParamProcessingError)?)), - } - } -} - -impl TypeParser for TypeDefVariant { - fn parse(&self, arg: &str, registry: &PortableRegistry) -> Result { - let input = arg.trim(); - // Parse variant with data (e.g., `Some(value1, value2, ...)`). - if let Some(start) = input.find('(') { - if !input.ends_with(')') { - return Err(Error::ParamProcessingError); - } - let name = input[..start].trim(); - let data_str = &input[start + 1..input.len() - 1]; - let variant_def = self - .variants - .iter() - .find(|v| v.name == name) - .ok_or_else(|| Error::ParamProcessingError)?; - - let mut values = Vec::new(); - for field in variant_def.fields.iter() { - let field_type_id = field.ty.id; - let field_ty = - registry.resolve(field_type_id).ok_or_else(|| Error::ParamProcessingError)?; - // Recursive for the sub parameters of variant type. - let field_value = process_argument(data_str, field_ty, registry)?; - values.push(field_value); - } - - Ok(Value::unnamed_variant(name.to_string(), values)) - } else { - // Parse variant without data (e.g., `None`). - let name = input.to_string(); - let variant_def = self - .variants - .iter() - .find(|v| v.name == name) - .ok_or_else(|| Error::ParamProcessingError)?; - if !variant_def.fields.is_empty() { - return Err(Error::ParamProcessingError); - } - Ok(Value::unnamed_variant(name, vec![])) - } - } -} - -impl TypeParser for TypeDefComposite { - // Example: A composite type is input by the user [true, hello, 42, world], convert into proper - // composite type: {"a": true, "b": "hello", "c": { "d": 42, e: "world" }} - fn parse(&self, arg: &str, registry: &PortableRegistry) -> Result { - let mut values: Vec<&str> = arg.split(',').map(str::trim).collect(); - - let mut field_values = Vec::new(); - for (index, field) in self.fields.iter().enumerate() { - let field_name = field - .name - .clone() - .or_else(|| field.type_name.clone()) - .unwrap_or_else(|| format!("unnamed_field_{}", index)); - - let field_type = registry.resolve(field.ty.id).ok_or(Error::ParamProcessingError)?; - if values.is_empty() { - return Err(Error::ParamProcessingError); - } - let value = match &field_type.type_def { - TypeDef::Composite(nested_composite) => { - if nested_composite.fields.is_empty() { - // Recursive for the sub parameters of nested composite type. - let raw_value = values.remove(0); - process_argument(raw_value, field_type, registry)? - } else { - // Parse nested composite type. - let nested_args_count = nested_composite.fields.len(); - if values.len() < nested_args_count { - return Err(Error::ParamProcessingError); - } - let nested_args: Vec = - values.drain(..nested_args_count).map(String::from).collect(); - let nested_arg_str = nested_args.join(","); - nested_composite.parse(&nested_arg_str, registry)? - } - }, - _ => { - // Recursive for the sub parameters of the composite type. - let raw_value = values.remove(0); - process_argument(raw_value, field_type, registry)? - }, - }; - field_values.push((field_name, value)); - } - Ok(Value::named_composite(field_values)) - } -} - -impl TypeParser for TypeDefSequence { - // Example: [val1, val2, ...] - fn parse(&self, arg: &str, _registry: &PortableRegistry) -> Result { - Ok(Value::from_bytes(arg)) - } -} - -impl TypeParser for TypeDefArray { - // Example: [val1, val2, ...] - fn parse(&self, arg: &str, _registry: &PortableRegistry) -> Result { - Ok(Value::from_bytes(arg)) - } -} - -impl TypeParser for TypeDefTuple { - fn parse(&self, arg: &str, registry: &PortableRegistry) -> Result { - let input = arg.trim(); - // Extract tuple contents from parentheses (e.g., `(value1, value2, ...)`). - let tuple_content = if input.starts_with('(') && input.ends_with(')') { - &input[1..input.len() - 1] - } else { - input - }; - let tuple_values: Vec<&str> = tuple_content.split(',').map(|s| s.trim()).collect(); - if tuple_values.len() != self.fields.len() { - return Err(Error::ParamProcessingError); - } - let mut values = Vec::new(); - for (sub_ty_id, sub_arg) in self.fields.iter().zip(tuple_values.iter()) { - let sub_ty = - registry.resolve(sub_ty_id.id).ok_or_else(|| Error::ParamProcessingError)?; - // Recursive for each value of the tuple. - let value = process_argument(sub_arg.trim(), sub_ty, registry)?; - values.push(value); - } - Ok(Value::unnamed_composite(values)) - } -} - -impl TypeParser for TypeDefCompact { - fn parse(&self, arg: &str, registry: &PortableRegistry) -> Result { - // Parse compact types as their sub type (e.g., `Compact`). - let sub_ty = registry - .resolve(self.type_param.id) - .ok_or_else(|| Error::ParamProcessingError)?; - // Recursive for the inner value. - process_argument(arg, sub_ty, registry) - } -} diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs index a487674c8..fd55d1baa 100644 --- a/crates/pop-parachains/src/call/mod.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -16,13 +16,11 @@ pub async fn set_up_api(url: &str) -> Result, Erro } pub async fn construct_extrinsic( - api: &OnlineClient, pallet_name: &str, extrinsic_name: &str, args: Vec, ) -> Result { - let parsed_args: Vec = - metadata::process_extrinsic_args(api, pallet_name, extrinsic_name, args).await?; + let parsed_args: Vec = metadata::parse_extrinsic_arguments(args).await?; Ok(subxt::dynamic::tx(pallet_name, extrinsic_name, parsed_args)) } diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index 58c76cc49..de4934869 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -19,7 +19,9 @@ pub use call::{ construct_extrinsic, encode_call_data, metadata::{ action::{supported_actions, Action}, - find_extrinsic_by_name, find_pallet_by_name, parse_chain_metadata, Pallet, Param, + find_extrinsic_by_name, find_pallet_by_name, + params::Param, + parse_chain_metadata, Pallet, }, set_up_api, sign_and_submit_extrinsic, }; From b6dba520688944a77cb2a8ed386a6179da8727b7 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Sun, 24 Nov 2024 17:30:10 +0100 Subject: [PATCH 068/211] feat: add Purchase on-demand coretime use cases --- crates/pop-parachains/src/call/metadata/action.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/pop-parachains/src/call/metadata/action.rs b/crates/pop-parachains/src/call/metadata/action.rs index a26e5c9e3..ec089099d 100644 --- a/crates/pop-parachains/src/call/metadata/action.rs +++ b/crates/pop-parachains/src/call/metadata/action.rs @@ -46,6 +46,13 @@ pub enum Action { props(Pallet = "Nfts") )] MintNFT, + #[strum( + serialize = "place_order_allow_death", + message = "place_order_allow_death", + detailed_message = "Purchase on-demand coretime", + props(Pallet = "OnDemand") + )] + PurchaseOnDemandCoretime, #[strum( serialize = "transfer", message = "transfer_allow_death", From a5c15d52672a31a5f7ab9c6b1263315df7228027 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 26 Nov 2024 11:32:18 +0100 Subject: [PATCH 069/211] test: add skip_confirm, move when prompt for the signer and create the integration test --- crates/pop-cli/src/commands/call/parachain.rs | 34 ++++++++----- crates/pop-cli/tests/parachain.rs | 49 +++++++++++++++++++ 2 files changed, 71 insertions(+), 12 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 86f44bc9c..220224fbf 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -33,6 +33,9 @@ pub struct CallParachainCommand { /// - with a password "//Alice///SECRET_PASSWORD" #[clap(name = "suri", long, short, default_value = DEFAULT_URI)] suri: String, + /// Automatically signs and submits the extrinsic without asking for confirmation. + #[clap(short('y'), long)] + skip_confirm: bool, } impl CallParachainCommand { @@ -91,6 +94,11 @@ impl CallParachainCommand { cli.intro("Call a parachain")?; } + // If extrinsic has been specified via command line arguments, return early. + if self.extrinsic.is_some() { + return Ok(set_up_api(self.url.as_str()).await?); + } + // Resolve url. if !repeat && self.url.as_str() == DEFAULT_URL { // Prompt for url. @@ -167,6 +175,15 @@ impl CallParachainCommand { self.args = contract_args; } + // Resolve who is sigining the extrinsic. + if self.suri == DEFAULT_URI { + self.suri = cli + .input("Signer of the extrinsic:") + .placeholder("//Alice") + .default_input("//Alice") + .interact()?; + } + cli.info(self.display())?; Ok(api) } @@ -199,6 +216,7 @@ impl CallParachainCommand { Ok(tx) } + /// Sign an submit an extrinsic. async fn send_extrinsic( &mut self, api: OnlineClient, @@ -206,18 +224,10 @@ impl CallParachainCommand { prompt_to_repeat_call: bool, cli: &mut impl cli::traits::Cli, ) -> Result<()> { - if self.suri == DEFAULT_URI { - self.suri = cli - .input("Signer of the extrinsic:") - .placeholder("//Alice") - .default_input("//Alice") - .interact()?; - } - cli.info(self.display())?; - if !cli - .confirm("Do you want to submit the extrinsic?") - .initial_value(true) - .interact()? + if !self.skip_confirm && + !cli.confirm("Do you want to submit the extrinsic?") + .initial_value(true) + .interact()? { display_message( &format!( diff --git a/crates/pop-cli/tests/parachain.rs b/crates/pop-cli/tests/parachain.rs index 0e17d1017..1cd6b2468 100644 --- a/crates/pop-cli/tests/parachain.rs +++ b/crates/pop-cli/tests/parachain.rs @@ -87,12 +87,61 @@ async fn parachain_lifecycle() -> Result<()> { assert!(content.contains("\"relay_chain\": \"paseo-local\"")); assert!(content.contains("\"protocolId\": \"pop-protocol\"")); + // Custom network to manually set the port + let network_toml_path = temp_parachain_dir.join("network.toml"); + fs::create_dir_all(&temp_parachain_dir)?; + fs::write( + &network_toml_path, + r#"[relaychain] +chain = "paseo-local" + +[[relaychain.nodes]] +name = "alice" +rpc_port = 8833 +validator = true + +[[relaychain.nodes]] +name = "bob" +validator = true + +[[parachains]] +id = 2000 +default_command = "./target/release/parachain-template-node" + +[[parachains.collators]] +name = "collator-01" +"#, + )?; + // pop up parachain -p "./test_parachain" let mut cmd = Cmd::new(cargo_bin("pop")) .current_dir(&temp_parachain_dir) .args(&["up", "parachain", "-f", "./network.toml", "--skip-confirm"]) .spawn() .unwrap(); + + // pop call parachain --pallet System --extrinsic remark --args "0x11" --url + // ws://127.0.0.1:8833/ --suri //Alice --skip-confirm + Command::cargo_bin("pop") + .unwrap() + .args(&[ + "call", + "parachain", + "--pallet", + "System", + "--extrinsic", + "remark", + "--args", + "0x11", + "--url", + "ws://127.0.0.1:8833/", + "--suri", + "//Alice", + "--skip-confirm", + ]) + .assert() + .success(); + // If after 20 secs is still running probably execution is ok, or waiting for user response sleep(Duration::from_secs(20)).await; From 61bccd48f1fa5e9df9dcd088976332e0e894382a Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Wed, 27 Nov 2024 09:46:27 +0100 Subject: [PATCH 070/211] test: call parachain ui unit test --- crates/pop-cli/src/commands/call/parachain.rs | 75 ++++++++++++++++++- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 220224fbf..afe4dc756 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -59,7 +59,6 @@ impl CallParachainCommand { return Ok(()); }, }; - // TODO: If call_data, go directly here. // Finally execute the call. if let Err(e) = self.send_extrinsic(api, tx, prompt_to_repeat_call, &mut cli::Cli).await { display_message(&e.to_string(), false, &mut cli::Cli)?; @@ -224,8 +223,9 @@ impl CallParachainCommand { prompt_to_repeat_call: bool, cli: &mut impl cli::traits::Cli, ) -> Result<()> { - if !self.skip_confirm && - !cli.confirm("Do you want to submit the extrinsic?") + if !self.skip_confirm + && !cli + .confirm("Do you want to submit the extrinsic?") .initial_value(true) .interact()? { @@ -414,3 +414,72 @@ fn prompt_for_tuple_param( } Ok(format!("({})", tuple_values.join(", "))) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::cli::MockCli; + use url::Url; + + // This test only covers the interactive portion of the call parachain command, without actually + // submitting any extrinsic. + #[tokio::test] + async fn guide_user_to_call_parachain_works() -> Result<()> { + let mut cli = MockCli::new().expect_intro("Call a parachain"); + + let mut call_config = CallParachainCommand { + pallet: None, + extrinsic: Some("Test".to_string()), + args: vec![].to_vec(), + url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, + suri: DEFAULT_URI.to_string(), + skip_confirm: false, + }; + // If the extrinsic has been specified via command line arguments, return early. + call_config.configure(&mut cli, false).await?; + cli.verify()?; + + // Test all process specifying pallet, and see the prompted extrinsics. + call_config.extrinsic = None; + call_config.pallet = Some("System".to_string()); + call_config.url = Url::parse(DEFAULT_URL)?; + + cli = MockCli::new() + .expect_intro("Call a parachain") + .expect_input("Signer of the extrinsic:", "//Bob".into()) + .expect_input("Enter the value for the parameter: remark", "0x11".into()) + .expect_input("Which chain would you like to interact with?", "wss://rpc1.paseo.popnetwork.xyz".into()) + .expect_select::( + "Select the extrinsic to call:", + Some(true), + true, + Some( + [ + ("remark".to_string(), "Make some on-chain remark.Can be executed by every `origin`.".to_string()), + ("set_heap_pages".to_string(), "Set the number of pages in the WebAssembly environment's heap.".to_string()), + ("set_code".to_string(), "Set the new runtime code.".to_string()), + ("set_code_without_checks".to_string(), "Set the new runtime code without doing any checks of the given `code`.Note that runtime upgrades will not run if this is called with a not-increasing specversion!".to_string()), + ("set_storage".to_string(), "Set some items of storage.".to_string()), + ("kill_storage".to_string(), "Kill some items from storage.".to_string()), + ("kill_prefix".to_string(), "Kill all storage items with a key that starts with the given prefix.**NOTE:** We rely on the Root origin to provide us the number of subkeys underthe prefix we are removing to accurately calculate the weight of this function.".to_string()), + ("remark_with_event".to_string(), "Make some on-chain remark and emit event.".to_string()), + ("authorize_upgrade".to_string(), "Authorize an upgrade to a given `code_hash` for the runtime. The runtime can be suppliedlater.This call requires Root origin.".to_string()), + ("authorize_upgrade_without_checks".to_string(), "Authorize an upgrade to a given `code_hash` for the runtime. The runtime can be suppliedlater.WARNING: This authorizes an upgrade that will take place without any safety checks, forexample that the spec name remains the same and that the version number increases. Notrecommended for normal use. Use `authorize_upgrade` instead.This call requires Root origin.".to_string()), + ("apply_authorized_upgrade".to_string(), "Provide the preimage (runtime binary) `code` for an upgrade that has been authorized.If the authorization required a version check, this call will ensure the spec nameremains unchanged and that the spec version has increased.Depending on the runtime's `OnSetCode` configuration, this function may directly applythe new `code` in the same block or attempt to schedule the upgrade.All origins are allowed.".to_string()), + ] + .to_vec(), + ), + 0, // "remark" extrinsic + ).expect_info("pop call parachain --pallet System --extrinsic remark --args \"0x11\" --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Bob"); + + call_config.configure(&mut cli, false).await?; + + assert_eq!(call_config.pallet, Some("System".to_string())); + assert_eq!(call_config.extrinsic, Some("remark".to_string())); + assert_eq!(call_config.args, ["0x11".to_string()].to_vec()); + assert_eq!(call_config.url, Url::parse("wss://rpc1.paseo.popnetwork.xyz")?); + assert_eq!(call_config.suri, "//Bob".to_string()); + assert_eq!(call_config.display(), "pop call parachain --pallet System --extrinsic remark --args \"0x11\" --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Bob"); + cli.verify() + } +} From 8fbc4a57fd8e12456181819fd5143bd5f0e079fd Mon Sep 17 00:00:00 2001 From: Daanvdplas Date: Thu, 28 Nov 2024 00:12:11 +0100 Subject: [PATCH 071/211] refactor: separate structs --- crates/pop-cli/src/commands/call/parachain.rs | 377 +++++++++--------- .../src/call/metadata/action.rs | 24 +- .../pop-parachains/src/call/metadata/mod.rs | 15 +- crates/pop-parachains/src/call/mod.rs | 4 +- crates/pop-parachains/src/lib.rs | 2 +- 5 files changed, 220 insertions(+), 202 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 220224fbf..a48502ea3 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -6,8 +6,9 @@ use clap::Args; use pop_parachains::{ construct_extrinsic, encode_call_data, find_extrinsic_by_name, find_pallet_by_name, parse_chain_metadata, set_up_api, sign_and_submit_extrinsic, supported_actions, Action, - DynamicPayload, OnlineClient, Pallet, Param, SubstrateConfig, + DynamicPayload, Extrinsic, OnlineClient, Pallet, Param, SubstrateConfig, }; +use url::Url; const DEFAULT_URL: &str = "ws://localhost:9944/"; const DEFAULT_URI: &str = "//Alice"; @@ -15,198 +16,205 @@ const DEFAULT_URI: &str = "//Alice"; #[derive(Args, Clone)] pub struct CallParachainCommand { /// The pallet containing the extrinsic to execute. - #[clap(long, short)] + #[arg(long)] pallet: Option, /// The extrinsic to execute within the chosen pallet. - #[clap(long, short)] + #[arg(long)] extrinsic: Option, /// The extrinsic arguments, encoded as strings. - #[clap(long, num_args = 0..,)] + #[arg(long, num_args = 0..,)] args: Vec, /// Websocket endpoint of a node. - #[clap(name = "url", short = 'u', long, value_parser, default_value = DEFAULT_URL)] - url: url::Url, + #[arg(short = 'u', long, value_parser)] + url: Option, /// Secret key URI for the account signing the extrinsic. /// /// e.g. /// - for a dev account "//Alice" /// - with a password "//Alice///SECRET_PASSWORD" - #[clap(name = "suri", long, short, default_value = DEFAULT_URI)] - suri: String, + #[arg(long)] + suri: Option, /// Automatically signs and submits the extrinsic without asking for confirmation. - #[clap(short('y'), long)] + #[arg(short('y'), long)] skip_confirm: bool, } impl CallParachainCommand { /// Executes the command. pub(crate) async fn execute(mut self) -> Result<()> { - // Check if message specified via command line argument. - let prompt_to_repeat_call = self.extrinsic.is_none(); - // Configure the call based on command line arguments/call UI. - let api = match self.configure(&mut cli::Cli, false).await { - Ok(api) => api, - Err(e) => { - display_message(&e.to_string(), false, &mut cli::Cli)?; - return Ok(()); - }, - }; - // Prepare Extrinsic. - let tx = match self.prepare_extrinsic(&api, &mut cli::Cli).await { - Ok(api) => api, - Err(e) => { - display_message(&e.to_string(), false, &mut cli::Cli)?; - return Ok(()); - }, - }; - // TODO: If call_data, go directly here. - // Finally execute the call. - if let Err(e) = self.send_extrinsic(api, tx, prompt_to_repeat_call, &mut cli::Cli).await { - display_message(&e.to_string(), false, &mut cli::Cli)?; + let mut cli = cli::Cli; + cli.intro("Call a parachain")?; + + // Configure the chain. + let chain = self.configure_chain(&mut cli).await?; + loop { + // Configure the call based on command line arguments/call UI. + let mut call = match self.configure_call(&chain, &mut cli).await { + Ok(call) => call, + Err(e) => { + display_message(&e.to_string(), false, &mut cli)?; + break + }, + }; + // Display the configured call. + cli.info(call.display(&chain))?; + // Prepare the extrinsic. + let tx = match call.prepare_extrinsic(&chain.api, &mut cli).await { + Ok(api) => api, + Err(e) => { + display_message(&e.to_string(), false, &mut cli)?; + break + }, + }; + // TODO: If call_data, go directly here (?). + // Send the extrinsic. + if let Err(e) = + call.send_extrinsic(&chain.api, tx, &mut cli).await + { + display_message(&e.to_string(), false, &mut cli)?; + } + if !cli + .confirm("Do you want to perform another call?") + .initial_value(false) + .interact()? + { + display_message("Parachain calling complete.", true, &mut cli)?; + break + } } Ok(()) } - fn display(&self) -> String { - let mut full_message = "pop call parachain".to_string(); - if let Some(pallet) = &self.pallet { - full_message.push_str(&format!(" --pallet {}", pallet)); - } - if let Some(extrinsic) = &self.extrinsic { - full_message.push_str(&format!(" --extrinsic {}", extrinsic)); - } - if !self.args.is_empty() { - let args: Vec<_> = self.args.iter().map(|a| format!("\"{a}\"")).collect(); - full_message.push_str(&format!(" --args {}", args.join(" "))); - } - full_message.push_str(&format!(" --url {} --suri {}", self.url, self.suri)); - full_message + async fn configure_chain(&self, cli: &mut impl Cli) -> Result { + // Resolve url. + let url = match self.clone().url { + Some(url) => url, + None => { + // Prompt for url. + let url: String = cli + .input("Which chain would you like to interact with?") + .default_input(DEFAULT_URL) + .interact()?; + Url::parse(&url)? + }, + }; + + // Parse metadata from chain url. + let api = set_up_api(&url.as_str()).await?; + let pallets = parse_chain_metadata(&api).await.map_err(|e| { + anyhow!(format!("Unable to fetch the chain metadata: {}", e.to_string())) + })?; + Ok(Chain { url, api, pallets }) } /// Configure the call based on command line arguments/call UI. - async fn configure( + async fn configure_call( &mut self, - cli: &mut impl cli::traits::Cli, - repeat: bool, - ) -> Result> { - // Show intro on first run. - if !repeat { - cli.intro("Call a parachain")?; - } - - // If extrinsic has been specified via command line arguments, return early. - if self.extrinsic.is_some() { - return Ok(set_up_api(self.url.as_str()).await?); - } + chain: &Chain, + cli: &mut impl Cli, + ) -> Result { + loop { + // Resolve pallet. + let pallet = match self.pallet { + Some(ref pallet_name) => find_pallet_by_name(&chain.pallets, pallet_name).await?, + None => { + // Specific predefined actions first. + if let Some(action) = prompt_predefined_actions(&chain.pallets, cli).await? { + self.extrinsic = Some(action.extrinsic_name().to_string()); + find_pallet_by_name(&chain.pallets, action.pallet_name()).await? + } else { + let mut prompt = cli.select("Select the pallet to call:"); + for pallet_item in &chain.pallets { + prompt = + prompt.item(pallet_item.clone(), &pallet_item.name, &pallet_item.docs); + } + prompt.interact()? + } + }, + }; - // Resolve url. - if !repeat && self.url.as_str() == DEFAULT_URL { - // Prompt for url. - let url: String = cli - .input("Which chain would you like to interact with?") - .placeholder("wss://rpc1.paseo.popnetwork.xyz") - .default_input("wss://rpc1.paseo.popnetwork.xyz") - .interact()?; - self.url = url::Url::parse(&url)? - }; + // Resolve extrinsic. + let extrinsic = match self.extrinsic { + Some(ref extrinsic_name) => + find_extrinsic_by_name(&chain.pallets, &pallet.name, extrinsic_name).await?, + None => { + let mut prompt_extrinsic = cli.select("Select the extrinsic to call:"); + for extrinsic in &pallet.extrinsics { + prompt_extrinsic = + prompt_extrinsic.item(extrinsic.clone(), &extrinsic.name, &extrinsic.docs); + } + prompt_extrinsic.interact()? + }, + }; + // Certain extrinsics are not supported yet due to complexity. + if !extrinsic.is_supported { + cli.outro_cancel( + "The selected extrinsic is not supported yet. Please choose another one.", + )?; + continue + } - // Parse metadata from url chain. - let api = set_up_api(self.url.as_str()).await?; - let pallets = match parse_chain_metadata(&api).await { - Ok(pallets) => pallets, - Err(e) => { - return Err(anyhow!(format!( - "Unable to fetch the chain metadata: {}", - e.to_string() - ))); - }, - }; - // Resolve pallet. - let pallet = if let Some(ref pallet_name) = self.pallet { - // Specified by the user. - find_pallet_by_name(&pallets, pallet_name).await? - } else { - // Specific predefined actions first. - let action: Option = prompt_predefined_actions(&pallets, cli).await?; - if let Some(action) = action { - self.pallet = Some(action.pallet_name().to_string()); - self.extrinsic = Some(action.extrinsic_name().to_string()); - find_pallet_by_name(&pallets, action.pallet_name()).await? - } else { - let mut prompt = cli.select("Select the pallet to call:"); - for pallet_item in &pallets { - prompt = prompt.item(pallet_item.clone(), &pallet_item.name, &pallet_item.docs); + // Resolve message arguments. + let args = if self.clone().args.is_empty() { + let mut args = Vec::new(); + for param in &extrinsic.params { + let input = prompt_for_param(&chain.api, cli, ¶m)?; + args.push(input); } - let pallet_prompted = prompt.interact()?; - self.pallet = Some(pallet_prompted.name.clone()); - pallet_prompted - } - }; - // Resolve extrinsic. - let extrinsic = if let Some(ref extrinsic_name) = self.extrinsic { - find_extrinsic_by_name(&pallets, &pallet.name, extrinsic_name).await? - } else { - let mut prompt_extrinsic = cli.select("Select the extrinsic to call:"); - for extrinsic in pallet.extrinsics { - prompt_extrinsic = - prompt_extrinsic.item(extrinsic.clone(), &extrinsic.name, &extrinsic.docs); - } - let extrinsic_prompted = prompt_extrinsic.interact()?; - self.extrinsic = Some(extrinsic_prompted.name.clone()); - extrinsic_prompted - }; - // Certain extrinsics are not supported yet due to complexity. - if !extrinsic.is_supported { - cli.outro_cancel( - "The selected extrinsic is not supported yet. Please choose another one.", - )?; - // Reset specific items from the last call and repeat. - self.reset_for_new_call(); - Box::pin(self.configure(cli, true)).await?; - } + args + } else { + self.clone().args + }; - // Resolve message arguments. - if self.args.is_empty() { - let mut contract_args = Vec::new(); - for param in extrinsic.params { - let input = prompt_for_param(&api, cli, ¶m)?; - contract_args.push(input); - } - self.args = contract_args; - } + // Resolve who is signing the extrinsic. + let suri = match self.clone().suri { + Some(suri) => suri, + None => cli.input("Signer of the extrinsic:").default_input(DEFAULT_URI).interact()?, + }; - // Resolve who is sigining the extrinsic. - if self.suri == DEFAULT_URI { - self.suri = cli - .input("Signer of the extrinsic:") - .placeholder("//Alice") - .default_input("//Alice") - .interact()?; + return Ok(CallParachain { pallet, extrinsic, args, suri, skip_confirm: self.skip_confirm }); } - - cli.info(self.display())?; - Ok(api) } +} - /// Prepares the extrinsic or query. +struct Chain { + url: Url, + api: OnlineClient, + pallets: Vec, +} + +#[derive(Clone)] +struct CallParachain { + /// The pallet of the extrinsic. + pallet: Pallet, + /// The extrinsic to execute. + extrinsic: Extrinsic, + /// The extrinsic arguments, encoded as strings. + args: Vec, + /// Secret key URI for the account signing the extrinsic. + /// + /// e.g. + /// - for a dev account "//Alice" + /// - with a password "//Alice///SECRET_PASSWORD" + suri: String, + /// Whether to automatically sign and submit the extrinsic without asking for confirmation. + skip_confirm: bool, +} + +impl CallParachain { + // Prepares the extrinsic or query. async fn prepare_extrinsic( &self, api: &OnlineClient, - cli: &mut impl cli::traits::Cli, + cli: &mut impl Cli, ) -> Result { - let extrinsic = match &self.extrinsic { - Some(extrinsic) => extrinsic.to_string(), - None => { - return Err(anyhow!("Please specify the extrinsic.")); - }, - }; - let pallet = match &self.pallet { - Some(pallet) => pallet.to_string(), - None => { - return Err(anyhow!("Please specify the pallet.")); - }, - }; - let tx = match construct_extrinsic(&pallet, &extrinsic, self.args.clone()).await { + let tx = match construct_extrinsic( + &self.pallet.name.as_str(), + &self.extrinsic.name.as_str(), + self.args.clone(), + ) + .await + { Ok(tx) => tx, Err(e) => { return Err(anyhow!("Error: {}", e)); @@ -216,13 +224,12 @@ impl CallParachainCommand { Ok(tx) } - /// Sign an submit an extrinsic. + // Sign and submit an extrinsic. async fn send_extrinsic( &mut self, - api: OnlineClient, + api: &OnlineClient, tx: DynamicPayload, - prompt_to_repeat_call: bool, - cli: &mut impl cli::traits::Cli, + cli: &mut impl Cli, ) -> Result<()> { if !self.skip_confirm && !cli.confirm("Do you want to submit the extrinsic?") @@ -246,36 +253,22 @@ impl CallParachainCommand { .map_err(|err| anyhow!("{}", format!("{err:?}")))?; spinner.stop(&format!("Extrinsic submitted with hash: {:?}", result)); - - // Prompt for any additional calls. - if !prompt_to_repeat_call { - display_message("Extrinsic submitted successfully!", true, cli)?; - return Ok(()); - } - if cli - .confirm("Do you want to perform another call to the same chain?") - .initial_value(false) - .interact()? - { - // Reset specific items from the last call and repeat. - self.reset_for_new_call(); - self.configure(cli, true).await?; - let tx = self.prepare_extrinsic(&api, &mut cli::Cli).await?; - Box::pin(self.send_extrinsic(api, tx, prompt_to_repeat_call, cli)).await - } else { - display_message("Parachain calling complete.", true, cli)?; - Ok(()) - } + Ok(()) } - /// Resets specific fields to default values for a new call. - fn reset_for_new_call(&mut self) { - self.pallet = None; - self.extrinsic = None; - self.args.clear(); + fn display(&self, chain: &Chain) -> String { + let mut full_message = "pop call parachain".to_string(); + full_message.push_str(&format!(" --pallet {}", self.pallet)); + full_message.push_str(&format!(" --extrinsic {}", self.extrinsic)); + if !self.args.is_empty() { + let args: Vec<_> = self.args.iter().map(|a| format!("\"{a}\"")).collect(); + full_message.push_str(&format!(" --args {}", args.join(" "))); + } + full_message.push_str(&format!(" --url {} --suri {}", chain.url, self.suri)); + full_message } } -fn display_message(message: &str, success: bool, cli: &mut impl cli::traits::Cli) -> Result<()> { +fn display_message(message: &str, success: bool, cli: &mut impl Cli) -> Result<()> { if success { cli.outro(message)?; } else { @@ -287,7 +280,7 @@ fn display_message(message: &str, success: bool, cli: &mut impl cli::traits::Cli // Prompts the user for some predefined actions. async fn prompt_predefined_actions( pallets: &[Pallet], - cli: &mut impl cli::traits::Cli, + cli: &mut impl Cli, ) -> Result> { let mut predefined_action = cli.select("What would you like to do?"); for action in supported_actions(&pallets).await { @@ -304,7 +297,7 @@ async fn prompt_predefined_actions( // Prompts the user for the value of a parameter. fn prompt_for_param( api: &OnlineClient, - cli: &mut impl cli::traits::Cli, + cli: &mut impl Cli, param: &Param, ) -> Result { if param.is_optional { @@ -327,7 +320,7 @@ fn prompt_for_param( // Resolves the value of a parameter based on its type. fn get_param_value( api: &OnlineClient, - cli: &mut impl cli::traits::Cli, + cli: &mut impl Cli, param: &Param, ) -> Result { if param.sub_params.is_empty() { @@ -342,7 +335,7 @@ fn get_param_value( } // Prompt for the value when is a primitive. -fn prompt_for_primitive_param(cli: &mut impl cli::traits::Cli, param: &Param) -> Result { +fn prompt_for_primitive_param(cli: &mut impl Cli, param: &Param) -> Result { Ok(cli .input(format!("Enter the value for the parameter: {}", param.name)) .placeholder(&format!("Type required: {}", param.type_name)) @@ -353,7 +346,7 @@ fn prompt_for_primitive_param(cli: &mut impl cli::traits::Cli, param: &Param) -> // fields. Output example: Id(5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY) for the Id variant. fn prompt_for_variant_param( api: &OnlineClient, - cli: &mut impl cli::traits::Cli, + cli: &mut impl Cli, param: &Param, ) -> Result { let selected_variant = { @@ -379,7 +372,7 @@ fn prompt_for_variant_param( // Recursively prompt the user for all the nested fields in a Composite type. fn prompt_for_composite_param( api: &OnlineClient, - cli: &mut impl cli::traits::Cli, + cli: &mut impl Cli, param: &Param, ) -> Result { let mut field_values = Vec::new(); @@ -404,7 +397,7 @@ fn prompt_for_composite_param( // Recursively prompt the user for the tuple values. fn prompt_for_tuple_param( api: &OnlineClient, - cli: &mut impl cli::traits::Cli, + cli: &mut impl Cli, param: &Param, ) -> Result { let mut tuple_values = Vec::new(); diff --git a/crates/pop-parachains/src/call/metadata/action.rs b/crates/pop-parachains/src/call/metadata/action.rs index ec089099d..40f64f54a 100644 --- a/crates/pop-parachains/src/call/metadata/action.rs +++ b/crates/pop-parachains/src/call/metadata/action.rs @@ -18,6 +18,13 @@ use strum_macros::{AsRefStr, Display, EnumMessage, EnumProperty, EnumString, Var VariantArray, )] pub enum Action { + #[strum( + serialize = "transfer", + message = "transfer_allow_death", + detailed_message = "Transfer Balance", + props(Pallet = "Balances") + )] + Transfer, #[strum( serialize = "create", message = "create", @@ -54,12 +61,19 @@ pub enum Action { )] PurchaseOnDemandCoretime, #[strum( - serialize = "transfer", - message = "transfer_allow_death", - detailed_message = "Transfer Balance", - props(Pallet = "Balances") + serialize = "reserve", + message = "reserve", + detailed_message = "Reserve para id", + props(Pallet = "Registrar") )] - Transfer, + Reserve, + #[strum( + serialize = "register", + message = "register", + detailed_message = "Register para id with genesis state and code", + props(Pallet = "Registrar") + )] + Register, } impl Action { diff --git a/crates/pop-parachains/src/call/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs index e7f863d0a..6c68d8376 100644 --- a/crates/pop-parachains/src/call/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -3,6 +3,7 @@ use crate::errors::Error; use params::Param; use scale_value::stringify::custom_parsers; +use std::fmt::{Display, Formatter}; use subxt::{dynamic::Value, Metadata, OnlineClient, SubstrateConfig}; pub mod action; @@ -15,10 +16,16 @@ pub struct Pallet { pub name: String, /// The documentation of the pallet. pub docs: String, - // The extrinsics of the pallet. + /// The extrinsics of the pallet. pub extrinsics: Vec, } +impl Display for Pallet { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name) + } +} + #[derive(Clone, PartialEq, Eq, Debug)] /// Represents an extrinsic in a pallet. pub struct Extrinsic { @@ -32,6 +39,12 @@ pub struct Extrinsic { pub is_supported: bool, } +impl Display for Extrinsic { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name) + } +} + /// Parses the chain metadata to extract information about pallets and their extrinsics with its /// parameters. /// diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs index fd55d1baa..8fd3c6414 100644 --- a/crates/pop-parachains/src/call/mod.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -34,9 +34,7 @@ pub async fn sign_and_submit_extrinsic( .tx() .sign_and_submit_then_watch_default(&tx, &signer) .await? - .wait_for_finalized() - .await? - .wait_for_success() + .wait_for_finalized_success() .await?; Ok(format!("{:?}", result.extrinsic_hash())) } diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index de4934869..637547a21 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -21,7 +21,7 @@ pub use call::{ action::{supported_actions, Action}, find_extrinsic_by_name, find_pallet_by_name, params::Param, - parse_chain_metadata, Pallet, + parse_chain_metadata, Extrinsic, Pallet, }, set_up_api, sign_and_submit_extrinsic, }; From b22e0d3c083c25627fd701ad0f367857c9324986 Mon Sep 17 00:00:00 2001 From: Daanvdplas Date: Thu, 28 Nov 2024 00:15:13 +0100 Subject: [PATCH 072/211] fmt --- crates/pop-cli/src/commands/call/parachain.rs | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index a48502ea3..bfd64c706 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -68,9 +68,7 @@ impl CallParachainCommand { }; // TODO: If call_data, go directly here (?). // Send the extrinsic. - if let Err(e) = - call.send_extrinsic(&chain.api, tx, &mut cli).await - { + if let Err(e) = call.send_extrinsic(&chain.api, tx, &mut cli).await { display_message(&e.to_string(), false, &mut cli)?; } if !cli @@ -108,11 +106,7 @@ impl CallParachainCommand { } /// Configure the call based on command line arguments/call UI. - async fn configure_call( - &mut self, - chain: &Chain, - cli: &mut impl Cli, - ) -> Result { + async fn configure_call(&mut self, chain: &Chain, cli: &mut impl Cli) -> Result { loop { // Resolve pallet. let pallet = match self.pallet { @@ -125,8 +119,11 @@ impl CallParachainCommand { } else { let mut prompt = cli.select("Select the pallet to call:"); for pallet_item in &chain.pallets { - prompt = - prompt.item(pallet_item.clone(), &pallet_item.name, &pallet_item.docs); + prompt = prompt.item( + pallet_item.clone(), + &pallet_item.name, + &pallet_item.docs, + ); } prompt.interact()? } @@ -140,8 +137,11 @@ impl CallParachainCommand { None => { let mut prompt_extrinsic = cli.select("Select the extrinsic to call:"); for extrinsic in &pallet.extrinsics { - prompt_extrinsic = - prompt_extrinsic.item(extrinsic.clone(), &extrinsic.name, &extrinsic.docs); + prompt_extrinsic = prompt_extrinsic.item( + extrinsic.clone(), + &extrinsic.name, + &extrinsic.docs, + ); } prompt_extrinsic.interact()? }, @@ -169,10 +169,17 @@ impl CallParachainCommand { // Resolve who is signing the extrinsic. let suri = match self.clone().suri { Some(suri) => suri, - None => cli.input("Signer of the extrinsic:").default_input(DEFAULT_URI).interact()?, + None => + cli.input("Signer of the extrinsic:").default_input(DEFAULT_URI).interact()?, }; - return Ok(CallParachain { pallet, extrinsic, args, suri, skip_confirm: self.skip_confirm }); + return Ok(CallParachain { + pallet, + extrinsic, + args, + suri, + skip_confirm: self.skip_confirm, + }); } } } From dcf0d33c70137663c6d59955853a80880faf4dac Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 28 Nov 2024 12:03:53 +0100 Subject: [PATCH 073/211] test: pop-cli unit testing --- crates/pop-cli/src/commands/call/parachain.rs | 243 +++++++++++++++++- 1 file changed, 236 insertions(+), 7 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index afe4dc756..505798b2a 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -223,17 +223,13 @@ impl CallParachainCommand { prompt_to_repeat_call: bool, cli: &mut impl cli::traits::Cli, ) -> Result<()> { - if !self.skip_confirm - && !cli - .confirm("Do you want to submit the extrinsic?") + if !self.skip_confirm && + !cli.confirm("Do you want to submit the extrinsic?") .initial_value(true) .interact()? { display_message( - &format!( - "Extrinsic {:?} was not submitted. Operation canceled by the user.", - self.extrinsic - ), + &format!("Extrinsic not submitted. Operation canceled by the user."), false, cli, )?; @@ -482,4 +478,237 @@ mod tests { assert_eq!(call_config.display(), "pop call parachain --pallet System --extrinsic remark --args \"0x11\" --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Bob"); cli.verify() } + + // This test only covers the interactive portion of the call parachain command selecting one of + // the predefined actions, without actually submitting any extrinsic. + #[tokio::test] + async fn guide_user_to_configure_predefined_action_works() -> Result<()> { + let mut call_config = CallParachainCommand { + pallet: None, + extrinsic: None, + args: vec![].to_vec(), + url: Url::parse(DEFAULT_URL)?, + suri: DEFAULT_URI.to_string(), + skip_confirm: false, + }; + + let mut cli = MockCli::new() + .expect_intro("Call a parachain") + .expect_input("Signer of the extrinsic:", "//Bob".into()) + .expect_input("Enter the value for the parameter: para_id", "2000".into()) + .expect_input("Enter the value for the parameter: max_amount", "10000".into()) + .expect_input("Which chain would you like to interact with?", "wss://polkadot-rpc.publicnode.com".into()) + .expect_select::( + "What would you like to do?", + Some(true), + true, + Some( + [ + ("Purchase on-demand coretime".to_string(), "OnDemand".to_string()), + ("Transfer Balance".to_string(), "Balances".to_string()), + ("All".to_string(), "Explore all pallets and extrinsics".to_string()), + ] + .to_vec(), + ), + 0, // "Purchase on-demand coretime" action + ).expect_info("pop call parachain --pallet OnDemand --extrinsic place_order_allow_death --args \"10000\" \"2000\" --url wss://polkadot-rpc.publicnode.com/ --suri //Bob"); + + call_config.configure(&mut cli, false).await?; + + assert_eq!(call_config.pallet, Some("OnDemand".to_string())); + assert_eq!(call_config.extrinsic, Some("place_order_allow_death".to_string())); + assert_eq!(call_config.args, ["10000".to_string(), "2000".to_string()].to_vec()); + assert_eq!(call_config.url, Url::parse("wss://polkadot-rpc.publicnode.com")?); + assert_eq!(call_config.suri, "//Bob".to_string()); + assert_eq!(call_config.display(), "pop call parachain --pallet OnDemand --extrinsic place_order_allow_death --args \"10000\" \"2000\" --url wss://polkadot-rpc.publicnode.com/ --suri //Bob"); + cli.verify() + } + + #[tokio::test] + async fn prepare_extrinsic_works() -> Result<()> { + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let mut call_config = CallParachainCommand { + pallet: None, + extrinsic: None, + args: vec!["0x11".to_string()].to_vec(), + url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, + suri: DEFAULT_URI.to_string(), + skip_confirm: false, + }; + let mut cli = MockCli::new(); + // Error, no extrinsic specified. + assert!( + matches!(call_config.prepare_extrinsic(&api, &mut cli).await, Err(message) if message.to_string().contains("Please specify the extrinsic.")) + ); + call_config.extrinsic = Some("remark".to_string()); + // Error, no pallet specified. + assert!( + matches!(call_config.prepare_extrinsic(&api, &mut cli).await, Err(message) if message.to_string().contains("Please specify the pallet.")) + ); + call_config.pallet = Some("WrongName".to_string()); + // Error, no extrinsic specified. + assert!( + matches!(call_config.prepare_extrinsic(&api, &mut cli).await, Err(message) if message.to_string().contains("Metadata Error: Pallet with name WrongName not found")) + ); + // Success, extrinsic and pallet specified. + cli = MockCli::new().expect_info("Encoded call data: 0x00000411"); + call_config.pallet = Some("System".to_string()); + call_config.prepare_extrinsic(&api, &mut cli).await?; + + cli.verify() + } + + #[tokio::test] + async fn user_cancel_send_extrinsic_works() -> Result<()> { + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let mut call_config = CallParachainCommand { + pallet: Some("System".to_string()), + extrinsic: Some("remark".to_string()), + args: vec!["0x11".to_string()].to_vec(), + url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, + suri: DEFAULT_URI.to_string(), + skip_confirm: false, + }; + let mut cli = MockCli::new() + .expect_confirm("Do you want to submit the extrinsic?", false) + .expect_outro_cancel("Extrinsic not submitted. Operation canceled by the user."); + let tx = call_config.prepare_extrinsic(&api, &mut cli).await?; + call_config.send_extrinsic(api, tx, false, &mut cli).await?; + call_config.extrinsic = Some("remark".to_string()); + + cli.verify() + } + + #[test] + fn reset_for_new_call_works() -> Result<()> { + let mut call_config = CallParachainCommand { + pallet: Some("System".to_string()), + extrinsic: Some("remark".to_string()), + args: vec!["0x11".to_string()].to_vec(), + url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, + suri: DEFAULT_URI.to_string(), + skip_confirm: false, + }; + call_config.reset_for_new_call(); + assert_eq!(call_config.pallet, None); + assert_eq!(call_config.extrinsic, None); + assert_eq!(call_config.args.len(), 0); + Ok(()) + } + + #[test] + fn display_message_works() -> Result<()> { + let mut cli = MockCli::new().expect_outro(&"Call completed successfully!"); + display_message("Call completed successfully!", true, &mut cli)?; + cli.verify()?; + let mut cli = MockCli::new().expect_outro_cancel("Call failed."); + display_message("Call failed.", false, &mut cli)?; + cli.verify() + } + + #[tokio::test] + async fn prompt_predefined_actions_works() -> Result<()> { + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let pallets = parse_chain_metadata(&api).await?; + let mut cli = MockCli::new().expect_select::( + "What would you like to do?", + Some(true), + true, + Some( + [ + ("Create an Asset".to_string(), "Assets".to_string()), + ("Mint an Asset".to_string(), "Assets".to_string()), + ("Create an NFT Collection".to_string(), "Nfts".to_string()), + ("Mint an NFT".to_string(), "Nfts".to_string()), + ("Transfer Balance".to_string(), "Balances".to_string()), + ("All".to_string(), "Explore all pallets and extrinsics".to_string()), + ] + .to_vec(), + ), + 1, // "Mint an Asset" action + ); + let action = prompt_predefined_actions(&pallets, &mut cli).await?; + assert_eq!(action, Some(Action::MintAsset)); + cli.verify() + } + + #[tokio::test] + async fn prompt_for_param_works() -> Result<()> { + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let pallets = parse_chain_metadata(&api).await?; + // Using NFT mint extrinsic to test the majority of subfunctions + let extrinsic = find_extrinsic_by_name(&pallets, "Nfts", "mint").await?; + let mut cli = MockCli::new() + .expect_input("Enter the value for the parameter: mint_price", "1000".into()) + .expect_input( + "Enter the value for the parameter: Id", + "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty".into(), + ) + .expect_input("Enter the value for the parameter: item", "0".into()) + .expect_input("Enter the value for the parameter: collection", "0".into()) + .expect_select::( + "Select the value for the parameter: mint_to", + Some(true), + true, + Some( + [ + ("Id".to_string(), "".to_string()), + ("Index".to_string(), "".to_string()), + ("Raw".to_string(), "".to_string()), + ("Address32".to_string(), "".to_string()), + ("Address20".to_string(), "".to_string()), + ] + .to_vec(), + ), + 0, // "Id" action + ) + .expect_confirm( + "Do you want to provide a value for the optional parameter: mint_price?", + true, + ) + .expect_confirm( + "Do you want to provide a value for the optional parameter: owned_item?", + false, + ) + .expect_confirm( + "Do you want to provide a value for the optional parameter: witness_data?", + true, + ); + // Test all the extrinsic params + let mut params: Vec = Vec::new(); + for param in extrinsic.params { + params.push(prompt_for_param(&api, &mut cli, ¶m)?); + } + assert_eq!(params.len(), 4); + assert_eq!(params[0], "0".to_string()); // collection: test primitive + assert_eq!(params[1], "0".to_string()); // item: test primitive + assert_eq!(params[2], "Id(5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty)".to_string()); // mint_to: test variant + assert_eq!(params[3], "Some({owned_item: None(), mint_price: Some(1000)})".to_string()); // witness_data: test composite + cli.verify()?; + + // Using Scheduler set_retry extrinsic to test the tuple params + let extrinsic = find_extrinsic_by_name(&pallets, "Scheduler", "set_retry").await?; + let mut cli = MockCli::new() + .expect_input("Enter the value for the parameter: period", "0".into()) + .expect_input("Enter the value for the parameter: retries", "0".into()) + .expect_input( + "Enter the value for the parameter: Index 1 of the tuple task", + "0".into(), + ) + .expect_input( + "Enter the value for the parameter: Index 0 of the tuple task", + "0".into(), + ); + + // Test all the extrinsic params + let mut params: Vec = Vec::new(); + for param in extrinsic.params { + params.push(prompt_for_param(&api, &mut cli, ¶m)?); + } + assert_eq!(params.len(), 3); + assert_eq!(params[0], "(0, 0)".to_string()); // task: test tuples + assert_eq!(params[1], "0".to_string()); // retries: test primitive + assert_eq!(params[2], "0".to_string()); // period: test primitive + cli.verify() + } } From dd8a121ade9572555fe1b79c100d31e073bae62c Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 28 Nov 2024 12:34:02 +0100 Subject: [PATCH 074/211] test: pop-common unit tests --- crates/pop-common/src/metadata.rs | 62 ++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/crates/pop-common/src/metadata.rs b/crates/pop-common/src/metadata.rs index 4ea58d806..7aae66132 100644 --- a/crates/pop-common/src/metadata.rs +++ b/crates/pop-common/src/metadata.rs @@ -30,7 +30,6 @@ pub fn format_type(ty: &Type, registry: &PortableRegistry) -> Stri .collect(); name = format!("{name}<{}>", params.join(",")); } - name = format!( "{name}{}", match &ty.type_def { @@ -155,3 +154,64 @@ pub fn format_type(ty: &Type, registry: &PortableRegistry) -> Stri name } + +#[cfg(test)] +mod tests { + use super::*; + + use anyhow::Result; + use subxt::{OnlineClient, SubstrateConfig}; + + #[tokio::test] + async fn format_type_works() -> Result<()> { + let api = + OnlineClient::::from_url("wss://rpc1.paseo.popnetwork.xyz").await?; + let metadata = api.metadata(); + let registry = metadata.types(); + // Extrinsic Nfts::mint to test the majority of expresions. + let mut extrinsic = + metadata.pallet_by_name("Nfts").unwrap().call_variant_by_name("mint").unwrap(); + let mut types_formatted = Vec::new(); + for field in &extrinsic.fields { + let type_info = registry.resolve(field.ty.id).unwrap(); + types_formatted.push(format_type(&type_info, ®istry)); + } + assert_eq!(types_formatted.len(), 4); + assert_eq!(types_formatted[0], "u32"); // collection + assert_eq!(types_formatted[1], "u32"); // item + assert_eq!(types_formatted[2], "MultiAddress: Id(AccountId32 ([u8;32])), Index(Compact<()>), Raw([u8]), Address32([u8;32]), Address20([u8;20])"); // mint_to + assert_eq!(types_formatted[3], "Option { owned_item: Option, mint_price: Option }>: None, Some(MintWitness { owned_item: Option, mint_price: Option })"); // witness_data + + // Extrinsic Sytem::remark to testing sequences. + extrinsic = metadata + .pallet_by_name("System") + .unwrap() + .call_variant_by_name("remark") + .unwrap(); + types_formatted.clear(); + for field in &extrinsic.fields { + let type_info = registry.resolve(field.ty.id).unwrap(); + types_formatted.push(format_type(&type_info, ®istry)); + } + assert_eq!(types_formatted.len(), 1); + assert_eq!(types_formatted[0], "[u8]"); // remark + + // Extrinsic Scheduler::set_retry to test tuples. + extrinsic = metadata + .pallet_by_name("Scheduler") + .unwrap() + .call_variant_by_name("set_retry") + .unwrap(); + types_formatted.clear(); + for field in &extrinsic.fields { + let type_info = registry.resolve(field.ty.id).unwrap(); + types_formatted.push(format_type(&type_info, ®istry)); + } + assert_eq!(types_formatted.len(), 3); + assert_eq!(types_formatted[0], "(u32,u32)"); // task + assert_eq!(types_formatted[1], "u8"); // retries + assert_eq!(types_formatted[2], "u32"); // period + + Ok(()) + } +} From f62ec260cd624cdb6fbfc2265ffb777f1b98c1cb Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 28 Nov 2024 15:17:20 +0100 Subject: [PATCH 075/211] test: parse metadata unit tests --- crates/pop-common/src/metadata.rs | 1 - .../src/call/metadata/action.rs | 77 +++++++++++ .../pop-parachains/src/call/metadata/mod.rs | 123 ++++++++++++++++++ 3 files changed, 200 insertions(+), 1 deletion(-) diff --git a/crates/pop-common/src/metadata.rs b/crates/pop-common/src/metadata.rs index 7aae66132..82851c8f7 100644 --- a/crates/pop-common/src/metadata.rs +++ b/crates/pop-common/src/metadata.rs @@ -158,7 +158,6 @@ pub fn format_type(ty: &Type, registry: &PortableRegistry) -> Stri #[cfg(test)] mod tests { use super::*; - use anyhow::Result; use subxt::{OnlineClient, SubstrateConfig}; diff --git a/crates/pop-parachains/src/call/metadata/action.rs b/crates/pop-parachains/src/call/metadata/action.rs index ec089099d..73e152b9e 100644 --- a/crates/pop-parachains/src/call/metadata/action.rs +++ b/crates/pop-parachains/src/call/metadata/action.rs @@ -14,6 +14,7 @@ use strum_macros::{AsRefStr, Display, EnumMessage, EnumProperty, EnumString, Var EnumString, EnumProperty, Eq, + Hash, PartialEq, VariantArray, )] @@ -96,3 +97,79 @@ pub async fn supported_actions(pallets: &[Pallet]) -> Vec { } actions } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{parse_chain_metadata, set_up_api}; + use anyhow::Result; + use std::collections::HashMap; + + #[test] + fn action_descriptions_are_correct() { + let descriptions = HashMap::from([ + (Action::CreateAsset, "Create an Asset"), + (Action::MintAsset, "Mint an Asset"), + (Action::CreateCollection, "Create an NFT Collection"), + (Action::MintNFT, "Mint an NFT"), + (Action::PurchaseOnDemandCoretime, "Purchase on-demand coretime"), + (Action::Transfer, "Transfer Balance"), + ]); + + for action in Action::VARIANTS.iter() { + assert_eq!(&action.description(), descriptions.get(action).unwrap()); + } + } + + #[test] + fn pallet_names_are_correct() { + let pallets = HashMap::from([ + (Action::CreateAsset, "Assets"), + (Action::MintAsset, "Assets"), + (Action::CreateCollection, "Nfts"), + (Action::MintNFT, "Nfts"), + (Action::PurchaseOnDemandCoretime, "OnDemand"), + (Action::Transfer, "Balances"), + ]); + + for action in Action::VARIANTS.iter() { + assert_eq!(&action.pallet_name(), pallets.get(action).unwrap(),); + } + } + + #[test] + fn extrinsic_names_are_correct() { + let pallets = HashMap::from([ + (Action::CreateAsset, "create"), + (Action::MintAsset, "mint"), + (Action::CreateCollection, "create"), + (Action::MintNFT, "mint"), + (Action::PurchaseOnDemandCoretime, "place_order_allow_death"), + (Action::Transfer, "transfer_allow_death"), + ]); + + for action in Action::VARIANTS.iter() { + assert_eq!(&action.extrinsic_name(), pallets.get(action).unwrap(),); + } + } + + #[tokio::test] + async fn supported_actions_works() -> Result<()> { + // Test Pop Parachain. + let mut api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let mut actions = supported_actions(&parse_chain_metadata(&api).await?).await; + assert_eq!(actions.len(), 5); + assert_eq!(actions[0], Action::CreateAsset); + assert_eq!(actions[1], Action::MintAsset); + assert_eq!(actions[2], Action::CreateCollection); + assert_eq!(actions[3], Action::MintNFT); + assert_eq!(actions[4], Action::Transfer); + // Test Polkadot Relay Chain. + api = set_up_api("wss://polkadot-rpc.publicnode.com").await?; + actions = supported_actions(&parse_chain_metadata(&api).await?).await; + assert_eq!(actions.len(), 2); + assert_eq!(actions[0], Action::PurchaseOnDemandCoretime); + assert_eq!(actions[1], Action::Transfer); + Ok(()) + } +} diff --git a/crates/pop-parachains/src/call/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs index e7f863d0a..26890a7d1 100644 --- a/crates/pop-parachains/src/call/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -147,3 +147,126 @@ pub async fn parse_extrinsic_arguments(raw_params: Vec) -> Result Result<()> { + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let pallets = parse_chain_metadata(&api).await?; + // Test the first pallet is parsed correctly + let first_pallet = pallets.first().unwrap(); + assert_eq!(first_pallet.name, "System"); + assert_eq!(first_pallet.docs, ""); + assert_eq!(first_pallet.extrinsics.len(), 11); + let first_extrinsic = first_pallet.extrinsics.first().unwrap(); + assert_eq!(first_extrinsic.name, "remark"); + assert_eq!( + first_extrinsic.docs, + "Make some on-chain remark.Can be executed by every `origin`." + ); + assert!(first_extrinsic.is_supported); + assert_eq!(first_extrinsic.params.first().unwrap().name, "remark"); + assert_eq!(first_extrinsic.params.first().unwrap().type_name, "[u8]"); + assert_eq!(first_extrinsic.params.first().unwrap().sub_params.len(), 0); + assert!(!first_extrinsic.params.first().unwrap().is_optional); + assert!(!first_extrinsic.params.first().unwrap().is_tuple); + assert!(!first_extrinsic.params.first().unwrap().is_variant); + Ok(()) + } + + #[tokio::test] + async fn find_pallet_by_name_works() -> Result<()> { + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let pallets = parse_chain_metadata(&api).await?; + assert!(matches!( + find_pallet_by_name(&pallets, "WrongName").await, + Err(Error::PalletNotFound(pallet)) if pallet == "WrongName".to_string())); + let pallet = find_pallet_by_name(&pallets, "Balances").await?; + assert_eq!(pallet.name, "Balances"); + assert_eq!(pallet.extrinsics.len(), 9); + Ok(()) + } + + #[tokio::test] + async fn find_extrinsic_by_name_works() -> Result<()> { + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let pallets = parse_chain_metadata(&api).await?; + assert!(matches!( + find_extrinsic_by_name(&pallets, "WrongName", "wrong_extrinsic").await, + Err(Error::PalletNotFound(pallet)) if pallet == "WrongName".to_string())); + assert!(matches!( + find_extrinsic_by_name(&pallets, "Balances", "wrong_extrinsic").await, + Err(Error::ExtrinsicNotSupported(extrinsic)) if extrinsic == "wrong_extrinsic".to_string())); + let extrinsic = find_extrinsic_by_name(&pallets, "Balances", "force_transfer").await?; + assert_eq!(extrinsic.name, "force_transfer"); + assert_eq!(extrinsic.docs, "Exactly as `transfer_allow_death`, except the origin must be root and the source accountmay be specified."); + assert_eq!(extrinsic.is_supported, true); + assert_eq!(extrinsic.params.len(), 3); + Ok(()) + } + + #[tokio::test] + async fn parse_extrinsic_arguments_works() -> Result<()> { + // Values for testing from: https://docs.rs/scale-value/0.18.0/scale_value/stringify/fn.from_str.html + // and https://docs.rs/scale-value/0.18.0/scale_value/stringify/fn.from_str_custom.html + let args = [ + "1".to_string(), + "-1".to_string(), + "true".to_string(), + "'a'".to_string(), + "\"hi\"".to_string(), + "{ a: true, b: \"hello\" }".to_string(), + "MyVariant { a: true, b: \"hello\" }".to_string(), + "<0101>".to_string(), + "(1,2,0x030405)".to_string(), + r#"{ + name: "Alice", + address: 5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty + }"# + .to_string(), + ] + .to_vec(); + let addr: Vec<_> = + hex::decode("8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48") + .unwrap() + .into_iter() + .map(|b| Value::u128(b as u128)) + .collect(); + assert_eq!( + parse_extrinsic_arguments(args).await?, + [ + Value::u128(1), + Value::i128(-1), + Value::bool(true), + Value::char('a'), + Value::string("hi"), + Value::named_composite(vec![ + ("a", Value::bool(true)), + ("b", Value::string("hello")) + ]), + Value::named_variant( + "MyVariant", + vec![("a", Value::bool(true)), ("b", Value::string("hello"))] + ), + Value::bit_sequence(scale_bits::Bits::from_iter([false, true, false, true])), + Value::unnamed_composite(vec![ + Value::u128(1), + Value::u128(2), + Value::unnamed_composite(vec![Value::u128(3), Value::u128(4), Value::u128(5),]) + ]), + Value::named_composite(vec![ + ("name", Value::string("Alice")), + ("address", Value::unnamed_composite(addr)) + ]) + ] + ); + Ok(()) + } +} From ab9e37c4f71d7088949b58d52aa8dbf5f57d2637 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 28 Nov 2024 16:58:12 +0100 Subject: [PATCH 076/211] test: refactor and test processing parameters --- .../pop-parachains/src/call/metadata/mod.rs | 15 ++-- .../src/call/metadata/params.rs | 77 ++++++++++++++++--- crates/pop-parachains/src/errors.rs | 4 +- 3 files changed, 77 insertions(+), 19 deletions(-) diff --git a/crates/pop-parachains/src/call/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs index 26890a7d1..7d30bb9b3 100644 --- a/crates/pop-parachains/src/call/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -57,15 +57,15 @@ pub async fn parse_chain_metadata( let params = { let mut parsed_params = Vec::new(); for field in &variant.fields { - match params::field_to_param(api, &variant.name, field) { + match params::field_to_param(api, field) { Ok(param) => parsed_params.push(param), - Err(Error::ExtrinsicNotSupported(_)) => { - // Unsupported extrinsic due to complex types + Err(_) => { + // If an error occurs while parsing the values, mark the + // extrinsic as unsupported rather than error. is_supported = false; parsed_params.clear(); break; }, - Err(e) => return Err(e), } } parsed_params @@ -126,7 +126,7 @@ pub async fn find_extrinsic_by_name( if let Some(extrinsic) = pallet.extrinsics.iter().find(|&e| e.name == extrinsic_name) { return Ok(extrinsic.clone()); } else { - return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); + return Err(Error::ExtrinsicNotSupported); } } @@ -202,8 +202,9 @@ mod tests { find_extrinsic_by_name(&pallets, "WrongName", "wrong_extrinsic").await, Err(Error::PalletNotFound(pallet)) if pallet == "WrongName".to_string())); assert!(matches!( - find_extrinsic_by_name(&pallets, "Balances", "wrong_extrinsic").await, - Err(Error::ExtrinsicNotSupported(extrinsic)) if extrinsic == "wrong_extrinsic".to_string())); + find_extrinsic_by_name(&pallets, "Balances", "wrong_extrinsic").await, + Err(Error::ExtrinsicNotSupported) + )); let extrinsic = find_extrinsic_by_name(&pallets, "Balances", "force_transfer").await?; assert_eq!(extrinsic.name, "force_transfer"); assert_eq!(extrinsic.docs, "Exactly as `transfer_allow_death`, except the origin must be root and the source accountmay be specified."); diff --git a/crates/pop-parachains/src/call/metadata/params.rs b/crates/pop-parachains/src/call/metadata/params.rs index 9c98287ac..cec262768 100644 --- a/crates/pop-parachains/src/call/metadata/params.rs +++ b/crates/pop-parachains/src/call/metadata/params.rs @@ -29,13 +29,12 @@ pub struct Param { /// * `field`: A reference to a metadata field of the extrinsic. pub fn field_to_param( api: &OnlineClient, - extrinsic_name: &str, field: &Field, ) -> Result { let metadata: Metadata = api.metadata(); let registry = metadata.types(); let name = field.name.clone().unwrap_or("Unnamed".to_string()); //It can be unnamed field - type_to_param(extrinsic_name, name, registry, field.ty.id, &field.type_name) + type_to_param(name, registry, field.ty.id, &field.type_name) } /// Converts a type's metadata into a `Param` representation. @@ -46,7 +45,6 @@ pub fn field_to_param( /// * `type_id`: The ID of the type to be converted. /// * `type_name`: An optional descriptive name for the type. fn type_to_param( - extrinsic_name: &str, name: String, registry: &PortableRegistry, type_id: u32, @@ -55,7 +53,7 @@ fn type_to_param( let type_info = registry.resolve(type_id).ok_or(Error::MetadataParsingError(name.clone()))?; if let Some(last_segment) = type_info.path.segments.last() { if last_segment == "RuntimeCall" { - return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); + return Err(Error::ExtrinsicNotSupported); } } for param in &type_info.type_params { @@ -63,14 +61,13 @@ fn type_to_param( param.name == "Vec" || param.name == "Vec<::RuntimeCall>" { - return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); + return Err(Error::ExtrinsicNotSupported); } } if type_info.path.segments == ["Option"] { if let Some(sub_type_id) = type_info.type_params.get(0).and_then(|param| param.ty) { // Recursive for the sub parameters - let sub_param = - type_to_param(extrinsic_name, name.clone(), registry, sub_type_id.id, type_name)?; + let sub_param = type_to_param(name.clone(), registry, sub_type_id.id, type_name)?; return Ok(Param { name, type_name: sub_param.type_name, @@ -104,7 +101,6 @@ fn type_to_param( .map(|field| { // Recursive for the sub parameters of composite type. type_to_param( - extrinsic_name, field.name.clone().unwrap_or(name.clone()), registry, field.ty.id, @@ -133,7 +129,6 @@ fn type_to_param( .map(|field| { // Recursive for the sub parameters of variant type. type_to_param( - extrinsic_name, field.name.clone().unwrap_or(variant_param.name.clone()), registry, field.ty.id, @@ -168,7 +163,6 @@ fn type_to_param( .enumerate() .map(|(index, field_id)| { type_to_param( - extrinsic_name, format!("Index {} of the tuple {}", index.to_string(), name), registry, field_id.id, @@ -190,3 +184,66 @@ fn type_to_param( } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::set_up_api; + use anyhow::Result; + + #[tokio::test] + async fn field_to_param_works() -> Result<()> { + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let metadata = api.metadata(); + // Test a supported extrinsic + let extrinsic = metadata + .pallet_by_name("Balances") + .unwrap() + .call_variant_by_name("force_transfer") + .unwrap(); + let mut params = Vec::new(); + for field in &extrinsic.fields { + params.push(field_to_param(&api, field)?) + } + assert_eq!(params.len(), 3); + assert_eq!(params.first().unwrap().name, "source"); + assert_eq!(params.first().unwrap().type_name, "MultiAddress: Id(AccountId32 ([u8;32])), Index(Compact<()>), Raw([u8]), Address32([u8;32]), Address20([u8;20])"); + assert_eq!(params.first().unwrap().sub_params.len(), 5); + assert_eq!(params.first().unwrap().sub_params.first().unwrap().name, "Id"); + assert_eq!(params.first().unwrap().sub_params.first().unwrap().type_name, ""); + assert_eq!( + params + .first() + .unwrap() + .sub_params + .first() + .unwrap() + .sub_params + .first() + .unwrap() + .name, + "Id" + ); + assert_eq!( + params + .first() + .unwrap() + .sub_params + .first() + .unwrap() + .sub_params + .first() + .unwrap() + .type_name, + "AccountId32 ([u8;32])" + ); + // Test a extrinsic not supported + let extrinsic = + metadata.pallet_by_name("Sudo").unwrap().call_variant_by_name("sudo").unwrap(); + assert!(matches!( + field_to_param(&api, &extrinsic.fields.first().unwrap()), + Err(Error::ExtrinsicNotSupported) + )); + Ok(()) + } +} diff --git a/crates/pop-parachains/src/errors.rs b/crates/pop-parachains/src/errors.rs index 244b70aa2..9ca07a8ac 100644 --- a/crates/pop-parachains/src/errors.rs +++ b/crates/pop-parachains/src/errors.rs @@ -18,8 +18,8 @@ pub enum Error { CurrentDirAccess, #[error("Failed to parse the endowment value")] EndowmentError, - #[error("The extrinsic {0} is not supported")] - ExtrinsicNotSupported(String), + #[error("The extrinsic is not supported")] + ExtrinsicNotSupported, #[error("Failed decode a value")] FromHexError(#[from] hex::FromHexError), #[error("IO error: {0}")] From 18f0610961f0702660c2747efc057b0d8f3ddd9a Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 28 Nov 2024 17:18:54 +0100 Subject: [PATCH 077/211] test: comments and unit test in call functions --- crates/pop-parachains/src/call/mod.rs | 71 +++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs index fd55d1baa..a347fea22 100644 --- a/crates/pop-parachains/src/call/mod.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -10,11 +10,21 @@ use subxt::{ pub mod metadata; +/// Sets up an OnlineClient instance for connecting to a blockchain. +/// +/// # Arguments +/// * `url` - Endpoint of the node. pub async fn set_up_api(url: &str) -> Result, Error> { let api = OnlineClient::::from_url(url).await?; Ok(api) } +/// Constructs a dynamic extrinsic payload for a specified pallet and extrinsic. +/// +/// # Arguments +/// * `pallet_name` - The name of the pallet containing the extrinsic. +/// * `extrinsic_name` - The specific extrinsic name within the pallet. +/// * `args` - A vector of string arguments to be passed to the extrinsic. pub async fn construct_extrinsic( pallet_name: &str, extrinsic_name: &str, @@ -24,6 +34,12 @@ pub async fn construct_extrinsic( Ok(subxt::dynamic::tx(pallet_name, extrinsic_name, parsed_args)) } +/// Signs and submits a given extrinsic to the blockchain. +/// +/// # Arguments +/// * `api` - Reference to an `OnlineClient` connected to the chain. +/// * `tx` - The transaction to be signed and submitted. +/// * `suri` - The secret URI (e.g., mnemonic or private key) for signing the extrinsic. pub async fn sign_and_submit_extrinsic( api: OnlineClient, tx: DynamicPayload, @@ -41,6 +57,11 @@ pub async fn sign_and_submit_extrinsic( Ok(format!("{:?}", result.extrinsic_hash())) } +/// Encodes the call data for a given extrinsic into a hexadecimal string. +/// +/// # Arguments +/// * `api` - Reference to an `OnlineClient` connected to the chain. +/// * `tx` - The transaction whose call data will be encoded and returned. pub fn encode_call_data( api: &OnlineClient, tx: &DynamicPayload, @@ -48,3 +69,53 @@ pub fn encode_call_data( let call_data = tx.encode_call_data(&api.metadata())?; Ok(format!("0x{}", hex::encode(call_data))) } + +#[cfg(test)] +mod tests { + use super::*; + + use crate::set_up_api; + use anyhow::Result; + + #[tokio::test] + async fn set_up_api_works() -> Result<()> { + assert!(matches!(set_up_api("wss://wronguri.xyz").await, Err(Error::SubxtError(_)))); + set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + Ok(()) + } + + #[tokio::test] + async fn construct_extrinsic_works() -> Result<()> { + // Wrong parameters + assert!(matches!( + construct_extrinsic( + "Balances", + "transfer_allow_death", + vec!["Bob".to_string(), "100".to_string()], + ) + .await, + Err(Error::ParamProcessingError) + )); + // Valid parameters + let extrinsic = construct_extrinsic( + "Balances", + "transfer_allow_death", + vec![ + "Id(5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty)".to_string(), + "100".to_string(), + ], + ) + .await?; + assert_eq!(extrinsic.call_name(), "transfer_allow_death"); + assert_eq!(extrinsic.pallet_name(), "Balances"); + Ok(()) + } + + #[tokio::test] + async fn encode_call_data_works() -> Result<()> { + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let extrinsic = construct_extrinsic("System", "remark", vec!["0x11".to_string()]).await?; + assert_eq!(encode_call_data(&api, &extrinsic)?, "0x00000411"); + Ok(()) + } +} From 49454510642da4d0d03f1a8d0faa7336971ab8c0 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 29 Nov 2024 12:32:40 +0100 Subject: [PATCH 078/211] fix: clippy warnings --- crates/pop-cli/src/commands/call/contract.rs | 2 +- crates/pop-cli/src/commands/call/parachain.rs | 17 ++++----- .../pop-parachains/src/call/metadata/mod.rs | 4 +-- .../src/call/metadata/params.rs | 36 ++++++++----------- 4 files changed, 26 insertions(+), 33 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index d9e604b1e..d08908981 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -200,7 +200,7 @@ impl CallContractCommand { }; // Resolve contract address. - if let None = self.contract { + if self.contract.is_none() { // Prompt for contract address. let contract_address: String = cli .input("Paste the on-chain contract address:") diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 505798b2a..ed48c1b11 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -223,13 +223,14 @@ impl CallParachainCommand { prompt_to_repeat_call: bool, cli: &mut impl cli::traits::Cli, ) -> Result<()> { - if !self.skip_confirm && - !cli.confirm("Do you want to submit the extrinsic?") + if !self.skip_confirm + && !cli + .confirm("Do you want to submit the extrinsic?") .initial_value(true) .interact()? { display_message( - &format!("Extrinsic not submitted. Operation canceled by the user."), + "Extrinsic not submitted. Operation canceled by the user.", false, cli, )?; @@ -241,7 +242,7 @@ impl CallParachainCommand { .await .map_err(|err| anyhow!("{}", format!("{err:?}")))?; - spinner.stop(&format!("Extrinsic submitted with hash: {:?}", result)); + spinner.stop(format!("Extrinsic submitted with hash: {:?}", result)); // Prompt for any additional calls. if !prompt_to_repeat_call { @@ -286,7 +287,7 @@ async fn prompt_predefined_actions( cli: &mut impl cli::traits::Cli, ) -> Result> { let mut predefined_action = cli.select("What would you like to do?"); - for action in supported_actions(&pallets).await { + for action in supported_actions(pallets).await { predefined_action = predefined_action.item( Some(action.clone()), action.description(), @@ -385,13 +386,13 @@ fn prompt_for_composite_param( // sub_params: [Param { name: "Id", type_name: "[u8;32]", is_optional: false, sub_params: // [], is_variant: false }], is_variant: false } if param.sub_params.len() == 1 && param.name == param.sub_params[0].name { - field_values.push(format!("{}", field_value)); + field_values.push(field_value); } else { field_values.push(format!("{}: {}", field_arg.name, field_value)); } } if param.sub_params.len() == 1 && param.name == param.sub_params[0].name { - Ok(format!("{}", field_values.join(", "))) + Ok(field_values.join(", ").to_string()) } else { Ok(format!("{{{}}}", field_values.join(", "))) } @@ -404,7 +405,7 @@ fn prompt_for_tuple_param( param: &Param, ) -> Result { let mut tuple_values = Vec::new(); - for (_index, tuple_param) in param.sub_params.iter().enumerate() { + for tuple_param in param.sub_params.iter() { let tuple_value = prompt_for_param(api, cli, tuple_param)?; tuple_values.push(tuple_value); } diff --git a/crates/pop-parachains/src/call/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs index 7d30bb9b3..3d38f249e 100644 --- a/crates/pop-parachains/src/call/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -124,9 +124,9 @@ pub async fn find_extrinsic_by_name( ) -> Result { let pallet = find_pallet_by_name(pallets, pallet_name).await?; if let Some(extrinsic) = pallet.extrinsics.iter().find(|&e| e.name == extrinsic_name) { - return Ok(extrinsic.clone()); + Ok(extrinsic.clone()) } else { - return Err(Error::ExtrinsicNotSupported); + Err(Error::ExtrinsicNotSupported) } } diff --git a/crates/pop-parachains/src/call/metadata/params.rs b/crates/pop-parachains/src/call/metadata/params.rs index cec262768..073814cd0 100644 --- a/crates/pop-parachains/src/call/metadata/params.rs +++ b/crates/pop-parachains/src/call/metadata/params.rs @@ -34,7 +34,7 @@ pub fn field_to_param( let metadata: Metadata = api.metadata(); let registry = metadata.types(); let name = field.name.clone().unwrap_or("Unnamed".to_string()); //It can be unnamed field - type_to_param(name, registry, field.ty.id, &field.type_name) + type_to_param(name, registry, field.ty.id) } /// Converts a type's metadata into a `Param` representation. @@ -44,12 +44,7 @@ pub fn field_to_param( /// * `registry`: A reference to the `PortableRegistry` to resolve type dependencies. /// * `type_id`: The ID of the type to be converted. /// * `type_name`: An optional descriptive name for the type. -fn type_to_param( - name: String, - registry: &PortableRegistry, - type_id: u32, - type_name: &Option, -) -> Result { +fn type_to_param(name: String, registry: &PortableRegistry, type_id: u32) -> Result { let type_info = registry.resolve(type_id).ok_or(Error::MetadataParsingError(name.clone()))?; if let Some(last_segment) = type_info.path.segments.last() { if last_segment == "RuntimeCall" { @@ -57,25 +52,25 @@ fn type_to_param( } } for param in &type_info.type_params { - if param.name == "RuntimeCall" || - param.name == "Vec" || - param.name == "Vec<::RuntimeCall>" + if param.name == "RuntimeCall" + || param.name == "Vec" + || param.name == "Vec<::RuntimeCall>" { return Err(Error::ExtrinsicNotSupported); } } if type_info.path.segments == ["Option"] { - if let Some(sub_type_id) = type_info.type_params.get(0).and_then(|param| param.ty) { + if let Some(sub_type_id) = type_info.type_params.first().and_then(|param| param.ty) { // Recursive for the sub parameters - let sub_param = type_to_param(name.clone(), registry, sub_type_id.id, type_name)?; - return Ok(Param { + let sub_param = type_to_param(name.clone(), registry, sub_type_id.id)?; + Ok(Param { name, type_name: sub_param.type_name, sub_params: sub_param.sub_params, is_optional: true, is_tuple: false, is_variant: false, - }); + }) } else { Err(Error::MetadataParsingError(name)) } @@ -83,10 +78,10 @@ fn type_to_param( // Determine the formatted type name. let type_name = format_type(type_info, registry); match &type_info.type_def { - TypeDef::Primitive(_) | - TypeDef::Array(_) | - TypeDef::Sequence(_) | - TypeDef::Compact(_) => Ok(Param { + TypeDef::Primitive(_) + | TypeDef::Array(_) + | TypeDef::Sequence(_) + | TypeDef::Compact(_) => Ok(Param { name, type_name, sub_params: Vec::new(), @@ -104,7 +99,6 @@ fn type_to_param( field.name.clone().unwrap_or(name.clone()), registry, field.ty.id, - &field.type_name, ) }) .collect::, Error>>()?; @@ -132,7 +126,6 @@ fn type_to_param( field.name.clone().unwrap_or(variant_param.name.clone()), registry, field.ty.id, - &field.type_name, ) }) .collect::, Error>>()?; @@ -163,10 +156,9 @@ fn type_to_param( .enumerate() .map(|(index, field_id)| { type_to_param( - format!("Index {} of the tuple {}", index.to_string(), name), + format!("Index {index} of the tuple {name}"), registry, field_id.id, - &None, ) }) .collect::, Error>>()?; From a756bed533e56ed80dedae8aac562b9efd5b27a1 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 29 Nov 2024 13:06:27 +0100 Subject: [PATCH 079/211] chore: fmt --- crates/pop-cli/src/commands/call/parachain.rs | 5 ++--- crates/pop-parachains/src/call/metadata/params.rs | 14 +++++++------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index ed48c1b11..d6c99982b 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -223,9 +223,8 @@ impl CallParachainCommand { prompt_to_repeat_call: bool, cli: &mut impl cli::traits::Cli, ) -> Result<()> { - if !self.skip_confirm - && !cli - .confirm("Do you want to submit the extrinsic?") + if !self.skip_confirm && + !cli.confirm("Do you want to submit the extrinsic?") .initial_value(true) .interact()? { diff --git a/crates/pop-parachains/src/call/metadata/params.rs b/crates/pop-parachains/src/call/metadata/params.rs index 073814cd0..feb73039b 100644 --- a/crates/pop-parachains/src/call/metadata/params.rs +++ b/crates/pop-parachains/src/call/metadata/params.rs @@ -52,9 +52,9 @@ fn type_to_param(name: String, registry: &PortableRegistry, type_id: u32) -> Res } } for param in &type_info.type_params { - if param.name == "RuntimeCall" - || param.name == "Vec" - || param.name == "Vec<::RuntimeCall>" + if param.name == "RuntimeCall" || + param.name == "Vec" || + param.name == "Vec<::RuntimeCall>" { return Err(Error::ExtrinsicNotSupported); } @@ -78,10 +78,10 @@ fn type_to_param(name: String, registry: &PortableRegistry, type_id: u32) -> Res // Determine the formatted type name. let type_name = format_type(type_info, registry); match &type_info.type_def { - TypeDef::Primitive(_) - | TypeDef::Array(_) - | TypeDef::Sequence(_) - | TypeDef::Compact(_) => Ok(Param { + TypeDef::Primitive(_) | + TypeDef::Array(_) | + TypeDef::Sequence(_) | + TypeDef::Compact(_) => Ok(Param { name, type_name, sub_params: Vec::new(), From 7b03c9a31e38689305eff1febdb6034f08992883 Mon Sep 17 00:00:00 2001 From: Alex Bean Date: Fri, 29 Nov 2024 21:30:51 +0100 Subject: [PATCH 080/211] fix: solve conflicts and unit tests (#359) * test: call parachain ui unit test * test: pop-cli unit testing * test: pop-common unit tests * test: parse metadata unit tests * test: refactor and test processing parameters * test: comments and unit test in call functions * fix: clippy warnings * chore: fmt * fix: conflicts and unit tests * test: remove test and improve test --- crates/pop-cli/src/commands/call/contract.rs | 2 +- crates/pop-cli/src/commands/call/parachain.rs | 352 +++++++++++++++++- crates/pop-common/src/metadata.rs | 61 ++- .../src/call/metadata/action.rs | 87 +++++ .../pop-parachains/src/call/metadata/mod.rs | 136 ++++++- .../src/call/metadata/params.rs | 95 +++-- crates/pop-parachains/src/call/mod.rs | 71 ++++ crates/pop-parachains/src/errors.rs | 4 +- 8 files changed, 756 insertions(+), 52 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index d9e604b1e..d08908981 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -200,7 +200,7 @@ impl CallContractCommand { }; // Resolve contract address. - if let None = self.contract { + if self.contract.is_none() { // Prompt for contract address. let contract_address: String = cli .input("Paste the on-chain contract address:") diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index bfd64c706..4cf4f2a02 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -43,8 +43,6 @@ impl CallParachainCommand { /// Executes the command. pub(crate) async fn execute(mut self) -> Result<()> { let mut cli = cli::Cli; - cli.intro("Call a parachain")?; - // Configure the chain. let chain = self.configure_chain(&mut cli).await?; loop { @@ -53,7 +51,7 @@ impl CallParachainCommand { Ok(call) => call, Err(e) => { display_message(&e.to_string(), false, &mut cli)?; - break + break; }, }; // Display the configured call. @@ -63,10 +61,9 @@ impl CallParachainCommand { Ok(api) => api, Err(e) => { display_message(&e.to_string(), false, &mut cli)?; - break + break; }, }; - // TODO: If call_data, go directly here (?). // Send the extrinsic. if let Err(e) = call.send_extrinsic(&chain.api, tx, &mut cli).await { display_message(&e.to_string(), false, &mut cli)?; @@ -77,13 +74,14 @@ impl CallParachainCommand { .interact()? { display_message("Parachain calling complete.", true, &mut cli)?; - break + break; } } Ok(()) } async fn configure_chain(&self, cli: &mut impl Cli) -> Result { + cli.intro("Call a parachain")?; // Resolve url. let url = match self.clone().url { Some(url) => url, @@ -132,8 +130,9 @@ impl CallParachainCommand { // Resolve extrinsic. let extrinsic = match self.extrinsic { - Some(ref extrinsic_name) => - find_extrinsic_by_name(&chain.pallets, &pallet.name, extrinsic_name).await?, + Some(ref extrinsic_name) => { + find_extrinsic_by_name(&chain.pallets, &pallet.name, extrinsic_name).await? + }, None => { let mut prompt_extrinsic = cli.select("Select the extrinsic to call:"); for extrinsic in &pallet.extrinsics { @@ -151,7 +150,7 @@ impl CallParachainCommand { cli.outro_cancel( "The selected extrinsic is not supported yet. Please choose another one.", )?; - continue + continue; } // Resolve message arguments. @@ -169,8 +168,9 @@ impl CallParachainCommand { // Resolve who is signing the extrinsic. let suri = match self.clone().suri { Some(suri) => suri, - None => - cli.input("Signer of the extrinsic:").default_input(DEFAULT_URI).interact()?, + None => { + cli.input("Signer of the extrinsic:").default_input(DEFAULT_URI).interact()? + }, }; return Ok(CallParachain { @@ -238,15 +238,16 @@ impl CallParachain { tx: DynamicPayload, cli: &mut impl Cli, ) -> Result<()> { - if !self.skip_confirm && - !cli.confirm("Do you want to submit the extrinsic?") + if !self.skip_confirm + && !cli + .confirm("Do you want to submit the extrinsic?") .initial_value(true) .interact()? { display_message( &format!( - "Extrinsic {:?} was not submitted. Operation canceled by the user.", - self.extrinsic + "Extrinsic {} was not submitted. Operation canceled by the user.", + self.extrinsic.name ), false, cli, @@ -290,7 +291,7 @@ async fn prompt_predefined_actions( cli: &mut impl Cli, ) -> Result> { let mut predefined_action = cli.select("What would you like to do?"); - for action in supported_actions(&pallets).await { + for action in supported_actions(pallets).await { predefined_action = predefined_action.item( Some(action.clone()), action.description(), @@ -389,13 +390,13 @@ fn prompt_for_composite_param( // sub_params: [Param { name: "Id", type_name: "[u8;32]", is_optional: false, sub_params: // [], is_variant: false }], is_variant: false } if param.sub_params.len() == 1 && param.name == param.sub_params[0].name { - field_values.push(format!("{}", field_value)); + field_values.push(field_value); } else { field_values.push(format!("{}: {}", field_arg.name, field_value)); } } if param.sub_params.len() == 1 && param.name == param.sub_params[0].name { - Ok(format!("{}", field_values.join(", "))) + Ok(field_values.join(", ").to_string()) } else { Ok(format!("{{{}}}", field_values.join(", "))) } @@ -408,9 +409,322 @@ fn prompt_for_tuple_param( param: &Param, ) -> Result { let mut tuple_values = Vec::new(); - for (_index, tuple_param) in param.sub_params.iter().enumerate() { + for tuple_param in param.sub_params.iter() { let tuple_value = prompt_for_param(api, cli, tuple_param)?; tuple_values.push(tuple_value); } Ok(format!("({})", tuple_values.join(", "))) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::cli::MockCli; + use url::Url; + + #[tokio::test] + async fn configure_chain_works() -> Result<()> { + let call_config = CallParachainCommand { + pallet: None, + extrinsic: None, + args: vec![].to_vec(), + url: None, + suri: Some(DEFAULT_URI.to_string()), + skip_confirm: false, + }; + let mut cli = MockCli::new().expect_intro("Call a parachain").expect_input( + "Which chain would you like to interact with?", + "wss://rpc1.paseo.popnetwork.xyz".into(), + ); + let chain = call_config.configure_chain(&mut cli).await?; + assert_eq!(chain.url, Url::parse("wss://rpc1.paseo.popnetwork.xyz")?); + cli.verify() + } + + // This test only covers the interactive portion of the call parachain command, without actually + // submitting any extrinsic. + #[tokio::test] + async fn guide_user_to_call_parachain_works() -> Result<()> { + // Test all process specifying pallet, and see the prompted extrinsics. + let mut call_config = CallParachainCommand { + pallet: Some("System".to_string()), + extrinsic: None, + args: vec![].to_vec(), + url: None, + suri: None, + skip_confirm: false, + }; + + let mut cli = MockCli::new() + .expect_intro("Call a parachain") + .expect_input("Signer of the extrinsic:", "//Bob".into()) + .expect_input("Enter the value for the parameter: remark", "0x11".into()) + .expect_input("Which chain would you like to interact with?", "wss://rpc1.paseo.popnetwork.xyz".into()) + .expect_select::( + "Select the extrinsic to call:", + Some(true), + true, + Some( + [ + ("remark".to_string(), "Make some on-chain remark.Can be executed by every `origin`.".to_string()), + ("set_heap_pages".to_string(), "Set the number of pages in the WebAssembly environment's heap.".to_string()), + ("set_code".to_string(), "Set the new runtime code.".to_string()), + ("set_code_without_checks".to_string(), "Set the new runtime code without doing any checks of the given `code`.Note that runtime upgrades will not run if this is called with a not-increasing specversion!".to_string()), + ("set_storage".to_string(), "Set some items of storage.".to_string()), + ("kill_storage".to_string(), "Kill some items from storage.".to_string()), + ("kill_prefix".to_string(), "Kill all storage items with a key that starts with the given prefix.**NOTE:** We rely on the Root origin to provide us the number of subkeys underthe prefix we are removing to accurately calculate the weight of this function.".to_string()), + ("remark_with_event".to_string(), "Make some on-chain remark and emit event.".to_string()), + ("authorize_upgrade".to_string(), "Authorize an upgrade to a given `code_hash` for the runtime. The runtime can be suppliedlater.This call requires Root origin.".to_string()), + ("authorize_upgrade_without_checks".to_string(), "Authorize an upgrade to a given `code_hash` for the runtime. The runtime can be suppliedlater.WARNING: This authorizes an upgrade that will take place without any safety checks, forexample that the spec name remains the same and that the version number increases. Notrecommended for normal use. Use `authorize_upgrade` instead.This call requires Root origin.".to_string()), + ("apply_authorized_upgrade".to_string(), "Provide the preimage (runtime binary) `code` for an upgrade that has been authorized.If the authorization required a version check, this call will ensure the spec nameremains unchanged and that the spec version has increased.Depending on the runtime's `OnSetCode` configuration, this function may directly applythe new `code` in the same block or attempt to schedule the upgrade.All origins are allowed.".to_string()), + ] + .to_vec(), + ), + 0, // "remark" extrinsic + ); + + let chain = call_config.configure_chain(&mut cli).await?; + assert_eq!(chain.url, Url::parse("wss://rpc1.paseo.popnetwork.xyz")?); + + let call_parachain = call_config.configure_call(&chain, &mut cli).await?; + assert_eq!(call_parachain.pallet.name, "System"); + assert_eq!(call_parachain.extrinsic.name, "remark"); + assert_eq!(call_parachain.args, ["0x11".to_string()].to_vec()); + assert_eq!(call_parachain.suri, "//Bob"); + assert_eq!(call_parachain.display(&chain), "pop call parachain --pallet System --extrinsic remark --args \"0x11\" --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Bob"); + cli.verify() + } + + // This test only covers the interactive portion of the call parachain command selecting one of + // the predefined actions, without actually submitting any extrinsic. + #[tokio::test] + async fn guide_user_to_configure_predefined_action_works() -> Result<()> { + let mut call_config = CallParachainCommand { + pallet: None, + extrinsic: None, + args: vec![].to_vec(), + url: None, + suri: None, + skip_confirm: false, + }; + + let mut cli = MockCli::new() + .expect_intro("Call a parachain") + .expect_input("Signer of the extrinsic:", "//Bob".into()) + .expect_input("Enter the value for the parameter: para_id", "2000".into()) + .expect_input("Enter the value for the parameter: max_amount", "10000".into()) + .expect_input( + "Which chain would you like to interact with?", + "wss://polkadot-rpc.publicnode.com".into(), + ) + .expect_select::( + "What would you like to do?", + Some(true), + true, + Some( + [ + ("Transfer Balance".to_string(), "Balances".to_string()), + ("Purchase on-demand coretime".to_string(), "OnDemand".to_string()), + ("Reserve para id".to_string(), "Registrar".to_string()), + ( + "Register para id with genesis state and code".to_string(), + "Registrar".to_string(), + ), + ("All".to_string(), "Explore all pallets and extrinsics".to_string()), + ] + .to_vec(), + ), + 1, // "Purchase on-demand coretime" action + ); + + let chain = call_config.configure_chain(&mut cli).await?; + assert_eq!(chain.url, Url::parse("wss://polkadot-rpc.publicnode.com")?); + + let call_parachain = call_config.configure_call(&chain, &mut cli).await?; + + assert_eq!(call_parachain.pallet.name, "OnDemand"); + assert_eq!(call_parachain.extrinsic.name, "place_order_allow_death"); + assert_eq!(call_parachain.args, ["10000".to_string(), "2000".to_string()].to_vec()); + assert_eq!(call_parachain.suri, "//Bob"); + assert_eq!(call_parachain.display(&chain), "pop call parachain --pallet OnDemand --extrinsic place_order_allow_death --args \"10000\" \"2000\" --url wss://polkadot-rpc.publicnode.com/ --suri //Bob"); + cli.verify() + } + + #[tokio::test] + async fn prepare_extrinsic_works() -> Result<()> { + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let mut call_config = CallParachain { + pallet: Pallet { + name: "WrongName".to_string(), + docs: "".to_string(), + extrinsics: vec![], + }, + extrinsic: Extrinsic { + name: "WrongName".to_string(), + docs: "".to_string(), + is_supported: false, + params: vec![], + }, + args: vec!["0x11".to_string()].to_vec(), + suri: DEFAULT_URI.to_string(), + skip_confirm: false, + }; + let mut cli = MockCli::new(); + // Error, wrong name of the pallet. + assert!( + matches!(call_config.prepare_extrinsic(&api, &mut cli).await, Err(message) if message.to_string().contains("Metadata Error: Pallet with name WrongName not found")) + ); + let pallets = parse_chain_metadata(&api).await?; + call_config.pallet = find_pallet_by_name(&pallets, "System").await?; + // Error, wrong name of the extrinsic. + assert!( + matches!(call_config.prepare_extrinsic(&api, &mut cli).await, Err(message) if message.to_string().contains("Metadata Error: Call with name WrongName not found")) + ); + // Success, extrinsic and pallet specified. + cli = MockCli::new().expect_info("Encoded call data: 0x00000411"); + call_config.extrinsic = find_extrinsic_by_name(&pallets, "System", "remark").await?; + let tx = call_config.prepare_extrinsic(&api, &mut cli).await?; + assert_eq!(tx.call_name(), "remark"); + assert_eq!(tx.pallet_name(), "System"); + + cli.verify() + } + + #[tokio::test] + async fn user_cancel_send_extrinsic_works() -> Result<()> { + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let pallets = parse_chain_metadata(&api).await?; + let mut call_config = CallParachain { + pallet: find_pallet_by_name(&pallets, "System").await?, + extrinsic: find_extrinsic_by_name(&pallets, "System", "remark").await?, + args: vec!["0x11".to_string()].to_vec(), + suri: DEFAULT_URI.to_string(), + skip_confirm: false, + }; + let mut cli = MockCli::new() + .expect_confirm("Do you want to submit the extrinsic?", false) + .expect_outro_cancel( + "Extrinsic remark was not submitted. Operation canceled by the user.", + ); + let tx = call_config.prepare_extrinsic(&api, &mut cli).await?; + call_config.send_extrinsic(&api, tx, &mut cli).await?; + + cli.verify() + } + + #[test] + fn display_message_works() -> Result<()> { + let mut cli = MockCli::new().expect_outro(&"Call completed successfully!"); + display_message("Call completed successfully!", true, &mut cli)?; + cli.verify()?; + let mut cli = MockCli::new().expect_outro_cancel("Call failed."); + display_message("Call failed.", false, &mut cli)?; + cli.verify() + } + + #[tokio::test] + async fn prompt_predefined_actions_works() -> Result<()> { + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let pallets = parse_chain_metadata(&api).await?; + let mut cli = MockCli::new().expect_select::( + "What would you like to do?", + Some(true), + true, + Some( + [ + ("Transfer Balance".to_string(), "Balances".to_string()), + ("Create an Asset".to_string(), "Assets".to_string()), + ("Mint an Asset".to_string(), "Assets".to_string()), + ("Create an NFT Collection".to_string(), "Nfts".to_string()), + ("Mint an NFT".to_string(), "Nfts".to_string()), + ("All".to_string(), "Explore all pallets and extrinsics".to_string()), + ] + .to_vec(), + ), + 2, // "Mint an Asset" action + ); + let action = prompt_predefined_actions(&pallets, &mut cli).await?; + assert_eq!(action, Some(Action::MintAsset)); + cli.verify() + } + + #[tokio::test] + async fn prompt_for_param_works() -> Result<()> { + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let pallets = parse_chain_metadata(&api).await?; + // Using NFT mint extrinsic to test the majority of subfunctions + let extrinsic = find_extrinsic_by_name(&pallets, "Nfts", "mint").await?; + let mut cli = MockCli::new() + .expect_input("Enter the value for the parameter: mint_price", "1000".into()) + .expect_input( + "Enter the value for the parameter: Id", + "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty".into(), + ) + .expect_input("Enter the value for the parameter: item", "0".into()) + .expect_input("Enter the value for the parameter: collection", "0".into()) + .expect_select::( + "Select the value for the parameter: mint_to", + Some(true), + true, + Some( + [ + ("Id".to_string(), "".to_string()), + ("Index".to_string(), "".to_string()), + ("Raw".to_string(), "".to_string()), + ("Address32".to_string(), "".to_string()), + ("Address20".to_string(), "".to_string()), + ] + .to_vec(), + ), + 0, // "Id" action + ) + .expect_confirm( + "Do you want to provide a value for the optional parameter: mint_price?", + true, + ) + .expect_confirm( + "Do you want to provide a value for the optional parameter: owned_item?", + false, + ) + .expect_confirm( + "Do you want to provide a value for the optional parameter: witness_data?", + true, + ); + // Test all the extrinsic params + let mut params: Vec = Vec::new(); + for param in extrinsic.params { + params.push(prompt_for_param(&api, &mut cli, ¶m)?); + } + assert_eq!(params.len(), 4); + assert_eq!(params[0], "0".to_string()); // collection: test primitive + assert_eq!(params[1], "0".to_string()); // item: test primitive + assert_eq!(params[2], "Id(5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty)".to_string()); // mint_to: test variant + assert_eq!(params[3], "Some({owned_item: None(), mint_price: Some(1000)})".to_string()); // witness_data: test composite + cli.verify()?; + + // Using Scheduler set_retry extrinsic to test the tuple params + let extrinsic = find_extrinsic_by_name(&pallets, "Scheduler", "set_retry").await?; + let mut cli = MockCli::new() + .expect_input("Enter the value for the parameter: period", "0".into()) + .expect_input("Enter the value for the parameter: retries", "0".into()) + .expect_input( + "Enter the value for the parameter: Index 1 of the tuple task", + "0".into(), + ) + .expect_input( + "Enter the value for the parameter: Index 0 of the tuple task", + "0".into(), + ); + + // Test all the extrinsic params + let mut params: Vec = Vec::new(); + for param in extrinsic.params { + params.push(prompt_for_param(&api, &mut cli, ¶m)?); + } + assert_eq!(params.len(), 3); + assert_eq!(params[0], "(0, 0)".to_string()); // task: test tuples + assert_eq!(params[1], "0".to_string()); // retries: test primitive + assert_eq!(params[2], "0".to_string()); // period: test primitive + cli.verify() + } +} diff --git a/crates/pop-common/src/metadata.rs b/crates/pop-common/src/metadata.rs index 4ea58d806..82851c8f7 100644 --- a/crates/pop-common/src/metadata.rs +++ b/crates/pop-common/src/metadata.rs @@ -30,7 +30,6 @@ pub fn format_type(ty: &Type, registry: &PortableRegistry) -> Stri .collect(); name = format!("{name}<{}>", params.join(",")); } - name = format!( "{name}{}", match &ty.type_def { @@ -155,3 +154,63 @@ pub fn format_type(ty: &Type, registry: &PortableRegistry) -> Stri name } + +#[cfg(test)] +mod tests { + use super::*; + use anyhow::Result; + use subxt::{OnlineClient, SubstrateConfig}; + + #[tokio::test] + async fn format_type_works() -> Result<()> { + let api = + OnlineClient::::from_url("wss://rpc1.paseo.popnetwork.xyz").await?; + let metadata = api.metadata(); + let registry = metadata.types(); + // Extrinsic Nfts::mint to test the majority of expresions. + let mut extrinsic = + metadata.pallet_by_name("Nfts").unwrap().call_variant_by_name("mint").unwrap(); + let mut types_formatted = Vec::new(); + for field in &extrinsic.fields { + let type_info = registry.resolve(field.ty.id).unwrap(); + types_formatted.push(format_type(&type_info, ®istry)); + } + assert_eq!(types_formatted.len(), 4); + assert_eq!(types_formatted[0], "u32"); // collection + assert_eq!(types_formatted[1], "u32"); // item + assert_eq!(types_formatted[2], "MultiAddress: Id(AccountId32 ([u8;32])), Index(Compact<()>), Raw([u8]), Address32([u8;32]), Address20([u8;20])"); // mint_to + assert_eq!(types_formatted[3], "Option { owned_item: Option, mint_price: Option }>: None, Some(MintWitness { owned_item: Option, mint_price: Option })"); // witness_data + + // Extrinsic Sytem::remark to testing sequences. + extrinsic = metadata + .pallet_by_name("System") + .unwrap() + .call_variant_by_name("remark") + .unwrap(); + types_formatted.clear(); + for field in &extrinsic.fields { + let type_info = registry.resolve(field.ty.id).unwrap(); + types_formatted.push(format_type(&type_info, ®istry)); + } + assert_eq!(types_formatted.len(), 1); + assert_eq!(types_formatted[0], "[u8]"); // remark + + // Extrinsic Scheduler::set_retry to test tuples. + extrinsic = metadata + .pallet_by_name("Scheduler") + .unwrap() + .call_variant_by_name("set_retry") + .unwrap(); + types_formatted.clear(); + for field in &extrinsic.fields { + let type_info = registry.resolve(field.ty.id).unwrap(); + types_formatted.push(format_type(&type_info, ®istry)); + } + assert_eq!(types_formatted.len(), 3); + assert_eq!(types_formatted[0], "(u32,u32)"); // task + assert_eq!(types_formatted[1], "u8"); // retries + assert_eq!(types_formatted[2], "u32"); // period + + Ok(()) + } +} diff --git a/crates/pop-parachains/src/call/metadata/action.rs b/crates/pop-parachains/src/call/metadata/action.rs index 40f64f54a..63d49ee84 100644 --- a/crates/pop-parachains/src/call/metadata/action.rs +++ b/crates/pop-parachains/src/call/metadata/action.rs @@ -14,6 +14,7 @@ use strum_macros::{AsRefStr, Display, EnumMessage, EnumProperty, EnumString, Var EnumString, EnumProperty, Eq, + Hash, PartialEq, VariantArray, )] @@ -110,3 +111,89 @@ pub async fn supported_actions(pallets: &[Pallet]) -> Vec { } actions } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{parse_chain_metadata, set_up_api}; + use anyhow::Result; + use std::collections::HashMap; + + #[test] + fn action_descriptions_are_correct() { + let descriptions = HashMap::from([ + (Action::CreateAsset, "Create an Asset"), + (Action::MintAsset, "Mint an Asset"), + (Action::CreateCollection, "Create an NFT Collection"), + (Action::MintNFT, "Mint an NFT"), + (Action::PurchaseOnDemandCoretime, "Purchase on-demand coretime"), + (Action::Transfer, "Transfer Balance"), + (Action::Register, "Register para id with genesis state and code"), + (Action::Reserve, "Reserve para id"), + ]); + + for action in Action::VARIANTS.iter() { + assert_eq!(&action.description(), descriptions.get(action).unwrap()); + } + } + + #[test] + fn pallet_names_are_correct() { + let pallets = HashMap::from([ + (Action::CreateAsset, "Assets"), + (Action::MintAsset, "Assets"), + (Action::CreateCollection, "Nfts"), + (Action::MintNFT, "Nfts"), + (Action::PurchaseOnDemandCoretime, "OnDemand"), + (Action::Transfer, "Balances"), + (Action::Register, "Registrar"), + (Action::Reserve, "Registrar"), + ]); + + for action in Action::VARIANTS.iter() { + assert_eq!(&action.pallet_name(), pallets.get(action).unwrap(),); + } + } + + #[test] + fn extrinsic_names_are_correct() { + let pallets = HashMap::from([ + (Action::CreateAsset, "create"), + (Action::MintAsset, "mint"), + (Action::CreateCollection, "create"), + (Action::MintNFT, "mint"), + (Action::PurchaseOnDemandCoretime, "place_order_allow_death"), + (Action::Transfer, "transfer_allow_death"), + (Action::Register, "register"), + (Action::Reserve, "reserve"), + ]); + + for action in Action::VARIANTS.iter() { + assert_eq!(&action.extrinsic_name(), pallets.get(action).unwrap(),); + } + } + + #[tokio::test] + async fn supported_actions_works() -> Result<()> { + // Test Pop Parachain. + let mut api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let mut actions = supported_actions(&parse_chain_metadata(&api).await?).await; + assert_eq!(actions.len(), 5); + assert_eq!(actions[0], Action::Transfer); + assert_eq!(actions[1], Action::CreateAsset); + assert_eq!(actions[2], Action::MintAsset); + assert_eq!(actions[3], Action::CreateCollection); + assert_eq!(actions[4], Action::MintNFT); + + // Test Polkadot Relay Chain. + api = set_up_api("wss://polkadot-rpc.publicnode.com").await?; + actions = supported_actions(&parse_chain_metadata(&api).await?).await; + assert_eq!(actions.len(), 4); + assert_eq!(actions[0], Action::Transfer); + assert_eq!(actions[1], Action::PurchaseOnDemandCoretime); + assert_eq!(actions[2], Action::Reserve); + assert_eq!(actions[3], Action::Register); + + Ok(()) + } +} diff --git a/crates/pop-parachains/src/call/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs index 6c68d8376..2d6d6b0eb 100644 --- a/crates/pop-parachains/src/call/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -70,15 +70,15 @@ pub async fn parse_chain_metadata( let params = { let mut parsed_params = Vec::new(); for field in &variant.fields { - match params::field_to_param(api, &variant.name, field) { + match params::field_to_param(api, field) { Ok(param) => parsed_params.push(param), - Err(Error::ExtrinsicNotSupported(_)) => { - // Unsupported extrinsic due to complex types + Err(_) => { + // If an error occurs while parsing the values, mark the + // extrinsic as unsupported rather than error. is_supported = false; parsed_params.clear(); break; }, - Err(e) => return Err(e), } } parsed_params @@ -137,9 +137,9 @@ pub async fn find_extrinsic_by_name( ) -> Result { let pallet = find_pallet_by_name(pallets, pallet_name).await?; if let Some(extrinsic) = pallet.extrinsics.iter().find(|&e| e.name == extrinsic_name) { - return Ok(extrinsic.clone()); + Ok(extrinsic.clone()) } else { - return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); + Err(Error::ExtrinsicNotSupported) } } @@ -160,3 +160,127 @@ pub async fn parse_extrinsic_arguments(raw_params: Vec) -> Result Result<()> { + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let pallets = parse_chain_metadata(&api).await?; + // Test the first pallet is parsed correctly + let first_pallet = pallets.first().unwrap(); + assert_eq!(first_pallet.name, "System"); + assert_eq!(first_pallet.docs, ""); + assert_eq!(first_pallet.extrinsics.len(), 11); + let first_extrinsic = first_pallet.extrinsics.first().unwrap(); + assert_eq!(first_extrinsic.name, "remark"); + assert_eq!( + first_extrinsic.docs, + "Make some on-chain remark.Can be executed by every `origin`." + ); + assert!(first_extrinsic.is_supported); + assert_eq!(first_extrinsic.params.first().unwrap().name, "remark"); + assert_eq!(first_extrinsic.params.first().unwrap().type_name, "[u8]"); + assert_eq!(first_extrinsic.params.first().unwrap().sub_params.len(), 0); + assert!(!first_extrinsic.params.first().unwrap().is_optional); + assert!(!first_extrinsic.params.first().unwrap().is_tuple); + assert!(!first_extrinsic.params.first().unwrap().is_variant); + Ok(()) + } + + #[tokio::test] + async fn find_pallet_by_name_works() -> Result<()> { + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let pallets = parse_chain_metadata(&api).await?; + assert!(matches!( + find_pallet_by_name(&pallets, "WrongName").await, + Err(Error::PalletNotFound(pallet)) if pallet == "WrongName".to_string())); + let pallet = find_pallet_by_name(&pallets, "Balances").await?; + assert_eq!(pallet.name, "Balances"); + assert_eq!(pallet.extrinsics.len(), 9); + Ok(()) + } + + #[tokio::test] + async fn find_extrinsic_by_name_works() -> Result<()> { + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let pallets = parse_chain_metadata(&api).await?; + assert!(matches!( + find_extrinsic_by_name(&pallets, "WrongName", "wrong_extrinsic").await, + Err(Error::PalletNotFound(pallet)) if pallet == "WrongName".to_string())); + assert!(matches!( + find_extrinsic_by_name(&pallets, "Balances", "wrong_extrinsic").await, + Err(Error::ExtrinsicNotSupported) + )); + let extrinsic = find_extrinsic_by_name(&pallets, "Balances", "force_transfer").await?; + assert_eq!(extrinsic.name, "force_transfer"); + assert_eq!(extrinsic.docs, "Exactly as `transfer_allow_death`, except the origin must be root and the source accountmay be specified."); + assert_eq!(extrinsic.is_supported, true); + assert_eq!(extrinsic.params.len(), 3); + Ok(()) + } + + #[tokio::test] + async fn parse_extrinsic_arguments_works() -> Result<()> { + // Values for testing from: https://docs.rs/scale-value/0.18.0/scale_value/stringify/fn.from_str.html + // and https://docs.rs/scale-value/0.18.0/scale_value/stringify/fn.from_str_custom.html + let args = [ + "1".to_string(), + "-1".to_string(), + "true".to_string(), + "'a'".to_string(), + "\"hi\"".to_string(), + "{ a: true, b: \"hello\" }".to_string(), + "MyVariant { a: true, b: \"hello\" }".to_string(), + "<0101>".to_string(), + "(1,2,0x030405)".to_string(), + r#"{ + name: "Alice", + address: 5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty + }"# + .to_string(), + ] + .to_vec(); + let addr: Vec<_> = + hex::decode("8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48") + .unwrap() + .into_iter() + .map(|b| Value::u128(b as u128)) + .collect(); + assert_eq!( + parse_extrinsic_arguments(args).await?, + [ + Value::u128(1), + Value::i128(-1), + Value::bool(true), + Value::char('a'), + Value::string("hi"), + Value::named_composite(vec![ + ("a", Value::bool(true)), + ("b", Value::string("hello")) + ]), + Value::named_variant( + "MyVariant", + vec![("a", Value::bool(true)), ("b", Value::string("hello"))] + ), + Value::bit_sequence(scale_bits::Bits::from_iter([false, true, false, true])), + Value::unnamed_composite(vec![ + Value::u128(1), + Value::u128(2), + Value::unnamed_composite(vec![Value::u128(3), Value::u128(4), Value::u128(5),]) + ]), + Value::named_composite(vec![ + ("name", Value::string("Alice")), + ("address", Value::unnamed_composite(addr)) + ]) + ] + ); + Ok(()) + } +} diff --git a/crates/pop-parachains/src/call/metadata/params.rs b/crates/pop-parachains/src/call/metadata/params.rs index 9c98287ac..feb73039b 100644 --- a/crates/pop-parachains/src/call/metadata/params.rs +++ b/crates/pop-parachains/src/call/metadata/params.rs @@ -29,13 +29,12 @@ pub struct Param { /// * `field`: A reference to a metadata field of the extrinsic. pub fn field_to_param( api: &OnlineClient, - extrinsic_name: &str, field: &Field, ) -> Result { let metadata: Metadata = api.metadata(); let registry = metadata.types(); let name = field.name.clone().unwrap_or("Unnamed".to_string()); //It can be unnamed field - type_to_param(extrinsic_name, name, registry, field.ty.id, &field.type_name) + type_to_param(name, registry, field.ty.id) } /// Converts a type's metadata into a `Param` representation. @@ -45,17 +44,11 @@ pub fn field_to_param( /// * `registry`: A reference to the `PortableRegistry` to resolve type dependencies. /// * `type_id`: The ID of the type to be converted. /// * `type_name`: An optional descriptive name for the type. -fn type_to_param( - extrinsic_name: &str, - name: String, - registry: &PortableRegistry, - type_id: u32, - type_name: &Option, -) -> Result { +fn type_to_param(name: String, registry: &PortableRegistry, type_id: u32) -> Result { let type_info = registry.resolve(type_id).ok_or(Error::MetadataParsingError(name.clone()))?; if let Some(last_segment) = type_info.path.segments.last() { if last_segment == "RuntimeCall" { - return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); + return Err(Error::ExtrinsicNotSupported); } } for param in &type_info.type_params { @@ -63,22 +56,21 @@ fn type_to_param( param.name == "Vec" || param.name == "Vec<::RuntimeCall>" { - return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); + return Err(Error::ExtrinsicNotSupported); } } if type_info.path.segments == ["Option"] { - if let Some(sub_type_id) = type_info.type_params.get(0).and_then(|param| param.ty) { + if let Some(sub_type_id) = type_info.type_params.first().and_then(|param| param.ty) { // Recursive for the sub parameters - let sub_param = - type_to_param(extrinsic_name, name.clone(), registry, sub_type_id.id, type_name)?; - return Ok(Param { + let sub_param = type_to_param(name.clone(), registry, sub_type_id.id)?; + Ok(Param { name, type_name: sub_param.type_name, sub_params: sub_param.sub_params, is_optional: true, is_tuple: false, is_variant: false, - }); + }) } else { Err(Error::MetadataParsingError(name)) } @@ -104,11 +96,9 @@ fn type_to_param( .map(|field| { // Recursive for the sub parameters of composite type. type_to_param( - extrinsic_name, field.name.clone().unwrap_or(name.clone()), registry, field.ty.id, - &field.type_name, ) }) .collect::, Error>>()?; @@ -133,11 +123,9 @@ fn type_to_param( .map(|field| { // Recursive for the sub parameters of variant type. type_to_param( - extrinsic_name, field.name.clone().unwrap_or(variant_param.name.clone()), registry, field.ty.id, - &field.type_name, ) }) .collect::, Error>>()?; @@ -168,11 +156,9 @@ fn type_to_param( .enumerate() .map(|(index, field_id)| { type_to_param( - extrinsic_name, - format!("Index {} of the tuple {}", index.to_string(), name), + format!("Index {index} of the tuple {name}"), registry, field_id.id, - &None, ) }) .collect::, Error>>()?; @@ -190,3 +176,66 @@ fn type_to_param( } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::set_up_api; + use anyhow::Result; + + #[tokio::test] + async fn field_to_param_works() -> Result<()> { + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let metadata = api.metadata(); + // Test a supported extrinsic + let extrinsic = metadata + .pallet_by_name("Balances") + .unwrap() + .call_variant_by_name("force_transfer") + .unwrap(); + let mut params = Vec::new(); + for field in &extrinsic.fields { + params.push(field_to_param(&api, field)?) + } + assert_eq!(params.len(), 3); + assert_eq!(params.first().unwrap().name, "source"); + assert_eq!(params.first().unwrap().type_name, "MultiAddress: Id(AccountId32 ([u8;32])), Index(Compact<()>), Raw([u8]), Address32([u8;32]), Address20([u8;20])"); + assert_eq!(params.first().unwrap().sub_params.len(), 5); + assert_eq!(params.first().unwrap().sub_params.first().unwrap().name, "Id"); + assert_eq!(params.first().unwrap().sub_params.first().unwrap().type_name, ""); + assert_eq!( + params + .first() + .unwrap() + .sub_params + .first() + .unwrap() + .sub_params + .first() + .unwrap() + .name, + "Id" + ); + assert_eq!( + params + .first() + .unwrap() + .sub_params + .first() + .unwrap() + .sub_params + .first() + .unwrap() + .type_name, + "AccountId32 ([u8;32])" + ); + // Test a extrinsic not supported + let extrinsic = + metadata.pallet_by_name("Sudo").unwrap().call_variant_by_name("sudo").unwrap(); + assert!(matches!( + field_to_param(&api, &extrinsic.fields.first().unwrap()), + Err(Error::ExtrinsicNotSupported) + )); + Ok(()) + } +} diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs index 8fd3c6414..89acd8861 100644 --- a/crates/pop-parachains/src/call/mod.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -10,11 +10,21 @@ use subxt::{ pub mod metadata; +/// Sets up an OnlineClient instance for connecting to a blockchain. +/// +/// # Arguments +/// * `url` - Endpoint of the node. pub async fn set_up_api(url: &str) -> Result, Error> { let api = OnlineClient::::from_url(url).await?; Ok(api) } +/// Constructs a dynamic extrinsic payload for a specified pallet and extrinsic. +/// +/// # Arguments +/// * `pallet_name` - The name of the pallet containing the extrinsic. +/// * `extrinsic_name` - The specific extrinsic name within the pallet. +/// * `args` - A vector of string arguments to be passed to the extrinsic. pub async fn construct_extrinsic( pallet_name: &str, extrinsic_name: &str, @@ -24,6 +34,12 @@ pub async fn construct_extrinsic( Ok(subxt::dynamic::tx(pallet_name, extrinsic_name, parsed_args)) } +/// Signs and submits a given extrinsic to the blockchain. +/// +/// # Arguments +/// * `api` - Reference to an `OnlineClient` connected to the chain. +/// * `tx` - The transaction to be signed and submitted. +/// * `suri` - The secret URI (e.g., mnemonic or private key) for signing the extrinsic. pub async fn sign_and_submit_extrinsic( api: OnlineClient, tx: DynamicPayload, @@ -39,6 +55,11 @@ pub async fn sign_and_submit_extrinsic( Ok(format!("{:?}", result.extrinsic_hash())) } +/// Encodes the call data for a given extrinsic into a hexadecimal string. +/// +/// # Arguments +/// * `api` - Reference to an `OnlineClient` connected to the chain. +/// * `tx` - The transaction whose call data will be encoded and returned. pub fn encode_call_data( api: &OnlineClient, tx: &DynamicPayload, @@ -46,3 +67,53 @@ pub fn encode_call_data( let call_data = tx.encode_call_data(&api.metadata())?; Ok(format!("0x{}", hex::encode(call_data))) } + +#[cfg(test)] +mod tests { + use super::*; + + use crate::set_up_api; + use anyhow::Result; + + #[tokio::test] + async fn set_up_api_works() -> Result<()> { + assert!(matches!(set_up_api("wss://wronguri.xyz").await, Err(Error::SubxtError(_)))); + set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + Ok(()) + } + + #[tokio::test] + async fn construct_extrinsic_works() -> Result<()> { + // Wrong parameters + assert!(matches!( + construct_extrinsic( + "Balances", + "transfer_allow_death", + vec!["Bob".to_string(), "100".to_string()], + ) + .await, + Err(Error::ParamProcessingError) + )); + // Valid parameters + let extrinsic = construct_extrinsic( + "Balances", + "transfer_allow_death", + vec![ + "Id(5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty)".to_string(), + "100".to_string(), + ], + ) + .await?; + assert_eq!(extrinsic.call_name(), "transfer_allow_death"); + assert_eq!(extrinsic.pallet_name(), "Balances"); + Ok(()) + } + + #[tokio::test] + async fn encode_call_data_works() -> Result<()> { + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let extrinsic = construct_extrinsic("System", "remark", vec!["0x11".to_string()]).await?; + assert_eq!(encode_call_data(&api, &extrinsic)?, "0x00000411"); + Ok(()) + } +} diff --git a/crates/pop-parachains/src/errors.rs b/crates/pop-parachains/src/errors.rs index 244b70aa2..9ca07a8ac 100644 --- a/crates/pop-parachains/src/errors.rs +++ b/crates/pop-parachains/src/errors.rs @@ -18,8 +18,8 @@ pub enum Error { CurrentDirAccess, #[error("Failed to parse the endowment value")] EndowmentError, - #[error("The extrinsic {0} is not supported")] - ExtrinsicNotSupported(String), + #[error("The extrinsic is not supported")] + ExtrinsicNotSupported, #[error("Failed decode a value")] FromHexError(#[from] hex::FromHexError), #[error("IO error: {0}")] From 7cba66b4ee3d157ffd7f36d64eb30c09613b2eda Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 5 Sep 2024 14:09:34 +0200 Subject: [PATCH 081/211] feat: guide user for calling a contract --- Cargo.lock | 1 + crates/pop-cli/src/commands/call/contract.rs | 154 +++++++++++++++--- crates/pop-contracts/Cargo.toml | 3 +- crates/pop-contracts/src/call/metadata.rs | 48 ++++++ .../src/{call.rs => call/mod.rs} | 2 + crates/pop-contracts/src/lib.rs | 4 +- 6 files changed, 185 insertions(+), 27 deletions(-) create mode 100644 crates/pop-contracts/src/call/metadata.rs rename crates/pop-contracts/src/{call.rs => call/mod.rs} (99%) diff --git a/Cargo.lock b/Cargo.lock index 1313b0cb7..3ea832635 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4727,6 +4727,7 @@ dependencies = [ "anyhow", "contract-build", "contract-extrinsics", + "contract-transcode", "dirs", "duct", "flate2", diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 52210cf09..cc9dadccb 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -1,31 +1,35 @@ // SPDX-License-Identifier: GPL-3.0 -use crate::style::Theme; -use anyhow::anyhow; +use crate::{ + cli::{traits::Cli as _, Cli}, + style::Theme, +}; +use anyhow::{anyhow, Result}; use clap::Args; -use cliclack::{clear_screen, intro, log, outro, outro_cancel, set_theme}; +use cliclack::{clear_screen, confirm, input, intro, log, outro, outro_cancel, set_theme}; use console::style; use pop_contracts::{ - call_smart_contract, dry_run_call, dry_run_gas_estimate_call, set_up_call, CallOpts, + call_smart_contract, dry_run_call, dry_run_gas_estimate_call, get_messages, set_up_call, + CallOpts, Message, }; use sp_weights::Weight; use std::path::PathBuf; -#[derive(Args)] +#[derive(Args, Clone)] pub struct CallContractCommand { /// Path to the contract build directory. #[arg(short = 'p', long)] path: Option, /// The address of the contract to call. #[clap(name = "contract", long, env = "CONTRACT")] - contract: String, + contract: Option, /// The name of the contract message to call. #[clap(long, short)] - message: String, + message: Option, /// The constructor arguments, encoded as strings. #[clap(long, num_args = 0..)] args: Vec, - /// Transfers an initial balance to the instantiated contract. + /// Transfers an initial balance to the contract. #[clap(name = "value", long, default_value = "0")] value: String, /// Maximum amount of gas to be used for this command. @@ -40,7 +44,7 @@ pub struct CallContractCommand { /// Websocket endpoint of a node. #[clap(name = "url", long, value_parser, default_value = "ws://localhost:9944")] url: url::Url, - /// Secret key URI for the account deploying the contract. + /// Secret key URI for the account calling the contract. /// /// e.g. /// - for a dev account "//Alice" @@ -57,26 +61,38 @@ pub struct CallContractCommand { impl CallContractCommand { /// Executes the command. - pub(crate) async fn execute(self) -> anyhow::Result<()> { + pub(crate) async fn execute(self) -> Result<()> { clear_screen()?; intro(format!("{}: Calling a contract", style(" Pop CLI ").black().on_magenta()))?; set_theme(Theme); + let call_config = if self.contract.is_none() { + guide_user_to_call_contract().await? + } else { + self.clone() + }; + let contract = call_config + .contract + .expect("contract can not be none as fallback above is interactive input; qed"); + let message = call_config + .message + .expect("message can not be none as fallback above is interactive input; qed"); + let call_exec = set_up_call(CallOpts { - path: self.path.clone(), - contract: self.contract.clone(), - message: self.message.clone(), - args: self.args.clone(), - value: self.value.clone(), - gas_limit: self.gas_limit, - proof_size: self.proof_size, - url: self.url.clone(), - suri: self.suri.clone(), - execute: self.execute, + path: call_config.path, + contract, + message, + args: call_config.args, + value: call_config.value, + gas_limit: call_config.gas_limit, + proof_size: call_config.proof_size, + url: call_config.url, + suri: call_config.suri, + execute: call_config.execute, }) .await?; - if self.dry_run { + if call_config.dry_run { let spinner = cliclack::spinner(); spinner.start("Doing a dry run to estimate the gas..."); match dry_run_gas_estimate_call(&call_exec).await { @@ -92,16 +108,16 @@ impl CallContractCommand { return Ok(()); } - if !self.execute { + if !call_config.execute { let spinner = cliclack::spinner(); spinner.start("Calling the contract..."); let call_dry_run_result = dry_run_call(&call_exec).await?; log::info(format!("Result: {}", call_dry_run_result))?; log::warning("Your call has not been executed.")?; log::warning(format!( - "To submit the transaction and execute the call on chain, add {} flag to the command.", - "-x/--execute" - ))?; + "To submit the transaction and execute the call on chain, add {} flag to the command.", + "-x/--execute" + ))?; } else { let weight_limit = if self.gas_limit.is_some() && self.proof_size.is_some() { Weight::from_parts(self.gas_limit.unwrap(), self.proof_size.unwrap()) @@ -134,3 +150,91 @@ impl CallContractCommand { Ok(()) } } + +/// Guide the user to call the contract. +async fn guide_user_to_call_contract() -> anyhow::Result { + Cli.intro("Calling a contract")?; + + // Prompt for location of your contract. + let path: String = input("Path to your contract") + .placeholder("./my_contract") + .default_input("./") + .interact()?; + let contract_path = PathBuf::from(&path); + + // Prompt for contract address. + let contract_address: String = input("Paste the on-chain contract address:") + .placeholder("5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") + .default_input("5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") + .interact()?; + + // TODO: Guess the metadata path from the contract path. + let metadata_path = contract_path.join("target/ink/metadata.json"); + let messages = get_messages(metadata_path)?; + let message = display_select_options(&messages)?; + let mut contract_args = Vec::new(); + for arg in &message.args { + contract_args.push(input(arg).placeholder(arg).interact()?); + } + let mut value = "0".to_string(); + if message.payable { + value = input("Value to transfer to the contract: ") + .placeholder("0") + .default_input("0") + .interact()?; + } + // Prompt for gas limit of the call. + let gas_limit_input: u64 = input("Gas Limit:") + .required(false) + .placeholder("By default it will use an Estimation") + .default_input("0") + .interact()?; + let gas_limit: Option = (gas_limit_input != 0).then_some(gas_limit_input); + + // Prompt for proof_size of your contract. + let proof_size_input: u64 = input("Proof size:") + .required(false) + .placeholder("By default it will use an Estimation") + .default_input("0") + .interact()?; + let proof_size: Option = (proof_size_input != 0).then_some(proof_size_input); + + // Prompt for contract location. + let url: String = input("Where is your contract?") + .placeholder("ws://localhost:9944") + .default_input("ws://localhost:9944") + .interact()?; + + // Who is calling the contract. + let suri: String = input("Secret key URI for the account calling the contract:") + .placeholder("//Alice") + .default_input("//Alice") + .interact()?; + + let is_call_confirmed: bool = + confirm("Do you want to execute the call? (Selecting 'No' will perform a dry run)") + .initial_value(true) + .interact()?; + + Ok(CallContractCommand { + path: Some(contract_path), + contract: Some(contract_address), + message: Some(message.label.clone()), + args: contract_args, + value, + gas_limit, + proof_size, + url: url::Url::parse(&url)?, + suri, + execute: message.mutates, + dry_run: is_call_confirmed, + }) +} + +fn display_select_options(messages: &Vec) -> Result<&Message> { + let mut prompt = cliclack::select("Select the call:".to_string()); + for message in messages { + prompt = prompt.item(message, &message.label, &message.docs); + } + Ok(prompt.interact()?) +} diff --git a/crates/pop-contracts/Cargo.toml b/crates/pop-contracts/Cargo.toml index 23c6f0a60..3d4f15fb1 100644 --- a/crates/pop-contracts/Cargo.toml +++ b/crates/pop-contracts/Cargo.toml @@ -33,7 +33,8 @@ subxt.workspace = true # cargo-contracts contract-build.workspace = true contract-extrinsics.workspace = true - +contract-transcode.workspace = true +scale-info.workspace = true # pop pop-common = { path = "../pop-common", version = "0.5.0" } diff --git a/crates/pop-contracts/src/call/metadata.rs b/crates/pop-contracts/src/call/metadata.rs new file mode 100644 index 000000000..e026b522f --- /dev/null +++ b/crates/pop-contracts/src/call/metadata.rs @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-3.0 + +use crate::errors::Error; +use contract_transcode::{ink_metadata::MessageParamSpec, ContractMessageTranscoder}; +use scale_info::form::PortableForm; +use std::path::PathBuf; + +#[derive(Clone, PartialEq, Eq)] +// TODO: We are ignoring selector, return type for now. +/// Describes a contract message. +pub struct Message { + /// The label of the message. + pub label: String, + /// If the message is allowed to mutate the contract state. + pub mutates: bool, + /// If the message accepts any `value` from the caller. + pub payable: bool, + /// The parameters of the deployment handler. + pub args: Vec, + /// The message documentation. + pub docs: String, + /// If the message is the default for off-chain consumers (e.g UIs). + pub default: bool, +} + +pub fn get_messages(metadata_path: PathBuf) -> Result, Error> { + let transcoder = ContractMessageTranscoder::load(metadata_path)?; + let mut messages: Vec = Vec::new(); + for message in transcoder.metadata().spec().messages() { + messages.push(Message { + label: message.label().to_string(), + mutates: message.mutates(), + payable: message.payable(), + args: process_args(message.args()), + docs: message.docs().join("."), + default: *message.default(), + }); + } + Ok(messages) +} +//TODO: We are ignoring the type of the argument. +fn process_args(message_params: &[MessageParamSpec]) -> Vec { + let mut args: Vec = Vec::new(); + for arg in message_params { + args.push(arg.label().to_string()); + } + args +} diff --git a/crates/pop-contracts/src/call.rs b/crates/pop-contracts/src/call/mod.rs similarity index 99% rename from crates/pop-contracts/src/call.rs rename to crates/pop-contracts/src/call/mod.rs index 92ef55c61..91fb796f8 100644 --- a/crates/pop-contracts/src/call.rs +++ b/crates/pop-contracts/src/call/mod.rs @@ -20,6 +20,8 @@ use subxt::{Config, PolkadotConfig as DefaultConfig}; use subxt_signer::sr25519::Keypair; use url::Url; +pub mod metadata; + /// Attributes for the `call` command. pub struct CallOpts { /// Path to the contract build directory. diff --git a/crates/pop-contracts/src/lib.rs b/crates/pop-contracts/src/lib.rs index 1d558de30..36272e23a 100644 --- a/crates/pop-contracts/src/lib.rs +++ b/crates/pop-contracts/src/lib.rs @@ -13,7 +13,9 @@ mod utils; pub use build::{build_smart_contract, is_supported, Verbosity}; pub use call::{ - call_smart_contract, dry_run_call, dry_run_gas_estimate_call, set_up_call, CallOpts, + call_smart_contract, dry_run_call, dry_run_gas_estimate_call, + metadata::{get_messages, Message}, + set_up_call, CallOpts, }; pub use new::{create_smart_contract, is_valid_contract_name}; pub use node::{contracts_node_generator, is_chain_alive, run_contracts_node}; From 7427eace4f3e7996c796a364c50dad4d76a00b75 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 5 Sep 2024 17:03:06 +0200 Subject: [PATCH 082/211] feat: get metadata contract from the contract path --- crates/pop-cli/src/commands/call/contract.rs | 12 +++++------- crates/pop-contracts/src/call/metadata.rs | 15 +++++++++++---- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index cc9dadccb..ea6e14083 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -13,7 +13,7 @@ use pop_contracts::{ CallOpts, Message, }; use sp_weights::Weight; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; #[derive(Args, Clone)] pub struct CallContractCommand { @@ -156,11 +156,11 @@ async fn guide_user_to_call_contract() -> anyhow::Result { Cli.intro("Calling a contract")?; // Prompt for location of your contract. - let path: String = input("Path to your contract") + let input_path: String = input("Path to your contract") .placeholder("./my_contract") .default_input("./") .interact()?; - let contract_path = PathBuf::from(&path); + let contract_path = Path::new(&input_path); // Prompt for contract address. let contract_address: String = input("Paste the on-chain contract address:") @@ -168,9 +168,7 @@ async fn guide_user_to_call_contract() -> anyhow::Result { .default_input("5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") .interact()?; - // TODO: Guess the metadata path from the contract path. - let metadata_path = contract_path.join("target/ink/metadata.json"); - let messages = get_messages(metadata_path)?; + let messages = get_messages(contract_path)?; let message = display_select_options(&messages)?; let mut contract_args = Vec::new(); for arg in &message.args { @@ -217,7 +215,7 @@ async fn guide_user_to_call_contract() -> anyhow::Result { .interact()?; Ok(CallContractCommand { - path: Some(contract_path), + path: Some(contract_path.to_path_buf()), contract: Some(contract_address), message: Some(message.label.clone()), args: contract_args, diff --git a/crates/pop-contracts/src/call/metadata.rs b/crates/pop-contracts/src/call/metadata.rs index e026b522f..651c6040e 100644 --- a/crates/pop-contracts/src/call/metadata.rs +++ b/crates/pop-contracts/src/call/metadata.rs @@ -1,9 +1,10 @@ // SPDX-License-Identifier: GPL-3.0 use crate::errors::Error; -use contract_transcode::{ink_metadata::MessageParamSpec, ContractMessageTranscoder}; +use contract_extrinsics::ContractArtifacts; +use contract_transcode::ink_metadata::MessageParamSpec; use scale_info::form::PortableForm; -use std::path::PathBuf; +use std::path::Path; #[derive(Clone, PartialEq, Eq)] // TODO: We are ignoring selector, return type for now. @@ -23,8 +24,14 @@ pub struct Message { pub default: bool, } -pub fn get_messages(metadata_path: PathBuf) -> Result, Error> { - let transcoder = ContractMessageTranscoder::load(metadata_path)?; +pub fn get_messages(path: &Path) -> Result, Error> { + let cargo_toml_path = match path.ends_with("Cargo.toml") { + true => path.to_path_buf(), + false => path.join("Cargo.toml"), + }; + let contract_artifacts = + ContractArtifacts::from_manifest_or_file(Some(&cargo_toml_path), None)?; + let transcoder = contract_artifacts.contract_transcoder()?; let mut messages: Vec = Vec::new(); for message in transcoder.metadata().spec().messages() { messages.push(Message { From ebaff08770d50ff5be0705c33aaa269b93c8d101 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 5 Sep 2024 18:25:24 +0200 Subject: [PATCH 083/211] refactor: refactor test and validate address input --- crates/pop-cli/src/commands/call/contract.rs | 44 ++++++++++---------- crates/pop-contracts/src/call/metadata.rs | 2 +- crates/pop-contracts/src/lib.rs | 2 +- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index ea6e14083..17aeabb52 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -9,8 +9,8 @@ use clap::Args; use cliclack::{clear_screen, confirm, input, intro, log, outro, outro_cancel, set_theme}; use console::style; use pop_contracts::{ - call_smart_contract, dry_run_call, dry_run_gas_estimate_call, get_messages, set_up_call, - CallOpts, Message, + call_smart_contract, dry_run_call, dry_run_gas_estimate_call, get_messages, parse_account, + set_up_call, CallOpts, Message, }; use sp_weights::Weight; use std::path::{Path, PathBuf}; @@ -153,11 +153,11 @@ impl CallContractCommand { /// Guide the user to call the contract. async fn guide_user_to_call_contract() -> anyhow::Result { - Cli.intro("Calling a contract")?; + Cli.intro("Call a contract")?; // Prompt for location of your contract. - let input_path: String = input("Path to your contract") - .placeholder("./my_contract") + let input_path: String = input("Where is your project located?") + .placeholder("./") .default_input("./") .interact()?; let contract_path = Path::new(&input_path); @@ -165,6 +165,10 @@ async fn guide_user_to_call_contract() -> anyhow::Result { // Prompt for contract address. let contract_address: String = input("Paste the on-chain contract address:") .placeholder("5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") + .validate(|input: &String| match parse_account(input) { + Ok(_) => Ok(()), + Err(_) => Err("Invalid address."), + }) .default_input("5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") .interact()?; @@ -176,35 +180,33 @@ async fn guide_user_to_call_contract() -> anyhow::Result { } let mut value = "0".to_string(); if message.payable { - value = input("Value to transfer to the contract: ") + value = input("Value to transfer to the call:") .placeholder("0") .default_input("0") .interact()?; } - // Prompt for gas limit of the call. - let gas_limit_input: u64 = input("Gas Limit:") + // Prompt for gas limit and proof_size of the call. + let gas_limit_input: String = input("Enter the gas limit:") .required(false) - .placeholder("By default it will use an Estimation") - .default_input("0") + .default_input("") + .placeholder("if left blank, an estimation will be used") .interact()?; - let gas_limit: Option = (gas_limit_input != 0).then_some(gas_limit_input); - - // Prompt for proof_size of your contract. - let proof_size_input: u64 = input("Proof size:") + let gas_limit: Option = gas_limit_input.parse::().ok(); // If blank or bad input, estimate it. + let proof_size_input: String = input("Enter the proof size limit:") .required(false) - .placeholder("By default it will use an Estimation") - .default_input("0") + .placeholder("if left blank, an estimation will be used") + .default_input("") .interact()?; - let proof_size: Option = (proof_size_input != 0).then_some(proof_size_input); + let proof_size: Option = proof_size_input.parse::().ok(); // If blank or bad input, estimate it. // Prompt for contract location. - let url: String = input("Where is your contract?") + let url: String = input("Where is your contract deployed?") .placeholder("ws://localhost:9944") .default_input("ws://localhost:9944") .interact()?; // Who is calling the contract. - let suri: String = input("Secret key URI for the account calling the contract:") + let suri: String = input("Signer calling the contract:") .placeholder("//Alice") .default_input("//Alice") .interact()?; @@ -225,12 +227,12 @@ async fn guide_user_to_call_contract() -> anyhow::Result { url: url::Url::parse(&url)?, suri, execute: message.mutates, - dry_run: is_call_confirmed, + dry_run: !is_call_confirmed, }) } fn display_select_options(messages: &Vec) -> Result<&Message> { - let mut prompt = cliclack::select("Select the call:".to_string()); + let mut prompt = cliclack::select("Select the message to call:"); for message in messages { prompt = prompt.item(message, &message.label, &message.docs); } diff --git a/crates/pop-contracts/src/call/metadata.rs b/crates/pop-contracts/src/call/metadata.rs index 651c6040e..0de02cf90 100644 --- a/crates/pop-contracts/src/call/metadata.rs +++ b/crates/pop-contracts/src/call/metadata.rs @@ -39,7 +39,7 @@ pub fn get_messages(path: &Path) -> Result, Error> { mutates: message.mutates(), payable: message.payable(), args: process_args(message.args()), - docs: message.docs().join("."), + docs: message.docs().join(" "), default: *message.default(), }); } diff --git a/crates/pop-contracts/src/lib.rs b/crates/pop-contracts/src/lib.rs index 36272e23a..c883c165c 100644 --- a/crates/pop-contracts/src/lib.rs +++ b/crates/pop-contracts/src/lib.rs @@ -25,4 +25,4 @@ pub use up::{ dry_run_gas_estimate_instantiate, dry_run_upload, instantiate_smart_contract, set_up_deployment, set_up_upload, upload_smart_contract, UpOpts, }; -pub use utils::signer::parse_hex_bytes; +pub use utils::{helpers::parse_account, signer::parse_hex_bytes}; From afe670bddd5eadec1598d4dcaa65525ed09af5f2 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 6 Sep 2024 11:53:49 +0200 Subject: [PATCH 084/211] fix: apply feedback --- crates/pop-cli/src/commands/call/contract.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 17aeabb52..3c80c49dd 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -164,7 +164,7 @@ async fn guide_user_to_call_contract() -> anyhow::Result { // Prompt for contract address. let contract_address: String = input("Paste the on-chain contract address:") - .placeholder("5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") + .placeholder("e.g. 5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") .validate(|input: &String| match parse_account(input) { Ok(_) => Ok(()), Err(_) => Err("Invalid address."), @@ -172,7 +172,13 @@ async fn guide_user_to_call_contract() -> anyhow::Result { .default_input("5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") .interact()?; - let messages = get_messages(contract_path)?; + let messages = match get_messages(contract_path) { + Ok(messages) => messages, + Err(e) => { + outro_cancel("Unable to fetch contract metadata.")?; + return Err(anyhow!(format!("{}", e.to_string()))); + }, + }; let message = display_select_options(&messages)?; let mut contract_args = Vec::new(); for arg in &message.args { @@ -189,12 +195,12 @@ async fn guide_user_to_call_contract() -> anyhow::Result { let gas_limit_input: String = input("Enter the gas limit:") .required(false) .default_input("") - .placeholder("if left blank, an estimation will be used") + .placeholder("If left blank, an estimation will be used") .interact()?; let gas_limit: Option = gas_limit_input.parse::().ok(); // If blank or bad input, estimate it. let proof_size_input: String = input("Enter the proof size limit:") .required(false) - .placeholder("if left blank, an estimation will be used") + .placeholder("If left blank, an estimation will be used") .default_input("") .interact()?; let proof_size: Option = proof_size_input.parse::().ok(); // If blank or bad input, estimate it. From 6c9315dd75dd263471f1a1d45bc5b2346e71468e Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 6 Sep 2024 14:48:46 +0200 Subject: [PATCH 085/211] feat: prompt to have another call and skip questions for queries --- crates/pop-cli/src/commands/call/contract.rs | 56 ++++++++++++-------- crates/pop-cli/src/commands/mod.rs | 2 +- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 3c80c49dd..37bc2b97c 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -61,7 +61,7 @@ pub struct CallContractCommand { impl CallContractCommand { /// Executes the command. - pub(crate) async fn execute(self) -> Result<()> { + pub(crate) async fn execute(self: Box) -> Result<()> { clear_screen()?; intro(format!("{}: Calling a contract", style(" Pop CLI ").black().on_magenta()))?; set_theme(Theme); @@ -69,7 +69,7 @@ impl CallContractCommand { let call_config = if self.contract.is_none() { guide_user_to_call_contract().await? } else { - self.clone() + *self.clone() }; let contract = call_config .contract @@ -114,10 +114,6 @@ impl CallContractCommand { let call_dry_run_result = dry_run_call(&call_exec).await?; log::info(format!("Result: {}", call_dry_run_result))?; log::warning("Your call has not been executed.")?; - log::warning(format!( - "To submit the transaction and execute the call on chain, add {} flag to the command.", - "-x/--execute" - ))?; } else { let weight_limit = if self.gas_limit.is_some() && self.proof_size.is_some() { Weight::from_parts(self.gas_limit.unwrap(), self.proof_size.unwrap()) @@ -145,6 +141,13 @@ impl CallContractCommand { log::info(call_result)?; } + if self.contract.is_none() { + let another_call: bool = + confirm("Do you want to do another call?").initial_value(false).interact()?; + if another_call { + Box::pin(self.execute()).await?; + } + } outro("Call completed successfully!")?; Ok(()) @@ -191,19 +194,23 @@ async fn guide_user_to_call_contract() -> anyhow::Result { .default_input("0") .interact()?; } - // Prompt for gas limit and proof_size of the call. - let gas_limit_input: String = input("Enter the gas limit:") - .required(false) - .default_input("") - .placeholder("If left blank, an estimation will be used") - .interact()?; - let gas_limit: Option = gas_limit_input.parse::().ok(); // If blank or bad input, estimate it. - let proof_size_input: String = input("Enter the proof size limit:") - .required(false) - .placeholder("If left blank, an estimation will be used") - .default_input("") - .interact()?; - let proof_size: Option = proof_size_input.parse::().ok(); // If blank or bad input, estimate it. + let mut gas_limit: Option = None; + let mut proof_size: Option = None; + if message.mutates { + // Prompt for gas limit and proof_size of the call. + let gas_limit_input: String = input("Enter the gas limit:") + .required(false) + .default_input("") + .placeholder("If left blank, an estimation will be used") + .interact()?; + gas_limit = gas_limit_input.parse::().ok(); // If blank or bad input, estimate it. + let proof_size_input: String = input("Enter the proof size limit:") + .required(false) + .placeholder("If left blank, an estimation will be used") + .default_input("") + .interact()?; + proof_size = proof_size_input.parse::().ok(); // If blank or bad input, estimate it. + } // Prompt for contract location. let url: String = input("Where is your contract deployed?") @@ -217,10 +224,13 @@ async fn guide_user_to_call_contract() -> anyhow::Result { .default_input("//Alice") .interact()?; - let is_call_confirmed: bool = - confirm("Do you want to execute the call? (Selecting 'No' will perform a dry run)") - .initial_value(true) - .interact()?; + let mut is_call_confirmed: bool = true; + if message.mutates { + is_call_confirmed = + confirm("Do you want to execute the call? (Selecting 'No' will perform a dry run)") + .initial_value(true) + .interact()?; + } Ok(CallContractCommand { path: Some(contract_path.to_path_buf()), diff --git a/crates/pop-cli/src/commands/mod.rs b/crates/pop-cli/src/commands/mod.rs index 34c2f10b3..8a2941c95 100644 --- a/crates/pop-cli/src/commands/mod.rs +++ b/crates/pop-cli/src/commands/mod.rs @@ -98,7 +98,7 @@ impl Command { }, #[cfg(feature = "contract")] Self::Call(args) => match args.command { - call::Command::Contract(cmd) => cmd.execute().await.map(|_| Value::Null), + call::Command::Contract(cmd) => Box::new(cmd).execute().await.map(|_| Value::Null), }, #[cfg(any(feature = "parachain", feature = "contract"))] Self::Up(args) => match args.command { From 69de583f3021ebb578ade59a4a1dd1d07a9d3116 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Sun, 8 Sep 2024 22:14:33 +0200 Subject: [PATCH 086/211] refactor: use Cli module instead of cliclack --- crates/pop-cli/src/cli.rs | 94 ++++++++++++++++++++ crates/pop-cli/src/commands/call/contract.rs | 71 ++++++++------- 2 files changed, 132 insertions(+), 33 deletions(-) diff --git a/crates/pop-cli/src/cli.rs b/crates/pop-cli/src/cli.rs index 353a4704d..d11f6a473 100644 --- a/crates/pop-cli/src/cli.rs +++ b/crates/pop-cli/src/cli.rs @@ -13,6 +13,8 @@ pub(crate) mod traits { fn confirm(&mut self, prompt: impl Display) -> impl Confirm; /// Prints an info message. fn info(&mut self, text: impl Display) -> Result<()>; + /// Constructs a new [`Input`] prompt. + fn input(&mut self, prompt: impl Display) -> impl Input; /// Prints a header of the prompt sequence. fn intro(&mut self, title: impl Display) -> Result<()>; /// Constructs a new [`MultiSelect`] prompt. @@ -21,6 +23,8 @@ pub(crate) mod traits { fn outro(&mut self, message: impl Display) -> Result<()>; /// Prints a footer of the prompt sequence with a failure style. fn outro_cancel(&mut self, message: impl Display) -> Result<()>; + /// Constructs a new [`Select`] prompt. + fn select(&mut self, prompt: impl Display) -> impl Select; /// Prints a success message. fn success(&mut self, message: impl Display) -> Result<()>; /// Prints a warning message. @@ -29,10 +33,29 @@ pub(crate) mod traits { /// A confirmation prompt. pub trait Confirm { + /// Sets the initially selected value. + fn initial_value(self, initial_value: bool) -> Self; /// Starts the prompt interaction. fn interact(&mut self) -> Result; } + /// A text input prompt. + pub trait Input { + /// Sets the default value for the input. + fn default_input(self, value: &str) -> Self; + /// Starts the prompt interaction. + fn interact(&mut self) -> Result; + /// Sets the placeholder (hint) text for the input. + fn placeholder(self, value: &str) -> Self; + /// Sets whether the input is required. + fn required(self, required: bool) -> Self; + /// Sets a validation callback for the input that is called when the user submits. + fn validate( + self, + validator: impl Fn(&String) -> std::result::Result<(), &'static str> + 'static, + ) -> Self; + } + /// A multi-select prompt. pub trait MultiSelect { /// Starts the prompt interaction. @@ -42,6 +65,14 @@ pub(crate) mod traits { /// Sets whether the input is required. fn required(self, required: bool) -> Self; } + + /// A select prompt. + pub trait Select { + /// Starts the prompt interaction. + fn interact(&mut self) -> Result; + /// Adds an item to the selection prompt. + fn item(self, value: T, label: impl Display, hint: impl Display) -> Self; + } } /// A command line interface using cliclack. @@ -57,6 +88,11 @@ impl traits::Cli for Cli { cliclack::log::info(text) } + /// Constructs a new [`Input`] prompt. + fn input(&mut self, prompt: impl Display) -> impl traits::Input { + Input(cliclack::input(prompt)) + } + /// Prints a header of the prompt sequence. fn intro(&mut self, title: impl Display) -> Result<()> { cliclack::clear_screen()?; @@ -79,6 +115,11 @@ impl traits::Cli for Cli { cliclack::outro_cancel(message) } + /// Constructs a new [`Select`] prompt. + fn select(&mut self, prompt: impl Display) -> impl traits::Select { + Select::(cliclack::select(prompt)) + } + /// Prints a success message. fn success(&mut self, message: impl Display) -> Result<()> { cliclack::log::success(message) @@ -97,6 +138,43 @@ impl traits::Confirm for Confirm { fn interact(&mut self) -> Result { self.0.interact() } + /// Sets the initially selected value. + fn initial_value(mut self, initial_value: bool) -> Self { + self.0 = self.0.initial_value(initial_value); + self + } +} + +/// A input prompt using cliclack. +struct Input(cliclack::Input); +impl traits::Input for Input { + /// Sets the default value for the input. + fn default_input(mut self, value: &str) -> Self { + self.0 = self.0.default_input(value); + self + } + /// Starts the prompt interaction. + fn interact(&mut self) -> Result { + self.0.interact() + } + /// Sets the placeholder (hint) text for the input. + fn placeholder(mut self, placeholder: &str) -> Self { + self.0 = self.0.placeholder(placeholder); + self + } + /// Sets whether the input is required. + fn required(mut self, required: bool) -> Self { + self.0 = self.0.required(required); + self + } + /// Sets a validation callback for the input that is called when the user submits. + fn validate( + mut self, + validator: impl Fn(&String) -> std::result::Result<(), &'static str> + 'static, + ) -> Self { + self.0 = self.0.validate(validator); + self + } } /// A multi-select prompt using cliclack. @@ -121,6 +199,22 @@ impl traits::MultiSelect for MultiSelect { } } +/// A select prompt using cliclack. +struct Select(cliclack::Select); + +impl traits::Select for Select { + /// Starts the prompt interaction. + fn interact(&mut self) -> Result { + self.0.interact() + } + + /// Adds an item to the selection prompt. + fn item(mut self, value: T, label: impl Display, hint: impl Display) -> Self { + self.0 = self.0.item(value, label, hint); + self + } +} + #[cfg(test)] pub(crate) mod tests { use super::traits::*; diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 37bc2b97c..576ddae2a 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -1,13 +1,11 @@ // SPDX-License-Identifier: GPL-3.0 -use crate::{ - cli::{traits::Cli as _, Cli}, - style::Theme, +use crate::cli::{ + traits::{Cli as _, Confirm, Input, Select}, + Cli, }; use anyhow::{anyhow, Result}; use clap::Args; -use cliclack::{clear_screen, confirm, input, intro, log, outro, outro_cancel, set_theme}; -use console::style; use pop_contracts::{ call_smart_contract, dry_run_call, dry_run_gas_estimate_call, get_messages, parse_account, set_up_call, CallOpts, Message, @@ -62,9 +60,7 @@ pub struct CallContractCommand { impl CallContractCommand { /// Executes the command. pub(crate) async fn execute(self: Box) -> Result<()> { - clear_screen()?; - intro(format!("{}: Calling a contract", style(" Pop CLI ").black().on_magenta()))?; - set_theme(Theme); + Cli.intro("Calling a contract")?; let call_config = if self.contract.is_none() { guide_user_to_call_contract().await? @@ -97,12 +93,12 @@ impl CallContractCommand { spinner.start("Doing a dry run to estimate the gas..."); match dry_run_gas_estimate_call(&call_exec).await { Ok(w) => { - log::info(format!("Gas limit: {:?}", w))?; - log::warning("Your call has not been executed.")?; + Cli.info(format!("Gas limit: {:?}", w))?; + Cli.warning("Your call has not been executed.")?; }, Err(e) => { spinner.error(format!("{e}")); - outro_cancel("Call failed.")?; + Cli.outro_cancel("Call failed.")?; }, }; return Ok(()); @@ -112,8 +108,8 @@ impl CallContractCommand { let spinner = cliclack::spinner(); spinner.start("Calling the contract..."); let call_dry_run_result = dry_run_call(&call_exec).await?; - log::info(format!("Result: {}", call_dry_run_result))?; - log::warning("Your call has not been executed.")?; + Cli.info(format!("Result: {}", call_dry_run_result))?; + Cli.warning("Your call has not been executed.")?; } else { let weight_limit = if self.gas_limit.is_some() && self.proof_size.is_some() { Weight::from_parts(self.gas_limit.unwrap(), self.proof_size.unwrap()) @@ -122,12 +118,12 @@ impl CallContractCommand { spinner.start("Doing a dry run to estimate the gas..."); match dry_run_gas_estimate_call(&call_exec).await { Ok(w) => { - log::info(format!("Gas limit: {:?}", w))?; + Cli.info(format!("Gas limit: {:?}", w))?; w }, Err(e) => { spinner.error(format!("{e}")); - outro_cancel("Call failed.")?; + Cli.outro_cancel("Call failed.")?; return Ok(()); }, } @@ -139,17 +135,17 @@ impl CallContractCommand { .await .map_err(|err| anyhow!("{} {}", "ERROR:", format!("{err:?}")))?; - log::info(call_result)?; + Cli.info(call_result)?; } if self.contract.is_none() { let another_call: bool = - confirm("Do you want to do another call?").initial_value(false).interact()?; + Cli.confirm("Do you want to do another call?").initial_value(false).interact()?; if another_call { Box::pin(self.execute()).await?; } } - outro("Call completed successfully!")?; + Cli.outro("Call completed successfully!")?; Ok(()) } } @@ -159,14 +155,16 @@ async fn guide_user_to_call_contract() -> anyhow::Result { Cli.intro("Call a contract")?; // Prompt for location of your contract. - let input_path: String = input("Where is your project located?") + let input_path: String = Cli + .input("Where is your project located?") .placeholder("./") .default_input("./") .interact()?; let contract_path = Path::new(&input_path); // Prompt for contract address. - let contract_address: String = input("Paste the on-chain contract address:") + let contract_address: String = Cli + .input("Paste the on-chain contract address:") .placeholder("e.g. 5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") .validate(|input: &String| match parse_account(input) { Ok(_) => Ok(()), @@ -178,18 +176,19 @@ async fn guide_user_to_call_contract() -> anyhow::Result { let messages = match get_messages(contract_path) { Ok(messages) => messages, Err(e) => { - outro_cancel("Unable to fetch contract metadata.")?; + Cli.outro_cancel("Unable to fetch contract metadata.")?; return Err(anyhow!(format!("{}", e.to_string()))); }, }; let message = display_select_options(&messages)?; let mut contract_args = Vec::new(); for arg in &message.args { - contract_args.push(input(arg).placeholder(arg).interact()?); + contract_args.push(Cli.input(arg).placeholder(arg).interact()?); } let mut value = "0".to_string(); if message.payable { - value = input("Value to transfer to the call:") + value = Cli + .input("Value to transfer to the call:") .placeholder("0") .default_input("0") .interact()?; @@ -198,13 +197,15 @@ async fn guide_user_to_call_contract() -> anyhow::Result { let mut proof_size: Option = None; if message.mutates { // Prompt for gas limit and proof_size of the call. - let gas_limit_input: String = input("Enter the gas limit:") + let gas_limit_input: String = Cli + .input("Enter the gas limit:") .required(false) .default_input("") .placeholder("If left blank, an estimation will be used") .interact()?; gas_limit = gas_limit_input.parse::().ok(); // If blank or bad input, estimate it. - let proof_size_input: String = input("Enter the proof size limit:") + let proof_size_input: String = Cli + .input("Enter the proof size limit:") .required(false) .placeholder("If left blank, an estimation will be used") .default_input("") @@ -213,23 +214,25 @@ async fn guide_user_to_call_contract() -> anyhow::Result { } // Prompt for contract location. - let url: String = input("Where is your contract deployed?") + let url: String = Cli + .input("Where is your contract deployed?") .placeholder("ws://localhost:9944") .default_input("ws://localhost:9944") .interact()?; // Who is calling the contract. - let suri: String = input("Signer calling the contract:") + let suri: String = Cli + .input("Signer calling the contract:") .placeholder("//Alice") .default_input("//Alice") .interact()?; let mut is_call_confirmed: bool = true; if message.mutates { - is_call_confirmed = - confirm("Do you want to execute the call? (Selecting 'No' will perform a dry run)") - .initial_value(true) - .interact()?; + is_call_confirmed = Cli + .confirm("Do you want to execute the call? (Selecting 'No' will perform a dry run)") + .initial_value(true) + .interact()?; } Ok(CallContractCommand { @@ -248,9 +251,11 @@ async fn guide_user_to_call_contract() -> anyhow::Result { } fn display_select_options(messages: &Vec) -> Result<&Message> { - let mut prompt = cliclack::select("Select the message to call:"); + let mut cli = Cli; + let mut prompt = cli.select("Select the message to call:"); for message in messages { prompt = prompt.item(message, &message.label, &message.docs); } - Ok(prompt.interact()?) + let selected_message = prompt.interact()?; + Ok(selected_message) } From 71edc87c8a81c99cb38cbf0829fc6d513e86d5c4 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Mon, 9 Sep 2024 17:21:11 +0200 Subject: [PATCH 087/211] test: unit test pop-cli crate --- crates/pop-cli/src/cli.rs | 131 +++++- crates/pop-cli/src/commands/call/contract.rs | 313 +++++++++++-- crates/pop-cli/src/commands/mod.rs | 7 +- crates/pop-contracts/src/call/mod.rs | 2 +- crates/pop-contracts/src/up.rs | 2 +- .../tests => tests}/files/testing.contract | 0 tests/files/testing.json | 424 ++++++++++++++++++ 7 files changed, 829 insertions(+), 50 deletions(-) rename {crates/pop-contracts/tests => tests}/files/testing.contract (100%) create mode 100644 tests/files/testing.json diff --git a/crates/pop-cli/src/cli.rs b/crates/pop-cli/src/cli.rs index d11f6a473..65999288d 100644 --- a/crates/pop-cli/src/cli.rs +++ b/crates/pop-cli/src/cli.rs @@ -218,18 +218,21 @@ impl traits::Select for Select { #[cfg(test)] pub(crate) mod tests { use super::traits::*; - use std::{fmt::Display, io::Result}; + use std::{fmt::Display, io::Result, usize}; /// Mock Cli with optional expectations #[derive(Default)] pub(crate) struct MockCli { confirm_expectation: Option<(String, bool)>, info_expectations: Vec, + input_expectations: Vec<(String, String)>, intro_expectation: Option, outro_expectation: Option, multiselect_expectation: Option<(String, Option, bool, Option>)>, outro_cancel_expectation: Option, + select_expectation: + Option<(String, Option, bool, Option>, usize)>, success_expectations: Vec, warning_expectations: Vec, } @@ -244,6 +247,11 @@ pub(crate) mod tests { self } + pub(crate) fn expect_input(mut self, prompt: impl Display, input: String) -> Self { + self.input_expectations.push((prompt.to_string(), input)); + self + } + pub(crate) fn expect_info(mut self, message: impl Display) -> Self { self.info_expectations.push(message.to_string()); self @@ -275,6 +283,18 @@ pub(crate) mod tests { self } + pub(crate) fn expect_select( + mut self, + prompt: impl Display, + required: Option, + collect: bool, + items: Option>, + item: usize, + ) -> Self { + self.select_expectation = Some((prompt.to_string(), required, collect, items, item)); + self + } + pub(crate) fn expect_success(mut self, message: impl Display) -> Self { self.success_expectations.push(message.to_string()); self @@ -292,6 +312,9 @@ pub(crate) mod tests { if !self.info_expectations.is_empty() { panic!("`{}` info log expectations not satisfied", self.info_expectations.join(",")) } + if !self.input_expectations.is_empty() { + panic!("`{:?}` input expectation not satisfied", self.input_expectations) + } if let Some(expectation) = self.intro_expectation { panic!("`{expectation}` intro expectation not satisfied") } @@ -304,6 +327,9 @@ pub(crate) mod tests { if let Some(expectation) = self.outro_cancel_expectation { panic!("`{expectation}` outro cancel expectation not satisfied") } + if let Some((prompt, _, _, _, _)) = self.select_expectation { + panic!("`{prompt}` select prompt expectation not satisfied") + } if !self.success_expectations.is_empty() { panic!( "`{}` success log expectations not satisfied", @@ -336,6 +362,20 @@ pub(crate) mod tests { Ok(()) } + fn input(&mut self, prompt: impl Display) -> impl Input { + let prompt = prompt.to_string(); + if let Some((expectation, input)) = self.input_expectations.pop() { + assert_eq!(expectation, prompt, "prompt does not satisfy expectation"); + return MockInput { + prompt: input.clone(), + input, + placeholder: "".to_string(), + required: false, + }; + } + MockInput::default() + } + fn intro(&mut self, title: impl Display) -> Result<()> { if let Some(expectation) = self.intro_expectation.take() { assert_eq!(expectation, title.to_string(), "intro does not satisfy expectation"); @@ -382,6 +422,21 @@ pub(crate) mod tests { Ok(()) } + fn select(&mut self, prompt: impl Display) -> impl Select { + let prompt = prompt.to_string(); + println!("prompt: {}", prompt); + if let Some((expectation, _, collect, items_expectation, item)) = + self.select_expectation.take() + { + println!("expectation: {}", expectation); + println!("items_expectation: {:?}", items_expectation); + assert_eq!(expectation, prompt, "prompt does not satisfy expectation"); + return MockSelect { items_expectation, collect, items: vec![], item }; + } + + MockSelect::default() + } + fn success(&mut self, message: impl Display) -> Result<()> { let message = message.to_string(); self.success_expectations.retain(|x| *x != message); @@ -405,6 +460,46 @@ pub(crate) mod tests { fn interact(&mut self) -> Result { Ok(self.confirm) } + fn initial_value(mut self, initial_value: bool) -> Self { + self.confirm = initial_value; + self + } + } + + /// Mock input prompt + #[derive(Default)] + struct MockInput { + prompt: String, + input: String, + placeholder: String, + required: bool, + } + + impl Input for MockInput { + fn interact(&mut self) -> Result { + Ok(self.prompt.clone()) + } + fn default_input(mut self, value: &str) -> Self { + self.input = value.to_string(); + self + } + + fn placeholder(mut self, value: &str) -> Self { + self.placeholder = value.to_string(); + self + } + + fn required(mut self, value: bool) -> Self { + self.required = value; + self + } + + fn validate( + self, + _validator: impl Fn(&String) -> std::result::Result<(), &'static str> + 'static, + ) -> Self { + self + } } /// Mock multi-select prompt @@ -454,4 +549,38 @@ pub(crate) mod tests { self } } + + /// Mock select prompt + pub(crate) struct MockSelect { + items_expectation: Option>, + collect: bool, + items: Vec, + item: usize, + } + + impl MockSelect { + pub(crate) fn default() -> Self { + Self { items_expectation: None, collect: false, items: vec![], item: 0 } + } + } + + impl Select for MockSelect { + fn interact(&mut self) -> Result { + Ok(self.items[self.item].clone()) + } + + fn item(mut self, value: T, label: impl Display, hint: impl Display) -> Self { + // Check expectations + if let Some(items) = self.items_expectation.as_mut() { + let item = (label.to_string(), hint.to_string()); + assert!(items.contains(&item), "`{item:?}` item does not satisfy any expectations"); + items.retain(|x| *x != item); + } + // Collect if specified + if self.collect { + self.items.push(value); + } + self + } + } } diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 576ddae2a..0c4f5ac3f 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -1,14 +1,11 @@ // SPDX-License-Identifier: GPL-3.0 -use crate::cli::{ - traits::{Cli as _, Confirm, Input, Select}, - Cli, -}; +use crate::cli::traits::*; use anyhow::{anyhow, Result}; use clap::Args; use pop_contracts::{ call_smart_contract, dry_run_call, dry_run_gas_estimate_call, get_messages, parse_account, - set_up_call, CallOpts, Message, + set_up_call, CallOpts, }; use sp_weights::Weight; use std::path::{Path, PathBuf}; @@ -57,19 +54,27 @@ pub struct CallContractCommand { dry_run: bool, } -impl CallContractCommand { +pub(crate) struct CallContract<'a, CLI: Cli> { + /// The cli to be used. + pub(crate) cli: &'a mut CLI, + /// The args to call. + pub(crate) args: CallContractCommand, +} + +impl<'a, CLI: Cli> CallContract<'a, CLI> { /// Executes the command. - pub(crate) async fn execute(self: Box) -> Result<()> { - Cli.intro("Calling a contract")?; + pub(crate) async fn execute(mut self: Box) -> Result<()> { + self.cli.intro("Call a contract")?; - let call_config = if self.contract.is_none() { - guide_user_to_call_contract().await? + let call_config = if self.args.contract.is_none() { + guide_user_to_call_contract(&mut self).await? } else { - *self.clone() + self.args.clone() }; let contract = call_config .contract .expect("contract can not be none as fallback above is interactive input; qed"); + // TODO: Can be nill pop call contract --contract let message = call_config .message .expect("message can not be none as fallback above is interactive input; qed"); @@ -82,7 +87,7 @@ impl CallContractCommand { value: call_config.value, gas_limit: call_config.gas_limit, proof_size: call_config.proof_size, - url: call_config.url, + url: call_config.url.clone(), suri: call_config.suri, execute: call_config.execute, }) @@ -93,12 +98,12 @@ impl CallContractCommand { spinner.start("Doing a dry run to estimate the gas..."); match dry_run_gas_estimate_call(&call_exec).await { Ok(w) => { - Cli.info(format!("Gas limit: {:?}", w))?; - Cli.warning("Your call has not been executed.")?; + self.cli.info(format!("Gas limit: {:?}", w))?; + self.cli.warning("Your call has not been executed.")?; }, Err(e) => { spinner.error(format!("{e}")); - Cli.outro_cancel("Call failed.")?; + self.cli.outro_cancel("Call failed.")?; }, }; return Ok(()); @@ -108,8 +113,8 @@ impl CallContractCommand { let spinner = cliclack::spinner(); spinner.start("Calling the contract..."); let call_dry_run_result = dry_run_call(&call_exec).await?; - Cli.info(format!("Result: {}", call_dry_run_result))?; - Cli.warning("Your call has not been executed.")?; + self.cli.info(format!("Result: {}", call_dry_run_result))?; + self.cli.warning("Your call has not been executed.")?; } else { let weight_limit = if self.gas_limit.is_some() && self.proof_size.is_some() { Weight::from_parts(self.gas_limit.unwrap(), self.proof_size.unwrap()) @@ -118,12 +123,12 @@ impl CallContractCommand { spinner.start("Doing a dry run to estimate the gas..."); match dry_run_gas_estimate_call(&call_exec).await { Ok(w) => { - Cli.info(format!("Gas limit: {:?}", w))?; + self.cli.info(format!("Gas limit: {:?}", w))?; w }, Err(e) => { spinner.error(format!("{e}")); - Cli.outro_cancel("Call failed.")?; + self.cli.outro_cancel("Call failed.")?; return Ok(()); }, } @@ -131,39 +136,49 @@ impl CallContractCommand { let spinner = cliclack::spinner(); spinner.start("Calling the contract..."); - let call_result = call_smart_contract(call_exec, weight_limit, &self.url) + let call_result = call_smart_contract(call_exec, weight_limit, &call_config.url) .await .map_err(|err| anyhow!("{} {}", "ERROR:", format!("{err:?}")))?; - Cli.info(call_result)?; + self.cli.info(call_result)?; } - if self.contract.is_none() { - let another_call: bool = - Cli.confirm("Do you want to do another call?").initial_value(false).interact()?; + if self.args.contract.is_none() { + let another_call: bool = self + .cli + .confirm("Do you want to do another call?") + .initial_value(false) + .interact()?; if another_call { Box::pin(self.execute()).await?; + } else { + self.cli.outro("Call completed successfully!")?; } + } else { + self.cli.outro("Call completed successfully!")?; } - - Cli.outro("Call completed successfully!")?; Ok(()) } } /// Guide the user to call the contract. -async fn guide_user_to_call_contract() -> anyhow::Result { - Cli.intro("Call a contract")?; +async fn guide_user_to_call_contract<'a, CLI: Cli>( + command: &mut CallContract<'a, CLI>, +) -> anyhow::Result { + command.cli.intro("Call a contract")?; // Prompt for location of your contract. - let input_path: String = Cli + let input_path: String = command + .cli .input("Where is your project located?") .placeholder("./") .default_input("./") .interact()?; let contract_path = Path::new(&input_path); + println!("path: {:?}", contract_path); // Prompt for contract address. - let contract_address: String = Cli + let contract_address: String = command + .cli .input("Paste the on-chain contract address:") .placeholder("e.g. 5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") .validate(|input: &String| match parse_account(input) { @@ -176,18 +191,27 @@ async fn guide_user_to_call_contract() -> anyhow::Result { let messages = match get_messages(contract_path) { Ok(messages) => messages, Err(e) => { - Cli.outro_cancel("Unable to fetch contract metadata.")?; + command.cli.outro_cancel("Unable to fetch contract metadata.")?; return Err(anyhow!(format!("{}", e.to_string()))); }, }; - let message = display_select_options(&messages)?; + let message = { + let mut prompt = command.cli.select("Select the message to call:"); + for select_message in messages { + prompt = + prompt.item(select_message.clone(), &select_message.label, &select_message.docs); + } + prompt.interact()? + }; + let mut contract_args = Vec::new(); for arg in &message.args { - contract_args.push(Cli.input(arg).placeholder(arg).interact()?); + contract_args.push(command.cli.input(arg).placeholder(arg).interact()?); } let mut value = "0".to_string(); if message.payable { - value = Cli + value = command + .cli .input("Value to transfer to the call:") .placeholder("0") .default_input("0") @@ -197,14 +221,16 @@ async fn guide_user_to_call_contract() -> anyhow::Result { let mut proof_size: Option = None; if message.mutates { // Prompt for gas limit and proof_size of the call. - let gas_limit_input: String = Cli + let gas_limit_input: String = command + .cli .input("Enter the gas limit:") .required(false) .default_input("") .placeholder("If left blank, an estimation will be used") .interact()?; gas_limit = gas_limit_input.parse::().ok(); // If blank or bad input, estimate it. - let proof_size_input: String = Cli + let proof_size_input: String = command + .cli .input("Enter the proof size limit:") .required(false) .placeholder("If left blank, an estimation will be used") @@ -214,14 +240,16 @@ async fn guide_user_to_call_contract() -> anyhow::Result { } // Prompt for contract location. - let url: String = Cli + let url: String = command + .cli .input("Where is your contract deployed?") .placeholder("ws://localhost:9944") .default_input("ws://localhost:9944") .interact()?; // Who is calling the contract. - let suri: String = Cli + let suri: String = command + .cli .input("Signer calling the contract:") .placeholder("//Alice") .default_input("//Alice") @@ -229,7 +257,8 @@ async fn guide_user_to_call_contract() -> anyhow::Result { let mut is_call_confirmed: bool = true; if message.mutates { - is_call_confirmed = Cli + is_call_confirmed = command + .cli .confirm("Do you want to execute the call? (Selecting 'No' will perform a dry run)") .initial_value(true) .interact()?; @@ -250,12 +279,204 @@ async fn guide_user_to_call_contract() -> anyhow::Result { }) } -fn display_select_options(messages: &Vec) -> Result<&Message> { - let mut cli = Cli; - let mut prompt = cli.select("Select the message to call:"); - for message in messages { - prompt = prompt.item(message, &message.label, &message.docs); +#[cfg(test)] +mod tests { + use super::*; + use crate::cli::MockCli; + use pop_contracts::{create_smart_contract, Contract}; + use std::{env, fs}; + use url::Url; + + fn generate_smart_contract_test_environment() -> Result { + let temp_dir = tempfile::tempdir().expect("Could not create temp dir"); + let temp_contract_dir = temp_dir.path().join("testing"); + fs::create_dir(&temp_contract_dir)?; + create_smart_contract("testing", temp_contract_dir.as_path(), &Contract::Standard)?; + Ok(temp_dir) + } + // Function that mocks the build process generating the contract artifacts. + fn mock_build_process(temp_contract_dir: PathBuf) -> Result<()> { + // Create a target directory + let target_contract_dir = temp_contract_dir.join("target"); + fs::create_dir(&target_contract_dir)?; + fs::create_dir(&target_contract_dir.join("ink"))?; + // Copy a mocked testing.contract and testing.json files inside the target directory + let current_dir = env::current_dir().expect("Failed to get current directory"); + let contract_file = current_dir.join("../../tests/files/testing.contract"); + fs::copy(contract_file, &target_contract_dir.join("ink/testing.contract"))?; + let metadata_file = current_dir.join("../../tests/files/testing.json"); + fs::copy(metadata_file, &target_contract_dir.join("ink/testing.json"))?; + Ok(()) + } + + #[tokio::test] + async fn call_contract_messages_are_ok() -> Result<()> { + let temp_dir = generate_smart_contract_test_environment()?; + mock_build_process(temp_dir.path().join("testing"))?; + + let mut cli = MockCli::new() + .expect_intro(&"Call a contract") + .expect_warning("Your call has not been executed.") + .expect_outro("Call completed successfully!"); + + // Contract deployed on Pop Network testnet, test get + Box::new(CallContract { + cli: &mut cli, + args: CallContractCommand { + path: Some(temp_dir.path().join("testing")), + contract: Some("14BfwaXddoarT9Z6LcfXBmunutk6Ssmy1kBdWX6sy9KRskJz".to_string()), + message: Some("get".to_string()), + args: vec![].to_vec(), + value: "0".to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, + suri: "//Alice".to_string(), + dry_run: false, + execute: false, + }, + }) + .execute() + .await?; + + cli.verify() + } + + // This test only covers the interactive portion of the call contract command, without actually calling the contract. + #[tokio::test] + async fn guide_user_to_query_contract_works() -> Result<()> { + let temp_dir = generate_smart_contract_test_environment()?; + mock_build_process(temp_dir.path().join("testing"))?; + + let items = vec![ + ("flip".into(), " A message that can be called on instantiated contracts. This one flips the value of the stored `bool` from `true` to `false` and vice versa.".into()), + ("get".into(), " Simply returns the current value of our `bool`.".into()), + ]; + // The inputs are processed in reverse order. + let mut cli = MockCli::new() + .expect_intro(&"Call a contract") + .expect_input("Signer calling the contract:", "//Alice".into()) + .expect_input( + "Where is your contract deployed?", + "wss://rpc1.paseo.popnetwork.xyz".into(), + ) + .expect_select::( + "Select the message to call:", + Some(false), + true, + Some(items), + 1, // "get" message + ) + .expect_input( + "Paste the on-chain contract address:", + "14BfwaXddoarT9Z6LcfXBmunutk6Ssmy1kBdWX6sy9KRskJz".into(), + ) + .expect_input( + "Where is your project located?", + temp_dir.path().join("testing").display().to_string(), + ); + + let call_config = guide_user_to_call_contract(&mut CallContract { + cli: &mut cli, + args: CallContractCommand { + path: Some(temp_dir.path().join("testing")), + contract: None, + message: None, + args: vec![].to_vec(), + value: "0".to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse("ws://localhost:9944")?, + suri: "//Alice".to_string(), + dry_run: false, + execute: false, + }, + }) + .await?; + assert_eq!( + call_config.contract, + Some("14BfwaXddoarT9Z6LcfXBmunutk6Ssmy1kBdWX6sy9KRskJz".to_string()) + ); + assert_eq!(call_config.message, Some("get".to_string())); + assert_eq!(call_config.args.len(), 0); + assert_eq!(call_config.value, "0".to_string()); + assert_eq!(call_config.gas_limit, None); + assert_eq!(call_config.proof_size, None); + assert_eq!(call_config.url.to_string(), "wss://rpc1.paseo.popnetwork.xyz/"); + assert_eq!(call_config.suri, "//Alice"); + assert!(!call_config.execute); + assert!(!call_config.dry_run); + + cli.verify() + } + + // This test only covers the interactive portion of the call contract command, without actually calling the contract. + #[tokio::test] + async fn guide_user_to_call_contract_works() -> Result<()> { + let temp_dir = generate_smart_contract_test_environment()?; + mock_build_process(temp_dir.path().join("testing"))?; + + let items = vec![ + ("flip".into(), " A message that can be called on instantiated contracts. This one flips the value of the stored `bool` from `true` to `false` and vice versa.".into()), + ("get".into(), " Simply returns the current value of our `bool`.".into()), + ]; + // The inputs are processed in reverse order. + let mut cli = MockCli::new() + .expect_intro(&"Call a contract") + .expect_input("Signer calling the contract:", "//Alice".into()) + .expect_input( + "Where is your contract deployed?", + "wss://rpc1.paseo.popnetwork.xyz".into(), + ) + .expect_select::( + "Select the message to call:", + Some(false), + true, + Some(items), + 0, // "flip" message + ) + .expect_input("Enter the proof size limit:", "".into()) + .expect_input("Enter the gas limit:", "".into()) + .expect_input( + "Paste the on-chain contract address:", + "14BfwaXddoarT9Z6LcfXBmunutk6Ssmy1kBdWX6sy9KRskJz".into(), + ) + .expect_input( + "Where is your project located?", + temp_dir.path().join("testing").display().to_string(), + ); + + let call_config = guide_user_to_call_contract(&mut CallContract { + cli: &mut cli, + args: CallContractCommand { + path: Some(temp_dir.path().join("testing")), + contract: None, + message: None, + args: vec![].to_vec(), + value: "0".to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse("ws://localhost:9944")?, + suri: "//Alice".to_string(), + dry_run: false, + execute: false, + }, + }) + .await?; + assert_eq!( + call_config.contract, + Some("14BfwaXddoarT9Z6LcfXBmunutk6Ssmy1kBdWX6sy9KRskJz".to_string()) + ); + assert_eq!(call_config.message, Some("flip".to_string())); + assert_eq!(call_config.args.len(), 0); + assert_eq!(call_config.value, "0".to_string()); + assert_eq!(call_config.gas_limit, None); + assert_eq!(call_config.proof_size, None); + assert_eq!(call_config.url.to_string(), "wss://rpc1.paseo.popnetwork.xyz/"); + assert_eq!(call_config.suri, "//Alice"); + assert!(call_config.execute); + assert!(!call_config.dry_run); + + cli.verify() } - let selected_message = prompt.interact()?; - Ok(selected_message) } diff --git a/crates/pop-cli/src/commands/mod.rs b/crates/pop-cli/src/commands/mod.rs index 8a2941c95..6e8967449 100644 --- a/crates/pop-cli/src/commands/mod.rs +++ b/crates/pop-cli/src/commands/mod.rs @@ -98,7 +98,12 @@ impl Command { }, #[cfg(feature = "contract")] Self::Call(args) => match args.command { - call::Command::Contract(cmd) => Box::new(cmd).execute().await.map(|_| Value::Null), + call::Command::Contract(cmd) => { + Box::new(call::contract::CallContract { cli: &mut Cli, args: cmd }) + .execute() + .await + .map(|_| Value::Null) + }, }, #[cfg(any(feature = "parachain", feature = "contract"))] Self::Up(args) => match args.command { diff --git a/crates/pop-contracts/src/call/mod.rs b/crates/pop-contracts/src/call/mod.rs index 91fb796f8..3ef57ef8f 100644 --- a/crates/pop-contracts/src/call/mod.rs +++ b/crates/pop-contracts/src/call/mod.rs @@ -185,7 +185,7 @@ mod tests { fs::create_dir(&target_contract_dir.join("ink"))?; // Copy a mocked testing.contract file inside the target directory let current_dir = env::current_dir().expect("Failed to get current directory"); - let contract_file = current_dir.join("tests/files/testing.contract"); + let contract_file = current_dir.join("../../tests/files/testing.contract"); fs::copy(contract_file, &target_contract_dir.join("ink/testing.contract"))?; Ok(()) } diff --git a/crates/pop-contracts/src/up.rs b/crates/pop-contracts/src/up.rs index 93a2742ed..f37a07dbc 100644 --- a/crates/pop-contracts/src/up.rs +++ b/crates/pop-contracts/src/up.rs @@ -239,7 +239,7 @@ mod tests { fs::create_dir(&target_contract_dir.join("ink"))?; // Copy a mocked testing.contract file inside the target directory let current_dir = env::current_dir().expect("Failed to get current directory"); - let contract_file = current_dir.join("tests/files/testing.contract"); + let contract_file = current_dir.join("../../tests/files/testing.contract"); fs::copy(contract_file, &target_contract_dir.join("ink/testing.contract"))?; Ok(()) } diff --git a/crates/pop-contracts/tests/files/testing.contract b/tests/files/testing.contract similarity index 100% rename from crates/pop-contracts/tests/files/testing.contract rename to tests/files/testing.contract diff --git a/tests/files/testing.json b/tests/files/testing.json new file mode 100644 index 000000000..78cff4b6b --- /dev/null +++ b/tests/files/testing.json @@ -0,0 +1,424 @@ +{ + "source": { + "hash": "0x29972a7ea06ca802e5d857dfb7cb6455220679f4e9314a533840b9865ccbb799", + "language": "ink! 5.0.0", + "compiler": "rustc 1.78.0", + "build_info": { + "rust_toolchain": "stable-aarch64-apple-darwin", + "cargo_contract_version": "4.1.1", + "build_mode": "Release", + "wasm_opt_settings": { + "optimization_passes": "Z", + "keep_debug_symbols": false + } + } + }, + "contract": { + "name": "testing", + "version": "0.1.0", + "authors": [ + "[your_name] <[your_email]>" + ] + }, + "image": null, + "version": 5, + "types": [ + { + "id": 0, + "type": { + "def": { + "primitive": "bool" + } + } + }, + { + "id": 1, + "type": { + "path": [ + "testing", + "testing", + "Testing" + ], + "def": { + "composite": { + "fields": [ + { + "name": "value", + "type": 0, + "typeName": ",>>::Type" + } + ] + } + } + } + }, + { + "id": 2, + "type": { + "path": [ + "Result" + ], + "params": [ + { + "name": "T", + "type": 3 + }, + { + "name": "E", + "type": 4 + } + ], + "def": { + "variant": { + "variants": [ + { + "name": "Ok", + "fields": [ + { + "type": 3 + } + ], + "index": 0 + }, + { + "name": "Err", + "fields": [ + { + "type": 4 + } + ], + "index": 1 + } + ] + } + } + } + }, + { + "id": 3, + "type": { + "def": { + "tuple": [] + } + } + }, + { + "id": 4, + "type": { + "path": [ + "ink_primitives", + "LangError" + ], + "def": { + "variant": { + "variants": [ + { + "name": "CouldNotReadInput", + "index": 1 + } + ] + } + } + } + }, + { + "id": 5, + "type": { + "path": [ + "Result" + ], + "params": [ + { + "name": "T", + "type": 0 + }, + { + "name": "E", + "type": 4 + } + ], + "def": { + "variant": { + "variants": [ + { + "name": "Ok", + "fields": [ + { + "type": 0 + } + ], + "index": 0 + }, + { + "name": "Err", + "fields": [ + { + "type": 4 + } + ], + "index": 1 + } + ] + } + } + } + }, + { + "id": 6, + "type": { + "path": [ + "ink_primitives", + "types", + "AccountId" + ], + "def": { + "composite": { + "fields": [ + { + "type": 7, + "typeName": "[u8; 32]" + } + ] + } + } + } + }, + { + "id": 7, + "type": { + "def": { + "array": { + "len": 32, + "type": 8 + } + } + } + }, + { + "id": 8, + "type": { + "def": { + "primitive": "u8" + } + } + }, + { + "id": 9, + "type": { + "def": { + "primitive": "u128" + } + } + }, + { + "id": 10, + "type": { + "path": [ + "ink_primitives", + "types", + "Hash" + ], + "def": { + "composite": { + "fields": [ + { + "type": 7, + "typeName": "[u8; 32]" + } + ] + } + } + } + }, + { + "id": 11, + "type": { + "def": { + "primitive": "u64" + } + } + }, + { + "id": 12, + "type": { + "def": { + "primitive": "u32" + } + } + }, + { + "id": 13, + "type": { + "path": [ + "ink_env", + "types", + "NoChainExtension" + ], + "def": { + "variant": {} + } + } + } + ], + "storage": { + "root": { + "root_key": "0x00000000", + "layout": { + "struct": { + "name": "Testing", + "fields": [ + { + "name": "value", + "layout": { + "leaf": { + "key": "0x00000000", + "ty": 0 + } + } + } + ] + } + }, + "ty": 1 + } + }, + "spec": { + "constructors": [ + { + "label": "new", + "selector": "0x9bae9d5e", + "payable": false, + "args": [ + { + "label": "init_value", + "type": { + "type": 0, + "displayName": [ + "bool" + ] + } + } + ], + "returnType": { + "type": 2, + "displayName": [ + "ink_primitives", + "ConstructorResult" + ] + }, + "docs": [ + "Constructor that initializes the `bool` value to the given `init_value`." + ], + "default": false + }, + { + "label": "default", + "selector": "0xed4b9d1b", + "payable": false, + "args": [], + "returnType": { + "type": 2, + "displayName": [ + "ink_primitives", + "ConstructorResult" + ] + }, + "docs": [ + "Constructor that initializes the `bool` value to `false`.", + "", + "Constructors can delegate to other constructors." + ], + "default": false + } + ], + "messages": [ + { + "label": "flip", + "selector": "0x633aa551", + "mutates": true, + "payable": false, + "args": [], + "returnType": { + "type": 2, + "displayName": [ + "ink", + "MessageResult" + ] + }, + "docs": [ + " A message that can be called on instantiated contracts.", + " This one flips the value of the stored `bool` from `true`", + " to `false` and vice versa." + ], + "default": false + }, + { + "label": "get", + "selector": "0x2f865bd9", + "mutates": false, + "payable": false, + "args": [], + "returnType": { + "type": 5, + "displayName": [ + "ink", + "MessageResult" + ] + }, + "docs": [ + " Simply returns the current value of our `bool`." + ], + "default": false + } + ], + "events": [], + "docs": [], + "lang_error": { + "type": 4, + "displayName": [ + "ink", + "LangError" + ] + }, + "environment": { + "accountId": { + "type": 6, + "displayName": [ + "AccountId" + ] + }, + "balance": { + "type": 9, + "displayName": [ + "Balance" + ] + }, + "hash": { + "type": 10, + "displayName": [ + "Hash" + ] + }, + "timestamp": { + "type": 11, + "displayName": [ + "Timestamp" + ] + }, + "blockNumber": { + "type": 12, + "displayName": [ + "BlockNumber" + ] + }, + "chainExtension": { + "type": 13, + "displayName": [ + "ChainExtension" + ] + }, + "maxEventTopics": 4, + "staticBufferSize": 16384 + } + } +} \ No newline at end of file From 48934bd21d1b352ba8b54e56703222eddf2693e8 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Mon, 9 Sep 2024 17:36:46 +0200 Subject: [PATCH 088/211] test: unit contracts crate --- crates/pop-contracts/src/call/metadata.rs | 40 +++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/crates/pop-contracts/src/call/metadata.rs b/crates/pop-contracts/src/call/metadata.rs index 0de02cf90..cdc1071a2 100644 --- a/crates/pop-contracts/src/call/metadata.rs +++ b/crates/pop-contracts/src/call/metadata.rs @@ -53,3 +53,43 @@ fn process_args(message_params: &[MessageParamSpec]) -> Vec Result { + let temp_dir = tempfile::tempdir().expect("Could not create temp dir"); + let temp_contract_dir = temp_dir.path().join("testing"); + fs::create_dir(&temp_contract_dir)?; + create_smart_contract("testing", temp_contract_dir.as_path(), &Contract::Standard)?; + Ok(temp_dir) + } + // Function that mocks the build process generating the contract artifacts. + fn mock_build_process(temp_contract_dir: PathBuf) -> Result<(), Error> { + // Create a target directory + let target_contract_dir = temp_contract_dir.join("target"); + fs::create_dir(&target_contract_dir)?; + fs::create_dir(&target_contract_dir.join("ink"))?; + // Copy a mocked testing.contract file inside the target directory + let current_dir = env::current_dir().expect("Failed to get current directory"); + let contract_file = current_dir.join("../../tests/files/testing.contract"); + fs::copy(contract_file, &target_contract_dir.join("ink/testing.contract"))?; + Ok(()) + } + #[test] + fn get_messages_work() -> Result<()> { + let temp_dir = generate_smart_contract_test_environment()?; + mock_build_process(temp_dir.path().join("testing"))?; + let message = get_messages(&temp_dir.path().join("testing"))?; + assert_eq!(message.len(), 2); + assert_eq!(message[0].label, "flip"); + assert_eq!(message[0].docs, " A message that can be called on instantiated contracts. This one flips the value of the stored `bool` from `true` to `false` and vice versa."); + assert_eq!(message[1].label, "get"); + assert_eq!(message[1].docs, " Simply returns the current value of our `bool`."); + Ok(()) + } +} From 82f932ff342cb01ce8b9bf15bf2d866d9b369c41 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Mon, 9 Sep 2024 22:14:50 +0200 Subject: [PATCH 089/211] chore: format --- crates/pop-cli/src/commands/call/contract.rs | 6 ++++-- crates/pop-cli/src/commands/mod.rs | 5 ++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 0c4f5ac3f..57a8fc94f 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -342,7 +342,8 @@ mod tests { cli.verify() } - // This test only covers the interactive portion of the call contract command, without actually calling the contract. + // This test only covers the interactive portion of the call contract command, without actually + // calling the contract. #[tokio::test] async fn guide_user_to_query_contract_works() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; @@ -410,7 +411,8 @@ mod tests { cli.verify() } - // This test only covers the interactive portion of the call contract command, without actually calling the contract. + // This test only covers the interactive portion of the call contract command, without actually + // calling the contract. #[tokio::test] async fn guide_user_to_call_contract_works() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; diff --git a/crates/pop-cli/src/commands/mod.rs b/crates/pop-cli/src/commands/mod.rs index 6e8967449..4530ce4cb 100644 --- a/crates/pop-cli/src/commands/mod.rs +++ b/crates/pop-cli/src/commands/mod.rs @@ -98,12 +98,11 @@ impl Command { }, #[cfg(feature = "contract")] Self::Call(args) => match args.command { - call::Command::Contract(cmd) => { + call::Command::Contract(cmd) => Box::new(call::contract::CallContract { cli: &mut Cli, args: cmd }) .execute() .await - .map(|_| Value::Null) - }, + .map(|_| Value::Null), }, #[cfg(any(feature = "parachain", feature = "contract"))] Self::Up(args) => match args.command { From 97ac2208055c94a8095e4d513d1c1babdd2e30c1 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 10 Sep 2024 11:00:55 +0200 Subject: [PATCH 090/211] test: refactor and improve test cases --- crates/pop-cli/src/cli.rs | 3 - crates/pop-cli/src/commands/call/contract.rs | 77 +++++++++---------- crates/pop-contracts/src/call/metadata.rs | 38 ++++----- crates/pop-contracts/src/call/mod.rs | 56 +++++++------- crates/pop-contracts/src/init_tests.rs | 29 +++++++ crates/pop-contracts/src/lib.rs | 2 + crates/pop-contracts/src/up.rs | 68 +++++++++------- .../tests/files/testing.contract | 1 + .../pop-contracts/tests}/files/testing.json | 33 +++++++- tests/files/testing.contract | 1 - 10 files changed, 182 insertions(+), 126 deletions(-) create mode 100644 crates/pop-contracts/src/init_tests.rs create mode 100644 crates/pop-contracts/tests/files/testing.contract rename {tests => crates/pop-contracts/tests}/files/testing.json (90%) delete mode 100644 tests/files/testing.contract diff --git a/crates/pop-cli/src/cli.rs b/crates/pop-cli/src/cli.rs index 65999288d..29598c7e7 100644 --- a/crates/pop-cli/src/cli.rs +++ b/crates/pop-cli/src/cli.rs @@ -424,12 +424,9 @@ pub(crate) mod tests { fn select(&mut self, prompt: impl Display) -> impl Select { let prompt = prompt.to_string(); - println!("prompt: {}", prompt); if let Some((expectation, _, collect, items_expectation, item)) = self.select_expectation.take() { - println!("expectation: {}", expectation); - println!("items_expectation: {:?}", items_expectation); assert_eq!(expectation, prompt, "prompt does not satisfy expectation"); return MockSelect { items_expectation, collect, items: vec![], item }; } diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 57a8fc94f..f1d9b8c2d 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -174,7 +174,6 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( .default_input("./") .interact()?; let contract_path = Path::new(&input_path); - println!("path: {:?}", contract_path); // Prompt for contract address. let contract_address: String = command @@ -283,36 +282,20 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( mod tests { use super::*; use crate::cli::MockCli; - use pop_contracts::{create_smart_contract, Contract}; - use std::{env, fs}; + use pop_contracts::{generate_smart_contract_test_environment, mock_build_process}; + use std::env; use url::Url; - fn generate_smart_contract_test_environment() -> Result { - let temp_dir = tempfile::tempdir().expect("Could not create temp dir"); - let temp_contract_dir = temp_dir.path().join("testing"); - fs::create_dir(&temp_contract_dir)?; - create_smart_contract("testing", temp_contract_dir.as_path(), &Contract::Standard)?; - Ok(temp_dir) - } - // Function that mocks the build process generating the contract artifacts. - fn mock_build_process(temp_contract_dir: PathBuf) -> Result<()> { - // Create a target directory - let target_contract_dir = temp_contract_dir.join("target"); - fs::create_dir(&target_contract_dir)?; - fs::create_dir(&target_contract_dir.join("ink"))?; - // Copy a mocked testing.contract and testing.json files inside the target directory - let current_dir = env::current_dir().expect("Failed to get current directory"); - let contract_file = current_dir.join("../../tests/files/testing.contract"); - fs::copy(contract_file, &target_contract_dir.join("ink/testing.contract"))?; - let metadata_file = current_dir.join("../../tests/files/testing.json"); - fs::copy(metadata_file, &target_contract_dir.join("ink/testing.json"))?; - Ok(()) - } - #[tokio::test] async fn call_contract_messages_are_ok() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; - mock_build_process(temp_dir.path().join("testing"))?; + let mut current_dir = env::current_dir().expect("Failed to get current directory"); + current_dir.pop(); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("pop-contracts/tests/files/testing.contract"), + current_dir.join("pop-contracts/tests/files/testing.json"), + )?; let mut cli = MockCli::new() .expect_intro(&"Call a contract") @@ -324,7 +307,7 @@ mod tests { cli: &mut cli, args: CallContractCommand { path: Some(temp_dir.path().join("testing")), - contract: Some("14BfwaXddoarT9Z6LcfXBmunutk6Ssmy1kBdWX6sy9KRskJz".to_string()), + contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), message: Some("get".to_string()), args: vec![].to_vec(), value: "0".to_string(), @@ -347,7 +330,13 @@ mod tests { #[tokio::test] async fn guide_user_to_query_contract_works() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; - mock_build_process(temp_dir.path().join("testing"))?; + let mut current_dir = env::current_dir().expect("Failed to get current directory"); + current_dir.pop(); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("pop-contracts/tests/files/testing.contract"), + current_dir.join("pop-contracts/tests/files/testing.json"), + )?; let items = vec![ ("flip".into(), " A message that can be called on instantiated contracts. This one flips the value of the stored `bool` from `true` to `false` and vice versa.".into()), @@ -370,7 +359,7 @@ mod tests { ) .expect_input( "Paste the on-chain contract address:", - "14BfwaXddoarT9Z6LcfXBmunutk6Ssmy1kBdWX6sy9KRskJz".into(), + "15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".into(), ) .expect_input( "Where is your project located?", @@ -396,7 +385,7 @@ mod tests { .await?; assert_eq!( call_config.contract, - Some("14BfwaXddoarT9Z6LcfXBmunutk6Ssmy1kBdWX6sy9KRskJz".to_string()) + Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) ); assert_eq!(call_config.message, Some("get".to_string())); assert_eq!(call_config.args.len(), 0); @@ -416,11 +405,18 @@ mod tests { #[tokio::test] async fn guide_user_to_call_contract_works() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; - mock_build_process(temp_dir.path().join("testing"))?; + let mut current_dir = env::current_dir().expect("Failed to get current directory"); + current_dir.pop(); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("pop-contracts/tests/files/testing.contract"), + current_dir.join("pop-contracts/tests/files/testing.json"), + )?; let items = vec![ ("flip".into(), " A message that can be called on instantiated contracts. This one flips the value of the stored `bool` from `true` to `false` and vice versa.".into()), ("get".into(), " Simply returns the current value of our `bool`.".into()), + ("specific_flip".into(), " A message for testing, flips the value of the stored `bool` with `new_value` and is payable".into()) ]; // The inputs are processed in reverse order. let mut cli = MockCli::new() @@ -430,18 +426,20 @@ mod tests { "Where is your contract deployed?", "wss://rpc1.paseo.popnetwork.xyz".into(), ) + .expect_input("Enter the proof size limit:", "".into()) // Only if call + .expect_input("Enter the gas limit:", "".into()) // Only if call + .expect_input("Value to transfer to the call:", "50".into()) // Only if payable + .expect_input("new_value", "true".into()) // Args for specific_flip .expect_select::( "Select the message to call:", Some(false), true, Some(items), - 0, // "flip" message + 2, // "specific_flip" message ) - .expect_input("Enter the proof size limit:", "".into()) - .expect_input("Enter the gas limit:", "".into()) .expect_input( "Paste the on-chain contract address:", - "14BfwaXddoarT9Z6LcfXBmunutk6Ssmy1kBdWX6sy9KRskJz".into(), + "15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".into(), ) .expect_input( "Where is your project located?", @@ -467,11 +465,12 @@ mod tests { .await?; assert_eq!( call_config.contract, - Some("14BfwaXddoarT9Z6LcfXBmunutk6Ssmy1kBdWX6sy9KRskJz".to_string()) + Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) ); - assert_eq!(call_config.message, Some("flip".to_string())); - assert_eq!(call_config.args.len(), 0); - assert_eq!(call_config.value, "0".to_string()); + assert_eq!(call_config.message, Some("specific_flip".to_string())); + assert_eq!(call_config.args.len(), 1); + assert_eq!(call_config.args[0], "true".to_string()); + assert_eq!(call_config.value, "50".to_string()); assert_eq!(call_config.gas_limit, None); assert_eq!(call_config.proof_size, None); assert_eq!(call_config.url.to_string(), "wss://rpc1.paseo.popnetwork.xyz/"); diff --git a/crates/pop-contracts/src/call/metadata.rs b/crates/pop-contracts/src/call/metadata.rs index cdc1071a2..a2b378e63 100644 --- a/crates/pop-contracts/src/call/metadata.rs +++ b/crates/pop-contracts/src/call/metadata.rs @@ -7,7 +7,6 @@ use scale_info::form::PortableForm; use std::path::Path; #[derive(Clone, PartialEq, Eq)] -// TODO: We are ignoring selector, return type for now. /// Describes a contract message. pub struct Message { /// The label of the message. @@ -56,40 +55,31 @@ fn process_args(message_params: &[MessageParamSpec]) -> Vec Result { - let temp_dir = tempfile::tempdir().expect("Could not create temp dir"); - let temp_contract_dir = temp_dir.path().join("testing"); - fs::create_dir(&temp_contract_dir)?; - create_smart_contract("testing", temp_contract_dir.as_path(), &Contract::Standard)?; - Ok(temp_dir) - } - // Function that mocks the build process generating the contract artifacts. - fn mock_build_process(temp_contract_dir: PathBuf) -> Result<(), Error> { - // Create a target directory - let target_contract_dir = temp_contract_dir.join("target"); - fs::create_dir(&target_contract_dir)?; - fs::create_dir(&target_contract_dir.join("ink"))?; - // Copy a mocked testing.contract file inside the target directory - let current_dir = env::current_dir().expect("Failed to get current directory"); - let contract_file = current_dir.join("../../tests/files/testing.contract"); - fs::copy(contract_file, &target_contract_dir.join("ink/testing.contract"))?; - Ok(()) - } #[test] fn get_messages_work() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; - mock_build_process(temp_dir.path().join("testing"))?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; let message = get_messages(&temp_dir.path().join("testing"))?; - assert_eq!(message.len(), 2); + assert_eq!(message.len(), 3); assert_eq!(message[0].label, "flip"); assert_eq!(message[0].docs, " A message that can be called on instantiated contracts. This one flips the value of the stored `bool` from `true` to `false` and vice versa."); assert_eq!(message[1].label, "get"); assert_eq!(message[1].docs, " Simply returns the current value of our `bool`."); + assert_eq!(message[2].label, "specific_flip"); + assert_eq!(message[2].docs, " A message for testing, flips the value of the stored `bool` with `new_value` and is payable"); + // assert parsed arguments + assert_eq!(message[2].args, vec!["new_value".to_string()]); Ok(()) } } diff --git a/crates/pop-contracts/src/call/mod.rs b/crates/pop-contracts/src/call/mod.rs index 3ef57ef8f..ade88a482 100644 --- a/crates/pop-contracts/src/call/mod.rs +++ b/crates/pop-contracts/src/call/mod.rs @@ -160,40 +160,25 @@ pub async fn call_smart_contract( mod tests { use super::*; use crate::{ - contracts_node_generator, create_smart_contract, dry_run_gas_estimate_instantiate, - errors::Error, instantiate_smart_contract, run_contracts_node, set_up_deployment, Contract, - UpOpts, + contracts_node_generator, dry_run_gas_estimate_instantiate, errors::Error, + generate_smart_contract_test_environment, instantiate_smart_contract, mock_build_process, + run_contracts_node, set_up_deployment, UpOpts, }; use anyhow::Result; use sp_core::Bytes; - use std::{env, fs, process::Command}; + use std::{env, process::Command}; const CONTRACTS_NETWORK_URL: &str = "wss://rpc2.paseo.popnetwork.xyz"; - fn generate_smart_contract_test_environment() -> Result { - let temp_dir = tempfile::tempdir().expect("Could not create temp dir"); - let temp_contract_dir = temp_dir.path().join("testing"); - fs::create_dir(&temp_contract_dir)?; - create_smart_contract("testing", temp_contract_dir.as_path(), &Contract::Standard)?; - Ok(temp_dir) - } - // Function that mocks the build process generating the contract artifacts. - fn mock_build_process(temp_contract_dir: PathBuf) -> Result<(), Error> { - // Create a target directory - let target_contract_dir = temp_contract_dir.join("target"); - fs::create_dir(&target_contract_dir)?; - fs::create_dir(&target_contract_dir.join("ink"))?; - // Copy a mocked testing.contract file inside the target directory - let current_dir = env::current_dir().expect("Failed to get current directory"); - let contract_file = current_dir.join("../../tests/files/testing.contract"); - fs::copy(contract_file, &target_contract_dir.join("ink/testing.contract"))?; - Ok(()) - } - #[tokio::test] async fn test_set_up_call() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; - mock_build_process(temp_dir.path().join("testing"))?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; let call_opts = CallOpts { path: Some(temp_dir.path().join("testing")), @@ -259,7 +244,12 @@ mod tests { #[tokio::test] async fn test_dry_run_call_error_contract_not_deployed() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; - mock_build_process(temp_dir.path().join("testing"))?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; let call_opts = CallOpts { path: Some(temp_dir.path().join("testing")), @@ -281,7 +271,12 @@ mod tests { #[tokio::test] async fn test_dry_run_estimate_call_error_contract_not_deployed() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; - mock_build_process(temp_dir.path().join("testing"))?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; let call_opts = CallOpts { path: Some(temp_dir.path().join("testing")), @@ -307,7 +302,12 @@ mod tests { async fn call_works() -> Result<()> { const LOCALHOST_URL: &str = "ws://127.0.0.1:9944"; let temp_dir = generate_smart_contract_test_environment()?; - mock_build_process(temp_dir.path().join("testing"))?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; let cache = temp_dir.path().join(""); diff --git a/crates/pop-contracts/src/init_tests.rs b/crates/pop-contracts/src/init_tests.rs new file mode 100644 index 000000000..f1c24c7b9 --- /dev/null +++ b/crates/pop-contracts/src/init_tests.rs @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-3.0 + +use crate::{create_smart_contract, Contract}; +use anyhow::Result; +use std::{fs, path::PathBuf}; + +pub fn generate_smart_contract_test_environment() -> Result { + let temp_dir = tempfile::tempdir().expect("Could not create temp dir"); + let temp_contract_dir = temp_dir.path().join("testing"); + fs::create_dir(&temp_contract_dir)?; + create_smart_contract("testing", temp_contract_dir.as_path(), &Contract::Standard)?; + Ok(temp_dir) +} + +// Function that mocks the build process generating the contract artifacts. +pub fn mock_build_process( + temp_contract_dir: PathBuf, + contract_file: PathBuf, + metadata_file: PathBuf, +) -> Result<()> { + // Create a target directory + let target_contract_dir = temp_contract_dir.join("target"); + fs::create_dir(&target_contract_dir)?; + fs::create_dir(&target_contract_dir.join("ink"))?; + // Copy a mocked testing.contract and testing.json files inside the target directory + fs::copy(contract_file, &target_contract_dir.join("ink/testing.contract"))?; + fs::copy(metadata_file, &target_contract_dir.join("ink/testing.json"))?; + Ok(()) +} diff --git a/crates/pop-contracts/src/lib.rs b/crates/pop-contracts/src/lib.rs index c883c165c..4fd24bd4e 100644 --- a/crates/pop-contracts/src/lib.rs +++ b/crates/pop-contracts/src/lib.rs @@ -4,6 +4,7 @@ mod build; mod call; mod errors; +mod init_tests; mod new; mod node; mod templates; @@ -17,6 +18,7 @@ pub use call::{ metadata::{get_messages, Message}, set_up_call, CallOpts, }; +pub use init_tests::{generate_smart_contract_test_environment, mock_build_process}; pub use new::{create_smart_contract, is_valid_contract_name}; pub use node::{contracts_node_generator, is_chain_alive, run_contracts_node}; pub use templates::{Contract, ContractType}; diff --git a/crates/pop-contracts/src/up.rs b/crates/pop-contracts/src/up.rs index f37a07dbc..6a081386e 100644 --- a/crates/pop-contracts/src/up.rs +++ b/crates/pop-contracts/src/up.rs @@ -215,39 +215,24 @@ pub async fn upload_smart_contract( mod tests { use super::*; use crate::{ - contracts_node_generator, create_smart_contract, errors::Error, run_contracts_node, - templates::Contract, + contracts_node_generator, errors::Error, generate_smart_contract_test_environment, + mock_build_process, run_contracts_node, }; use anyhow::Result; - use std::{env, fs, process::Command}; + use std::{env, process::Command}; use url::Url; const CONTRACTS_NETWORK_URL: &str = "wss://rpc2.paseo.popnetwork.xyz"; - fn generate_smart_contract_test_environment() -> Result { - let temp_dir = tempfile::tempdir().expect("Could not create temp dir"); - let temp_contract_dir = temp_dir.path().join("testing"); - fs::create_dir(&temp_contract_dir)?; - create_smart_contract("testing", temp_contract_dir.as_path(), &Contract::Standard)?; - Ok(temp_dir) - } - // Function that mocks the build process generating the contract artifacts. - fn mock_build_process(temp_contract_dir: PathBuf) -> Result<(), Error> { - // Create a target directory - let target_contract_dir = temp_contract_dir.join("target"); - fs::create_dir(&target_contract_dir)?; - fs::create_dir(&target_contract_dir.join("ink"))?; - // Copy a mocked testing.contract file inside the target directory - let current_dir = env::current_dir().expect("Failed to get current directory"); - let contract_file = current_dir.join("../../tests/files/testing.contract"); - fs::copy(contract_file, &target_contract_dir.join("ink/testing.contract"))?; - Ok(()) - } - #[tokio::test] async fn set_up_deployment_works() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; - mock_build_process(temp_dir.path().join("testing"))?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; let up_opts = UpOpts { path: Some(temp_dir.path().join("testing")), constructor: "new".to_string(), @@ -266,7 +251,12 @@ mod tests { #[tokio::test] async fn set_up_upload_works() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; - mock_build_process(temp_dir.path().join("testing"))?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; let up_opts = UpOpts { path: Some(temp_dir.path().join("testing")), constructor: "new".to_string(), @@ -285,7 +275,12 @@ mod tests { #[tokio::test] async fn dry_run_gas_estimate_instantiate_works() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; - mock_build_process(temp_dir.path().join("testing"))?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; let up_opts = UpOpts { path: Some(temp_dir.path().join("testing")), constructor: "new".to_string(), @@ -307,7 +302,12 @@ mod tests { #[tokio::test] async fn dry_run_gas_estimate_instantiate_throw_custom_error() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; - mock_build_process(temp_dir.path().join("testing"))?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; let up_opts = UpOpts { path: Some(temp_dir.path().join("testing")), constructor: "new".to_string(), @@ -330,7 +330,12 @@ mod tests { #[tokio::test] async fn dry_run_upload_throw_custom_error() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; - mock_build_process(temp_dir.path().join("testing"))?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; let up_opts = UpOpts { path: Some(temp_dir.path().join("testing")), constructor: "new".to_string(), @@ -353,7 +358,12 @@ mod tests { async fn instantiate_and_upload() -> Result<()> { const LOCALHOST_URL: &str = "ws://127.0.0.1:9944"; let temp_dir = generate_smart_contract_test_environment()?; - mock_build_process(temp_dir.path().join("testing"))?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; let cache = temp_dir.path().join(""); diff --git a/crates/pop-contracts/tests/files/testing.contract b/crates/pop-contracts/tests/files/testing.contract new file mode 100644 index 000000000..5fb54f3ce --- /dev/null +++ b/crates/pop-contracts/tests/files/testing.contract @@ -0,0 +1 @@ +{"source":{"hash":"0x80776e58b218850d7d86447b2edea78d827ed0ed2499ff3a92b7ea10e4f95eb5","language":"ink! 5.0.0","compiler":"rustc 1.78.0","wasm":"0x0061736d01000000012b0860027f7f0060037f7f7f017f60000060047f7f7f7f017f60037f7f7f0060017f006000017f60017f017f027406057365616c310b6765745f73746f726167650003057365616c3005696e7075740000057365616c320b7365745f73746f726167650003057365616c300b7365616c5f72657475726e0004057365616c301176616c75655f7472616e73666572726564000003656e76066d656d6f72790201021003100f0101010105040006070002050002020616037f01418080040b7f00418080050b7f00418080050b0711020463616c6c0012066465706c6f7900130aa80c0f2b01017f037f2002200346047f200005200020036a200120036a2d00003a0000200341016a21030c010b0b0b6f01017f0240200020014d04402000210303402002450d02200320012d00003a0000200341016a2103200141016a2101200241016b21020c000b000b200041016b2103200141016b210103402002450d01200220036a200120026a2d00003a0000200241016b21020c000b000b20000b2501017f037f2002200346047f200005200020036a20013a0000200341016a21030c010b0b0b3f01027f0340200245044041000f0b200241016b210220012d0000210320002d00002104200141016a2101200041016a210020032004460d000b200420036b0b2601017f230041106b220124002001410036020c20002001410c6a4104100a200141106a24000b4801027f024002402000280208220320026a22042003490d00200420002802044b0d00200420036b2002470d01200028020020036a2001200210051a200020043602080f0b000b000b2601017f230041106b22022400200220003a000f20012002410f6a4101100a200241106a24000b6102027f027e230041206b22002400200041106a22014200370300200042003703082000411036021c200041086a2000411c6a1004200028021c41114f0440000b2001290300210220002903082103200041206a2400410541042002200384501b0b3f01017f2000280204220145044041020f0b2000200141016b36020420002000280200220041016a3602004101410220002d000022004101461b410020001b0b3c01027f027f200145044041808004210141010c010b410121024180800441013a000041818004210141020b2103200120023a0000200020031011000b12004180800441003b0100410041021011000b8a0101057f230041106b22012400200142808001370208200141808004360204200141046a22041009024020012802082205200128020c2202490d00200128020421032001410036020c2001200520026b3602082001200220036a36020420002004100b200128020c220020012802084b0d00200320022001280204200010021a200141106a24000f0b000b0d0020004180800420011003000b990401067f230041106b2200240020004180800136020441808004200041046a10010240024020002802042202418180014f0d000240024020024104490d002000418480043602042000200241046b360208418380042d00002101418280042d00002104418180042d00002103418080042d00002202412f470440200241ec00470440200241e300470d02410221022003413a46200441a5014671200141d10046710d030c020b2003410f472004411d4772200141f70147720d01200041046a100d220241ff01714102460d010c020b41032102200341860146200441db004671200141d90146710d010b41014101100e000b200042808001370208200041808004360204200041046a2204100920002802082205200028020c2201490d00200028020421032000200520016b220536020420032001200120036a2201200410002000280204220320054b720d0020002003360208200020013602042004100d220141ff01714102460d0020002802080d000240024002404102200241026b41ff01712200200041024f1b41016b0e020100020b2002410171101041004100100e000b100c41ff01714105470d01230041106b220024002000418080043602044180800441003a00002000428080818010370208200141ff0171410047200041046a100b200028020c2200418180014f0440000b410020001011000b100c41ff01714105460d010b000b200141ff017145101041004100100e000be50101057f230041106b2200240002400240100c41ff01714105470d0020004180800136020c418080042000410c6a1001200028020c2201418180014f0d0020014104490d012000418480043602042000200141046b360208418380042d00002101418280042d00002102418180042d000021030240418080042d0000220441ed014704402004419b0147200341ae0147722002419d0147200141de004772720d03200041046a100d220041ff01714102470d010c030b200341cb00462002419d0146712001411b4671450d0241001010100f000b20001010100f000b000b41014101100e000b","build_info":{"rust_toolchain":"stable-aarch64-apple-darwin","cargo_contract_version":"5.0.0-alpha","build_mode":"Release","wasm_opt_settings":{"optimization_passes":"Z","keep_debug_symbols":false}}},"contract":{"name":"testing","version":"0.1.0","authors":["[your_name] <[your_email]>"]},"image":null,"version":5,"types":[{"id":0,"type":{"def":{"primitive":"bool"}}},{"id":1,"type":{"path":["testing","testing","Testing"],"def":{"composite":{"fields":[{"name":"value","type":0,"typeName":",>>::Type"}]}}}},{"id":2,"type":{"path":["Result"],"params":[{"name":"T","type":3},{"name":"E","type":4}],"def":{"variant":{"variants":[{"name":"Ok","fields":[{"type":3}],"index":0},{"name":"Err","fields":[{"type":4}],"index":1}]}}}},{"id":3,"type":{"def":{"tuple":[]}}},{"id":4,"type":{"path":["ink_primitives","LangError"],"def":{"variant":{"variants":[{"name":"CouldNotReadInput","index":1}]}}}},{"id":5,"type":{"path":["Result"],"params":[{"name":"T","type":0},{"name":"E","type":4}],"def":{"variant":{"variants":[{"name":"Ok","fields":[{"type":0}],"index":0},{"name":"Err","fields":[{"type":4}],"index":1}]}}}},{"id":6,"type":{"path":["ink_primitives","types","AccountId"],"def":{"composite":{"fields":[{"type":7,"typeName":"[u8; 32]"}]}}}},{"id":7,"type":{"def":{"array":{"len":32,"type":8}}}},{"id":8,"type":{"def":{"primitive":"u8"}}},{"id":9,"type":{"def":{"primitive":"u128"}}},{"id":10,"type":{"path":["ink_primitives","types","Hash"],"def":{"composite":{"fields":[{"type":7,"typeName":"[u8; 32]"}]}}}},{"id":11,"type":{"def":{"primitive":"u64"}}},{"id":12,"type":{"def":{"primitive":"u32"}}},{"id":13,"type":{"path":["ink_env","types","NoChainExtension"],"def":{"variant":{}}}}],"storage":{"root":{"root_key":"0x00000000","layout":{"struct":{"name":"Testing","fields":[{"name":"value","layout":{"leaf":{"key":"0x00000000","ty":0}}}]}},"ty":1}},"spec":{"constructors":[{"label":"new","selector":"0x9bae9d5e","payable":false,"args":[{"label":"init_value","type":{"type":0,"displayName":["bool"]}}],"returnType":{"type":2,"displayName":["ink_primitives","ConstructorResult"]},"docs":["Constructor that initializes the `bool` value to the given `init_value`."],"default":false},{"label":"default","selector":"0xed4b9d1b","payable":false,"args":[],"returnType":{"type":2,"displayName":["ink_primitives","ConstructorResult"]},"docs":["Constructor that initializes the `bool` value to `false`.","","Constructors can delegate to other constructors."],"default":false}],"messages":[{"label":"flip","selector":"0x633aa551","mutates":true,"payable":false,"args":[],"returnType":{"type":2,"displayName":["ink","MessageResult"]},"docs":[" A message that can be called on instantiated contracts."," This one flips the value of the stored `bool` from `true`"," to `false` and vice versa."],"default":false},{"label":"get","selector":"0x2f865bd9","mutates":false,"payable":false,"args":[],"returnType":{"type":5,"displayName":["ink","MessageResult"]},"docs":[" Simply returns the current value of our `bool`."],"default":false},{"label":"specific_flip","selector":"0x6c0f1df7","mutates":true,"payable":true,"args":[{"label":"new_value","type":{"type":0,"displayName":["bool"]}}],"returnType":{"type":2,"displayName":["ink","MessageResult"]},"docs":[" A message for testing, flips the value of the stored `bool` with `new_value`"," and is payable"],"default":false}],"events":[],"docs":[],"lang_error":{"type":4,"displayName":["ink","LangError"]},"environment":{"accountId":{"type":6,"displayName":["AccountId"]},"balance":{"type":9,"displayName":["Balance"]},"hash":{"type":10,"displayName":["Hash"]},"timestamp":{"type":11,"displayName":["Timestamp"]},"blockNumber":{"type":12,"displayName":["BlockNumber"]},"chainExtension":{"type":13,"displayName":["ChainExtension"]},"maxEventTopics":4,"staticBufferSize":16384}}} \ No newline at end of file diff --git a/tests/files/testing.json b/crates/pop-contracts/tests/files/testing.json similarity index 90% rename from tests/files/testing.json rename to crates/pop-contracts/tests/files/testing.json index 78cff4b6b..ed230c98f 100644 --- a/tests/files/testing.json +++ b/crates/pop-contracts/tests/files/testing.json @@ -1,11 +1,11 @@ { "source": { - "hash": "0x29972a7ea06ca802e5d857dfb7cb6455220679f4e9314a533840b9865ccbb799", + "hash": "0x80776e58b218850d7d86447b2edea78d827ed0ed2499ff3a92b7ea10e4f95eb5", "language": "ink! 5.0.0", "compiler": "rustc 1.78.0", "build_info": { "rust_toolchain": "stable-aarch64-apple-darwin", - "cargo_contract_version": "4.1.1", + "cargo_contract_version": "5.0.0-alpha", "build_mode": "Release", "wasm_opt_settings": { "optimization_passes": "Z", @@ -369,6 +369,35 @@ " Simply returns the current value of our `bool`." ], "default": false + }, + { + "label": "specific_flip", + "selector": "0x6c0f1df7", + "mutates": true, + "payable": true, + "args": [ + { + "label": "new_value", + "type": { + "type": 0, + "displayName": [ + "bool" + ] + } + } + ], + "returnType": { + "type": 2, + "displayName": [ + "ink", + "MessageResult" + ] + }, + "docs": [ + " A message for testing, flips the value of the stored `bool` with `new_value`", + " and is payable" + ], + "default": false } ], "events": [], diff --git a/tests/files/testing.contract b/tests/files/testing.contract deleted file mode 100644 index 8701e6560..000000000 --- a/tests/files/testing.contract +++ /dev/null @@ -1 +0,0 @@ -{"source":{"hash":"0xb15348075722f8ac92352b8fcfd6fa3506e2a3f430adadcc79fa73cf23bfe9e7","language":"ink! 5.0.0","compiler":"rustc 1.78.0","wasm":"0x0061736d0100000001400b60037f7f7f017f60027f7f017f60027f7f0060037f7f7f0060017f0060047f7f7f7f017f60047f7f7f7f0060000060057f7f7f7f7f006000017f60017f017f028a0107057365616c310b6765745f73746f726167650005057365616c3005696e7075740002057365616c320b7365745f73746f726167650005057365616c300d64656275675f6d6573736167650001057365616c300b7365616c5f72657475726e0003057365616c301176616c75655f7472616e73666572726564000203656e76066d656d6f7279020102100335340000000006030403020901020a00010702040302020704070306020301010004000101010104020101080506050802010103000104050170010e0e0616037f01418080040b7f0041c092050b7f0041ba92050b0711020463616c6c001b066465706c6f79001d0913010041010b0d103528392a362529252627232c0ae63b342b01017f037f2002200346047f200005200020036a200120036a2d00003a0000200341016a21030c010b0b0b6f01017f0240200020014d04402000210303402002450d02200320012d00003a0000200341016a2103200141016a2101200241016b21020c000b000b200041016b2103200141016b210103402002450d01200220036a200120026a2d00003a0000200241016b21020c000b000b20000b2501017f037f2002200346047f200005200020036a20013a0000200341016a21030c010b0b0b3f01027f0340200245044041000f0b200241016b210220012d0000210320002d00002104200141016a2101200041016a210020032004460d000b200420036b0b2300200120034b04402001200341f49004100b000b20002001360204200020023602000b6801017f230041306b2203240020032001360204200320003602002003412c6a41033602002003410236020c200341a88a0436020820034202370214200341033602242003200341206a3602102003200341046a36022820032003360220200341086a20021011000b2601017f230041106b220124002001410036020c20002001410c6a4104100d200141106a24000b920101037f02402000280208220420026a220320044f04402003200028020422054b0d01200028020020046a200320046b2001200241d08f041033200020033602080f0b230041206b22002400200041013602042000420037020c200041988e043602082000412b36021c200041f286043602182000200041186a360200200041b08f041011000b2003200541c08f04100b000b2601017f230041106b22022400200220003a000f20012002410f6a4101100d200241106a24000b6d02037f027e230041206b22002400200041106a22014200370300200042003703082000411036021c200041086a2000411c6a1005200028021c220241114f04402002411041f49004100b000b2001290300210320002903082104200041206a2400410541042003200484501b0b810101017f230041306b220224002002410136020c200241988d043602082002420137021420024102360224200220002d00004102742200418092046a28020036022c20022000419492046a2802003602282002200241206a3602102002200241286a36022020012802142001280218200241086a10242100200241306a240020000b3c01017f230041206b22022400200241013b011c2002200136021820022000360214200241b88704360210200241988e0436020c2002410c6a102b000b4901017f230041106b22012400200141003a000f027f20002001410f6a410110134504404101410220012d000f22004101461b410020001b0c010b41020b2100200141106a240020000b3d01027f2000280204220320024922044504402001200220002802002201200241f0910410332000200320026b3602042000200120026a3602000b20040b11002001410036000020002001410410130b5301027f230041106b22002400200042808001370208200041ba9204360204200041046a220141001019200141001019200028020c2200418180014f044020004180800141f08004100b000b41002000101a000b5b01027f230041106b22022400200242808001370208200241ba9204360204200241046a22032001047f20034101101941010541000b1019200228020c2201418180014f044020014180800141f08004100b000b20002001101a000ba70102057f017e230041306b2201240020014100360220200142808001370228200141ba9204360224200141246a2202100c20012001290224370218200141106a200141186a2203200128022c10182001280214210420012802102105200129021821062001410036022c2001200637022420002002100e20012001290224370218200141086a2003200128022c1018200520042001280208200128020c10021a200141306a24000b7401027f230041206b220324002002200128020422044b04402003410136020c200341a48e0436020820034200370214200341988e04360210200341086a41f08f041011000b2001200420026b36020420012001280200220120026a3602002000200236020420002001360200200341206a24000b940101027f20002802082202200028020422034904402000200241016a360208200028020020026a20013a00000f0b230041306b2200240020002003360204200020023602002000412c6a41033602002000410236020c20004188880436020820004202370214200041033602242000200041206a360210200020003602282000200041046a360220200041086a41e08f041011000b0d00200041ba920420011004000bef0401077f230041406a220024000240024002400240100f41ff0171410546044020004180800136022841ba9204200041286a22011001200041106a200028022841ba920441808001100a2000200029031037022820012000411c6a10140d0320002d001f210120002d001e210220002d001d2103024020002d001c2204412f470440200441e300470d05410121042003413a47200241a5014772200141d1004772450d010c050b41002104200341860147200241db004772200141d90147720d040b2000410036022420004280800137022c200041ba9204360228200041286a2202100c2000200029022837021c200041086a2000411c6a20002802301018200028020c210320002802082105200028021c21012000200028022022063602282005200320012002100021022000200028022820012006100a02400240024020020e0400040401040b200028020021012000200028020436022c20002001360228200041286a1012220141ff01714102470440200028022c450d020b2000410136022c200041bc82043602280c060b2000410136022c2000418c82043602280c050b20040d02230041106b22002400200042808001370208200041ba9204360204200041046a220241001019200141ff01714100472002100e200028020c2200418180014f044020004180800141f08004100b000b41002000101a000b200041043a0028200041286a101c000b2000410136022c2000419c810436022820004200370234200041988e04360230200041286a41a481041011000b200141ff0171451017410041001016000b410141011016000b20004200370234200041988e04360230200041286a41e481041011000b4501017f230041206b2201240020014101360204200141988d043602002001420137020c2001410136021c200120003602182001200141186a360208200141e481041011000be90101057f230041206b220024000240100f220141ff0171410546044020004180800136021441ba9204200041146a22011001200041086a200028021441ba920441808001100a2000200029030837021420012000411c6a10140d0120002d001f210120002d001e210220002d001d210320002d001c2204419b01470440200341cb00462002419d0146712001411b467145200441ed0147720d02410010171015000b200341ae01472002419d014772200141de0047720d01200041146a1012220041ff01714102460d01200010171015000b200020013a0014200041146a101c000b410141011016000b6001027f230041106b2203240020022000280200200028020822046b4b0440200341086a200020042002101f2003280208200328020c1020200028020821040b200028020420046a2001200210061a2000200220046a360208200341106a24000b9a0301077f230041206b220624000240200220036a22032002490d00410121044108200128020022024101742205200320032005491b2203200341084d1b2203417f73411f76210502402002450440410021040c010b2006200236021c200620012802043602140b20062004360218200641086a210820032102200641146a2104230041106b22072400027f027f024020050440200241004e0d01410121054100210241040c030b2008410036020441010c010b027f2004280204044020042802082209450440200741086a20052002102120072802082104200728020c0c020b2004280200210a02402005200210222204450440410021040c010b2004200a200910061a0b20020c010b20072005200210212007280200210420072802040b210920082004200520041b3602042009200220041b21022004450b210541080b20086a200236020020082005360200200741106a2400200628020c21042006280208450440200120033602002001200436020441818080807821040c010b200628021021030b2000200336020420002004360200200641206a24000bcb0100024020004181808080784704402000450d01230041306b220024002000200136020c20004102360214200041a085043602102000420137021c2000410336022c2000200041286a36021820002000410c6a360228230041206b22012400200141003b011c200141b085043602182001200041106a360214200141b88704360210200141988e0436020c2001410c6a102b000b0f0b230041206b220024002000410136020c20004188830436020820004200370214200041988e04360210200041086a418084041011000b200041a892042d00001a200120021022210120002002360204200020013602000bc50101017f027f41ac92042d0000044041b092042802000c010b3f00210241b0920441c0920536020041ac920441013a000041b49204200241107436020041c092050b21020240027f4100200020026a41016b410020006b71220020016a22022000490d001a41b492042802002002490440200141ffff036a220241107640002200417f460d022000411074220020024180807c716a22022000490d0241b4920420023602004100200020016a22022000490d011a0b41b09204200236020020000b0f0b41000b0c00200041dc8204200110240b850401077f230041406a22032400200341033a003c2003412036022c200341003602382003200136023420032000360230200341003602242003410036021c027f0240024020022802102201450440200228020c22004103742105200041ffffffff01712106200228020421082002280200210720022802082101034020042005460d02200420076a220041046a28020022020440200328023020002802002002200328023428020c1100000d040b200441086a21042001280200210020012802042102200141086a210120002003411c6a2002110100450d000b0c020b200228021422044105742100200441ffffff3f712106200228020c2109200228020821052002280204210820022802002207210403402000450d01200441046a28020022020440200328023020042802002002200328023428020c1100000d030b2003200128021036022c200320012d001c3a003c20032001280218360238200341106a2005200141086a10372003200329031037021c200341086a20052001103720032003290308370224200441086a2104200041206b210020012802142102200141206a2101200520024103746a22022802002003411c6a2002280204110100450d000b0c010b200620084904402003280230200720064103746a22002802002000280204200328023428020c1100000d010b41000c010b41010b2101200341406b240020010b0300010b0c00200020012002101e41000bb20201047f230041106b220224000240027f0240024020014180014f04402002410036020c2001418010490d012001418080044f0d0220022001410c7641e001723a000c20022001410676413f71418001723a000d4102210341030c030b200028020822032000280200460440230041106b22042400200441086a200020034101101f2004280208200428020c1020200441106a2400200028020821030b2000200341016a360208200028020420036a20013a00000c030b2002200141067641c001723a000c4101210341020c010b20022001410676413f71418001723a000e20022001410c76413f71418001723a000d2002200141127641077141f001723a000c4103210341040b210420032002410c6a2205722001413f71418001723a0000200020052004101e0b200241106a240041000bdb05020b7f027e230041406a220324004127210202402000350200220d4290ce00540440200d210e0c010b0340200341196a20026a220041046b200d4290ce0080220e42f0b1037e200d7ca7220441ffff037141e4006e220641017441ac88046a2f00003b0000200041026b2006419c7f6c20046a41ffff037141017441ac88046a2f00003b0000200241046b2102200d42ffc1d72f562100200e210d20000d000b0b200ea7220041e3004b0440200241026b2202200341196a6a200ea7220441ffff037141e4006e2200419c7f6c20046a41ffff037141017441ac88046a2f00003b00000b02402000410a4f0440200241026b2202200341196a6a200041017441ac88046a2f00003b00000c010b200241016b2202200341196a6a20004130723a00000b200128021c22054101712207412720026b22066a2100410021042005410471044041988e04210441988e0441988e04102d20006a21000b412b418080c40020071b2107200341196a20026a2108024020012802004504404101210220012802142200200128021822012007200410300d01200020082006200128020c11000021020c010b2000200128020422094f04404101210220012802142200200128021822012007200410300d01200020082006200128020c11000021020c010b200541087104402001280210210b2001413036021020012d0020210c41012102200141013a0020200128021422052001280218220a2007200410300d01200341106a2001200920006b4101103120032802102200418080c400460d0120032802142104200520082006200a28020c1100000d01200020042005200a10320d012001200c3a00202001200b360210410021020c010b41012102200341086a2001200920006b4101103120032802082205418080c400460d00200328020c210920012802142200200128021822012007200410300d00200020082006200128020c1100000d002005200920002001103221020b200341406b240020020b1800200128021441d482044105200128021828020c1100000b0e0020002802001a03400c000b000baa0201017f230041406a220124002001200036020c20014102360214200141b08e043602102001420137021c2001410436022c2001200141286a36021820012001410c6a360228200141003602382001428080808010370230200141306a200141106a10234504402001280234210020012802382101024041b892042d000045044041b992042d00000d010b200020011003410947044041b8920441013a00000b41b9920441013a00000b000b230041406a220024002000413336020c200041c08504360208200041c4820436021420002001413f6a3602102000413c6a41063602002000410236021c2000419c880436021820004202370224200041023602342000200041306a3602202000200041106a3602382000200041086a360230200041186a41e086041011000b2200200042eeb4d39ded9bae93907f370308200042d4ce8f88d3c5f6dba47f3703000ba10301067f230041106b220224000240200120006b220141104f04402000200041036a417c71220520006b2200102e2005200120006b2200417c716a2000410371102e6a21042000410276210303402003450d0220022005200341c0012003200341c0014f1b41ac8b04102f200228020c21032002280208210520022002280200200228020422002000417c7141888d04102f024020022802042200450440410021010c010b2002280200220620004102746a21074100210103404100210003402001200020066a2802002201417f734107762001410676724181828408716a2101200041046a22004110470d000b200641106a22062007470d000b0b200141087641ff81fc0771200141ff81fc07716a418180046c41107620046a2104200228020c2201450d000b2002280208210020014102742103410021010340200120002802002201417f734107762001410676724181828408716a2101200041046a2100200341046b22030d000b200141087641ff81fc0771200141ff81fc07716a418180046c41107620046a21040c010b20002001102e21040b200241106a240020040b2c01017f200104400340200220002c000041bf7f4a6a2102200041016a2100200141016b22010d000b0b20020b6b01017f230041206b22052400200220034904402005410136020c200541a48e0436020820054200370214200541988e04360210200541086a20041011000b20002003360204200020013602002000200220036b36020c2000200120034102746a360208200541206a24000b39000240027f2002418080c40047044041012000200220012802101101000d011a0b20030d0141000b0f0b200020034100200128020c1100000b990101027f024002400240024020012d0020220441016b0e03010200030b200341ff01710d00410021040c020b20022104410021020c010b20024101762104200241016a41017621020b200441016a210420012802102103200128021821052001280214210102400340200441016b2204450d01200120032005280210110100450d000b418080c40021030b20002002360204200020033602000b3201017f027f0340200120012004460d011a200441016a2104200220002003280210110100450d000b200441016b0b2001490b78002001200346044020002002200110061a0f0b230041306b2200240020002003360204200020013602002000412c6a41033602002000410336020c200041fc8b0436020820004202370214200041033602242000200041206a360210200020003602282000200041046a360220200041086a20041011000bf60101067f2000027f418080c400200128020022022001280204460d001a2001200241016a2205360200024020022d0000220341187441187541004e0d002001200241026a220536020020022d0001413f7121042003411f712106200341df014d0440200641067420047221030c010b2001200241036a220536020020022d0002413f712004410674722104200341f00149044020042006410c747221030c010b2001200241046a2205360200418080c4002006411274418080f0007120022d0003413f71200441067472722203418080c400460d011a0b200120012802082207200520026b6a36020820030b360204200020073602000baa0301067f230041306b22022400200028020421042000280200210302400240200128020022062001280208220072044002402000450d00200128020c21002002410036022c200220033602242002200320046a360228200041016a21000340200041016b22000440200241186a200241246a1034200228021c418080c400470d010c020b0b200241106a200241246a10342002280214418080c400460d000240024020022802102205450d00200420054d04404100210020042005460d010c020b41002100200320056a2c00004140480d010b200321000b2005200420001b21042000200320001b21030b2006450440200128021420032004200128021828020c11000021000c030b200128020422002003200320046a102d22054d0d01200241086a2001200020056b410010314101210020022802082205418080c400460d02200228020c210620012802142207200320042001280218220128020c1100000d022005200620072001103221000c020b200128021420032004200128021828020c11000021000c010b200128021420032004200128021828020c11000021000b200241306a240020000b140020002802002001200028020428020c1101000b5501027f0240027f02400240200228020041016b0e020103000b200241046a0c010b200120022802044103746a22012802044105470d0120012802000b2802002104410121030b20002004360204200020033602000b0a0020002001200210240be00201067f230041406a22022400200028020021054101210002402001280214220441c88704410c2001280218220628020c22011100000d00200528020c21032002413c6a4103360200200241346a410336020020024103360214200241a087043602102002420337021c20022003410c6a3602382002200341086a3602302002410236022c200220033602282002200241286a220736021820042006200241106a10380d00200528020822030440200441d48704410220011100000d01200241386a200341106a290200370300200241306a200341086a29020037030020022003290200370328200420062007103821000c010b200220052802002203200528020428020c11020041002100200229030042e4dec78590d085de7d520d00200229030842c1f7f9e8cc93b2d141520d0041012100200441d48704410220011100000d00200420032802002003280204200111000021000b200241406b240020000b0bb0120100418080040ba7122f55736572732f616c65786265616e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f696e6b5f656e762d352e302e302f7372632f656e67696e652f6f6e5f636861696e2f696d706c732e727300000001006f0000001a01000032000000656e636f756e746572656420756e6578706563746564206572726f72800001001c000000000001006f000000e3000000170000002f55736572732f616c65786265616e2f446f63756d656e74732f726f6775652f74657374696e672f6c69622e72730000b40001002e000000060000000500000073746f7261676520656e7472792077617320656d70747900f400010017000000636f756c64206e6f742070726f7065726c79206465636f64652073746f7261676520656e747279001401010027000000070000000000000001000000080000004572726f72000000090000000c000000040000000a0000000b0000000c0000006361706163697479206f766572666c6f7700000074010100110000002f55736572732f616c65786265616e2f2e7275737475702f746f6f6c636861696e732f737461626c652d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f7261775f7665632e7273900101007000000019000000050000002f55736572732f616c65786265616e2f2e7275737475702f746f6f6c636861696e732f737461626c652d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f616c6c6f632e72736d656d6f727920616c6c6f636174696f6e206f6620206279746573206661696c65647e02010015000000930201000d000000100201006e000000a50100000d0000006120666f726d617474696e6720747261697420696d706c656d656e746174696f6e2072657475726e656420616e206572726f722f55736572732f616c65786265616e2f2e7275737475702f746f6f6c636861696e732f737461626c652d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f666d742e727300f30201006c0000007902000020000000293a63616c6c656420604f7074696f6e3a3a756e77726170282960206f6e206120604e6f6e65602076616c75650000001807010000000000710301000100000071030100010000000700000000000000010000000d00000070616e69636b6564206174203a0a696e646578206f7574206f6620626f756e64733a20746865206c656e20697320206275742074686520696e64657820697320d603010020000000f6030100120000003a200000180701000000000018040100020000003030303130323033303430353036303730383039313031313132313331343135313631373138313932303231323232333234323532363237323832393330333133323333333433353336333733383339343034313432343334343435343634373438343935303531353235333534353535363537353835393630363136323633363436353636363736383639373037313732373337343735373637373738373938303831383238333834383538363837383838393930393139323933393439353936393739383939206f7574206f662072616e676520666f7220736c696365206f66206c656e6774682072616e676520656e6420696e6465782000001605010010000000f4040100220000002f55736572732f616c65786265616e2f2e7275737475702f746f6f6c636861696e732f737461626c652d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f736c6963652f697465722e727300003805010072000000ce05000025000000736f7572636520736c696365206c656e67746820282920646f6573206e6f74206d617463682064657374696e6174696f6e20736c696365206c656e6774682028bc05010015000000d10501002b00000070030100010000002f55736572732f616c65786265616e2f2e7275737475702f746f6f6c636861696e732f737461626c652d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f7374722f636f756e742e727300000014060100710000004f000000320000001807010000000000756e61626c6520746f206465636f64652073656c6563746f72656e636f756e746572656420756e6b6e6f776e2073656c6563746f72756e61626c6520746f206465636f646520696e707574636f756c64206e6f74207265616420696e7075747061696420616e20756e70617961626c65206d6573736167656d6964203e206c656e00000018070100090000000a00000018070100000000002c070100010000002f55736572732f616c65786265616e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f696e6b5f656e762d352e302e302f7372632f656e67696e652f6f6e5f636861696e2f6275666665722e727340070100700000005c0000003b00000040070100700000005c0000001400000040070100700000005d0000000e00000040070100700000006800000009000000400701007000000090000000210000002f55736572732f616c65786265616e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f70616c6c65742d636f6e7472616374732d756170692d6e6578742d362e302e332f7372632f686f73742e727300000000080100710000002d000000170000002f55736572732f616c65786265616e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f7061726974792d7363616c652d636f6465632d332e362e31322f7372632f636f6465632e727300840801006b000000770000000e000000190000001c000000160000001400000019000000a0060100b9060100d5060100eb060100ff0601","build_info":{"build_mode":"Debug","cargo_contract_version":"4.1.1","rust_toolchain":"stable-aarch64-apple-darwin","wasm_opt_settings":{"keep_debug_symbols":false,"optimization_passes":"Z"}}},"contract":{"name":"testing","version":"0.1.0","authors":["[your_name] <[your_email]>"]},"image":null,"spec":{"constructors":[{"args":[{"label":"init_value","type":{"displayName":["bool"],"type":0}}],"default":false,"docs":["Constructor that initializes the `bool` value to the given `init_value`."],"label":"new","payable":false,"returnType":{"displayName":["ink_primitives","ConstructorResult"],"type":2},"selector":"0x9bae9d5e"},{"args":[],"default":false,"docs":["Constructor that initializes the `bool` value to `false`.","","Constructors can delegate to other constructors."],"label":"default","payable":false,"returnType":{"displayName":["ink_primitives","ConstructorResult"],"type":2},"selector":"0xed4b9d1b"}],"docs":[],"environment":{"accountId":{"displayName":["AccountId"],"type":6},"balance":{"displayName":["Balance"],"type":9},"blockNumber":{"displayName":["BlockNumber"],"type":12},"chainExtension":{"displayName":["ChainExtension"],"type":13},"hash":{"displayName":["Hash"],"type":10},"maxEventTopics":4,"staticBufferSize":16384,"timestamp":{"displayName":["Timestamp"],"type":11}},"events":[],"lang_error":{"displayName":["ink","LangError"],"type":4},"messages":[{"args":[],"default":false,"docs":[" A message that can be called on instantiated contracts."," This one flips the value of the stored `bool` from `true`"," to `false` and vice versa."],"label":"flip","mutates":true,"payable":false,"returnType":{"displayName":["ink","MessageResult"],"type":2},"selector":"0x633aa551"},{"args":[],"default":false,"docs":[" Simply returns the current value of our `bool`."],"label":"get","mutates":false,"payable":false,"returnType":{"displayName":["ink","MessageResult"],"type":5},"selector":"0x2f865bd9"}]},"storage":{"root":{"layout":{"struct":{"fields":[{"layout":{"leaf":{"key":"0x00000000","ty":0}},"name":"value"}],"name":"Testing"}},"root_key":"0x00000000","ty":1}},"types":[{"id":0,"type":{"def":{"primitive":"bool"}}},{"id":1,"type":{"def":{"composite":{"fields":[{"name":"value","type":0,"typeName":",>>::Type"}]}},"path":["testing","testing","Testing"]}},{"id":2,"type":{"def":{"variant":{"variants":[{"fields":[{"type":3}],"index":0,"name":"Ok"},{"fields":[{"type":4}],"index":1,"name":"Err"}]}},"params":[{"name":"T","type":3},{"name":"E","type":4}],"path":["Result"]}},{"id":3,"type":{"def":{"tuple":[]}}},{"id":4,"type":{"def":{"variant":{"variants":[{"index":1,"name":"CouldNotReadInput"}]}},"path":["ink_primitives","LangError"]}},{"id":5,"type":{"def":{"variant":{"variants":[{"fields":[{"type":0}],"index":0,"name":"Ok"},{"fields":[{"type":4}],"index":1,"name":"Err"}]}},"params":[{"name":"T","type":0},{"name":"E","type":4}],"path":["Result"]}},{"id":6,"type":{"def":{"composite":{"fields":[{"type":7,"typeName":"[u8; 32]"}]}},"path":["ink_primitives","types","AccountId"]}},{"id":7,"type":{"def":{"array":{"len":32,"type":8}}}},{"id":8,"type":{"def":{"primitive":"u8"}}},{"id":9,"type":{"def":{"primitive":"u128"}}},{"id":10,"type":{"def":{"composite":{"fields":[{"type":7,"typeName":"[u8; 32]"}]}},"path":["ink_primitives","types","Hash"]}},{"id":11,"type":{"def":{"primitive":"u64"}}},{"id":12,"type":{"def":{"primitive":"u32"}}},{"id":13,"type":{"def":{"variant":{}},"path":["ink_env","types","NoChainExtension"]}}],"version":5} \ No newline at end of file From 295a7a3c845b2c216f423005cc8ba00b6ef4d37e Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 10 Sep 2024 11:13:36 +0200 Subject: [PATCH 091/211] fix: fix todos and refactor --- crates/pop-cli/src/commands/call/contract.rs | 49 ++++++++++++++++++-- crates/pop-contracts/src/call/metadata.rs | 6 ++- 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index f1d9b8c2d..3be6e70c9 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -74,10 +74,13 @@ impl<'a, CLI: Cli> CallContract<'a, CLI> { let contract = call_config .contract .expect("contract can not be none as fallback above is interactive input; qed"); - // TODO: Can be nill pop call contract --contract - let message = call_config - .message - .expect("message can not be none as fallback above is interactive input; qed"); + let message = match call_config.message { + Some(m) => m, + None => { + self.cli.outro_cancel("Please specify the message to call.")?; + return Ok(()); + }, + }; let call_exec = set_up_call(CallOpts { path: call_config.path, @@ -480,4 +483,42 @@ mod tests { cli.verify() } + + #[tokio::test] + async fn call_contract_messages_fails_no_message() -> Result<()> { + let temp_dir = generate_smart_contract_test_environment()?; + let mut current_dir = env::current_dir().expect("Failed to get current directory"); + current_dir.pop(); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("pop-contracts/tests/files/testing.contract"), + current_dir.join("pop-contracts/tests/files/testing.json"), + )?; + + let mut cli = MockCli::new() + .expect_intro(&"Call a contract") + .expect_outro_cancel("Please specify the message to call."); + + // Contract deployed on Pop Network testnet, test get + Box::new(CallContract { + cli: &mut cli, + args: CallContractCommand { + path: Some(temp_dir.path().join("testing")), + contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), + message: None, + args: vec![].to_vec(), + value: "0".to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, + suri: "//Alice".to_string(), + dry_run: false, + execute: false, + }, + }) + .execute() + .await?; + + cli.verify() + } } diff --git a/crates/pop-contracts/src/call/metadata.rs b/crates/pop-contracts/src/call/metadata.rs index a2b378e63..43c6997be 100644 --- a/crates/pop-contracts/src/call/metadata.rs +++ b/crates/pop-contracts/src/call/metadata.rs @@ -23,6 +23,10 @@ pub struct Message { pub default: bool, } +/// Extracts a list of smart contract messages parsing the metadata file. +/// +/// # Arguments +/// * `path` - Location path of the project. pub fn get_messages(path: &Path) -> Result, Error> { let cargo_toml_path = match path.ends_with("Cargo.toml") { true => path.to_path_buf(), @@ -44,7 +48,7 @@ pub fn get_messages(path: &Path) -> Result, Error> { } Ok(messages) } -//TODO: We are ignoring the type of the argument. +// Parse the message parameters into a vector of argument labels. fn process_args(message_params: &[MessageParamSpec]) -> Vec { let mut args: Vec = Vec::new(); for arg in message_params { From 149b5544c88db237ba8559e23011b91945028e28 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 10 Sep 2024 11:50:01 +0200 Subject: [PATCH 092/211] test: fix unit test --- crates/pop-cli/src/commands/call/contract.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 3be6e70c9..d8851a7c6 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -344,6 +344,7 @@ mod tests { let items = vec![ ("flip".into(), " A message that can be called on instantiated contracts. This one flips the value of the stored `bool` from `true` to `false` and vice versa.".into()), ("get".into(), " Simply returns the current value of our `bool`.".into()), + ("specific_flip".into(), " A message for testing, flips the value of the stored `bool` with `new_value` and is payable".into()) ]; // The inputs are processed in reverse order. let mut cli = MockCli::new() From 3eea9954609f62ca36b92bfb1639cd188e3fc0cf Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Wed, 11 Sep 2024 18:51:30 +0200 Subject: [PATCH 093/211] feat: parse types of parameters and display it to the user in the placeholder --- crates/pop-cli/src/commands/call/contract.rs | 10 +++++++-- crates/pop-contracts/src/call/metadata.rs | 23 +++++++++++++++----- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index d8851a7c6..369694f97 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -208,7 +208,13 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( let mut contract_args = Vec::new(); for arg in &message.args { - contract_args.push(command.cli.input(arg).placeholder(arg).interact()?); + contract_args.push( + command + .cli + .input(format!("Enter the value for the parameter: {}", arg.label)) + .placeholder(&format!("Type required: {}", &arg.type_name)) + .interact()?, + ); } let mut value = "0".to_string(); if message.payable { @@ -433,7 +439,7 @@ mod tests { .expect_input("Enter the proof size limit:", "".into()) // Only if call .expect_input("Enter the gas limit:", "".into()) // Only if call .expect_input("Value to transfer to the call:", "50".into()) // Only if payable - .expect_input("new_value", "true".into()) // Args for specific_flip + .expect_input("Enter the value for the parameter: new_value", "true".into()) // Args for specific_flip .expect_select::( "Select the message to call:", Some(false), diff --git a/crates/pop-contracts/src/call/metadata.rs b/crates/pop-contracts/src/call/metadata.rs index 43c6997be..82e7589b7 100644 --- a/crates/pop-contracts/src/call/metadata.rs +++ b/crates/pop-contracts/src/call/metadata.rs @@ -6,6 +6,14 @@ use contract_transcode::ink_metadata::MessageParamSpec; use scale_info::form::PortableForm; use std::path::Path; +#[derive(Clone, PartialEq, Eq)] +/// Describes a contract message. +pub struct Param { + /// The label of the parameter. + pub label: String, + /// The type name of the parameter. + pub type_name: String, +} #[derive(Clone, PartialEq, Eq)] /// Describes a contract message. pub struct Message { @@ -16,7 +24,7 @@ pub struct Message { /// If the message accepts any `value` from the caller. pub payable: bool, /// The parameters of the deployment handler. - pub args: Vec, + pub args: Vec, /// The message documentation. pub docs: String, /// If the message is the default for off-chain consumers (e.g UIs). @@ -49,10 +57,13 @@ pub fn get_messages(path: &Path) -> Result, Error> { Ok(messages) } // Parse the message parameters into a vector of argument labels. -fn process_args(message_params: &[MessageParamSpec]) -> Vec { - let mut args: Vec = Vec::new(); +fn process_args(message_params: &[MessageParamSpec]) -> Vec { + let mut args: Vec = Vec::new(); for arg in message_params { - args.push(arg.label().to_string()); + args.push(Param { + label: arg.label().to_string(), + type_name: arg.ty().display_name().to_string(), + }); } args } @@ -83,7 +94,9 @@ mod tests { assert_eq!(message[2].label, "specific_flip"); assert_eq!(message[2].docs, " A message for testing, flips the value of the stored `bool` with `new_value` and is payable"); // assert parsed arguments - assert_eq!(message[2].args, vec!["new_value".to_string()]); + assert_eq!(message[2].args.len(), 1); + assert_eq!(message[2].args[0].label, "new_value".to_string()); + assert_eq!(message[2].args[0].type_name, "bool".to_string()); Ok(()) } } From baa6be5c3b82a4c00e20a7e22a46a0428eb95048 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 12 Sep 2024 11:30:06 +0200 Subject: [PATCH 094/211] refactor: error handling for pop call --- crates/pop-cli/src/commands/call/contract.rs | 29 ++++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 369694f97..924aa7a7f 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -67,7 +67,13 @@ impl<'a, CLI: Cli> CallContract<'a, CLI> { self.cli.intro("Call a contract")?; let call_config = if self.args.contract.is_none() { - guide_user_to_call_contract(&mut self).await? + match guide_user_to_call_contract(&mut self).await { + Ok(config) => config, + Err(e) => { + self.cli.outro_cancel(format!("{}", e.to_string()))?; + return Ok(()); + }, + } } else { self.args.clone() }; @@ -82,7 +88,7 @@ impl<'a, CLI: Cli> CallContract<'a, CLI> { }, }; - let call_exec = set_up_call(CallOpts { + let call_exec = match set_up_call(CallOpts { path: call_config.path, contract, message, @@ -94,7 +100,14 @@ impl<'a, CLI: Cli> CallContract<'a, CLI> { suri: call_config.suri, execute: call_config.execute, }) - .await?; + .await + { + Ok(call_exec) => call_exec, + Err(e) => { + self.cli.outro_cancel(format!("{}", e.root_cause().to_string()))?; + return Ok(()); + }, + }; if call_config.dry_run { let spinner = cliclack::spinner(); @@ -193,8 +206,10 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( let messages = match get_messages(contract_path) { Ok(messages) => messages, Err(e) => { - command.cli.outro_cancel("Unable to fetch contract metadata.")?; - return Err(anyhow!(format!("{}", e.to_string()))); + return Err(anyhow!(format!( + "Unable to fetch contract metadata: {}", + e.to_string().replace("Anyhow error: ", "") + ))); }, }; let message = { @@ -223,6 +238,10 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( .input("Value to transfer to the call:") .placeholder("0") .default_input("0") + .validate(|input: &String| match input.parse::() { + Ok(_) => Ok(()), + Err(_) => Err("Invalid value."), + }) .interact()?; } let mut gas_limit: Option = None; From 9e4d3a892ba2e0529c325fdf67971c8f9e335e45 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 12 Sep 2024 12:57:55 +0200 Subject: [PATCH 095/211] refactor: display call to be executed after guide and reorder --- crates/pop-cli/src/commands/call/contract.rs | 149 +++++++++++++++---- 1 file changed, 117 insertions(+), 32 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 924aa7a7f..672f7db81 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -53,6 +53,40 @@ pub struct CallContractCommand { #[clap(long, conflicts_with = "execute")] dry_run: bool, } +impl CallContractCommand { + fn display(&self) -> String { + let mut full_message = format!("pop call contract"); + if let Some(path) = &self.path { + full_message.push_str(&format!(" --path {}", path.display().to_string())); + } + if let Some(contract) = &self.contract { + full_message.push_str(&format!(" --contract {}", contract)); + } + if let Some(message) = &self.message { + full_message.push_str(&format!(" --message {}", message)); + } + if !self.args.is_empty() { + full_message.push_str(&format!(" --args {}", self.args.join(" "))); + } + if self.value != "0" { + full_message.push_str(&format!(" --value {}", self.value)); + } + if let Some(gas_limit) = self.gas_limit { + full_message.push_str(&format!(" --gas {}", gas_limit)); + } + if let Some(proof_size) = self.proof_size { + full_message.push_str(&format!(" --proof_size {}", proof_size)); + } + full_message.push_str(&format!(" --url {} --suri {}", self.url, self.suri)); + if self.execute { + full_message.push_str(" --execute"); + } + if self.dry_run { + full_message.push_str(" --dry_run"); + } + full_message + } +} pub(crate) struct CallContract<'a, CLI: Cli> { /// The cli to be used. @@ -65,7 +99,6 @@ impl<'a, CLI: Cli> CallContract<'a, CLI> { /// Executes the command. pub(crate) async fn execute(mut self: Box) -> Result<()> { self.cli.intro("Call a contract")?; - let call_config = if self.args.contract.is_none() { match guide_user_to_call_contract(&mut self).await { Ok(config) => config, @@ -191,6 +224,24 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( .interact()?; let contract_path = Path::new(&input_path); + let messages = match get_messages(contract_path) { + Ok(messages) => messages, + Err(e) => { + return Err(anyhow!(format!( + "Unable to fetch contract metadata: {}", + e.to_string().replace("Anyhow error: ", "") + ))); + }, + }; + + // Prompt for contract location. + let url: String = command + .cli + .input("Where is your contract deployed?") + .placeholder("ws://localhost:9944") + .default_input("ws://localhost:9944") + .interact()?; + // Prompt for contract address. let contract_address: String = command .cli @@ -203,15 +254,6 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( .default_input("5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") .interact()?; - let messages = match get_messages(contract_path) { - Ok(messages) => messages, - Err(e) => { - return Err(anyhow!(format!( - "Unable to fetch contract metadata: {}", - e.to_string().replace("Anyhow error: ", "") - ))); - }, - }; let message = { let mut prompt = command.cli.select("Select the message to call:"); for select_message in messages { @@ -266,14 +308,6 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( proof_size = proof_size_input.parse::().ok(); // If blank or bad input, estimate it. } - // Prompt for contract location. - let url: String = command - .cli - .input("Where is your contract deployed?") - .placeholder("ws://localhost:9944") - .default_input("ws://localhost:9944") - .interact()?; - // Who is calling the contract. let suri: String = command .cli @@ -290,8 +324,7 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( .initial_value(true) .interact()?; } - - Ok(CallContractCommand { + let call_command = CallContractCommand { path: Some(contract_path.to_path_buf()), contract: Some(contract_address), message: Some(message.label.clone()), @@ -303,7 +336,9 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( suri, execute: message.mutates, dry_run: !is_call_confirmed, - }) + }; + command.cli.info(call_command.display())?; + Ok(call_command) } #[cfg(test)] @@ -375,10 +410,6 @@ mod tests { let mut cli = MockCli::new() .expect_intro(&"Call a contract") .expect_input("Signer calling the contract:", "//Alice".into()) - .expect_input( - "Where is your contract deployed?", - "wss://rpc1.paseo.popnetwork.xyz".into(), - ) .expect_select::( "Select the message to call:", Some(false), @@ -390,10 +421,17 @@ mod tests { "Paste the on-chain contract address:", "15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".into(), ) + .expect_input( + "Where is your contract deployed?", + "wss://rpc1.paseo.popnetwork.xyz".into(), + ) .expect_input( "Where is your project located?", temp_dir.path().join("testing").display().to_string(), - ); + ).expect_info(format!( + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message get --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice", + temp_dir.path().join("testing").display().to_string(), + )); let call_config = guide_user_to_call_contract(&mut CallContract { cli: &mut cli, @@ -425,6 +463,10 @@ mod tests { assert_eq!(call_config.suri, "//Alice"); assert!(!call_config.execute); assert!(!call_config.dry_run); + assert_eq!(call_config.display(), format!( + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message get --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice", + temp_dir.path().join("testing").display().to_string(), + )); cli.verify() } @@ -451,10 +493,6 @@ mod tests { let mut cli = MockCli::new() .expect_intro(&"Call a contract") .expect_input("Signer calling the contract:", "//Alice".into()) - .expect_input( - "Where is your contract deployed?", - "wss://rpc1.paseo.popnetwork.xyz".into(), - ) .expect_input("Enter the proof size limit:", "".into()) // Only if call .expect_input("Enter the gas limit:", "".into()) // Only if call .expect_input("Value to transfer to the call:", "50".into()) // Only if payable @@ -470,10 +508,17 @@ mod tests { "Paste the on-chain contract address:", "15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".into(), ) + .expect_input( + "Where is your contract deployed?", + "wss://rpc1.paseo.popnetwork.xyz".into(), + ) .expect_input( "Where is your project located?", temp_dir.path().join("testing").display().to_string(), - ); + ).expect_info(format!( + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + temp_dir.path().join("testing").display().to_string(), + )); let call_config = guide_user_to_call_contract(&mut CallContract { cli: &mut cli, @@ -506,6 +551,10 @@ mod tests { assert_eq!(call_config.suri, "//Alice"); assert!(call_config.execute); assert!(!call_config.dry_run); + assert_eq!(call_config.display(), format!( + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + temp_dir.path().join("testing").display().to_string(), + )); cli.verify() } @@ -525,7 +574,6 @@ mod tests { .expect_intro(&"Call a contract") .expect_outro_cancel("Please specify the message to call."); - // Contract deployed on Pop Network testnet, test get Box::new(CallContract { cli: &mut cli, args: CallContractCommand { @@ -547,4 +595,41 @@ mod tests { cli.verify() } + + #[tokio::test] + async fn call_contract_messages_fails_parse_metadata() -> Result<()> { + let temp_dir = generate_smart_contract_test_environment()?; + let mut current_dir = env::current_dir().expect("Failed to get current directory"); + current_dir.pop(); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("pop-contracts/tests/files/testing.contract"), + current_dir.join("pop-contracts/tests/files/testing.json"), + )?; + + let mut cli = MockCli::new() + .expect_intro(&"Call a contract") + .expect_outro_cancel("Unable to fetch contract metadata: No 'ink' dependency found"); + + Box::new(CallContract { + cli: &mut cli, + args: CallContractCommand { + path: Some(temp_dir.path().join("wrong-testing")), + contract: None, + message: None, + args: vec![].to_vec(), + value: "0".to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse("ws://localhost:9944")?, + suri: "//Alice".to_string(), + dry_run: false, + execute: false, + }, + }) + .execute() + .await?; + + cli.verify() + } } From 97e2133abe1fef5b42d582ec94a8b0591933f111 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 12 Sep 2024 21:55:11 +0200 Subject: [PATCH 096/211] refactor: when repeat call use same contract values and dont clean screen --- crates/pop-cli/src/commands/call/contract.rs | 259 ++++++++++--------- crates/pop-cli/src/commands/mod.rs | 7 +- 2 files changed, 137 insertions(+), 129 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 672f7db81..e6ac97ed5 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -8,7 +8,7 @@ use pop_contracts::{ set_up_call, CallOpts, }; use sp_weights::Weight; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; #[derive(Args, Clone)] pub struct CallContractCommand { @@ -97,10 +97,10 @@ pub(crate) struct CallContract<'a, CLI: Cli> { impl<'a, CLI: Cli> CallContract<'a, CLI> { /// Executes the command. - pub(crate) async fn execute(mut self: Box) -> Result<()> { + pub(crate) async fn execute(mut self: Self) -> Result<()> { self.cli.intro("Call a contract")?; let call_config = if self.args.contract.is_none() { - match guide_user_to_call_contract(&mut self).await { + match guide_user_to_call_contract(&mut self, None, None, None).await { Ok(config) => config, Err(e) => { self.cli.outro_cancel(format!("{}", e.to_string()))?; @@ -110,19 +110,29 @@ impl<'a, CLI: Cli> CallContract<'a, CLI> { } else { self.args.clone() }; + match self.execute_call(call_config.clone()).await { + Ok(_) => Ok(()), + Err(e) => { + self.cli.outro_cancel(format!("{}", e.to_string()))?; + return Ok(()); + }, + } + } + /// Executes the call. + async fn execute_call(&mut self, call_config: CallContractCommand) -> Result<()> { let contract = call_config .contract + .clone() .expect("contract can not be none as fallback above is interactive input; qed"); let message = match call_config.message { Some(m) => m, None => { - self.cli.outro_cancel("Please specify the message to call.")?; - return Ok(()); + return Err(anyhow!("Please specify the message to call.")); }, }; let call_exec = match set_up_call(CallOpts { - path: call_config.path, + path: call_config.path.clone(), contract, message, args: call_config.args, @@ -137,8 +147,7 @@ impl<'a, CLI: Cli> CallContract<'a, CLI> { { Ok(call_exec) => call_exec, Err(e) => { - self.cli.outro_cancel(format!("{}", e.root_cause().to_string()))?; - return Ok(()); + return Err(anyhow!(format!("{}", e.root_cause().to_string()))); }, }; @@ -177,8 +186,7 @@ impl<'a, CLI: Cli> CallContract<'a, CLI> { }, Err(e) => { spinner.error(format!("{e}")); - self.cli.outro_cancel("Call failed.")?; - return Ok(()); + return Err(anyhow!("Call failed.")); }, } }; @@ -194,37 +202,52 @@ impl<'a, CLI: Cli> CallContract<'a, CLI> { if self.args.contract.is_none() { let another_call: bool = self .cli - .confirm("Do you want to do another call?") + .confirm("Do you want to do another call using the existing smart contract?") .initial_value(false) .interact()?; if another_call { - Box::pin(self.execute()).await?; + // Remove only the prompt asking for another call. + console::Term::stderr().clear_last_lines(2)?; + let new_call_config = guide_user_to_call_contract( + self, + call_config.path, + Some(call_config.url), + call_config.contract, + ) + .await?; + Box::pin(self.execute_call(new_call_config)).await?; } else { self.cli.outro("Call completed successfully!")?; } } else { self.cli.outro("Call completed successfully!")?; } - Ok(()) + return Ok(()); } } /// Guide the user to call the contract. async fn guide_user_to_call_contract<'a, CLI: Cli>( command: &mut CallContract<'a, CLI>, + contract_path: Option, + url: Option, + contract_address: Option, ) -> anyhow::Result { - command.cli.intro("Call a contract")?; - - // Prompt for location of your contract. - let input_path: String = command - .cli - .input("Where is your project located?") - .placeholder("./") - .default_input("./") - .interact()?; - let contract_path = Path::new(&input_path); - - let messages = match get_messages(contract_path) { + let contract_path: PathBuf = match contract_path { + Some(path) => path, + None => { + // Prompt for path. + let input_path: String = command + .cli + .input("Where is your project located?") + .placeholder("./") + .default_input("./") + .interact()?; + PathBuf::from(input_path) + }, + }; + // Parse the contract metadata provided. If there error, do not prompt for more. + let messages = match get_messages(&contract_path) { Ok(messages) => messages, Err(e) => { return Err(anyhow!(format!( @@ -233,32 +256,45 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( ))); }, }; - - // Prompt for contract location. - let url: String = command - .cli - .input("Where is your contract deployed?") - .placeholder("ws://localhost:9944") - .default_input("ws://localhost:9944") - .interact()?; - - // Prompt for contract address. - let contract_address: String = command - .cli - .input("Paste the on-chain contract address:") - .placeholder("e.g. 5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") - .validate(|input: &String| match parse_account(input) { - Ok(_) => Ok(()), - Err(_) => Err("Invalid address."), - }) - .default_input("5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") - .interact()?; + let url: url::Url = match url { + Some(url) => url, + None => { + // Prompt for url. + let url: String = command + .cli + .input("Where is your contract deployed?") + .placeholder("ws://localhost:9944") + .default_input("ws://localhost:9944") + .interact()?; + url::Url::parse(&url)? + }, + }; + let contract_address: String = match contract_address { + Some(contract_address) => contract_address, + None => { + // Prompt for contract address. + let contract_address: String = command + .cli + .input("Paste the on-chain contract address:") + .placeholder("e.g. 5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") + .validate(|input: &String| match parse_account(input) { + Ok(_) => Ok(()), + Err(_) => Err("Invalid address."), + }) + .default_input("5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") + .interact()?; + contract_address + }, + }; let message = { let mut prompt = command.cli.select("Select the message to call:"); for select_message in messages { - prompt = - prompt.item(select_message.clone(), &select_message.label, &select_message.docs); + prompt = prompt.item( + select_message.clone(), + format!("{}\n", &select_message.label), + &select_message.docs, + ); } prompt.interact()? }; @@ -325,14 +361,14 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( .interact()?; } let call_command = CallContractCommand { - path: Some(contract_path.to_path_buf()), + path: Some(contract_path), contract: Some(contract_address), message: Some(message.label.clone()), args: contract_args, value, gas_limit, proof_size, - url: url::Url::parse(&url)?, + url, suri, execute: message.mutates, dry_run: !is_call_confirmed, @@ -402,13 +438,12 @@ mod tests { )?; let items = vec![ - ("flip".into(), " A message that can be called on instantiated contracts. This one flips the value of the stored `bool` from `true` to `false` and vice versa.".into()), - ("get".into(), " Simply returns the current value of our `bool`.".into()), - ("specific_flip".into(), " A message for testing, flips the value of the stored `bool` with `new_value` and is payable".into()) + ("flip\n".into(), " A message that can be called on instantiated contracts. This one flips the value of the stored `bool` from `true` to `false` and vice versa.".into()), + ("get\n".into(), " Simply returns the current value of our `bool`.".into()), + ("specific_flip\n".into(), " A message for testing, flips the value of the stored `bool` with `new_value` and is payable".into()) ]; // The inputs are processed in reverse order. let mut cli = MockCli::new() - .expect_intro(&"Call a contract") .expect_input("Signer calling the contract:", "//Alice".into()) .expect_select::( "Select the message to call:", @@ -433,22 +468,27 @@ mod tests { temp_dir.path().join("testing").display().to_string(), )); - let call_config = guide_user_to_call_contract(&mut CallContract { - cli: &mut cli, - args: CallContractCommand { - path: Some(temp_dir.path().join("testing")), - contract: None, - message: None, - args: vec![].to_vec(), - value: "0".to_string(), - gas_limit: None, - proof_size: None, - url: Url::parse("ws://localhost:9944")?, - suri: "//Alice".to_string(), - dry_run: false, - execute: false, + let call_config = guide_user_to_call_contract( + &mut CallContract { + cli: &mut cli, + args: CallContractCommand { + path: Some(temp_dir.path().join("testing")), + contract: None, + message: None, + args: vec![].to_vec(), + value: "0".to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse("ws://localhost:9944")?, + suri: "//Alice".to_string(), + dry_run: false, + execute: false, + }, }, - }) + None, + None, + None, + ) .await?; assert_eq!( call_config.contract, @@ -485,13 +525,12 @@ mod tests { )?; let items = vec![ - ("flip".into(), " A message that can be called on instantiated contracts. This one flips the value of the stored `bool` from `true` to `false` and vice versa.".into()), - ("get".into(), " Simply returns the current value of our `bool`.".into()), - ("specific_flip".into(), " A message for testing, flips the value of the stored `bool` with `new_value` and is payable".into()) + ("flip\n".into(), " A message that can be called on instantiated contracts. This one flips the value of the stored `bool` from `true` to `false` and vice versa.".into()), + ("get\n".into(), " Simply returns the current value of our `bool`.".into()), + ("specific_flip\n".into(), " A message for testing, flips the value of the stored `bool` with `new_value` and is payable".into()) ]; // The inputs are processed in reverse order. let mut cli = MockCli::new() - .expect_intro(&"Call a contract") .expect_input("Signer calling the contract:", "//Alice".into()) .expect_input("Enter the proof size limit:", "".into()) // Only if call .expect_input("Enter the gas limit:", "".into()) // Only if call @@ -520,22 +559,27 @@ mod tests { temp_dir.path().join("testing").display().to_string(), )); - let call_config = guide_user_to_call_contract(&mut CallContract { - cli: &mut cli, - args: CallContractCommand { - path: Some(temp_dir.path().join("testing")), - contract: None, - message: None, - args: vec![].to_vec(), - value: "0".to_string(), - gas_limit: None, - proof_size: None, - url: Url::parse("ws://localhost:9944")?, - suri: "//Alice".to_string(), - dry_run: false, - execute: false, + let call_config = guide_user_to_call_contract( + &mut CallContract { + cli: &mut cli, + args: CallContractCommand { + path: Some(temp_dir.path().join("testing")), + contract: None, + message: None, + args: vec![].to_vec(), + value: "0".to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse("ws://localhost:9944")?, + suri: "//Alice".to_string(), + dry_run: false, + execute: false, + }, }, - }) + None, + None, + None, + ) .await?; assert_eq!( call_config.contract, @@ -574,7 +618,7 @@ mod tests { .expect_intro(&"Call a contract") .expect_outro_cancel("Please specify the message to call."); - Box::new(CallContract { + CallContract { cli: &mut cli, args: CallContractCommand { path: Some(temp_dir.path().join("testing")), @@ -589,44 +633,7 @@ mod tests { dry_run: false, execute: false, }, - }) - .execute() - .await?; - - cli.verify() - } - - #[tokio::test] - async fn call_contract_messages_fails_parse_metadata() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; - let mut current_dir = env::current_dir().expect("Failed to get current directory"); - current_dir.pop(); - mock_build_process( - temp_dir.path().join("testing"), - current_dir.join("pop-contracts/tests/files/testing.contract"), - current_dir.join("pop-contracts/tests/files/testing.json"), - )?; - - let mut cli = MockCli::new() - .expect_intro(&"Call a contract") - .expect_outro_cancel("Unable to fetch contract metadata: No 'ink' dependency found"); - - Box::new(CallContract { - cli: &mut cli, - args: CallContractCommand { - path: Some(temp_dir.path().join("wrong-testing")), - contract: None, - message: None, - args: vec![].to_vec(), - value: "0".to_string(), - gas_limit: None, - proof_size: None, - url: Url::parse("ws://localhost:9944")?, - suri: "//Alice".to_string(), - dry_run: false, - execute: false, - }, - }) + } .execute() .await?; diff --git a/crates/pop-cli/src/commands/mod.rs b/crates/pop-cli/src/commands/mod.rs index 4530ce4cb..a7797845b 100644 --- a/crates/pop-cli/src/commands/mod.rs +++ b/crates/pop-cli/src/commands/mod.rs @@ -98,11 +98,12 @@ impl Command { }, #[cfg(feature = "contract")] Self::Call(args) => match args.command { - call::Command::Contract(cmd) => - Box::new(call::contract::CallContract { cli: &mut Cli, args: cmd }) + call::Command::Contract(cmd) => { + call::contract::CallContract { cli: &mut Cli, args: cmd } .execute() .await - .map(|_| Value::Null), + .map(|_| Value::Null) + }, }, #[cfg(any(feature = "parachain", feature = "contract"))] Self::Up(args) => match args.command { From 383efb7d21bf9f7dfd9589e2cdbef139ad3b9550 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 13 Sep 2024 11:23:06 +0200 Subject: [PATCH 097/211] test: add dry-run test --- crates/pop-cli/src/commands/call/contract.rs | 49 ++++++++++++++++++-- crates/pop-cli/src/commands/mod.rs | 5 +- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index e6ac97ed5..bccfaa88f 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -24,12 +24,12 @@ pub struct CallContractCommand { /// The constructor arguments, encoded as strings. #[clap(long, num_args = 0..)] args: Vec, - /// Transfers an initial balance to the contract. + /// The value to be transferred as part of the call. #[clap(name = "value", long, default_value = "0")] value: String, /// Maximum amount of gas to be used for this command. /// If not specified it will perform a dry-run to estimate the gas consumed for the - /// instantiation. + /// call. #[clap(name = "gas", long)] gas_limit: Option, /// Maximum proof size for this command. @@ -370,7 +370,7 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( proof_size, url, suri, - execute: message.mutates, + execute: if is_call_confirmed { message.mutates } else { false }, dry_run: !is_call_confirmed, }; command.cli.info(call_command.display())?; @@ -402,7 +402,7 @@ mod tests { .expect_outro("Call completed successfully!"); // Contract deployed on Pop Network testnet, test get - Box::new(CallContract { + CallContract { cli: &mut cli, args: CallContractCommand { path: Some(temp_dir.path().join("testing")), @@ -417,13 +417,52 @@ mod tests { dry_run: false, execute: false, }, - }) + } .execute() .await?; cli.verify() } + #[tokio::test] + async fn call_contract_dry_run_works() -> Result<()> { + let temp_dir = generate_smart_contract_test_environment()?; + let mut current_dir = env::current_dir().expect("Failed to get current directory"); + current_dir.pop(); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("pop-contracts/tests/files/testing.contract"), + current_dir.join("pop-contracts/tests/files/testing.json"), + )?; + + let mut cli = MockCli::new() + .expect_intro(&"Call a contract") + .expect_warning("Your call has not been executed.") + .expect_info("Gas limit: Weight { ref_time: 100, proof_size: 10 }"); + + let call_config = CallContractCommand { + path: Some(temp_dir.path().join("testing")), + contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), + message: Some("flip".to_string()), + args: vec![].to_vec(), + value: "0".to_string(), + gas_limit: Some(100), + proof_size: Some(10), + url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, + suri: "//Alice".to_string(), + dry_run: true, + execute: false, + }; + assert_eq!(call_config.display(), format!( + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message flip --gas 100 --proof_size 10 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --dry_run", + temp_dir.path().join("testing").display().to_string(), + )); + // Contract deployed on Pop Network testnet, test dry-run + CallContract { cli: &mut cli, args: call_config }.execute().await?; + + cli.verify() + } + // This test only covers the interactive portion of the call contract command, without actually // calling the contract. #[tokio::test] diff --git a/crates/pop-cli/src/commands/mod.rs b/crates/pop-cli/src/commands/mod.rs index a7797845b..234b8d57b 100644 --- a/crates/pop-cli/src/commands/mod.rs +++ b/crates/pop-cli/src/commands/mod.rs @@ -98,12 +98,11 @@ impl Command { }, #[cfg(feature = "contract")] Self::Call(args) => match args.command { - call::Command::Contract(cmd) => { + call::Command::Contract(cmd) => call::contract::CallContract { cli: &mut Cli, args: cmd } .execute() .await - .map(|_| Value::Null) - }, + .map(|_| Value::Null), }, #[cfg(any(feature = "parachain", feature = "contract"))] Self::Up(args) => match args.command { From 915e8a0b331e27177267b32fd114f6f268ee228d Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 19 Sep 2024 21:15:01 +0200 Subject: [PATCH 098/211] test: refactor and add more test coverage --- crates/pop-cli/src/commands/call/contract.rs | 350 ++++++++++++------- crates/pop-cli/src/commands/mod.rs | 6 +- 2 files changed, 223 insertions(+), 133 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index bccfaa88f..8850ec188 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -use crate::cli::traits::*; +use crate::cli::{self, traits::*}; use anyhow::{anyhow, Result}; use clap::Args; use pop_contracts::{ @@ -54,6 +54,24 @@ pub struct CallContractCommand { dry_run: bool, } impl CallContractCommand { + /// Executes the command. + pub(crate) async fn execute(self) -> Result<()> { + let call_config: CallContractCommand = match self.set_up_call_config(&mut cli::Cli).await { + Ok(call_config) => call_config, + Err(e) => { + display_message(&format!("{}", e.to_string()), false, &mut cli::Cli)?; + return Ok(()); + }, + }; + match execute_call(call_config, self.contract.is_none(), &mut cli::Cli).await { + Ok(_) => Ok(()), + Err(e) => { + display_message(&format!("{}", e.to_string()), false, &mut cli::Cli)?; + Ok(()) + }, + } + } + fn display(&self) -> String { let mut full_message = format!("pop call contract"); if let Some(path) = &self.path { @@ -86,29 +104,22 @@ impl CallContractCommand { } full_message } -} -pub(crate) struct CallContract<'a, CLI: Cli> { - /// The cli to be used. - pub(crate) cli: &'a mut CLI, - /// The args to call. - pub(crate) args: CallContractCommand, -} - -impl<'a, CLI: Cli> CallContract<'a, CLI> { - /// Executes the command. - pub(crate) async fn execute(mut self: Self) -> Result<()> { - self.cli.intro("Call a contract")?; - let call_config = if self.args.contract.is_none() { - match guide_user_to_call_contract(&mut self, None, None, None).await { + /// Set up the config call. + async fn set_up_call_config( + &self, + cli: &mut impl cli::traits::Cli, + ) -> anyhow::Result { + cli.intro("Call a contract")?; + let call_config = if self.contract.is_none() { + match guide_user_to_call_contract(None, None, None, cli).await { Ok(config) => config, Err(e) => { - self.cli.outro_cancel(format!("{}", e.to_string()))?; - return Ok(()); + return Err(anyhow!(format!("{}", e.to_string()))); }, } } else { - self.args.clone() + self.clone() }; match self.execute_call(call_config.clone()).await { Ok(_) => Ok(()), @@ -227,18 +238,17 @@ impl<'a, CLI: Cli> CallContract<'a, CLI> { } /// Guide the user to call the contract. -async fn guide_user_to_call_contract<'a, CLI: Cli>( - command: &mut CallContract<'a, CLI>, +async fn guide_user_to_call_contract( contract_path: Option, url: Option, contract_address: Option, + cli: &mut impl cli::traits::Cli, ) -> anyhow::Result { let contract_path: PathBuf = match contract_path { Some(path) => path, None => { // Prompt for path. - let input_path: String = command - .cli + let input_path: String = cli .input("Where is your project located?") .placeholder("./") .default_input("./") @@ -260,8 +270,7 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( Some(url) => url, None => { // Prompt for url. - let url: String = command - .cli + let url: String = cli .input("Where is your contract deployed?") .placeholder("ws://localhost:9944") .default_input("ws://localhost:9944") @@ -273,8 +282,7 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( Some(contract_address) => contract_address, None => { // Prompt for contract address. - let contract_address: String = command - .cli + let contract_address: String = cli .input("Paste the on-chain contract address:") .placeholder("e.g. 5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") .validate(|input: &String| match parse_account(input) { @@ -288,7 +296,7 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( }; let message = { - let mut prompt = command.cli.select("Select the message to call:"); + let mut prompt = cli.select("Select the message to call:"); for select_message in messages { prompt = prompt.item( select_message.clone(), @@ -302,17 +310,14 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( let mut contract_args = Vec::new(); for arg in &message.args { contract_args.push( - command - .cli - .input(format!("Enter the value for the parameter: {}", arg.label)) + cli.input(format!("Enter the value for the parameter: {}", arg.label)) .placeholder(&format!("Type required: {}", &arg.type_name)) .interact()?, ); } let mut value = "0".to_string(); if message.payable { - value = command - .cli + value = cli .input("Value to transfer to the call:") .placeholder("0") .default_input("0") @@ -326,16 +331,14 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( let mut proof_size: Option = None; if message.mutates { // Prompt for gas limit and proof_size of the call. - let gas_limit_input: String = command - .cli + let gas_limit_input: String = cli .input("Enter the gas limit:") .required(false) .default_input("") .placeholder("If left blank, an estimation will be used") .interact()?; gas_limit = gas_limit_input.parse::().ok(); // If blank or bad input, estimate it. - let proof_size_input: String = command - .cli + let proof_size_input: String = cli .input("Enter the proof size limit:") .required(false) .placeholder("If left blank, an estimation will be used") @@ -345,8 +348,7 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( } // Who is calling the contract. - let suri: String = command - .cli + let suri: String = cli .input("Signer calling the contract:") .placeholder("//Alice") .default_input("//Alice") @@ -354,8 +356,7 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( let mut is_call_confirmed: bool = true; if message.mutates { - is_call_confirmed = command - .cli + is_call_confirmed = cli .confirm("Do you want to execute the call? (Selecting 'No' will perform a dry run)") .initial_value(true) .interact()?; @@ -373,10 +374,131 @@ async fn guide_user_to_call_contract<'a, CLI: Cli>( execute: if is_call_confirmed { message.mutates } else { false }, dry_run: !is_call_confirmed, }; - command.cli.info(call_command.display())?; + cli.info(call_command.display())?; Ok(call_command) } +/// Executes the call. +async fn execute_call( + call_config: CallContractCommand, + prompt_to_repeat_call: bool, + cli: &mut impl cli::traits::Cli, +) -> anyhow::Result<()> { + let contract = call_config + .contract + .clone() + .expect("contract can not be none as fallback above is interactive input; qed"); + let message = match call_config.message { + Some(m) => m, + None => { + return Err(anyhow!("Please specify the message to call.")); + }, + }; + + let call_exec = match set_up_call(CallOpts { + path: call_config.path.clone(), + contract, + message, + args: call_config.args, + value: call_config.value, + gas_limit: call_config.gas_limit, + proof_size: call_config.proof_size, + url: call_config.url.clone(), + suri: call_config.suri, + execute: call_config.execute, + }) + .await + { + Ok(call_exec) => call_exec, + Err(e) => { + return Err(anyhow!(format!("{}", e.root_cause().to_string()))); + }, + }; + + if call_config.dry_run { + let spinner = cliclack::spinner(); + spinner.start("Doing a dry run to estimate the gas..."); + match dry_run_gas_estimate_call(&call_exec).await { + Ok(w) => { + cli.info(format!("Gas limit: {:?}", w))?; + cli.warning("Your call has not been executed.")?; + }, + Err(e) => { + spinner.error(format!("{e}")); + display_message("Call failed.", false, cli)?; + }, + }; + return Ok(()); + } + + if !call_config.execute { + let spinner = cliclack::spinner(); + spinner.start("Calling the contract..."); + let call_dry_run_result = dry_run_call(&call_exec).await?; + cli.info(format!("Result: {}", call_dry_run_result))?; + cli.warning("Your call has not been executed.")?; + } else { + let weight_limit; + if call_config.gas_limit.is_some() && call_config.proof_size.is_some() { + weight_limit = + Weight::from_parts(call_config.gas_limit.unwrap(), call_config.proof_size.unwrap()); + } else { + let spinner = cliclack::spinner(); + spinner.start("Doing a dry run to estimate the gas..."); + weight_limit = match dry_run_gas_estimate_call(&call_exec).await { + Ok(w) => { + cli.info(format!("Gas limit: {:?}", w))?; + w + }, + Err(e) => { + spinner.error(format!("{e}")); + return Err(anyhow!("Call failed.")); + }, + }; + } + let spinner = cliclack::spinner(); + spinner.start("Calling the contract..."); + + let call_result = call_smart_contract(call_exec, weight_limit, &call_config.url) + .await + .map_err(|err| anyhow!("{} {}", "ERROR:", format!("{err:?}")))?; + + cli.info(call_result)?; + } + if prompt_to_repeat_call { + let another_call: bool = cli + .confirm("Do you want to do another call using the existing smart contract?") + .initial_value(false) + .interact()?; + if another_call { + // Remove only the prompt asking for another call. + console::Term::stderr().clear_last_lines(2)?; + let new_call_config = guide_user_to_call_contract( + call_config.path, + Some(call_config.url), + call_config.contract, + cli, + ) + .await?; + Box::pin(execute_call(new_call_config, prompt_to_repeat_call, cli)).await?; + } else { + display_message("Call completed successfully!", true, cli)?; + } + } else { + display_message("Call completed successfully!", true, cli)?; + } + return Ok(()); +} + +fn display_message(message: &str, success: bool, cli: &mut impl cli::traits::Cli) -> Result<()> { + if success { + cli.outro(message)?; + } else { + cli.outro_cancel(message)?; + } + Ok(()) +} + #[cfg(test)] mod tests { use super::*; @@ -386,7 +508,7 @@ mod tests { use url::Url; #[tokio::test] - async fn call_contract_messages_are_ok() -> Result<()> { + async fn call_contract_query_works() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; let mut current_dir = env::current_dir().expect("Failed to get current directory"); current_dir.pop(); @@ -399,27 +521,30 @@ mod tests { let mut cli = MockCli::new() .expect_intro(&"Call a contract") .expect_warning("Your call has not been executed.") + .expect_confirm( + "Do you want to do another call using the existing smart contract?", + false, + ) .expect_outro("Call completed successfully!"); // Contract deployed on Pop Network testnet, test get - CallContract { - cli: &mut cli, - args: CallContractCommand { - path: Some(temp_dir.path().join("testing")), - contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), - message: Some("get".to_string()), - args: vec![].to_vec(), - value: "0".to_string(), - gas_limit: None, - proof_size: None, - url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, - suri: "//Alice".to_string(), - dry_run: false, - execute: false, - }, + let config_call = CallContractCommand { + path: Some(temp_dir.path().join("testing")), + contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), + message: Some("get".to_string()), + args: vec![].to_vec(), + value: "0".to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, + suri: "//Alice".to_string(), + dry_run: false, + execute: false, } - .execute() + .set_up_call_config(&mut cli) .await?; + // Test the query. With true, it will prompt for another call. + execute_call(config_call, true, &mut cli).await?; cli.verify() } @@ -452,13 +577,15 @@ mod tests { suri: "//Alice".to_string(), dry_run: true, execute: false, - }; + } + .set_up_call_config(&mut cli) + .await?; assert_eq!(call_config.display(), format!( "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message flip --gas 100 --proof_size 10 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --dry_run", temp_dir.path().join("testing").display().to_string(), )); // Contract deployed on Pop Network testnet, test dry-run - CallContract { cli: &mut cli, args: call_config }.execute().await?; + execute_call(call_config, false, &mut cli).await?; cli.verify() } @@ -503,32 +630,11 @@ mod tests { "Where is your project located?", temp_dir.path().join("testing").display().to_string(), ).expect_info(format!( - "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message get --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice", - temp_dir.path().join("testing").display().to_string(), - )); - - let call_config = guide_user_to_call_contract( - &mut CallContract { - cli: &mut cli, - args: CallContractCommand { - path: Some(temp_dir.path().join("testing")), - contract: None, - message: None, - args: vec![].to_vec(), - value: "0".to_string(), - gas_limit: None, - proof_size: None, - url: Url::parse("ws://localhost:9944")?, - suri: "//Alice".to_string(), - dry_run: false, - execute: false, - }, - }, - None, - None, - None, - ) - .await?; + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message get --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice", + temp_dir.path().join("testing").display().to_string(), + )); + + let call_config = guide_user_to_call_contract(None, None, None, &mut cli).await?; assert_eq!( call_config.contract, Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) @@ -598,28 +704,7 @@ mod tests { temp_dir.path().join("testing").display().to_string(), )); - let call_config = guide_user_to_call_contract( - &mut CallContract { - cli: &mut cli, - args: CallContractCommand { - path: Some(temp_dir.path().join("testing")), - contract: None, - message: None, - args: vec![].to_vec(), - value: "0".to_string(), - gas_limit: None, - proof_size: None, - url: Url::parse("ws://localhost:9944")?, - suri: "//Alice".to_string(), - dry_run: false, - execute: false, - }, - }, - None, - None, - None, - ) - .await?; + let call_config = guide_user_to_call_contract(None, None, None, &mut cli).await?; assert_eq!( call_config.contract, Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) @@ -653,29 +738,38 @@ mod tests { current_dir.join("pop-contracts/tests/files/testing.json"), )?; - let mut cli = MockCli::new() - .expect_intro(&"Call a contract") - .expect_outro_cancel("Please specify the message to call."); - - CallContract { - cli: &mut cli, - args: CallContractCommand { - path: Some(temp_dir.path().join("testing")), - contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), - message: None, - args: vec![].to_vec(), - value: "0".to_string(), - gas_limit: None, - proof_size: None, - url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, - suri: "//Alice".to_string(), - dry_run: false, - execute: false, - }, + let mut cli = MockCli::new().expect_intro(&"Call a contract"); + + let call_config = CallContractCommand { + path: Some(temp_dir.path().join("testing")), + contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), + message: None, + args: vec![].to_vec(), + value: "0".to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, + suri: "//Alice".to_string(), + dry_run: false, + execute: false, } - .execute() + .set_up_call_config(&mut cli) .await?; + assert!(matches!( + execute_call(call_config, false, &mut cli).await, + anyhow::Result::Err(message) if message.to_string() == "Please specify the message to call." + )); + + cli.verify() + } + #[test] + fn test_display_message() -> Result<()> { + let mut cli = MockCli::new().expect_outro(&"Call completed successfully!"); + display_message("Call completed successfully!", true, &mut cli)?; + cli.verify()?; + let mut cli = MockCli::new().expect_outro_cancel("Call failed."); + display_message("Call failed.", false, &mut cli)?; cli.verify() } } diff --git a/crates/pop-cli/src/commands/mod.rs b/crates/pop-cli/src/commands/mod.rs index 234b8d57b..34c2f10b3 100644 --- a/crates/pop-cli/src/commands/mod.rs +++ b/crates/pop-cli/src/commands/mod.rs @@ -98,11 +98,7 @@ impl Command { }, #[cfg(feature = "contract")] Self::Call(args) => match args.command { - call::Command::Contract(cmd) => - call::contract::CallContract { cli: &mut Cli, args: cmd } - .execute() - .await - .map(|_| Value::Null), + call::Command::Contract(cmd) => cmd.execute().await.map(|_| Value::Null), }, #[cfg(any(feature = "parachain", feature = "contract"))] Self::Up(args) => match args.command { From 1ce90e77a250476f3b96fe5c760550f5d65afb9a Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 20 Sep 2024 09:21:49 +0200 Subject: [PATCH 099/211] test: more coverage --- crates/pop-cli/src/cli.rs | 18 +-- crates/pop-cli/src/commands/call/contract.rs | 120 ++++++++++++++----- crates/pop-contracts/src/init_tests.rs | 6 +- 3 files changed, 101 insertions(+), 43 deletions(-) diff --git a/crates/pop-cli/src/cli.rs b/crates/pop-cli/src/cli.rs index 29598c7e7..968c1a4f9 100644 --- a/crates/pop-cli/src/cli.rs +++ b/crates/pop-cli/src/cli.rs @@ -223,7 +223,7 @@ pub(crate) mod tests { /// Mock Cli with optional expectations #[derive(Default)] pub(crate) struct MockCli { - confirm_expectation: Option<(String, bool)>, + confirm_expectation: Vec<(String, bool)>, info_expectations: Vec, input_expectations: Vec<(String, String)>, intro_expectation: Option, @@ -243,7 +243,7 @@ pub(crate) mod tests { } pub(crate) fn expect_confirm(mut self, prompt: impl Display, confirm: bool) -> Self { - self.confirm_expectation = Some((prompt.to_string(), confirm)); + self.confirm_expectation.push((prompt.to_string(), confirm)); self } @@ -306,8 +306,8 @@ pub(crate) mod tests { } pub(crate) fn verify(self) -> anyhow::Result<()> { - if let Some((expectation, _)) = self.confirm_expectation { - panic!("`{expectation}` confirm expectation not satisfied") + if !self.confirm_expectation.is_empty() { + panic!("`{:?}` confirm expectations not satisfied", self.confirm_expectation) } if !self.info_expectations.is_empty() { panic!("`{}` info log expectations not satisfied", self.info_expectations.join(",")) @@ -349,7 +349,7 @@ pub(crate) mod tests { impl Cli for MockCli { fn confirm(&mut self, prompt: impl Display) -> impl Confirm { let prompt = prompt.to_string(); - if let Some((expectation, confirm)) = self.confirm_expectation.take() { + if let Some((expectation, confirm)) = self.confirm_expectation.pop() { assert_eq!(expectation, prompt, "prompt does not satisfy expectation"); return MockConfirm { confirm }; } @@ -454,13 +454,13 @@ pub(crate) mod tests { } impl Confirm for MockConfirm { + fn initial_value(mut self, _initial_value: bool) -> Self { + self.confirm = self.confirm; // Ignore initial value and always return mock value + self + } fn interact(&mut self) -> Result { Ok(self.confirm) } - fn initial_value(mut self, initial_value: bool) -> Self { - self.confirm = initial_value; - self - } } /// Mock input prompt diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 8850ec188..abb470efa 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -59,23 +59,23 @@ impl CallContractCommand { let call_config: CallContractCommand = match self.set_up_call_config(&mut cli::Cli).await { Ok(call_config) => call_config, Err(e) => { - display_message(&format!("{}", e.to_string()), false, &mut cli::Cli)?; + display_message(&e.to_string(), false, &mut cli::Cli)?; return Ok(()); }, }; match execute_call(call_config, self.contract.is_none(), &mut cli::Cli).await { Ok(_) => Ok(()), Err(e) => { - display_message(&format!("{}", e.to_string()), false, &mut cli::Cli)?; + display_message(&e.to_string(), false, &mut cli::Cli)?; Ok(()) }, } } fn display(&self) -> String { - let mut full_message = format!("pop call contract"); + let mut full_message = "pop call contract".to_string(); if let Some(path) = &self.path { - full_message.push_str(&format!(" --path {}", path.display().to_string())); + full_message.push_str(&format!(" --path {}", path.display())); } if let Some(contract) = &self.contract { full_message.push_str(&format!(" --contract {}", contract)); @@ -438,14 +438,12 @@ async fn execute_call( cli.info(format!("Result: {}", call_dry_run_result))?; cli.warning("Your call has not been executed.")?; } else { - let weight_limit; - if call_config.gas_limit.is_some() && call_config.proof_size.is_some() { - weight_limit = - Weight::from_parts(call_config.gas_limit.unwrap(), call_config.proof_size.unwrap()); + let weight_limit = if call_config.gas_limit.is_some() && call_config.proof_size.is_some() { + Weight::from_parts(call_config.gas_limit.unwrap(), call_config.proof_size.unwrap()) } else { let spinner = cliclack::spinner(); spinner.start("Doing a dry run to estimate the gas..."); - weight_limit = match dry_run_gas_estimate_call(&call_exec).await { + match dry_run_gas_estimate_call(&call_exec).await { Ok(w) => { cli.info(format!("Gas limit: {:?}", w))?; w @@ -454,8 +452,8 @@ async fn execute_call( spinner.error(format!("{e}")); return Err(anyhow!("Call failed.")); }, - }; - } + } + }; let spinner = cliclack::spinner(); spinner.start("Calling the contract..."); @@ -487,7 +485,7 @@ async fn execute_call( } else { display_message("Call completed successfully!", true, cli)?; } - return Ok(()); + Ok(()) } fn display_message(message: &str, success: bool, cli: &mut impl cli::traits::Cli) -> Result<()> { @@ -508,7 +506,7 @@ mod tests { use url::Url; #[tokio::test] - async fn call_contract_query_works() -> Result<()> { + async fn execute_query_works() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; let mut current_dir = env::current_dir().expect("Failed to get current directory"); current_dir.pop(); @@ -517,18 +515,8 @@ mod tests { current_dir.join("pop-contracts/tests/files/testing.contract"), current_dir.join("pop-contracts/tests/files/testing.json"), )?; - - let mut cli = MockCli::new() - .expect_intro(&"Call a contract") - .expect_warning("Your call has not been executed.") - .expect_confirm( - "Do you want to do another call using the existing smart contract?", - false, - ) - .expect_outro("Call completed successfully!"); - // Contract deployed on Pop Network testnet, test get - let config_call = CallContractCommand { + CallContractCommand { path: Some(temp_dir.path().join("testing")), contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), message: Some("get".to_string()), @@ -541,12 +529,9 @@ mod tests { dry_run: false, execute: false, } - .set_up_call_config(&mut cli) + .execute() .await?; - // Test the query. With true, it will prompt for another call. - execute_call(config_call, true, &mut cli).await?; - - cli.verify() + Ok(()) } #[tokio::test] @@ -590,6 +575,69 @@ mod tests { cli.verify() } + #[tokio::test] + async fn call_contract_query_duplicate_call_works() -> Result<()> { + let temp_dir = generate_smart_contract_test_environment()?; + let mut current_dir = env::current_dir().expect("Failed to get current directory"); + current_dir.pop(); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("pop-contracts/tests/files/testing.contract"), + current_dir.join("pop-contracts/tests/files/testing.json"), + )?; + let items = vec![ + ("flip\n".into(), " A message that can be called on instantiated contracts. This one flips the value of the stored `bool` from `true` to `false` and vice versa.".into()), + ("get\n".into(), " Simply returns the current value of our `bool`.".into()), + ("specific_flip\n".into(), " A message for testing, flips the value of the stored `bool` with `new_value` and is payable".into()) + ]; + let mut cli = MockCli::new() + .expect_intro(&"Call a contract") + .expect_warning("Your call has not been executed.") + .expect_confirm( + "Do you want to do another call using the existing smart contract?", + false, + ) + .expect_confirm( + "Do you want to do another call using the existing smart contract?", + true, + ) + .expect_select::( + "Select the message to call:", + Some(false), + true, + Some(items), + 1, // "get" message + ) + .expect_input("Signer calling the contract:", "//Alice".into()) + .expect_info(format!( + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message get --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice", + temp_dir.path().join("testing").display().to_string(), + )) + .expect_warning("Your call has not been executed.") + .expect_outro("Call completed successfully!"); + + // Contract deployed on Pop Network testnet, test get + let config_call = CallContractCommand { + path: Some(temp_dir.path().join("testing")), + contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), + message: Some("get".to_string()), + args: vec![].to_vec(), + value: "0".to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, + suri: "//Alice".to_string(), + dry_run: false, + execute: false, + } + .set_up_call_config(&mut cli) + .await?; + // Test the query. With true, it will prompt for another call. + execute_call(config_call, true, &mut cli).await?; + + cli.verify() + } + // This test only covers the interactive portion of the call contract command, without actually // calling the contract. #[tokio::test] @@ -728,7 +776,17 @@ mod tests { } #[tokio::test] - async fn call_contract_messages_fails_no_message() -> Result<()> { + async fn guide_user_to_call_contract_fails_not_build() -> Result<()> { + let temp_dir = generate_smart_contract_test_environment()?; + let mut cli = MockCli::new(); + assert!( + matches!(guide_user_to_call_contract(Some(temp_dir.path().join("testing")), None, None, &mut cli).await, anyhow::Result::Err(message) if message.to_string().contains("Unable to fetch contract metadata: Failed to find any contract artifacts in target directory.")) + ); + cli.verify() + } + + #[tokio::test] + async fn call_contract_fails_no_message() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; let mut current_dir = env::current_dir().expect("Failed to get current directory"); current_dir.pop(); @@ -764,7 +822,7 @@ mod tests { } #[test] - fn test_display_message() -> Result<()> { + fn display_message_works() -> Result<()> { let mut cli = MockCli::new().expect_outro(&"Call completed successfully!"); display_message("Call completed successfully!", true, &mut cli)?; cli.verify()?; diff --git a/crates/pop-contracts/src/init_tests.rs b/crates/pop-contracts/src/init_tests.rs index f1c24c7b9..0541220f8 100644 --- a/crates/pop-contracts/src/init_tests.rs +++ b/crates/pop-contracts/src/init_tests.rs @@ -21,9 +21,9 @@ pub fn mock_build_process( // Create a target directory let target_contract_dir = temp_contract_dir.join("target"); fs::create_dir(&target_contract_dir)?; - fs::create_dir(&target_contract_dir.join("ink"))?; + fs::create_dir(target_contract_dir.join("ink"))?; // Copy a mocked testing.contract and testing.json files inside the target directory - fs::copy(contract_file, &target_contract_dir.join("ink/testing.contract"))?; - fs::copy(metadata_file, &target_contract_dir.join("ink/testing.json"))?; + fs::copy(contract_file, target_contract_dir.join("ink/testing.contract"))?; + fs::copy(metadata_file, target_contract_dir.join("ink/testing.json"))?; Ok(()) } From 63a1f8a71b3174b7f229dc086543809d482b595a Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 20 Sep 2024 10:21:06 +0200 Subject: [PATCH 100/211] fix: unit test --- crates/pop-cli/src/commands/call/contract.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index abb470efa..8657caca3 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -724,6 +724,7 @@ mod tests { ]; // The inputs are processed in reverse order. let mut cli = MockCli::new() + .expect_confirm("Do you want to execute the call? (Selecting 'No' will perform a dry run)", true) .expect_input("Signer calling the contract:", "//Alice".into()) .expect_input("Enter the proof size limit:", "".into()) // Only if call .expect_input("Enter the gas limit:", "".into()) // Only if call From 32c8b567dd6ef576d700e3036b5d10a7c365ceb2 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 5 Nov 2024 11:09:26 +0100 Subject: [PATCH 101/211] feat: dev mode to skip certain user prompts --- crates/pop-cli/src/commands/call/contract.rs | 93 ++++++++++++++++++-- 1 file changed, 87 insertions(+), 6 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 8657caca3..85ab9f89f 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -52,6 +52,10 @@ pub struct CallContractCommand { /// Perform a dry-run via RPC to estimate the gas usage. This does not submit a transaction. #[clap(long, conflicts_with = "execute")] dry_run: bool, + /// Enables developer mode, bypassing certain user prompts for faster testing. + /// Recommended for testing and local development only. + #[clap(name = "dev", long, short, default_value = "false")] + dev_mode: bool, } impl CallContractCommand { /// Executes the command. @@ -112,7 +116,7 @@ impl CallContractCommand { ) -> anyhow::Result { cli.intro("Call a contract")?; let call_config = if self.contract.is_none() { - match guide_user_to_call_contract(None, None, None, cli).await { + match guide_user_to_call_contract(None, None, None, self.dev_mode, cli).await { Ok(config) => config, Err(e) => { return Err(anyhow!(format!("{}", e.to_string()))); @@ -242,6 +246,7 @@ async fn guide_user_to_call_contract( contract_path: Option, url: Option, contract_address: Option, + dev_mode: bool, cli: &mut impl cli::traits::Cli, ) -> anyhow::Result { let contract_path: PathBuf = match contract_path { @@ -329,7 +334,7 @@ async fn guide_user_to_call_contract( } let mut gas_limit: Option = None; let mut proof_size: Option = None; - if message.mutates { + if message.mutates && !dev_mode { // Prompt for gas limit and proof_size of the call. let gas_limit_input: String = cli .input("Enter the gas limit:") @@ -355,7 +360,7 @@ async fn guide_user_to_call_contract( .interact()?; let mut is_call_confirmed: bool = true; - if message.mutates { + if message.mutates && !dev_mode { is_call_confirmed = cli .confirm("Do you want to execute the call? (Selecting 'No' will perform a dry run)") .initial_value(true) @@ -373,6 +378,7 @@ async fn guide_user_to_call_contract( suri, execute: if is_call_confirmed { message.mutates } else { false }, dry_run: !is_call_confirmed, + dev_mode, }; cli.info(call_command.display())?; Ok(call_command) @@ -475,6 +481,7 @@ async fn execute_call( call_config.path, Some(call_config.url), call_config.contract, + call_config.dev_mode, cli, ) .await?; @@ -528,6 +535,7 @@ mod tests { suri: "//Alice".to_string(), dry_run: false, execute: false, + dev_mode: false, } .execute() .await?; @@ -562,6 +570,7 @@ mod tests { suri: "//Alice".to_string(), dry_run: true, execute: false, + dev_mode: false, } .set_up_call_config(&mut cli) .await?; @@ -629,6 +638,7 @@ mod tests { suri: "//Alice".to_string(), dry_run: false, execute: false, + dev_mode: false, } .set_up_call_config(&mut cli) .await?; @@ -682,7 +692,7 @@ mod tests { temp_dir.path().join("testing").display().to_string(), )); - let call_config = guide_user_to_call_contract(None, None, None, &mut cli).await?; + let call_config = guide_user_to_call_contract(None, None, None, false, &mut cli).await?; assert_eq!( call_config.contract, Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) @@ -753,7 +763,7 @@ mod tests { temp_dir.path().join("testing").display().to_string(), )); - let call_config = guide_user_to_call_contract(None, None, None, &mut cli).await?; + let call_config = guide_user_to_call_contract(None, None, None, false, &mut cli).await?; assert_eq!( call_config.contract, Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) @@ -776,12 +786,82 @@ mod tests { cli.verify() } + // This test only covers the interactive portion of the call contract command, without actually + // calling the contract. + #[tokio::test] + async fn guide_user_to_call_contract_in_dev_mode_works() -> Result<()> { + let temp_dir = generate_smart_contract_test_environment()?; + let mut current_dir = env::current_dir().expect("Failed to get current directory"); + current_dir.pop(); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("pop-contracts/tests/files/testing.contract"), + current_dir.join("pop-contracts/tests/files/testing.json"), + )?; + + let items = vec![ + ("flip\n".into(), " A message that can be called on instantiated contracts. This one flips the value of the stored `bool` from `true` to `false` and vice versa.".into()), + ("get\n".into(), " Simply returns the current value of our `bool`.".into()), + ("specific_flip\n".into(), " A message for testing, flips the value of the stored `bool` with `new_value` and is payable".into()) + ]; + // The inputs are processed in reverse order. + let mut cli = MockCli::new() + .expect_input("Signer calling the contract:", "//Alice".into()) + .expect_input("Value to transfer to the call:", "50".into()) // Only if payable + .expect_input("Enter the value for the parameter: new_value", "true".into()) // Args for specific_flip + .expect_select::( + "Select the message to call:", + Some(false), + true, + Some(items), + 2, // "specific_flip" message + ) + .expect_input( + "Paste the on-chain contract address:", + "15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".into(), + ) + .expect_input( + "Where is your contract deployed?", + "wss://rpc1.paseo.popnetwork.xyz".into(), + ) + .expect_input( + "Where is your project located?", + temp_dir.path().join("testing").display().to_string(), + ).expect_info(format!( + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + temp_dir.path().join("testing").display().to_string(), + )); + + let call_config = guide_user_to_call_contract(None, None, None, true, &mut cli).await?; + assert_eq!( + call_config.contract, + Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) + ); + assert_eq!(call_config.message, Some("specific_flip".to_string())); + assert_eq!(call_config.args.len(), 1); + assert_eq!(call_config.args[0], "true".to_string()); + assert_eq!(call_config.value, "50".to_string()); + assert_eq!(call_config.gas_limit, None); + assert_eq!(call_config.proof_size, None); + assert_eq!(call_config.url.to_string(), "wss://rpc1.paseo.popnetwork.xyz/"); + assert_eq!(call_config.suri, "//Alice"); + assert!(call_config.execute); + assert!(!call_config.dry_run); + assert!(call_config.dev_mode); + assert_eq!(call_config.display(), format!( + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + temp_dir.path().join("testing").display().to_string(), + )); + + cli.verify() + } + #[tokio::test] async fn guide_user_to_call_contract_fails_not_build() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; let mut cli = MockCli::new(); assert!( - matches!(guide_user_to_call_contract(Some(temp_dir.path().join("testing")), None, None, &mut cli).await, anyhow::Result::Err(message) if message.to_string().contains("Unable to fetch contract metadata: Failed to find any contract artifacts in target directory.")) + matches!(guide_user_to_call_contract(Some(temp_dir.path().join("testing")), None, None, false, &mut cli).await, anyhow::Result::Err(message) if message.to_string().contains("Unable to fetch contract metadata: Failed to find any contract artifacts in target directory.")) ); cli.verify() } @@ -811,6 +891,7 @@ mod tests { suri: "//Alice".to_string(), dry_run: false, execute: false, + dev_mode: false, } .set_up_call_config(&mut cli) .await?; From fb1bc956f029db73117f14395952922346b5ded1 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 5 Nov 2024 15:39:49 +0100 Subject: [PATCH 102/211] refactor: test functions, renaming and fix clippy --- crates/pop-cli/src/commands/call/contract.rs | 18 ++++----- crates/pop-contracts/src/call/metadata.rs | 4 +- crates/pop-contracts/src/call/mod.rs | 14 +++---- crates/pop-contracts/src/init_tests.rs | 29 --------------- crates/pop-contracts/src/lib.rs | 4 +- crates/pop-contracts/src/testing.rs | 39 ++++++++++++++++++++ crates/pop-contracts/src/up.rs | 16 ++++---- 7 files changed, 67 insertions(+), 57 deletions(-) delete mode 100644 crates/pop-contracts/src/init_tests.rs create mode 100644 crates/pop-contracts/src/testing.rs diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 85ab9f89f..da79b1433 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -508,13 +508,13 @@ fn display_message(message: &str, success: bool, cli: &mut impl cli::traits::Cli mod tests { use super::*; use crate::cli::MockCli; - use pop_contracts::{generate_smart_contract_test_environment, mock_build_process}; + use pop_contracts::{mock_build_process, new_environment}; use std::env; use url::Url; #[tokio::test] async fn execute_query_works() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let mut current_dir = env::current_dir().expect("Failed to get current directory"); current_dir.pop(); mock_build_process( @@ -544,7 +544,7 @@ mod tests { #[tokio::test] async fn call_contract_dry_run_works() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let mut current_dir = env::current_dir().expect("Failed to get current directory"); current_dir.pop(); mock_build_process( @@ -586,7 +586,7 @@ mod tests { #[tokio::test] async fn call_contract_query_duplicate_call_works() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let mut current_dir = env::current_dir().expect("Failed to get current directory"); current_dir.pop(); mock_build_process( @@ -652,7 +652,7 @@ mod tests { // calling the contract. #[tokio::test] async fn guide_user_to_query_contract_works() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let mut current_dir = env::current_dir().expect("Failed to get current directory"); current_dir.pop(); mock_build_process( @@ -718,7 +718,7 @@ mod tests { // calling the contract. #[tokio::test] async fn guide_user_to_call_contract_works() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let mut current_dir = env::current_dir().expect("Failed to get current directory"); current_dir.pop(); mock_build_process( @@ -790,7 +790,7 @@ mod tests { // calling the contract. #[tokio::test] async fn guide_user_to_call_contract_in_dev_mode_works() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let mut current_dir = env::current_dir().expect("Failed to get current directory"); current_dir.pop(); mock_build_process( @@ -858,7 +858,7 @@ mod tests { #[tokio::test] async fn guide_user_to_call_contract_fails_not_build() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let mut cli = MockCli::new(); assert!( matches!(guide_user_to_call_contract(Some(temp_dir.path().join("testing")), None, None, false, &mut cli).await, anyhow::Result::Err(message) if message.to_string().contains("Unable to fetch contract metadata: Failed to find any contract artifacts in target directory.")) @@ -868,7 +868,7 @@ mod tests { #[tokio::test] async fn call_contract_fails_no_message() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let mut current_dir = env::current_dir().expect("Failed to get current directory"); current_dir.pop(); mock_build_process( diff --git a/crates/pop-contracts/src/call/metadata.rs b/crates/pop-contracts/src/call/metadata.rs index 82e7589b7..605d06206 100644 --- a/crates/pop-contracts/src/call/metadata.rs +++ b/crates/pop-contracts/src/call/metadata.rs @@ -73,12 +73,12 @@ mod tests { use std::env; use super::*; - use crate::{generate_smart_contract_test_environment, mock_build_process}; + use crate::{mock_build_process, new_environment}; use anyhow::Result; #[test] fn get_messages_work() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let current_dir = env::current_dir().expect("Failed to get current directory"); mock_build_process( temp_dir.path().join("testing"), diff --git a/crates/pop-contracts/src/call/mod.rs b/crates/pop-contracts/src/call/mod.rs index ade88a482..73066bbd8 100644 --- a/crates/pop-contracts/src/call/mod.rs +++ b/crates/pop-contracts/src/call/mod.rs @@ -161,8 +161,8 @@ mod tests { use super::*; use crate::{ contracts_node_generator, dry_run_gas_estimate_instantiate, errors::Error, - generate_smart_contract_test_environment, instantiate_smart_contract, mock_build_process, - run_contracts_node, set_up_deployment, UpOpts, + instantiate_smart_contract, mock_build_process, new_environment, run_contracts_node, + set_up_deployment, UpOpts, }; use anyhow::Result; use sp_core::Bytes; @@ -172,7 +172,7 @@ mod tests { #[tokio::test] async fn test_set_up_call() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let current_dir = env::current_dir().expect("Failed to get current directory"); mock_build_process( temp_dir.path().join("testing"), @@ -199,7 +199,7 @@ mod tests { #[tokio::test] async fn test_set_up_call_error_contract_not_build() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let call_opts = CallOpts { path: Some(temp_dir.path().join("testing")), contract: "5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A".to_string(), @@ -243,7 +243,7 @@ mod tests { #[tokio::test] async fn test_dry_run_call_error_contract_not_deployed() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let current_dir = env::current_dir().expect("Failed to get current directory"); mock_build_process( temp_dir.path().join("testing"), @@ -270,7 +270,7 @@ mod tests { #[tokio::test] async fn test_dry_run_estimate_call_error_contract_not_deployed() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let current_dir = env::current_dir().expect("Failed to get current directory"); mock_build_process( temp_dir.path().join("testing"), @@ -301,7 +301,7 @@ mod tests { #[tokio::test] async fn call_works() -> Result<()> { const LOCALHOST_URL: &str = "ws://127.0.0.1:9944"; - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let current_dir = env::current_dir().expect("Failed to get current directory"); mock_build_process( temp_dir.path().join("testing"), diff --git a/crates/pop-contracts/src/init_tests.rs b/crates/pop-contracts/src/init_tests.rs deleted file mode 100644 index 0541220f8..000000000 --- a/crates/pop-contracts/src/init_tests.rs +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 - -use crate::{create_smart_contract, Contract}; -use anyhow::Result; -use std::{fs, path::PathBuf}; - -pub fn generate_smart_contract_test_environment() -> Result { - let temp_dir = tempfile::tempdir().expect("Could not create temp dir"); - let temp_contract_dir = temp_dir.path().join("testing"); - fs::create_dir(&temp_contract_dir)?; - create_smart_contract("testing", temp_contract_dir.as_path(), &Contract::Standard)?; - Ok(temp_dir) -} - -// Function that mocks the build process generating the contract artifacts. -pub fn mock_build_process( - temp_contract_dir: PathBuf, - contract_file: PathBuf, - metadata_file: PathBuf, -) -> Result<()> { - // Create a target directory - let target_contract_dir = temp_contract_dir.join("target"); - fs::create_dir(&target_contract_dir)?; - fs::create_dir(target_contract_dir.join("ink"))?; - // Copy a mocked testing.contract and testing.json files inside the target directory - fs::copy(contract_file, target_contract_dir.join("ink/testing.contract"))?; - fs::copy(metadata_file, target_contract_dir.join("ink/testing.json"))?; - Ok(()) -} diff --git a/crates/pop-contracts/src/lib.rs b/crates/pop-contracts/src/lib.rs index 4fd24bd4e..a3ff0175c 100644 --- a/crates/pop-contracts/src/lib.rs +++ b/crates/pop-contracts/src/lib.rs @@ -4,11 +4,11 @@ mod build; mod call; mod errors; -mod init_tests; mod new; mod node; mod templates; mod test; +mod testing; mod up; mod utils; @@ -18,11 +18,11 @@ pub use call::{ metadata::{get_messages, Message}, set_up_call, CallOpts, }; -pub use init_tests::{generate_smart_contract_test_environment, mock_build_process}; pub use new::{create_smart_contract, is_valid_contract_name}; pub use node::{contracts_node_generator, is_chain_alive, run_contracts_node}; pub use templates::{Contract, ContractType}; pub use test::{test_e2e_smart_contract, test_smart_contract}; +pub use testing::{mock_build_process, new_environment}; pub use up::{ dry_run_gas_estimate_instantiate, dry_run_upload, instantiate_smart_contract, set_up_deployment, set_up_upload, upload_smart_contract, UpOpts, diff --git a/crates/pop-contracts/src/testing.rs b/crates/pop-contracts/src/testing.rs new file mode 100644 index 000000000..6a8abfcd9 --- /dev/null +++ b/crates/pop-contracts/src/testing.rs @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0 + +use crate::{create_smart_contract, Contract}; +use anyhow::Result; +use std::{ + fs::{copy, create_dir}, + path::Path, +}; + +/// Generates a smart contract test environment. +/// +/// * `name` - The name of the contract to be created. +pub fn new_environment(name: &str) -> Result { + let temp_dir = tempfile::tempdir().expect("Could not create temp dir"); + let temp_contract_dir = temp_dir.path().join(name); + create_dir(&temp_contract_dir)?; + create_smart_contract(name, temp_contract_dir.as_path(), &Contract::Standard)?; + Ok(temp_dir) +} + +/// Mocks the build process by generating contract artifacts in a specified temporary directory. +/// +/// * `temp_contract_dir` - The root directory where the `target` folder and artifacts will be +/// created. +/// * `contract_file` - The path to the mocked contract file to be copied. +/// * `metadata_file` - The path to the mocked metadata file to be copied. +pub fn mock_build_process

(temp_contract_dir: P, contract_file: P, metadata_file: P) -> Result<()> +where + P: AsRef, +{ + // Create a target directory + let target_contract_dir = temp_contract_dir.as_ref().join("target"); + create_dir(&target_contract_dir)?; + create_dir(target_contract_dir.join("ink"))?; + // Copy a mocked testing.contract and testing.json files inside the target directory + copy(contract_file, target_contract_dir.join("ink/testing.contract"))?; + copy(metadata_file, target_contract_dir.join("ink/testing.json"))?; + Ok(()) +} diff --git a/crates/pop-contracts/src/up.rs b/crates/pop-contracts/src/up.rs index 6a081386e..0ee6a2d0b 100644 --- a/crates/pop-contracts/src/up.rs +++ b/crates/pop-contracts/src/up.rs @@ -215,8 +215,8 @@ pub async fn upload_smart_contract( mod tests { use super::*; use crate::{ - contracts_node_generator, errors::Error, generate_smart_contract_test_environment, - mock_build_process, run_contracts_node, + contracts_node_generator, errors::Error, mock_build_process, new_environment, + run_contracts_node, }; use anyhow::Result; use std::{env, process::Command}; @@ -226,7 +226,7 @@ mod tests { #[tokio::test] async fn set_up_deployment_works() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let current_dir = env::current_dir().expect("Failed to get current directory"); mock_build_process( temp_dir.path().join("testing"), @@ -250,7 +250,7 @@ mod tests { #[tokio::test] async fn set_up_upload_works() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let current_dir = env::current_dir().expect("Failed to get current directory"); mock_build_process( temp_dir.path().join("testing"), @@ -274,7 +274,7 @@ mod tests { #[tokio::test] async fn dry_run_gas_estimate_instantiate_works() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let current_dir = env::current_dir().expect("Failed to get current directory"); mock_build_process( temp_dir.path().join("testing"), @@ -301,7 +301,7 @@ mod tests { #[tokio::test] async fn dry_run_gas_estimate_instantiate_throw_custom_error() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let current_dir = env::current_dir().expect("Failed to get current directory"); mock_build_process( temp_dir.path().join("testing"), @@ -329,7 +329,7 @@ mod tests { #[tokio::test] async fn dry_run_upload_throw_custom_error() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let current_dir = env::current_dir().expect("Failed to get current directory"); mock_build_process( temp_dir.path().join("testing"), @@ -357,7 +357,7 @@ mod tests { #[tokio::test] async fn instantiate_and_upload() -> Result<()> { const LOCALHOST_URL: &str = "ws://127.0.0.1:9944"; - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let current_dir = env::current_dir().expect("Failed to get current directory"); mock_build_process( temp_dir.path().join("testing"), From 06a0e54d20622729bb9b347ad7cd89998dd2b4e6 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 5 Nov 2024 16:30:58 +0100 Subject: [PATCH 103/211] refactor: improve devex of pop call contract --- crates/pop-cli/src/commands/call/contract.rs | 446 +++++++++---------- 1 file changed, 221 insertions(+), 225 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index da79b1433..b0e5895f8 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -10,6 +10,10 @@ use pop_contracts::{ use sp_weights::Weight; use std::path::PathBuf; +const DEFAULT_URL: &str = "ws://localhost:9944"; +const DEFAULT_URI: &str = "//Alice"; +const DEFAULT_PAYABLE_VALUE: &str = "0"; + #[derive(Args, Clone)] pub struct CallContractCommand { /// Path to the contract build directory. @@ -25,7 +29,7 @@ pub struct CallContractCommand { #[clap(long, num_args = 0..)] args: Vec, /// The value to be transferred as part of the call. - #[clap(name = "value", long, default_value = "0")] + #[clap(name = "value", long, default_value = DEFAULT_PAYABLE_VALUE)] value: String, /// Maximum amount of gas to be used for this command. /// If not specified it will perform a dry-run to estimate the gas consumed for the @@ -37,14 +41,14 @@ pub struct CallContractCommand { #[clap(long)] proof_size: Option, /// Websocket endpoint of a node. - #[clap(name = "url", long, value_parser, default_value = "ws://localhost:9944")] + #[clap(name = "url", long, value_parser, default_value = DEFAULT_URL)] url: url::Url, /// Secret key URI for the account calling the contract. /// /// e.g. /// - for a dev account "//Alice" /// - with a password "//Alice///SECRET_PASSWORD" - #[clap(name = "suri", long, short, default_value = "//Alice")] + #[clap(name = "suri", long, short, default_value = DEFAULT_URI)] suri: String, /// Submit an extrinsic for on-chain execution. #[clap(short('x'), long)] @@ -67,7 +71,7 @@ impl CallContractCommand { return Ok(()); }, }; - match execute_call(call_config, self.contract.is_none(), &mut cli::Cli).await { + match call_config.execute_call(self.message.is_none(), &mut cli::Cli).await { Ok(_) => Ok(()), Err(e) => { display_message(&e.to_string(), false, &mut cli::Cli)?; @@ -115,8 +119,8 @@ impl CallContractCommand { cli: &mut impl cli::traits::Cli, ) -> anyhow::Result { cli.intro("Call a contract")?; - let call_config = if self.contract.is_none() { - match guide_user_to_call_contract(None, None, None, self.dev_mode, cli).await { + let call_config = if self.message.is_none() { + match self.guide_user_to_call_contract(cli).await { Ok(config) => config, Err(e) => { return Err(anyhow!(format!("{}", e.to_string()))); @@ -239,41 +243,35 @@ impl CallContractCommand { } return Ok(()); } -} -/// Guide the user to call the contract. -async fn guide_user_to_call_contract( - contract_path: Option, - url: Option, - contract_address: Option, - dev_mode: bool, - cli: &mut impl cli::traits::Cli, -) -> anyhow::Result { - let contract_path: PathBuf = match contract_path { - Some(path) => path, - None => { - // Prompt for path. - let input_path: String = cli - .input("Where is your project located?") - .placeholder("./") - .default_input("./") - .interact()?; - PathBuf::from(input_path) - }, - }; - // Parse the contract metadata provided. If there error, do not prompt for more. - let messages = match get_messages(&contract_path) { - Ok(messages) => messages, - Err(e) => { - return Err(anyhow!(format!( - "Unable to fetch contract metadata: {}", - e.to_string().replace("Anyhow error: ", "") - ))); - }, - }; - let url: url::Url = match url { - Some(url) => url, - None => { + /// Guide the user to call the contract. + async fn guide_user_to_call_contract( + &self, + cli: &mut impl cli::traits::Cli, + ) -> anyhow::Result { + let contract_path: PathBuf = match &self.path { + Some(path) => path.to_path_buf(), + None => { + // Prompt for path. + let input_path: String = cli + .input("Where is your project located?") + .placeholder("./") + .default_input("./") + .interact()?; + PathBuf::from(input_path) + }, + }; + // Parse the contract metadata provided. If there is an error, do not prompt for more. + let messages = match get_messages(&contract_path) { + Ok(messages) => messages, + Err(e) => { + return Err(anyhow!(format!( + "Unable to fetch contract metadata: {}", + e.to_string().replace("Anyhow error: ", "") + ))); + }, + }; + let url = if self.url.as_str() == DEFAULT_URL { // Prompt for url. let url: String = cli .input("Where is your contract deployed?") @@ -281,218 +279,216 @@ async fn guide_user_to_call_contract( .default_input("ws://localhost:9944") .interact()?; url::Url::parse(&url)? - }, - }; - let contract_address: String = match contract_address { - Some(contract_address) => contract_address, - None => { - // Prompt for contract address. - let contract_address: String = cli - .input("Paste the on-chain contract address:") - .placeholder("e.g. 5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") - .validate(|input: &String| match parse_account(input) { + } else { + self.url.clone() + }; + let contract_address: String = match &self.contract { + Some(contract_address) => contract_address.to_string(), + None => { + // Prompt for contract address. + let contract_address: String = cli + .input("Paste the on-chain contract address:") + .placeholder("e.g. 5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") + .validate(|input: &String| match parse_account(input) { + Ok(_) => Ok(()), + Err(_) => Err("Invalid address."), + }) + .default_input("5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") + .interact()?; + contract_address + }, + }; + + let message = { + let mut prompt = cli.select("Select the message to call:"); + for select_message in messages { + prompt = prompt.item( + select_message.clone(), + format!("{}\n", &select_message.label), + &select_message.docs, + ); + } + prompt.interact()? + }; + + let mut contract_args = Vec::new(); + for arg in &message.args { + contract_args.push( + cli.input(format!("Enter the value for the parameter: {}", arg.label)) + .placeholder(&format!("Type required: {}", &arg.type_name)) + .interact()?, + ); + } + let mut value = "0".to_string(); + if message.payable { + value = cli + .input("Value to transfer to the call:") + .placeholder("0") + .default_input("0") + .validate(|input: &String| match input.parse::() { Ok(_) => Ok(()), - Err(_) => Err("Invalid address."), + Err(_) => Err("Invalid value."), }) - .default_input("5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") .interact()?; - contract_address - }, - }; - - let message = { - let mut prompt = cli.select("Select the message to call:"); - for select_message in messages { - prompt = prompt.item( - select_message.clone(), - format!("{}\n", &select_message.label), - &select_message.docs, - ); } - prompt.interact()? - }; - - let mut contract_args = Vec::new(); - for arg in &message.args { - contract_args.push( - cli.input(format!("Enter the value for the parameter: {}", arg.label)) - .placeholder(&format!("Type required: {}", &arg.type_name)) - .interact()?, - ); - } - let mut value = "0".to_string(); - if message.payable { - value = cli - .input("Value to transfer to the call:") - .placeholder("0") - .default_input("0") - .validate(|input: &String| match input.parse::() { - Ok(_) => Ok(()), - Err(_) => Err("Invalid value."), - }) - .interact()?; - } - let mut gas_limit: Option = None; - let mut proof_size: Option = None; - if message.mutates && !dev_mode { - // Prompt for gas limit and proof_size of the call. - let gas_limit_input: String = cli - .input("Enter the gas limit:") - .required(false) - .default_input("") - .placeholder("If left blank, an estimation will be used") - .interact()?; - gas_limit = gas_limit_input.parse::().ok(); // If blank or bad input, estimate it. - let proof_size_input: String = cli - .input("Enter the proof size limit:") - .required(false) - .placeholder("If left blank, an estimation will be used") - .default_input("") - .interact()?; - proof_size = proof_size_input.parse::().ok(); // If blank or bad input, estimate it. - } + let mut gas_limit: Option = None; + let mut proof_size: Option = None; + if message.mutates && !self.dev_mode { + // Prompt for gas limit and proof_size of the call. + let gas_limit_input: String = cli + .input("Enter the gas limit:") + .required(false) + .default_input("") + .placeholder("If left blank, an estimation will be used") + .interact()?; + gas_limit = gas_limit_input.parse::().ok(); // If blank or bad input, estimate it. + let proof_size_input: String = cli + .input("Enter the proof size limit:") + .required(false) + .placeholder("If left blank, an estimation will be used") + .default_input("") + .interact()?; + proof_size = proof_size_input.parse::().ok(); // If blank or bad input, estimate it. + } - // Who is calling the contract. - let suri: String = cli - .input("Signer calling the contract:") - .placeholder("//Alice") - .default_input("//Alice") - .interact()?; - - let mut is_call_confirmed: bool = true; - if message.mutates && !dev_mode { - is_call_confirmed = cli - .confirm("Do you want to execute the call? (Selecting 'No' will perform a dry run)") - .initial_value(true) - .interact()?; + // Who is calling the contract. + let suri = if self.suri == DEFAULT_URI { + // Prompt for uri. + cli.input("Signer calling the contract:") + .placeholder("//Alice") + .default_input("//Alice") + .interact()? + } else { + self.suri.clone() + }; + + let mut is_call_confirmed: bool = true; + if message.mutates && !self.dev_mode { + is_call_confirmed = cli + .confirm("Do you want to execute the call? (Selecting 'No' will perform a dry run)") + .initial_value(true) + .interact()?; + } + let call_command = CallContractCommand { + path: Some(contract_path), + contract: Some(contract_address), + message: Some(message.label.clone()), + args: contract_args, + value, + gas_limit, + proof_size, + url, + suri, + execute: if is_call_confirmed { message.mutates } else { false }, + dry_run: !is_call_confirmed, + dev_mode: self.dev_mode, + }; + cli.info(call_command.display())?; + Ok(call_command) } - let call_command = CallContractCommand { - path: Some(contract_path), - contract: Some(contract_address), - message: Some(message.label.clone()), - args: contract_args, - value, - gas_limit, - proof_size, - url, - suri, - execute: if is_call_confirmed { message.mutates } else { false }, - dry_run: !is_call_confirmed, - dev_mode, - }; - cli.info(call_command.display())?; - Ok(call_command) -} -/// Executes the call. -async fn execute_call( - call_config: CallContractCommand, - prompt_to_repeat_call: bool, - cli: &mut impl cli::traits::Cli, -) -> anyhow::Result<()> { - let contract = call_config - .contract - .clone() - .expect("contract can not be none as fallback above is interactive input; qed"); - let message = match call_config.message { - Some(m) => m, - None => { - return Err(anyhow!("Please specify the message to call.")); - }, - }; - - let call_exec = match set_up_call(CallOpts { - path: call_config.path.clone(), - contract, - message, - args: call_config.args, - value: call_config.value, - gas_limit: call_config.gas_limit, - proof_size: call_config.proof_size, - url: call_config.url.clone(), - suri: call_config.suri, - execute: call_config.execute, - }) - .await - { - Ok(call_exec) => call_exec, - Err(e) => { - return Err(anyhow!(format!("{}", e.root_cause().to_string()))); - }, - }; - - if call_config.dry_run { - let spinner = cliclack::spinner(); - spinner.start("Doing a dry run to estimate the gas..."); - match dry_run_gas_estimate_call(&call_exec).await { - Ok(w) => { - cli.info(format!("Gas limit: {:?}", w))?; - cli.warning("Your call has not been executed.")?; + /// Executes the call. + async fn execute_call( + &self, + prompt_to_repeat_call: bool, + cli: &mut impl cli::traits::Cli, + ) -> anyhow::Result<()> { + let message = self + .message + .clone() + .expect("message can not be none as fallback above is interactive input; qed"); + let contract = match &self.contract { + Some(contract) => contract.to_string(), + None => { + return Err(anyhow!("Please specify the contract address.")); }, + }; + let call_exec = match set_up_call(CallOpts { + path: self.path.clone(), + contract, + message, + args: self.args.clone(), + value: self.value.clone(), + gas_limit: self.gas_limit, + proof_size: self.proof_size, + url: self.url.clone(), + suri: self.suri.clone(), + execute: self.execute, + }) + .await + { + Ok(call_exec) => call_exec, Err(e) => { - spinner.error(format!("{e}")); - display_message("Call failed.", false, cli)?; + return Err(anyhow!(format!("{}", e.root_cause().to_string()))); }, }; - return Ok(()); - } - if !call_config.execute { - let spinner = cliclack::spinner(); - spinner.start("Calling the contract..."); - let call_dry_run_result = dry_run_call(&call_exec).await?; - cli.info(format!("Result: {}", call_dry_run_result))?; - cli.warning("Your call has not been executed.")?; - } else { - let weight_limit = if call_config.gas_limit.is_some() && call_config.proof_size.is_some() { - Weight::from_parts(call_config.gas_limit.unwrap(), call_config.proof_size.unwrap()) - } else { + if self.dry_run { let spinner = cliclack::spinner(); spinner.start("Doing a dry run to estimate the gas..."); match dry_run_gas_estimate_call(&call_exec).await { Ok(w) => { cli.info(format!("Gas limit: {:?}", w))?; - w + cli.warning("Your call has not been executed.")?; }, Err(e) => { spinner.error(format!("{e}")); - return Err(anyhow!("Call failed.")); + display_message("Call failed.", false, cli)?; }, - } - }; - let spinner = cliclack::spinner(); - spinner.start("Calling the contract..."); + }; + return Ok(()); + } + + if !self.execute { + let spinner = cliclack::spinner(); + spinner.start("Calling the contract..."); + let call_dry_run_result = dry_run_call(&call_exec).await?; + cli.info(format!("Result: {}", call_dry_run_result))?; + cli.warning("Your call has not been executed.")?; + } else { + let weight_limit = if self.gas_limit.is_some() && self.proof_size.is_some() { + Weight::from_parts(self.gas_limit.unwrap(), self.proof_size.unwrap()) + } else { + let spinner = cliclack::spinner(); + spinner.start("Doing a dry run to estimate the gas..."); + match dry_run_gas_estimate_call(&call_exec).await { + Ok(w) => { + cli.info(format!("Gas limit: {:?}", w))?; + w + }, + Err(e) => { + spinner.error(format!("{e}")); + return Err(anyhow!("Call failed.")); + }, + } + }; + let spinner = cliclack::spinner(); + spinner.start("Calling the contract..."); - let call_result = call_smart_contract(call_exec, weight_limit, &call_config.url) - .await - .map_err(|err| anyhow!("{} {}", "ERROR:", format!("{err:?}")))?; + let call_result = call_smart_contract(call_exec, weight_limit, &self.url) + .await + .map_err(|err| anyhow!("{} {}", "ERROR:", format!("{err:?}")))?; - cli.info(call_result)?; - } - if prompt_to_repeat_call { - let another_call: bool = cli - .confirm("Do you want to do another call using the existing smart contract?") - .initial_value(false) - .interact()?; - if another_call { - // Remove only the prompt asking for another call. - console::Term::stderr().clear_last_lines(2)?; - let new_call_config = guide_user_to_call_contract( - call_config.path, - Some(call_config.url), - call_config.contract, - call_config.dev_mode, - cli, - ) - .await?; - Box::pin(execute_call(new_call_config, prompt_to_repeat_call, cli)).await?; + cli.info(call_result)?; + } + if prompt_to_repeat_call { + let another_call: bool = cli + .confirm("Do you want to do another call using the existing smart contract?") + .initial_value(false) + .interact()?; + if another_call { + // Remove only the prompt asking for another call. + console::Term::stderr().clear_last_lines(2)?; + let new_call_config = self.guide_user_to_call_contract(cli).await?; + Box::pin(new_call_config.execute_call(prompt_to_repeat_call, cli)).await?; + } else { + display_message("Call completed successfully!", true, cli)?; + } } else { display_message("Call completed successfully!", true, cli)?; } - } else { - display_message("Call completed successfully!", true, cli)?; + Ok(()) } - Ok(()) } fn display_message(message: &str, success: bool, cli: &mut impl cli::traits::Cli) -> Result<()> { From 044b97e3c8cb175f08d57f7cf0d307e413c9b369 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 5 Nov 2024 17:30:57 +0100 Subject: [PATCH 104/211] test: adjust tests to refactor --- crates/pop-cli/src/commands/call/contract.rs | 178 +++++++++++++------ 1 file changed, 123 insertions(+), 55 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index b0e5895f8..df7bd32af 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -10,7 +10,7 @@ use pop_contracts::{ use sp_weights::Weight; use std::path::PathBuf; -const DEFAULT_URL: &str = "ws://localhost:9944"; +const DEFAULT_URL: &str = "ws://localhost:9944/"; const DEFAULT_URI: &str = "//Alice"; const DEFAULT_PAYABLE_VALUE: &str = "0"; @@ -94,7 +94,7 @@ impl CallContractCommand { if !self.args.is_empty() { full_message.push_str(&format!(" --args {}", self.args.join(" "))); } - if self.value != "0" { + if self.value != DEFAULT_PAYABLE_VALUE { full_message.push_str(&format!(" --value {}", self.value)); } if let Some(gas_limit) = self.gas_limit { @@ -119,13 +119,8 @@ impl CallContractCommand { cli: &mut impl cli::traits::Cli, ) -> anyhow::Result { cli.intro("Call a contract")?; - let call_config = if self.message.is_none() { - match self.guide_user_to_call_contract(cli).await { - Ok(config) => config, - Err(e) => { - return Err(anyhow!(format!("{}", e.to_string()))); - }, - } + if self.message.is_none() { + self.guide_user_to_call_contract(cli).await } else { self.clone() }; @@ -250,7 +245,7 @@ impl CallContractCommand { cli: &mut impl cli::traits::Cli, ) -> anyhow::Result { let contract_path: PathBuf = match &self.path { - Some(path) => path.to_path_buf(), + Some(path) => path.clone(), None => { // Prompt for path. let input_path: String = cli @@ -283,7 +278,7 @@ impl CallContractCommand { self.url.clone() }; let contract_address: String = match &self.contract { - Some(contract_address) => contract_address.to_string(), + Some(contract_address) => contract_address.clone(), None => { // Prompt for contract address. let contract_address: String = cli @@ -301,9 +296,9 @@ impl CallContractCommand { let message = { let mut prompt = cli.select("Select the message to call:"); - for select_message in messages { + for select_message in &messages { prompt = prompt.item( - select_message.clone(), + select_message, format!("{}\n", &select_message.label), &select_message.docs, ); @@ -362,13 +357,14 @@ impl CallContractCommand { self.suri.clone() }; - let mut is_call_confirmed: bool = true; - if message.mutates && !self.dev_mode { - is_call_confirmed = cli - .confirm("Do you want to execute the call? (Selecting 'No' will perform a dry run)") + let is_call_confirmed = if message.mutates && !self.dev_mode { + cli.confirm("Do you want to execute the call? (Selecting 'No' will perform a dry run)") .initial_value(true) - .interact()?; - } + .interact()? + } else { + true + }; + let call_command = CallContractCommand { path: Some(contract_path), contract: Some(contract_address), @@ -393,10 +389,12 @@ impl CallContractCommand { prompt_to_repeat_call: bool, cli: &mut impl cli::traits::Cli, ) -> anyhow::Result<()> { - let message = self - .message - .clone() - .expect("message can not be none as fallback above is interactive input; qed"); + let message = match &self.message { + Some(message) => message.to_string(), + None => { + return Err(anyhow!("Please specify the message to call.")); + }, + }; let contract = match &self.contract { Some(contract) => contract.to_string(), None => { @@ -472,11 +470,11 @@ impl CallContractCommand { cli.info(call_result)?; } if prompt_to_repeat_call { - let another_call: bool = cli + if cli .confirm("Do you want to do another call using the existing smart contract?") .initial_value(false) - .interact()?; - if another_call { + .interact()? + { // Remove only the prompt asking for another call. console::Term::stderr().clear_last_lines(2)?; let new_call_config = self.guide_user_to_call_contract(cli).await?; @@ -575,7 +573,7 @@ mod tests { temp_dir.path().join("testing").display().to_string(), )); // Contract deployed on Pop Network testnet, test dry-run - execute_call(call_config, false, &mut cli).await?; + call_config.execute_call(false, &mut cli).await?; cli.verify() } @@ -622,7 +620,7 @@ mod tests { .expect_outro("Call completed successfully!"); // Contract deployed on Pop Network testnet, test get - let config_call = CallContractCommand { + let call_config = CallContractCommand { path: Some(temp_dir.path().join("testing")), contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), message: Some("get".to_string()), @@ -639,7 +637,7 @@ mod tests { .set_up_call_config(&mut cli) .await?; // Test the query. With true, it will prompt for another call. - execute_call(config_call, true, &mut cli).await?; + call_config.execute_call(true, &mut cli).await?; cli.verify() } @@ -688,7 +686,22 @@ mod tests { temp_dir.path().join("testing").display().to_string(), )); - let call_config = guide_user_to_call_contract(None, None, None, false, &mut cli).await?; + let call_config = CallContractCommand { + path: None, + contract: None, + message: None, + args: vec![].to_vec(), + value: DEFAULT_PAYABLE_VALUE.to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse(DEFAULT_URL)?, + suri: DEFAULT_URI.to_string(), + dry_run: false, + execute: false, + dev_mode: false, + } + .guide_user_to_call_contract(&mut cli) + .await?; assert_eq!( call_config.contract, Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) @@ -759,7 +772,22 @@ mod tests { temp_dir.path().join("testing").display().to_string(), )); - let call_config = guide_user_to_call_contract(None, None, None, false, &mut cli).await?; + let call_config = CallContractCommand { + path: None, + contract: None, + message: None, + args: vec![].to_vec(), + value: DEFAULT_PAYABLE_VALUE.to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse(DEFAULT_URL)?, + suri: DEFAULT_URI.to_string(), + dry_run: false, + execute: false, + dev_mode: false, + } + .guide_user_to_call_contract(&mut cli) + .await?; assert_eq!( call_config.contract, Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) @@ -828,7 +856,22 @@ mod tests { temp_dir.path().join("testing").display().to_string(), )); - let call_config = guide_user_to_call_contract(None, None, None, true, &mut cli).await?; + let call_config = CallContractCommand { + path: None, + contract: None, + message: None, + args: vec![].to_vec(), + value: DEFAULT_PAYABLE_VALUE.to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse(DEFAULT_URL)?, + suri: DEFAULT_URI.to_string(), + dry_run: false, + execute: false, + dev_mode: true, + } + .guide_user_to_call_contract(&mut cli) + .await?; assert_eq!( call_config.contract, Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) @@ -856,14 +899,25 @@ mod tests { async fn guide_user_to_call_contract_fails_not_build() -> Result<()> { let temp_dir = new_environment("testing")?; let mut cli = MockCli::new(); - assert!( - matches!(guide_user_to_call_contract(Some(temp_dir.path().join("testing")), None, None, false, &mut cli).await, anyhow::Result::Err(message) if message.to_string().contains("Unable to fetch contract metadata: Failed to find any contract artifacts in target directory.")) - ); + assert!(matches!(CallContractCommand { + path: Some(temp_dir.path().join("testing")), + contract: None, + message: None, + args: vec![].to_vec(), + value: "0".to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, + suri: "//Alice".to_string(), + dry_run: false, + execute: false, + dev_mode: false, + }.guide_user_to_call_contract(&mut cli).await, anyhow::Result::Err(message) if message.to_string().contains("Unable to fetch contract metadata: Failed to find any contract artifacts in target directory."))); cli.verify() } #[tokio::test] - async fn call_contract_fails_no_message() -> Result<()> { + async fn execute_contract_fails_no_message_or_contract() -> Result<()> { let temp_dir = new_environment("testing")?; let mut current_dir = env::current_dir().expect("Failed to get current directory"); current_dir.pop(); @@ -873,29 +927,43 @@ mod tests { current_dir.join("pop-contracts/tests/files/testing.json"), )?; - let mut cli = MockCli::new().expect_intro(&"Call a contract"); - - let call_config = CallContractCommand { - path: Some(temp_dir.path().join("testing")), - contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), - message: None, - args: vec![].to_vec(), - value: "0".to_string(), - gas_limit: None, - proof_size: None, - url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, - suri: "//Alice".to_string(), - dry_run: false, - execute: false, - dev_mode: false, - } - .set_up_call_config(&mut cli) - .await?; + let mut cli = MockCli::new(); assert!(matches!( - execute_call(call_config, false, &mut cli).await, + CallContractCommand { + path: Some(temp_dir.path().join("testing")), + contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), + message: None, + args: vec![].to_vec(), + value: "0".to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, + suri: "//Alice".to_string(), + dry_run: false, + execute: false, + dev_mode: false, + }.execute_call(false, &mut cli).await, anyhow::Result::Err(message) if message.to_string() == "Please specify the message to call." )); + assert!(matches!( + CallContractCommand { + path: Some(temp_dir.path().join("testing")), + contract: None, + message: Some("get".to_string()), + args: vec![].to_vec(), + value: "0".to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, + suri: "//Alice".to_string(), + dry_run: false, + execute: false, + dev_mode: false, + }.execute_call(false, &mut cli).await, + anyhow::Result::Err(message) if message.to_string() == "Please specify the contract address." + )); + cli.verify() } From f331fa993fe07f00056d203354356d08278d3fec Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Wed, 6 Nov 2024 11:39:24 +0100 Subject: [PATCH 105/211] chore: reset_for_new_call fields --- crates/pop-cli/src/commands/call/contract.rs | 46 ++++++++++++-------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index df7bd32af..92cf8283b 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -64,13 +64,14 @@ pub struct CallContractCommand { impl CallContractCommand { /// Executes the command. pub(crate) async fn execute(self) -> Result<()> { - let call_config: CallContractCommand = match self.set_up_call_config(&mut cli::Cli).await { - Ok(call_config) => call_config, - Err(e) => { - display_message(&e.to_string(), false, &mut cli::Cli)?; - return Ok(()); - }, - }; + let mut call_config: CallContractCommand = + match self.set_up_call_config(&mut cli::Cli).await { + Ok(call_config) => call_config, + Err(e) => { + display_message(&e.to_string(), false, &mut cli::Cli)?; + return Ok(()); + }, + }; match call_config.execute_call(self.message.is_none(), &mut cli::Cli).await { Ok(_) => Ok(()), Err(e) => { @@ -314,8 +315,8 @@ impl CallContractCommand { .interact()?, ); } - let mut value = "0".to_string(); - if message.payable { + let mut value = self.value.clone(); + if message.payable && value == DEFAULT_PAYABLE_VALUE { value = cli .input("Value to transfer to the call:") .placeholder("0") @@ -326,9 +327,9 @@ impl CallContractCommand { }) .interact()?; } - let mut gas_limit: Option = None; - let mut proof_size: Option = None; - if message.mutates && !self.dev_mode { + let mut gas_limit: Option = self.gas_limit; + let mut proof_size: Option = self.proof_size; + if message.mutates && !self.dev_mode && gas_limit.is_none() { // Prompt for gas limit and proof_size of the call. let gas_limit_input: String = cli .input("Enter the gas limit:") @@ -337,6 +338,9 @@ impl CallContractCommand { .placeholder("If left blank, an estimation will be used") .interact()?; gas_limit = gas_limit_input.parse::().ok(); // If blank or bad input, estimate it. + } + + if message.mutates && !self.dev_mode && proof_size.is_none() { let proof_size_input: String = cli .input("Enter the proof size limit:") .required(false) @@ -385,7 +389,7 @@ impl CallContractCommand { /// Executes the call. async fn execute_call( - &self, + &mut self, prompt_to_repeat_call: bool, cli: &mut impl cli::traits::Cli, ) -> anyhow::Result<()> { @@ -417,7 +421,7 @@ impl CallContractCommand { { Ok(call_exec) => call_exec, Err(e) => { - return Err(anyhow!(format!("{}", e.root_cause().to_string()))); + return Err(anyhow!(format!("{}", e.to_string()))); }, }; @@ -477,7 +481,8 @@ impl CallContractCommand { { // Remove only the prompt asking for another call. console::Term::stderr().clear_last_lines(2)?; - let new_call_config = self.guide_user_to_call_contract(cli).await?; + self.reset_for_new_call(); + let mut new_call_config = self.guide_user_to_call_contract(cli).await?; Box::pin(new_call_config.execute_call(prompt_to_repeat_call, cli)).await?; } else { display_message("Call completed successfully!", true, cli)?; @@ -487,6 +492,13 @@ impl CallContractCommand { } Ok(()) } + + /// Resets message specific fields to default values for a new call. + fn reset_for_new_call(&mut self) { + self.value = DEFAULT_PAYABLE_VALUE.to_string(); + self.gas_limit = None; + self.proof_size = None; + } } fn display_message(message: &str, success: bool, cli: &mut impl cli::traits::Cli) -> Result<()> { @@ -552,7 +564,7 @@ mod tests { .expect_warning("Your call has not been executed.") .expect_info("Gas limit: Weight { ref_time: 100, proof_size: 10 }"); - let call_config = CallContractCommand { + let mut call_config = CallContractCommand { path: Some(temp_dir.path().join("testing")), contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), message: Some("flip".to_string()), @@ -620,7 +632,7 @@ mod tests { .expect_outro("Call completed successfully!"); // Contract deployed on Pop Network testnet, test get - let call_config = CallContractCommand { + let mut call_config = CallContractCommand { path: Some(temp_dir.path().join("testing")), contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), message: Some("get".to_string()), From b878401b139d5b9aa2fee8ee0c6963bbd8a32bad Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Wed, 6 Nov 2024 19:44:30 +0100 Subject: [PATCH 106/211] fix: build contract if has not been built --- crates/pop-cli/src/commands/call/contract.rs | 36 +++++++++++++-- crates/pop-cli/src/commands/up/contract.rs | 45 +----------------- crates/pop-cli/src/common/build.rs | 48 ++++++++++++++++++++ crates/pop-cli/src/common/mod.rs | 2 + 4 files changed, 85 insertions(+), 46 deletions(-) create mode 100644 crates/pop-cli/src/common/build.rs diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 92cf8283b..75fd9a636 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -1,11 +1,15 @@ // SPDX-License-Identifier: GPL-3.0 -use crate::cli::{self, traits::*}; +use crate::{ + cli::{self, traits::*}, + common::build::has_contract_been_built, +}; use anyhow::{anyhow, Result}; use clap::Args; +use cliclack::spinner; use pop_contracts::{ - call_smart_contract, dry_run_call, dry_run_gas_estimate_call, get_messages, parse_account, - set_up_call, CallOpts, + build_smart_contract, call_smart_contract, dry_run_call, dry_run_gas_estimate_call, + get_messages, parse_account, set_up_call, CallOpts, Verbosity, }; use sp_weights::Weight; use std::path::PathBuf; @@ -64,6 +68,7 @@ pub struct CallContractCommand { impl CallContractCommand { /// Executes the command. pub(crate) async fn execute(self) -> Result<()> { + self.ensure_contract_built(&mut cli::Cli).await?; let mut call_config: CallContractCommand = match self.set_up_call_config(&mut cli::Cli).await { Ok(call_config) => call_config, @@ -114,6 +119,31 @@ impl CallContractCommand { full_message } + /// Checks if the contract has been built; if not, builds it. + async fn ensure_contract_built(&self, cli: &mut impl cli::traits::Cli) -> Result<()> { + // Check if build exists in the specified "Contract build directory" + if !has_contract_been_built(self.path.as_deref()) { + // Build the contract in release mode + cli.warning("NOTE: contract has not yet been built.")?; + let spinner = spinner(); + spinner.start("Building contract in RELEASE mode..."); + let result = match build_smart_contract(self.path.as_deref(), true, Verbosity::Quiet) { + Ok(result) => result, + Err(e) => { + return Err(anyhow!(format!( + "🚫 An error occurred building your contract: {}\nUse `pop build` to retry with build output.", + e.to_string() + ))); + }, + }; + spinner.stop(format!( + "Your contract artifacts are ready. You can find them in: {}", + result.target_directory.display() + )); + } + Ok(()) + } + /// Set up the config call. async fn set_up_call_config( &self, diff --git a/crates/pop-cli/src/commands/up/contract.rs b/crates/pop-cli/src/commands/up/contract.rs index 7b3a2603f..2bdef1ae1 100644 --- a/crates/pop-cli/src/commands/up/contract.rs +++ b/crates/pop-cli/src/commands/up/contract.rs @@ -2,13 +2,12 @@ use crate::{ cli::{traits::Cli as _, Cli}, - common::contracts::check_contracts_node_and_prompt, + common::{build::has_contract_been_built, contracts::check_contracts_node_and_prompt}, style::style, }; use clap::Args; use cliclack::{confirm, log, log::error, spinner}; use console::{Emoji, Style}; -use pop_common::manifest::from_path; use pop_contracts::{ build_smart_contract, dry_run_gas_estimate_instantiate, dry_run_upload, instantiate_smart_contract, is_chain_alive, parse_hex_bytes, run_contracts_node, @@ -17,7 +16,7 @@ use pop_contracts::{ use sp_core::Bytes; use sp_weights::Weight; use std::{ - path::{Path, PathBuf}, + path::PathBuf, process::{Child, Command}, }; use tempfile::NamedTempFile; @@ -330,28 +329,9 @@ impl From for UpOpts { } } -/// Checks if a contract has been built by verifying the existence of the build directory and the -/// .contract file. -/// -/// # Arguments -/// * `path` - An optional path to the project directory. If no path is provided, the current -/// directory is used. -pub fn has_contract_been_built(path: Option<&Path>) -> bool { - let project_path = path.unwrap_or_else(|| Path::new("./")); - let manifest = match from_path(Some(project_path)) { - Ok(manifest) => manifest, - Err(_) => return false, - }; - let contract_name = manifest.package().name(); - project_path.join("target/ink").exists() && - project_path.join(format!("target/ink/{}.contract", contract_name)).exists() -} - #[cfg(test)] mod tests { use super::*; - use duct::cmd; - use std::fs::{self, File}; use url::Url; #[test] @@ -387,25 +367,4 @@ mod tests { ); Ok(()) } - - #[test] - fn has_contract_been_built_works() -> anyhow::Result<()> { - let temp_dir = tempfile::tempdir()?; - let path = temp_dir.path(); - - // Standard rust project - let name = "hello_world"; - cmd("cargo", ["new", name]).dir(&path).run()?; - let contract_path = path.join(name); - assert!(!has_contract_been_built(Some(&contract_path))); - - cmd("cargo", ["build"]).dir(&contract_path).run()?; - // Mock build directory - fs::create_dir(&contract_path.join("target/ink"))?; - assert!(!has_contract_been_built(Some(&path.join(name)))); - // Create a mocked .contract file inside the target directory - File::create(contract_path.join(format!("target/ink/{}.contract", name)))?; - assert!(has_contract_been_built(Some(&path.join(name)))); - Ok(()) - } } diff --git a/crates/pop-cli/src/common/build.rs b/crates/pop-cli/src/common/build.rs new file mode 100644 index 000000000..6bc7fd4d2 --- /dev/null +++ b/crates/pop-cli/src/common/build.rs @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-3.0 + +use pop_common::manifest::from_path; +use std::path::Path; +/// Checks if a contract has been built by verifying the existence of the build directory and the +/// .contract file. +/// +/// # Arguments +/// * `path` - An optional path to the project directory. If no path is provided, the current +/// directory is used. +pub fn has_contract_been_built(path: Option<&Path>) -> bool { + let project_path = path.unwrap_or_else(|| Path::new("./")); + let manifest = match from_path(Some(project_path)) { + Ok(manifest) => manifest, + Err(_) => return false, + }; + let contract_name = manifest.package().name(); + project_path.join("target/ink").exists() && + project_path.join(format!("target/ink/{}.contract", contract_name)).exists() +} + +#[cfg(test)] +mod tests { + use super::*; + use duct::cmd; + use std::fs::{self, File}; + + #[test] + fn has_contract_been_built_works() -> anyhow::Result<()> { + let temp_dir = tempfile::tempdir()?; + let path = temp_dir.path(); + + // Standard rust project + let name = "hello_world"; + cmd("cargo", ["new", name]).dir(&path).run()?; + let contract_path = path.join(name); + assert!(!has_contract_been_built(Some(&contract_path))); + + cmd("cargo", ["build"]).dir(&contract_path).run()?; + // Mock build directory + fs::create_dir(&contract_path.join("target/ink"))?; + assert!(!has_contract_been_built(Some(&path.join(name)))); + // Create a mocked .contract file inside the target directory + File::create(contract_path.join(format!("target/ink/{}.contract", name)))?; + assert!(has_contract_been_built(Some(&path.join(name)))); + Ok(()) + } +} diff --git a/crates/pop-cli/src/common/mod.rs b/crates/pop-cli/src/common/mod.rs index 1cb3ee579..0469db60a 100644 --- a/crates/pop-cli/src/common/mod.rs +++ b/crates/pop-cli/src/common/mod.rs @@ -1,5 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 +#[cfg(feature = "contract")] +pub mod build; #[cfg(feature = "contract")] pub mod contracts; pub mod helpers; From bee10d5b3ef886b0e50bab8e9952bc1b46b1e631 Mon Sep 17 00:00:00 2001 From: Frank Bell <60948618+evilrobot-01@users.noreply.github.com> Date: Wed, 6 Nov 2024 22:03:35 +0000 Subject: [PATCH 107/211] refactor: use command state (#338) Merged set_up_call_config and guide_user_to_call_contract into a single function. Also adds short symbols for arguments. --- crates/pop-cli/src/commands/call/contract.rs | 417 +++++++------------ 1 file changed, 148 insertions(+), 269 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 75fd9a636..1a559ebc7 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -24,7 +24,7 @@ pub struct CallContractCommand { #[arg(short = 'p', long)] path: Option, /// The address of the contract to call. - #[clap(name = "contract", long, env = "CONTRACT")] + #[clap(name = "contract", short = 'c', long, env = "CONTRACT")] contract: Option, /// The name of the contract message to call. #[clap(long, short)] @@ -33,19 +33,19 @@ pub struct CallContractCommand { #[clap(long, num_args = 0..)] args: Vec, /// The value to be transferred as part of the call. - #[clap(name = "value", long, default_value = DEFAULT_PAYABLE_VALUE)] + #[clap(name = "value", short = 'v', long, default_value = DEFAULT_PAYABLE_VALUE)] value: String, /// Maximum amount of gas to be used for this command. /// If not specified it will perform a dry-run to estimate the gas consumed for the /// call. - #[clap(name = "gas", long)] + #[clap(name = "gas", short = 'g', long)] gas_limit: Option, /// Maximum proof size for this command. /// If not specified it will perform a dry-run to estimate the proof size required. - #[clap(long)] + #[clap(short = 'P', long)] proof_size: Option, /// Websocket endpoint of a node. - #[clap(name = "url", long, value_parser, default_value = DEFAULT_URL)] + #[clap(name = "url", short = 'u', long, value_parser, default_value = DEFAULT_URL)] url: url::Url, /// Secret key URI for the account calling the contract. /// @@ -58,7 +58,7 @@ pub struct CallContractCommand { #[clap(short('x'), long)] execute: bool, /// Perform a dry-run via RPC to estimate the gas usage. This does not submit a transaction. - #[clap(long, conflicts_with = "execute")] + #[clap(short = 'D', long, conflicts_with = "execute")] dry_run: bool, /// Enables developer mode, bypassing certain user prompts for faster testing. /// Recommended for testing and local development only. @@ -67,23 +67,21 @@ pub struct CallContractCommand { } impl CallContractCommand { /// Executes the command. - pub(crate) async fn execute(self) -> Result<()> { + pub(crate) async fn execute(mut self) -> Result<()> { + // Ensure contract is built. self.ensure_contract_built(&mut cli::Cli).await?; - let mut call_config: CallContractCommand = - match self.set_up_call_config(&mut cli::Cli).await { - Ok(call_config) => call_config, - Err(e) => { - display_message(&e.to_string(), false, &mut cli::Cli)?; - return Ok(()); - }, - }; - match call_config.execute_call(self.message.is_none(), &mut cli::Cli).await { - Ok(_) => Ok(()), - Err(e) => { - display_message(&e.to_string(), false, &mut cli::Cli)?; - Ok(()) - }, + // Check if message specified via command line argument. + let prompt_to_repeat_call = self.message.is_none(); + // Configure the call based on command line arguments/call UI. + if let Err(e) = self.configure(&mut cli::Cli, false).await { + display_message(&e.to_string(), false, &mut cli::Cli)?; + return Ok(()); + }; + // Finally execute the call. + if let Err(e) = self.execute_call(&mut cli::Cli, prompt_to_repeat_call).await { + display_message(&e.to_string(), false, &mut cli::Cli)?; } + Ok(()) } fn display(&self) -> String { @@ -120,7 +118,7 @@ impl CallContractCommand { } /// Checks if the contract has been built; if not, builds it. - async fn ensure_contract_built(&self, cli: &mut impl cli::traits::Cli) -> Result<()> { + async fn ensure_contract_built(&self, cli: &mut impl Cli) -> Result<()> { // Check if build exists in the specified "Contract build directory" if !has_contract_been_built(self.path.as_deref()) { // Build the contract in release mode @@ -144,149 +142,39 @@ impl CallContractCommand { Ok(()) } - /// Set up the config call. - async fn set_up_call_config( - &self, - cli: &mut impl cli::traits::Cli, - ) -> anyhow::Result { - cli.intro("Call a contract")?; - if self.message.is_none() { - self.guide_user_to_call_contract(cli).await - } else { - self.clone() - }; - match self.execute_call(call_config.clone()).await { - Ok(_) => Ok(()), - Err(e) => { - self.cli.outro_cancel(format!("{}", e.to_string()))?; - return Ok(()); - }, + /// Configure the call based on command line arguments/call UI. + async fn configure(&mut self, cli: &mut impl Cli, repeat: bool) -> Result<()> { + // Show intro on first run. + if !repeat { + cli.intro("Call a contract")?; } - } - /// Executes the call. - async fn execute_call(&mut self, call_config: CallContractCommand) -> Result<()> { - let contract = call_config - .contract - .clone() - .expect("contract can not be none as fallback above is interactive input; qed"); - let message = match call_config.message { - Some(m) => m, - None => { - return Err(anyhow!("Please specify the message to call.")); - }, - }; - let call_exec = match set_up_call(CallOpts { - path: call_config.path.clone(), - contract, - message, - args: call_config.args, - value: call_config.value, - gas_limit: call_config.gas_limit, - proof_size: call_config.proof_size, - url: call_config.url.clone(), - suri: call_config.suri, - execute: call_config.execute, - }) - .await - { - Ok(call_exec) => call_exec, - Err(e) => { - return Err(anyhow!(format!("{}", e.root_cause().to_string()))); - }, - }; - - if call_config.dry_run { - let spinner = cliclack::spinner(); - spinner.start("Doing a dry run to estimate the gas..."); - match dry_run_gas_estimate_call(&call_exec).await { - Ok(w) => { - self.cli.info(format!("Gas limit: {:?}", w))?; - self.cli.warning("Your call has not been executed.")?; - }, - Err(e) => { - spinner.error(format!("{e}")); - self.cli.outro_cancel("Call failed.")?; - }, - }; + // If message has been specified via command line arguments, return early. + if self.message.is_some() { return Ok(()); } - if !call_config.execute { - let spinner = cliclack::spinner(); - spinner.start("Calling the contract..."); - let call_dry_run_result = dry_run_call(&call_exec).await?; - self.cli.info(format!("Result: {}", call_dry_run_result))?; - self.cli.warning("Your call has not been executed.")?; - } else { - let weight_limit = if self.gas_limit.is_some() && self.proof_size.is_some() { - Weight::from_parts(self.gas_limit.unwrap(), self.proof_size.unwrap()) - } else { - let spinner = cliclack::spinner(); - spinner.start("Doing a dry run to estimate the gas..."); - match dry_run_gas_estimate_call(&call_exec).await { - Ok(w) => { - self.cli.info(format!("Gas limit: {:?}", w))?; - w - }, - Err(e) => { - spinner.error(format!("{e}")); - return Err(anyhow!("Call failed.")); - }, + // Resolve path. + let contract_path = match self.path.as_ref() { + None => { + let path = Some(PathBuf::from("./")); + if has_contract_been_built(path.as_deref()) { + self.path = path; + } else { + // Prompt for path. + let input_path: String = cli + .input("Where is your project located?") + .placeholder("./") + .default_input("./") + .interact()?; + self.path = Some(PathBuf::from(input_path)); } - }; - let spinner = cliclack::spinner(); - spinner.start("Calling the contract..."); - - let call_result = call_smart_contract(call_exec, weight_limit, &call_config.url) - .await - .map_err(|err| anyhow!("{} {}", "ERROR:", format!("{err:?}")))?; - self.cli.info(call_result)?; - } - if self.args.contract.is_none() { - let another_call: bool = self - .cli - .confirm("Do you want to do another call using the existing smart contract?") - .initial_value(false) - .interact()?; - if another_call { - // Remove only the prompt asking for another call. - console::Term::stderr().clear_last_lines(2)?; - let new_call_config = guide_user_to_call_contract( - self, - call_config.path, - Some(call_config.url), - call_config.contract, - ) - .await?; - Box::pin(self.execute_call(new_call_config)).await?; - } else { - self.cli.outro("Call completed successfully!")?; - } - } else { - self.cli.outro("Call completed successfully!")?; - } - return Ok(()); - } - - /// Guide the user to call the contract. - async fn guide_user_to_call_contract( - &self, - cli: &mut impl cli::traits::Cli, - ) -> anyhow::Result { - let contract_path: PathBuf = match &self.path { - Some(path) => path.clone(), - None => { - // Prompt for path. - let input_path: String = cli - .input("Where is your project located?") - .placeholder("./") - .default_input("./") - .interact()?; - PathBuf::from(input_path) + self.path.as_ref().unwrap() }, + Some(p) => p, }; + // Parse the contract metadata provided. If there is an error, do not prompt for more. let messages = match get_messages(&contract_path) { Ok(messages) => messages, @@ -297,34 +185,34 @@ impl CallContractCommand { ))); }, }; - let url = if self.url.as_str() == DEFAULT_URL { + + // Resolve url. + if !repeat && self.url.as_str() == DEFAULT_URL { // Prompt for url. let url: String = cli .input("Where is your contract deployed?") .placeholder("ws://localhost:9944") .default_input("ws://localhost:9944") .interact()?; - url::Url::parse(&url)? - } else { - self.url.clone() + self.url = url::Url::parse(&url)? }; - let contract_address: String = match &self.contract { - Some(contract_address) => contract_address.clone(), - None => { - // Prompt for contract address. - let contract_address: String = cli - .input("Paste the on-chain contract address:") - .placeholder("e.g. 5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") - .validate(|input: &String| match parse_account(input) { - Ok(_) => Ok(()), - Err(_) => Err("Invalid address."), - }) - .default_input("5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") - .interact()?; - contract_address - }, + + // Resolve contract address. + if let None = self.contract { + // Prompt for contract address. + let contract_address: String = cli + .input("Paste the on-chain contract address:") + .placeholder("e.g. 5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") + .validate(|input: &String| match parse_account(input) { + Ok(_) => Ok(()), + Err(_) => Err("Invalid address."), + }) + .default_input("5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") + .interact()?; + self.contract = Some(contract_address); }; + // Resolve message. let message = { let mut prompt = cli.select("Select the message to call:"); for select_message in &messages { @@ -334,9 +222,12 @@ impl CallContractCommand { &select_message.docs, ); } - prompt.interact()? + let message = prompt.interact()?; + self.message = Some(message.label.clone()); + message }; + // Resolve message arguments. let mut contract_args = Vec::new(); for arg in &message.args { contract_args.push( @@ -345,9 +236,11 @@ impl CallContractCommand { .interact()?, ); } - let mut value = self.value.clone(); - if message.payable && value == DEFAULT_PAYABLE_VALUE { - value = cli + self.args = contract_args; + + // Resolve value. + if message.payable && self.value == DEFAULT_PAYABLE_VALUE { + self.value = cli .input("Value to transfer to the call:") .placeholder("0") .default_input("0") @@ -357,9 +250,9 @@ impl CallContractCommand { }) .interact()?; } - let mut gas_limit: Option = self.gas_limit; - let mut proof_size: Option = self.proof_size; - if message.mutates && !self.dev_mode && gas_limit.is_none() { + + // Resolve gas limit. + if message.mutates && !self.dev_mode && self.gas_limit.is_none() { // Prompt for gas limit and proof_size of the call. let gas_limit_input: String = cli .input("Enter the gas limit:") @@ -367,30 +260,31 @@ impl CallContractCommand { .default_input("") .placeholder("If left blank, an estimation will be used") .interact()?; - gas_limit = gas_limit_input.parse::().ok(); // If blank or bad input, estimate it. + self.gas_limit = gas_limit_input.parse::().ok(); // If blank or bad input, estimate it. } - if message.mutates && !self.dev_mode && proof_size.is_none() { + // Resolve proof size. + if message.mutates && !self.dev_mode && self.proof_size.is_none() { let proof_size_input: String = cli .input("Enter the proof size limit:") .required(false) .placeholder("If left blank, an estimation will be used") .default_input("") .interact()?; - proof_size = proof_size_input.parse::().ok(); // If blank or bad input, estimate it. + self.proof_size = proof_size_input.parse::().ok(); // If blank or bad input, estimate it. } - // Who is calling the contract. - let suri = if self.suri == DEFAULT_URI { + // Resolve who is calling the contract. + if self.suri == DEFAULT_URI { // Prompt for uri. - cli.input("Signer calling the contract:") + self.suri = cli + .input("Signer calling the contract:") .placeholder("//Alice") .default_input("//Alice") - .interact()? - } else { - self.suri.clone() + .interact()?; }; + // Finally prompt for confirmation. let is_call_confirmed = if message.mutates && !self.dev_mode { cli.confirm("Do you want to execute the call? (Selecting 'No' will perform a dry run)") .initial_value(true) @@ -398,31 +292,19 @@ impl CallContractCommand { } else { true }; + self.execute = is_call_confirmed && message.mutates; + self.dry_run = !is_call_confirmed; - let call_command = CallContractCommand { - path: Some(contract_path), - contract: Some(contract_address), - message: Some(message.label.clone()), - args: contract_args, - value, - gas_limit, - proof_size, - url, - suri, - execute: if is_call_confirmed { message.mutates } else { false }, - dry_run: !is_call_confirmed, - dev_mode: self.dev_mode, - }; - cli.info(call_command.display())?; - Ok(call_command) + cli.info(self.display())?; + Ok(()) } - /// Executes the call. + /// Execute the call. async fn execute_call( &mut self, + cli: &mut impl Cli, prompt_to_repeat_call: bool, - cli: &mut impl cli::traits::Cli, - ) -> anyhow::Result<()> { + ) -> Result<()> { let message = match &self.message { Some(message) => message.to_string(), None => { @@ -456,7 +338,7 @@ impl CallContractCommand { }; if self.dry_run { - let spinner = cliclack::spinner(); + let spinner = spinner(); spinner.start("Doing a dry run to estimate the gas..."); match dry_run_gas_estimate_call(&call_exec).await { Ok(w) => { @@ -472,7 +354,7 @@ impl CallContractCommand { } if !self.execute { - let spinner = cliclack::spinner(); + let spinner = spinner(); spinner.start("Calling the contract..."); let call_dry_run_result = dry_run_call(&call_exec).await?; cli.info(format!("Result: {}", call_dry_run_result))?; @@ -481,7 +363,7 @@ impl CallContractCommand { let weight_limit = if self.gas_limit.is_some() && self.proof_size.is_some() { Weight::from_parts(self.gas_limit.unwrap(), self.proof_size.unwrap()) } else { - let spinner = cliclack::spinner(); + let spinner = spinner(); spinner.start("Doing a dry run to estimate the gas..."); match dry_run_gas_estimate_call(&call_exec).await { Ok(w) => { @@ -494,7 +376,7 @@ impl CallContractCommand { }, } }; - let spinner = cliclack::spinner(); + let spinner = spinner(); spinner.start("Calling the contract..."); let call_result = call_smart_contract(call_exec, weight_limit, &self.url) @@ -503,35 +385,37 @@ impl CallContractCommand { cli.info(call_result)?; } - if prompt_to_repeat_call { - if cli - .confirm("Do you want to do another call using the existing smart contract?") - .initial_value(false) - .interact()? - { - // Remove only the prompt asking for another call. - console::Term::stderr().clear_last_lines(2)?; - self.reset_for_new_call(); - let mut new_call_config = self.guide_user_to_call_contract(cli).await?; - Box::pin(new_call_config.execute_call(prompt_to_repeat_call, cli)).await?; - } else { - display_message("Call completed successfully!", true, cli)?; - } - } else { + + // Prompt for any additional calls. + if !prompt_to_repeat_call { display_message("Call completed successfully!", true, cli)?; + return Ok(()); + } + if cli + .confirm("Do you want to perform another call using the existing smart contract?") + .initial_value(false) + .interact()? + { + // Reset specific items from the last call and repeat. + self.reset_for_new_call(); + self.configure(cli, true).await?; + Box::pin(self.execute_call(cli, prompt_to_repeat_call)).await + } else { + display_message("Contract calling complete.", true, cli)?; + Ok(()) } - Ok(()) } /// Resets message specific fields to default values for a new call. fn reset_for_new_call(&mut self) { + self.message = None; self.value = DEFAULT_PAYABLE_VALUE.to_string(); self.gas_limit = None; self.proof_size = None; } } -fn display_message(message: &str, success: bool, cli: &mut impl cli::traits::Cli) -> Result<()> { +fn display_message(message: &str, success: bool, cli: &mut impl Cli) -> Result<()> { if success { cli.outro(message)?; } else { @@ -607,15 +491,14 @@ mod tests { dry_run: true, execute: false, dev_mode: false, - } - .set_up_call_config(&mut cli) - .await?; + }; + call_config.configure(&mut cli, false).await?; assert_eq!(call_config.display(), format!( "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message flip --gas 100 --proof_size 10 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --dry_run", temp_dir.path().join("testing").display().to_string(), )); // Contract deployed on Pop Network testnet, test dry-run - call_config.execute_call(false, &mut cli).await?; + call_config.execute_call(&mut cli, false).await?; cli.verify() } @@ -639,11 +522,11 @@ mod tests { .expect_intro(&"Call a contract") .expect_warning("Your call has not been executed.") .expect_confirm( - "Do you want to do another call using the existing smart contract?", + "Do you want to perform another call using the existing smart contract?", false, ) .expect_confirm( - "Do you want to do another call using the existing smart contract?", + "Do you want to perform another call using the existing smart contract?", true, ) .expect_select::( @@ -659,7 +542,7 @@ mod tests { temp_dir.path().join("testing").display().to_string(), )) .expect_warning("Your call has not been executed.") - .expect_outro("Call completed successfully!"); + .expect_outro("Contract calling complete."); // Contract deployed on Pop Network testnet, test get let mut call_config = CallContractCommand { @@ -675,11 +558,10 @@ mod tests { dry_run: false, execute: false, dev_mode: false, - } - .set_up_call_config(&mut cli) - .await?; + }; + call_config.configure(&mut cli, false).await?; // Test the query. With true, it will prompt for another call. - call_config.execute_call(true, &mut cli).await?; + call_config.execute_call(&mut cli, true).await?; cli.verify() } @@ -728,7 +610,7 @@ mod tests { temp_dir.path().join("testing").display().to_string(), )); - let call_config = CallContractCommand { + let mut call_config = CallContractCommand { path: None, contract: None, message: None, @@ -741,9 +623,8 @@ mod tests { dry_run: false, execute: false, dev_mode: false, - } - .guide_user_to_call_contract(&mut cli) - .await?; + }; + call_config.configure(&mut cli, false).await?; assert_eq!( call_config.contract, Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) @@ -814,7 +695,7 @@ mod tests { temp_dir.path().join("testing").display().to_string(), )); - let call_config = CallContractCommand { + let mut call_config = CallContractCommand { path: None, contract: None, message: None, @@ -827,9 +708,8 @@ mod tests { dry_run: false, execute: false, dev_mode: false, - } - .guide_user_to_call_contract(&mut cli) - .await?; + }; + call_config.configure(&mut cli, false).await?; assert_eq!( call_config.contract, Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) @@ -898,7 +778,7 @@ mod tests { temp_dir.path().join("testing").display().to_string(), )); - let call_config = CallContractCommand { + let mut call_config = CallContractCommand { path: None, contract: None, message: None, @@ -911,9 +791,8 @@ mod tests { dry_run: false, execute: false, dev_mode: true, - } - .guide_user_to_call_contract(&mut cli) - .await?; + }; + call_config.configure(&mut cli, false).await?; assert_eq!( call_config.contract, Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) @@ -942,19 +821,19 @@ mod tests { let temp_dir = new_environment("testing")?; let mut cli = MockCli::new(); assert!(matches!(CallContractCommand { - path: Some(temp_dir.path().join("testing")), - contract: None, - message: None, - args: vec![].to_vec(), - value: "0".to_string(), - gas_limit: None, - proof_size: None, - url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, - suri: "//Alice".to_string(), - dry_run: false, - execute: false, - dev_mode: false, - }.guide_user_to_call_contract(&mut cli).await, anyhow::Result::Err(message) if message.to_string().contains("Unable to fetch contract metadata: Failed to find any contract artifacts in target directory."))); + path: Some(temp_dir.path().join("testing")), + contract: None, + message: None, + args: vec![].to_vec(), + value: "0".to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, + suri: "//Alice".to_string(), + dry_run: false, + execute: false, + dev_mode: false, + }.configure(&mut cli, false).await, Err(message) if message.to_string().contains("Unable to fetch contract metadata: Failed to find any contract artifacts in target directory."))); cli.verify() } @@ -984,7 +863,7 @@ mod tests { dry_run: false, execute: false, dev_mode: false, - }.execute_call(false, &mut cli).await, + }.execute_call(&mut cli, false).await, anyhow::Result::Err(message) if message.to_string() == "Please specify the message to call." )); @@ -1002,7 +881,7 @@ mod tests { dry_run: false, execute: false, dev_mode: false, - }.execute_call(false, &mut cli).await, + }.execute_call(&mut cli, false).await, anyhow::Result::Err(message) if message.to_string() == "Please specify the contract address." )); From cc92276908799c174ca5ff7221c82ec221f78d0c Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Mon, 4 Nov 2024 18:26:58 +0100 Subject: [PATCH 108/211] fix: automatically add some or none to Option argument --- crates/pop-cli/src/commands/call/contract.rs | 4 +-- crates/pop-contracts/src/call/metadata.rs | 35 ++++++++++++++++++++ crates/pop-contracts/src/errors.rs | 2 ++ crates/pop-contracts/src/lib.rs | 2 +- 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 1a559ebc7..fc9b7787a 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -30,7 +30,7 @@ pub struct CallContractCommand { #[clap(long, short)] message: Option, /// The constructor arguments, encoded as strings. - #[clap(long, num_args = 0..)] + #[clap(long, num_args = 0.., value_delimiter = ',')] args: Vec, /// The value to be transferred as part of the call. #[clap(name = "value", short = 'v', long, default_value = DEFAULT_PAYABLE_VALUE)] @@ -96,7 +96,7 @@ impl CallContractCommand { full_message.push_str(&format!(" --message {}", message)); } if !self.args.is_empty() { - full_message.push_str(&format!(" --args {}", self.args.join(" "))); + full_message.push_str(&format!(" --args {}", self.args.join(","))); } if self.value != DEFAULT_PAYABLE_VALUE { full_message.push_str(&format!(" --value {}", self.value)); diff --git a/crates/pop-contracts/src/call/metadata.rs b/crates/pop-contracts/src/call/metadata.rs index 605d06206..31c2a07d2 100644 --- a/crates/pop-contracts/src/call/metadata.rs +++ b/crates/pop-contracts/src/call/metadata.rs @@ -56,6 +56,19 @@ pub fn get_messages(path: &Path) -> Result, Error> { } Ok(messages) } + +/// Extracts the information of a smart contract message parsing the metadata file. +/// +/// # Arguments +/// * `path` - Location path of the project. +/// * `message` - The label of the contract message. +pub fn get_message(path: &Path, message: &str) -> Result { + get_messages(path)? + .into_iter() + .find(|msg| msg.label == message) + .ok_or_else(|| Error::InvalidMessageName(message.to_string())) +} + // Parse the message parameters into a vector of argument labels. fn process_args(message_params: &[MessageParamSpec]) -> Vec { let mut args: Vec = Vec::new(); @@ -99,4 +112,26 @@ mod tests { assert_eq!(message[2].args[0].type_name, "bool".to_string()); Ok(()) } + + #[test] + fn get_message_work() -> Result<()> { + let temp_dir = generate_smart_contract_test_environment()?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; + assert!(matches!( + get_message(&temp_dir.path().join("testing"), "wrong_flip"), + Err(Error::InvalidMessageName(name)) if name == "wrong_flip".to_string())); + let message = get_message(&temp_dir.path().join("testing"), "specific_flip")?; + assert_eq!(message.label, "specific_flip"); + assert_eq!(message.docs, " A message for testing, flips the value of the stored `bool` with `new_value` and is payable"); + // assert parsed arguments + assert_eq!(message.args.len(), 1); + assert_eq!(message.args[0].label, "new_value".to_string()); + assert_eq!(message.args[0].type_name, "bool".to_string()); + Ok(()) + } } diff --git a/crates/pop-contracts/src/errors.rs b/crates/pop-contracts/src/errors.rs index a0ea14ade..066e35e16 100644 --- a/crates/pop-contracts/src/errors.rs +++ b/crates/pop-contracts/src/errors.rs @@ -28,6 +28,8 @@ pub enum Error { InstallContractsNode(String), #[error("{0}")] InstantiateContractError(String), + #[error("Invalid message name: {0}")] + InvalidMessageName(String), #[error("Invalid name: {0}")] InvalidName(String), #[error("IO error: {0}")] diff --git a/crates/pop-contracts/src/lib.rs b/crates/pop-contracts/src/lib.rs index a3ff0175c..521833be1 100644 --- a/crates/pop-contracts/src/lib.rs +++ b/crates/pop-contracts/src/lib.rs @@ -15,7 +15,7 @@ mod utils; pub use build::{build_smart_contract, is_supported, Verbosity}; pub use call::{ call_smart_contract, dry_run_call, dry_run_gas_estimate_call, - metadata::{get_messages, Message}, + metadata::{get_message, get_messages, Message}, set_up_call, CallOpts, }; pub use new::{create_smart_contract, is_valid_contract_name}; From 183758860edbf4a310d42a090dc35ad95cf25288 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Mon, 4 Nov 2024 20:25:36 +0100 Subject: [PATCH 109/211] test: refactor and tests --- crates/pop-cli/src/commands/call/contract.rs | 8 +- crates/pop-contracts/src/call/metadata.rs | 84 ++++++++++- crates/pop-contracts/src/call/mod.rs | 26 ++-- crates/pop-contracts/src/errors.rs | 8 +- crates/pop-contracts/src/lib.rs | 2 +- .../tests/files/testing.contract | 2 +- crates/pop-contracts/tests/files/testing.json | 140 ++++++++++++------ 7 files changed, 206 insertions(+), 64 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index fc9b7787a..3b22d014c 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -671,6 +671,7 @@ mod tests { .expect_input("Enter the proof size limit:", "".into()) // Only if call .expect_input("Enter the gas limit:", "".into()) // Only if call .expect_input("Value to transfer to the call:", "50".into()) // Only if payable + .expect_input("Enter the value for the parameter: number", "2".into()) // Args for specific_flip .expect_input("Enter the value for the parameter: new_value", "true".into()) // Args for specific_flip .expect_select::( "Select the message to call:", @@ -691,7 +692,7 @@ mod tests { "Where is your project located?", temp_dir.path().join("testing").display().to_string(), ).expect_info(format!( - "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true,2 --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", temp_dir.path().join("testing").display().to_string(), )); @@ -715,8 +716,9 @@ mod tests { Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) ); assert_eq!(call_config.message, Some("specific_flip".to_string())); - assert_eq!(call_config.args.len(), 1); + assert_eq!(call_config.args.len(), 2); assert_eq!(call_config.args[0], "true".to_string()); + assert_eq!(call_config.args[1], "2".to_string()); assert_eq!(call_config.value, "50".to_string()); assert_eq!(call_config.gas_limit, None); assert_eq!(call_config.proof_size, None); @@ -725,7 +727,7 @@ mod tests { assert!(call_config.execute); assert!(!call_config.dry_run); assert_eq!(call_config.display(), format!( - "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true,2 --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", temp_dir.path().join("testing").display().to_string(), )); diff --git a/crates/pop-contracts/src/call/metadata.rs b/crates/pop-contracts/src/call/metadata.rs index 31c2a07d2..6c4b58db3 100644 --- a/crates/pop-contracts/src/call/metadata.rs +++ b/crates/pop-contracts/src/call/metadata.rs @@ -62,7 +62,7 @@ pub fn get_messages(path: &Path) -> Result, Error> { /// # Arguments /// * `path` - Location path of the project. /// * `message` - The label of the contract message. -pub fn get_message(path: &Path, message: &str) -> Result { +fn get_message(path: &Path, message: &str) -> Result { get_messages(path)? .into_iter() .find(|msg| msg.label == message) @@ -81,6 +81,41 @@ fn process_args(message_params: &[MessageParamSpec]) -> Vec args } +/// Generates a list of processed argument values for a specified contract message, +/// wrapping each value in `Some(...)` or replacing it with `None` if the argument is optional +/// +/// # Arguments +/// * `path` - Location path of the project. +/// * `message_label` - Label of the contract message to retrieve. +/// * `args` - Argument values provided by the user. +pub fn generate_message_args( + path: Option<&Path>, + message_label: &str, + args: Vec, +) -> Result, Error> { + let contract_path = path.unwrap_or_else(|| Path::new("./")); + let message = get_message(&contract_path, message_label)?; + if args.len() != message.args.len() { + return Err(Error::WrongNumberArguments { + expected: message.args.len(), + provided: args.len(), + }); + } + Ok(args + .into_iter() + .zip(&message.args) + .map(|(arg, param)| { + if param.type_name == "Option" && arg.is_empty() { + "None".to_string() + } else if param.type_name == "Option" { + format!("Some({})", arg) + } else { + arg + } + }) + .collect::>()) +} + #[cfg(test)] mod tests { use std::env; @@ -107,9 +142,11 @@ mod tests { assert_eq!(message[2].label, "specific_flip"); assert_eq!(message[2].docs, " A message for testing, flips the value of the stored `bool` with `new_value` and is payable"); // assert parsed arguments - assert_eq!(message[2].args.len(), 1); + assert_eq!(message[2].args.len(), 2); assert_eq!(message[2].args[0].label, "new_value".to_string()); assert_eq!(message[2].args[0].type_name, "bool".to_string()); + assert_eq!(message[2].args[1].label, "number".to_string()); + assert_eq!(message[2].args[1].type_name, "Option".to_string()); Ok(()) } @@ -129,9 +166,50 @@ mod tests { assert_eq!(message.label, "specific_flip"); assert_eq!(message.docs, " A message for testing, flips the value of the stored `bool` with `new_value` and is payable"); // assert parsed arguments - assert_eq!(message.args.len(), 1); + assert_eq!(message.args.len(), 2); assert_eq!(message.args[0].label, "new_value".to_string()); assert_eq!(message.args[0].type_name, "bool".to_string()); + assert_eq!(message.args[1].label, "number".to_string()); + assert_eq!(message.args[1].type_name, "Option".to_string()); + Ok(()) + } + + #[test] + fn generate_message_args_work() -> Result<()> { + let temp_dir = generate_smart_contract_test_environment()?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; + assert!(matches!( + generate_message_args(Some(&temp_dir.path().join("testing")),"wrong_flip", Vec::new()), + Err(Error::InvalidMessageName(error)) if error == "wrong_flip".to_string())); + assert!(matches!( + generate_message_args( + Some(&temp_dir.path().join("testing")), + "specific_flip", + Vec::new() + ), + Err(Error::WrongNumberArguments {expected, provided }) if expected == 2 && provided == 0 + )); + assert_eq!( + generate_message_args( + Some(&temp_dir.path().join("testing")), + "specific_flip", + ["true".to_string(), "2".to_string()].to_vec() + )?, + ["true".to_string(), "Some(2)".to_string()] + ); + assert_eq!( + generate_message_args( + Some(&temp_dir.path().join("testing")), + "specific_flip", + ["true".to_string(), "".to_string()].to_vec() + )?, + ["true".to_string(), "None".to_string()] + ); Ok(()) } } diff --git a/crates/pop-contracts/src/call/mod.rs b/crates/pop-contracts/src/call/mod.rs index 73066bbd8..6af84c03b 100644 --- a/crates/pop-contracts/src/call/mod.rs +++ b/crates/pop-contracts/src/call/mod.rs @@ -53,7 +53,7 @@ pub struct CallOpts { /// * `call_opts` - options for the `call` command. pub async fn set_up_call( call_opts: CallOpts, -) -> anyhow::Result> { +) -> Result, Error> { let token_metadata = TokenMetadata::query::(&call_opts.url).await?; let manifest_path = get_manifest_path(call_opts.path.as_deref())?; let signer = create_signer(&call_opts.suri)?; @@ -67,10 +67,16 @@ pub async fn set_up_call( parse_balance(&call_opts.value)?; let contract: ::AccountId = parse_account(&call_opts.contract)?; + // Parse the argument values input by the user. + let args = metadata::generate_message_args( + call_opts.path.as_deref(), + &call_opts.message, + call_opts.args, + )?; let call_exec: CallExec = CallCommandBuilder::new(contract.clone(), &call_opts.message, extrinsic_opts) - .args(call_opts.args.clone()) + .args(args) .value(value.denominate_balance(&token_metadata)?) .gas_limit(call_opts.gas_limit) .proof_size(call_opts.proof_size) @@ -212,11 +218,9 @@ mod tests { suri: "//Alice".to_string(), execute: false, }; - let call = set_up_call(call_opts).await; - assert!(call.is_err()); - let error = call.err().unwrap(); - assert_eq!(error.root_cause().to_string(), "Failed to find any contract artifacts in target directory. \nRun `cargo contract build --release` to generate the artifacts."); - + assert!( + matches!(set_up_call(call_opts).await, Err(Error::AnyhowError(message)) if message.root_cause().to_string() == "Failed to find any contract artifacts in target directory. \nRun `cargo contract build --release` to generate the artifacts.") + ); Ok(()) } #[tokio::test] @@ -233,11 +237,9 @@ mod tests { suri: "//Alice".to_string(), execute: false, }; - let call = set_up_call(call_opts).await; - assert!(call.is_err()); - let error = call.err().unwrap(); - assert_eq!(error.root_cause().to_string(), "No 'ink' dependency found"); - + assert!( + matches!(set_up_call(call_opts).await, Err(Error::AnyhowError(message)) if message.root_cause().to_string() == "No 'ink' dependency found") + ); Ok(()) } diff --git a/crates/pop-contracts/src/errors.rs b/crates/pop-contracts/src/errors.rs index 066e35e16..a22a0dcb7 100644 --- a/crates/pop-contracts/src/errors.rs +++ b/crates/pop-contracts/src/errors.rs @@ -38,6 +38,8 @@ pub enum Error { KeyPairCreation(String), #[error("Failed to get manifest path: {0}")] ManifestPath(String), + #[error("Argument {0} is required")] + MissingArgument(String), #[error("Failed to create new contract project: {0}")] NewContract(String), #[error("ParseError error: {0}")] @@ -46,12 +48,14 @@ pub enum Error { ParseSecretURI(String), #[error("The `Repository` property is missing from the template variant")] RepositoryMissing, + #[error("Sourcing error {0}")] + SourcingError(SourcingError), #[error("Failed to execute test command: {0}")] TestCommand(String), #[error("Unsupported platform: {os}")] UnsupportedPlatform { os: &'static str }, #[error("{0}")] UploadContractError(String), - #[error("Sourcing error {0}")] - SourcingError(SourcingError), + #[error("Wrong number of arguments provided. Expecting {expected}, {provided} provided")] + WrongNumberArguments { expected: usize, provided: usize }, } diff --git a/crates/pop-contracts/src/lib.rs b/crates/pop-contracts/src/lib.rs index 521833be1..a3ff0175c 100644 --- a/crates/pop-contracts/src/lib.rs +++ b/crates/pop-contracts/src/lib.rs @@ -15,7 +15,7 @@ mod utils; pub use build::{build_smart_contract, is_supported, Verbosity}; pub use call::{ call_smart_contract, dry_run_call, dry_run_gas_estimate_call, - metadata::{get_message, get_messages, Message}, + metadata::{get_messages, Message}, set_up_call, CallOpts, }; pub use new::{create_smart_contract, is_valid_contract_name}; diff --git a/crates/pop-contracts/tests/files/testing.contract b/crates/pop-contracts/tests/files/testing.contract index 5fb54f3ce..73ff479a5 100644 --- a/crates/pop-contracts/tests/files/testing.contract +++ b/crates/pop-contracts/tests/files/testing.contract @@ -1 +1 @@ -{"source":{"hash":"0x80776e58b218850d7d86447b2edea78d827ed0ed2499ff3a92b7ea10e4f95eb5","language":"ink! 5.0.0","compiler":"rustc 1.78.0","wasm":"0x0061736d01000000012b0860027f7f0060037f7f7f017f60000060047f7f7f7f017f60037f7f7f0060017f006000017f60017f017f027406057365616c310b6765745f73746f726167650003057365616c3005696e7075740000057365616c320b7365745f73746f726167650003057365616c300b7365616c5f72657475726e0004057365616c301176616c75655f7472616e73666572726564000003656e76066d656d6f72790201021003100f0101010105040006070002050002020616037f01418080040b7f00418080050b7f00418080050b0711020463616c6c0012066465706c6f7900130aa80c0f2b01017f037f2002200346047f200005200020036a200120036a2d00003a0000200341016a21030c010b0b0b6f01017f0240200020014d04402000210303402002450d02200320012d00003a0000200341016a2103200141016a2101200241016b21020c000b000b200041016b2103200141016b210103402002450d01200220036a200120026a2d00003a0000200241016b21020c000b000b20000b2501017f037f2002200346047f200005200020036a20013a0000200341016a21030c010b0b0b3f01027f0340200245044041000f0b200241016b210220012d0000210320002d00002104200141016a2101200041016a210020032004460d000b200420036b0b2601017f230041106b220124002001410036020c20002001410c6a4104100a200141106a24000b4801027f024002402000280208220320026a22042003490d00200420002802044b0d00200420036b2002470d01200028020020036a2001200210051a200020043602080f0b000b000b2601017f230041106b22022400200220003a000f20012002410f6a4101100a200241106a24000b6102027f027e230041206b22002400200041106a22014200370300200042003703082000411036021c200041086a2000411c6a1004200028021c41114f0440000b2001290300210220002903082103200041206a2400410541042002200384501b0b3f01017f2000280204220145044041020f0b2000200141016b36020420002000280200220041016a3602004101410220002d000022004101461b410020001b0b3c01027f027f200145044041808004210141010c010b410121024180800441013a000041818004210141020b2103200120023a0000200020031011000b12004180800441003b0100410041021011000b8a0101057f230041106b22012400200142808001370208200141808004360204200141046a22041009024020012802082205200128020c2202490d00200128020421032001410036020c2001200520026b3602082001200220036a36020420002004100b200128020c220020012802084b0d00200320022001280204200010021a200141106a24000f0b000b0d0020004180800420011003000b990401067f230041106b2200240020004180800136020441808004200041046a10010240024020002802042202418180014f0d000240024020024104490d002000418480043602042000200241046b360208418380042d00002101418280042d00002104418180042d00002103418080042d00002202412f470440200241ec00470440200241e300470d02410221022003413a46200441a5014671200141d10046710d030c020b2003410f472004411d4772200141f70147720d01200041046a100d220241ff01714102460d010c020b41032102200341860146200441db004671200141d90146710d010b41014101100e000b200042808001370208200041808004360204200041046a2204100920002802082205200028020c2201490d00200028020421032000200520016b220536020420032001200120036a2201200410002000280204220320054b720d0020002003360208200020013602042004100d220141ff01714102460d0020002802080d000240024002404102200241026b41ff01712200200041024f1b41016b0e020100020b2002410171101041004100100e000b100c41ff01714105470d01230041106b220024002000418080043602044180800441003a00002000428080818010370208200141ff0171410047200041046a100b200028020c2200418180014f0440000b410020001011000b100c41ff01714105460d010b000b200141ff017145101041004100100e000be50101057f230041106b2200240002400240100c41ff01714105470d0020004180800136020c418080042000410c6a1001200028020c2201418180014f0d0020014104490d012000418480043602042000200141046b360208418380042d00002101418280042d00002102418180042d000021030240418080042d0000220441ed014704402004419b0147200341ae0147722002419d0147200141de004772720d03200041046a100d220041ff01714102470d010c030b200341cb00462002419d0146712001411b4671450d0241001010100f000b20001010100f000b000b41014101100e000b","build_info":{"rust_toolchain":"stable-aarch64-apple-darwin","cargo_contract_version":"5.0.0-alpha","build_mode":"Release","wasm_opt_settings":{"optimization_passes":"Z","keep_debug_symbols":false}}},"contract":{"name":"testing","version":"0.1.0","authors":["[your_name] <[your_email]>"]},"image":null,"version":5,"types":[{"id":0,"type":{"def":{"primitive":"bool"}}},{"id":1,"type":{"path":["testing","testing","Testing"],"def":{"composite":{"fields":[{"name":"value","type":0,"typeName":",>>::Type"}]}}}},{"id":2,"type":{"path":["Result"],"params":[{"name":"T","type":3},{"name":"E","type":4}],"def":{"variant":{"variants":[{"name":"Ok","fields":[{"type":3}],"index":0},{"name":"Err","fields":[{"type":4}],"index":1}]}}}},{"id":3,"type":{"def":{"tuple":[]}}},{"id":4,"type":{"path":["ink_primitives","LangError"],"def":{"variant":{"variants":[{"name":"CouldNotReadInput","index":1}]}}}},{"id":5,"type":{"path":["Result"],"params":[{"name":"T","type":0},{"name":"E","type":4}],"def":{"variant":{"variants":[{"name":"Ok","fields":[{"type":0}],"index":0},{"name":"Err","fields":[{"type":4}],"index":1}]}}}},{"id":6,"type":{"path":["ink_primitives","types","AccountId"],"def":{"composite":{"fields":[{"type":7,"typeName":"[u8; 32]"}]}}}},{"id":7,"type":{"def":{"array":{"len":32,"type":8}}}},{"id":8,"type":{"def":{"primitive":"u8"}}},{"id":9,"type":{"def":{"primitive":"u128"}}},{"id":10,"type":{"path":["ink_primitives","types","Hash"],"def":{"composite":{"fields":[{"type":7,"typeName":"[u8; 32]"}]}}}},{"id":11,"type":{"def":{"primitive":"u64"}}},{"id":12,"type":{"def":{"primitive":"u32"}}},{"id":13,"type":{"path":["ink_env","types","NoChainExtension"],"def":{"variant":{}}}}],"storage":{"root":{"root_key":"0x00000000","layout":{"struct":{"name":"Testing","fields":[{"name":"value","layout":{"leaf":{"key":"0x00000000","ty":0}}}]}},"ty":1}},"spec":{"constructors":[{"label":"new","selector":"0x9bae9d5e","payable":false,"args":[{"label":"init_value","type":{"type":0,"displayName":["bool"]}}],"returnType":{"type":2,"displayName":["ink_primitives","ConstructorResult"]},"docs":["Constructor that initializes the `bool` value to the given `init_value`."],"default":false},{"label":"default","selector":"0xed4b9d1b","payable":false,"args":[],"returnType":{"type":2,"displayName":["ink_primitives","ConstructorResult"]},"docs":["Constructor that initializes the `bool` value to `false`.","","Constructors can delegate to other constructors."],"default":false}],"messages":[{"label":"flip","selector":"0x633aa551","mutates":true,"payable":false,"args":[],"returnType":{"type":2,"displayName":["ink","MessageResult"]},"docs":[" A message that can be called on instantiated contracts."," This one flips the value of the stored `bool` from `true`"," to `false` and vice versa."],"default":false},{"label":"get","selector":"0x2f865bd9","mutates":false,"payable":false,"args":[],"returnType":{"type":5,"displayName":["ink","MessageResult"]},"docs":[" Simply returns the current value of our `bool`."],"default":false},{"label":"specific_flip","selector":"0x6c0f1df7","mutates":true,"payable":true,"args":[{"label":"new_value","type":{"type":0,"displayName":["bool"]}}],"returnType":{"type":2,"displayName":["ink","MessageResult"]},"docs":[" A message for testing, flips the value of the stored `bool` with `new_value`"," and is payable"],"default":false}],"events":[],"docs":[],"lang_error":{"type":4,"displayName":["ink","LangError"]},"environment":{"accountId":{"type":6,"displayName":["AccountId"]},"balance":{"type":9,"displayName":["Balance"]},"hash":{"type":10,"displayName":["Hash"]},"timestamp":{"type":11,"displayName":["Timestamp"]},"blockNumber":{"type":12,"displayName":["BlockNumber"]},"chainExtension":{"type":13,"displayName":["ChainExtension"]},"maxEventTopics":4,"staticBufferSize":16384}}} \ No newline at end of file +{"source":{"hash":"0xd35166baa65bf10eb07c3d30c052d0fe752d6b98a864b61062e189be1a1ed550","language":"ink! 5.0.0","compiler":"rustc 1.78.0","wasm":"0x0061736d0100000001270760027f7f0060037f7f7f017f60000060047f7f7f7f017f60037f7f7f006000017f60017f017f027406057365616c310b6765745f73746f726167650003057365616c3005696e7075740000057365616c320b7365745f73746f726167650003057365616c300b7365616c5f72657475726e0004057365616c301176616c75655f7472616e73666572726564000003656e76066d656d6f727902010210031110010101010004000506020000000002020616037f01418080040b7f00418080050b7f00418080050b0711020463616c6c0013066465706c6f7900140aff0d102b01017f037f2002200346047f200005200020036a200120036a2d00003a0000200341016a21030c010b0b0b6f01017f0240200020014d04402000210303402002450d02200320012d00003a0000200341016a2103200141016a2101200241016b21020c000b000b200041016b2103200141016b210103402002450d01200220036a200120026a2d00003a0000200241016b21020c000b000b20000b2501017f037f2002200346047f200005200020036a20013a0000200341016a21030c010b0b0b3f01027f0340200245044041000f0b200241016b210220012d0000210320002d00002104200141016a2101200041016a210020032004460d000b200420036b0b2601017f230041106b220224002002200036020c20012002410c6a4104100a200241106a24000b4801027f024002402000280208220320026a22042003490d00200420002802044b0d00200420036b2002470d01200028020020036a2001200210051a200020043602080f0b000b000b2601017f230041106b22022400200220003a000f20012002410f6a4101100a200241106a24000b6102027f027e230041206b22002400200041106a22014200370300200042003703082000411036021c200041086a2000411c6a1004200028021c41114f0440000b2001290300210220002903082103200041206a2400410541042002200384501b0b7301037f230041106b22012400200141086a220320002802042202047f2000200241016b36020420002000280200220041016a36020020002d00000520000b3a000120032002453a000020012d0009210020012d00082102200141106a240041024101410220004101461b410020001b20021b0b12004180800441003b0100410041021011000b3c01027f027f200145044041808004210141010c010b410121024180800441013a000041818004210141020b2103200120023a0000200020031011000b920101057f230041106b220224002002428080013702082002418080043602044100200241046a22041009024020022802082206200228020c2203490d00200228020421052002410036020c2002200620036b3602082002200320056a36020420012004100b200020041009200228020c220020022802084b0d00200520032002280204200010021a200241106a24000f0b000b0d0020004180800420011003000b08002000200110100ba70501087f230041206b2200240020004180800136021441808004200041146a100102400240024020002802142202418180014f0d004104210541042106024020024104490d0020004184800436020c2000200241046b360210418380042d00002101418280042d00002104418180042d0000210302400240418080042d00002202412f470440200241ec00460d01200241e300472003413a4772200441a50147200141d1004772720d03410221010c020b200341860147200441db004772200141d90147720d02410321010c010b2003410f472004411d4772200141f70147720d012000410c6a100d220241ff01714102460d0120002802102204450d010240200028020c22032d000022010e020100020b20044105490d0120032800012106410121010b20002001360218200020023a0014410821050b200041146a220220056a2006360200200028021822044104460d01200028021c210520002d0014210620004280800137021820004180800436021441002002100920002802182207200028021c2201490d00200028021421032000200720016b220736021420032001200120036a2201200210002000280214220320074b720d0020002003360218200020013602142002100d220241ff01714102460d002000280218220141034d2001410447720d00200028021428000021000240024002404102200441026b2201200141024f1b41016b0e020100020b2005200020041b2006410047101241004100100f000b100c41ff01714105470d01230041106b220024002000418080043602044180800441003a00002000428080818010370208200241ff0171410047200041046a100b200028020c2200418180014f0440000b410020001011000b100c41ff01714105460d020b000b41014101100f000b2000200241ff017145101241004100100f000be90101057f230041106b2200240002400240100c41ff01714105470d0020004180800136020c418080042000410c6a1001200028020c2201418180014f0d0020014104490d012000418480043602042000200141046b360208418380042d00002101418280042d00002102418180042d000021030240418080042d0000220441ed014704402004419b0147200341ae0147722002419d0147200141de004772720d03200041046a100d220041ff01714102470d010c030b200341cb00462002419d0146712001411b4671450d02410041001010100e000b410020001010100e000b000b41014101100f000b","build_info":{"rust_toolchain":"stable-aarch64-apple-darwin","cargo_contract_version":"5.0.0-alpha","build_mode":"Release","wasm_opt_settings":{"optimization_passes":"Z","keep_debug_symbols":false}}},"contract":{"name":"testing","version":"0.1.0","authors":["[your_name] <[your_email]>"]},"image":null,"version":5,"types":[{"id":0,"type":{"def":{"primitive":"bool"}}},{"id":1,"type":{"def":{"primitive":"u32"}}},{"id":2,"type":{"path":["testing","testing","Testing"],"def":{"composite":{"fields":[{"name":"value","type":0,"typeName":",>>::Type"},{"name":"number","type":1,"typeName":",>>::Type"}]}}}},{"id":3,"type":{"path":["Result"],"params":[{"name":"T","type":4},{"name":"E","type":5}],"def":{"variant":{"variants":[{"name":"Ok","fields":[{"type":4}],"index":0},{"name":"Err","fields":[{"type":5}],"index":1}]}}}},{"id":4,"type":{"def":{"tuple":[]}}},{"id":5,"type":{"path":["ink_primitives","LangError"],"def":{"variant":{"variants":[{"name":"CouldNotReadInput","index":1}]}}}},{"id":6,"type":{"path":["Result"],"params":[{"name":"T","type":0},{"name":"E","type":5}],"def":{"variant":{"variants":[{"name":"Ok","fields":[{"type":0}],"index":0},{"name":"Err","fields":[{"type":5}],"index":1}]}}}},{"id":7,"type":{"path":["Option"],"params":[{"name":"T","type":1}],"def":{"variant":{"variants":[{"name":"None","index":0},{"name":"Some","fields":[{"type":1}],"index":1}]}}}},{"id":8,"type":{"path":["ink_primitives","types","AccountId"],"def":{"composite":{"fields":[{"type":9,"typeName":"[u8; 32]"}]}}}},{"id":9,"type":{"def":{"array":{"len":32,"type":10}}}},{"id":10,"type":{"def":{"primitive":"u8"}}},{"id":11,"type":{"def":{"primitive":"u128"}}},{"id":12,"type":{"path":["ink_primitives","types","Hash"],"def":{"composite":{"fields":[{"type":9,"typeName":"[u8; 32]"}]}}}},{"id":13,"type":{"def":{"primitive":"u64"}}},{"id":14,"type":{"path":["ink_env","types","NoChainExtension"],"def":{"variant":{}}}}],"storage":{"root":{"root_key":"0x00000000","layout":{"struct":{"name":"Testing","fields":[{"name":"value","layout":{"leaf":{"key":"0x00000000","ty":0}}},{"name":"number","layout":{"leaf":{"key":"0x00000000","ty":1}}}]}},"ty":2}},"spec":{"constructors":[{"label":"new","selector":"0x9bae9d5e","payable":false,"args":[{"label":"init_value","type":{"type":0,"displayName":["bool"]}}],"returnType":{"type":3,"displayName":["ink_primitives","ConstructorResult"]},"docs":["Constructor that initializes the `bool` value to the given `init_value`."],"default":false},{"label":"default","selector":"0xed4b9d1b","payable":false,"args":[],"returnType":{"type":3,"displayName":["ink_primitives","ConstructorResult"]},"docs":["Constructor that initializes the `bool` value to `false`.","","Constructors can delegate to other constructors."],"default":false}],"messages":[{"label":"flip","selector":"0x633aa551","mutates":true,"payable":false,"args":[],"returnType":{"type":3,"displayName":["ink","MessageResult"]},"docs":[" A message that can be called on instantiated contracts."," This one flips the value of the stored `bool` from `true`"," to `false` and vice versa."],"default":false},{"label":"get","selector":"0x2f865bd9","mutates":false,"payable":false,"args":[],"returnType":{"type":6,"displayName":["ink","MessageResult"]},"docs":[" Simply returns the current value of our `bool`."],"default":false},{"label":"specific_flip","selector":"0x6c0f1df7","mutates":true,"payable":true,"args":[{"label":"new_value","type":{"type":0,"displayName":["bool"]}},{"label":"number","type":{"type":7,"displayName":["Option"]}}],"returnType":{"type":3,"displayName":["ink","MessageResult"]},"docs":[" A message for testing, flips the value of the stored `bool` with `new_value`"," and is payable"],"default":false}],"events":[],"docs":[],"lang_error":{"type":5,"displayName":["ink","LangError"]},"environment":{"accountId":{"type":8,"displayName":["AccountId"]},"balance":{"type":11,"displayName":["Balance"]},"hash":{"type":12,"displayName":["Hash"]},"timestamp":{"type":13,"displayName":["Timestamp"]},"blockNumber":{"type":1,"displayName":["BlockNumber"]},"chainExtension":{"type":14,"displayName":["ChainExtension"]},"maxEventTopics":4,"staticBufferSize":16384}}} \ No newline at end of file diff --git a/crates/pop-contracts/tests/files/testing.json b/crates/pop-contracts/tests/files/testing.json index ed230c98f..8f28780d5 100644 --- a/crates/pop-contracts/tests/files/testing.json +++ b/crates/pop-contracts/tests/files/testing.json @@ -1,6 +1,6 @@ { "source": { - "hash": "0x80776e58b218850d7d86447b2edea78d827ed0ed2499ff3a92b7ea10e4f95eb5", + "hash": "0xd35166baa65bf10eb07c3d30c052d0fe752d6b98a864b61062e189be1a1ed550", "language": "ink! 5.0.0", "compiler": "rustc 1.78.0", "build_info": { @@ -33,6 +33,14 @@ }, { "id": 1, + "type": { + "def": { + "primitive": "u32" + } + } + }, + { + "id": 2, "type": { "path": [ "testing", @@ -46,6 +54,11 @@ "name": "value", "type": 0, "typeName": ",>>::Type" + }, + { + "name": "number", + "type": 1, + "typeName": ",>>::Type" } ] } @@ -53,7 +66,7 @@ } }, { - "id": 2, + "id": 3, "type": { "path": [ "Result" @@ -61,11 +74,11 @@ "params": [ { "name": "T", - "type": 3 + "type": 4 }, { "name": "E", - "type": 4 + "type": 5 } ], "def": { @@ -75,7 +88,7 @@ "name": "Ok", "fields": [ { - "type": 3 + "type": 4 } ], "index": 0 @@ -84,7 +97,7 @@ "name": "Err", "fields": [ { - "type": 4 + "type": 5 } ], "index": 1 @@ -95,7 +108,7 @@ } }, { - "id": 3, + "id": 4, "type": { "def": { "tuple": [] @@ -103,7 +116,7 @@ } }, { - "id": 4, + "id": 5, "type": { "path": [ "ink_primitives", @@ -122,7 +135,7 @@ } }, { - "id": 5, + "id": 6, "type": { "path": [ "Result" @@ -134,7 +147,7 @@ }, { "name": "E", - "type": 4 + "type": 5 } ], "def": { @@ -153,7 +166,7 @@ "name": "Err", "fields": [ { - "type": 4 + "type": 5 } ], "index": 1 @@ -164,7 +177,40 @@ } }, { - "id": 6, + "id": 7, + "type": { + "path": [ + "Option" + ], + "params": [ + { + "name": "T", + "type": 1 + } + ], + "def": { + "variant": { + "variants": [ + { + "name": "None", + "index": 0 + }, + { + "name": "Some", + "fields": [ + { + "type": 1 + } + ], + "index": 1 + } + ] + } + } + } + }, + { + "id": 8, "type": { "path": [ "ink_primitives", @@ -175,7 +221,7 @@ "composite": { "fields": [ { - "type": 7, + "type": 9, "typeName": "[u8; 32]" } ] @@ -184,18 +230,18 @@ } }, { - "id": 7, + "id": 9, "type": { "def": { "array": { "len": 32, - "type": 8 + "type": 10 } } } }, { - "id": 8, + "id": 10, "type": { "def": { "primitive": "u8" @@ -203,7 +249,7 @@ } }, { - "id": 9, + "id": 11, "type": { "def": { "primitive": "u128" @@ -211,7 +257,7 @@ } }, { - "id": 10, + "id": 12, "type": { "path": [ "ink_primitives", @@ -222,7 +268,7 @@ "composite": { "fields": [ { - "type": 7, + "type": 9, "typeName": "[u8; 32]" } ] @@ -231,7 +277,7 @@ } }, { - "id": 11, + "id": 13, "type": { "def": { "primitive": "u64" @@ -239,15 +285,7 @@ } }, { - "id": 12, - "type": { - "def": { - "primitive": "u32" - } - } - }, - { - "id": 13, + "id": 14, "type": { "path": [ "ink_env", @@ -275,11 +313,20 @@ "ty": 0 } } + }, + { + "name": "number", + "layout": { + "leaf": { + "key": "0x00000000", + "ty": 1 + } + } } ] } }, - "ty": 1 + "ty": 2 } }, "spec": { @@ -300,7 +347,7 @@ } ], "returnType": { - "type": 2, + "type": 3, "displayName": [ "ink_primitives", "ConstructorResult" @@ -317,7 +364,7 @@ "payable": false, "args": [], "returnType": { - "type": 2, + "type": 3, "displayName": [ "ink_primitives", "ConstructorResult" @@ -339,7 +386,7 @@ "payable": false, "args": [], "returnType": { - "type": 2, + "type": 3, "displayName": [ "ink", "MessageResult" @@ -359,7 +406,7 @@ "payable": false, "args": [], "returnType": { - "type": 5, + "type": 6, "displayName": [ "ink", "MessageResult" @@ -384,10 +431,19 @@ "bool" ] } + }, + { + "label": "number", + "type": { + "type": 7, + "displayName": [ + "Option" + ] + } } ], "returnType": { - "type": 2, + "type": 3, "displayName": [ "ink", "MessageResult" @@ -403,7 +459,7 @@ "events": [], "docs": [], "lang_error": { - "type": 4, + "type": 5, "displayName": [ "ink", "LangError" @@ -411,37 +467,37 @@ }, "environment": { "accountId": { - "type": 6, + "type": 8, "displayName": [ "AccountId" ] }, "balance": { - "type": 9, + "type": 11, "displayName": [ "Balance" ] }, "hash": { - "type": 10, + "type": 12, "displayName": [ "Hash" ] }, "timestamp": { - "type": 11, + "type": 13, "displayName": [ "Timestamp" ] }, "blockNumber": { - "type": 12, + "type": 1, "displayName": [ "BlockNumber" ] }, "chainExtension": { - "type": 13, + "type": 14, "displayName": [ "ChainExtension" ] From 740ae9ae7f07cbcc64f547d9548a2f1b95e1ab67 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 5 Nov 2024 09:04:57 +0100 Subject: [PATCH 110/211] refactor: improve code and comments --- crates/pop-contracts/src/call/metadata.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/crates/pop-contracts/src/call/metadata.rs b/crates/pop-contracts/src/call/metadata.rs index 6c4b58db3..76737a0af 100644 --- a/crates/pop-contracts/src/call/metadata.rs +++ b/crates/pop-contracts/src/call/metadata.rs @@ -94,7 +94,7 @@ pub fn generate_message_args( args: Vec, ) -> Result, Error> { let contract_path = path.unwrap_or_else(|| Path::new("./")); - let message = get_message(&contract_path, message_label)?; + let message = get_message(contract_path, message_label)?; if args.len() != message.args.len() { return Err(Error::WrongNumberArguments { expected: message.args.len(), @@ -104,14 +104,12 @@ pub fn generate_message_args( Ok(args .into_iter() .zip(&message.args) - .map(|(arg, param)| { - if param.type_name == "Option" && arg.is_empty() { - "None".to_string() - } else if param.type_name == "Option" { - format!("Some({})", arg) - } else { - arg - } + .map(|(arg, param)| match (param.type_name.as_str(), arg.is_empty()) { + ("Option", true) => "None".to_string(), /* If the argument is Option and empty, + * replace it with `None` */ + ("Option", false) => format!("Some({})", arg), /* If the argument is Option and not + * empty, wrap it in `Some(...)` */ + _ => arg, // If the argument is not Option, return it as is }) .collect::>()) } From 0ae993d08792dd4045606d16d98a4cda5cfdc8c5 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 5 Nov 2024 22:15:50 +0100 Subject: [PATCH 111/211] fix: renaming and clean code --- crates/pop-contracts/src/call/metadata.rs | 51 +++++++++++++---------- crates/pop-contracts/src/call/mod.rs | 6 +-- crates/pop-contracts/src/errors.rs | 4 +- 3 files changed, 33 insertions(+), 28 deletions(-) diff --git a/crates/pop-contracts/src/call/metadata.rs b/crates/pop-contracts/src/call/metadata.rs index 76737a0af..d6c3ccb5f 100644 --- a/crates/pop-contracts/src/call/metadata.rs +++ b/crates/pop-contracts/src/call/metadata.rs @@ -62,8 +62,11 @@ pub fn get_messages(path: &Path) -> Result, Error> { /// # Arguments /// * `path` - Location path of the project. /// * `message` - The label of the contract message. -fn get_message(path: &Path, message: &str) -> Result { - get_messages(path)? +fn get_message

(path: P, message: &str) -> Result +where + P: AsRef, +{ + get_messages(path.as_ref())? .into_iter() .find(|msg| msg.label == message) .ok_or_else(|| Error::InvalidMessageName(message.to_string())) @@ -81,22 +84,24 @@ fn process_args(message_params: &[MessageParamSpec]) -> Vec args } -/// Generates a list of processed argument values for a specified contract message, +/// Processes a list of argument values for a specified contract message, /// wrapping each value in `Some(...)` or replacing it with `None` if the argument is optional /// /// # Arguments /// * `path` - Location path of the project. /// * `message_label` - Label of the contract message to retrieve. /// * `args` - Argument values provided by the user. -pub fn generate_message_args( - path: Option<&Path>, - message_label: &str, +pub fn process_message_args

( + path: P, + message: &str, args: Vec, -) -> Result, Error> { - let contract_path = path.unwrap_or_else(|| Path::new("./")); - let message = get_message(contract_path, message_label)?; +) -> Result, Error> +where + P: AsRef, +{ + let message = get_message(path, message)?; if args.len() != message.args.len() { - return Err(Error::WrongNumberArguments { + return Err(Error::IncorrectArguments { expected: message.args.len(), provided: args.len(), }); @@ -105,10 +110,10 @@ pub fn generate_message_args( .into_iter() .zip(&message.args) .map(|(arg, param)| match (param.type_name.as_str(), arg.is_empty()) { - ("Option", true) => "None".to_string(), /* If the argument is Option and empty, - * replace it with `None` */ - ("Option", false) => format!("Some({})", arg), /* If the argument is Option and not - * empty, wrap it in `Some(...)` */ + ("Option", true) => "None".to_string(), /* If the argument is Option and empty, */ + // replace it with `None` + ("Option", false) => format!("Some({})", arg), /* If the argument is Option and not */ + // empty, wrap it in `Some(...)` _ => arg, // If the argument is not Option, return it as is }) .collect::>()) @@ -173,7 +178,7 @@ mod tests { } #[test] - fn generate_message_args_work() -> Result<()> { + fn process_message_args_work() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; let current_dir = env::current_dir().expect("Failed to get current directory"); mock_build_process( @@ -182,27 +187,27 @@ mod tests { current_dir.join("./tests/files/testing.json"), )?; assert!(matches!( - generate_message_args(Some(&temp_dir.path().join("testing")),"wrong_flip", Vec::new()), + process_message_args(temp_dir.path().join("testing"),"wrong_flip", Vec::new()), Err(Error::InvalidMessageName(error)) if error == "wrong_flip".to_string())); assert!(matches!( - generate_message_args( - Some(&temp_dir.path().join("testing")), + process_message_args( + temp_dir.path().join("testing"), "specific_flip", Vec::new() ), - Err(Error::WrongNumberArguments {expected, provided }) if expected == 2 && provided == 0 + Err(Error::IncorrectArguments {expected, provided }) if expected == 2 && provided == 0 )); assert_eq!( - generate_message_args( - Some(&temp_dir.path().join("testing")), + process_message_args( + temp_dir.path().join("testing"), "specific_flip", ["true".to_string(), "2".to_string()].to_vec() )?, ["true".to_string(), "Some(2)".to_string()] ); assert_eq!( - generate_message_args( - Some(&temp_dir.path().join("testing")), + process_message_args( + temp_dir.path().join("testing"), "specific_flip", ["true".to_string(), "".to_string()].to_vec() )?, diff --git a/crates/pop-contracts/src/call/mod.rs b/crates/pop-contracts/src/call/mod.rs index 6af84c03b..a6fdf06b4 100644 --- a/crates/pop-contracts/src/call/mod.rs +++ b/crates/pop-contracts/src/call/mod.rs @@ -67,9 +67,9 @@ pub async fn set_up_call( parse_balance(&call_opts.value)?; let contract: ::AccountId = parse_account(&call_opts.contract)?; - // Parse the argument values input by the user. - let args = metadata::generate_message_args( - call_opts.path.as_deref(), + // Process the argument values input by the user. + let args = metadata::process_message_args( + call_opts.path.unwrap_or_else(|| PathBuf::from("./")), &call_opts.message, call_opts.args, )?; diff --git a/crates/pop-contracts/src/errors.rs b/crates/pop-contracts/src/errors.rs index a22a0dcb7..f0e019f1c 100644 --- a/crates/pop-contracts/src/errors.rs +++ b/crates/pop-contracts/src/errors.rs @@ -56,6 +56,6 @@ pub enum Error { UnsupportedPlatform { os: &'static str }, #[error("{0}")] UploadContractError(String), - #[error("Wrong number of arguments provided. Expecting {expected}, {provided} provided")] - WrongNumberArguments { expected: usize, provided: usize }, + #[error("Incorrect number of arguments provided. Expecting {expected}, {provided} provided")] + IncorrectArguments { expected: usize, provided: usize }, } From 907458b055f57b741bc307b5f5c0536f73c3cf00 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Wed, 6 Nov 2024 09:57:13 +0100 Subject: [PATCH 112/211] chore: option params not mandatory --- crates/pop-cli/src/commands/call/contract.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 3b22d014c..204aee356 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -230,11 +230,15 @@ impl CallContractCommand { // Resolve message arguments. let mut contract_args = Vec::new(); for arg in &message.args { - contract_args.push( - cli.input(format!("Enter the value for the parameter: {}", arg.label)) - .placeholder(&format!("Type required: {}", &arg.type_name)) - .interact()?, - ); + let mut input = cli + .input(format!("Enter the value for the parameter: {}", arg.label)) + .placeholder(&format!("Type required: {}", arg.type_name)); + + // Set default input only if the parameter type is `Option` (Not mandatory) + if arg.type_name == "Option" { + input = input.default_input(""); + } + contract_args.push(input.interact()?); } self.args = contract_args; From 9f69ade55d16647db0984f03d5c5279e0c57150e Mon Sep 17 00:00:00 2001 From: Alex Bean Date: Wed, 6 Nov 2024 09:59:43 +0100 Subject: [PATCH 113/211] fix: parse user inputs for Option arguments in constructor (#335) * fix: automatically add some or none to Option argument * fix: tests --- crates/pop-cli/src/commands/call/contract.rs | 8 +- crates/pop-cli/src/commands/up/contract.rs | 2 +- .../src/{call/mod.rs => call.rs} | 3 +- crates/pop-contracts/src/errors.rs | 2 + crates/pop-contracts/src/lib.rs | 10 +- crates/pop-contracts/src/up.rs | 9 +- .../src/{call => utils}/metadata.rs | 193 +++++++++++++++++- crates/pop-contracts/src/utils/mod.rs | 1 + .../tests/files/testing.contract | 2 +- crates/pop-contracts/tests/files/testing.json | 109 +++++----- 10 files changed, 274 insertions(+), 65 deletions(-) rename crates/pop-contracts/src/{call/mod.rs => call.rs} (99%) rename crates/pop-contracts/src/{call => utils}/metadata.rs (52%) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 204aee356..d0c3e8b13 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -760,6 +760,7 @@ mod tests { let mut cli = MockCli::new() .expect_input("Signer calling the contract:", "//Alice".into()) .expect_input("Value to transfer to the call:", "50".into()) // Only if payable + .expect_input("Enter the value for the parameter: number", "2".into()) // Args for specific_flip .expect_input("Enter the value for the parameter: new_value", "true".into()) // Args for specific_flip .expect_select::( "Select the message to call:", @@ -780,7 +781,7 @@ mod tests { "Where is your project located?", temp_dir.path().join("testing").display().to_string(), ).expect_info(format!( - "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true,2 --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", temp_dir.path().join("testing").display().to_string(), )); @@ -804,8 +805,9 @@ mod tests { Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) ); assert_eq!(call_config.message, Some("specific_flip".to_string())); - assert_eq!(call_config.args.len(), 1); + assert_eq!(call_config.args.len(), 2); assert_eq!(call_config.args[0], "true".to_string()); + assert_eq!(call_config.args[1], "2".to_string()); assert_eq!(call_config.value, "50".to_string()); assert_eq!(call_config.gas_limit, None); assert_eq!(call_config.proof_size, None); @@ -815,7 +817,7 @@ mod tests { assert!(!call_config.dry_run); assert!(call_config.dev_mode); assert_eq!(call_config.display(), format!( - "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true,2 --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", temp_dir.path().join("testing").display().to_string(), )); diff --git a/crates/pop-cli/src/commands/up/contract.rs b/crates/pop-cli/src/commands/up/contract.rs index 2bdef1ae1..2ec2d9a4b 100644 --- a/crates/pop-cli/src/commands/up/contract.rs +++ b/crates/pop-cli/src/commands/up/contract.rs @@ -35,7 +35,7 @@ pub struct UpContractCommand { #[clap(name = "constructor", long, default_value = "new")] constructor: String, /// The constructor arguments, encoded as strings. - #[clap(long, num_args = 0..)] + #[clap(long, num_args = 0.., value_delimiter = ',')] args: Vec, /// Transfers an initial balance to the instantiated contract. #[clap(name = "value", long, default_value = "0")] diff --git a/crates/pop-contracts/src/call/mod.rs b/crates/pop-contracts/src/call.rs similarity index 99% rename from crates/pop-contracts/src/call/mod.rs rename to crates/pop-contracts/src/call.rs index a6fdf06b4..76f7fae58 100644 --- a/crates/pop-contracts/src/call/mod.rs +++ b/crates/pop-contracts/src/call.rs @@ -4,6 +4,7 @@ use crate::{ errors::Error, utils::{ helpers::{get_manifest_path, parse_account, parse_balance}, + metadata, signer::create_signer, }, }; @@ -20,8 +21,6 @@ use subxt::{Config, PolkadotConfig as DefaultConfig}; use subxt_signer::sr25519::Keypair; use url::Url; -pub mod metadata; - /// Attributes for the `call` command. pub struct CallOpts { /// Path to the contract build directory. diff --git a/crates/pop-contracts/src/errors.rs b/crates/pop-contracts/src/errors.rs index f0e019f1c..3d19d679b 100644 --- a/crates/pop-contracts/src/errors.rs +++ b/crates/pop-contracts/src/errors.rs @@ -28,6 +28,8 @@ pub enum Error { InstallContractsNode(String), #[error("{0}")] InstantiateContractError(String), + #[error("Invalid constructor name: {0}")] + InvalidConstructorName(String), #[error("Invalid message name: {0}")] InvalidMessageName(String), #[error("Invalid name: {0}")] diff --git a/crates/pop-contracts/src/lib.rs b/crates/pop-contracts/src/lib.rs index a3ff0175c..2fbb4a639 100644 --- a/crates/pop-contracts/src/lib.rs +++ b/crates/pop-contracts/src/lib.rs @@ -14,9 +14,7 @@ mod utils; pub use build::{build_smart_contract, is_supported, Verbosity}; pub use call::{ - call_smart_contract, dry_run_call, dry_run_gas_estimate_call, - metadata::{get_messages, Message}, - set_up_call, CallOpts, + call_smart_contract, dry_run_call, dry_run_gas_estimate_call, set_up_call, CallOpts, }; pub use new::{create_smart_contract, is_valid_contract_name}; pub use node::{contracts_node_generator, is_chain_alive, run_contracts_node}; @@ -27,4 +25,8 @@ pub use up::{ dry_run_gas_estimate_instantiate, dry_run_upload, instantiate_smart_contract, set_up_deployment, set_up_upload, upload_smart_contract, UpOpts, }; -pub use utils::{helpers::parse_account, signer::parse_hex_bytes}; +pub use utils::{ + helpers::parse_account, + metadata::{get_messages, Message}, + signer::parse_hex_bytes, +}; diff --git a/crates/pop-contracts/src/up.rs b/crates/pop-contracts/src/up.rs index 0ee6a2d0b..b32f4c3e6 100644 --- a/crates/pop-contracts/src/up.rs +++ b/crates/pop-contracts/src/up.rs @@ -3,6 +3,7 @@ use crate::{ errors::Error, utils::{ helpers::{get_manifest_path, parse_balance}, + metadata, signer::create_signer, }, }; @@ -62,10 +63,16 @@ pub async fn set_up_deployment( let value: BalanceVariant<::Balance> = parse_balance(&up_opts.value)?; + // Process the argument values input by the user. + let args = metadata::process_constructor_args( + up_opts.path.unwrap_or_else(|| PathBuf::from("./")), + &up_opts.constructor, + up_opts.args, + )?; let instantiate_exec: InstantiateExec = InstantiateCommandBuilder::new(extrinsic_opts) .constructor(up_opts.constructor.clone()) - .args(up_opts.args.clone()) + .args(args) .value(value.denominate_balance(&token_metadata)?) .gas_limit(up_opts.gas_limit) .proof_size(up_opts.proof_size) diff --git a/crates/pop-contracts/src/call/metadata.rs b/crates/pop-contracts/src/utils/metadata.rs similarity index 52% rename from crates/pop-contracts/src/call/metadata.rs rename to crates/pop-contracts/src/utils/metadata.rs index d6c3ccb5f..89f887329 100644 --- a/crates/pop-contracts/src/call/metadata.rs +++ b/crates/pop-contracts/src/utils/metadata.rs @@ -14,6 +14,22 @@ pub struct Param { /// The type name of the parameter. pub type_name: String, } + +#[derive(Clone, PartialEq, Eq)] +/// Describes a contract message. +pub struct Constructor { + /// The label of the constructor. + pub label: String, + /// If the message accepts any `value` from the caller. + pub payable: bool, + /// The parameters of the deployment handler. + pub args: Vec, + /// The constructor documentation. + pub docs: String, + /// If the constructor is the default for off-chain consumers (e.g UIs). + pub default: bool, +} + #[derive(Clone, PartialEq, Eq)] /// Describes a contract message. pub struct Message { @@ -72,10 +88,50 @@ where .ok_or_else(|| Error::InvalidMessageName(message.to_string())) } -// Parse the message parameters into a vector of argument labels. -fn process_args(message_params: &[MessageParamSpec]) -> Vec { +/// Extracts a list of smart contract contructors parsing the metadata file. +/// +/// # Arguments +/// * `path` - Location path of the project. +pub fn get_constructors(path: &Path) -> Result, Error> { + let cargo_toml_path = match path.ends_with("Cargo.toml") { + true => path.to_path_buf(), + false => path.join("Cargo.toml"), + }; + let contract_artifacts = + ContractArtifacts::from_manifest_or_file(Some(&cargo_toml_path), None)?; + let transcoder = contract_artifacts.contract_transcoder()?; + let mut constructors: Vec = Vec::new(); + for constructor in transcoder.metadata().spec().constructors() { + constructors.push(Constructor { + label: constructor.label().to_string(), + payable: *constructor.payable(), + args: process_args(constructor.args()), + docs: constructor.docs().join(" "), + default: *constructor.default(), + }); + } + Ok(constructors) +} + +/// Extracts the information of a smart contract constructor parsing the metadata file. +/// +/// # Arguments +/// * `path` - Location path of the project. +/// * `constructor` - The label of the constructor. +fn get_constructor

(path: P, constructor: &str) -> Result +where + P: AsRef, +{ + get_constructors(path.as_ref())? + .into_iter() + .find(|c: &Constructor| c.label == constructor) + .ok_or_else(|| Error::InvalidConstructorName(constructor.to_string())) +} + +// Parse the parameters into a vector of argument labels. +fn process_args(params: &[MessageParamSpec]) -> Vec { let mut args: Vec = Vec::new(); - for arg in message_params { + for arg in params { args.push(Param { label: arg.label().to_string(), type_name: arg.ty().display_name().to_string(), @@ -119,6 +175,41 @@ where .collect::>()) } +/// Processes a list of argument values for a specified contract constructor, +/// wrapping each value in `Some(...)` or replacing it with `None` if the argument is optional +/// +/// # Arguments +/// * `path` - Location path of the project. +/// * `constructor` - Label of the contract constructor to retrieve. +/// * `args` - Argument values provided by the user. +pub fn process_constructor_args

( + path: P, + constructor: &str, + args: Vec, +) -> Result, Error> +where + P: AsRef, +{ + let constructor = get_constructor(path, constructor)?; + if args.len() != constructor.args.len() { + return Err(Error::IncorrectArguments { + expected: constructor.args.len(), + provided: args.len(), + }); + } + Ok(args + .into_iter() + .zip(&constructor.args) + .map(|(arg, param)| match (param.type_name.as_str(), arg.is_empty()) { + ("Option", true) => "None".to_string(), /* If the argument is Option and empty, */ + // replace it with `None` + ("Option", false) => format!("Some({})", arg), /* If the argument is Option and not */ + // empty, wrap it in `Some(...)` + _ => arg, // If the argument is not Option, return it as is + }) + .collect::>()) +} + #[cfg(test)] mod tests { use std::env; @@ -177,6 +268,63 @@ mod tests { Ok(()) } + #[test] + fn get_constructors_work() -> Result<()> { + let temp_dir = new_environment("testing")?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; + let constructor = get_constructors(&temp_dir.path().join("testing"))?; + assert_eq!(constructor.len(), 2); + assert_eq!(constructor[0].label, "new"); + assert_eq!( + constructor[0].docs, + "Constructor that initializes the `bool` value to the given `init_value`." + ); + assert_eq!(constructor[1].label, "default"); + assert_eq!( + constructor[1].docs, + "Constructor that initializes the `bool` value to `false`. Constructors can delegate to other constructors." + ); + // assert parsed arguments + assert_eq!(constructor[0].args.len(), 2); + assert_eq!(constructor[0].args[0].label, "init_value".to_string()); + assert_eq!(constructor[0].args[0].type_name, "bool".to_string()); + assert_eq!(constructor[0].args[1].label, "number".to_string()); + assert_eq!(constructor[0].args[1].type_name, "Option".to_string()); + Ok(()) + } + + #[test] + fn get_constructor_work() -> Result<()> { + let temp_dir = new_environment("testing")?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; + assert!(matches!( + get_constructor(&temp_dir.path().join("testing"), "wrong_constructor"), + Err(Error::InvalidConstructorName(name)) if name == "wrong_constructor".to_string())); + let constructor = get_constructor(&temp_dir.path().join("testing"), "new")?; + assert_eq!(constructor.label, "new"); + assert_eq!( + constructor.docs, + "Constructor that initializes the `bool` value to the given `init_value`." + ); + // assert parsed arguments + assert_eq!(constructor.args.len(), 2); + assert_eq!(constructor.args[0].label, "init_value".to_string()); + assert_eq!(constructor.args[0].type_name, "bool".to_string()); + assert_eq!(constructor.args[1].label, "number".to_string()); + assert_eq!(constructor.args[1].type_name, "Option".to_string()); + Ok(()) + } + #[test] fn process_message_args_work() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; @@ -215,4 +363,43 @@ mod tests { ); Ok(()) } + + #[test] + fn process_constructor_args_work() -> Result<()> { + let temp_dir = new_environment("testing")?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; + assert!(matches!( + process_constructor_args(temp_dir.path().join("testing"),"wrong_constructor", Vec::new()), + Err(Error::InvalidConstructorName(error)) if error == "wrong_constructor".to_string())); + assert!(matches!( + process_constructor_args( + temp_dir.path().join("testing"), + "new", + Vec::new() + ), + Err(Error::IncorrectArguments {expected, provided }) if expected == 2 && provided == 0 + )); + assert_eq!( + process_constructor_args( + temp_dir.path().join("testing"), + "new", + ["true".to_string(), "2".to_string()].to_vec() + )?, + ["true".to_string(), "Some(2)".to_string()] + ); + assert_eq!( + process_constructor_args( + temp_dir.path().join("testing"), + "new", + ["true".to_string(), "".to_string()].to_vec() + )?, + ["true".to_string(), "None".to_string()] + ); + Ok(()) + } } diff --git a/crates/pop-contracts/src/utils/mod.rs b/crates/pop-contracts/src/utils/mod.rs index 357c66082..ad49e2dc6 100644 --- a/crates/pop-contracts/src/utils/mod.rs +++ b/crates/pop-contracts/src/utils/mod.rs @@ -1,4 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 pub mod helpers; +pub mod metadata; pub mod signer; diff --git a/crates/pop-contracts/tests/files/testing.contract b/crates/pop-contracts/tests/files/testing.contract index 73ff479a5..7918b7184 100644 --- a/crates/pop-contracts/tests/files/testing.contract +++ b/crates/pop-contracts/tests/files/testing.contract @@ -1 +1 @@ -{"source":{"hash":"0xd35166baa65bf10eb07c3d30c052d0fe752d6b98a864b61062e189be1a1ed550","language":"ink! 5.0.0","compiler":"rustc 1.78.0","wasm":"0x0061736d0100000001270760027f7f0060037f7f7f017f60000060047f7f7f7f017f60037f7f7f006000017f60017f017f027406057365616c310b6765745f73746f726167650003057365616c3005696e7075740000057365616c320b7365745f73746f726167650003057365616c300b7365616c5f72657475726e0004057365616c301176616c75655f7472616e73666572726564000003656e76066d656d6f727902010210031110010101010004000506020000000002020616037f01418080040b7f00418080050b7f00418080050b0711020463616c6c0013066465706c6f7900140aff0d102b01017f037f2002200346047f200005200020036a200120036a2d00003a0000200341016a21030c010b0b0b6f01017f0240200020014d04402000210303402002450d02200320012d00003a0000200341016a2103200141016a2101200241016b21020c000b000b200041016b2103200141016b210103402002450d01200220036a200120026a2d00003a0000200241016b21020c000b000b20000b2501017f037f2002200346047f200005200020036a20013a0000200341016a21030c010b0b0b3f01027f0340200245044041000f0b200241016b210220012d0000210320002d00002104200141016a2101200041016a210020032004460d000b200420036b0b2601017f230041106b220224002002200036020c20012002410c6a4104100a200241106a24000b4801027f024002402000280208220320026a22042003490d00200420002802044b0d00200420036b2002470d01200028020020036a2001200210051a200020043602080f0b000b000b2601017f230041106b22022400200220003a000f20012002410f6a4101100a200241106a24000b6102027f027e230041206b22002400200041106a22014200370300200042003703082000411036021c200041086a2000411c6a1004200028021c41114f0440000b2001290300210220002903082103200041206a2400410541042002200384501b0b7301037f230041106b22012400200141086a220320002802042202047f2000200241016b36020420002000280200220041016a36020020002d00000520000b3a000120032002453a000020012d0009210020012d00082102200141106a240041024101410220004101461b410020001b20021b0b12004180800441003b0100410041021011000b3c01027f027f200145044041808004210141010c010b410121024180800441013a000041818004210141020b2103200120023a0000200020031011000b920101057f230041106b220224002002428080013702082002418080043602044100200241046a22041009024020022802082206200228020c2203490d00200228020421052002410036020c2002200620036b3602082002200320056a36020420012004100b200020041009200228020c220020022802084b0d00200520032002280204200010021a200241106a24000f0b000b0d0020004180800420011003000b08002000200110100ba70501087f230041206b2200240020004180800136021441808004200041146a100102400240024020002802142202418180014f0d004104210541042106024020024104490d0020004184800436020c2000200241046b360210418380042d00002101418280042d00002104418180042d0000210302400240418080042d00002202412f470440200241ec00460d01200241e300472003413a4772200441a50147200141d1004772720d03410221010c020b200341860147200441db004772200141d90147720d02410321010c010b2003410f472004411d4772200141f70147720d012000410c6a100d220241ff01714102460d0120002802102204450d010240200028020c22032d000022010e020100020b20044105490d0120032800012106410121010b20002001360218200020023a0014410821050b200041146a220220056a2006360200200028021822044104460d01200028021c210520002d0014210620004280800137021820004180800436021441002002100920002802182207200028021c2201490d00200028021421032000200720016b220736021420032001200120036a2201200210002000280214220320074b720d0020002003360218200020013602142002100d220241ff01714102460d002000280218220141034d2001410447720d00200028021428000021000240024002404102200441026b2201200141024f1b41016b0e020100020b2005200020041b2006410047101241004100100f000b100c41ff01714105470d01230041106b220024002000418080043602044180800441003a00002000428080818010370208200241ff0171410047200041046a100b200028020c2200418180014f0440000b410020001011000b100c41ff01714105460d020b000b41014101100f000b2000200241ff017145101241004100100f000be90101057f230041106b2200240002400240100c41ff01714105470d0020004180800136020c418080042000410c6a1001200028020c2201418180014f0d0020014104490d012000418480043602042000200141046b360208418380042d00002101418280042d00002102418180042d000021030240418080042d0000220441ed014704402004419b0147200341ae0147722002419d0147200141de004772720d03200041046a100d220041ff01714102470d010c030b200341cb00462002419d0146712001411b4671450d02410041001010100e000b410020001010100e000b000b41014101100f000b","build_info":{"rust_toolchain":"stable-aarch64-apple-darwin","cargo_contract_version":"5.0.0-alpha","build_mode":"Release","wasm_opt_settings":{"optimization_passes":"Z","keep_debug_symbols":false}}},"contract":{"name":"testing","version":"0.1.0","authors":["[your_name] <[your_email]>"]},"image":null,"version":5,"types":[{"id":0,"type":{"def":{"primitive":"bool"}}},{"id":1,"type":{"def":{"primitive":"u32"}}},{"id":2,"type":{"path":["testing","testing","Testing"],"def":{"composite":{"fields":[{"name":"value","type":0,"typeName":",>>::Type"},{"name":"number","type":1,"typeName":",>>::Type"}]}}}},{"id":3,"type":{"path":["Result"],"params":[{"name":"T","type":4},{"name":"E","type":5}],"def":{"variant":{"variants":[{"name":"Ok","fields":[{"type":4}],"index":0},{"name":"Err","fields":[{"type":5}],"index":1}]}}}},{"id":4,"type":{"def":{"tuple":[]}}},{"id":5,"type":{"path":["ink_primitives","LangError"],"def":{"variant":{"variants":[{"name":"CouldNotReadInput","index":1}]}}}},{"id":6,"type":{"path":["Result"],"params":[{"name":"T","type":0},{"name":"E","type":5}],"def":{"variant":{"variants":[{"name":"Ok","fields":[{"type":0}],"index":0},{"name":"Err","fields":[{"type":5}],"index":1}]}}}},{"id":7,"type":{"path":["Option"],"params":[{"name":"T","type":1}],"def":{"variant":{"variants":[{"name":"None","index":0},{"name":"Some","fields":[{"type":1}],"index":1}]}}}},{"id":8,"type":{"path":["ink_primitives","types","AccountId"],"def":{"composite":{"fields":[{"type":9,"typeName":"[u8; 32]"}]}}}},{"id":9,"type":{"def":{"array":{"len":32,"type":10}}}},{"id":10,"type":{"def":{"primitive":"u8"}}},{"id":11,"type":{"def":{"primitive":"u128"}}},{"id":12,"type":{"path":["ink_primitives","types","Hash"],"def":{"composite":{"fields":[{"type":9,"typeName":"[u8; 32]"}]}}}},{"id":13,"type":{"def":{"primitive":"u64"}}},{"id":14,"type":{"path":["ink_env","types","NoChainExtension"],"def":{"variant":{}}}}],"storage":{"root":{"root_key":"0x00000000","layout":{"struct":{"name":"Testing","fields":[{"name":"value","layout":{"leaf":{"key":"0x00000000","ty":0}}},{"name":"number","layout":{"leaf":{"key":"0x00000000","ty":1}}}]}},"ty":2}},"spec":{"constructors":[{"label":"new","selector":"0x9bae9d5e","payable":false,"args":[{"label":"init_value","type":{"type":0,"displayName":["bool"]}}],"returnType":{"type":3,"displayName":["ink_primitives","ConstructorResult"]},"docs":["Constructor that initializes the `bool` value to the given `init_value`."],"default":false},{"label":"default","selector":"0xed4b9d1b","payable":false,"args":[],"returnType":{"type":3,"displayName":["ink_primitives","ConstructorResult"]},"docs":["Constructor that initializes the `bool` value to `false`.","","Constructors can delegate to other constructors."],"default":false}],"messages":[{"label":"flip","selector":"0x633aa551","mutates":true,"payable":false,"args":[],"returnType":{"type":3,"displayName":["ink","MessageResult"]},"docs":[" A message that can be called on instantiated contracts."," This one flips the value of the stored `bool` from `true`"," to `false` and vice versa."],"default":false},{"label":"get","selector":"0x2f865bd9","mutates":false,"payable":false,"args":[],"returnType":{"type":6,"displayName":["ink","MessageResult"]},"docs":[" Simply returns the current value of our `bool`."],"default":false},{"label":"specific_flip","selector":"0x6c0f1df7","mutates":true,"payable":true,"args":[{"label":"new_value","type":{"type":0,"displayName":["bool"]}},{"label":"number","type":{"type":7,"displayName":["Option"]}}],"returnType":{"type":3,"displayName":["ink","MessageResult"]},"docs":[" A message for testing, flips the value of the stored `bool` with `new_value`"," and is payable"],"default":false}],"events":[],"docs":[],"lang_error":{"type":5,"displayName":["ink","LangError"]},"environment":{"accountId":{"type":8,"displayName":["AccountId"]},"balance":{"type":11,"displayName":["Balance"]},"hash":{"type":12,"displayName":["Hash"]},"timestamp":{"type":13,"displayName":["Timestamp"]},"blockNumber":{"type":1,"displayName":["BlockNumber"]},"chainExtension":{"type":14,"displayName":["ChainExtension"]},"maxEventTopics":4,"staticBufferSize":16384}}} \ No newline at end of file +{"source":{"hash":"0xea3a2d4428c3717ab229f1822014375d42a0497a0b86eb8fbb405ec6733899e9","language":"ink! 5.0.0","compiler":"rustc 1.78.0","wasm":"0x0061736d0100000001270760027f7f0060037f7f7f017f60000060047f7f7f7f017f60037f7f7f0060017f017f6000017f027406057365616c310b6765745f73746f726167650003057365616c3005696e7075740000057365616c320b7365745f73746f726167650003057365616c300b7365616c5f72657475726e0004057365616c301176616c75655f7472616e73666572726564000003656e76066d656d6f7279020102100313120101010100050000040006020000000002020616037f01418080040b7f00418080050b7f00418080050b0711020463616c6c0015066465706c6f7900160ac910122b01017f037f2002200346047f200005200020036a200120036a2d00003a0000200341016a21030c010b0b0b6f01017f0240200020014d04402000210303402002450d02200320012d00003a0000200341016a2103200141016a2101200241016b21020c000b000b200041016b2103200141016b210103402002450d01200220036a200120026a2d00003a0000200241016b21020c000b000b20000b2501017f037f2002200346047f200005200020036a20013a0000200341016a21030c010b0b0b3f01027f0340200245044041000f0b200241016b210220012d0000210320002d00002104200141016a2101200041016a210020032004460d000b200420036b0bc00101057f230041106b22022400410221044104210502402001100a220641ff01714102460d00200241086a2001100b20022d00080d000240024020022d000922030e020100020b20012802042203410449047f4101052001200341046b36020420012001280200220141046a3602002001280000210341000b2101200220033602042002200136020020022802000d0141012103200228020421040b20002003360204200020063a0000410821050b200020056a2004360200200241106a24000b3f01027f230041106b22012400200141086a2000100b20012d0009210020012d00082102200141106a240041024101410220004101461b410020001b20021b0b3c01017f200020012802042202047f2001200241016b36020420012001280200220141016a36020020012d00000520010b3a000120002002453a00000b2601017f230041106b220224002002200036020c20012002410c6a4104100d200241106a24000b4801027f024002402000280208220320026a22042003490d00200420002802044b0d00200420036b2002470d01200028020020036a2001200210051a200020043602080f0b000b000b2601017f230041106b22022400200220003a000f20012002410f6a4101100d200241106a24000b6102027f027e230041206b22002400200041106a22014200370300200042003703082000411036021c200041086a2000411c6a1004200028021c41114f0440000b2001290300210220002903082103200041206a2400410541042002200384501b0b12004180800441003b0100410041021013000b3c01027f027f200145044041808004210141010c010b410121024180800441013a000041818004210141020b2103200120023a0000200020031013000b920101057f230041106b220224002002428080013702082002418080043602044100200241046a2204100c024020022802082206200228020c2203490d00200228020421052002410036020c2002200620036b3602082002200320056a36020420012004100e20002004100c200228020c220020022802084b0d00200520032002280204200010021a200241106a24000f0b000b0d0020004180800420011003000b08002000200110120bb60501087f230041206b2200240020004180800136021441808004200041146a100102400240024020002802142201418180014f0d0041042104027f410420014104490d001a20004184800436020c2000200141046b360210418380042d00002101418280042d00002102418180042d00002103024002400240418080042d00002205412f470440200541ec00460d014104200541e300470d041a41042003413a470d041a4104200241a501470d041a41042202200141d100470d041a410221010c020b41042003418601470d031a4104200241db00470d031a41042202200141d901470d031a410321010c010b41042003410f470d021a41042002411d470d021a4104200141f701470d021a200041146a2000410c6a1009200028021822014102460d01200028021c210420002d001421020b20002001360204200020023a000041080c010b41040b20006a2004360200200028020422024104460d012000280208210520002d000021072000428080013702182000418080043602144100200041146a2203100c20002802182206200028021c2201490d00200028021421042000200620016b220636021420042001200120046a2201200310002000280214220420064b720d0020002004360218200020013602142003100a220141ff01714102460d002000280218220341034d2003410447720d00200028021428000021000240024002404102200241026b2203200341024f1b41016b0e020100020b2005200020021b20074100471014410041001011000b100f41ff01714105470d01230041106b220024002000418080043602044180800441003a00002000428080818010370208200141ff0171410047200041046a100e200028020c2200418180014f0440000b410020001013000b100f41ff01714105460d020b000b410141011011000b2000200141ff0171451014410041001011000bd90201067f230041206b22002400024002400240100f41ff01714105470d0020004180800136021441808004200041146a100120002802142201418180014f0d004104210220014104490d0120004184800436020c2000200141046b360210418380042d00002103418280042d00002101418180042d0000210402400240418080042d0000220541ed014704402005419b0147200441ae0147722001419d0147720d04200341de00460d010c040b200441cb00472001419d0147720d03410321012003411b470d04410221030c010b200041146a2000410c6a1009200028021822034102460d02200028021c210120002d001421020b20002003360204200020023a0000410821020c020b000b410321010b200020026a2001360200024020002802042202410347044020024102470d014100410010121010000b410141011011000b2000280208410020021b20002d000041004710121010000b","build_info":{"rust_toolchain":"stable-aarch64-apple-darwin","cargo_contract_version":"5.0.0-alpha","build_mode":"Release","wasm_opt_settings":{"optimization_passes":"Z","keep_debug_symbols":false}}},"contract":{"name":"testing","version":"0.1.0","authors":["[your_name] <[your_email]>"]},"image":null,"version":5,"types":[{"id":0,"type":{"def":{"primitive":"bool"}}},{"id":1,"type":{"def":{"primitive":"u32"}}},{"id":2,"type":{"path":["testing","testing","Testing"],"def":{"composite":{"fields":[{"name":"value","type":0,"typeName":",>>::Type"},{"name":"number","type":1,"typeName":",>>::Type"}]}}}},{"id":3,"type":{"path":["Option"],"params":[{"name":"T","type":1}],"def":{"variant":{"variants":[{"name":"None","index":0},{"name":"Some","fields":[{"type":1}],"index":1}]}}}},{"id":4,"type":{"path":["Result"],"params":[{"name":"T","type":5},{"name":"E","type":6}],"def":{"variant":{"variants":[{"name":"Ok","fields":[{"type":5}],"index":0},{"name":"Err","fields":[{"type":6}],"index":1}]}}}},{"id":5,"type":{"def":{"tuple":[]}}},{"id":6,"type":{"path":["ink_primitives","LangError"],"def":{"variant":{"variants":[{"name":"CouldNotReadInput","index":1}]}}}},{"id":7,"type":{"path":["Result"],"params":[{"name":"T","type":0},{"name":"E","type":6}],"def":{"variant":{"variants":[{"name":"Ok","fields":[{"type":0}],"index":0},{"name":"Err","fields":[{"type":6}],"index":1}]}}}},{"id":8,"type":{"path":["ink_primitives","types","AccountId"],"def":{"composite":{"fields":[{"type":9,"typeName":"[u8; 32]"}]}}}},{"id":9,"type":{"def":{"array":{"len":32,"type":10}}}},{"id":10,"type":{"def":{"primitive":"u8"}}},{"id":11,"type":{"def":{"primitive":"u128"}}},{"id":12,"type":{"path":["ink_primitives","types","Hash"],"def":{"composite":{"fields":[{"type":9,"typeName":"[u8; 32]"}]}}}},{"id":13,"type":{"def":{"primitive":"u64"}}},{"id":14,"type":{"path":["ink_env","types","NoChainExtension"],"def":{"variant":{}}}}],"storage":{"root":{"root_key":"0x00000000","layout":{"struct":{"name":"Testing","fields":[{"name":"value","layout":{"leaf":{"key":"0x00000000","ty":0}}},{"name":"number","layout":{"leaf":{"key":"0x00000000","ty":1}}}]}},"ty":2}},"spec":{"constructors":[{"label":"new","selector":"0x9bae9d5e","payable":false,"args":[{"label":"init_value","type":{"type":0,"displayName":["bool"]}},{"label":"number","type":{"type":3,"displayName":["Option"]}}],"returnType":{"type":4,"displayName":["ink_primitives","ConstructorResult"]},"docs":["Constructor that initializes the `bool` value to the given `init_value`."],"default":false},{"label":"default","selector":"0xed4b9d1b","payable":false,"args":[],"returnType":{"type":4,"displayName":["ink_primitives","ConstructorResult"]},"docs":["Constructor that initializes the `bool` value to `false`.","","Constructors can delegate to other constructors."],"default":false}],"messages":[{"label":"flip","selector":"0x633aa551","mutates":true,"payable":false,"args":[],"returnType":{"type":4,"displayName":["ink","MessageResult"]},"docs":[" A message that can be called on instantiated contracts."," This one flips the value of the stored `bool` from `true`"," to `false` and vice versa."],"default":false},{"label":"get","selector":"0x2f865bd9","mutates":false,"payable":false,"args":[],"returnType":{"type":7,"displayName":["ink","MessageResult"]},"docs":[" Simply returns the current value of our `bool`."],"default":false},{"label":"specific_flip","selector":"0x6c0f1df7","mutates":true,"payable":true,"args":[{"label":"new_value","type":{"type":0,"displayName":["bool"]}},{"label":"number","type":{"type":3,"displayName":["Option"]}}],"returnType":{"type":4,"displayName":["ink","MessageResult"]},"docs":[" A message for testing, flips the value of the stored `bool` with `new_value`"," and is payable"],"default":false}],"events":[],"docs":[],"lang_error":{"type":6,"displayName":["ink","LangError"]},"environment":{"accountId":{"type":8,"displayName":["AccountId"]},"balance":{"type":11,"displayName":["Balance"]},"hash":{"type":12,"displayName":["Hash"]},"timestamp":{"type":13,"displayName":["Timestamp"]},"blockNumber":{"type":1,"displayName":["BlockNumber"]},"chainExtension":{"type":14,"displayName":["ChainExtension"]},"maxEventTopics":4,"staticBufferSize":16384}}} \ No newline at end of file diff --git a/crates/pop-contracts/tests/files/testing.json b/crates/pop-contracts/tests/files/testing.json index 8f28780d5..a5bad9291 100644 --- a/crates/pop-contracts/tests/files/testing.json +++ b/crates/pop-contracts/tests/files/testing.json @@ -1,6 +1,6 @@ { "source": { - "hash": "0xd35166baa65bf10eb07c3d30c052d0fe752d6b98a864b61062e189be1a1ed550", + "hash": "0xea3a2d4428c3717ab229f1822014375d42a0497a0b86eb8fbb405ec6733899e9", "language": "ink! 5.0.0", "compiler": "rustc 1.78.0", "build_info": { @@ -67,6 +67,39 @@ }, { "id": 3, + "type": { + "path": [ + "Option" + ], + "params": [ + { + "name": "T", + "type": 1 + } + ], + "def": { + "variant": { + "variants": [ + { + "name": "None", + "index": 0 + }, + { + "name": "Some", + "fields": [ + { + "type": 1 + } + ], + "index": 1 + } + ] + } + } + } + }, + { + "id": 4, "type": { "path": [ "Result" @@ -74,11 +107,11 @@ "params": [ { "name": "T", - "type": 4 + "type": 5 }, { "name": "E", - "type": 5 + "type": 6 } ], "def": { @@ -88,7 +121,7 @@ "name": "Ok", "fields": [ { - "type": 4 + "type": 5 } ], "index": 0 @@ -97,7 +130,7 @@ "name": "Err", "fields": [ { - "type": 5 + "type": 6 } ], "index": 1 @@ -108,7 +141,7 @@ } }, { - "id": 4, + "id": 5, "type": { "def": { "tuple": [] @@ -116,7 +149,7 @@ } }, { - "id": 5, + "id": 6, "type": { "path": [ "ink_primitives", @@ -135,7 +168,7 @@ } }, { - "id": 6, + "id": 7, "type": { "path": [ "Result" @@ -147,7 +180,7 @@ }, { "name": "E", - "type": 5 + "type": 6 } ], "def": { @@ -166,40 +199,7 @@ "name": "Err", "fields": [ { - "type": 5 - } - ], - "index": 1 - } - ] - } - } - } - }, - { - "id": 7, - "type": { - "path": [ - "Option" - ], - "params": [ - { - "name": "T", - "type": 1 - } - ], - "def": { - "variant": { - "variants": [ - { - "name": "None", - "index": 0 - }, - { - "name": "Some", - "fields": [ - { - "type": 1 + "type": 6 } ], "index": 1 @@ -344,10 +344,19 @@ "bool" ] } + }, + { + "label": "number", + "type": { + "type": 3, + "displayName": [ + "Option" + ] + } } ], "returnType": { - "type": 3, + "type": 4, "displayName": [ "ink_primitives", "ConstructorResult" @@ -364,7 +373,7 @@ "payable": false, "args": [], "returnType": { - "type": 3, + "type": 4, "displayName": [ "ink_primitives", "ConstructorResult" @@ -386,7 +395,7 @@ "payable": false, "args": [], "returnType": { - "type": 3, + "type": 4, "displayName": [ "ink", "MessageResult" @@ -406,7 +415,7 @@ "payable": false, "args": [], "returnType": { - "type": 6, + "type": 7, "displayName": [ "ink", "MessageResult" @@ -435,7 +444,7 @@ { "label": "number", "type": { - "type": 7, + "type": 3, "displayName": [ "Option" ] @@ -443,7 +452,7 @@ } ], "returnType": { - "type": 3, + "type": 4, "displayName": [ "ink", "MessageResult" @@ -459,7 +468,7 @@ "events": [], "docs": [], "lang_error": { - "type": 5, + "type": 6, "displayName": [ "ink", "LangError" From d3e76678f780f681b009f7b44bfba5da3fcc7abd Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Wed, 6 Nov 2024 12:50:28 +0100 Subject: [PATCH 114/211] refactor: process_function_args --- crates/pop-contracts/src/call.rs | 5 +- crates/pop-contracts/src/lib.rs | 2 +- crates/pop-contracts/src/up.rs | 5 +- crates/pop-contracts/src/utils/metadata.rs | 148 ++++++++------------- 4 files changed, 60 insertions(+), 100 deletions(-) diff --git a/crates/pop-contracts/src/call.rs b/crates/pop-contracts/src/call.rs index 76f7fae58..e45a0a9c8 100644 --- a/crates/pop-contracts/src/call.rs +++ b/crates/pop-contracts/src/call.rs @@ -4,7 +4,7 @@ use crate::{ errors::Error, utils::{ helpers::{get_manifest_path, parse_account, parse_balance}, - metadata, + metadata::{process_function_args, FunctionType}, signer::create_signer, }, }; @@ -67,10 +67,11 @@ pub async fn set_up_call( let contract: ::AccountId = parse_account(&call_opts.contract)?; // Process the argument values input by the user. - let args = metadata::process_message_args( + let args = process_function_args( call_opts.path.unwrap_or_else(|| PathBuf::from("./")), &call_opts.message, call_opts.args, + FunctionType::Message, )?; let call_exec: CallExec = diff --git a/crates/pop-contracts/src/lib.rs b/crates/pop-contracts/src/lib.rs index 2fbb4a639..142283998 100644 --- a/crates/pop-contracts/src/lib.rs +++ b/crates/pop-contracts/src/lib.rs @@ -27,6 +27,6 @@ pub use up::{ }; pub use utils::{ helpers::parse_account, - metadata::{get_messages, Message}, + metadata::{get_messages, ContractFunction}, signer::parse_hex_bytes, }; diff --git a/crates/pop-contracts/src/up.rs b/crates/pop-contracts/src/up.rs index b32f4c3e6..17b1bfa71 100644 --- a/crates/pop-contracts/src/up.rs +++ b/crates/pop-contracts/src/up.rs @@ -3,7 +3,7 @@ use crate::{ errors::Error, utils::{ helpers::{get_manifest_path, parse_balance}, - metadata, + metadata::{process_function_args, FunctionType}, signer::create_signer, }, }; @@ -64,10 +64,11 @@ pub async fn set_up_deployment( parse_balance(&up_opts.value)?; // Process the argument values input by the user. - let args = metadata::process_constructor_args( + let args = process_function_args( up_opts.path.unwrap_or_else(|| PathBuf::from("./")), &up_opts.constructor, up_opts.args, + FunctionType::Constructor, )?; let instantiate_exec: InstantiateExec = InstantiateCommandBuilder::new(extrinsic_opts) diff --git a/crates/pop-contracts/src/utils/metadata.rs b/crates/pop-contracts/src/utils/metadata.rs index 89f887329..bf3458313 100644 --- a/crates/pop-contracts/src/utils/metadata.rs +++ b/crates/pop-contracts/src/utils/metadata.rs @@ -16,8 +16,8 @@ pub struct Param { } #[derive(Clone, PartialEq, Eq)] -/// Describes a contract message. -pub struct Constructor { +/// Describes a contract function. +pub struct ContractFunction { /// The label of the constructor. pub label: String, /// If the message accepts any `value` from the caller. @@ -28,30 +28,22 @@ pub struct Constructor { pub docs: String, /// If the constructor is the default for off-chain consumers (e.g UIs). pub default: bool, + /// If the message is allowed to mutate the contract state. true for constructors. + pub mutates: bool, } #[derive(Clone, PartialEq, Eq)] -/// Describes a contract message. -pub struct Message { - /// The label of the message. - pub label: String, - /// If the message is allowed to mutate the contract state. - pub mutates: bool, - /// If the message accepts any `value` from the caller. - pub payable: bool, - /// The parameters of the deployment handler. - pub args: Vec, - /// The message documentation. - pub docs: String, - /// If the message is the default for off-chain consumers (e.g UIs). - pub default: bool, +/// Specifies the type of contract funtion, either a constructor or a message. +pub enum FunctionType { + Constructor, + Message, } /// Extracts a list of smart contract messages parsing the metadata file. /// /// # Arguments /// * `path` - Location path of the project. -pub fn get_messages(path: &Path) -> Result, Error> { +pub fn get_messages(path: &Path) -> Result, Error> { let cargo_toml_path = match path.ends_with("Cargo.toml") { true => path.to_path_buf(), false => path.join("Cargo.toml"), @@ -59,9 +51,9 @@ pub fn get_messages(path: &Path) -> Result, Error> { let contract_artifacts = ContractArtifacts::from_manifest_or_file(Some(&cargo_toml_path), None)?; let transcoder = contract_artifacts.contract_transcoder()?; - let mut messages: Vec = Vec::new(); + let mut messages: Vec = Vec::new(); for message in transcoder.metadata().spec().messages() { - messages.push(Message { + messages.push(ContractFunction { label: message.label().to_string(), mutates: message.mutates(), payable: message.payable(), @@ -78,7 +70,7 @@ pub fn get_messages(path: &Path) -> Result, Error> { /// # Arguments /// * `path` - Location path of the project. /// * `message` - The label of the contract message. -fn get_message

(path: P, message: &str) -> Result +fn get_message

(path: P, message: &str) -> Result where P: AsRef, { @@ -92,7 +84,7 @@ where /// /// # Arguments /// * `path` - Location path of the project. -pub fn get_constructors(path: &Path) -> Result, Error> { +pub fn get_constructors(path: &Path) -> Result, Error> { let cargo_toml_path = match path.ends_with("Cargo.toml") { true => path.to_path_buf(), false => path.join("Cargo.toml"), @@ -100,14 +92,15 @@ pub fn get_constructors(path: &Path) -> Result, Error> { let contract_artifacts = ContractArtifacts::from_manifest_or_file(Some(&cargo_toml_path), None)?; let transcoder = contract_artifacts.contract_transcoder()?; - let mut constructors: Vec = Vec::new(); + let mut constructors: Vec = Vec::new(); for constructor in transcoder.metadata().spec().constructors() { - constructors.push(Constructor { + constructors.push(ContractFunction { label: constructor.label().to_string(), payable: *constructor.payable(), args: process_args(constructor.args()), docs: constructor.docs().join(" "), default: *constructor.default(), + mutates: true, }); } Ok(constructors) @@ -118,13 +111,13 @@ pub fn get_constructors(path: &Path) -> Result, Error> { /// # Arguments /// * `path` - Location path of the project. /// * `constructor` - The label of the constructor. -fn get_constructor

(path: P, constructor: &str) -> Result +fn get_constructor

(path: P, constructor: &str) -> Result where P: AsRef, { get_constructors(path.as_ref())? .into_iter() - .find(|c: &Constructor| c.label == constructor) + .find(|c| c.label == constructor) .ok_or_else(|| Error::InvalidConstructorName(constructor.to_string())) } @@ -140,66 +133,35 @@ fn process_args(params: &[MessageParamSpec]) -> Vec { args } -/// Processes a list of argument values for a specified contract message, +/// Processes a list of argument values for a specified contract function, /// wrapping each value in `Some(...)` or replacing it with `None` if the argument is optional /// /// # Arguments /// * `path` - Location path of the project. -/// * `message_label` - Label of the contract message to retrieve. +/// * `label` - Label of the contract message to retrieve. /// * `args` - Argument values provided by the user. -pub fn process_message_args

( +pub fn process_function_args

( path: P, - message: &str, + label: &str, args: Vec, + function_type: FunctionType, ) -> Result, Error> where P: AsRef, { - let message = get_message(path, message)?; - if args.len() != message.args.len() { - return Err(Error::IncorrectArguments { - expected: message.args.len(), - provided: args.len(), - }); - } - Ok(args - .into_iter() - .zip(&message.args) - .map(|(arg, param)| match (param.type_name.as_str(), arg.is_empty()) { - ("Option", true) => "None".to_string(), /* If the argument is Option and empty, */ - // replace it with `None` - ("Option", false) => format!("Some({})", arg), /* If the argument is Option and not */ - // empty, wrap it in `Some(...)` - _ => arg, // If the argument is not Option, return it as is - }) - .collect::>()) -} - -/// Processes a list of argument values for a specified contract constructor, -/// wrapping each value in `Some(...)` or replacing it with `None` if the argument is optional -/// -/// # Arguments -/// * `path` - Location path of the project. -/// * `constructor` - Label of the contract constructor to retrieve. -/// * `args` - Argument values provided by the user. -pub fn process_constructor_args

( - path: P, - constructor: &str, - args: Vec, -) -> Result, Error> -where - P: AsRef, -{ - let constructor = get_constructor(path, constructor)?; - if args.len() != constructor.args.len() { + let function = match function_type { + FunctionType::Message => get_message(path, label)?, + FunctionType::Constructor => get_constructor(path, label)?, + }; + if args.len() != function.args.len() { return Err(Error::IncorrectArguments { - expected: constructor.args.len(), + expected: function.args.len(), provided: args.len(), }); } Ok(args .into_iter() - .zip(&constructor.args) + .zip(&function.args) .map(|(arg, param)| match (param.type_name.as_str(), arg.is_empty()) { ("Option", true) => "None".to_string(), /* If the argument is Option and empty, */ // replace it with `None` @@ -326,8 +288,8 @@ mod tests { } #[test] - fn process_message_args_work() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + fn process_function_args_work() -> Result<()> { + let temp_dir = new_environment("testing")?; let current_dir = env::current_dir().expect("Failed to get current directory"); mock_build_process( temp_dir.path().join("testing"), @@ -335,68 +297,64 @@ mod tests { current_dir.join("./tests/files/testing.json"), )?; assert!(matches!( - process_message_args(temp_dir.path().join("testing"),"wrong_flip", Vec::new()), + process_function_args(temp_dir.path().join("testing"),"wrong_flip", Vec::new(), FunctionType::Message), Err(Error::InvalidMessageName(error)) if error == "wrong_flip".to_string())); assert!(matches!( - process_message_args( + process_function_args( temp_dir.path().join("testing"), "specific_flip", - Vec::new() + Vec::new(), + FunctionType::Message ), Err(Error::IncorrectArguments {expected, provided }) if expected == 2 && provided == 0 )); assert_eq!( - process_message_args( + process_function_args( temp_dir.path().join("testing"), "specific_flip", - ["true".to_string(), "2".to_string()].to_vec() + ["true".to_string(), "2".to_string()].to_vec(), + FunctionType::Message )?, ["true".to_string(), "Some(2)".to_string()] ); assert_eq!( - process_message_args( + process_function_args( temp_dir.path().join("testing"), "specific_flip", - ["true".to_string(), "".to_string()].to_vec() + ["true".to_string(), "".to_string()].to_vec(), + FunctionType::Message )?, ["true".to_string(), "None".to_string()] ); - Ok(()) - } - #[test] - fn process_constructor_args_work() -> Result<()> { - let temp_dir = new_environment("testing")?; - let current_dir = env::current_dir().expect("Failed to get current directory"); - mock_build_process( - temp_dir.path().join("testing"), - current_dir.join("./tests/files/testing.contract"), - current_dir.join("./tests/files/testing.json"), - )?; + // Test constructors assert!(matches!( - process_constructor_args(temp_dir.path().join("testing"),"wrong_constructor", Vec::new()), + process_function_args(temp_dir.path().join("testing"),"wrong_constructor", Vec::new(), FunctionType::Constructor), Err(Error::InvalidConstructorName(error)) if error == "wrong_constructor".to_string())); assert!(matches!( - process_constructor_args( + process_function_args( temp_dir.path().join("testing"), "new", - Vec::new() + Vec::new(), + FunctionType::Constructor ), Err(Error::IncorrectArguments {expected, provided }) if expected == 2 && provided == 0 )); assert_eq!( - process_constructor_args( + process_function_args( temp_dir.path().join("testing"), "new", - ["true".to_string(), "2".to_string()].to_vec() + ["true".to_string(), "2".to_string()].to_vec(), + FunctionType::Constructor )?, ["true".to_string(), "Some(2)".to_string()] ); assert_eq!( - process_constructor_args( + process_function_args( temp_dir.path().join("testing"), "new", - ["true".to_string(), "".to_string()].to_vec() + ["true".to_string(), "".to_string()].to_vec(), + FunctionType::Constructor )?, ["true".to_string(), "None".to_string()] ); From e0cb38c41f5556b15aee6875c7e6a9518ffa0912 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Wed, 6 Nov 2024 13:00:43 +0100 Subject: [PATCH 115/211] test: update tests accordingly last changes --- crates/pop-cli/src/commands/up/contract.rs | 2 +- crates/pop-contracts/src/utils/metadata.rs | 21 +-- .../tests/files/testing.contract | 2 +- crates/pop-contracts/tests/files/testing.json | 126 ++++++++++-------- 4 files changed, 82 insertions(+), 69 deletions(-) diff --git a/crates/pop-cli/src/commands/up/contract.rs b/crates/pop-cli/src/commands/up/contract.rs index 2ec2d9a4b..447024122 100644 --- a/crates/pop-cli/src/commands/up/contract.rs +++ b/crates/pop-cli/src/commands/up/contract.rs @@ -339,7 +339,7 @@ mod tests { let command = UpContractCommand { path: None, constructor: "new".to_string(), - args: vec!["false".to_string()].to_vec(), + args: vec![], value: "0".to_string(), gas_limit: None, proof_size: None, diff --git a/crates/pop-contracts/src/utils/metadata.rs b/crates/pop-contracts/src/utils/metadata.rs index bf3458313..cf20d8787 100644 --- a/crates/pop-contracts/src/utils/metadata.rs +++ b/crates/pop-contracts/src/utils/metadata.rs @@ -252,11 +252,14 @@ mod tests { "Constructor that initializes the `bool` value to `false`. Constructors can delegate to other constructors." ); // assert parsed arguments - assert_eq!(constructor[0].args.len(), 2); + assert_eq!(constructor[0].args.len(), 1); assert_eq!(constructor[0].args[0].label, "init_value".to_string()); assert_eq!(constructor[0].args[0].type_name, "bool".to_string()); - assert_eq!(constructor[0].args[1].label, "number".to_string()); - assert_eq!(constructor[0].args[1].type_name, "Option".to_string()); + assert_eq!(constructor[1].args.len(), 2); + assert_eq!(constructor[1].args[0].label, "init_value".to_string()); + assert_eq!(constructor[1].args[0].type_name, "bool".to_string()); + assert_eq!(constructor[1].args[1].label, "number".to_string()); + assert_eq!(constructor[1].args[1].type_name, "Option".to_string()); Ok(()) } @@ -272,11 +275,11 @@ mod tests { assert!(matches!( get_constructor(&temp_dir.path().join("testing"), "wrong_constructor"), Err(Error::InvalidConstructorName(name)) if name == "wrong_constructor".to_string())); - let constructor = get_constructor(&temp_dir.path().join("testing"), "new")?; - assert_eq!(constructor.label, "new"); + let constructor = get_constructor(&temp_dir.path().join("testing"), "default")?; + assert_eq!(constructor.label, "default"); assert_eq!( constructor.docs, - "Constructor that initializes the `bool` value to the given `init_value`." + "Constructor that initializes the `bool` value to `false`. Constructors can delegate to other constructors." ); // assert parsed arguments assert_eq!(constructor.args.len(), 2); @@ -334,7 +337,7 @@ mod tests { assert!(matches!( process_function_args( temp_dir.path().join("testing"), - "new", + "default", Vec::new(), FunctionType::Constructor ), @@ -343,7 +346,7 @@ mod tests { assert_eq!( process_function_args( temp_dir.path().join("testing"), - "new", + "default", ["true".to_string(), "2".to_string()].to_vec(), FunctionType::Constructor )?, @@ -352,7 +355,7 @@ mod tests { assert_eq!( process_function_args( temp_dir.path().join("testing"), - "new", + "default", ["true".to_string(), "".to_string()].to_vec(), FunctionType::Constructor )?, diff --git a/crates/pop-contracts/tests/files/testing.contract b/crates/pop-contracts/tests/files/testing.contract index 7918b7184..fbd8e6fba 100644 --- a/crates/pop-contracts/tests/files/testing.contract +++ b/crates/pop-contracts/tests/files/testing.contract @@ -1 +1 @@ -{"source":{"hash":"0xea3a2d4428c3717ab229f1822014375d42a0497a0b86eb8fbb405ec6733899e9","language":"ink! 5.0.0","compiler":"rustc 1.78.0","wasm":"0x0061736d0100000001270760027f7f0060037f7f7f017f60000060047f7f7f7f017f60037f7f7f0060017f017f6000017f027406057365616c310b6765745f73746f726167650003057365616c3005696e7075740000057365616c320b7365745f73746f726167650003057365616c300b7365616c5f72657475726e0004057365616c301176616c75655f7472616e73666572726564000003656e76066d656d6f7279020102100313120101010100050000040006020000000002020616037f01418080040b7f00418080050b7f00418080050b0711020463616c6c0015066465706c6f7900160ac910122b01017f037f2002200346047f200005200020036a200120036a2d00003a0000200341016a21030c010b0b0b6f01017f0240200020014d04402000210303402002450d02200320012d00003a0000200341016a2103200141016a2101200241016b21020c000b000b200041016b2103200141016b210103402002450d01200220036a200120026a2d00003a0000200241016b21020c000b000b20000b2501017f037f2002200346047f200005200020036a20013a0000200341016a21030c010b0b0b3f01027f0340200245044041000f0b200241016b210220012d0000210320002d00002104200141016a2101200041016a210020032004460d000b200420036b0bc00101057f230041106b22022400410221044104210502402001100a220641ff01714102460d00200241086a2001100b20022d00080d000240024020022d000922030e020100020b20012802042203410449047f4101052001200341046b36020420012001280200220141046a3602002001280000210341000b2101200220033602042002200136020020022802000d0141012103200228020421040b20002003360204200020063a0000410821050b200020056a2004360200200241106a24000b3f01027f230041106b22012400200141086a2000100b20012d0009210020012d00082102200141106a240041024101410220004101461b410020001b20021b0b3c01017f200020012802042202047f2001200241016b36020420012001280200220141016a36020020012d00000520010b3a000120002002453a00000b2601017f230041106b220224002002200036020c20012002410c6a4104100d200241106a24000b4801027f024002402000280208220320026a22042003490d00200420002802044b0d00200420036b2002470d01200028020020036a2001200210051a200020043602080f0b000b000b2601017f230041106b22022400200220003a000f20012002410f6a4101100d200241106a24000b6102027f027e230041206b22002400200041106a22014200370300200042003703082000411036021c200041086a2000411c6a1004200028021c41114f0440000b2001290300210220002903082103200041206a2400410541042002200384501b0b12004180800441003b0100410041021013000b3c01027f027f200145044041808004210141010c010b410121024180800441013a000041818004210141020b2103200120023a0000200020031013000b920101057f230041106b220224002002428080013702082002418080043602044100200241046a2204100c024020022802082206200228020c2203490d00200228020421052002410036020c2002200620036b3602082002200320056a36020420012004100e20002004100c200228020c220020022802084b0d00200520032002280204200010021a200241106a24000f0b000b0d0020004180800420011003000b08002000200110120bb60501087f230041206b2200240020004180800136021441808004200041146a100102400240024020002802142201418180014f0d0041042104027f410420014104490d001a20004184800436020c2000200141046b360210418380042d00002101418280042d00002102418180042d00002103024002400240418080042d00002205412f470440200541ec00460d014104200541e300470d041a41042003413a470d041a4104200241a501470d041a41042202200141d100470d041a410221010c020b41042003418601470d031a4104200241db00470d031a41042202200141d901470d031a410321010c010b41042003410f470d021a41042002411d470d021a4104200141f701470d021a200041146a2000410c6a1009200028021822014102460d01200028021c210420002d001421020b20002001360204200020023a000041080c010b41040b20006a2004360200200028020422024104460d012000280208210520002d000021072000428080013702182000418080043602144100200041146a2203100c20002802182206200028021c2201490d00200028021421042000200620016b220636021420042001200120046a2201200310002000280214220420064b720d0020002004360218200020013602142003100a220141ff01714102460d002000280218220341034d2003410447720d00200028021428000021000240024002404102200241026b2203200341024f1b41016b0e020100020b2005200020021b20074100471014410041001011000b100f41ff01714105470d01230041106b220024002000418080043602044180800441003a00002000428080818010370208200141ff0171410047200041046a100e200028020c2200418180014f0440000b410020001013000b100f41ff01714105460d020b000b410141011011000b2000200141ff0171451014410041001011000bd90201067f230041206b22002400024002400240100f41ff01714105470d0020004180800136021441808004200041146a100120002802142201418180014f0d004104210220014104490d0120004184800436020c2000200141046b360210418380042d00002103418280042d00002101418180042d0000210402400240418080042d0000220541ed014704402005419b0147200441ae0147722001419d0147720d04200341de00460d010c040b200441cb00472001419d0147720d03410321012003411b470d04410221030c010b200041146a2000410c6a1009200028021822034102460d02200028021c210120002d001421020b20002003360204200020023a0000410821020c020b000b410321010b200020026a2001360200024020002802042202410347044020024102470d014100410010121010000b410141011011000b2000280208410020021b20002d000041004710121010000b","build_info":{"rust_toolchain":"stable-aarch64-apple-darwin","cargo_contract_version":"5.0.0-alpha","build_mode":"Release","wasm_opt_settings":{"optimization_passes":"Z","keep_debug_symbols":false}}},"contract":{"name":"testing","version":"0.1.0","authors":["[your_name] <[your_email]>"]},"image":null,"version":5,"types":[{"id":0,"type":{"def":{"primitive":"bool"}}},{"id":1,"type":{"def":{"primitive":"u32"}}},{"id":2,"type":{"path":["testing","testing","Testing"],"def":{"composite":{"fields":[{"name":"value","type":0,"typeName":",>>::Type"},{"name":"number","type":1,"typeName":",>>::Type"}]}}}},{"id":3,"type":{"path":["Option"],"params":[{"name":"T","type":1}],"def":{"variant":{"variants":[{"name":"None","index":0},{"name":"Some","fields":[{"type":1}],"index":1}]}}}},{"id":4,"type":{"path":["Result"],"params":[{"name":"T","type":5},{"name":"E","type":6}],"def":{"variant":{"variants":[{"name":"Ok","fields":[{"type":5}],"index":0},{"name":"Err","fields":[{"type":6}],"index":1}]}}}},{"id":5,"type":{"def":{"tuple":[]}}},{"id":6,"type":{"path":["ink_primitives","LangError"],"def":{"variant":{"variants":[{"name":"CouldNotReadInput","index":1}]}}}},{"id":7,"type":{"path":["Result"],"params":[{"name":"T","type":0},{"name":"E","type":6}],"def":{"variant":{"variants":[{"name":"Ok","fields":[{"type":0}],"index":0},{"name":"Err","fields":[{"type":6}],"index":1}]}}}},{"id":8,"type":{"path":["ink_primitives","types","AccountId"],"def":{"composite":{"fields":[{"type":9,"typeName":"[u8; 32]"}]}}}},{"id":9,"type":{"def":{"array":{"len":32,"type":10}}}},{"id":10,"type":{"def":{"primitive":"u8"}}},{"id":11,"type":{"def":{"primitive":"u128"}}},{"id":12,"type":{"path":["ink_primitives","types","Hash"],"def":{"composite":{"fields":[{"type":9,"typeName":"[u8; 32]"}]}}}},{"id":13,"type":{"def":{"primitive":"u64"}}},{"id":14,"type":{"path":["ink_env","types","NoChainExtension"],"def":{"variant":{}}}}],"storage":{"root":{"root_key":"0x00000000","layout":{"struct":{"name":"Testing","fields":[{"name":"value","layout":{"leaf":{"key":"0x00000000","ty":0}}},{"name":"number","layout":{"leaf":{"key":"0x00000000","ty":1}}}]}},"ty":2}},"spec":{"constructors":[{"label":"new","selector":"0x9bae9d5e","payable":false,"args":[{"label":"init_value","type":{"type":0,"displayName":["bool"]}},{"label":"number","type":{"type":3,"displayName":["Option"]}}],"returnType":{"type":4,"displayName":["ink_primitives","ConstructorResult"]},"docs":["Constructor that initializes the `bool` value to the given `init_value`."],"default":false},{"label":"default","selector":"0xed4b9d1b","payable":false,"args":[],"returnType":{"type":4,"displayName":["ink_primitives","ConstructorResult"]},"docs":["Constructor that initializes the `bool` value to `false`.","","Constructors can delegate to other constructors."],"default":false}],"messages":[{"label":"flip","selector":"0x633aa551","mutates":true,"payable":false,"args":[],"returnType":{"type":4,"displayName":["ink","MessageResult"]},"docs":[" A message that can be called on instantiated contracts."," This one flips the value of the stored `bool` from `true`"," to `false` and vice versa."],"default":false},{"label":"get","selector":"0x2f865bd9","mutates":false,"payable":false,"args":[],"returnType":{"type":7,"displayName":["ink","MessageResult"]},"docs":[" Simply returns the current value of our `bool`."],"default":false},{"label":"specific_flip","selector":"0x6c0f1df7","mutates":true,"payable":true,"args":[{"label":"new_value","type":{"type":0,"displayName":["bool"]}},{"label":"number","type":{"type":3,"displayName":["Option"]}}],"returnType":{"type":4,"displayName":["ink","MessageResult"]},"docs":[" A message for testing, flips the value of the stored `bool` with `new_value`"," and is payable"],"default":false}],"events":[],"docs":[],"lang_error":{"type":6,"displayName":["ink","LangError"]},"environment":{"accountId":{"type":8,"displayName":["AccountId"]},"balance":{"type":11,"displayName":["Balance"]},"hash":{"type":12,"displayName":["Hash"]},"timestamp":{"type":13,"displayName":["Timestamp"]},"blockNumber":{"type":1,"displayName":["BlockNumber"]},"chainExtension":{"type":14,"displayName":["ChainExtension"]},"maxEventTopics":4,"staticBufferSize":16384}}} \ No newline at end of file +{"source":{"hash":"0xf4f0cafd08d8e362141b3c64e3c651ad6a38225dbf0b66f691c15bb0ea00eac3","language":"ink! 5.0.0","compiler":"rustc 1.78.0","wasm":"0x0061736d0100000001270760027f7f0060037f7f7f017f60000060047f7f7f7f017f60037f7f7f0060017f017f6000017f027406057365616c310b6765745f73746f726167650003057365616c3005696e7075740000057365616c320b7365745f73746f726167650003057365616c300b7365616c5f72657475726e0004057365616c301176616c75655f7472616e73666572726564000003656e76066d656d6f7279020102100313120101010100050000040006000200000002020616037f01418080040b7f00418080050b7f00418080050b0711020463616c6c0015066465706c6f7900160ad910122b01017f037f2002200346047f200005200020036a200120036a2d00003a0000200341016a21030c010b0b0b6f01017f0240200020014d04402000210303402002450d02200320012d00003a0000200341016a2103200141016a2101200241016b21020c000b000b200041016b2103200141016b210103402002450d01200220036a200120026a2d00003a0000200241016b21020c000b000b20000b2501017f037f2002200346047f200005200020036a20013a0000200341016a21030c010b0b0b3f01027f0340200245044041000f0b200241016b210220012d0000210320002d00002104200141016a2101200041016a210020032004460d000b200420036b0bc00101057f230041106b22022400410221044104210502402001100a220641ff01714102460d00200241086a2001100b20022d00080d000240024020022d000922030e020100020b20012802042203410449047f4101052001200341046b36020420012001280200220141046a3602002001280000210341000b2101200220033602042002200136020020022802000d0141012103200228020421040b20002003360204200020063a0000410821050b200020056a2004360200200241106a24000b3f01027f230041106b22012400200141086a2000100b20012d0009210020012d00082102200141106a240041024101410220004101461b410020001b20021b0b3c01017f200020012802042202047f2001200241016b36020420012001280200220141016a36020020012d00000520010b3a000120002002453a00000b2601017f230041106b220224002002200036020c20012002410c6a4104100d200241106a24000b4801027f024002402000280208220320026a22042003490d00200420002802044b0d00200420036b2002470d01200028020020036a2001200210051a200020043602080f0b000b000b2601017f230041106b22022400200220003a000f20012002410f6a4101100d200241106a24000b6102027f027e230041206b22002400200041106a22014200370300200042003703082000411036021c200041086a2000411c6a1004200028021c41114f0440000b2001290300210220002903082103200041206a2400410541042002200384501b0b3c01027f027f200145044041808004210141010c010b410121024180800441013a000041818004210141020b2103200120023a0000200020031013000b12004180800441003b0100410041021013000b920101057f230041106b220224002002428080013702082002418080043602044100200241046a2204100c024020022802082206200228020c2203490d00200228020421052002410036020c2002200620036b3602082002200320056a36020420012004100e20002004100c200228020c220020022802084b0d00200520032002280204200010021a200241106a24000f0b000b0d0020004180800420011003000b08002000200110120bb60501087f230041206b2200240020004180800136021441808004200041146a100102400240024020002802142201418180014f0d0041042104027f410420014104490d001a20004184800436020c2000200141046b360210418380042d00002101418280042d00002102418180042d00002103024002400240418080042d00002205412f470440200541ec00460d014104200541e300470d041a41042003413a470d041a4104200241a501470d041a41042202200141d100470d041a410221010c020b41042003418601470d031a4104200241db00470d031a41042202200141d901470d031a410321010c010b41042003410f470d021a41042002411d470d021a4104200141f701470d021a200041146a2000410c6a1009200028021822014102460d01200028021c210420002d001421020b20002001360204200020023a000041080c010b41040b20006a2004360200200028020422024104460d012000280208210520002d000021072000428080013702182000418080043602144100200041146a2203100c20002802182206200028021c2201490d00200028021421042000200620016b220636021420042001200120046a2201200310002000280214220420064b720d0020002004360218200020013602142003100a220141ff01714102460d002000280218220341034d2003410447720d00200028021428000021000240024002404102200241026b2203200341024f1b41016b0e020100020b2005200020021b20074100471014410041001010000b100f41ff01714105470d01230041106b220024002000418080043602044180800441003a00002000428080818010370208200141ff0171410047200041046a100e200028020c2200418180014f0440000b410020001013000b100f41ff01714105460d020b000b410141011010000b2000200141ff0171451014410041001010000be90201067f230041206b22002400024002400240100f41ff01714105470d0020004180800136021441808004200041146a100120002802142201418180014f0d004104210220014104490d0120004184800436020c2000200141046b360210418380042d00002101418280042d00002103418180042d00002104027f418080042d0000220541ed014704402005419b0147200441ae0147722003419d0147200141de004772720d0341022103410322012000410c6a100a220441ff01714102470d011a0c040b200441cb00472003419d0147722001411b47720d02200041146a2000410c6a1009200028021822034102460d0220002d00142104200028021c0b210120002003360204200020043a0000410821020c020b000b410321010b200020026a2001360200024020002802042201410347044020002d0000210220014102460d012000280208410020011b200241004710121011000b410141011010000b4100200210121011000b","build_info":{"rust_toolchain":"stable-aarch64-apple-darwin","cargo_contract_version":"5.0.0-alpha","build_mode":"Release","wasm_opt_settings":{"optimization_passes":"Z","keep_debug_symbols":false}}},"contract":{"name":"testing","version":"0.1.0","authors":["[your_name] <[your_email]>"]},"image":null,"version":5,"types":[{"id":0,"type":{"def":{"primitive":"bool"}}},{"id":1,"type":{"def":{"primitive":"u32"}}},{"id":2,"type":{"path":["testing","testing","Testing"],"def":{"composite":{"fields":[{"name":"value","type":0,"typeName":",>>::Type"},{"name":"number","type":1,"typeName":",>>::Type"}]}}}},{"id":3,"type":{"path":["Result"],"params":[{"name":"T","type":4},{"name":"E","type":5}],"def":{"variant":{"variants":[{"name":"Ok","fields":[{"type":4}],"index":0},{"name":"Err","fields":[{"type":5}],"index":1}]}}}},{"id":4,"type":{"def":{"tuple":[]}}},{"id":5,"type":{"path":["ink_primitives","LangError"],"def":{"variant":{"variants":[{"name":"CouldNotReadInput","index":1}]}}}},{"id":6,"type":{"path":["Option"],"params":[{"name":"T","type":1}],"def":{"variant":{"variants":[{"name":"None","index":0},{"name":"Some","fields":[{"type":1}],"index":1}]}}}},{"id":7,"type":{"path":["Result"],"params":[{"name":"T","type":0},{"name":"E","type":5}],"def":{"variant":{"variants":[{"name":"Ok","fields":[{"type":0}],"index":0},{"name":"Err","fields":[{"type":5}],"index":1}]}}}},{"id":8,"type":{"path":["ink_primitives","types","AccountId"],"def":{"composite":{"fields":[{"type":9,"typeName":"[u8; 32]"}]}}}},{"id":9,"type":{"def":{"array":{"len":32,"type":10}}}},{"id":10,"type":{"def":{"primitive":"u8"}}},{"id":11,"type":{"def":{"primitive":"u128"}}},{"id":12,"type":{"path":["ink_primitives","types","Hash"],"def":{"composite":{"fields":[{"type":9,"typeName":"[u8; 32]"}]}}}},{"id":13,"type":{"def":{"primitive":"u64"}}},{"id":14,"type":{"path":["ink_env","types","NoChainExtension"],"def":{"variant":{}}}}],"storage":{"root":{"root_key":"0x00000000","layout":{"struct":{"name":"Testing","fields":[{"name":"value","layout":{"leaf":{"key":"0x00000000","ty":0}}},{"name":"number","layout":{"leaf":{"key":"0x00000000","ty":1}}}]}},"ty":2}},"spec":{"constructors":[{"label":"new","selector":"0x9bae9d5e","payable":false,"args":[{"label":"init_value","type":{"type":0,"displayName":["bool"]}}],"returnType":{"type":3,"displayName":["ink_primitives","ConstructorResult"]},"docs":["Constructor that initializes the `bool` value to the given `init_value`."],"default":false},{"label":"default","selector":"0xed4b9d1b","payable":false,"args":[{"label":"init_value","type":{"type":0,"displayName":["bool"]}},{"label":"number","type":{"type":6,"displayName":["Option"]}}],"returnType":{"type":3,"displayName":["ink_primitives","ConstructorResult"]},"docs":["Constructor that initializes the `bool` value to `false`.","","Constructors can delegate to other constructors."],"default":false}],"messages":[{"label":"flip","selector":"0x633aa551","mutates":true,"payable":false,"args":[],"returnType":{"type":3,"displayName":["ink","MessageResult"]},"docs":[" A message that can be called on instantiated contracts."," This one flips the value of the stored `bool` from `true`"," to `false` and vice versa."],"default":false},{"label":"get","selector":"0x2f865bd9","mutates":false,"payable":false,"args":[],"returnType":{"type":7,"displayName":["ink","MessageResult"]},"docs":[" Simply returns the current value of our `bool`."],"default":false},{"label":"specific_flip","selector":"0x6c0f1df7","mutates":true,"payable":true,"args":[{"label":"new_value","type":{"type":0,"displayName":["bool"]}},{"label":"number","type":{"type":6,"displayName":["Option"]}}],"returnType":{"type":3,"displayName":["ink","MessageResult"]},"docs":[" A message for testing, flips the value of the stored `bool` with `new_value`"," and is payable"],"default":false}],"events":[],"docs":[],"lang_error":{"type":5,"displayName":["ink","LangError"]},"environment":{"accountId":{"type":8,"displayName":["AccountId"]},"balance":{"type":11,"displayName":["Balance"]},"hash":{"type":12,"displayName":["Hash"]},"timestamp":{"type":13,"displayName":["Timestamp"]},"blockNumber":{"type":1,"displayName":["BlockNumber"]},"chainExtension":{"type":14,"displayName":["ChainExtension"]},"maxEventTopics":4,"staticBufferSize":16384}}} \ No newline at end of file diff --git a/crates/pop-contracts/tests/files/testing.json b/crates/pop-contracts/tests/files/testing.json index a5bad9291..61921986b 100644 --- a/crates/pop-contracts/tests/files/testing.json +++ b/crates/pop-contracts/tests/files/testing.json @@ -1,6 +1,6 @@ { "source": { - "hash": "0xea3a2d4428c3717ab229f1822014375d42a0497a0b86eb8fbb405ec6733899e9", + "hash": "0xf4f0cafd08d8e362141b3c64e3c651ad6a38225dbf0b66f691c15bb0ea00eac3", "language": "ink! 5.0.0", "compiler": "rustc 1.78.0", "build_info": { @@ -67,39 +67,6 @@ }, { "id": 3, - "type": { - "path": [ - "Option" - ], - "params": [ - { - "name": "T", - "type": 1 - } - ], - "def": { - "variant": { - "variants": [ - { - "name": "None", - "index": 0 - }, - { - "name": "Some", - "fields": [ - { - "type": 1 - } - ], - "index": 1 - } - ] - } - } - } - }, - { - "id": 4, "type": { "path": [ "Result" @@ -107,11 +74,11 @@ "params": [ { "name": "T", - "type": 5 + "type": 4 }, { "name": "E", - "type": 6 + "type": 5 } ], "def": { @@ -121,7 +88,7 @@ "name": "Ok", "fields": [ { - "type": 5 + "type": 4 } ], "index": 0 @@ -130,7 +97,7 @@ "name": "Err", "fields": [ { - "type": 6 + "type": 5 } ], "index": 1 @@ -141,7 +108,7 @@ } }, { - "id": 5, + "id": 4, "type": { "def": { "tuple": [] @@ -149,7 +116,7 @@ } }, { - "id": 6, + "id": 5, "type": { "path": [ "ink_primitives", @@ -167,6 +134,39 @@ } } }, + { + "id": 6, + "type": { + "path": [ + "Option" + ], + "params": [ + { + "name": "T", + "type": 1 + } + ], + "def": { + "variant": { + "variants": [ + { + "name": "None", + "index": 0 + }, + { + "name": "Some", + "fields": [ + { + "type": 1 + } + ], + "index": 1 + } + ] + } + } + } + }, { "id": 7, "type": { @@ -180,7 +180,7 @@ }, { "name": "E", - "type": 6 + "type": 5 } ], "def": { @@ -199,7 +199,7 @@ "name": "Err", "fields": [ { - "type": 6 + "type": 5 } ], "index": 1 @@ -344,19 +344,10 @@ "bool" ] } - }, - { - "label": "number", - "type": { - "type": 3, - "displayName": [ - "Option" - ] - } } ], "returnType": { - "type": 4, + "type": 3, "displayName": [ "ink_primitives", "ConstructorResult" @@ -371,9 +362,28 @@ "label": "default", "selector": "0xed4b9d1b", "payable": false, - "args": [], + "args": [ + { + "label": "init_value", + "type": { + "type": 0, + "displayName": [ + "bool" + ] + } + }, + { + "label": "number", + "type": { + "type": 6, + "displayName": [ + "Option" + ] + } + } + ], "returnType": { - "type": 4, + "type": 3, "displayName": [ "ink_primitives", "ConstructorResult" @@ -395,7 +405,7 @@ "payable": false, "args": [], "returnType": { - "type": 4, + "type": 3, "displayName": [ "ink", "MessageResult" @@ -444,7 +454,7 @@ { "label": "number", "type": { - "type": 3, + "type": 6, "displayName": [ "Option" ] @@ -452,7 +462,7 @@ } ], "returnType": { - "type": 4, + "type": 3, "displayName": [ "ink", "MessageResult" @@ -468,7 +478,7 @@ "events": [], "docs": [], "lang_error": { - "type": 6, + "type": 5, "displayName": [ "ink", "LangError" From 3be78c7450dda76522c1dd6b1e3fe9595b2baeb5 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Wed, 6 Nov 2024 14:53:04 +0100 Subject: [PATCH 116/211] fix: issue with delimiter --- crates/pop-cli/src/commands/call/contract.rs | 14 ++++++-------- crates/pop-cli/src/commands/up/contract.rs | 2 +- crates/pop-contracts/src/utils/metadata.rs | 7 ++++--- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index d0c3e8b13..9a5a61c38 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -30,7 +30,7 @@ pub struct CallContractCommand { #[clap(long, short)] message: Option, /// The constructor arguments, encoded as strings. - #[clap(long, num_args = 0.., value_delimiter = ',')] + #[clap(long, num_args = 0..,)] args: Vec, /// The value to be transferred as part of the call. #[clap(name = "value", short = 'v', long, default_value = DEFAULT_PAYABLE_VALUE)] @@ -482,7 +482,7 @@ mod tests { .expect_warning("Your call has not been executed.") .expect_info("Gas limit: Weight { ref_time: 100, proof_size: 10 }"); - let mut call_config = CallContractCommand { + let call_config = CallContractCommand { path: Some(temp_dir.path().join("testing")), contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), message: Some("flip".to_string()), @@ -549,7 +549,7 @@ mod tests { .expect_outro("Contract calling complete."); // Contract deployed on Pop Network testnet, test get - let mut call_config = CallContractCommand { + let call_config = CallContractCommand { path: Some(temp_dir.path().join("testing")), contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), message: Some("get".to_string()), @@ -760,7 +760,6 @@ mod tests { let mut cli = MockCli::new() .expect_input("Signer calling the contract:", "//Alice".into()) .expect_input("Value to transfer to the call:", "50".into()) // Only if payable - .expect_input("Enter the value for the parameter: number", "2".into()) // Args for specific_flip .expect_input("Enter the value for the parameter: new_value", "true".into()) // Args for specific_flip .expect_select::( "Select the message to call:", @@ -781,7 +780,7 @@ mod tests { "Where is your project located?", temp_dir.path().join("testing").display().to_string(), ).expect_info(format!( - "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true,2 --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", temp_dir.path().join("testing").display().to_string(), )); @@ -805,9 +804,8 @@ mod tests { Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) ); assert_eq!(call_config.message, Some("specific_flip".to_string())); - assert_eq!(call_config.args.len(), 2); + assert_eq!(call_config.args.len(), 1); assert_eq!(call_config.args[0], "true".to_string()); - assert_eq!(call_config.args[1], "2".to_string()); assert_eq!(call_config.value, "50".to_string()); assert_eq!(call_config.gas_limit, None); assert_eq!(call_config.proof_size, None); @@ -817,7 +815,7 @@ mod tests { assert!(!call_config.dry_run); assert!(call_config.dev_mode); assert_eq!(call_config.display(), format!( - "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true,2 --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", temp_dir.path().join("testing").display().to_string(), )); diff --git a/crates/pop-cli/src/commands/up/contract.rs b/crates/pop-cli/src/commands/up/contract.rs index 447024122..9acbc2b63 100644 --- a/crates/pop-cli/src/commands/up/contract.rs +++ b/crates/pop-cli/src/commands/up/contract.rs @@ -35,7 +35,7 @@ pub struct UpContractCommand { #[clap(name = "constructor", long, default_value = "new")] constructor: String, /// The constructor arguments, encoded as strings. - #[clap(long, num_args = 0.., value_delimiter = ',')] + #[clap(long, num_args = 0..,)] args: Vec, /// Transfers an initial balance to the instantiated contract. #[clap(name = "value", long, default_value = "0")] diff --git a/crates/pop-contracts/src/utils/metadata.rs b/crates/pop-contracts/src/utils/metadata.rs index cf20d8787..4b84fa257 100644 --- a/crates/pop-contracts/src/utils/metadata.rs +++ b/crates/pop-contracts/src/utils/metadata.rs @@ -149,17 +149,18 @@ pub fn process_function_args

( where P: AsRef, { + let parsed_args = args.into_iter().map(|arg| arg.replace(",", "")).collect::>(); let function = match function_type { FunctionType::Message => get_message(path, label)?, FunctionType::Constructor => get_constructor(path, label)?, }; - if args.len() != function.args.len() { + if parsed_args.len() != function.args.len() { return Err(Error::IncorrectArguments { expected: function.args.len(), - provided: args.len(), + provided: parsed_args.len(), }); } - Ok(args + Ok(parsed_args .into_iter() .zip(&function.args) .map(|(arg, param)| match (param.type_name.as_str(), arg.is_empty()) { From 471881bdd4a8519e7e2a43d97f8a7912dc8b188f Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Wed, 6 Nov 2024 23:37:44 +0100 Subject: [PATCH 117/211] test: fix unit test --- crates/pop-contracts/src/utils/metadata.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pop-contracts/src/utils/metadata.rs b/crates/pop-contracts/src/utils/metadata.rs index 4b84fa257..eb9c7b6de 100644 --- a/crates/pop-contracts/src/utils/metadata.rs +++ b/crates/pop-contracts/src/utils/metadata.rs @@ -209,7 +209,7 @@ mod tests { #[test] fn get_message_work() -> Result<()> { - let temp_dir = generate_smart_contract_test_environment()?; + let temp_dir = new_environment("testing")?; let current_dir = env::current_dir().expect("Failed to get current directory"); mock_build_process( temp_dir.path().join("testing"), From 438681d788e36d5ab57176a8ed6a31ded5ed328e Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 7 Nov 2024 15:22:36 +0100 Subject: [PATCH 118/211] refactor: renaming and fix comments --- crates/pop-cli/src/commands/call/contract.rs | 2 +- crates/pop-contracts/src/utils/metadata.rs | 44 +++++++++++--------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 9a5a61c38..a0f28dc43 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -176,7 +176,7 @@ impl CallContractCommand { }; // Parse the contract metadata provided. If there is an error, do not prompt for more. - let messages = match get_messages(&contract_path) { + let messages = match get_messages(contract_path) { Ok(messages) => messages, Err(e) => { return Err(anyhow!(format!( diff --git a/crates/pop-contracts/src/utils/metadata.rs b/crates/pop-contracts/src/utils/metadata.rs index eb9c7b6de..a16e7876a 100644 --- a/crates/pop-contracts/src/utils/metadata.rs +++ b/crates/pop-contracts/src/utils/metadata.rs @@ -6,8 +6,8 @@ use contract_transcode::ink_metadata::MessageParamSpec; use scale_info::form::PortableForm; use std::path::Path; +/// Describes a parameter. #[derive(Clone, PartialEq, Eq)] -/// Describes a contract message. pub struct Param { /// The label of the parameter. pub label: String, @@ -15,25 +15,25 @@ pub struct Param { pub type_name: String, } -#[derive(Clone, PartialEq, Eq)] /// Describes a contract function. +#[derive(Clone, PartialEq, Eq)] pub struct ContractFunction { - /// The label of the constructor. + /// The label of the function. pub label: String, - /// If the message accepts any `value` from the caller. + /// If the function accepts any `value` from the caller. pub payable: bool, /// The parameters of the deployment handler. pub args: Vec, - /// The constructor documentation. + /// The function documentation. pub docs: String, - /// If the constructor is the default for off-chain consumers (e.g UIs). + /// If the message/constructor is the default for off-chain consumers (e.g UIs). pub default: bool, /// If the message is allowed to mutate the contract state. true for constructors. pub mutates: bool, } -#[derive(Clone, PartialEq, Eq)] /// Specifies the type of contract funtion, either a constructor or a message. +#[derive(Clone, PartialEq, Eq)] pub enum FunctionType { Constructor, Message, @@ -51,18 +51,20 @@ pub fn get_messages(path: &Path) -> Result, Error> { let contract_artifacts = ContractArtifacts::from_manifest_or_file(Some(&cargo_toml_path), None)?; let transcoder = contract_artifacts.contract_transcoder()?; - let mut messages: Vec = Vec::new(); - for message in transcoder.metadata().spec().messages() { - messages.push(ContractFunction { + Ok(transcoder + .metadata() + .spec() + .messages() + .iter() + .map(|message| ContractFunction { label: message.label().to_string(), mutates: message.mutates(), payable: message.payable(), args: process_args(message.args()), docs: message.docs().join(" "), default: *message.default(), - }); - } - Ok(messages) + }) + .collect()) } /// Extracts the information of a smart contract message parsing the metadata file. @@ -92,18 +94,20 @@ pub fn get_constructors(path: &Path) -> Result, Error> { let contract_artifacts = ContractArtifacts::from_manifest_or_file(Some(&cargo_toml_path), None)?; let transcoder = contract_artifacts.contract_transcoder()?; - let mut constructors: Vec = Vec::new(); - for constructor in transcoder.metadata().spec().constructors() { - constructors.push(ContractFunction { + Ok(transcoder + .metadata() + .spec() + .constructors() + .iter() + .map(|constructor| ContractFunction { label: constructor.label().to_string(), payable: *constructor.payable(), args: process_args(constructor.args()), docs: constructor.docs().join(" "), default: *constructor.default(), mutates: true, - }); - } - Ok(constructors) + }) + .collect()) } /// Extracts the information of a smart contract constructor parsing the metadata file. @@ -149,7 +153,7 @@ pub fn process_function_args

( where P: AsRef, { - let parsed_args = args.into_iter().map(|arg| arg.replace(",", "")).collect::>(); + let parsed_args = args.into_iter().map(|arg| arg.replace(",", "")).collect::>(); let function = match function_type { FunctionType::Message => get_message(path, label)?, FunctionType::Constructor => get_constructor(path, label)?, From dcde21e3b85a3fb37c2d41e52d191166cd0a4643 Mon Sep 17 00:00:00 2001 From: Frank Bell <60948618+evilrobot-01@users.noreply.github.com> Date: Fri, 8 Nov 2024 08:35:46 +0000 Subject: [PATCH 119/211] refactor: format types (#339) Shows the full type representation, making it easier to see the entry format of parameter values. --- crates/pop-cli/src/commands/call/contract.rs | 21 +- crates/pop-contracts/src/utils/metadata.rs | 201 ++++++++++++++++--- 2 files changed, 186 insertions(+), 36 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index a0f28dc43..8db1cc63c 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -96,7 +96,8 @@ impl CallContractCommand { full_message.push_str(&format!(" --message {}", message)); } if !self.args.is_empty() { - full_message.push_str(&format!(" --args {}", self.args.join(","))); + let args: Vec<_> = self.args.iter().map(|a| format!("\"{a}\"")).collect(); + full_message.push_str(&format!(" --args {}", args.join(", "))); } if self.value != DEFAULT_PAYABLE_VALUE { full_message.push_str(&format!(" --value {}", self.value)); @@ -235,7 +236,7 @@ impl CallContractCommand { .placeholder(&format!("Type required: {}", arg.type_name)); // Set default input only if the parameter type is `Option` (Not mandatory) - if arg.type_name == "Option" { + if arg.type_name.starts_with("Option<") { input = input.default_input(""); } contract_args.push(input.interact()?); @@ -482,7 +483,7 @@ mod tests { .expect_warning("Your call has not been executed.") .expect_info("Gas limit: Weight { ref_time: 100, proof_size: 10 }"); - let call_config = CallContractCommand { + let mut call_config = CallContractCommand { path: Some(temp_dir.path().join("testing")), contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), message: Some("flip".to_string()), @@ -549,7 +550,7 @@ mod tests { .expect_outro("Contract calling complete."); // Contract deployed on Pop Network testnet, test get - let call_config = CallContractCommand { + let mut call_config = CallContractCommand { path: Some(temp_dir.path().join("testing")), contract: Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()), message: Some("get".to_string()), @@ -696,7 +697,7 @@ mod tests { "Where is your project located?", temp_dir.path().join("testing").display().to_string(), ).expect_info(format!( - "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true,2 --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args \"true\", \"2\" --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", temp_dir.path().join("testing").display().to_string(), )); @@ -731,7 +732,7 @@ mod tests { assert!(call_config.execute); assert!(!call_config.dry_run); assert_eq!(call_config.display(), format!( - "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true,2 --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args \"true\", \"2\" --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", temp_dir.path().join("testing").display().to_string(), )); @@ -760,6 +761,7 @@ mod tests { let mut cli = MockCli::new() .expect_input("Signer calling the contract:", "//Alice".into()) .expect_input("Value to transfer to the call:", "50".into()) // Only if payable + .expect_input("Enter the value for the parameter: number", "2".into()) // Args for specific_flip .expect_input("Enter the value for the parameter: new_value", "true".into()) // Args for specific_flip .expect_select::( "Select the message to call:", @@ -780,7 +782,7 @@ mod tests { "Where is your project located?", temp_dir.path().join("testing").display().to_string(), ).expect_info(format!( - "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args \"true\", \"2\" --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", temp_dir.path().join("testing").display().to_string(), )); @@ -804,8 +806,9 @@ mod tests { Some("15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".to_string()) ); assert_eq!(call_config.message, Some("specific_flip".to_string())); - assert_eq!(call_config.args.len(), 1); + assert_eq!(call_config.args.len(), 2); assert_eq!(call_config.args[0], "true".to_string()); + assert_eq!(call_config.args[1], "2".to_string()); assert_eq!(call_config.value, "50".to_string()); assert_eq!(call_config.gas_limit, None); assert_eq!(call_config.proof_size, None); @@ -815,7 +818,7 @@ mod tests { assert!(!call_config.dry_run); assert!(call_config.dev_mode); assert_eq!(call_config.display(), format!( - "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args true --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args \"true\", \"2\" --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", temp_dir.path().join("testing").display().to_string(), )); diff --git a/crates/pop-contracts/src/utils/metadata.rs b/crates/pop-contracts/src/utils/metadata.rs index a16e7876a..f0dd2b8e1 100644 --- a/crates/pop-contracts/src/utils/metadata.rs +++ b/crates/pop-contracts/src/utils/metadata.rs @@ -3,11 +3,11 @@ use crate::errors::Error; use contract_extrinsics::ContractArtifacts; use contract_transcode::ink_metadata::MessageParamSpec; -use scale_info::form::PortableForm; +use scale_info::{form::PortableForm, PortableRegistry, Type, TypeDef, TypeDefPrimitive}; use std::path::Path; /// Describes a parameter. -#[derive(Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Param { /// The label of the parameter. pub label: String, @@ -51,8 +51,8 @@ pub fn get_messages(path: &Path) -> Result, Error> { let contract_artifacts = ContractArtifacts::from_manifest_or_file(Some(&cargo_toml_path), None)?; let transcoder = contract_artifacts.contract_transcoder()?; - Ok(transcoder - .metadata() + let metadata = transcoder.metadata(); + Ok(metadata .spec() .messages() .iter() @@ -60,7 +60,7 @@ pub fn get_messages(path: &Path) -> Result, Error> { label: message.label().to_string(), mutates: message.mutates(), payable: message.payable(), - args: process_args(message.args()), + args: process_args(message.args(), metadata.registry()), docs: message.docs().join(" "), default: *message.default(), }) @@ -94,15 +94,15 @@ pub fn get_constructors(path: &Path) -> Result, Error> { let contract_artifacts = ContractArtifacts::from_manifest_or_file(Some(&cargo_toml_path), None)?; let transcoder = contract_artifacts.contract_transcoder()?; - Ok(transcoder - .metadata() + let metadata = transcoder.metadata(); + Ok(metadata .spec() .constructors() .iter() .map(|constructor| ContractFunction { label: constructor.label().to_string(), payable: *constructor.payable(), - args: process_args(constructor.args()), + args: process_args(constructor.args(), metadata.registry()), docs: constructor.docs().join(" "), default: *constructor.default(), mutates: true, @@ -126,17 +126,164 @@ where } // Parse the parameters into a vector of argument labels. -fn process_args(params: &[MessageParamSpec]) -> Vec { +fn process_args( + params: &[MessageParamSpec], + registry: &PortableRegistry, +) -> Vec { let mut args: Vec = Vec::new(); for arg in params { - args.push(Param { - label: arg.label().to_string(), - type_name: arg.ty().display_name().to_string(), - }); + // Resolve type from registry to provide full type representation. + let type_name = + format_type(registry.resolve(arg.ty().ty().id).expect("type not found"), registry); + args.push(Param { label: arg.label().to_string(), type_name }); } args } +// Formats a specified type, using the registry to output its full type representation. +fn format_type(ty: &Type, registry: &PortableRegistry) -> String { + let mut name = ty + .path + .segments + .last() + .map(|s| s.to_owned()) + .unwrap_or_else(|| ty.path.to_string()); + + if !ty.type_params.is_empty() { + let params: Vec<_> = ty + .type_params + .iter() + .filter_map(|p| registry.resolve(p.ty.unwrap().id)) + .map(|t| format_type(t, registry)) + .collect(); + name = format!("{name}<{}>", params.join(",")); + } + + name = format!( + "{name}{}", + match &ty.type_def { + TypeDef::Composite(composite) => { + if composite.fields.is_empty() { + return "".to_string(); + } + + let mut named = false; + let fields: Vec<_> = composite + .fields + .iter() + .filter_map(|f| match f.name.as_ref() { + None => registry.resolve(f.ty.id).map(|t| format_type(t, registry)), + Some(field) => { + named = true; + f.type_name.as_ref().map(|t| format!("{field}: {t}")) + }, + }) + .collect(); + match named { + true => format!(" {{ {} }}", fields.join(", ")), + false => format!(" ({})", fields.join(", ")), + } + }, + TypeDef::Variant(variant) => { + let variants: Vec<_> = variant + .variants + .iter() + .map(|v| { + if v.fields.is_empty() { + return v.name.clone(); + } + + let name = v.name.as_str(); + let mut named = false; + let fields: Vec<_> = v + .fields + .iter() + .filter_map(|f| match f.name.as_ref() { + None => registry.resolve(f.ty.id).map(|t| format_type(t, registry)), + Some(field) => { + named = true; + f.type_name.as_ref().map(|t| format!("{field}: {t}")) + }, + }) + .collect(); + format!( + "{name}{}", + match named { + true => format!("{{ {} }}", fields.join(", ")), + false => format!("({})", fields.join(", ")), + } + ) + }) + .collect(); + format!(": {}", variants.join(", ")) + }, + TypeDef::Sequence(sequence) => { + format!( + "[{}]", + format_type( + registry.resolve(sequence.type_param.id).expect("sequence type not found"), + registry + ) + ) + }, + TypeDef::Array(array) => { + format!( + "[{};{}]", + format_type( + registry.resolve(array.type_param.id).expect("array type not found"), + registry + ), + array.len + ) + }, + TypeDef::Tuple(tuple) => { + let fields: Vec<_> = tuple + .fields + .iter() + .filter_map(|p| registry.resolve(p.id)) + .map(|t| format_type(t, registry)) + .collect(); + format!("({})", fields.join(",")) + }, + TypeDef::Primitive(primitive) => { + use TypeDefPrimitive::*; + match primitive { + Bool => "bool", + Char => "char", + Str => "str", + U8 => "u8", + U16 => "u16", + U32 => "u32", + U64 => "u64", + U128 => "u128", + U256 => "u256", + I8 => "i8", + I16 => "i16", + I32 => "i32", + I64 => "i64", + I128 => "i128", + I256 => "i256", + } + .to_string() + }, + TypeDef::Compact(compact) => { + format!( + "Compact<{}>", + format_type( + registry.resolve(compact.type_param.id).expect("compact type not found"), + registry + ) + ) + }, + TypeDef::BitSequence(_) => { + unimplemented!("bit sequence not currently supported") + }, + } + ); + + name +} + /// Processes a list of argument values for a specified contract function, /// wrapping each value in `Some(...)` or replacing it with `None` if the argument is optional /// @@ -153,26 +300,26 @@ pub fn process_function_args

( where P: AsRef, { - let parsed_args = args.into_iter().map(|arg| arg.replace(",", "")).collect::>(); let function = match function_type { FunctionType::Message => get_message(path, label)?, FunctionType::Constructor => get_constructor(path, label)?, }; - if parsed_args.len() != function.args.len() { + if args.len() != function.args.len() { return Err(Error::IncorrectArguments { expected: function.args.len(), - provided: parsed_args.len(), + provided: args.len(), }); } - Ok(parsed_args + Ok(args .into_iter() .zip(&function.args) - .map(|(arg, param)| match (param.type_name.as_str(), arg.is_empty()) { - ("Option", true) => "None".to_string(), /* If the argument is Option and empty, */ - // replace it with `None` - ("Option", false) => format!("Some({})", arg), /* If the argument is Option and not */ - // empty, wrap it in `Some(...)` - _ => arg, // If the argument is not Option, return it as is + .map(|(arg, param)| match (param.type_name.starts_with("Option<"), arg.is_empty()) { + // If the argument is Option and empty, replace it with `None` + (true, true) => "None".to_string(), + // If the argument is Option and not empty, wrap it in `Some(...)` + (true, false) => format!("Some({})", arg), + // If the argument is not Option, return it as is + _ => arg, }) .collect::>()) } @@ -207,7 +354,7 @@ mod tests { assert_eq!(message[2].args[0].label, "new_value".to_string()); assert_eq!(message[2].args[0].type_name, "bool".to_string()); assert_eq!(message[2].args[1].label, "number".to_string()); - assert_eq!(message[2].args[1].type_name, "Option".to_string()); + assert_eq!(message[2].args[1].type_name, "Option: None, Some(u32)".to_string()); Ok(()) } @@ -231,7 +378,7 @@ mod tests { assert_eq!(message.args[0].label, "new_value".to_string()); assert_eq!(message.args[0].type_name, "bool".to_string()); assert_eq!(message.args[1].label, "number".to_string()); - assert_eq!(message.args[1].type_name, "Option".to_string()); + assert_eq!(message.args[1].type_name, "Option: None, Some(u32)".to_string()); Ok(()) } @@ -264,7 +411,7 @@ mod tests { assert_eq!(constructor[1].args[0].label, "init_value".to_string()); assert_eq!(constructor[1].args[0].type_name, "bool".to_string()); assert_eq!(constructor[1].args[1].label, "number".to_string()); - assert_eq!(constructor[1].args[1].type_name, "Option".to_string()); + assert_eq!(constructor[1].args[1].type_name, "Option: None, Some(u32)".to_string()); Ok(()) } @@ -291,7 +438,7 @@ mod tests { assert_eq!(constructor.args[0].label, "init_value".to_string()); assert_eq!(constructor.args[0].type_name, "bool".to_string()); assert_eq!(constructor.args[1].label, "number".to_string()); - assert_eq!(constructor.args[1].type_name, "Option".to_string()); + assert_eq!(constructor.args[1].type_name, "Option: None, Some(u32)".to_string()); Ok(()) } From 80fcc99267b753ee7e743e7d4856d2406c83a24f Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Wed, 11 Sep 2024 13:14:43 +0200 Subject: [PATCH 120/211] feat: pop call parachain prototype --- Cargo.lock | 1 + crates/pop-cli/src/commands/call/mod.rs | 6 + crates/pop-cli/src/commands/call/parachain.rs | 134 ++++++++++++++++++ crates/pop-cli/src/commands/mod.rs | 9 +- crates/pop-parachains/Cargo.toml | 2 + crates/pop-parachains/src/call.rs | 104 ++++++++++++++ crates/pop-parachains/src/errors.rs | 8 ++ crates/pop-parachains/src/lib.rs | 4 + 8 files changed, 267 insertions(+), 1 deletion(-) create mode 100644 crates/pop-cli/src/commands/call/parachain.rs create mode 100644 crates/pop-parachains/src/call.rs diff --git a/Cargo.lock b/Cargo.lock index 3ea832635..ad3ca6795 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4767,6 +4767,7 @@ dependencies = [ "serde_json", "strum 0.26.3", "strum_macros 0.26.4", + "subxt", "symlink", "tar", "tempfile", diff --git a/crates/pop-cli/src/commands/call/mod.rs b/crates/pop-cli/src/commands/call/mod.rs index 6cb137f4b..241ebbdb9 100644 --- a/crates/pop-cli/src/commands/call/mod.rs +++ b/crates/pop-cli/src/commands/call/mod.rs @@ -4,6 +4,8 @@ use clap::{Args, Subcommand}; #[cfg(feature = "contract")] pub(crate) mod contract; +#[cfg(feature = "parachain")] +pub(crate) mod parachain; /// Arguments for calling a smart contract. #[derive(Args)] @@ -16,6 +18,10 @@ pub(crate) struct CallArgs { /// Call a smart contract. #[derive(Subcommand)] pub(crate) enum Command { + /// Call a parachain + #[cfg(feature = "parachain")] + #[clap(alias = "p")] + Parachain(parachain::CallParachainCommand), /// Call a contract #[cfg(feature = "contract")] #[clap(alias = "c")] diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs new file mode 100644 index 000000000..537e7c565 --- /dev/null +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: GPL-3.0 + +use crate::cli::traits::*; +use anyhow::{anyhow, Result}; +use clap::Args; +use pop_parachains::{fetch_metadata, parse_chain_metadata, query, storage_info, Metadata}; + +#[derive(Args, Clone)] +pub struct CallParachainCommand { + /// The name of the pallet to call. + #[clap(long, short)] + pallet: Option, + /// The name of extrinsic to call. + #[clap(long, short)] + extrinsic: Option, + /// The constructor arguments, encoded as strings. + #[clap(long, num_args = 0..)] + args: Vec, + /// Transfers an initial balance to the contract. + #[clap(name = "value", long, default_value = "0")] + value: String, + /// Maximum amount of gas to be used for this command. + /// If not specified it will perform a dry-run to estimate the gas consumed for the + /// instantiation. + #[clap(name = "gas", long)] + gas_limit: Option, + /// Maximum proof size for this command. + /// If not specified it will perform a dry-run to estimate the proof size required. + #[clap(long)] + proof_size: Option, + /// Websocket endpoint of a node. + #[clap(name = "url", long, value_parser, default_value = "ws://localhost:9944")] + url: String, + /// Secret key URI for the account calling the contract. + /// + /// e.g. + /// - for a dev account "//Alice" + /// - with a password "//Alice///SECRET_PASSWORD" + #[clap(name = "suri", long, short, default_value = "//Alice")] + suri: String, +} + +pub(crate) struct CallParachain<'a, CLI: Cli> { + /// The cli to be used. + pub(crate) cli: &'a mut CLI, + /// The args to call. + pub(crate) args: CallParachainCommand, +} + +impl<'a, CLI: Cli> CallParachain<'a, CLI> { + /// Executes the command. + pub(crate) async fn execute(mut self: Box) -> Result<()> { + self.cli.intro("Call a parachain")?; + let metadata = fetch_metadata("wss://rpc1.paseo.popnetwork.xyz").await?; + let call_config = if self.args.pallet.is_none() && self.args.extrinsic.is_none() { + guide_user_to_call_chain(&mut self, metadata).await? + } else { + self.args.clone() + }; + Ok(()) + } +} + +#[derive(Clone, Eq, PartialEq)] +enum Action { + Extrinsic, + Query, +} + +/// Guide the user to call the contract. +async fn guide_user_to_call_chain<'a, CLI: Cli>( + command: &mut CallParachain<'a, CLI>, + metadata: Metadata, +) -> anyhow::Result { + command.cli.intro("Call a contract")?; + + let pallets = match parse_chain_metadata(metadata.clone()).await { + Ok(pallets) => pallets, + Err(e) => { + command.cli.outro_cancel("Unable to fetch the chain metadata.")?; + return Err(anyhow!(format!("{}", e.to_string()))); + }, + }; + let pallet = { + let mut prompt = command.cli.select("Select the pallet to call:"); + for pallet_item in pallets { + prompt = prompt.item(pallet_item.clone(), &pallet_item.label, &pallet_item.docs); + } + prompt.interact()? + }; + let action = command + .cli + .select("What do you want to do?") + .item(Action::Extrinsic, "Submit an extrinsic", "hint") + .item(Action::Query, "Query storage", "hint") + .interact()?; + + if action == Action::Extrinsic { + let extrinsic = { + let mut prompt_extrinsic = command.cli.select("Select the extrinsic to call:"); + for extrinsic in pallet.extrinsics { + prompt_extrinsic = prompt_extrinsic.item( + extrinsic.clone(), + &extrinsic.name, + &extrinsic.docs.concat(), + ); + } + prompt_extrinsic.interact()? + }; + } else { + let storage = { + let mut prompt_storage = command.cli.select("Select the storage to query:"); + for storage in pallet.storage { + prompt_storage = prompt_storage.item(storage.clone(), &storage.name, &storage.docs); + } + prompt_storage.interact()? + }; + let a = storage_info(&pallet.label, &storage.name, &metadata)?; + query(&pallet.label, &storage.name, vec![], "wss://rpc1.paseo.popnetwork.xyz").await?; + } + // println!("Selected pallet: {:?}", pallet.label); + // println!("ext: {:?}", pallet.extrinsics); + + Ok(CallParachainCommand { + pallet: Some("pallet".to_string()), + extrinsic: Some("extrinsic".to_string()), + args: vec!["".to_string()], + value: "0".to_string(), + gas_limit: None, + proof_size: None, + url: "wss://rpc2.paseo.popnetwork.xyz".to_string(), + suri: "//Alice".to_string(), + }) +} diff --git a/crates/pop-cli/src/commands/mod.rs b/crates/pop-cli/src/commands/mod.rs index 34c2f10b3..742c96342 100644 --- a/crates/pop-cli/src/commands/mod.rs +++ b/crates/pop-cli/src/commands/mod.rs @@ -96,8 +96,15 @@ impl Command { build::Command::Spec(cmd) => cmd.execute().await.map(|_| Value::Null), }, }, - #[cfg(feature = "contract")] Self::Call(args) => match args.command { + #[cfg(feature = "parachain")] + call::Command::Parachain(cmd) => { + Box::new(call::parachain::CallParachain { cli: &mut Cli, args: cmd }) + .execute() + .await + .map(|_| Value::Null) + }, + #[cfg(feature = "contract")] call::Command::Contract(cmd) => cmd.execute().await.map(|_| Value::Null), }, #[cfg(any(feature = "parachain", feature = "contract"))] diff --git a/crates/pop-parachains/Cargo.toml b/crates/pop-parachains/Cargo.toml index dd6406824..e73a7fa75 100644 --- a/crates/pop-parachains/Cargo.toml +++ b/crates/pop-parachains/Cargo.toml @@ -26,6 +26,8 @@ url.workspace = true askama.workspace = true indexmap.workspace = true reqwest.workspace = true +scale-info.workspace = true +subxt.workspace = true symlink.workspace = true toml_edit.workspace = true walkdir.workspace = true diff --git a/crates/pop-parachains/src/call.rs b/crates/pop-parachains/src/call.rs new file mode 100644 index 000000000..840541ca4 --- /dev/null +++ b/crates/pop-parachains/src/call.rs @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-3.0 + +// use subxt_codegen::fetch_metadata; +// use subxt_metadata::Metadata; +use crate::errors::Error; +use scale_info::{form::PortableForm, PortableRegistry, Variant}; +use subxt::{ + dynamic::Value, + error::MetadataError, + metadata::types::{PalletMetadata, StorageEntryMetadata, StorageMetadata}, + Metadata, OnlineClient, SubstrateConfig, +}; + +#[derive(Clone, PartialEq, Eq)] +pub struct Storage { + pub name: String, + pub docs: String, +} + +#[derive(Clone, PartialEq, Eq)] +/// Describes a contract message. +pub struct Pallet { + /// The label of the message. + pub label: String, + /// The message documentation. + pub docs: String, + // The extrinsics of the pallet. + pub extrinsics: Vec>, + // The storage of the pallet. + pub storage: Vec, + // /// The constants of the pallet. + // pub consts: Vec, +} + +pub async fn fetch_metadata(url: &str) -> Result { + let api = OnlineClient::::from_url(url).await?; + Ok(api.metadata()) +} + +pub async fn query( + pallet_name: &str, + entry_name: &str, + args: Vec, + url: &str, +) -> Result<(), Error> { + let api = OnlineClient::::from_url(url).await?; + println!("here"); + let storage_query = subxt::dynamic::storage(pallet_name, entry_name, args); + println!("here"); + let mut results = api.storage().at_latest().await?.iter(storage_query).await?; + println!("{:?}", results); + while let Some(Ok(kv)) = results.next().await { + println!("Keys decoded: {:?}", kv.keys); + println!("Value: {:?}", kv.value.to_value().map_err(|_| Error::ParsingResponseError)?); + } + Ok(()) +} + +pub async fn parse_chain_metadata(metadata: Metadata) -> Result, Error> { + let mut pallets: Vec = Vec::new(); + for pallet in metadata.pallets() { + let extrinsics = + pallet.call_variants().map(|variants| variants.to_vec()).unwrap_or_default(); // Return an empty Vec if Option is None + let storage: Vec = pallet + .storage() + .map(|metadata| { + metadata + .entries() + .iter() + .map(|entry| Storage { + name: entry.name().to_string(), + docs: entry.docs().concat(), + }) + .collect() + }) + .unwrap_or_default(); // Return an empty Vec if Option is None + + pallets.push(Pallet { + label: pallet.name().to_string(), + extrinsics, + docs: pallet.docs().join(" "), + storage, + }); + } + Ok(pallets) +} + +/// Return details about the given storage entry. +pub fn storage_info<'a>( + pallet_name: &str, + entry_name: &str, + metadata: &'a Metadata, +) -> Result<&'a StorageEntryMetadata, Error> { + let pallet_metadata = metadata + .pallet_by_name(pallet_name) + .ok_or(Error::PalletNotFound(pallet_name.to_string()))?; + let storage_metadata = pallet_metadata + .storage() + .ok_or_else(|| MetadataError::StorageNotFoundInPallet(pallet_name.to_owned()))?; + let storage_entry = storage_metadata + .entry_by_name(entry_name) + .ok_or_else(|| MetadataError::StorageEntryNotFound(entry_name.to_owned()))?; + Ok(storage_entry) +} diff --git a/crates/pop-parachains/src/errors.rs b/crates/pop-parachains/src/errors.rs index 3eff7bbfc..7e9e8e19d 100644 --- a/crates/pop-parachains/src/errors.rs +++ b/crates/pop-parachains/src/errors.rs @@ -21,6 +21,8 @@ pub enum Error { IO(#[from] std::io::Error), #[error("JSON error: {0}")] JsonError(#[from] serde_json::Error), + #[error("Metadata error: {0}")] + MetadataError(#[from] subxt::error::MetadataError), #[error("Missing binary: {0}")] MissingBinary(String), #[error("Missing chain spec file at: {0}")] @@ -31,12 +33,18 @@ pub enum Error { OrchestratorError(#[from] OrchestratorError), #[error("Failed to create pallet directory")] PalletDirCreation, + #[error("Failed to find the pallet {0}")] + PalletNotFound(String), + #[error("Failed to parse the response")] + ParsingResponseError, #[error("Invalid path")] PathError, #[error("Failed to execute rustfmt")] RustfmtError(std::io::Error), #[error("Template error: {0}")] SourcingError(#[from] pop_common::sourcing::Error), + #[error("{0}")] + SubxtError(#[from] subxt::Error), #[error("Toml error: {0}")] TomlError(#[from] toml_edit::de::Error), #[error("Unsupported command: {0}")] diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index 06bee8f4a..37f4c64e8 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -2,6 +2,7 @@ #![doc = include_str!("../README.md")] mod build; +mod call; mod errors; mod generator; mod new_pallet; @@ -14,10 +15,13 @@ pub use build::{ binary_path, build_parachain, export_wasm_file, generate_genesis_state_file, generate_plain_chain_spec, generate_raw_chain_spec, is_supported, ChainSpec, }; +pub use call::{fetch_metadata, parse_chain_metadata, query, storage_info}; pub use errors::Error; pub use indexmap::IndexSet; pub use new_pallet::{create_pallet_template, new_pallet_options::*, TemplatePalletConfig}; pub use new_parachain::instantiate_template_dir; +// External export from subxt. +pub use subxt::Metadata; pub use templates::{Config, Parachain, Provider}; pub use up::Zombienet; pub use utils::helpers::is_initial_endowment_valid; From 0079514e442f94eb040f0c506a4fc6824c1d5a85 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Wed, 11 Sep 2024 18:00:13 +0200 Subject: [PATCH 121/211] feat: dispaly arguments of extrinsic --- crates/pop-cli/src/commands/call/parachain.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 537e7c565..21bc13795 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -73,6 +73,13 @@ async fn guide_user_to_call_chain<'a, CLI: Cli>( metadata: Metadata, ) -> anyhow::Result { command.cli.intro("Call a contract")?; + // Prompt for contract location. + let url: String = command + .cli + .input("Which chain would you like to interact with?") + .placeholder("wss://rpc1.paseo.popnetwork.xyz") + .default_input("wss://rpc1.paseo.popnetwork.xyz") + .interact()?; let pallets = match parse_chain_metadata(metadata.clone()).await { Ok(pallets) => pallets, @@ -107,6 +114,18 @@ async fn guide_user_to_call_chain<'a, CLI: Cli>( } prompt_extrinsic.interact()? }; + let mut args = Vec::new(); + for argument in extrinsic.fields { + let value = command + .cli + .input(&format!( + "Enter the value for the argument '{}':", + argument.name.unwrap_or_default() + )) + .interact()?; + args.push(value); + } + println!("Extrinsic to submit: {:?} with args {:?}", extrinsic.name, args); } else { let storage = { let mut prompt_storage = command.cli.select("Select the storage to query:"); From b18cb4ee98455b7f180de4fcbce7741a7f345a78 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 13 Sep 2024 17:30:02 +0200 Subject: [PATCH 122/211] refactor: structure similar to pop call contract --- crates/pop-cli/src/commands/call/parachain.rs | 68 ++++++++++++------- crates/pop-cli/src/commands/mod.rs | 10 ++- 2 files changed, 48 insertions(+), 30 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 21bc13795..68ecc2025 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -16,22 +16,10 @@ pub struct CallParachainCommand { /// The constructor arguments, encoded as strings. #[clap(long, num_args = 0..)] args: Vec, - /// Transfers an initial balance to the contract. - #[clap(name = "value", long, default_value = "0")] - value: String, - /// Maximum amount of gas to be used for this command. - /// If not specified it will perform a dry-run to estimate the gas consumed for the - /// instantiation. - #[clap(name = "gas", long)] - gas_limit: Option, - /// Maximum proof size for this command. - /// If not specified it will perform a dry-run to estimate the proof size required. - #[clap(long)] - proof_size: Option, /// Websocket endpoint of a node. #[clap(name = "url", long, value_parser, default_value = "ws://localhost:9944")] url: String, - /// Secret key URI for the account calling the contract. + /// Secret key URI for the account signing the extrinsic. /// /// e.g. /// - for a dev account "//Alice" @@ -39,6 +27,22 @@ pub struct CallParachainCommand { #[clap(name = "suri", long, short, default_value = "//Alice")] suri: String, } +impl CallParachainCommand { + fn display(&self) -> String { + let mut full_message = format!("pop call parachain"); + if let Some(pallet) = &self.pallet { + full_message.push_str(&format!(" --pallet {}", pallet)); + } + if let Some(extrinsic) = &self.extrinsic { + full_message.push_str(&format!(" --extrinsic {}", extrinsic)); + } + if !self.args.is_empty() { + full_message.push_str(&format!(" --args {}", self.args.join(" "))); + } + full_message.push_str(&format!(" --url {} --suri {}", self.url, self.suri)); + full_message + } +} pub(crate) struct CallParachain<'a, CLI: Cli> { /// The cli to be used. @@ -49,7 +53,7 @@ pub(crate) struct CallParachain<'a, CLI: Cli> { impl<'a, CLI: Cli> CallParachain<'a, CLI> { /// Executes the command. - pub(crate) async fn execute(mut self: Box) -> Result<()> { + pub(crate) async fn execute(mut self: Self) -> Result<()> { self.cli.intro("Call a parachain")?; let metadata = fetch_metadata("wss://rpc1.paseo.popnetwork.xyz").await?; let call_config = if self.args.pallet.is_none() && self.args.extrinsic.is_none() { @@ -57,6 +61,27 @@ impl<'a, CLI: Cli> CallParachain<'a, CLI> { } else { self.args.clone() }; + match self.execute_extrinsic(call_config.clone()).await { + Ok(_) => Ok(()), + Err(e) => { + self.cli.outro_cancel(format!("{}", e.to_string()))?; + return Ok(()); + }, + } + } + /// Executes the extrinsic or query. + async fn execute_extrinsic(&mut self, call_config: CallParachainCommand) -> Result<()> { + let pallet = call_config + .pallet + .clone() + .expect("pallet can not be none as fallback above is interactive input; qed"); + let extrinsic = call_config + .extrinsic + .clone() + .expect("extrinsic can not be none as fallback above is interactive input; qed"); + // TODO: Check if exists? + // println!("Extrinsic to submit: {:?} with args {:?}", extrinsic.name, args); + // query(&pallet.label, &storage.name, vec![], "wss://rpc1.paseo.popnetwork.xyz").await?; Ok(()) } } @@ -102,8 +127,10 @@ async fn guide_user_to_call_chain<'a, CLI: Cli>( .item(Action::Query, "Query storage", "hint") .interact()?; + let mut extrinsic; + let mut args = Vec::new(); if action == Action::Extrinsic { - let extrinsic = { + extrinsic = { let mut prompt_extrinsic = command.cli.select("Select the extrinsic to call:"); for extrinsic in pallet.extrinsics { prompt_extrinsic = prompt_extrinsic.item( @@ -114,7 +141,6 @@ async fn guide_user_to_call_chain<'a, CLI: Cli>( } prompt_extrinsic.interact()? }; - let mut args = Vec::new(); for argument in extrinsic.fields { let value = command .cli @@ -125,7 +151,6 @@ async fn guide_user_to_call_chain<'a, CLI: Cli>( .interact()?; args.push(value); } - println!("Extrinsic to submit: {:?} with args {:?}", extrinsic.name, args); } else { let storage = { let mut prompt_storage = command.cli.select("Select the storage to query:"); @@ -135,18 +160,13 @@ async fn guide_user_to_call_chain<'a, CLI: Cli>( prompt_storage.interact()? }; let a = storage_info(&pallet.label, &storage.name, &metadata)?; - query(&pallet.label, &storage.name, vec![], "wss://rpc1.paseo.popnetwork.xyz").await?; + println!("STORAGE {:?}", a); } - // println!("Selected pallet: {:?}", pallet.label); - // println!("ext: {:?}", pallet.extrinsics); Ok(CallParachainCommand { - pallet: Some("pallet".to_string()), + pallet: Some(pallet.label), extrinsic: Some("extrinsic".to_string()), args: vec!["".to_string()], - value: "0".to_string(), - gas_limit: None, - proof_size: None, url: "wss://rpc2.paseo.popnetwork.xyz".to_string(), suri: "//Alice".to_string(), }) diff --git a/crates/pop-cli/src/commands/mod.rs b/crates/pop-cli/src/commands/mod.rs index 742c96342..9e8b0c406 100644 --- a/crates/pop-cli/src/commands/mod.rs +++ b/crates/pop-cli/src/commands/mod.rs @@ -98,12 +98,10 @@ impl Command { }, Self::Call(args) => match args.command { #[cfg(feature = "parachain")] - call::Command::Parachain(cmd) => { - Box::new(call::parachain::CallParachain { cli: &mut Cli, args: cmd }) - .execute() - .await - .map(|_| Value::Null) - }, + call::Command::Parachain(cmd) => call::parachain::CallParachain { cli: &mut Cli, args: cmd } + .execute() + .await + .map(|_| Value::Null), #[cfg(feature = "contract")] call::Command::Contract(cmd) => cmd.execute().await.map(|_| Value::Null), }, From d5929f986f6e53fdd1f3068e0344bd3cc20f0c68 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 19 Sep 2024 18:01:13 +0200 Subject: [PATCH 123/211] feat: parse all values for extrinsic/storage --- Cargo.lock | 24 +++ crates/pop-cli/src/commands/call/parachain.rs | 162 ++++++++++-------- crates/pop-cli/src/commands/mod.rs | 5 +- crates/pop-parachains/Cargo.toml | 1 + crates/pop-parachains/src/call.rs | 75 +++++--- crates/pop-parachains/src/lib.rs | 2 +- 6 files changed, 160 insertions(+), 109 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ad3ca6795..88549a863 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4417,6 +4417,12 @@ dependencies = [ "password-hash", ] +[[package]] +name = "peekmore" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9163e1259760e83d528d1b3171e5100c1767f10c52e1c4d6afad26e63d47d758" + [[package]] name = "pem" version = "3.0.4" @@ -5695,6 +5701,24 @@ dependencies = [ "thiserror", ] +[[package]] +name = "scale-typegen-description" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ff4cabffc407f6378bc7a164fab8280bfcd862b2dd063cc5c9914a520ea8566" +dependencies = [ + "anyhow", + "peekmore", + "proc-macro2", + "quote", + "rand", + "rand_chacha", + "scale-info", + "scale-typegen", + "scale-value", + "smallvec", +] + [[package]] name = "scale-value" version = "0.16.2" diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 68ecc2025..db2b25cf6 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-3.0 -use crate::cli::traits::*; +use crate::cli::{self, traits::*}; use anyhow::{anyhow, Result}; use clap::Args; -use pop_parachains::{fetch_metadata, parse_chain_metadata, query, storage_info, Metadata}; +use pop_parachains::{fetch_metadata, get_type_description, parse_chain_metadata, Metadata}; #[derive(Args, Clone)] pub struct CallParachainCommand { @@ -11,8 +11,11 @@ pub struct CallParachainCommand { #[clap(long, short)] pallet: Option, /// The name of extrinsic to call. - #[clap(long, short)] + #[clap(long, short, conflicts_with = "query")] extrinsic: Option, + /// The name of storage to query. + #[clap(long, short, conflicts_with = "extrinsic")] + query: Option, /// The constructor arguments, encoded as strings. #[clap(long, num_args = 0..)] args: Vec, @@ -28,6 +31,38 @@ pub struct CallParachainCommand { suri: String, } impl CallParachainCommand { + /// Executes the command. + pub(crate) async fn execute(mut self) -> Result<()> { + let metadata = self.query_metadata(&mut cli::Cli).await?; + let call_config = + if self.pallet.is_none() && (self.extrinsic.is_none() || self.query.is_none()) { + guide_user_to_call_chain(&mut cli::Cli, metadata).await? + } else { + self.clone() + }; + execute_extrinsic(call_config.clone(), &mut cli::Cli).await?; + Ok(()) + } + ///Parse metadata. + async fn query_metadata( + &mut self, + cli: &mut impl cli::traits::Cli, + ) -> anyhow::Result { + cli.intro("Call a parachain")?; + let url: String = + if self.pallet.is_none() && (self.extrinsic.is_none() || self.query.is_none()) { + // Prompt for contract location. + cli.input("Which chain would you like to interact with?") + .placeholder("wss://rpc1.paseo.popnetwork.xyz") + .default_input("wss://rpc1.paseo.popnetwork.xyz") + .interact()? + } else { + self.url.clone() + }; + let metadata = fetch_metadata(&url).await?; + Ok(metadata) + } + fn display(&self) -> String { let mut full_message = format!("pop call parachain"); if let Some(pallet) = &self.pallet { @@ -36,6 +71,9 @@ impl CallParachainCommand { if let Some(extrinsic) = &self.extrinsic { full_message.push_str(&format!(" --extrinsic {}", extrinsic)); } + if let Some(query) = &self.query { + full_message.push_str(&format!(" --query {}", query)); + } if !self.args.is_empty() { full_message.push_str(&format!(" --args {}", self.args.join(" "))); } @@ -44,48 +82,6 @@ impl CallParachainCommand { } } -pub(crate) struct CallParachain<'a, CLI: Cli> { - /// The cli to be used. - pub(crate) cli: &'a mut CLI, - /// The args to call. - pub(crate) args: CallParachainCommand, -} - -impl<'a, CLI: Cli> CallParachain<'a, CLI> { - /// Executes the command. - pub(crate) async fn execute(mut self: Self) -> Result<()> { - self.cli.intro("Call a parachain")?; - let metadata = fetch_metadata("wss://rpc1.paseo.popnetwork.xyz").await?; - let call_config = if self.args.pallet.is_none() && self.args.extrinsic.is_none() { - guide_user_to_call_chain(&mut self, metadata).await? - } else { - self.args.clone() - }; - match self.execute_extrinsic(call_config.clone()).await { - Ok(_) => Ok(()), - Err(e) => { - self.cli.outro_cancel(format!("{}", e.to_string()))?; - return Ok(()); - }, - } - } - /// Executes the extrinsic or query. - async fn execute_extrinsic(&mut self, call_config: CallParachainCommand) -> Result<()> { - let pallet = call_config - .pallet - .clone() - .expect("pallet can not be none as fallback above is interactive input; qed"); - let extrinsic = call_config - .extrinsic - .clone() - .expect("extrinsic can not be none as fallback above is interactive input; qed"); - // TODO: Check if exists? - // println!("Extrinsic to submit: {:?} with args {:?}", extrinsic.name, args); - // query(&pallet.label, &storage.name, vec![], "wss://rpc1.paseo.popnetwork.xyz").await?; - Ok(()) - } -} - #[derive(Clone, Eq, PartialEq)] enum Action { Extrinsic, @@ -93,45 +89,34 @@ enum Action { } /// Guide the user to call the contract. -async fn guide_user_to_call_chain<'a, CLI: Cli>( - command: &mut CallParachain<'a, CLI>, +async fn guide_user_to_call_chain( + cli: &mut impl cli::traits::Cli, metadata: Metadata, ) -> anyhow::Result { - command.cli.intro("Call a contract")?; - // Prompt for contract location. - let url: String = command - .cli - .input("Which chain would you like to interact with?") - .placeholder("wss://rpc1.paseo.popnetwork.xyz") - .default_input("wss://rpc1.paseo.popnetwork.xyz") - .interact()?; - let pallets = match parse_chain_metadata(metadata.clone()).await { Ok(pallets) => pallets, Err(e) => { - command.cli.outro_cancel("Unable to fetch the chain metadata.")?; + cli.outro_cancel("Unable to fetch the chain metadata.")?; return Err(anyhow!(format!("{}", e.to_string()))); }, }; let pallet = { - let mut prompt = command.cli.select("Select the pallet to call:"); + let mut prompt = cli.select("Select the pallet to call:"); for pallet_item in pallets { prompt = prompt.item(pallet_item.clone(), &pallet_item.label, &pallet_item.docs); } prompt.interact()? }; - let action = command - .cli + let action = cli .select("What do you want to do?") .item(Action::Extrinsic, "Submit an extrinsic", "hint") .item(Action::Query, "Query storage", "hint") .interact()?; - let mut extrinsic; let mut args = Vec::new(); if action == Action::Extrinsic { - extrinsic = { - let mut prompt_extrinsic = command.cli.select("Select the extrinsic to call:"); + let extrinsic = { + let mut prompt_extrinsic = cli.select("Select the extrinsic to call:"); for extrinsic in pallet.extrinsics { prompt_extrinsic = prompt_extrinsic.item( extrinsic.clone(), @@ -142,8 +127,7 @@ async fn guide_user_to_call_chain<'a, CLI: Cli>( prompt_extrinsic.interact()? }; for argument in extrinsic.fields { - let value = command - .cli + let value = cli .input(&format!( "Enter the value for the argument '{}':", argument.name.unwrap_or_default() @@ -151,23 +135,49 @@ async fn guide_user_to_call_chain<'a, CLI: Cli>( .interact()?; args.push(value); } + Ok(CallParachainCommand { + pallet: Some(pallet.label), + extrinsic: Some(extrinsic.name), + query: None, + args, + url: "wss://rpc2.paseo.popnetwork.xyz".to_string(), + suri: "//Alice".to_string(), + }) } else { - let storage = { - let mut prompt_storage = command.cli.select("Select the storage to query:"); + let query = { + let mut prompt_storage = cli.select("Select the storage to query:"); for storage in pallet.storage { prompt_storage = prompt_storage.item(storage.clone(), &storage.name, &storage.docs); } prompt_storage.interact()? }; - let a = storage_info(&pallet.label, &storage.name, &metadata)?; - println!("STORAGE {:?}", a); + let keys_needed = get_type_description(query.ty.1, &metadata)?; + for key in keys_needed { + let value = cli.input(&format!("Enter the key '{}':", key)).interact()?; + args.push(value); + } + Ok(CallParachainCommand { + pallet: Some(pallet.label), + extrinsic: None, + query: Some(query.name), + args, + url: "wss://rpc2.paseo.popnetwork.xyz".to_string(), + suri: "//Alice".to_string(), + }) } +} - Ok(CallParachainCommand { - pallet: Some(pallet.label), - extrinsic: Some("extrinsic".to_string()), - args: vec!["".to_string()], - url: "wss://rpc2.paseo.popnetwork.xyz".to_string(), - suri: "//Alice".to_string(), - }) +/// Executes the extrinsic or query. +async fn execute_extrinsic( + call_config: CallParachainCommand, + cli: &mut impl cli::traits::Cli, +) -> Result<()> { + cli.info(call_config.display())?; + // TODO: Check if exists? + if call_config.extrinsic.is_some() { + //self.execute_extrinsic(call_config.clone(), &mut cli::Cli).await?; + } else { + //self.execute_query(call_config.clone(), &mut cli::Cli).await?; + } + Ok(()) } diff --git a/crates/pop-cli/src/commands/mod.rs b/crates/pop-cli/src/commands/mod.rs index 9e8b0c406..9f00a8bdb 100644 --- a/crates/pop-cli/src/commands/mod.rs +++ b/crates/pop-cli/src/commands/mod.rs @@ -98,10 +98,7 @@ impl Command { }, Self::Call(args) => match args.command { #[cfg(feature = "parachain")] - call::Command::Parachain(cmd) => call::parachain::CallParachain { cli: &mut Cli, args: cmd } - .execute() - .await - .map(|_| Value::Null), + call::Command::Parachain(cmd) => cmd.execute().await.map(|_| Value::Null), #[cfg(feature = "contract")] call::Command::Contract(cmd) => cmd.execute().await.map(|_| Value::Null), }, diff --git a/crates/pop-parachains/Cargo.toml b/crates/pop-parachains/Cargo.toml index e73a7fa75..83542043d 100644 --- a/crates/pop-parachains/Cargo.toml +++ b/crates/pop-parachains/Cargo.toml @@ -27,6 +27,7 @@ askama.workspace = true indexmap.workspace = true reqwest.workspace = true scale-info.workspace = true +scale-typegen-description.workspace = true subxt.workspace = true symlink.workspace = true toml_edit.workspace = true diff --git a/crates/pop-parachains/src/call.rs b/crates/pop-parachains/src/call.rs index 840541ca4..d5c12099a 100644 --- a/crates/pop-parachains/src/call.rs +++ b/crates/pop-parachains/src/call.rs @@ -1,20 +1,17 @@ // SPDX-License-Identifier: GPL-3.0 -// use subxt_codegen::fetch_metadata; -// use subxt_metadata::Metadata; use crate::errors::Error; -use scale_info::{form::PortableForm, PortableRegistry, Variant}; +use scale_info::{form::PortableForm, Variant}; +use scale_typegen_description::type_description; use subxt::{ - dynamic::Value, - error::MetadataError, - metadata::types::{PalletMetadata, StorageEntryMetadata, StorageMetadata}, - Metadata, OnlineClient, SubstrateConfig, + dynamic::Value, metadata::types::StorageEntryType, Metadata, OnlineClient, SubstrateConfig, }; #[derive(Clone, PartialEq, Eq)] pub struct Storage { pub name: String, pub docs: String, + pub ty: (u32, Option), } #[derive(Clone, PartialEq, Eq)] @@ -28,8 +25,6 @@ pub struct Pallet { pub extrinsics: Vec>, // The storage of the pallet. pub storage: Vec, - // /// The constants of the pallet. - // pub consts: Vec, } pub async fn fetch_metadata(url: &str) -> Result { @@ -63,13 +58,17 @@ pub async fn parse_chain_metadata(metadata: Metadata) -> Result, Err pallet.call_variants().map(|variants| variants.to_vec()).unwrap_or_default(); // Return an empty Vec if Option is None let storage: Vec = pallet .storage() - .map(|metadata| { - metadata - .entries() + .map(|m| { + m.entries() .iter() .map(|entry| Storage { name: entry.name().to_string(), docs: entry.docs().concat(), + ty: match entry.entry_type() { + StorageEntryType::Plain(value) => (*value, None), + StorageEntryType::Map { value_ty, key_ty, .. } => + (*value_ty, Some(*key_ty)), + }, }) .collect() }) @@ -85,20 +84,40 @@ pub async fn parse_chain_metadata(metadata: Metadata) -> Result, Err Ok(pallets) } -/// Return details about the given storage entry. -pub fn storage_info<'a>( - pallet_name: &str, - entry_name: &str, - metadata: &'a Metadata, -) -> Result<&'a StorageEntryMetadata, Error> { - let pallet_metadata = metadata - .pallet_by_name(pallet_name) - .ok_or(Error::PalletNotFound(pallet_name.to_string()))?; - let storage_metadata = pallet_metadata - .storage() - .ok_or_else(|| MetadataError::StorageNotFoundInPallet(pallet_name.to_owned()))?; - let storage_entry = storage_metadata - .entry_by_name(entry_name) - .ok_or_else(|| MetadataError::StorageEntryNotFound(entry_name.to_owned()))?; - Ok(storage_entry) +pub fn get_type_description( + key_ty_id: Option, + metadata: &Metadata, +) -> Result, Error> { + if let Some(key_ty_id) = key_ty_id { + let key_ty_description = type_description(key_ty_id, metadata.types(), false)?; + let result = key_ty_description.trim().trim_matches(|c| c == '(' || c == ')'); + + let parsed_result: Vec = if result == "\"\"" { + vec![] + } else if !result.contains(',') { + vec![result.to_string()] + } else { + result.split(',').map(|s| s.trim().to_string()).collect() + }; + + Ok(parsed_result) + } else { + Ok(vec![]) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use anyhow::Result; + + #[tokio::test] + async fn storage_info_works() -> Result<()> { + let metadata = fetch_metadata("wss://rpc2.paseo.popnetwork.xyz").await?; + explore("Nfts", "Account", &metadata)?; + explore("Nfts", "Collection", &metadata)?; + explore("Nfts", "NextCollectionId", &metadata)?; + + Ok(()) + } } diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index 37f4c64e8..614aa377b 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -15,7 +15,7 @@ pub use build::{ binary_path, build_parachain, export_wasm_file, generate_genesis_state_file, generate_plain_chain_spec, generate_raw_chain_spec, is_supported, ChainSpec, }; -pub use call::{fetch_metadata, parse_chain_metadata, query, storage_info}; +pub use call::{fetch_metadata, get_type_description, parse_chain_metadata, query}; pub use errors::Error; pub use indexmap::IndexSet; pub use new_pallet::{create_pallet_template, new_pallet_options::*, TemplatePalletConfig}; From fecac84e5a2191444db326eb02de364d47505a0a Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Sat, 21 Sep 2024 09:29:54 +0200 Subject: [PATCH 124/211] refactor: signer in common --- Cargo.lock | 9 +- Cargo.toml | 4 + crates/pop-cli/src/commands/call/parachain.rs | 38 ++++- crates/pop-common/Cargo.toml | 2 + crates/pop-common/src/errors.rs | 4 + crates/pop-common/src/lib.rs | 15 +- .../src/utils => pop-common/src}/signer.rs | 24 +--- crates/pop-contracts/Cargo.toml | 2 - crates/pop-contracts/src/build.rs | 2 +- crates/pop-contracts/src/call.rs | 7 +- crates/pop-contracts/src/errors.rs | 4 - crates/pop-contracts/src/lib.rs | 3 +- crates/pop-contracts/src/new.rs | 2 +- crates/pop-contracts/src/up.rs | 7 +- crates/pop-contracts/src/utils/helpers.rs | 112 --------------- crates/pop-contracts/src/utils/mod.rs | 134 +++++++++++++++++- crates/pop-parachains/Cargo.toml | 1 + crates/pop-parachains/src/call.rs | 80 ++++++++--- crates/pop-parachains/src/errors.rs | 3 +- crates/pop-parachains/src/lib.rs | 6 +- 20 files changed, 269 insertions(+), 190 deletions(-) rename crates/{pop-contracts/src/utils => pop-common/src}/signer.rs (55%) delete mode 100644 crates/pop-contracts/src/utils/helpers.rs diff --git a/Cargo.lock b/Cargo.lock index 88549a863..224e6aca0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4718,6 +4718,8 @@ dependencies = [ "serde_json", "strum 0.26.3", "strum_macros 0.26.4", + "subxt", + "subxt-signer", "tar", "tempfile", "thiserror", @@ -4746,8 +4748,6 @@ dependencies = [ "sp-weights", "strum 0.26.3", "strum_macros 0.26.4", - "subxt", - "subxt-signer", "tar", "tempfile", "thiserror", @@ -4769,7 +4769,10 @@ dependencies = [ "indexmap 2.5.0", "mockito", "pop-common", - "reqwest 0.12.7", + "reqwest 0.12.5", + "scale-info", + "scale-typegen-description", + "scale-value", "serde_json", "strum 0.26.3", "strum_macros 0.26.4", diff --git a/Cargo.toml b/Cargo.toml index 30f4ac63f..e4845c308 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,10 @@ sp-core = "31" sp-weights = "30" contract-build = "5.0.0-alpha" contract-extrinsics = "5.0.0-alpha" +contract-transcode = "5.0.0-alpha" +scale-info = { version = "2.11.3", default-features = false, features = ["derive"] } +scale-typegen-description = "0.8.0" +scale-value = { version = "0.16.2", default-features = false } heck = "0.5.0" # parachains diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index db2b25cf6..74dad9cc3 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -3,7 +3,9 @@ use crate::cli::{self, traits::*}; use anyhow::{anyhow, Result}; use clap::Args; -use pop_parachains::{fetch_metadata, get_type_description, parse_chain_metadata, Metadata}; +use pop_parachains::{ + fetch_metadata, get_type_description, parse_chain_metadata, query, submit_extrinsic, Metadata, +}; #[derive(Args, Clone)] pub struct CallParachainCommand { @@ -64,7 +66,7 @@ impl CallParachainCommand { } fn display(&self) -> String { - let mut full_message = format!("pop call parachain"); + let mut full_message = "pop call parachain".to_string(); if let Some(pallet) = &self.pallet { full_message.push_str(&format!(" --pallet {}", pallet)); } @@ -120,8 +122,8 @@ async fn guide_user_to_call_chain( for extrinsic in pallet.extrinsics { prompt_extrinsic = prompt_extrinsic.item( extrinsic.clone(), - &extrinsic.name, - &extrinsic.docs.concat(), + format!("{}\n", &extrinsic.name), + extrinsic.docs.concat(), ); } prompt_extrinsic.interact()? @@ -132,6 +134,12 @@ async fn guide_user_to_call_chain( "Enter the value for the argument '{}':", argument.name.unwrap_or_default() )) + .placeholder(&format!( + "{} - {}", + argument.docs.join(","), + argument.type_name.unwrap_or_default() + )) + .required(false) .interact()?; args.push(value); } @@ -173,11 +181,29 @@ async fn execute_extrinsic( cli: &mut impl cli::traits::Cli, ) -> Result<()> { cli.info(call_config.display())?; + let pallet = call_config + .pallet + .expect("pallet can not be none as fallback above is interactive input; qed"); // TODO: Check if exists? if call_config.extrinsic.is_some() { - //self.execute_extrinsic(call_config.clone(), &mut cli::Cli).await?; + let extrinsic = call_config + .extrinsic + .expect("storage can not be none as fallback above is interactive input; qed"); + let result = submit_extrinsic( + &pallet, + &extrinsic, + call_config.args, + &call_config.url, + &call_config.suri, + ) + .await?; + cli.outro(format!("Extrinsic submitted successfully with hash: {}", result))?; } else { - //self.execute_query(call_config.clone(), &mut cli::Cli).await?; + let storage = call_config + .query + .expect("storage can not be none as fallback above is interactive input; qed"); + let result = query(&pallet, &storage, call_config.args, &call_config.url).await?; + cli.outro(result)?; } Ok(()) } diff --git a/crates/pop-common/Cargo.toml b/crates/pop-common/Cargo.toml index 108399db3..5c24e35a3 100644 --- a/crates/pop-common/Cargo.toml +++ b/crates/pop-common/Cargo.toml @@ -19,6 +19,8 @@ reqwest.workspace = true serde_json.workspace = true serde.workspace = true strum.workspace = true +subxt.workspace = true +subxt-signer.workspace = true tar.workspace = true tempfile.workspace = true thiserror.workspace = true diff --git a/crates/pop-common/src/errors.rs b/crates/pop-common/src/errors.rs index 02fb0c9b1..7598cd543 100644 --- a/crates/pop-common/src/errors.rs +++ b/crates/pop-common/src/errors.rs @@ -13,12 +13,16 @@ pub enum Error { Git(String), #[error("IO error: {0}")] IO(#[from] std::io::Error), + #[error("Failed to create keypair from URI: {0}")] + KeyPairCreation(String), #[error("Failed to get manifest path: {0}")] ManifestPath(String), #[error("Manifest error: {0}")] ManifestError(#[from] cargo_toml::Error), #[error("ParseError error: {0}")] ParseError(#[from] url::ParseError), + #[error("Failed to parse secret URI: {0}")] + ParseSecretURI(String), #[error("SourceError error: {0}")] SourceError(#[from] sourcing::Error), #[error("TemplateError error: {0}")] diff --git a/crates/pop-common/src/lib.rs b/crates/pop-common/src/lib.rs index bf2d6632a..cf82b7c67 100644 --- a/crates/pop-common/src/lib.rs +++ b/crates/pop-common/src/lib.rs @@ -4,6 +4,7 @@ pub mod git; pub mod helpers; pub mod manifest; pub mod polkadot_sdk; +pub mod signer; pub mod sourcing; pub mod templates; @@ -12,7 +13,11 @@ pub use errors::Error; pub use git::{Git, GitHub, Release}; pub use helpers::{get_project_name_from_path, prefix_with_current_dir_if_needed, replace_in_file}; pub use manifest::{add_crate_to_workspace, find_workspace_toml}; +pub use signer::create_signer; pub use templates::extractor::extract_template_files; +// External exports +pub use subxt::{Config, PolkadotConfig as DefaultConfig}; +pub use subxt_signer::sr25519::Keypair; static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); @@ -36,16 +41,18 @@ pub fn target() -> Result<&'static str, Error> { } match ARCH { - "aarch64" => + "aarch64" => { return match OS { "macos" => Ok("aarch64-apple-darwin"), _ => Ok("aarch64-unknown-linux-gnu"), - }, - "x86_64" | "x86" => + } + }, + "x86_64" | "x86" => { return match OS { "macos" => Ok("x86_64-apple-darwin"), _ => Ok("x86_64-unknown-linux-gnu"), - }, + } + }, &_ => {}, } Err(Error::UnsupportedPlatform { arch: ARCH, os: OS }) diff --git a/crates/pop-contracts/src/utils/signer.rs b/crates/pop-common/src/signer.rs similarity index 55% rename from crates/pop-contracts/src/utils/signer.rs rename to crates/pop-common/src/signer.rs index 51fc44b0e..79da4a283 100644 --- a/crates/pop-contracts/src/utils/signer.rs +++ b/crates/pop-common/src/signer.rs @@ -1,24 +1,16 @@ // SPDX-License-Identifier: GPL-3.0 use crate::errors::Error; -use contract_build::util::decode_hex; -use sp_core::Bytes; use subxt_signer::{sr25519::Keypair, SecretUri}; /// Create a Signer from a secret URI. -pub(crate) fn create_signer(suri: &str) -> Result { +pub fn create_signer(suri: &str) -> Result { let uri = ::from_str(suri) .map_err(|e| Error::ParseSecretURI(format!("{}", e)))?; let keypair = Keypair::from_uri(&uri).map_err(|e| Error::KeyPairCreation(format!("{}", e)))?; Ok(keypair) } -/// Parse hex encoded bytes. -pub fn parse_hex_bytes(input: &str) -> Result { - let bytes = decode_hex(input).map_err(|e| Error::HexParsing(format!("{}", e)))?; - Ok(bytes.into()) -} - #[cfg(test)] mod tests { use super::*; @@ -39,18 +31,4 @@ mod tests { assert!(matches!(create_signer("11111"), Err(Error::KeyPairCreation(..)))); Ok(()) } - - #[test] - fn parse_hex_bytes_works() -> Result<(), Error> { - let input_in_hex = "48656c6c6f"; - let result = parse_hex_bytes(input_in_hex)?; - assert_eq!(result, Bytes(vec![72, 101, 108, 108, 111])); - Ok(()) - } - - #[test] - fn parse_hex_bytes_fails_wrong_input() -> Result<(), Error> { - assert!(matches!(parse_hex_bytes("wronghexvalue"), Err(Error::HexParsing(..)))); - Ok(()) - } } diff --git a/crates/pop-contracts/Cargo.toml b/crates/pop-contracts/Cargo.toml index 3d4f15fb1..b3ab82cb8 100644 --- a/crates/pop-contracts/Cargo.toml +++ b/crates/pop-contracts/Cargo.toml @@ -27,8 +27,6 @@ sp-core.workspace = true sp-weights.workspace = true strum.workspace = true strum_macros.workspace = true -subxt-signer.workspace = true -subxt.workspace = true # cargo-contracts contract-build.workspace = true diff --git a/crates/pop-contracts/src/build.rs b/crates/pop-contracts/src/build.rs index dcbec25a8..df9cc4a3f 100644 --- a/crates/pop-contracts/src/build.rs +++ b/crates/pop-contracts/src/build.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -use crate::{errors::Error, utils::helpers::get_manifest_path}; +use crate::{errors::Error, utils::get_manifest_path}; pub use contract_build::Verbosity; use contract_build::{execute, BuildMode, BuildResult, ExecuteArgs}; use std::path::Path; diff --git a/crates/pop-contracts/src/call.rs b/crates/pop-contracts/src/call.rs index e45a0a9c8..82eb37594 100644 --- a/crates/pop-contracts/src/call.rs +++ b/crates/pop-contracts/src/call.rs @@ -3,9 +3,9 @@ use crate::{ errors::Error, utils::{ - helpers::{get_manifest_path, parse_account, parse_balance}, + get_manifest_path, metadata::{process_function_args, FunctionType}, - signer::create_signer, + parse_account, parse_balance, }, }; use anyhow::Context; @@ -15,10 +15,9 @@ use contract_extrinsics::{ ExtrinsicOptsBuilder, TokenMetadata, }; use ink_env::{DefaultEnvironment, Environment}; +use pop_common::{create_signer, Config, DefaultConfig, Keypair}; use sp_weights::Weight; use std::path::PathBuf; -use subxt::{Config, PolkadotConfig as DefaultConfig}; -use subxt_signer::sr25519::Keypair; use url::Url; /// Attributes for the `call` command. diff --git a/crates/pop-contracts/src/errors.rs b/crates/pop-contracts/src/errors.rs index 3d19d679b..c3e5de535 100644 --- a/crates/pop-contracts/src/errors.rs +++ b/crates/pop-contracts/src/errors.rs @@ -36,8 +36,6 @@ pub enum Error { InvalidName(String), #[error("IO error: {0}")] IO(#[from] std::io::Error), - #[error("Failed to create keypair from URI: {0}")] - KeyPairCreation(String), #[error("Failed to get manifest path: {0}")] ManifestPath(String), #[error("Argument {0} is required")] @@ -46,8 +44,6 @@ pub enum Error { NewContract(String), #[error("ParseError error: {0}")] ParseError(#[from] url::ParseError), - #[error("Failed to parse secret URI: {0}")] - ParseSecretURI(String), #[error("The `Repository` property is missing from the template variant")] RepositoryMissing, #[error("Sourcing error {0}")] diff --git a/crates/pop-contracts/src/lib.rs b/crates/pop-contracts/src/lib.rs index 142283998..c5e76e2f1 100644 --- a/crates/pop-contracts/src/lib.rs +++ b/crates/pop-contracts/src/lib.rs @@ -26,7 +26,6 @@ pub use up::{ set_up_deployment, set_up_upload, upload_smart_contract, UpOpts, }; pub use utils::{ - helpers::parse_account, metadata::{get_messages, ContractFunction}, - signer::parse_hex_bytes, + parse_account, parse_hex_bytes, }; diff --git a/crates/pop-contracts/src/new.rs b/crates/pop-contracts/src/new.rs index 8e11fbd16..6d4d7f125 100644 --- a/crates/pop-contracts/src/new.rs +++ b/crates/pop-contracts/src/new.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -use crate::{errors::Error, utils::helpers::canonicalized_path, Contract}; +use crate::{errors::Error, utils::canonicalized_path, Contract}; use anyhow::Result; use contract_build::new_contract_project; use heck::ToUpperCamelCase; diff --git a/crates/pop-contracts/src/up.rs b/crates/pop-contracts/src/up.rs index 17b1bfa71..a0fc1492c 100644 --- a/crates/pop-contracts/src/up.rs +++ b/crates/pop-contracts/src/up.rs @@ -2,9 +2,9 @@ use crate::{ errors::Error, utils::{ - helpers::{get_manifest_path, parse_balance}, + get_manifest_path, metadata::{process_function_args, FunctionType}, - signer::create_signer, + parse_balance, }, }; use contract_extrinsics::{ @@ -12,11 +12,10 @@ use contract_extrinsics::{ TokenMetadata, UploadCommandBuilder, UploadExec, }; use ink_env::{DefaultEnvironment, Environment}; +use pop_common::{create_signer, DefaultConfig, Keypair}; use sp_core::Bytes; use sp_weights::Weight; use std::{fmt::Write, path::PathBuf}; -use subxt::PolkadotConfig as DefaultConfig; -use subxt_signer::sr25519::Keypair; /// Attributes for the `up` command #[derive(Debug, PartialEq)] diff --git a/crates/pop-contracts/src/utils/helpers.rs b/crates/pop-contracts/src/utils/helpers.rs deleted file mode 100644 index d0797015c..000000000 --- a/crates/pop-contracts/src/utils/helpers.rs +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 - -use crate::errors::Error; -use contract_build::ManifestPath; -use contract_extrinsics::BalanceVariant; -use ink_env::{DefaultEnvironment, Environment}; -use std::{ - path::{Path, PathBuf}, - str::FromStr, -}; -use subxt::{Config, PolkadotConfig as DefaultConfig}; - -pub fn get_manifest_path(path: Option<&Path>) -> Result { - if let Some(path) = path { - let full_path = PathBuf::from(path.to_string_lossy().to_string() + "/Cargo.toml"); - ManifestPath::try_from(Some(full_path)) - .map_err(|e| Error::ManifestPath(format!("Failed to get manifest path: {}", e))) - } else { - ManifestPath::try_from(path.as_ref()) - .map_err(|e| Error::ManifestPath(format!("Failed to get manifest path: {}", e))) - } -} - -pub fn parse_balance( - balance: &str, -) -> Result::Balance>, Error> { - BalanceVariant::from_str(balance).map_err(|e| Error::BalanceParsing(format!("{}", e))) -} - -pub fn parse_account(account: &str) -> Result<::AccountId, Error> { - ::AccountId::from_str(account) - .map_err(|e| Error::AccountAddressParsing(format!("{}", e))) -} - -/// Canonicalizes the given path to ensure consistency and resolve any symbolic links. -/// -/// # Arguments -/// -/// * `target` - A reference to the `Path` to be canonicalized. -pub fn canonicalized_path(target: &Path) -> Result { - // Canonicalize the target path to ensure consistency and resolve any symbolic links. - target - .canonicalize() - // If an I/O error occurs during canonicalization, convert it into an Error enum variant. - .map_err(Error::IO) -} - -#[cfg(test)] -mod tests { - use super::*; - use anyhow::{Error, Result}; - use std::fs; - - fn setup_test_environment() -> Result { - let temp_dir = tempfile::tempdir().expect("Could not create temp dir"); - let temp_contract_dir = temp_dir.path().join("test_contract"); - fs::create_dir(&temp_contract_dir)?; - crate::create_smart_contract( - "test_contract", - temp_contract_dir.as_path(), - &crate::Contract::Standard, - )?; - Ok(temp_dir) - } - - #[test] - fn test_get_manifest_path() -> Result<(), Error> { - let temp_dir = setup_test_environment()?; - get_manifest_path(Some(&PathBuf::from(temp_dir.path().join("test_contract"))))?; - Ok(()) - } - - #[test] - fn test_canonicalized_path() -> Result<(), Error> { - let temp_dir = tempfile::tempdir()?; - // Error case - let error_directory = canonicalized_path(&temp_dir.path().join("my_directory")); - assert!(error_directory.is_err()); - // Success case - canonicalized_path(temp_dir.path())?; - Ok(()) - } - - #[test] - fn parse_balance_works() -> Result<(), Error> { - let balance = parse_balance("100000")?; - assert_eq!(balance, BalanceVariant::Default(100000)); - Ok(()) - } - - #[test] - fn parse_balance_fails_wrong_balance() -> Result<(), Error> { - assert!(matches!(parse_balance("wrongbalance"), Err(super::Error::BalanceParsing(..)))); - Ok(()) - } - - #[test] - fn parse_account_works() -> Result<(), Error> { - let account = parse_account("5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A")?; - assert_eq!(account.to_string(), "5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A"); - Ok(()) - } - - #[test] - fn parse_account_fails_wrong_value() -> Result<(), Error> { - assert!(matches!( - parse_account("wrongaccount"), - Err(super::Error::AccountAddressParsing(..)) - )); - Ok(()) - } -} diff --git a/crates/pop-contracts/src/utils/mod.rs b/crates/pop-contracts/src/utils/mod.rs index ad49e2dc6..17546b6ff 100644 --- a/crates/pop-contracts/src/utils/mod.rs +++ b/crates/pop-contracts/src/utils/mod.rs @@ -1,5 +1,135 @@ // SPDX-License-Identifier: GPL-3.0 -pub mod helpers; +use crate::errors::Error; +use contract_build::{util::decode_hex, ManifestPath}; +use contract_extrinsics::BalanceVariant; +use ink_env::{DefaultEnvironment, Environment}; +use pop_common::{Config, DefaultConfig}; +use sp_core::Bytes; +use std::{ + path::{Path, PathBuf}, + str::FromStr, +}; + pub mod metadata; -pub mod signer; + +pub fn get_manifest_path(path: Option<&Path>) -> Result { + if let Some(path) = path { + let full_path = PathBuf::from(path.to_string_lossy().to_string() + "/Cargo.toml"); + ManifestPath::try_from(Some(full_path)) + .map_err(|e| Error::ManifestPath(format!("Failed to get manifest path: {}", e))) + } else { + ManifestPath::try_from(path.as_ref()) + .map_err(|e| Error::ManifestPath(format!("Failed to get manifest path: {}", e))) + } +} + +pub fn parse_balance( + balance: &str, +) -> Result::Balance>, Error> { + BalanceVariant::from_str(balance).map_err(|e| Error::BalanceParsing(format!("{}", e))) +} + +pub fn parse_account(account: &str) -> Result<::AccountId, Error> { + ::AccountId::from_str(account) + .map_err(|e| Error::AccountAddressParsing(format!("{}", e))) +} + +/// Parse hex encoded bytes. +pub fn parse_hex_bytes(input: &str) -> Result { + let bytes = decode_hex(input).map_err(|e| Error::HexParsing(format!("{}", e)))?; + Ok(bytes.into()) +} + +/// Canonicalizes the given path to ensure consistency and resolve any symbolic links. +/// +/// # Arguments +/// +/// * `target` - A reference to the `Path` to be canonicalized. +pub fn canonicalized_path(target: &Path) -> Result { + // Canonicalize the target path to ensure consistency and resolve any symbolic links. + target + .canonicalize() + // If an I/O error occurs during canonicalization, convert it into an Error enum variant. + .map_err(Error::IO) +} + +#[cfg(test)] +mod tests { + use super::*; + use anyhow::{Error, Result}; + use std::fs; + + fn setup_test_environment() -> Result { + let temp_dir = tempfile::tempdir().expect("Could not create temp dir"); + let temp_contract_dir = temp_dir.path().join("test_contract"); + fs::create_dir(&temp_contract_dir)?; + crate::create_smart_contract( + "test_contract", + temp_contract_dir.as_path(), + &crate::Contract::Standard, + )?; + Ok(temp_dir) + } + + #[test] + fn test_get_manifest_path() -> Result<(), Error> { + let temp_dir = setup_test_environment()?; + get_manifest_path(Some(&PathBuf::from(temp_dir.path().join("test_contract"))))?; + Ok(()) + } + + #[test] + fn test_canonicalized_path() -> Result<(), Error> { + let temp_dir = tempfile::tempdir()?; + // Error case + let error_directory = canonicalized_path(&temp_dir.path().join("my_directory")); + assert!(error_directory.is_err()); + // Success case + canonicalized_path(temp_dir.path())?; + Ok(()) + } + + #[test] + fn parse_balance_works() -> Result<(), Error> { + let balance = parse_balance("100000")?; + assert_eq!(balance, BalanceVariant::Default(100000)); + Ok(()) + } + + #[test] + fn parse_balance_fails_wrong_balance() -> Result<(), Error> { + assert!(matches!(parse_balance("wrongbalance"), Err(super::Error::BalanceParsing(..)))); + Ok(()) + } + + #[test] + fn parse_account_works() -> Result<(), Error> { + let account = parse_account("5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A")?; + assert_eq!(account.to_string(), "5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A"); + Ok(()) + } + + #[test] + fn parse_account_fails_wrong_value() -> Result<(), Error> { + assert!(matches!( + parse_account("wrongaccount"), + Err(super::Error::AccountAddressParsing(..)) + )); + Ok(()) + } + + #[test] + fn parse_hex_bytes_works() -> Result<(), Error> { + let input_in_hex = "48656c6c6f"; + let result = parse_hex_bytes(input_in_hex)?; + assert_eq!(result, Bytes(vec![72, 101, 108, 108, 111])); + Ok(()) + } + + #[test] + fn parse_hex_bytes_fails_wrong_input() -> Result<(), Error> { + assert!(matches!(parse_hex_bytes("wronghexvalue"), Err(Error::HexParsing(..)))); + Ok(()) + } +} diff --git a/crates/pop-parachains/Cargo.toml b/crates/pop-parachains/Cargo.toml index 83542043d..e162e9bdb 100644 --- a/crates/pop-parachains/Cargo.toml +++ b/crates/pop-parachains/Cargo.toml @@ -28,6 +28,7 @@ indexmap.workspace = true reqwest.workspace = true scale-info.workspace = true scale-typegen-description.workspace = true +scale-value = { workspace = true } subxt.workspace = true symlink.workspace = true toml_edit.workspace = true diff --git a/crates/pop-parachains/src/call.rs b/crates/pop-parachains/src/call.rs index d5c12099a..c220e5cb6 100644 --- a/crates/pop-parachains/src/call.rs +++ b/crates/pop-parachains/src/call.rs @@ -1,11 +1,11 @@ // SPDX-License-Identifier: GPL-3.0 use crate::errors::Error; +use pop_common::create_signer; use scale_info::{form::PortableForm, Variant}; use scale_typegen_description::type_description; -use subxt::{ - dynamic::Value, metadata::types::StorageEntryType, Metadata, OnlineClient, SubstrateConfig, -}; +use scale_value::{stringify, Value}; +use subxt::{metadata::types::StorageEntryType, Metadata, OnlineClient, SubstrateConfig}; #[derive(Clone, PartialEq, Eq)] pub struct Storage { @@ -35,20 +35,45 @@ pub async fn fetch_metadata(url: &str) -> Result { pub async fn query( pallet_name: &str, entry_name: &str, - args: Vec, + args: Vec, url: &str, -) -> Result<(), Error> { +) -> Result { + let args_value: Vec = + args.into_iter().map(|v| stringify::from_str(&v).0.unwrap()).collect(); let api = OnlineClient::::from_url(url).await?; - println!("here"); - let storage_query = subxt::dynamic::storage(pallet_name, entry_name, args); - println!("here"); - let mut results = api.storage().at_latest().await?.iter(storage_query).await?; - println!("{:?}", results); - while let Some(Ok(kv)) = results.next().await { - println!("Keys decoded: {:?}", kv.keys); - println!("Value: {:?}", kv.value.to_value().map_err(|_| Error::ParsingResponseError)?); + let storage_query = subxt::dynamic::storage(pallet_name, entry_name, args_value); + let result = api.storage().at_latest().await?.fetch(&storage_query).await?; + if result.is_none() { + Ok("".to_string()) + } else { + Ok(result.unwrap().to_value()?.to_string()) } - Ok(()) +} + +pub async fn submit_extrinsic( + pallet_name: &str, + entry_name: &str, + args: Vec, + url: &str, + suri: &str, +) -> Result { + let args_value: Vec = args + .into_iter() + .filter_map(|v| match stringify::from_str(&v).0 { + Ok(value) => Some(value), + Err(_) => None, + }) + .collect(); + let api = OnlineClient::::from_url(url).await?; + let tx = subxt::dynamic::tx(pallet_name, entry_name, args_value); + let signer = create_signer(suri)?; + let result = api + .tx() + .sign_and_submit_then_watch_default(&tx, &signer) + .await? + .wait_for_finalized_success() + .await?; + Ok(result.extrinsic_hash().to_string()) } pub async fn parse_chain_metadata(metadata: Metadata) -> Result, Error> { @@ -112,11 +137,28 @@ mod tests { use anyhow::Result; #[tokio::test] - async fn storage_info_works() -> Result<()> { - let metadata = fetch_metadata("wss://rpc2.paseo.popnetwork.xyz").await?; - explore("Nfts", "Account", &metadata)?; - explore("Nfts", "Collection", &metadata)?; - explore("Nfts", "NextCollectionId", &metadata)?; + async fn query_works() -> Result<()> { + let result = + query("Assets", "Asset", vec!["50".into()], "wss://rpc2.paseo.popnetwork.xyz").await?; + println!("{:?}", result); + // query("Nfts", "Collection", &metadata)?; + // query("Nfts", "NextCollectionId", &metadata)?; + + Ok(()) + } + + #[tokio::test] + async fn extrinsic_works() -> Result<()> { + let result = query( + "Balances", + "TransferAllowDeath", + vec!["167Y1SbQrwQVNfkNUXtRkocfzVbaAHYjnZPkZRScWPQ46XDb".into(), "1".into()], + "wss://rpc2.paseo.popnetwork.xyz", + ) + .await?; + println!("{:?}", result); + // query("Nfts", "Collection", &metadata)?; + // query("Nfts", "NextCollectionId", &metadata)?; Ok(()) } diff --git a/crates/pop-parachains/src/errors.rs b/crates/pop-parachains/src/errors.rs index 7e9e8e19d..7ddb9dd75 100644 --- a/crates/pop-parachains/src/errors.rs +++ b/crates/pop-parachains/src/errors.rs @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 +use subxt::ext::scale_decode; use thiserror::Error; use zombienet_sdk::OrchestratorError; @@ -36,7 +37,7 @@ pub enum Error { #[error("Failed to find the pallet {0}")] PalletNotFound(String), #[error("Failed to parse the response")] - ParsingResponseError, + ParsingResponseError(#[from] scale_decode::Error), #[error("Invalid path")] PathError, #[error("Failed to execute rustfmt")] diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index 614aa377b..cb979b3fc 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -15,13 +15,15 @@ pub use build::{ binary_path, build_parachain, export_wasm_file, generate_genesis_state_file, generate_plain_chain_spec, generate_raw_chain_spec, is_supported, ChainSpec, }; -pub use call::{fetch_metadata, get_type_description, parse_chain_metadata, query}; +pub use call::{ + fetch_metadata, get_type_description, parse_chain_metadata, query, submit_extrinsic, +}; pub use errors::Error; pub use indexmap::IndexSet; pub use new_pallet::{create_pallet_template, new_pallet_options::*, TemplatePalletConfig}; pub use new_parachain::instantiate_template_dir; // External export from subxt. -pub use subxt::Metadata; +pub use subxt::{dynamic::Value, Metadata}; pub use templates::{Config, Parachain, Provider}; pub use up::Zombienet; pub use utils::helpers::is_initial_endowment_valid; From 430522a09a7167638a50637c5f039b0cf9a8190a Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Sun, 22 Sep 2024 10:06:13 +0200 Subject: [PATCH 125/211] refactor: improve messages --- crates/pop-cli/src/commands/call/parachain.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 74dad9cc3..cd740cb7d 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -111,8 +111,8 @@ async fn guide_user_to_call_chain( }; let action = cli .select("What do you want to do?") - .item(Action::Extrinsic, "Submit an extrinsic", "hint") - .item(Action::Query, "Query storage", "hint") + .item(Action::Extrinsic, "Submit an extrinsic", "") + .item(Action::Query, "Query storage", "") .interact()?; let mut args = Vec::new(); @@ -134,11 +134,7 @@ async fn guide_user_to_call_chain( "Enter the value for the argument '{}':", argument.name.unwrap_or_default() )) - .placeholder(&format!( - "{} - {}", - argument.docs.join(","), - argument.type_name.unwrap_or_default() - )) + .placeholder(&argument.type_name.unwrap_or_default()) .required(false) .interact()?; args.push(value); From a3d06c490896c70e2510302b0a8fe2f24a14ec07 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Mon, 23 Sep 2024 23:54:19 +0200 Subject: [PATCH 126/211] feat: call parachain ui --- Cargo.lock | 3 +- Cargo.toml | 1 + crates/pop-cli/src/commands/call/mod.rs | 2 + crates/pop-cli/src/commands/call/parachain.rs | 194 +++++++--------- crates/pop-cli/src/commands/call/use_cases.rs | 113 +++++++++ crates/pop-common/src/errors.rs | 2 + crates/pop-common/src/lib.rs | 2 +- crates/pop-common/src/signer.rs | 24 +- crates/pop-contracts/src/call.rs | 6 +- crates/pop-contracts/src/errors.rs | 2 - crates/pop-contracts/src/lib.rs | 2 +- crates/pop-contracts/src/utils/mod.rs | 22 -- crates/pop-parachains/Cargo.toml | 1 + crates/pop-parachains/src/call.rs | 217 ++++++++++++------ crates/pop-parachains/src/errors.rs | 8 + crates/pop-parachains/src/lib.rs | 5 +- 16 files changed, 389 insertions(+), 215 deletions(-) create mode 100644 crates/pop-cli/src/commands/call/use_cases.rs diff --git a/Cargo.lock b/Cargo.lock index 224e6aca0..a5ef17363 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4766,7 +4766,8 @@ dependencies = [ "duct", "flate2", "glob", - "indexmap 2.5.0", + "hex", + "indexmap 2.4.0", "mockito", "pop-common", "reqwest 0.12.5", diff --git a/Cargo.toml b/Cargo.toml index e4845c308..0d228b680 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ scale-info = { version = "2.11.3", default-features = false, features = ["derive scale-typegen-description = "0.8.0" scale-value = { version = "0.16.2", default-features = false } heck = "0.5.0" +hex = { version = "0.4.3", default-features = false } # parachains askama = "0.12" diff --git a/crates/pop-cli/src/commands/call/mod.rs b/crates/pop-cli/src/commands/call/mod.rs index 241ebbdb9..04f70634b 100644 --- a/crates/pop-cli/src/commands/call/mod.rs +++ b/crates/pop-cli/src/commands/call/mod.rs @@ -6,6 +6,8 @@ use clap::{Args, Subcommand}; pub(crate) mod contract; #[cfg(feature = "parachain")] pub(crate) mod parachain; +#[cfg(feature = "parachain")] +pub(crate) mod use_cases; /// Arguments for calling a smart contract. #[derive(Args)] diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index cd740cb7d..fd974a877 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -1,26 +1,20 @@ // SPDX-License-Identifier: GPL-3.0 use crate::cli::{self, traits::*}; -use anyhow::{anyhow, Result}; +use anyhow::Result; use clap::Args; use pop_parachains::{ - fetch_metadata, get_type_description, parse_chain_metadata, query, submit_extrinsic, Metadata, + prepare_extrinsic, set_up_api, submit_extrinsic, OnlineClient, Pallet, SubstrateConfig, }; +use strum::VariantArray; + +use super::use_cases::prompt_arguments; #[derive(Args, Clone)] pub struct CallParachainCommand { - /// The name of the pallet to call. + /// The signed extrinsic to call. #[clap(long, short)] - pallet: Option, - /// The name of extrinsic to call. - #[clap(long, short, conflicts_with = "query")] extrinsic: Option, - /// The name of storage to query. - #[clap(long, short, conflicts_with = "extrinsic")] - query: Option, - /// The constructor arguments, encoded as strings. - #[clap(long, num_args = 0..)] - args: Vec, /// Websocket endpoint of a node. #[clap(name = "url", long, value_parser, default_value = "ws://localhost:9944")] url: String, @@ -32,112 +26,71 @@ pub struct CallParachainCommand { #[clap(name = "suri", long, short, default_value = "//Alice")] suri: String, } + impl CallParachainCommand { /// Executes the command. pub(crate) async fn execute(mut self) -> Result<()> { - let metadata = self.query_metadata(&mut cli::Cli).await?; - let call_config = - if self.pallet.is_none() && (self.extrinsic.is_none() || self.query.is_none()) { - guide_user_to_call_chain(&mut cli::Cli, metadata).await? - } else { - self.clone() - }; - execute_extrinsic(call_config.clone(), &mut cli::Cli).await?; + let (api, url) = self.set_up_api(&mut cli::Cli).await?; + let call_config = if self.extrinsic.is_none() { + guide_user_to_call_chain(&api, url, &mut cli::Cli).await? + } else { + self.clone() + }; + execute_extrinsic(api, call_config, self.extrinsic.is_none(), &mut cli::Cli).await?; Ok(()) } - ///Parse metadata. - async fn query_metadata( + /// Prompt the user for the chain to use if not indicated and fetch the metadata. + async fn set_up_api( &mut self, cli: &mut impl cli::traits::Cli, - ) -> anyhow::Result { + ) -> anyhow::Result<(OnlineClient, String)> { cli.intro("Call a parachain")?; - let url: String = - if self.pallet.is_none() && (self.extrinsic.is_none() || self.query.is_none()) { - // Prompt for contract location. - cli.input("Which chain would you like to interact with?") - .placeholder("wss://rpc1.paseo.popnetwork.xyz") - .default_input("wss://rpc1.paseo.popnetwork.xyz") - .interact()? - } else { - self.url.clone() - }; - let metadata = fetch_metadata(&url).await?; - Ok(metadata) + let url: String = if self.extrinsic.is_none() { + // Prompt for contract location. + cli.input("Which chain would you like to interact with?") + .placeholder("wss://rpc1.paseo.popnetwork.xyz") + .default_input("wss://rpc1.paseo.popnetwork.xyz") + .interact()? + } else { + self.url.clone() + }; + let api = set_up_api(&url).await?; + Ok((api, url)) } fn display(&self) -> String { let mut full_message = "pop call parachain".to_string(); - if let Some(pallet) = &self.pallet { - full_message.push_str(&format!(" --pallet {}", pallet)); - } if let Some(extrinsic) = &self.extrinsic { full_message.push_str(&format!(" --extrinsic {}", extrinsic)); } - if let Some(query) = &self.query { - full_message.push_str(&format!(" --query {}", query)); + full_message.push_str(&format!("--url {}", self.url)); + if !self.suri.is_empty() { + full_message.push_str(&format!(" --suri {}", self.suri)); } - if !self.args.is_empty() { - full_message.push_str(&format!(" --args {}", self.args.join(" "))); - } - full_message.push_str(&format!(" --url {} --suri {}", self.url, self.suri)); full_message } } -#[derive(Clone, Eq, PartialEq)] -enum Action { - Extrinsic, - Query, -} - /// Guide the user to call the contract. async fn guide_user_to_call_chain( + api: &OnlineClient, + url: String, cli: &mut impl cli::traits::Cli, - metadata: Metadata, ) -> anyhow::Result { - let pallets = match parse_chain_metadata(metadata.clone()).await { - Ok(pallets) => pallets, - Err(e) => { - cli.outro_cancel("Unable to fetch the chain metadata.")?; - return Err(anyhow!(format!("{}", e.to_string()))); - }, - }; + let pallets = Pallet::VARIANTS; let pallet = { let mut prompt = cli.select("Select the pallet to call:"); for pallet_item in pallets { - prompt = prompt.item(pallet_item.clone(), &pallet_item.label, &pallet_item.docs); + prompt = prompt.item(pallet_item.clone(), pallet_item.as_ref(), ""); } prompt.interact()? }; - let action = cli - .select("What do you want to do?") - .item(Action::Extrinsic, "Submit an extrinsic", "") - .item(Action::Query, "Query storage", "") - .interact()?; - let mut args = Vec::new(); - if action == Action::Extrinsic { - let extrinsic = { - let mut prompt_extrinsic = cli.select("Select the extrinsic to call:"); - for extrinsic in pallet.extrinsics { - prompt_extrinsic = prompt_extrinsic.item( - extrinsic.clone(), - format!("{}\n", &extrinsic.name), - extrinsic.docs.concat(), - ); - } - prompt_extrinsic.interact()? - }; - for argument in extrinsic.fields { - let value = cli - .input(&format!( - "Enter the value for the argument '{}':", - argument.name.unwrap_or_default() - )) - .placeholder(&argument.type_name.unwrap_or_default()) - .required(false) - .interact()?; - args.push(value); + let extrinsic = { + let mut prompt_extrinsic = cli.select("Select the extrinsic to call:"); + for extrinsic in pallet.extrinsics() { + prompt_extrinsic = + prompt_extrinsic.item(extrinsic.clone(), format!("{}\n", &extrinsic.as_ref()), ""); } Ok(CallParachainCommand { pallet: Some(pallet.label), @@ -155,7 +108,7 @@ async fn guide_user_to_call_chain( } prompt_storage.interact()? }; - let keys_needed = get_type_description(query.ty.1, &metadata)?; + let keys_needed = get_type_description(query.ty.1, metadata)?; for key in keys_needed { let value = cli.input(&format!("Enter the key '{}':", key)).interact()?; args.push(value); @@ -173,33 +126,54 @@ async fn guide_user_to_call_chain( /// Executes the extrinsic or query. async fn execute_extrinsic( + api: OnlineClient, call_config: CallParachainCommand, + prompt_to_repeat_call: bool, cli: &mut impl cli::traits::Cli, ) -> Result<()> { cli.info(call_config.display())?; - let pallet = call_config - .pallet - .expect("pallet can not be none as fallback above is interactive input; qed"); - // TODO: Check if exists? - if call_config.extrinsic.is_some() { - let extrinsic = call_config - .extrinsic - .expect("storage can not be none as fallback above is interactive input; qed"); - let result = submit_extrinsic( - &pallet, - &extrinsic, - call_config.args, - &call_config.url, - &call_config.suri, - ) - .await?; - cli.outro(format!("Extrinsic submitted successfully with hash: {}", result))?; + let extrinsic = call_config + .extrinsic + .expect("extrinsic can not be none as fallback above is interactive input; qed"); + if !cli.confirm("Do you want to sign and submit the call?").interact()? { + display_message(&format!("Extrinsic: {} not submitted", extrinsic), true, cli)?; + return Ok(()); + } + // TODO: Handle error + let result = submit_extrinsic(api.clone(), extrinsic).await?; + // Repeat call. + if prompt_to_repeat_call { + let another_call: bool = cli + .confirm("Do you want to do another call to the same chain?") + .initial_value(false) + .interact()?; + if another_call { + // Remove only the prompt asking for another call. + console::Term::stderr().clear_last_lines(2)?; + let new_call_config = guide_user_to_call_chain(&api, call_config.url, cli).await?; + Box::pin(execute_extrinsic(api, new_call_config, prompt_to_repeat_call, cli)).await?; + } else { + display_message( + &format!("Extrinsic submitted successfully with hash: {}", result), + true, + cli, + )?; + } + } else { + display_message( + &format!("Extrinsic submitted successfully with hash: {}", result), + true, + cli, + )?; + } + Ok(()) +} + +fn display_message(message: &str, success: bool, cli: &mut impl cli::traits::Cli) -> Result<()> { + if success { + cli.outro(message)?; } else { - let storage = call_config - .query - .expect("storage can not be none as fallback above is interactive input; qed"); - let result = query(&pallet, &storage, call_config.args, &call_config.url).await?; - cli.outro(result)?; + cli.outro_cancel(message)?; } Ok(()) } diff --git a/crates/pop-cli/src/commands/call/use_cases.rs b/crates/pop-cli/src/commands/call/use_cases.rs new file mode 100644 index 000000000..0723a0bb5 --- /dev/null +++ b/crates/pop-cli/src/commands/call/use_cases.rs @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-3.0 + +use crate::cli::{ + self, + traits::{Confirm as _, Input as _}, +}; +use anyhow::Result; +use pop_common::parse_account; +use pop_parachains::{parse_string_into_scale_value, Extrinsic, Pallet, Value}; + +/// Prompt the user to select an operation. +pub fn prompt_arguments( + extrinsic: &Extrinsic, + cli: &mut impl cli::traits::Cli, +) -> Result> { + match extrinsic { + Extrinsic::CreateAsset => prompt_query_params(&extrinsic, cli), + Extrinsic::MintAsset => prompt_query_params(&extrinsic, cli), + Extrinsic::CreateCollection => prompt_query_params(&extrinsic, cli), + Extrinsic::MintNFT => prompt_query_params(&extrinsic, cli), + Extrinsic::Transfer => prompt_query_params(&extrinsic, cli), + } +} +fn prompt_query_params( + extrinsic: &Extrinsic, + cli: &mut impl cli::traits::Cli, +) -> Result> { + let mut args: Vec = Vec::new(); + let id = cli + .input(&format!( + "Enter the {} id", + if extrinsic.pallet()? == Pallet::Assets.to_string() { "Asset" } else { "Collection" } + )) + .placeholder("0") + .required(false) + .interact()?; + args.push(parse_string_into_scale_value(&id)?); + // if call == &Call::NFTItem { + // let nft_id = cli + // .input("Enter the Nft id") + // .placeholder("0 or None") + // .required(false) + // .interact()?; + // args.push(parse_string_into_scale_value(&nft_id)?); + // } + Ok(args) +} +fn prompt_mint_params( + extrinsic: &Extrinsic, + cli: &mut impl cli::traits::Cli, +) -> Result> { + let mut args: Vec = Vec::new(); + let id = cli + .input(&format!( + "Enter the {} id", + if extrinsic.pallet()? == Pallet::Assets.to_string() { "Asset" } else { "Collection" } + )) + .placeholder("0") + .required(true) + .interact()?; + args.push(parse_string_into_scale_value(&id)?); + if extrinsic == &Extrinsic::MintNFT { + let nft_id = cli + .input(&format!( + "Enter the {} id", + if extrinsic.pallet()? == Pallet::Assets.to_string() { + "Asset" + } else { + "Collection" + } + )) + .placeholder("0") + .required(true) + .interact()?; + args.push(parse_string_into_scale_value(&nft_id)?); + } + // Prompt for beneficiary + let beneficiary: String = cli + .input("Enter the beneficiary address") + .placeholder("e.g. 5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") + .required(true) + .validate(|input: &String| match parse_account(input) { + Ok(_) => Ok(()), + Err(_) => Err("Invalid address."), + }) + .interact()?; + args.push(parse_string_into_scale_value(&beneficiary)?); + // if extrinsic == &Extrinsic::AssetItem { + // let amount = cli.input("Enter the amount").placeholder("0").required(true).interact()?; + // args.push(parse_string_into_scale_value(&amount)?); + // } + if extrinsic == &Extrinsic::MintNFT { + if cli + .confirm("Do you want to include witness data?") + .initial_value(false) + .interact()? + { + let config = {}; + let owned_item = cli + .input("Id of the item in a required collection:") + .placeholder("0 or None") + .required(false) + .interact()?; + let owned_item = cli + .input("The price specified in mint settings:") + .placeholder("0 or None") + .required(false) + .interact()?; + //args.push(Value::u128(id)); + } + } + Ok(args) +} diff --git a/crates/pop-common/src/errors.rs b/crates/pop-common/src/errors.rs index 7598cd543..5a0066add 100644 --- a/crates/pop-common/src/errors.rs +++ b/crates/pop-common/src/errors.rs @@ -7,6 +7,8 @@ use thiserror::Error; pub enum Error { #[error("Anyhow error: {0}")] AnyhowError(#[from] anyhow::Error), + #[error("Failed to parse account address: {0}")] + AccountAddressParsing(String), #[error("Configuration error: {0}")] Config(String), #[error("a git error occurred: {0}")] diff --git a/crates/pop-common/src/lib.rs b/crates/pop-common/src/lib.rs index cf82b7c67..4b24729b4 100644 --- a/crates/pop-common/src/lib.rs +++ b/crates/pop-common/src/lib.rs @@ -13,7 +13,7 @@ pub use errors::Error; pub use git::{Git, GitHub, Release}; pub use helpers::{get_project_name_from_path, prefix_with_current_dir_if_needed, replace_in_file}; pub use manifest::{add_crate_to_workspace, find_workspace_toml}; -pub use signer::create_signer; +pub use signer::{create_signer, parse_account}; pub use templates::extractor::extract_template_files; // External exports pub use subxt::{Config, PolkadotConfig as DefaultConfig}; diff --git a/crates/pop-common/src/signer.rs b/crates/pop-common/src/signer.rs index 79da4a283..8cc3e9e25 100644 --- a/crates/pop-common/src/signer.rs +++ b/crates/pop-common/src/signer.rs @@ -1,8 +1,14 @@ // SPDX-License-Identifier: GPL-3.0 -use crate::errors::Error; +use crate::{errors::Error, Config, DefaultConfig}; +use std::str::FromStr; use subxt_signer::{sr25519::Keypair, SecretUri}; +pub fn parse_account(account: &str) -> Result<::AccountId, Error> { + ::AccountId::from_str(account) + .map_err(|e| Error::AccountAddressParsing(format!("{}", e))) +} + /// Create a Signer from a secret URI. pub fn create_signer(suri: &str) -> Result { let uri = ::from_str(suri) @@ -31,4 +37,20 @@ mod tests { assert!(matches!(create_signer("11111"), Err(Error::KeyPairCreation(..)))); Ok(()) } + + #[test] + fn parse_account_works() -> Result<(), Error> { + let account = parse_account("5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A")?; + assert_eq!(account.to_string(), "5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A"); + Ok(()) + } + + #[test] + fn parse_account_fails_wrong_value() -> Result<(), Error> { + assert!(matches!( + parse_account("wrongaccount"), + Err(super::Error::AccountAddressParsing(..)) + )); + Ok(()) + } } diff --git a/crates/pop-contracts/src/call.rs b/crates/pop-contracts/src/call.rs index 82eb37594..fdd8e7307 100644 --- a/crates/pop-contracts/src/call.rs +++ b/crates/pop-contracts/src/call.rs @@ -4,8 +4,8 @@ use crate::{ errors::Error, utils::{ get_manifest_path, - metadata::{process_function_args, FunctionType}, - parse_account, parse_balance, + metadata::{get_messages, ContractFunction}, + parse_balance, }, }; use anyhow::Context; @@ -15,7 +15,7 @@ use contract_extrinsics::{ ExtrinsicOptsBuilder, TokenMetadata, }; use ink_env::{DefaultEnvironment, Environment}; -use pop_common::{create_signer, Config, DefaultConfig, Keypair}; +use pop_common::{create_signer, parse_account, Config, DefaultConfig, Keypair}; use sp_weights::Weight; use std::path::PathBuf; use url::Url; diff --git a/crates/pop-contracts/src/errors.rs b/crates/pop-contracts/src/errors.rs index c3e5de535..348f352c1 100644 --- a/crates/pop-contracts/src/errors.rs +++ b/crates/pop-contracts/src/errors.rs @@ -8,8 +8,6 @@ use thiserror::Error; pub enum Error { #[error("Anyhow error: {0}")] AnyhowError(#[from] anyhow::Error), - #[error("Failed to parse account address: {0}")] - AccountAddressParsing(String), #[error("Failed to parse balance: {0}")] BalanceParsing(String), #[error("{0}")] diff --git a/crates/pop-contracts/src/lib.rs b/crates/pop-contracts/src/lib.rs index c5e76e2f1..fe512a56f 100644 --- a/crates/pop-contracts/src/lib.rs +++ b/crates/pop-contracts/src/lib.rs @@ -27,5 +27,5 @@ pub use up::{ }; pub use utils::{ metadata::{get_messages, ContractFunction}, - parse_account, parse_hex_bytes, + parse_hex_bytes, }; diff --git a/crates/pop-contracts/src/utils/mod.rs b/crates/pop-contracts/src/utils/mod.rs index 17546b6ff..7192839b4 100644 --- a/crates/pop-contracts/src/utils/mod.rs +++ b/crates/pop-contracts/src/utils/mod.rs @@ -4,7 +4,6 @@ use crate::errors::Error; use contract_build::{util::decode_hex, ManifestPath}; use contract_extrinsics::BalanceVariant; use ink_env::{DefaultEnvironment, Environment}; -use pop_common::{Config, DefaultConfig}; use sp_core::Bytes; use std::{ path::{Path, PathBuf}, @@ -30,11 +29,6 @@ pub fn parse_balance( BalanceVariant::from_str(balance).map_err(|e| Error::BalanceParsing(format!("{}", e))) } -pub fn parse_account(account: &str) -> Result<::AccountId, Error> { - ::AccountId::from_str(account) - .map_err(|e| Error::AccountAddressParsing(format!("{}", e))) -} - /// Parse hex encoded bytes. pub fn parse_hex_bytes(input: &str) -> Result { let bytes = decode_hex(input).map_err(|e| Error::HexParsing(format!("{}", e)))?; @@ -103,22 +97,6 @@ mod tests { Ok(()) } - #[test] - fn parse_account_works() -> Result<(), Error> { - let account = parse_account("5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A")?; - assert_eq!(account.to_string(), "5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A"); - Ok(()) - } - - #[test] - fn parse_account_fails_wrong_value() -> Result<(), Error> { - assert!(matches!( - parse_account("wrongaccount"), - Err(super::Error::AccountAddressParsing(..)) - )); - Ok(()) - } - #[test] fn parse_hex_bytes_works() -> Result<(), Error> { let input_in_hex = "48656c6c6f"; diff --git a/crates/pop-parachains/Cargo.toml b/crates/pop-parachains/Cargo.toml index e162e9bdb..969a1fa21 100644 --- a/crates/pop-parachains/Cargo.toml +++ b/crates/pop-parachains/Cargo.toml @@ -22,6 +22,7 @@ tempfile.workspace = true thiserror.workspace = true tokio.workspace = true url.workspace = true +hex.workspace = true askama.workspace = true indexmap.workspace = true diff --git a/crates/pop-parachains/src/call.rs b/crates/pop-parachains/src/call.rs index c220e5cb6..f7df6175a 100644 --- a/crates/pop-parachains/src/call.rs +++ b/crates/pop-parachains/src/call.rs @@ -2,77 +2,125 @@ use crate::errors::Error; use pop_common::create_signer; -use scale_info::{form::PortableForm, Variant}; -use scale_typegen_description::type_description; use scale_value::{stringify, Value}; -use subxt::{metadata::types::StorageEntryType, Metadata, OnlineClient, SubstrateConfig}; +use strum::{EnumMessage as _, EnumProperty as _, VariantArray as _}; +use strum_macros::{AsRefStr, Display, EnumMessage, EnumProperty, EnumString, VariantArray}; +use subxt::{ + config::DefaultExtrinsicParamsBuilder, tx::SubmittableExtrinsic, OnlineClient, SubstrateConfig, +}; +/// A supported pallet. +#[derive(AsRefStr, Clone, Debug, Display, EnumMessage, EnumString, Eq, PartialEq, VariantArray)] +pub enum Pallet { + //Assets. + #[strum(serialize = "Assets")] + Assets, + //Balances. + #[strum(serialize = "Balances")] + Balances, + /// NFT. + #[strum(serialize = "Nfts")] + Nfts, +} +impl Pallet { + /// Get the list of extrinsics available. + pub fn extrinsics(&self) -> Vec<&Extrinsic> { + Extrinsic::VARIANTS + .iter() + .filter(|t| t.get_str("Pallet") == Some(self.as_ref())) + .collect() + } +} -#[derive(Clone, PartialEq, Eq)] -pub struct Storage { - pub name: String, - pub docs: String, - pub ty: (u32, Option), +#[derive( + AsRefStr, + Clone, + Debug, + Display, + EnumMessage, + EnumProperty, + EnumString, + Eq, + PartialEq, + VariantArray, +)] +pub enum Extrinsic { + #[strum(serialize = "create_asset", message = "create", props(Pallet = "Assets"))] + CreateAsset, + #[strum(serialize = "mint_asset", message = "mint", props(Pallet = "Asset"))] + MintAsset, + #[strum(serialize = "create_nft", message = "create", props(Pallet = "Nfts"))] + CreateCollection, + #[strum(serialize = "mint", message = "mint", props(Pallet = "Nft"))] + MintNFT, + #[strum(serialize = "transfer", message = "transfer_allow_death", props(Pallet = "Balances"))] + Transfer, +} +impl Extrinsic { + /// Get the template's name. + fn extrinsic_name(&self) -> &str { + self.get_message().unwrap_or_default() + } + /// Get the pallet of the extrinsic. + pub fn pallet(&self) -> Result<&str, Error> { + self.get_str("Pallet").ok_or(Error::PalletMissing) + } } -#[derive(Clone, PartialEq, Eq)] -/// Describes a contract message. -pub struct Pallet { - /// The label of the message. - pub label: String, - /// The message documentation. - pub docs: String, - // The extrinsics of the pallet. - pub extrinsics: Vec>, - // The storage of the pallet. - pub storage: Vec, +pub fn parse_string_into_scale_value(str: &str) -> Result { + let value = stringify::from_str(str) + .0 + .map_err(|_| Error::ParsingValueError(str.to_string()))?; + Ok(value) } -pub async fn fetch_metadata(url: &str) -> Result { +pub async fn set_up_api(url: &str) -> Result, Error> { let api = OnlineClient::::from_url(url).await?; - Ok(api.metadata()) + Ok(api) } -pub async fn query( +// pub async fn prepare_query( +// api: &OnlineClient, +// pallet_name: &str, +// entry_name: &str, +// args: Vec, +// ) -> Result, Error> { +// //let args = convert_vec(args_value)?; +// let storage = subxt::dynamic::storage(pallet_name, entry_name, args); +// let addr_bytes = api.storage().address_bytes(&storage)?; +// let result = api.storage().at_latest().await?.fetch(&storage).await?; +// Ok(addr_bytes) +// } +fn encode_extrinsic(encoded_call_data: Vec) -> String { + format!("0x{}", hex::encode(encoded_call_data)) +} +fn decode_extrinsic(encoded_call_data: String) -> Result, Error> { + let hex_data = encoded_call_data.trim_start_matches("0x"); + Ok(hex::decode(hex_data)?) +} +pub async fn prepare_extrinsic( + api: &OnlineClient, pallet_name: &str, entry_name: &str, - args: Vec, - url: &str, + args_value: Vec, + suri: &str, ) -> Result { - let args_value: Vec = - args.into_iter().map(|v| stringify::from_str(&v).0.unwrap()).collect(); - let api = OnlineClient::::from_url(url).await?; - let storage_query = subxt::dynamic::storage(pallet_name, entry_name, args_value); - let result = api.storage().at_latest().await?.fetch(&storage_query).await?; - if result.is_none() { - Ok("".to_string()) - } else { - Ok(result.unwrap().to_value()?.to_string()) - } + let signer = create_signer(suri)?; + let tx = subxt::dynamic::tx(pallet_name, entry_name, args_value); + let signed_extrinsic: SubmittableExtrinsic> = + api.tx() + .create_signed(&tx, &signer, DefaultExtrinsicParamsBuilder::new().build()) + .await?; + Ok(encode_extrinsic(signed_extrinsic.encoded().to_vec())) } pub async fn submit_extrinsic( - pallet_name: &str, - entry_name: &str, - args: Vec, - url: &str, - suri: &str, + api: OnlineClient, + encoded_extrinsic: String, ) -> Result { - let args_value: Vec = args - .into_iter() - .filter_map(|v| match stringify::from_str(&v).0 { - Ok(value) => Some(value), - Err(_) => None, - }) - .collect(); - let api = OnlineClient::::from_url(url).await?; - let tx = subxt::dynamic::tx(pallet_name, entry_name, args_value); - let signer = create_signer(suri)?; - let result = api - .tx() - .sign_and_submit_then_watch_default(&tx, &signer) - .await? - .wait_for_finalized_success() - .await?; + let extrinsic = decode_extrinsic(encoded_extrinsic)?; + let signed_extrinsic: SubmittableExtrinsic> = + SubmittableExtrinsic::from_bytes(api, extrinsic); + let result = signed_extrinsic.submit_and_watch().await?.wait_for_finalized().await?; Ok(result.extrinsic_hash().to_string()) } @@ -91,8 +139,9 @@ pub async fn parse_chain_metadata(metadata: Metadata) -> Result, Err docs: entry.docs().concat(), ty: match entry.entry_type() { StorageEntryType::Plain(value) => (*value, None), - StorageEntryType::Map { value_ty, key_ty, .. } => - (*value_ty, Some(*key_ty)), + StorageEntryType::Map { value_ty, key_ty, .. } => { + (*value_ty, Some(*key_ty)) + }, }, }) .collect() @@ -135,28 +184,52 @@ pub fn get_type_description( mod tests { use super::*; use anyhow::Result; + use pop_common::parse_account; - #[tokio::test] - async fn query_works() -> Result<()> { - let result = - query("Assets", "Asset", vec!["50".into()], "wss://rpc2.paseo.popnetwork.xyz").await?; - println!("{:?}", result); - // query("Nfts", "Collection", &metadata)?; - // query("Nfts", "NextCollectionId", &metadata)?; + // #[tokio::test] + // async fn query_works() -> Result<()> { + // let api = set_up_api("wss://rpc2.paseo.popnetwork.xyz").await?; + // let result = prepare_query(&api, "Assets", "Asset", vec!["50".into()]).await?; + // println!("{:?}", result); + // // query("Nfts", "Collection", &metadata)?; + // // query("Nfts", "NextCollectionId", &metadata)?; - Ok(()) - } + // Ok(()) + // } #[tokio::test] async fn extrinsic_works() -> Result<()> { - let result = query( - "Balances", - "TransferAllowDeath", - vec!["167Y1SbQrwQVNfkNUXtRkocfzVbaAHYjnZPkZRScWPQ46XDb".into(), "1".into()], - "wss://rpc2.paseo.popnetwork.xyz", + let api = set_up_api("ws://127.0.0.1:53677").await?; + // let result = prepare_extrinsic( + // &api, + // "Nfts", + // "mint", + // vec![ + // "1".into(), + // "1".into(), + // "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty".into(), + // "None".into(), + // ], + // "//Alice", + // ) + // .await?; + let bob = parse_account("5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty")?; + let result = prepare_extrinsic( + &api, + "Assets", + "create", + vec![ + Value::u128(3), + Value::unnamed_variant("Id", vec![Value::from_bytes(bob)]), + Value::u128(1000000), + ], + "//Alice", ) .await?; - println!("{:?}", result); + //println!("{:?}", result); + println!("{:?}", format!("0x{}", hex::encode(result))); + // let rs = submit_extrinsic(api, result).await?; + // println!("{:?}", rs); // query("Nfts", "Collection", &metadata)?; // query("Nfts", "NextCollectionId", &metadata)?; diff --git a/crates/pop-parachains/src/errors.rs b/crates/pop-parachains/src/errors.rs index 7ddb9dd75..6c7b55b95 100644 --- a/crates/pop-parachains/src/errors.rs +++ b/crates/pop-parachains/src/errors.rs @@ -10,6 +10,8 @@ pub enum Error { Aborted, #[error("Anyhow error: {0}")] AnyhowError(#[from] anyhow::Error), + #[error("The `Call` property is missing from the call variant")] + CallMissing, #[error("{0}")] CommonError(#[from] pop_common::Error), #[error("Configuration error: {0}")] @@ -18,6 +20,8 @@ pub enum Error { CurrentDirAccess, #[error("Failed to parse the endowment value")] EndowmentError, + #[error("Failed decode a value")] + FromHexError(#[from] hex::FromHexError), #[error("IO error: {0}")] IO(#[from] std::io::Error), #[error("JSON error: {0}")] @@ -34,10 +38,14 @@ pub enum Error { OrchestratorError(#[from] OrchestratorError), #[error("Failed to create pallet directory")] PalletDirCreation, + #[error("The `Pallet` property is missing from the call variant")] + PalletMissing, #[error("Failed to find the pallet {0}")] PalletNotFound(String), #[error("Failed to parse the response")] ParsingResponseError(#[from] scale_decode::Error), + #[error("Failed to parse the argument {0}")] + ParsingValueError(String), #[error("Invalid path")] PathError, #[error("Failed to execute rustfmt")] diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index cb979b3fc..73604afff 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -16,14 +16,15 @@ pub use build::{ generate_plain_chain_spec, generate_raw_chain_spec, is_supported, ChainSpec, }; pub use call::{ - fetch_metadata, get_type_description, parse_chain_metadata, query, submit_extrinsic, + parse_string_into_scale_value, prepare_extrinsic, set_up_api, submit_extrinsic, Extrinsic, + Pallet, }; pub use errors::Error; pub use indexmap::IndexSet; pub use new_pallet::{create_pallet_template, new_pallet_options::*, TemplatePalletConfig}; pub use new_parachain::instantiate_template_dir; // External export from subxt. -pub use subxt::{dynamic::Value, Metadata}; +pub use subxt::{dynamic::Value, Metadata, OnlineClient, SubstrateConfig}; pub use templates::{Config, Parachain, Provider}; pub use up::Zombienet; pub use utils::helpers::is_initial_endowment_valid; From aeb6ed32d6acf2470b6f4c32d4f04649d880fb59 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 24 Sep 2024 16:17:05 +0200 Subject: [PATCH 127/211] fix: calls working --- Cargo.lock | 1 - Cargo.toml | 1 - crates/pop-cli/src/commands/call/mod.rs | 2 - crates/pop-cli/src/commands/call/parachain.rs | 244 +++++++++++++----- crates/pop-cli/src/commands/call/use_cases.rs | 113 -------- crates/pop-parachains/Cargo.toml | 1 - crates/pop-parachains/src/call.rs | 230 +++++++++-------- crates/pop-parachains/src/lib.rs | 5 +- 8 files changed, 308 insertions(+), 289 deletions(-) delete mode 100644 crates/pop-cli/src/commands/call/use_cases.rs diff --git a/Cargo.lock b/Cargo.lock index a5ef17363..ed26649a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4773,7 +4773,6 @@ dependencies = [ "reqwest 0.12.5", "scale-info", "scale-typegen-description", - "scale-value", "serde_json", "strum 0.26.3", "strum_macros 0.26.4", diff --git a/Cargo.toml b/Cargo.toml index 0d228b680..81148c56f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,6 @@ contract-extrinsics = "5.0.0-alpha" contract-transcode = "5.0.0-alpha" scale-info = { version = "2.11.3", default-features = false, features = ["derive"] } scale-typegen-description = "0.8.0" -scale-value = { version = "0.16.2", default-features = false } heck = "0.5.0" hex = { version = "0.4.3", default-features = false } diff --git a/crates/pop-cli/src/commands/call/mod.rs b/crates/pop-cli/src/commands/call/mod.rs index 04f70634b..241ebbdb9 100644 --- a/crates/pop-cli/src/commands/call/mod.rs +++ b/crates/pop-cli/src/commands/call/mod.rs @@ -6,8 +6,6 @@ use clap::{Args, Subcommand}; pub(crate) mod contract; #[cfg(feature = "parachain")] pub(crate) mod parachain; -#[cfg(feature = "parachain")] -pub(crate) mod use_cases; /// Arguments for calling a smart contract. #[derive(Args)] diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index fd974a877..846bc64d9 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -3,16 +3,16 @@ use crate::cli::{self, traits::*}; use anyhow::Result; use clap::Args; +use pop_common::parse_account; use pop_parachains::{ - prepare_extrinsic, set_up_api, submit_extrinsic, OnlineClient, Pallet, SubstrateConfig, + prepare_extrinsic, set_up_api, submit_extrinsic, Extrinsic, OnlineClient, Pallet, + SubstrateConfig, Value, }; use strum::VariantArray; -use super::use_cases::prompt_arguments; - #[derive(Args, Clone)] pub struct CallParachainCommand { - /// The signed extrinsic to call. + /// The signed extrinsic to submit. #[clap(long, short)] extrinsic: Option, /// Websocket endpoint of a node. @@ -25,6 +25,9 @@ pub struct CallParachainCommand { /// - with a password "//Alice///SECRET_PASSWORD" #[clap(name = "suri", long, short, default_value = "//Alice")] suri: String, + // pallet: Option, + // ext: Option, + // args: Option>, } impl CallParachainCommand { @@ -48,8 +51,8 @@ impl CallParachainCommand { let url: String = if self.extrinsic.is_none() { // Prompt for contract location. cli.input("Which chain would you like to interact with?") - .placeholder("wss://rpc1.paseo.popnetwork.xyz") - .default_input("wss://rpc1.paseo.popnetwork.xyz") + .placeholder("ws://127.0.0.1:53677") + .default_input("ws://127.0.0.1:53677") .interact()? } else { self.url.clone() @@ -63,7 +66,7 @@ impl CallParachainCommand { if let Some(extrinsic) = &self.extrinsic { full_message.push_str(&format!(" --extrinsic {}", extrinsic)); } - full_message.push_str(&format!("--url {}", self.url)); + full_message.push_str(&format!(" --url {}", self.url)); if !self.suri.is_empty() { full_message.push_str(&format!(" --suri {}", self.suri)); } @@ -77,51 +80,37 @@ async fn guide_user_to_call_chain( url: String, cli: &mut impl cli::traits::Cli, ) -> anyhow::Result { - let pallets = Pallet::VARIANTS; - let pallet = { - let mut prompt = cli.select("Select the pallet to call:"); - for pallet_item in pallets { - prompt = prompt.item(pallet_item.clone(), pallet_item.as_ref(), ""); - } - prompt.interact()? - }; + // let pallets = Pallet::VARIANTS; + // let pallet = { + // let mut prompt = cli.select("Select the pallet to call:"); + // for pallet_item in pallets { + // prompt = prompt.item(pallet_item.clone(), pallet_item.as_ref(), ""); + // } + // prompt.interact()? + // }; let extrinsic = { let mut prompt_extrinsic = cli.select("Select the extrinsic to call:"); - for extrinsic in pallet.extrinsics() { - prompt_extrinsic = - prompt_extrinsic.item(extrinsic.clone(), format!("{}\n", &extrinsic.as_ref()), ""); - } - Ok(CallParachainCommand { - pallet: Some(pallet.label), - extrinsic: Some(extrinsic.name), - query: None, - args, - url: "wss://rpc2.paseo.popnetwork.xyz".to_string(), - suri: "//Alice".to_string(), - }) - } else { - let query = { - let mut prompt_storage = cli.select("Select the storage to query:"); - for storage in pallet.storage { - prompt_storage = prompt_storage.item(storage.clone(), &storage.name, &storage.docs); - } - prompt_storage.interact()? - }; - let keys_needed = get_type_description(query.ty.1, metadata)?; - for key in keys_needed { - let value = cli.input(&format!("Enter the key '{}':", key)).interact()?; - args.push(value); + //for extrinsic in pallet.extrinsics() { + for extrinsic in Extrinsic::VARIANTS { + prompt_extrinsic = prompt_extrinsic.item( + extrinsic.clone(), + extrinsic.description(), + extrinsic.pallet(), + ); } - Ok(CallParachainCommand { - pallet: Some(pallet.label), - extrinsic: None, - query: Some(query.name), - args, - url: "wss://rpc2.paseo.popnetwork.xyz".to_string(), - suri: "//Alice".to_string(), - }) - } + prompt_extrinsic.interact()? + }; + let args = prompt_arguments(&extrinsic, cli)?; + let suri = cli::Cli + .input("Who is going to sign the extrinsic:") + .placeholder("//Alice") + .default_input("//Alice") + .interact()?; + // TODO: Handle error + let encoded_call_data = + prepare_extrinsic(api, extrinsic.pallet(), extrinsic.extrinsic_name(), args, &suri).await?; + Ok(CallParachainCommand { extrinsic: Some(encoded_call_data), url, suri }) } /// Executes the extrinsic or query. @@ -135,12 +124,26 @@ async fn execute_extrinsic( let extrinsic = call_config .extrinsic .expect("extrinsic can not be none as fallback above is interactive input; qed"); - if !cli.confirm("Do you want to sign and submit the call?").interact()? { + if !cli.confirm("Do you want to submit the call?").interact()? { display_message(&format!("Extrinsic: {} not submitted", extrinsic), true, cli)?; return Ok(()); } + let spinner = cliclack::spinner(); + spinner.start("Submitting the extrinsic..."); // TODO: Handle error - let result = submit_extrinsic(api.clone(), extrinsic).await?; + match submit_extrinsic(api.clone(), extrinsic).await { + Ok(result) => { + display_message( + &format!("Extrinsic submitted successfully with hash: {:?}", result), + true, + cli, + )?; + }, + Err(e) => { + display_message(&format!("Error submitting extrinsic: {}", e), false, cli)?; + }, + } + spinner.stop("message"); // Repeat call. if prompt_to_repeat_call { let another_call: bool = cli @@ -152,19 +155,7 @@ async fn execute_extrinsic( console::Term::stderr().clear_last_lines(2)?; let new_call_config = guide_user_to_call_chain(&api, call_config.url, cli).await?; Box::pin(execute_extrinsic(api, new_call_config, prompt_to_repeat_call, cli)).await?; - } else { - display_message( - &format!("Extrinsic submitted successfully with hash: {}", result), - true, - cli, - )?; } - } else { - display_message( - &format!("Extrinsic submitted successfully with hash: {}", result), - true, - cli, - )?; } Ok(()) } @@ -177,3 +168,132 @@ fn display_message(message: &str, success: bool, cli: &mut impl cli::traits::Cli } Ok(()) } +// Prompt the user to select an operation. +fn prompt_arguments(extrinsic: &Extrinsic, cli: &mut impl cli::traits::Cli) -> Result> { + let mut args: Vec = Vec::new(); + match extrinsic { + Extrinsic::CreateAsset => { + args.push(prompt_for_numeric_value("Enter the Asset ID", cli)?); + args.push(prompt_for_account("Enter the Admin Address", cli)?); + args.push(prompt_for_numeric_value("Enter the Minimum Balance", cli)?); + // TODO: ADD METEDATA + }, + Extrinsic::MintAsset => { + args.push(prompt_for_numeric_value("Enter the Asset ID", cli)?); + args.push(prompt_for_account("Enter the Beneficiary Address", cli)?); + args.push(prompt_for_numeric_value("Enter the Amount", cli)?); + }, + Extrinsic::CreateCollection => { + args.push(prompt_for_account("Enter the Admin Address", cli)?); + args.push(prompt_for_collection_config(cli)?); + }, + Extrinsic::MintNFT => { + args.push(prompt_for_numeric_value("Enter the Collection ID", cli)?); + args.push(prompt_for_numeric_value("Enter the Item ID", cli)?); + args.push(prompt_for_account("Enter the Beneficiary Address", cli)?); + args.push(prompt_for_witness_data(cli)?); + }, + Extrinsic::Transfer => { + args.push(prompt_for_account("Enter the Destination Address", cli)?); + args.push(prompt_for_numeric_value("Enter the Amount", cli)?); + }, + } + Ok(args) +} +fn prompt_for_numeric_value(message: &str, cli: &mut impl cli::traits::Cli) -> Result { + let id = cli + .input(message) + .placeholder("0") + .default_input("0") + .validate(|input: &String| match input.parse::() { + Ok(_) => Ok(()), + Err(_) => Err("Invalid value."), + }) + .required(true) + .interact()?; + Ok(Value::u128(id.parse::()?)) +} +fn prompt_for_account(message: &str, cli: &mut impl cli::traits::Cli) -> Result { + let account: String = cli + .input(message) + .placeholder("e.g. 5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") + .required(true) + .interact()?; + let account_id = parse_account(&account)?; + // TODO: Support other Adresses? Let the user pick Id, Address, or Index + Ok(Value::unnamed_variant("Id", vec![Value::from_bytes(account_id)])) +} +fn prompt_for_numeric_optional_value( + message: &str, + cli: &mut impl cli::traits::Cli, +) -> Result { + let value = cli + .input(message) + .placeholder("0 or (empty for None)") + .validate(|input: &String| match input.parse::() { + Ok(_) => Ok(()), + Err(_) => { + if input.is_empty() || input == "None" { + Ok(()) + } else { + Err("Invalid value.") + } + }, + }) + .required(false) + .interact()?; + if value.is_empty() || value == "None" { + Ok(Value::unnamed_variant("None", vec![])) + } else { + Ok(Value::unnamed_variant("Some", vec![Value::u128(value.parse::()?)])) + } +} +fn prompt_for_variant_value( + message: &str, + default_value: &str, + cli: &mut impl cli::traits::Cli, +) -> Result { + let mint_type: String = cli + .input(message) + .placeholder(&format!("e.g. {}", default_value)) + .default_input(default_value) + .required(true) + .interact()?; + Ok(Value::unnamed_variant(mint_type, vec![])) +} +fn prompt_for_collection_config(cli: &mut impl cli::traits::Cli) -> Result { + cli.info("Enter the Pallet NFT Collection Config:")?; + let settings = prompt_for_numeric_value("Collection's Settings", cli)?; + let max_supply = prompt_for_numeric_optional_value("Collection's Max Supply", cli)?; + cli.info("Enter the Mint Settings:")?; + let mint_type = prompt_for_variant_value("Who can mint?", "Issuer", cli)?; + let price_per_mint = prompt_for_numeric_optional_value("Price per mint", cli)?; + let start_block = prompt_for_numeric_optional_value("When the mint starts", cli)?; + let end_block = prompt_for_numeric_optional_value("When the mint ends", cli)?; + let default_item_settings = prompt_for_numeric_value("Default Item Settings", cli)?; + // mint settings + let mint_settings = Value::unnamed_composite(vec![ + mint_type, + price_per_mint, + start_block, + end_block, + default_item_settings, + ]); + let config_collection = Value::unnamed_composite(vec![settings, max_supply, mint_settings]); + + Ok(config_collection) +} +fn prompt_for_witness_data(cli: &mut impl cli::traits::Cli) -> Result { + if cli + .confirm("Do you want to enter witness data for mint") + .initial_value(false) + .interact()? + { + let owned_item = + prompt_for_numeric_optional_value("Id of the item in a required collection:", cli)?; + let mint_price = prompt_for_numeric_optional_value("Mint price:", cli)?; + Ok(Value::unnamed_variant("Some".to_string(), vec![owned_item, mint_price])) + } else { + Ok(Value::unnamed_variant("None", vec![])) + } +} diff --git a/crates/pop-cli/src/commands/call/use_cases.rs b/crates/pop-cli/src/commands/call/use_cases.rs deleted file mode 100644 index 0723a0bb5..000000000 --- a/crates/pop-cli/src/commands/call/use_cases.rs +++ /dev/null @@ -1,113 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 - -use crate::cli::{ - self, - traits::{Confirm as _, Input as _}, -}; -use anyhow::Result; -use pop_common::parse_account; -use pop_parachains::{parse_string_into_scale_value, Extrinsic, Pallet, Value}; - -/// Prompt the user to select an operation. -pub fn prompt_arguments( - extrinsic: &Extrinsic, - cli: &mut impl cli::traits::Cli, -) -> Result> { - match extrinsic { - Extrinsic::CreateAsset => prompt_query_params(&extrinsic, cli), - Extrinsic::MintAsset => prompt_query_params(&extrinsic, cli), - Extrinsic::CreateCollection => prompt_query_params(&extrinsic, cli), - Extrinsic::MintNFT => prompt_query_params(&extrinsic, cli), - Extrinsic::Transfer => prompt_query_params(&extrinsic, cli), - } -} -fn prompt_query_params( - extrinsic: &Extrinsic, - cli: &mut impl cli::traits::Cli, -) -> Result> { - let mut args: Vec = Vec::new(); - let id = cli - .input(&format!( - "Enter the {} id", - if extrinsic.pallet()? == Pallet::Assets.to_string() { "Asset" } else { "Collection" } - )) - .placeholder("0") - .required(false) - .interact()?; - args.push(parse_string_into_scale_value(&id)?); - // if call == &Call::NFTItem { - // let nft_id = cli - // .input("Enter the Nft id") - // .placeholder("0 or None") - // .required(false) - // .interact()?; - // args.push(parse_string_into_scale_value(&nft_id)?); - // } - Ok(args) -} -fn prompt_mint_params( - extrinsic: &Extrinsic, - cli: &mut impl cli::traits::Cli, -) -> Result> { - let mut args: Vec = Vec::new(); - let id = cli - .input(&format!( - "Enter the {} id", - if extrinsic.pallet()? == Pallet::Assets.to_string() { "Asset" } else { "Collection" } - )) - .placeholder("0") - .required(true) - .interact()?; - args.push(parse_string_into_scale_value(&id)?); - if extrinsic == &Extrinsic::MintNFT { - let nft_id = cli - .input(&format!( - "Enter the {} id", - if extrinsic.pallet()? == Pallet::Assets.to_string() { - "Asset" - } else { - "Collection" - } - )) - .placeholder("0") - .required(true) - .interact()?; - args.push(parse_string_into_scale_value(&nft_id)?); - } - // Prompt for beneficiary - let beneficiary: String = cli - .input("Enter the beneficiary address") - .placeholder("e.g. 5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") - .required(true) - .validate(|input: &String| match parse_account(input) { - Ok(_) => Ok(()), - Err(_) => Err("Invalid address."), - }) - .interact()?; - args.push(parse_string_into_scale_value(&beneficiary)?); - // if extrinsic == &Extrinsic::AssetItem { - // let amount = cli.input("Enter the amount").placeholder("0").required(true).interact()?; - // args.push(parse_string_into_scale_value(&amount)?); - // } - if extrinsic == &Extrinsic::MintNFT { - if cli - .confirm("Do you want to include witness data?") - .initial_value(false) - .interact()? - { - let config = {}; - let owned_item = cli - .input("Id of the item in a required collection:") - .placeholder("0 or None") - .required(false) - .interact()?; - let owned_item = cli - .input("The price specified in mint settings:") - .placeholder("0 or None") - .required(false) - .interact()?; - //args.push(Value::u128(id)); - } - } - Ok(args) -} diff --git a/crates/pop-parachains/Cargo.toml b/crates/pop-parachains/Cargo.toml index 969a1fa21..2fe879026 100644 --- a/crates/pop-parachains/Cargo.toml +++ b/crates/pop-parachains/Cargo.toml @@ -29,7 +29,6 @@ indexmap.workspace = true reqwest.workspace = true scale-info.workspace = true scale-typegen-description.workspace = true -scale-value = { workspace = true } subxt.workspace = true symlink.workspace = true toml_edit.workspace = true diff --git a/crates/pop-parachains/src/call.rs b/crates/pop-parachains/src/call.rs index f7df6175a..e4aff734f 100644 --- a/crates/pop-parachains/src/call.rs +++ b/crates/pop-parachains/src/call.rs @@ -1,12 +1,16 @@ // SPDX-License-Identifier: GPL-3.0 use crate::errors::Error; +use clap::builder::Str; use pop_common::create_signer; -use scale_value::{stringify, Value}; +use scale_info::form::PortableForm; use strum::{EnumMessage as _, EnumProperty as _, VariantArray as _}; use strum_macros::{AsRefStr, Display, EnumMessage, EnumProperty, EnumString, VariantArray}; use subxt::{ - config::DefaultExtrinsicParamsBuilder, tx::SubmittableExtrinsic, OnlineClient, SubstrateConfig, + config::DefaultExtrinsicParamsBuilder, + dynamic::Value, + tx::{Payload, SubmittableExtrinsic}, + OnlineClient, SubstrateConfig, }; /// A supported pallet. #[derive(AsRefStr, Clone, Debug, Display, EnumMessage, EnumString, Eq, PartialEq, VariantArray)] @@ -44,59 +48,70 @@ impl Pallet { VariantArray, )] pub enum Extrinsic { - #[strum(serialize = "create_asset", message = "create", props(Pallet = "Assets"))] + #[strum( + serialize = "create_asset", + message = "create", + detailed_message = "Create an Asset", + props(Pallet = "Assets") + )] CreateAsset, - #[strum(serialize = "mint_asset", message = "mint", props(Pallet = "Asset"))] + #[strum( + serialize = "mint_asset", + message = "mint", + detailed_message = "Mint an Asset", + props(Pallet = "Assets") + )] MintAsset, - #[strum(serialize = "create_nft", message = "create", props(Pallet = "Nfts"))] + #[strum( + serialize = "create_nft", + message = "create", + detailed_message = "Create a NFT Collection", + props(Pallet = "Nfts") + )] CreateCollection, - #[strum(serialize = "mint", message = "mint", props(Pallet = "Nft"))] + #[strum( + serialize = "mint", + message = "mint", + detailed_message = "Mint a NFT", + props(Pallet = "Nfts") + )] MintNFT, - #[strum(serialize = "transfer", message = "transfer_allow_death", props(Pallet = "Balances"))] + #[strum( + serialize = "transfer", + message = "transfer_allow_death", + detailed_message = "Transfer", + props(Pallet = "Balances") + )] Transfer, } impl Extrinsic { /// Get the template's name. - fn extrinsic_name(&self) -> &str { + pub fn extrinsic_name(&self) -> &str { self.get_message().unwrap_or_default() } + /// Get the description of the extrinsic. + pub fn description(&self) -> &str { + self.get_detailed_message().unwrap_or_default() + } + /// Get the pallet of the extrinsic. - pub fn pallet(&self) -> Result<&str, Error> { - self.get_str("Pallet").ok_or(Error::PalletMissing) + pub fn pallet(&self) -> &str { + self.get_str("Pallet").unwrap_or_default() } } -pub fn parse_string_into_scale_value(str: &str) -> Result { - let value = stringify::from_str(str) - .0 - .map_err(|_| Error::ParsingValueError(str.to_string()))?; - Ok(value) -} +// pub fn parse_string_into_scale_value(str: &str) -> Result { +// let value = stringify::from_str(str) +// .0 +// .map_err(|_| Error::ParsingValueError(str.to_string()))?; +// Ok(value) +// } pub async fn set_up_api(url: &str) -> Result, Error> { let api = OnlineClient::::from_url(url).await?; Ok(api) } -// pub async fn prepare_query( -// api: &OnlineClient, -// pallet_name: &str, -// entry_name: &str, -// args: Vec, -// ) -> Result, Error> { -// //let args = convert_vec(args_value)?; -// let storage = subxt::dynamic::storage(pallet_name, entry_name, args); -// let addr_bytes = api.storage().address_bytes(&storage)?; -// let result = api.storage().at_latest().await?.fetch(&storage).await?; -// Ok(addr_bytes) -// } -fn encode_extrinsic(encoded_call_data: Vec) -> String { - format!("0x{}", hex::encode(encoded_call_data)) -} -fn decode_extrinsic(encoded_call_data: String) -> Result, Error> { - let hex_data = encoded_call_data.trim_start_matches("0x"); - Ok(hex::decode(hex_data)?) -} pub async fn prepare_extrinsic( api: &OnlineClient, pallet_name: &str, @@ -120,71 +135,55 @@ pub async fn submit_extrinsic( let extrinsic = decode_extrinsic(encoded_extrinsic)?; let signed_extrinsic: SubmittableExtrinsic> = SubmittableExtrinsic::from_bytes(api, extrinsic); - let result = signed_extrinsic.submit_and_watch().await?.wait_for_finalized().await?; + let result = signed_extrinsic.submit_and_watch().await?; Ok(result.extrinsic_hash().to_string()) } -pub async fn parse_chain_metadata(metadata: Metadata) -> Result, Error> { - let mut pallets: Vec = Vec::new(); - for pallet in metadata.pallets() { - let extrinsics = - pallet.call_variants().map(|variants| variants.to_vec()).unwrap_or_default(); // Return an empty Vec if Option is None - let storage: Vec = pallet - .storage() - .map(|m| { - m.entries() - .iter() - .map(|entry| Storage { - name: entry.name().to_string(), - docs: entry.docs().concat(), - ty: match entry.entry_type() { - StorageEntryType::Plain(value) => (*value, None), - StorageEntryType::Map { value_ty, key_ty, .. } => { - (*value_ty, Some(*key_ty)) - }, - }, - }) - .collect() - }) - .unwrap_or_default(); // Return an empty Vec if Option is None - - pallets.push(Pallet { - label: pallet.name().to_string(), - extrinsics, - docs: pallet.docs().join(" "), - storage, - }); - } - Ok(pallets) +fn encode_extrinsic(encoded_call_data: Vec) -> String { + format!("0x{}", hex::encode(encoded_call_data)) +} +fn decode_extrinsic(encoded_call_data: String) -> Result, Error> { + let hex_data = encoded_call_data.trim_start_matches("0x"); + Ok(hex::decode(hex_data)?) } -pub fn get_type_description( - key_ty_id: Option, - metadata: &Metadata, -) -> Result, Error> { - if let Some(key_ty_id) = key_ty_id { - let key_ty_description = type_description(key_ty_id, metadata.types(), false)?; - let result = key_ty_description.trim().trim_matches(|c| c == '(' || c == ')'); - - let parsed_result: Vec = if result == "\"\"" { - vec![] - } else if !result.contains(',') { - vec![result.to_string()] - } else { - result.split(',').map(|s| s.trim().to_string()).collect() - }; - - Ok(parsed_result) - } else { - Ok(vec![]) - } +pub fn fetch_types( + api: &OnlineClient, + pallet_name: &str, + extrinsic: &str, +) -> Result { + let metadata = api.metadata(); + let pallet_metadata = metadata + .pallet_by_name(pallet_name) + .ok_or(Error::PalletNotFound(pallet_name.to_string()))?; + let extrinsic_metadata = pallet_metadata + .call_variant_by_name(extrinsic) + .ok_or(Error::PalletNotFound(pallet_name.to_string()))?; + //println!("{:?}", extrinsic_metadata.fields); + Ok("".to_string()) } #[cfg(test)] mod tests { + use std::vec; + use super::*; use anyhow::Result; use pop_common::parse_account; + use subxt::ext::{ + scale_encode::EncodeAsType, + scale_value::{self, value, Composite, Variant}, + }; + + #[tokio::test] + async fn fetch_works() -> Result<()> { + let api = set_up_api("ws://127.0.0.1:53677").await?; + let a = fetch_types(&api, "Nfts", "mint")?; + let me = api.metadata(); + let ty = me.types().resolve(279); + println!("TYPE {:?}", ty); + Ok(()) + } // #[tokio::test] // async fn query_works() -> Result<()> { @@ -196,40 +195,61 @@ mod tests { // Ok(()) // } - #[tokio::test] async fn extrinsic_works() -> Result<()> { let api = set_up_api("ws://127.0.0.1:53677").await?; + let bob = parse_account("5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty")?; + let alice = parse_account("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY")?; + let owned_item = Value::unnamed_variant("Some".to_string(), vec![Value::u128(1)]); + let mint_price = Value::unnamed_variant("Some".to_string(), vec![Value::u128(1)]); + let mint_witness = Value::unnamed_composite(vec![owned_item, mint_price]); + + let some = Value::unnamed_variant("Some".to_string(), vec![mint_witness]); + + let ni = Value::unnamed_variant( + "None", + vec![], // No fields for `None` + ); // let result = prepare_extrinsic( // &api, // "Nfts", // "mint", // vec![ - // "1".into(), - // "1".into(), - // "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty".into(), - // "None".into(), + // Value::u128(1), + // Value::u128(1), + // Value::unnamed_variant("Id", vec![Value::from_bytes(bob)]), + // ni, // ], // "//Alice", // ) // .await?; - let bob = parse_account("5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty")?; - let result = prepare_extrinsic( + + let max_supply = Value::unnamed_variant("Some".to_string(), vec![Value::u128(1)]); + let mint_type = Value::unnamed_variant("Issuer".to_string(), vec![]); + let price = Value::unnamed_variant("Some".to_string(), vec![Value::u128(1)]); + let start_block = Value::unnamed_variant("Some".to_string(), vec![Value::u128(1)]); + let end_block = Value::unnamed_variant("Some".to_string(), vec![Value::u128(1)]); + let mint_settings = Value::unnamed_composite(vec![ + mint_type, + price, + start_block, + end_block, + Value::u128(1), + ]); + let config_collection = + Value::unnamed_composite(vec![Value::u128(1), max_supply, mint_settings]); + let result2 = prepare_extrinsic( &api, - "Assets", + "Nfts", "create", - vec![ - Value::u128(3), - Value::unnamed_variant("Id", vec![Value::from_bytes(bob)]), - Value::u128(1000000), - ], + vec![Value::unnamed_variant("Id", vec![Value::from_bytes(alice)]), config_collection], "//Alice", ) .await?; - //println!("{:?}", result); - println!("{:?}", format!("0x{}", hex::encode(result))); - // let rs = submit_extrinsic(api, result).await?; - // println!("{:?}", rs); + //println!("{:?}", format!("0x{}", hex::encode(result))); + println!("{:?}", format!("0x{}", hex::encode(result2))); + //let rs = submit_extrinsic(api, result).await?; + //println!("{:?}", rs); // query("Nfts", "Collection", &metadata)?; // query("Nfts", "NextCollectionId", &metadata)?; diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index 73604afff..1a4052851 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -15,10 +15,7 @@ pub use build::{ binary_path, build_parachain, export_wasm_file, generate_genesis_state_file, generate_plain_chain_spec, generate_raw_chain_spec, is_supported, ChainSpec, }; -pub use call::{ - parse_string_into_scale_value, prepare_extrinsic, set_up_api, submit_extrinsic, Extrinsic, - Pallet, -}; +pub use call::{prepare_extrinsic, set_up_api, submit_extrinsic, Extrinsic, Pallet}; pub use errors::Error; pub use indexmap::IndexSet; pub use new_pallet::{create_pallet_template, new_pallet_options::*, TemplatePalletConfig}; From 14730a830f5b268f132600d36e0b3606de215c53 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 24 Sep 2024 17:31:59 +0200 Subject: [PATCH 128/211] refactor: remove unused code --- crates/pop-cli/src/commands/call/parachain.rs | 48 ++++--- crates/pop-parachains/src/call.rs | 120 ++++++------------ crates/pop-parachains/src/lib.rs | 4 +- 3 files changed, 63 insertions(+), 109 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 846bc64d9..a01c26c05 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -1,14 +1,13 @@ // SPDX-License-Identifier: GPL-3.0 use crate::cli::{self, traits::*}; -use anyhow::Result; +use anyhow::{anyhow, Result}; use clap::Args; use pop_common::parse_account; use pop_parachains::{ - prepare_extrinsic, set_up_api, submit_extrinsic, Extrinsic, OnlineClient, Pallet, + prepare_extrinsic, set_up_api, submit_extrinsic, supported_extrinsics, Extrinsic, OnlineClient, SubstrateConfig, Value, }; -use strum::VariantArray; #[derive(Args, Clone)] pub struct CallParachainCommand { @@ -16,7 +15,7 @@ pub struct CallParachainCommand { #[clap(long, short)] extrinsic: Option, /// Websocket endpoint of a node. - #[clap(name = "url", long, value_parser, default_value = "ws://localhost:9944")] + #[clap(name = "url", long, value_parser, default_value = "ws://127.0.0.1:9944")] url: String, /// Secret key URI for the account signing the extrinsic. /// @@ -25,9 +24,6 @@ pub struct CallParachainCommand { /// - with a password "//Alice///SECRET_PASSWORD" #[clap(name = "suri", long, short, default_value = "//Alice")] suri: String, - // pallet: Option, - // ext: Option, - // args: Option>, } impl CallParachainCommand { @@ -35,7 +31,13 @@ impl CallParachainCommand { pub(crate) async fn execute(mut self) -> Result<()> { let (api, url) = self.set_up_api(&mut cli::Cli).await?; let call_config = if self.extrinsic.is_none() { - guide_user_to_call_chain(&api, url, &mut cli::Cli).await? + match guide_user_to_call_chain(&api, url, &mut cli::Cli).await { + Ok(call_config) => call_config, + Err(e) => { + display_message(&format!("{}", e), false, &mut cli::Cli)?; + return Ok(()); + }, + } } else { self.clone() }; @@ -51,8 +53,8 @@ impl CallParachainCommand { let url: String = if self.extrinsic.is_none() { // Prompt for contract location. cli.input("Which chain would you like to interact with?") - .placeholder("ws://127.0.0.1:53677") - .default_input("ws://127.0.0.1:53677") + .placeholder("wss://rpc1.paseo.popnetwork.xyz") + .default_input("wss://rpc1.paseo.popnetwork.xyz") .interact()? } else { self.url.clone() @@ -80,19 +82,10 @@ async fn guide_user_to_call_chain( url: String, cli: &mut impl cli::traits::Cli, ) -> anyhow::Result { - // let pallets = Pallet::VARIANTS; - // let pallet = { - // let mut prompt = cli.select("Select the pallet to call:"); - // for pallet_item in pallets { - // prompt = prompt.item(pallet_item.clone(), pallet_item.as_ref(), ""); - // } - // prompt.interact()? - // }; - let extrinsic = { let mut prompt_extrinsic = cli.select("Select the extrinsic to call:"); //for extrinsic in pallet.extrinsics() { - for extrinsic in Extrinsic::VARIANTS { + for extrinsic in supported_extrinsics(api) { prompt_extrinsic = prompt_extrinsic.item( extrinsic.clone(), extrinsic.description(), @@ -107,10 +100,14 @@ async fn guide_user_to_call_chain( .placeholder("//Alice") .default_input("//Alice") .interact()?; - // TODO: Handle error - let encoded_call_data = - prepare_extrinsic(api, extrinsic.pallet(), extrinsic.extrinsic_name(), args, &suri).await?; - Ok(CallParachainCommand { extrinsic: Some(encoded_call_data), url, suri }) + + match prepare_extrinsic(api, extrinsic.pallet(), extrinsic.extrinsic_name(), args, &suri).await + { + Ok(encoded_call_data) => { + Ok(CallParachainCommand { extrinsic: Some(encoded_call_data), url, suri }) + }, + Err(e) => Err(anyhow!(format!("{}", e.to_string()))), + } } /// Executes the extrinsic or query. @@ -140,10 +137,9 @@ async fn execute_extrinsic( )?; }, Err(e) => { - display_message(&format!("Error submitting extrinsic: {}", e), false, cli)?; + display_message(&format!("{}", e), false, cli)?; }, } - spinner.stop("message"); // Repeat call. if prompt_to_repeat_call { let another_call: bool = cli diff --git a/crates/pop-parachains/src/call.rs b/crates/pop-parachains/src/call.rs index e4aff734f..b45846c73 100644 --- a/crates/pop-parachains/src/call.rs +++ b/crates/pop-parachains/src/call.rs @@ -1,39 +1,13 @@ // SPDX-License-Identifier: GPL-3.0 use crate::errors::Error; -use clap::builder::Str; use pop_common::create_signer; -use scale_info::form::PortableForm; use strum::{EnumMessage as _, EnumProperty as _, VariantArray as _}; use strum_macros::{AsRefStr, Display, EnumMessage, EnumProperty, EnumString, VariantArray}; use subxt::{ - config::DefaultExtrinsicParamsBuilder, - dynamic::Value, - tx::{Payload, SubmittableExtrinsic}, - OnlineClient, SubstrateConfig, + config::DefaultExtrinsicParamsBuilder, dynamic::Value, tx::SubmittableExtrinsic, OnlineClient, + SubstrateConfig, }; -/// A supported pallet. -#[derive(AsRefStr, Clone, Debug, Display, EnumMessage, EnumString, Eq, PartialEq, VariantArray)] -pub enum Pallet { - //Assets. - #[strum(serialize = "Assets")] - Assets, - //Balances. - #[strum(serialize = "Balances")] - Balances, - /// NFT. - #[strum(serialize = "Nfts")] - Nfts, -} -impl Pallet { - /// Get the list of extrinsics available. - pub fn extrinsics(&self) -> Vec<&Extrinsic> { - Extrinsic::VARIANTS - .iter() - .filter(|t| t.get_str("Pallet") == Some(self.as_ref())) - .collect() - } -} #[derive( AsRefStr, @@ -99,13 +73,12 @@ impl Extrinsic { self.get_str("Pallet").unwrap_or_default() } } - -// pub fn parse_string_into_scale_value(str: &str) -> Result { -// let value = stringify::from_str(str) -// .0 -// .map_err(|_| Error::ParsingValueError(str.to_string()))?; -// Ok(value) -// } +pub fn supported_extrinsics(api: &OnlineClient) -> Vec<&Extrinsic> { + Extrinsic::VARIANTS + .iter() + .filter(|t| extrinsic_is_supported(api, t.pallet(), t.extrinsic_name())) + .collect() +} pub async fn set_up_api(url: &str) -> Result, Error> { let api = OnlineClient::::from_url(url).await?; @@ -147,57 +120,42 @@ fn decode_extrinsic(encoded_call_data: String) -> Result, Error> { Ok(hex::decode(hex_data)?) } -pub fn fetch_types( +fn extrinsic_is_supported( api: &OnlineClient, pallet_name: &str, extrinsic: &str, -) -> Result { +) -> bool { let metadata = api.metadata(); - let pallet_metadata = metadata - .pallet_by_name(pallet_name) - .ok_or(Error::PalletNotFound(pallet_name.to_string()))?; - let extrinsic_metadata = pallet_metadata - .call_variant_by_name(extrinsic) - .ok_or(Error::PalletNotFound(pallet_name.to_string()))?; - //println!("{:?}", extrinsic_metadata.fields); - Ok("".to_string()) + // Try to get the pallet metadata by name + let pallet_metadata = match metadata.pallet_by_name(pallet_name) { + Some(pallet) => pallet, + None => return false, // Return false if pallet is not found + }; + // Try to get the extrinsic metadata by name from the pallet + match pallet_metadata.call_variant_by_name(extrinsic) { + Some(_) => true, // Return true if extrinsic is found + None => false, // Return false if extrinsic is not found + } } #[cfg(test)] mod tests { - use std::vec; - use super::*; use anyhow::Result; use pop_common::parse_account; - use subxt::ext::{ - scale_encode::EncodeAsType, - scale_value::{self, value, Composite, Variant}, - }; + use std::vec; #[tokio::test] - async fn fetch_works() -> Result<()> { - let api = set_up_api("ws://127.0.0.1:53677").await?; - let a = fetch_types(&api, "Nfts", "mint")?; - let me = api.metadata(); - let ty = me.types().resolve(279); - println!("TYPE {:?}", ty); + async fn extrinsic_is_supported_works() -> Result<()> { + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + assert!(extrinsic_is_supported(&api, "Nfts", "mint")); + assert!(!extrinsic_is_supported(&api, "Nfts", "mint_no_exist")); Ok(()) } - // #[tokio::test] - // async fn query_works() -> Result<()> { - // let api = set_up_api("wss://rpc2.paseo.popnetwork.xyz").await?; - // let result = prepare_query(&api, "Assets", "Asset", vec!["50".into()]).await?; - // println!("{:?}", result); - // // query("Nfts", "Collection", &metadata)?; - // // query("Nfts", "NextCollectionId", &metadata)?; - - // Ok(()) - // } #[tokio::test] async fn extrinsic_works() -> Result<()> { - let api = set_up_api("ws://127.0.0.1:53677").await?; + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; let bob = parse_account("5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty")?; let alice = parse_account("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY")?; let owned_item = Value::unnamed_variant("Some".to_string(), vec![Value::u128(1)]); @@ -210,19 +168,19 @@ mod tests { "None", vec![], // No fields for `None` ); - // let result = prepare_extrinsic( - // &api, - // "Nfts", - // "mint", - // vec![ - // Value::u128(1), - // Value::u128(1), - // Value::unnamed_variant("Id", vec![Value::from_bytes(bob)]), - // ni, - // ], - // "//Alice", - // ) - // .await?; + let result = prepare_extrinsic( + &api, + "Nfts", + "mint", + vec![ + Value::u128(1), + Value::u128(1), + Value::unnamed_variant("Id", vec![Value::from_bytes(bob)]), + ni, + ], + "//Alice", + ) + .await?; let max_supply = Value::unnamed_variant("Some".to_string(), vec![Value::u128(1)]); let mint_type = Value::unnamed_variant("Issuer".to_string(), vec![]); diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index 1a4052851..76c597692 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -15,13 +15,13 @@ pub use build::{ binary_path, build_parachain, export_wasm_file, generate_genesis_state_file, generate_plain_chain_spec, generate_raw_chain_spec, is_supported, ChainSpec, }; -pub use call::{prepare_extrinsic, set_up_api, submit_extrinsic, Extrinsic, Pallet}; +pub use call::{prepare_extrinsic, set_up_api, submit_extrinsic, supported_extrinsics, Extrinsic}; pub use errors::Error; pub use indexmap::IndexSet; pub use new_pallet::{create_pallet_template, new_pallet_options::*, TemplatePalletConfig}; pub use new_parachain::instantiate_template_dir; // External export from subxt. -pub use subxt::{dynamic::Value, Metadata, OnlineClient, SubstrateConfig}; +pub use subxt::{dynamic::Value, OnlineClient, SubstrateConfig}; pub use templates::{Config, Parachain, Provider}; pub use up::Zombienet; pub use utils::helpers::is_initial_endowment_valid; From ab17a671b338013bbbb4e28ade3aa1ba4c4f8429 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 24 Sep 2024 17:50:44 +0200 Subject: [PATCH 129/211] refactor: remove unused code --- crates/pop-parachains/src/call.rs | 61 ------------------------------- 1 file changed, 61 deletions(-) diff --git a/crates/pop-parachains/src/call.rs b/crates/pop-parachains/src/call.rs index b45846c73..76b47162a 100644 --- a/crates/pop-parachains/src/call.rs +++ b/crates/pop-parachains/src/call.rs @@ -152,65 +152,4 @@ mod tests { assert!(!extrinsic_is_supported(&api, "Nfts", "mint_no_exist")); Ok(()) } - - #[tokio::test] - async fn extrinsic_works() -> Result<()> { - let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; - let bob = parse_account("5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty")?; - let alice = parse_account("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY")?; - let owned_item = Value::unnamed_variant("Some".to_string(), vec![Value::u128(1)]); - let mint_price = Value::unnamed_variant("Some".to_string(), vec![Value::u128(1)]); - let mint_witness = Value::unnamed_composite(vec![owned_item, mint_price]); - - let some = Value::unnamed_variant("Some".to_string(), vec![mint_witness]); - - let ni = Value::unnamed_variant( - "None", - vec![], // No fields for `None` - ); - let result = prepare_extrinsic( - &api, - "Nfts", - "mint", - vec![ - Value::u128(1), - Value::u128(1), - Value::unnamed_variant("Id", vec![Value::from_bytes(bob)]), - ni, - ], - "//Alice", - ) - .await?; - - let max_supply = Value::unnamed_variant("Some".to_string(), vec![Value::u128(1)]); - let mint_type = Value::unnamed_variant("Issuer".to_string(), vec![]); - let price = Value::unnamed_variant("Some".to_string(), vec![Value::u128(1)]); - let start_block = Value::unnamed_variant("Some".to_string(), vec![Value::u128(1)]); - let end_block = Value::unnamed_variant("Some".to_string(), vec![Value::u128(1)]); - let mint_settings = Value::unnamed_composite(vec![ - mint_type, - price, - start_block, - end_block, - Value::u128(1), - ]); - let config_collection = - Value::unnamed_composite(vec![Value::u128(1), max_supply, mint_settings]); - let result2 = prepare_extrinsic( - &api, - "Nfts", - "create", - vec![Value::unnamed_variant("Id", vec![Value::from_bytes(alice)]), config_collection], - "//Alice", - ) - .await?; - //println!("{:?}", format!("0x{}", hex::encode(result))); - println!("{:?}", format!("0x{}", hex::encode(result2))); - //let rs = submit_extrinsic(api, result).await?; - //println!("{:?}", rs); - // query("Nfts", "Collection", &metadata)?; - // query("Nfts", "NextCollectionId", &metadata)?; - - Ok(()) - } } From c0aab357adcf3021c3b173850d6e12f94a9b5b80 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 24 Sep 2024 19:20:05 +0200 Subject: [PATCH 130/211] refactor: various fixes --- crates/pop-cli/src/commands/call/parachain.rs | 15 +++++++-------- crates/pop-common/src/lib.rs | 10 ++++------ crates/pop-parachains/src/call.rs | 10 ++++------ 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index a01c26c05..9d549ed19 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -103,9 +103,8 @@ async fn guide_user_to_call_chain( match prepare_extrinsic(api, extrinsic.pallet(), extrinsic.extrinsic_name(), args, &suri).await { - Ok(encoded_call_data) => { - Ok(CallParachainCommand { extrinsic: Some(encoded_call_data), url, suri }) - }, + Ok(encoded_call_data) => + Ok(CallParachainCommand { extrinsic: Some(encoded_call_data), url, suri }), Err(e) => Err(anyhow!(format!("{}", e.to_string()))), } } @@ -127,9 +126,9 @@ async fn execute_extrinsic( } let spinner = cliclack::spinner(); spinner.start("Submitting the extrinsic..."); - // TODO: Handle error match submit_extrinsic(api.clone(), extrinsic).await { Ok(result) => { + console::Term::stderr().clear_last_lines(1)?; display_message( &format!("Extrinsic submitted successfully with hash: {:?}", result), true, @@ -137,6 +136,7 @@ async fn execute_extrinsic( )?; }, Err(e) => { + console::Term::stderr().clear_last_lines(1)?; display_message(&format!("{}", e), false, cli)?; }, } @@ -172,7 +172,7 @@ fn prompt_arguments(extrinsic: &Extrinsic, cli: &mut impl cli::traits::Cli) -> R args.push(prompt_for_numeric_value("Enter the Asset ID", cli)?); args.push(prompt_for_account("Enter the Admin Address", cli)?); args.push(prompt_for_numeric_value("Enter the Minimum Balance", cli)?); - // TODO: ADD METEDATA + // TODO: ADD METADATA }, Extrinsic::MintAsset => { args.push(prompt_for_numeric_value("Enter the Asset ID", cli)?); @@ -228,13 +228,12 @@ fn prompt_for_numeric_optional_value( .placeholder("0 or (empty for None)") .validate(|input: &String| match input.parse::() { Ok(_) => Ok(()), - Err(_) => { + Err(_) => if input.is_empty() || input == "None" { Ok(()) } else { Err("Invalid value.") - } - }, + }, }) .required(false) .interact()?; diff --git a/crates/pop-common/src/lib.rs b/crates/pop-common/src/lib.rs index 4b24729b4..1f527d15f 100644 --- a/crates/pop-common/src/lib.rs +++ b/crates/pop-common/src/lib.rs @@ -41,18 +41,16 @@ pub fn target() -> Result<&'static str, Error> { } match ARCH { - "aarch64" => { + "aarch64" => return match OS { "macos" => Ok("aarch64-apple-darwin"), _ => Ok("aarch64-unknown-linux-gnu"), - } - }, - "x86_64" | "x86" => { + }, + "x86_64" | "x86" => return match OS { "macos" => Ok("x86_64-apple-darwin"), _ => Ok("x86_64-unknown-linux-gnu"), - } - }, + }, &_ => {}, } Err(Error::UnsupportedPlatform { arch: ARCH, os: OS }) diff --git a/crates/pop-parachains/src/call.rs b/crates/pop-parachains/src/call.rs index 76b47162a..066569883 100644 --- a/crates/pop-parachains/src/call.rs +++ b/crates/pop-parachains/src/call.rs @@ -109,7 +109,7 @@ pub async fn submit_extrinsic( let signed_extrinsic: SubmittableExtrinsic> = SubmittableExtrinsic::from_bytes(api, extrinsic); let result = signed_extrinsic.submit_and_watch().await?; - Ok(result.extrinsic_hash().to_string()) + Ok(format!("{:?}", result.extrinsic_hash())) } fn encode_extrinsic(encoded_call_data: Vec) -> String { @@ -132,18 +132,16 @@ fn extrinsic_is_supported( None => return false, // Return false if pallet is not found }; // Try to get the extrinsic metadata by name from the pallet - match pallet_metadata.call_variant_by_name(extrinsic) { - Some(_) => true, // Return true if extrinsic is found - None => false, // Return false if extrinsic is not found + if pallet_metadata.call_variant_by_name(extrinsic).is_some() { + return true; } + false } #[cfg(test)] mod tests { use super::*; use anyhow::Result; - use pop_common::parse_account; - use std::vec; #[tokio::test] async fn extrinsic_is_supported_works() -> Result<()> { From 418fdcae87e4d94e8b1eb5b06bf9c23e353d76d6 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 24 Sep 2024 22:23:07 +0200 Subject: [PATCH 131/211] refactor: various fixes --- Cargo.lock | 25 ------------------------- Cargo.toml | 1 - crates/pop-parachains/Cargo.toml | 1 - crates/pop-parachains/src/call.rs | 2 +- 4 files changed, 1 insertion(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ed26649a3..07f824cd1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4417,12 +4417,6 @@ dependencies = [ "password-hash", ] -[[package]] -name = "peekmore" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9163e1259760e83d528d1b3171e5100c1767f10c52e1c4d6afad26e63d47d758" - [[package]] name = "pem" version = "3.0.4" @@ -4772,7 +4766,6 @@ dependencies = [ "pop-common", "reqwest 0.12.5", "scale-info", - "scale-typegen-description", "serde_json", "strum 0.26.3", "strum_macros 0.26.4", @@ -5704,24 +5697,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "scale-typegen-description" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff4cabffc407f6378bc7a164fab8280bfcd862b2dd063cc5c9914a520ea8566" -dependencies = [ - "anyhow", - "peekmore", - "proc-macro2", - "quote", - "rand", - "rand_chacha", - "scale-info", - "scale-typegen", - "scale-value", - "smallvec", -] - [[package]] name = "scale-value" version = "0.16.2" diff --git a/Cargo.toml b/Cargo.toml index 81148c56f..2e1b68ae5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,6 @@ contract-build = "5.0.0-alpha" contract-extrinsics = "5.0.0-alpha" contract-transcode = "5.0.0-alpha" scale-info = { version = "2.11.3", default-features = false, features = ["derive"] } -scale-typegen-description = "0.8.0" heck = "0.5.0" hex = { version = "0.4.3", default-features = false } diff --git a/crates/pop-parachains/Cargo.toml b/crates/pop-parachains/Cargo.toml index 2fe879026..7ec7be467 100644 --- a/crates/pop-parachains/Cargo.toml +++ b/crates/pop-parachains/Cargo.toml @@ -28,7 +28,6 @@ askama.workspace = true indexmap.workspace = true reqwest.workspace = true scale-info.workspace = true -scale-typegen-description.workspace = true subxt.workspace = true symlink.workspace = true toml_edit.workspace = true diff --git a/crates/pop-parachains/src/call.rs b/crates/pop-parachains/src/call.rs index 066569883..efb964142 100644 --- a/crates/pop-parachains/src/call.rs +++ b/crates/pop-parachains/src/call.rs @@ -53,7 +53,7 @@ pub enum Extrinsic { #[strum( serialize = "transfer", message = "transfer_allow_death", - detailed_message = "Transfer", + detailed_message = "Transfer Balance", props(Pallet = "Balances") )] Transfer, From 7af045afc990586c19b45d7e4d4a6c6698d74c5a Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 27 Sep 2024 11:44:58 +0200 Subject: [PATCH 132/211] feat: add option to include params from command line --- crates/pop-cli/src/commands/call/parachain.rs | 245 +++++++++++------- crates/pop-contracts/src/utils/mod.rs | 4 +- crates/pop-parachains/src/call.rs | 233 ++++++++++++++--- crates/pop-parachains/src/errors.rs | 10 +- crates/pop-parachains/src/lib.rs | 7 +- 5 files changed, 364 insertions(+), 135 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 9d549ed19..884db4618 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -3,17 +3,22 @@ use crate::cli::{self, traits::*}; use anyhow::{anyhow, Result}; use clap::Args; -use pop_common::parse_account; use pop_parachains::{ - prepare_extrinsic, set_up_api, submit_extrinsic, supported_extrinsics, Extrinsic, OnlineClient, - SubstrateConfig, Value, + construct_extrinsic, encode_call_data, set_up_api, sign_and_submit_extrinsic, + supported_extrinsics, DynamicPayload, Extrinsic, OnlineClient, SubstrateConfig, }; #[derive(Args, Clone)] pub struct CallParachainCommand { - /// The signed extrinsic to submit. + /// The name of the pallet to call. + #[clap(long, short)] + pallet: Option, + /// The name of the extrinsic to submit. #[clap(long, short)] extrinsic: Option, + /// The constructor arguments, encoded as strings. + #[clap(long, num_args = 0..)] + args: Vec, /// Websocket endpoint of a node. #[clap(name = "url", long, value_parser, default_value = "ws://127.0.0.1:9944")] url: String, @@ -30,8 +35,9 @@ impl CallParachainCommand { /// Executes the command. pub(crate) async fn execute(mut self) -> Result<()> { let (api, url) = self.set_up_api(&mut cli::Cli).await?; - let call_config = if self.extrinsic.is_none() { - match guide_user_to_call_chain(&api, url, &mut cli::Cli).await { + // TODO: Option to send call_data, sign it and send it. + let mut call_config = if self.pallet.is_none() && self.extrinsic.is_none() { + match guide_user_to_call_chain(&api, "", &url, &mut cli::Cli).await { Ok(call_config) => call_config, Err(e) => { display_message(&format!("{}", e), false, &mut cli::Cli)?; @@ -41,7 +47,13 @@ impl CallParachainCommand { } else { self.clone() }; - execute_extrinsic(api, call_config, self.extrinsic.is_none(), &mut cli::Cli).await?; + prepare_and_submit_extrinsic( + api, + &mut call_config, + self.pallet.is_none() && self.extrinsic.is_none(), + &mut cli::Cli, + ) + .await?; Ok(()) } /// Prompt the user for the chain to use if not indicated and fetch the metadata. @@ -50,7 +62,7 @@ impl CallParachainCommand { cli: &mut impl cli::traits::Cli, ) -> anyhow::Result<(OnlineClient, String)> { cli.intro("Call a parachain")?; - let url: String = if self.extrinsic.is_none() { + let url: String = if self.pallet.is_none() && self.extrinsic.is_none() { // Prompt for contract location. cli.input("Which chain would you like to interact with?") .placeholder("wss://rpc1.paseo.popnetwork.xyz") @@ -65,9 +77,15 @@ impl CallParachainCommand { fn display(&self) -> String { let mut full_message = "pop call parachain".to_string(); + if let Some(pallet) = &self.pallet { + full_message.push_str(&format!(" --pallet {}", pallet)); + } if let Some(extrinsic) = &self.extrinsic { full_message.push_str(&format!(" --extrinsic {}", extrinsic)); } + if self.args.len() > 0 { + full_message.push_str(&format!(" --args {}", self.args.join(" "))); + } full_message.push_str(&format!(" --url {}", self.url)); if !self.suri.is_empty() { full_message.push_str(&format!(" --suri {}", self.suri)); @@ -79,11 +97,12 @@ impl CallParachainCommand { /// Guide the user to call the contract. async fn guide_user_to_call_chain( api: &OnlineClient, - url: String, + suri: &str, + url: &str, cli: &mut impl cli::traits::Cli, ) -> anyhow::Result { let extrinsic = { - let mut prompt_extrinsic = cli.select("Select the extrinsic to call:"); + let mut prompt_extrinsic = cli.select("What would you like to do on Polkadot?"); //for extrinsic in pallet.extrinsics() { for extrinsic in supported_extrinsics(api) { prompt_extrinsic = prompt_extrinsic.item( @@ -95,62 +114,102 @@ async fn guide_user_to_call_chain( prompt_extrinsic.interact()? }; let args = prompt_arguments(&extrinsic, cli)?; - let suri = cli::Cli - .input("Who is going to sign the extrinsic:") - .placeholder("//Alice") - .default_input("//Alice") - .interact()?; - match prepare_extrinsic(api, extrinsic.pallet(), extrinsic.extrinsic_name(), args, &suri).await - { - Ok(encoded_call_data) => - Ok(CallParachainCommand { extrinsic: Some(encoded_call_data), url, suri }), - Err(e) => Err(anyhow!(format!("{}", e.to_string()))), - } + Ok(CallParachainCommand { + pallet: Some(extrinsic.pallet().to_string()), + extrinsic: Some(extrinsic.extrinsic_name().to_string()), + args, + url: url.to_string(), + suri: suri.to_string(), + }) } -/// Executes the extrinsic or query. -async fn execute_extrinsic( +/// Prepares the extrinsic or query. +async fn prepare_and_submit_extrinsic( api: OnlineClient, - call_config: CallParachainCommand, + call_config: &mut CallParachainCommand, prompt_to_repeat_call: bool, cli: &mut impl cli::traits::Cli, ) -> Result<()> { - cli.info(call_config.display())?; - let extrinsic = call_config + let extrinsic: String = call_config .extrinsic + .clone() .expect("extrinsic can not be none as fallback above is interactive input; qed"); - if !cli.confirm("Do you want to submit the call?").interact()? { - display_message(&format!("Extrinsic: {} not submitted", extrinsic), true, cli)?; - return Ok(()); - } - let spinner = cliclack::spinner(); - spinner.start("Submitting the extrinsic..."); - match submit_extrinsic(api.clone(), extrinsic).await { - Ok(result) => { - console::Term::stderr().clear_last_lines(1)?; - display_message( - &format!("Extrinsic submitted successfully with hash: {:?}", result), - true, - cli, - )?; + let pallet = match call_config.pallet.clone() { + Some(m) => m, + None => { + return Err(anyhow!("Please specify the pallet to call.")); }, + }; + let tx = match construct_extrinsic(&pallet, &extrinsic, &call_config.args) { + Ok(tx) => tx, Err(e) => { - console::Term::stderr().clear_last_lines(1)?; - display_message(&format!("{}", e), false, cli)?; + display_message( + &format!("Error parsing the arguments: {}", e.to_string()), + false, + &mut cli::Cli, + )?; + return Ok(()); }, - } - // Repeat call. - if prompt_to_repeat_call { - let another_call: bool = cli - .confirm("Do you want to do another call to the same chain?") - .initial_value(false) + }; + cli.info(format!("Call data {}", encode_call_data(&api, &tx)?))?; + if call_config.suri.is_empty() { + call_config.suri = cli::Cli + .input("Who is going to sign the extrinsic:") + .placeholder("//Alice") + .default_input("//Alice") .interact()?; - if another_call { - // Remove only the prompt asking for another call. - console::Term::stderr().clear_last_lines(2)?; - let new_call_config = guide_user_to_call_chain(&api, call_config.url, cli).await?; - Box::pin(execute_extrinsic(api, new_call_config, prompt_to_repeat_call, cli)).await?; + } + cli.info(call_config.display())?; + if !cli.confirm("Do you want to submit the call?").initial_value(true).interact()? { + display_message( + &format!("Extrinsic: {} not submitted, user cancel the operation", extrinsic), + false, + cli, + )?; + return Ok(()); + } + send_extrinsic(api, tx, &call_config.url, &call_config.suri, prompt_to_repeat_call, cli) + .await?; + + Ok(()) +} + +async fn send_extrinsic( + api: OnlineClient, + tx: DynamicPayload, + url: &str, + suri: &str, + prompt_to_repeat_call: bool, + cli: &mut impl cli::traits::Cli, +) -> Result<()> { + let spinner = cliclack::spinner(); + spinner.start("Signing and submitting the extrinsic, please wait..."); + let result = sign_and_submit_extrinsic(api.clone(), tx, suri).await; + if let Err(e) = result { + console::Term::stderr().clear_last_lines(1)?; + display_message(&format!("{}", e), false, cli)?; + } else { + console::Term::stderr().clear_last_lines(1)?; + display_message(&format!("Extrinsic submitted with hash: {:?}", result?), true, cli)?; + // Repeat call. + if prompt_to_repeat_call { + let another_call: bool = cli + .confirm("Do you want to do another call to the same chain?") + .initial_value(false) + .interact()?; + if another_call { + // Remove only the prompt asking for another call. + console::Term::stderr().clear_last_lines(2)?; + let mut new_call_config = guide_user_to_call_chain(&api, suri, url, cli).await?; + Box::pin(prepare_and_submit_extrinsic( + api, + &mut new_call_config, + prompt_to_repeat_call, + cli, + )) + .await?; + } } } Ok(()) @@ -165,14 +224,13 @@ fn display_message(message: &str, success: bool, cli: &mut impl cli::traits::Cli Ok(()) } // Prompt the user to select an operation. -fn prompt_arguments(extrinsic: &Extrinsic, cli: &mut impl cli::traits::Cli) -> Result> { - let mut args: Vec = Vec::new(); +fn prompt_arguments(extrinsic: &Extrinsic, cli: &mut impl cli::traits::Cli) -> Result> { + let mut args: Vec = Vec::new(); match extrinsic { Extrinsic::CreateAsset => { args.push(prompt_for_numeric_value("Enter the Asset ID", cli)?); args.push(prompt_for_account("Enter the Admin Address", cli)?); args.push(prompt_for_numeric_value("Enter the Minimum Balance", cli)?); - // TODO: ADD METADATA }, Extrinsic::MintAsset => { args.push(prompt_for_numeric_value("Enter the Asset ID", cli)?); @@ -181,13 +239,13 @@ fn prompt_arguments(extrinsic: &Extrinsic, cli: &mut impl cli::traits::Cli) -> R }, Extrinsic::CreateCollection => { args.push(prompt_for_account("Enter the Admin Address", cli)?); - args.push(prompt_for_collection_config(cli)?); + args.extend(prompt_for_collection_config(cli)?); }, Extrinsic::MintNFT => { args.push(prompt_for_numeric_value("Enter the Collection ID", cli)?); args.push(prompt_for_numeric_value("Enter the Item ID", cli)?); args.push(prompt_for_account("Enter the Beneficiary Address", cli)?); - args.push(prompt_for_witness_data(cli)?); + args.extend(prompt_for_witness_data(cli)?); }, Extrinsic::Transfer => { args.push(prompt_for_account("Enter the Destination Address", cli)?); @@ -196,7 +254,7 @@ fn prompt_arguments(extrinsic: &Extrinsic, cli: &mut impl cli::traits::Cli) -> R } Ok(args) } -fn prompt_for_numeric_value(message: &str, cli: &mut impl cli::traits::Cli) -> Result { +fn prompt_for_numeric_value(message: &str, cli: &mut impl cli::traits::Cli) -> Result { let id = cli .input(message) .placeholder("0") @@ -207,88 +265,85 @@ fn prompt_for_numeric_value(message: &str, cli: &mut impl cli::traits::Cli) -> R }) .required(true) .interact()?; - Ok(Value::u128(id.parse::()?)) + Ok(id) } -fn prompt_for_account(message: &str, cli: &mut impl cli::traits::Cli) -> Result { +fn prompt_for_account(message: &str, cli: &mut impl cli::traits::Cli) -> Result { let account: String = cli .input(message) .placeholder("e.g. 5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") .required(true) .interact()?; - let account_id = parse_account(&account)?; + //let account_id = parse_account(&account)?; // TODO: Support other Adresses? Let the user pick Id, Address, or Index - Ok(Value::unnamed_variant("Id", vec![Value::from_bytes(account_id)])) + Ok(account) } fn prompt_for_numeric_optional_value( message: &str, cli: &mut impl cli::traits::Cli, -) -> Result { +) -> Result { let value = cli .input(message) .placeholder("0 or (empty for None)") .validate(|input: &String| match input.parse::() { Ok(_) => Ok(()), - Err(_) => + Err(_) => { if input.is_empty() || input == "None" { Ok(()) } else { Err("Invalid value.") - }, + } + }, }) .required(false) .interact()?; if value.is_empty() || value == "None" { - Ok(Value::unnamed_variant("None", vec![])) + Ok("None".to_string()) + //Ok(Value::unnamed_variant("None", vec![])) } else { - Ok(Value::unnamed_variant("Some", vec![Value::u128(value.parse::()?)])) + Ok(value) + //Ok(Value::unnamed_variant("Some", vec![Value::u128(value.parse::()?)])) } } fn prompt_for_variant_value( message: &str, default_value: &str, cli: &mut impl cli::traits::Cli, -) -> Result { +) -> Result { let mint_type: String = cli .input(message) .placeholder(&format!("e.g. {}", default_value)) .default_input(default_value) .required(true) .interact()?; - Ok(Value::unnamed_variant(mint_type, vec![])) + Ok(mint_type) } -fn prompt_for_collection_config(cli: &mut impl cli::traits::Cli) -> Result { +fn prompt_for_collection_config(cli: &mut impl cli::traits::Cli) -> Result> { + let mut args: Vec = Vec::new(); cli.info("Enter the Pallet NFT Collection Config:")?; - let settings = prompt_for_numeric_value("Collection's Settings", cli)?; - let max_supply = prompt_for_numeric_optional_value("Collection's Max Supply", cli)?; + args.push(prompt_for_numeric_value("Collection's Settings", cli)?); + args.push(prompt_for_numeric_optional_value("Collection's Max Supply", cli)?); cli.info("Enter the Mint Settings:")?; - let mint_type = prompt_for_variant_value("Who can mint?", "Issuer", cli)?; - let price_per_mint = prompt_for_numeric_optional_value("Price per mint", cli)?; - let start_block = prompt_for_numeric_optional_value("When the mint starts", cli)?; - let end_block = prompt_for_numeric_optional_value("When the mint ends", cli)?; - let default_item_settings = prompt_for_numeric_value("Default Item Settings", cli)?; - // mint settings - let mint_settings = Value::unnamed_composite(vec![ - mint_type, - price_per_mint, - start_block, - end_block, - default_item_settings, - ]); - let config_collection = Value::unnamed_composite(vec![settings, max_supply, mint_settings]); - - Ok(config_collection) + args.push(prompt_for_variant_value("Who can mint?", "Issuer", cli)?); + args.push(prompt_for_numeric_optional_value("Price per mint", cli)?); + args.push(prompt_for_numeric_optional_value("When the mint starts", cli)?); + args.push(prompt_for_numeric_optional_value("When the mint ends", cli)?); + args.push(prompt_for_numeric_value("Default Item Settings", cli)?); + Ok(args) } -fn prompt_for_witness_data(cli: &mut impl cli::traits::Cli) -> Result { +fn prompt_for_witness_data(cli: &mut impl cli::traits::Cli) -> Result> { + let mut args: Vec = Vec::new(); if cli .confirm("Do you want to enter witness data for mint") .initial_value(false) .interact()? { - let owned_item = - prompt_for_numeric_optional_value("Id of the item in a required collection:", cli)?; - let mint_price = prompt_for_numeric_optional_value("Mint price:", cli)?; - Ok(Value::unnamed_variant("Some".to_string(), vec![owned_item, mint_price])) + args.push(prompt_for_numeric_optional_value( + "Id of the item in a required collection:", + cli, + )?); + args.push(prompt_for_numeric_optional_value("Mint price:", cli)?); } else { - Ok(Value::unnamed_variant("None", vec![])) + args.push("None".to_string()); } + Ok(args) } diff --git a/crates/pop-contracts/src/utils/mod.rs b/crates/pop-contracts/src/utils/mod.rs index 7192839b4..12c7813ad 100644 --- a/crates/pop-contracts/src/utils/mod.rs +++ b/crates/pop-contracts/src/utils/mod.rs @@ -51,7 +51,7 @@ pub fn canonicalized_path(target: &Path) -> Result { #[cfg(test)] mod tests { use super::*; - use anyhow::{Error, Result}; + use anyhow::Result; use std::fs; fn setup_test_environment() -> Result { @@ -106,7 +106,7 @@ mod tests { } #[test] - fn parse_hex_bytes_fails_wrong_input() -> Result<(), Error> { + fn parse_hex_bytes_fails_wrong_input() -> Result<()> { assert!(matches!(parse_hex_bytes("wronghexvalue"), Err(Error::HexParsing(..)))); Ok(()) } diff --git a/crates/pop-parachains/src/call.rs b/crates/pop-parachains/src/call.rs index efb964142..c41b4d49a 100644 --- a/crates/pop-parachains/src/call.rs +++ b/crates/pop-parachains/src/call.rs @@ -1,36 +1,47 @@ // SPDX-License-Identifier: GPL-3.0 use crate::errors::Error; -use pop_common::create_signer; +use pop_common::{create_signer, parse_account}; use strum::{EnumMessage as _, EnumProperty as _, VariantArray as _}; use strum_macros::{AsRefStr, Display, EnumMessage, EnumProperty, EnumString, VariantArray}; use subxt::{ - config::DefaultExtrinsicParamsBuilder, dynamic::Value, tx::SubmittableExtrinsic, OnlineClient, - SubstrateConfig, + dynamic::Value, + tx::{DynamicPayload, Payload}, + OnlineClient, SubstrateConfig, }; +#[derive(AsRefStr, Clone, Debug, Display, EnumMessage, EnumString, Eq, PartialEq, VariantArray)] +pub enum Pallet { + #[strum(serialize = "Assets")] + Assets, + #[strum(serialize = "Balances")] + Balances, + #[strum(serialize = "Nfts")] + Nfts, +} + #[derive( AsRefStr, Clone, Debug, Display, EnumMessage, - EnumProperty, EnumString, + EnumProperty, Eq, PartialEq, VariantArray, )] pub enum Extrinsic { #[strum( - serialize = "create_asset", + serialize = "create", message = "create", detailed_message = "Create an Asset", props(Pallet = "Assets") )] CreateAsset, #[strum( - serialize = "mint_asset", + serialize = "mint", message = "mint", detailed_message = "Mint an Asset", props(Pallet = "Assets") @@ -44,7 +55,7 @@ pub enum Extrinsic { )] CreateCollection, #[strum( - serialize = "mint", + serialize = "mint_nft", message = "mint", detailed_message = "Mint a NFT", props(Pallet = "Nfts") @@ -73,6 +84,7 @@ impl Extrinsic { self.get_str("Pallet").unwrap_or_default() } } + pub fn supported_extrinsics(api: &OnlineClient) -> Vec<&Extrinsic> { Extrinsic::VARIANTS .iter() @@ -80,44 +92,183 @@ pub fn supported_extrinsics(api: &OnlineClient) -> Vec<&Extrins .collect() } +pub fn parse_args( + pallet_name: &str, + extrinsic_name: &str, + raw_args: &Vec, +) -> Result, Error> { + let mut args: Vec = Vec::new(); + let extrinsic = Extrinsic::VARIANTS + .iter() + .find(|t| t.pallet() == pallet_name && t.extrinsic_name() == extrinsic_name) + .ok_or(Error::ExtrinsicNotSupported(extrinsic_name.to_string()))?; + match extrinsic { + Extrinsic::CreateAsset => { + args.push(Value::u128( + raw_args[0].parse::().map_err(|_| Error::ParsingArgsError)?, + )); + args.push(Value::unnamed_variant( + "Id", + vec![Value::from_bytes(parse_account(&raw_args[1])?)], + )); + args.push(Value::u128( + raw_args[2].parse::().map_err(|_| Error::ParsingArgsError)?, + )); + }, + Extrinsic::MintAsset => { + args.push(Value::u128( + raw_args[0].parse::().map_err(|_| Error::ParsingArgsError)?, + )); + args.push(Value::unnamed_variant( + "Id", + vec![Value::from_bytes(parse_account(&raw_args[1])?)], + )); + args.push(Value::u128( + raw_args[2].parse::().map_err(|_| Error::ParsingArgsError)?, + )); + }, + Extrinsic::CreateCollection => { + args.push(Value::unnamed_variant( + "Id", + vec![Value::from_bytes(parse_account(&raw_args[0])?)], + )); + let mint_settings = Value::unnamed_composite(vec![ + Value::unnamed_variant(&raw_args[3], vec![]), + if raw_args[4] == "None" { + Value::unnamed_variant("None", vec![]) + } else { + Value::unnamed_variant( + "Some", + vec![Value::u128( + raw_args[4].parse::().map_err(|_| Error::ParsingArgsError)?, + )], + ) + }, + if raw_args[5] == "None" { + Value::unnamed_variant("None", vec![]) + } else { + Value::unnamed_variant( + "Some", + vec![Value::u128( + raw_args[5].parse::().map_err(|_| Error::ParsingArgsError)?, + )], + ) + }, + if raw_args[6] == "None" { + Value::unnamed_variant("None", vec![]) + } else { + Value::unnamed_variant( + "Some", + vec![Value::u128( + raw_args[6].parse::().map_err(|_| Error::ParsingArgsError)?, + )], + ) + }, + Value::u128(raw_args[7].parse::().map_err(|_| Error::ParsingArgsError)?), + ]); + let max_supply = if raw_args[2] == "None" { + Value::unnamed_variant("None", vec![]) + } else { + Value::unnamed_variant( + "Some", + vec![Value::u128( + raw_args[2].parse::().map_err(|_| Error::ParsingArgsError)?, + )], + ) + }; + args.push(Value::unnamed_composite(vec![ + Value::u128(raw_args[1].parse::().map_err(|_| Error::ParsingArgsError)?), + max_supply, + mint_settings, + ])) + }, + Extrinsic::MintNFT => { + args.push(Value::u128( + raw_args[0].parse::().map_err(|_| Error::ParsingArgsError)?, + )); + args.push(Value::u128( + raw_args[1].parse::().map_err(|_| Error::ParsingArgsError)?, + )); + args.push(Value::unnamed_variant( + "Id", + vec![Value::from_bytes(parse_account(&raw_args[2])?)], + )); + if raw_args[3] == "None" && raw_args.len() == 4 { + args.push(Value::unnamed_variant("None", vec![])); + } else { + let owned_item = if raw_args[3] == "None" { + Value::unnamed_variant("None", vec![]) + } else { + Value::unnamed_variant( + "Some", + vec![Value::u128( + raw_args[3].parse::().map_err(|_| Error::ParsingArgsError)?, + )], + ) + }; + let mint_price = if raw_args[4] == "None" { + Value::unnamed_variant("None", vec![]) + } else { + Value::unnamed_variant( + "Some", + vec![Value::u128( + raw_args[4].parse::().map_err(|_| Error::ParsingArgsError)?, + )], + ) + }; + args.push(Value::unnamed_variant("Some".to_string(), vec![owned_item, mint_price])); + } + }, + Extrinsic::Transfer => { + args.push(Value::unnamed_variant( + "Id", + vec![Value::from_bytes(parse_account(&raw_args[0])?)], + )); + args.push(Value::u128( + raw_args[1].parse::().map_err(|_| Error::ParsingArgsError)?, + )); + }, + } + Ok(args) +} + pub async fn set_up_api(url: &str) -> Result, Error> { let api = OnlineClient::::from_url(url).await?; Ok(api) } -pub async fn prepare_extrinsic( - api: &OnlineClient, +pub fn construct_extrinsic( pallet_name: &str, - entry_name: &str, - args_value: Vec, - suri: &str, -) -> Result { - let signer = create_signer(suri)?; - let tx = subxt::dynamic::tx(pallet_name, entry_name, args_value); - let signed_extrinsic: SubmittableExtrinsic> = - api.tx() - .create_signed(&tx, &signer, DefaultExtrinsicParamsBuilder::new().build()) - .await?; - Ok(encode_extrinsic(signed_extrinsic.encoded().to_vec())) + extrinsic_name: &str, + args: &Vec, +) -> Result { + let parsed_args: Vec = parse_args(pallet_name, extrinsic_name, &args)?; + Ok(subxt::dynamic::tx(pallet_name, extrinsic_name, parsed_args)) } -pub async fn submit_extrinsic( +pub async fn sign_and_submit_extrinsic( api: OnlineClient, - encoded_extrinsic: String, + tx: DynamicPayload, + suri: &str, ) -> Result { - let extrinsic = decode_extrinsic(encoded_extrinsic)?; - let signed_extrinsic: SubmittableExtrinsic> = - SubmittableExtrinsic::from_bytes(api, extrinsic); - let result = signed_extrinsic.submit_and_watch().await?; + let signer = create_signer(suri)?; + let result = api + .tx() + .sign_and_submit_then_watch_default(&tx, &signer) + .await? + .wait_for_finalized() + .await? + .wait_for_success() + .await?; Ok(format!("{:?}", result.extrinsic_hash())) } -fn encode_extrinsic(encoded_call_data: Vec) -> String { - format!("0x{}", hex::encode(encoded_call_data)) -} -fn decode_extrinsic(encoded_call_data: String) -> Result, Error> { - let hex_data = encoded_call_data.trim_start_matches("0x"); - Ok(hex::decode(hex_data)?) +pub fn encode_call_data( + api: &OnlineClient, + tx: &DynamicPayload, +) -> Result { + let call_data = tx.encode_call_data(&api.metadata())?; + Ok(format!("0x{}", hex::encode(call_data))) } fn extrinsic_is_supported( @@ -142,6 +293,7 @@ fn extrinsic_is_supported( mod tests { use super::*; use anyhow::Result; + use subxt::tx::Payload; #[tokio::test] async fn extrinsic_is_supported_works() -> Result<()> { @@ -150,4 +302,21 @@ mod tests { assert!(!extrinsic_is_supported(&api, "Nfts", "mint_no_exist")); Ok(()) } + + #[tokio::test] + async fn encode_call_data_works() -> Result<()> { + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let tx = construct_extrinsic( + "Assets", + "create", + &vec![ + "1000".to_string(), + "15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5".to_string(), + "1000000".to_string(), + ], + )?; + let call_data = tx.encode_call_data(&api.metadata())?; + assert_eq!("0x3400419c00d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d40420f00000000000000000000000000", encode_call_data(call_data)); + Ok(()) + } } diff --git a/crates/pop-parachains/src/errors.rs b/crates/pop-parachains/src/errors.rs index 6c7b55b95..2a54c4fed 100644 --- a/crates/pop-parachains/src/errors.rs +++ b/crates/pop-parachains/src/errors.rs @@ -10,8 +10,6 @@ pub enum Error { Aborted, #[error("Anyhow error: {0}")] AnyhowError(#[from] anyhow::Error), - #[error("The `Call` property is missing from the call variant")] - CallMissing, #[error("{0}")] CommonError(#[from] pop_common::Error), #[error("Configuration error: {0}")] @@ -20,6 +18,8 @@ pub enum Error { CurrentDirAccess, #[error("Failed to parse the endowment value")] EndowmentError, + #[error("The extrinsic {0} is not supported")] + ExtrinsicNotSupported(String), #[error("Failed decode a value")] FromHexError(#[from] hex::FromHexError), #[error("IO error: {0}")] @@ -42,10 +42,10 @@ pub enum Error { PalletMissing, #[error("Failed to find the pallet {0}")] PalletNotFound(String), + #[error("Failed to parse the arguments")] + ParsingArgsError, #[error("Failed to parse the response")] ParsingResponseError(#[from] scale_decode::Error), - #[error("Failed to parse the argument {0}")] - ParsingValueError(String), #[error("Invalid path")] PathError, #[error("Failed to execute rustfmt")] @@ -54,6 +54,8 @@ pub enum Error { SourcingError(#[from] pop_common::sourcing::Error), #[error("{0}")] SubxtError(#[from] subxt::Error), + #[error("{0}")] + SubxtExternalError(#[from] subxt::ext::subxt_core::Error), #[error("Toml error: {0}")] TomlError(#[from] toml_edit::de::Error), #[error("Unsupported command: {0}")] diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index 76c597692..53f603b11 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -15,13 +15,16 @@ pub use build::{ binary_path, build_parachain, export_wasm_file, generate_genesis_state_file, generate_plain_chain_spec, generate_raw_chain_spec, is_supported, ChainSpec, }; -pub use call::{prepare_extrinsic, set_up_api, submit_extrinsic, supported_extrinsics, Extrinsic}; +pub use call::{ + construct_extrinsic, encode_call_data, set_up_api, sign_and_submit_extrinsic, + supported_extrinsics, Extrinsic, Pallet, +}; pub use errors::Error; pub use indexmap::IndexSet; pub use new_pallet::{create_pallet_template, new_pallet_options::*, TemplatePalletConfig}; pub use new_parachain::instantiate_template_dir; // External export from subxt. -pub use subxt::{dynamic::Value, OnlineClient, SubstrateConfig}; +pub use subxt::{dynamic::Value, tx::DynamicPayload, OnlineClient, SubstrateConfig}; pub use templates::{Config, Parachain, Provider}; pub use up::Zombienet; pub use utils::helpers::is_initial_endowment_valid; From cbe58ed720c6278c01cd48f62809aa578ff7d754 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 27 Sep 2024 13:22:49 +0200 Subject: [PATCH 133/211] refactor: clean docs and refactor code --- crates/pop-cli/src/commands/call/parachain.rs | 26 +- crates/pop-parachains/src/call.rs | 259 +++++++----------- 2 files changed, 106 insertions(+), 179 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 884db4618..5d6584d39 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -35,7 +35,6 @@ impl CallParachainCommand { /// Executes the command. pub(crate) async fn execute(mut self) -> Result<()> { let (api, url) = self.set_up_api(&mut cli::Cli).await?; - // TODO: Option to send call_data, sign it and send it. let mut call_config = if self.pallet.is_none() && self.extrinsic.is_none() { match guide_user_to_call_chain(&api, "", &url, &mut cli::Cli).await { Ok(call_config) => call_config, @@ -83,7 +82,7 @@ impl CallParachainCommand { if let Some(extrinsic) = &self.extrinsic { full_message.push_str(&format!(" --extrinsic {}", extrinsic)); } - if self.args.len() > 0 { + if !self.args.is_empty() { full_message.push_str(&format!(" --args {}", self.args.join(" "))); } full_message.push_str(&format!(" --url {}", self.url)); @@ -102,7 +101,7 @@ async fn guide_user_to_call_chain( cli: &mut impl cli::traits::Cli, ) -> anyhow::Result { let extrinsic = { - let mut prompt_extrinsic = cli.select("What would you like to do on Polkadot?"); + let mut prompt_extrinsic = cli.select("What would you like to do?"); //for extrinsic in pallet.extrinsics() { for extrinsic in supported_extrinsics(api) { prompt_extrinsic = prompt_extrinsic.item( @@ -141,18 +140,14 @@ async fn prepare_and_submit_extrinsic( return Err(anyhow!("Please specify the pallet to call.")); }, }; - let tx = match construct_extrinsic(&pallet, &extrinsic, &call_config.args) { + let tx = match construct_extrinsic(&pallet, &extrinsic, call_config.args.clone()) { Ok(tx) => tx, Err(e) => { - display_message( - &format!("Error parsing the arguments: {}", e.to_string()), - false, - &mut cli::Cli, - )?; + display_message(&format!("Error parsing the arguments: {}", e), false, &mut cli::Cli)?; return Ok(()); }, }; - cli.info(format!("Call data {}", encode_call_data(&api, &tx)?))?; + cli.info(format!("Encoded call data: {}", encode_call_data(&api, &tx)?))?; if call_config.suri.is_empty() { call_config.suri = cli::Cli .input("Who is going to sign the extrinsic:") @@ -163,7 +158,7 @@ async fn prepare_and_submit_extrinsic( cli.info(call_config.display())?; if !cli.confirm("Do you want to submit the call?").initial_value(true).interact()? { display_message( - &format!("Extrinsic: {} not submitted, user cancel the operation", extrinsic), + &format!("Extrinsic {} was not submitted. Operation canceled by the user.", extrinsic), false, cli, )?; @@ -273,8 +268,6 @@ fn prompt_for_account(message: &str, cli: &mut impl cli::traits::Cli) -> Result< .placeholder("e.g. 5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") .required(true) .interact()?; - //let account_id = parse_account(&account)?; - // TODO: Support other Adresses? Let the user pick Id, Address, or Index Ok(account) } fn prompt_for_numeric_optional_value( @@ -286,22 +279,19 @@ fn prompt_for_numeric_optional_value( .placeholder("0 or (empty for None)") .validate(|input: &String| match input.parse::() { Ok(_) => Ok(()), - Err(_) => { + Err(_) => if input.is_empty() || input == "None" { Ok(()) } else { Err("Invalid value.") - } - }, + }, }) .required(false) .interact()?; if value.is_empty() || value == "None" { Ok("None".to_string()) - //Ok(Value::unnamed_variant("None", vec![])) } else { Ok(value) - //Ok(Value::unnamed_variant("Some", vec![Value::u128(value.parse::()?)])) } } fn prompt_for_variant_value( diff --git a/crates/pop-parachains/src/call.rs b/crates/pop-parachains/src/call.rs index c41b4d49a..93452638c 100644 --- a/crates/pop-parachains/src/call.rs +++ b/crates/pop-parachains/src/call.rs @@ -70,7 +70,7 @@ pub enum Extrinsic { Transfer, } impl Extrinsic { - /// Get the template's name. + /// Get the extrinsic's name. pub fn extrinsic_name(&self) -> &str { self.get_message().unwrap_or_default() } @@ -78,13 +78,17 @@ impl Extrinsic { pub fn description(&self) -> &str { self.get_detailed_message().unwrap_or_default() } - /// Get the pallet of the extrinsic. pub fn pallet(&self) -> &str { self.get_str("Pallet").unwrap_or_default() } } +pub async fn set_up_api(url: &str) -> Result, Error> { + let api = OnlineClient::::from_url(url).await?; + Ok(api) +} + pub fn supported_extrinsics(api: &OnlineClient) -> Vec<&Extrinsic> { Extrinsic::VARIANTS .iter() @@ -92,10 +96,44 @@ pub fn supported_extrinsics(api: &OnlineClient) -> Vec<&Extrins .collect() } -pub fn parse_args( +pub fn construct_extrinsic( pallet_name: &str, extrinsic_name: &str, - raw_args: &Vec, + args: Vec, +) -> Result { + let parsed_args: Vec = parse_args(pallet_name, extrinsic_name, args)?; + Ok(subxt::dynamic::tx(pallet_name, extrinsic_name, parsed_args)) +} + +pub async fn sign_and_submit_extrinsic( + api: OnlineClient, + tx: DynamicPayload, + suri: &str, +) -> Result { + let signer = create_signer(suri)?; + let result = api + .tx() + .sign_and_submit_then_watch_default(&tx, &signer) + .await? + .wait_for_finalized() + .await? + .wait_for_success() + .await?; + Ok(format!("{:?}", result.extrinsic_hash())) +} + +pub fn encode_call_data( + api: &OnlineClient, + tx: &DynamicPayload, +) -> Result { + let call_data = tx.encode_call_data(&api.metadata())?; + Ok(format!("0x{}", hex::encode(call_data))) +} + +fn parse_args( + pallet_name: &str, + extrinsic_name: &str, + raw_args: Vec, ) -> Result, Error> { let mut args: Vec = Vec::new(); let extrinsic = Extrinsic::VARIANTS @@ -104,171 +142,88 @@ pub fn parse_args( .ok_or(Error::ExtrinsicNotSupported(extrinsic_name.to_string()))?; match extrinsic { Extrinsic::CreateAsset => { - args.push(Value::u128( - raw_args[0].parse::().map_err(|_| Error::ParsingArgsError)?, - )); - args.push(Value::unnamed_variant( - "Id", - vec![Value::from_bytes(parse_account(&raw_args[1])?)], - )); - args.push(Value::u128( - raw_args[2].parse::().map_err(|_| Error::ParsingArgsError)?, - )); + if raw_args.len() < 3 { + return Err(Error::ParsingArgsError); + } + args.push(parse_u128(&raw_args[0])?); + args.push(parse_account_id(&raw_args[1])?); + args.push(parse_u128(&raw_args[2])?); }, Extrinsic::MintAsset => { - args.push(Value::u128( - raw_args[0].parse::().map_err(|_| Error::ParsingArgsError)?, - )); - args.push(Value::unnamed_variant( - "Id", - vec![Value::from_bytes(parse_account(&raw_args[1])?)], - )); - args.push(Value::u128( - raw_args[2].parse::().map_err(|_| Error::ParsingArgsError)?, - )); + if raw_args.len() < 3 { + return Err(Error::ParsingArgsError); + } + args.push(parse_u128(&raw_args[0])?); + args.push(parse_account_id(&raw_args[1])?); + args.push(parse_u128(&raw_args[2])?); }, Extrinsic::CreateCollection => { - args.push(Value::unnamed_variant( - "Id", - vec![Value::from_bytes(parse_account(&raw_args[0])?)], - )); + if raw_args.len() < 7 { + return Err(Error::ParsingArgsError); + } + args.push(parse_account_id(&raw_args[0])?); let mint_settings = Value::unnamed_composite(vec![ Value::unnamed_variant(&raw_args[3], vec![]), - if raw_args[4] == "None" { - Value::unnamed_variant("None", vec![]) - } else { - Value::unnamed_variant( - "Some", - vec![Value::u128( - raw_args[4].parse::().map_err(|_| Error::ParsingArgsError)?, - )], - ) - }, - if raw_args[5] == "None" { - Value::unnamed_variant("None", vec![]) - } else { - Value::unnamed_variant( - "Some", - vec![Value::u128( - raw_args[5].parse::().map_err(|_| Error::ParsingArgsError)?, - )], - ) - }, - if raw_args[6] == "None" { - Value::unnamed_variant("None", vec![]) - } else { - Value::unnamed_variant( - "Some", - vec![Value::u128( - raw_args[6].parse::().map_err(|_| Error::ParsingArgsError)?, - )], - ) - }, - Value::u128(raw_args[7].parse::().map_err(|_| Error::ParsingArgsError)?), + parse_optional_u128(&raw_args[4])?, + parse_optional_u128(&raw_args[5])?, + parse_optional_u128(&raw_args[6])?, + parse_u128(&raw_args[7])?, ]); - let max_supply = if raw_args[2] == "None" { - Value::unnamed_variant("None", vec![]) - } else { - Value::unnamed_variant( - "Some", - vec![Value::u128( - raw_args[2].parse::().map_err(|_| Error::ParsingArgsError)?, - )], - ) - }; + let max_supply = parse_optional_u128(&raw_args[2])?; args.push(Value::unnamed_composite(vec![ - Value::u128(raw_args[1].parse::().map_err(|_| Error::ParsingArgsError)?), + parse_u128(&raw_args[1])?, max_supply, mint_settings, ])) }, Extrinsic::MintNFT => { - args.push(Value::u128( - raw_args[0].parse::().map_err(|_| Error::ParsingArgsError)?, - )); - args.push(Value::u128( - raw_args[1].parse::().map_err(|_| Error::ParsingArgsError)?, - )); - args.push(Value::unnamed_variant( - "Id", - vec![Value::from_bytes(parse_account(&raw_args[2])?)], - )); + println!("{:?}", raw_args.len()); + if raw_args.len() < 4 { + return Err(Error::ParsingArgsError); + } + args.push(parse_u128(&raw_args[0])?); + args.push(parse_u128(&raw_args[1])?); + args.push(parse_account_id(&raw_args[2])?); if raw_args[3] == "None" && raw_args.len() == 4 { args.push(Value::unnamed_variant("None", vec![])); } else { - let owned_item = if raw_args[3] == "None" { - Value::unnamed_variant("None", vec![]) - } else { - Value::unnamed_variant( - "Some", - vec![Value::u128( - raw_args[3].parse::().map_err(|_| Error::ParsingArgsError)?, - )], - ) - }; - let mint_price = if raw_args[4] == "None" { - Value::unnamed_variant("None", vec![]) - } else { - Value::unnamed_variant( - "Some", - vec![Value::u128( - raw_args[4].parse::().map_err(|_| Error::ParsingArgsError)?, - )], - ) - }; - args.push(Value::unnamed_variant("Some".to_string(), vec![owned_item, mint_price])); + if raw_args.len() < 5 { + return Err(Error::ParsingArgsError); + } + let owned_item = parse_optional_u128(&raw_args[3])?; + let mint_price = parse_optional_u128(&raw_args[4])?; + args.push(Value::unnamed_variant( + "Some", + vec![Value::unnamed_composite(vec![owned_item, mint_price])], + )); } }, Extrinsic::Transfer => { - args.push(Value::unnamed_variant( - "Id", - vec![Value::from_bytes(parse_account(&raw_args[0])?)], - )); - args.push(Value::u128( - raw_args[1].parse::().map_err(|_| Error::ParsingArgsError)?, - )); + if raw_args.len() < 2 { + return Err(Error::ParsingArgsError); + } + args.push(parse_account_id(&raw_args[0])?); + args.push(parse_u128(&raw_args[1])?); }, } Ok(args) } -pub async fn set_up_api(url: &str) -> Result, Error> { - let api = OnlineClient::::from_url(url).await?; - Ok(api) +// Helper function to parse u128 +fn parse_u128(arg: &str) -> Result { + Ok(Value::u128(arg.parse::().map_err(|_| Error::ParsingArgsError)?)) } - -pub fn construct_extrinsic( - pallet_name: &str, - extrinsic_name: &str, - args: &Vec, -) -> Result { - let parsed_args: Vec = parse_args(pallet_name, extrinsic_name, &args)?; - Ok(subxt::dynamic::tx(pallet_name, extrinsic_name, parsed_args)) -} - -pub async fn sign_and_submit_extrinsic( - api: OnlineClient, - tx: DynamicPayload, - suri: &str, -) -> Result { - let signer = create_signer(suri)?; - let result = api - .tx() - .sign_and_submit_then_watch_default(&tx, &signer) - .await? - .wait_for_finalized() - .await? - .wait_for_success() - .await?; - Ok(format!("{:?}", result.extrinsic_hash())) +// Helper function to parse account id +fn parse_account_id(arg: &str) -> Result { + Ok(Value::unnamed_variant("Id", vec![Value::from_bytes(parse_account(arg)?)])) } - -pub fn encode_call_data( - api: &OnlineClient, - tx: &DynamicPayload, -) -> Result { - let call_data = tx.encode_call_data(&api.metadata())?; - Ok(format!("0x{}", hex::encode(call_data))) +// Helper function to handle "None" or Some(u128) values +fn parse_optional_u128(arg: &str) -> Result { + if arg == "None" { + Ok(Value::unnamed_variant("None", vec![])) + } else { + Ok(Value::unnamed_variant("Some", vec![parse_u128(arg)?])) + } } fn extrinsic_is_supported( @@ -293,7 +248,6 @@ fn extrinsic_is_supported( mod tests { use super::*; use anyhow::Result; - use subxt::tx::Payload; #[tokio::test] async fn extrinsic_is_supported_works() -> Result<()> { @@ -302,21 +256,4 @@ mod tests { assert!(!extrinsic_is_supported(&api, "Nfts", "mint_no_exist")); Ok(()) } - - #[tokio::test] - async fn encode_call_data_works() -> Result<()> { - let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; - let tx = construct_extrinsic( - "Assets", - "create", - &vec![ - "1000".to_string(), - "15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5".to_string(), - "1000000".to_string(), - ], - )?; - let call_data = tx.encode_call_data(&api.metadata())?; - assert_eq!("0x3400419c00d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d40420f00000000000000000000000000", encode_call_data(call_data)); - Ok(()) - } } From d42c526e4febd907c909cb61ad941581a5c76208 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Mon, 11 Nov 2024 19:41:39 +0100 Subject: [PATCH 134/211] fix: tests --- crates/pop-cli/src/commands/call/contract.rs | 3 ++- crates/pop-contracts/src/call.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 8db1cc63c..d9e604b1e 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -7,9 +7,10 @@ use crate::{ use anyhow::{anyhow, Result}; use clap::Args; use cliclack::spinner; +use pop_common::parse_account; use pop_contracts::{ build_smart_contract, call_smart_contract, dry_run_call, dry_run_gas_estimate_call, - get_messages, parse_account, set_up_call, CallOpts, Verbosity, + get_messages, set_up_call, CallOpts, Verbosity, }; use sp_weights::Weight; use std::path::PathBuf; diff --git a/crates/pop-contracts/src/call.rs b/crates/pop-contracts/src/call.rs index fdd8e7307..f7dea684d 100644 --- a/crates/pop-contracts/src/call.rs +++ b/crates/pop-contracts/src/call.rs @@ -4,7 +4,7 @@ use crate::{ errors::Error, utils::{ get_manifest_path, - metadata::{get_messages, ContractFunction}, + metadata::{process_function_args, FunctionType}, parse_balance, }, }; From 36c272bfd4505eb5164abf10fb641bb5f6215a3f Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Sun, 17 Nov 2024 16:25:18 +0100 Subject: [PATCH 135/211] refactor: parse all the metadata again --- crates/pop-cli/src/commands/call/parachain.rs | 510 +++++++++--------- crates/pop-parachains/src/call.rs | 221 +------- crates/pop-parachains/src/lib.rs | 10 +- crates/pop-parachains/src/utils/metadata.rs | 508 +++++++++++++++++ crates/pop-parachains/src/utils/mod.rs | 1 + 5 files changed, 775 insertions(+), 475 deletions(-) create mode 100644 crates/pop-parachains/src/utils/metadata.rs diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 5d6584d39..30f54dc44 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -4,10 +4,14 @@ use crate::cli::{self, traits::*}; use anyhow::{anyhow, Result}; use clap::Args; use pop_parachains::{ - construct_extrinsic, encode_call_data, set_up_api, sign_and_submit_extrinsic, - supported_extrinsics, DynamicPayload, Extrinsic, OnlineClient, SubstrateConfig, + construct_extrinsic, encode_call_data, find_pallet_by_name, parse_chain_metadata, + process_prompt_arguments, set_up_api, sign_and_submit_extrinsic, Arg, DynamicPayload, + OnlineClient, SubstrateConfig, }; +const DEFAULT_URL: &str = "ws://localhost:9944/"; +const DEFAULT_URI: &str = "//Alice"; + #[derive(Args, Clone)] pub struct CallParachainCommand { /// The name of the pallet to call. @@ -20,58 +24,119 @@ pub struct CallParachainCommand { #[clap(long, num_args = 0..)] args: Vec, /// Websocket endpoint of a node. - #[clap(name = "url", long, value_parser, default_value = "ws://127.0.0.1:9944")] - url: String, + #[clap(name = "url", short = 'u', long, value_parser, default_value = DEFAULT_URL)] + url: url::Url, /// Secret key URI for the account signing the extrinsic. /// /// e.g. /// - for a dev account "//Alice" /// - with a password "//Alice///SECRET_PASSWORD" - #[clap(name = "suri", long, short, default_value = "//Alice")] + #[clap(name = "suri", long, short, default_value = DEFAULT_URI)] suri: String, } impl CallParachainCommand { /// Executes the command. pub(crate) async fn execute(mut self) -> Result<()> { - let (api, url) = self.set_up_api(&mut cli::Cli).await?; - let mut call_config = if self.pallet.is_none() && self.extrinsic.is_none() { - match guide_user_to_call_chain(&api, "", &url, &mut cli::Cli).await { - Ok(call_config) => call_config, - Err(e) => { - display_message(&format!("{}", e), false, &mut cli::Cli)?; - return Ok(()); - }, - } - } else { - self.clone() + // Check if message specified via command line argument. + let prompt_to_repeat_call = self.extrinsic.is_none(); + // Configure the call based on command line arguments/call UI. + let api = match self.configure(&mut cli::Cli, false).await { + Ok(api) => api, + Err(e) => { + display_message(&e.to_string(), false, &mut cli::Cli)?; + return Ok(()); + }, }; - prepare_and_submit_extrinsic( - api, - &mut call_config, - self.pallet.is_none() && self.extrinsic.is_none(), - &mut cli::Cli, - ) - .await?; + // Prepare Extrinsic. + let tx = match self.prepare_extrinsic(&api, &mut cli::Cli).await { + Ok(api) => api, + Err(e) => { + display_message(&e.to_string(), false, &mut cli::Cli)?; + return Ok(()); + }, + }; + // TODO: If call_data, go directly here. + // Finally execute the call. + if let Err(e) = self.send_extrinsic(api, tx, prompt_to_repeat_call, &mut cli::Cli).await { + display_message(&e.to_string(), false, &mut cli::Cli)?; + } Ok(()) } - /// Prompt the user for the chain to use if not indicated and fetch the metadata. - async fn set_up_api( + + /// Configure the call based on command line arguments/call UI. + async fn configure( &mut self, cli: &mut impl cli::traits::Cli, - ) -> anyhow::Result<(OnlineClient, String)> { - cli.intro("Call a parachain")?; - let url: String = if self.pallet.is_none() && self.extrinsic.is_none() { - // Prompt for contract location. - cli.input("Which chain would you like to interact with?") + repeat: bool, + ) -> Result> { + // Show intro on first run. + if !repeat { + cli.intro("Call a parachain")?; + } + // If extrinsic has been specified via command line arguments, return early. + // TODO: CALL DATA + // if self.extrinsic.is_some() { + // return Ok(()); + // } + + // Resolve url. + if !repeat && self.url.as_str() == DEFAULT_URL { + // Prompt for url. + let url: String = cli + .input("Which chain would you like to interact with?") .placeholder("wss://rpc1.paseo.popnetwork.xyz") .default_input("wss://rpc1.paseo.popnetwork.xyz") - .interact()? + .interact()?; + self.url = url::Url::parse(&url)? + }; + // Parse metadata from url chain. + let api = set_up_api(self.url.as_str()).await?; + let pallets = match parse_chain_metadata(api.clone()).await { + Ok(pallets) => pallets, + Err(e) => { + return Err(anyhow!(format!( + "Unable to fetch the chain metadata: {}", + e.to_string() + ))); + }, + }; + // Resolve pallet. + let pallet = if let Some(ref pallet_name) = self.pallet { + find_pallet_by_name(&api, pallet_name).await? } else { - self.url.clone() + let mut prompt = cli.select("Select the pallet to call:"); + for pallet_item in pallets { + prompt = prompt.item(pallet_item.clone(), &pallet_item.name, &pallet_item.docs); + } + let pallet_prompted = prompt.interact()?; + self.pallet = Some(pallet_prompted.name.clone()); + pallet_prompted }; - let api = set_up_api(&url).await?; - Ok((api, url)) + // Resolve extrinsic. + let extrinsic = { + let mut prompt_extrinsic = cli.select("Select the extrinsic to call:"); + for extrinsic in pallet.extrinsics { + prompt_extrinsic = prompt_extrinsic.item( + extrinsic.clone(), + &extrinsic.name, + &extrinsic.docs.concat(), + ); + } + prompt_extrinsic.interact()? + }; + self.extrinsic = Some(extrinsic.name); + // Resolve message arguments. + let mut contract_args = Vec::new(); + for arg in extrinsic.fields { + let arg_metadata = process_prompt_arguments(&api, &arg)?; + let input = prompt_argument(&api, &arg_metadata, cli)?; + contract_args.push(input); + } + self.args = contract_args; + + cli.info(self.display())?; + Ok(api) } fn display(&self) -> String { @@ -83,131 +148,100 @@ impl CallParachainCommand { full_message.push_str(&format!(" --extrinsic {}", extrinsic)); } if !self.args.is_empty() { - full_message.push_str(&format!(" --args {}", self.args.join(" "))); - } - full_message.push_str(&format!(" --url {}", self.url)); - if !self.suri.is_empty() { - full_message.push_str(&format!(" --suri {}", self.suri)); + let args: Vec<_> = self.args.iter().map(|a| format!("\"{a}\"")).collect(); + full_message.push_str(&format!(" --args {}", args.join(", "))); } + full_message.push_str(&format!(" --url {} --suri {}", self.url, self.suri)); full_message } -} - -/// Guide the user to call the contract. -async fn guide_user_to_call_chain( - api: &OnlineClient, - suri: &str, - url: &str, - cli: &mut impl cli::traits::Cli, -) -> anyhow::Result { - let extrinsic = { - let mut prompt_extrinsic = cli.select("What would you like to do?"); - //for extrinsic in pallet.extrinsics() { - for extrinsic in supported_extrinsics(api) { - prompt_extrinsic = prompt_extrinsic.item( - extrinsic.clone(), - extrinsic.description(), - extrinsic.pallet(), - ); - } - prompt_extrinsic.interact()? - }; - let args = prompt_arguments(&extrinsic, cli)?; - Ok(CallParachainCommand { - pallet: Some(extrinsic.pallet().to_string()), - extrinsic: Some(extrinsic.extrinsic_name().to_string()), - args, - url: url.to_string(), - suri: suri.to_string(), - }) -} + /// Prepares the extrinsic or query. + async fn prepare_extrinsic( + &self, + api: &OnlineClient, + cli: &mut impl cli::traits::Cli, + ) -> Result { + let extrinsic = match &self.extrinsic { + Some(extrinsic) => extrinsic.to_string(), + None => { + return Err(anyhow!("Please specify the extrinsic.")); + }, + }; + let pallet = match &self.pallet { + Some(pallet) => pallet.to_string(), + None => { + return Err(anyhow!("Please specify the pallet.")); + }, + }; + let tx = match construct_extrinsic(api, &pallet, &extrinsic, self.args.clone()).await { + Ok(tx) => tx, + Err(e) => { + return Err(anyhow!("Error parsing the arguments: {}", e)); + }, + }; + cli.info(format!("Encoded call data: {}", encode_call_data(api, &tx)?))?; + Ok(tx) + } -/// Prepares the extrinsic or query. -async fn prepare_and_submit_extrinsic( - api: OnlineClient, - call_config: &mut CallParachainCommand, - prompt_to_repeat_call: bool, - cli: &mut impl cli::traits::Cli, -) -> Result<()> { - let extrinsic: String = call_config - .extrinsic - .clone() - .expect("extrinsic can not be none as fallback above is interactive input; qed"); - let pallet = match call_config.pallet.clone() { - Some(m) => m, - None => { - return Err(anyhow!("Please specify the pallet to call.")); - }, - }; - let tx = match construct_extrinsic(&pallet, &extrinsic, call_config.args.clone()) { - Ok(tx) => tx, - Err(e) => { - display_message(&format!("Error parsing the arguments: {}", e), false, &mut cli::Cli)?; + async fn send_extrinsic( + &mut self, + api: OnlineClient, + tx: DynamicPayload, + prompt_to_repeat_call: bool, + cli: &mut impl cli::traits::Cli, + ) -> Result<()> { + if self.suri.is_empty() { + self.suri = cli::Cli + .input("Who is going to sign the extrinsic:") + .placeholder("//Alice") + .default_input("//Alice") + .interact()?; + } + cli.info(self.display())?; + if !cli.confirm("Do you want to submit the call?").initial_value(true).interact()? { + display_message( + &format!( + "Extrinsic {:?} was not submitted. Operation canceled by the user.", + self.extrinsic + ), + false, + cli, + )?; return Ok(()); - }, - }; - cli.info(format!("Encoded call data: {}", encode_call_data(&api, &tx)?))?; - if call_config.suri.is_empty() { - call_config.suri = cli::Cli - .input("Who is going to sign the extrinsic:") - .placeholder("//Alice") - .default_input("//Alice") - .interact()?; - } - cli.info(call_config.display())?; - if !cli.confirm("Do you want to submit the call?").initial_value(true).interact()? { - display_message( - &format!("Extrinsic {} was not submitted. Operation canceled by the user.", extrinsic), - false, - cli, - )?; - return Ok(()); - } - send_extrinsic(api, tx, &call_config.url, &call_config.suri, prompt_to_repeat_call, cli) - .await?; + } + let spinner = cliclack::spinner(); + spinner.start("Signing and submitting the extrinsic, please wait..."); + let result = sign_and_submit_extrinsic(api.clone(), tx, &self.suri) + .await + .map_err(|err| anyhow!("{} {}", "ERROR:", format!("{err:?}")))?; - Ok(()) -} + display_message(&format!("Extrinsic submitted with hash: {:?}", result), true, cli)?; -async fn send_extrinsic( - api: OnlineClient, - tx: DynamicPayload, - url: &str, - suri: &str, - prompt_to_repeat_call: bool, - cli: &mut impl cli::traits::Cli, -) -> Result<()> { - let spinner = cliclack::spinner(); - spinner.start("Signing and submitting the extrinsic, please wait..."); - let result = sign_and_submit_extrinsic(api.clone(), tx, suri).await; - if let Err(e) = result { - console::Term::stderr().clear_last_lines(1)?; - display_message(&format!("{}", e), false, cli)?; - } else { - console::Term::stderr().clear_last_lines(1)?; - display_message(&format!("Extrinsic submitted with hash: {:?}", result?), true, cli)?; - // Repeat call. - if prompt_to_repeat_call { - let another_call: bool = cli - .confirm("Do you want to do another call to the same chain?") - .initial_value(false) - .interact()?; - if another_call { - // Remove only the prompt asking for another call. - console::Term::stderr().clear_last_lines(2)?; - let mut new_call_config = guide_user_to_call_chain(&api, suri, url, cli).await?; - Box::pin(prepare_and_submit_extrinsic( - api, - &mut new_call_config, - prompt_to_repeat_call, - cli, - )) - .await?; - } + // Prompt for any additional calls. + if !prompt_to_repeat_call { + display_message("Call completed successfully!", true, cli)?; + return Ok(()); + } + if cli + .confirm("Do you want to perform another call to the same chain?") + .initial_value(false) + .interact()? + { + // Reset specific items from the last call and repeat. + self.reset_for_new_call(); + self.configure(cli, true).await?; + let tx = self.prepare_extrinsic(&api, &mut cli::Cli).await?; + Box::pin(self.send_extrinsic(api, tx, prompt_to_repeat_call, cli)).await + } else { + display_message("Parachain calling complete.", true, cli)?; + Ok(()) } } - Ok(()) + /// Resets specific fields to default values for a new call. + fn reset_for_new_call(&mut self) { + self.pallet = None; + self.extrinsic = None; + } } fn display_message(message: &str, success: bool, cli: &mut impl cli::traits::Cli) -> Result<()> { @@ -218,122 +252,88 @@ fn display_message(message: &str, success: bool, cli: &mut impl cli::traits::Cli } Ok(()) } -// Prompt the user to select an operation. -fn prompt_arguments(extrinsic: &Extrinsic, cli: &mut impl cli::traits::Cli) -> Result> { - let mut args: Vec = Vec::new(); - match extrinsic { - Extrinsic::CreateAsset => { - args.push(prompt_for_numeric_value("Enter the Asset ID", cli)?); - args.push(prompt_for_account("Enter the Admin Address", cli)?); - args.push(prompt_for_numeric_value("Enter the Minimum Balance", cli)?); - }, - Extrinsic::MintAsset => { - args.push(prompt_for_numeric_value("Enter the Asset ID", cli)?); - args.push(prompt_for_account("Enter the Beneficiary Address", cli)?); - args.push(prompt_for_numeric_value("Enter the Amount", cli)?); - }, - Extrinsic::CreateCollection => { - args.push(prompt_for_account("Enter the Admin Address", cli)?); - args.extend(prompt_for_collection_config(cli)?); - }, - Extrinsic::MintNFT => { - args.push(prompt_for_numeric_value("Enter the Collection ID", cli)?); - args.push(prompt_for_numeric_value("Enter the Item ID", cli)?); - args.push(prompt_for_account("Enter the Beneficiary Address", cli)?); - args.extend(prompt_for_witness_data(cli)?); - }, - Extrinsic::Transfer => { - args.push(prompt_for_account("Enter the Destination Address", cli)?); - args.push(prompt_for_numeric_value("Enter the Amount", cli)?); - }, - } - Ok(args) + +// Prompt the user for the proper arguments. +fn prompt_argument( + api: &OnlineClient, + arg: &Arg, + cli: &mut impl cli::traits::Cli, +) -> Result { + Ok(if arg.optional { + // The argument is optional; prompt the user to decide whether to provide a value. + if !cli + .confirm(format!( + "Do you want to provide a value for the optional parameter: {}?", + arg.name + )) + .interact()? + { + return Ok("None".to_string()); + } + let value = prompt_argument_value(api, arg, cli)?; + format!("Some({})", value) + } else { + // Non-optional argument. + prompt_argument_value(api, arg, cli)? + }) } -fn prompt_for_numeric_value(message: &str, cli: &mut impl cli::traits::Cli) -> Result { - let id = cli - .input(message) - .placeholder("0") - .default_input("0") - .validate(|input: &String| match input.parse::() { - Ok(_) => Ok(()), - Err(_) => Err("Invalid value."), - }) - .required(true) - .interact()?; - Ok(id) + +fn prompt_argument_value( + api: &OnlineClient, + arg: &Arg, + cli: &mut impl cli::traits::Cli, +) -> Result { + if arg.options.is_empty() { + prompt_for_primitive(arg, cli) + } else if arg.variant { + prompt_for_variant(api, arg, cli) + } else { + prompt_for_composite(api, arg, cli) + } } -fn prompt_for_account(message: &str, cli: &mut impl cli::traits::Cli) -> Result { - let account: String = cli - .input(message) - .placeholder("e.g. 5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt") - .required(true) + +fn prompt_for_primitive(arg: &Arg, cli: &mut impl cli::traits::Cli) -> Result { + let user_input = cli + .input(format!("Enter the value for the parameter: {}", arg.name)) + .placeholder(&format!("Type required: {}", arg.type_input)) .interact()?; - Ok(account) + Ok(user_input) } -fn prompt_for_numeric_optional_value( - message: &str, + +fn prompt_for_variant( + api: &OnlineClient, + arg: &Arg, cli: &mut impl cli::traits::Cli, ) -> Result { - let value = cli - .input(message) - .placeholder("0 or (empty for None)") - .validate(|input: &String| match input.parse::() { - Ok(_) => Ok(()), - Err(_) => - if input.is_empty() || input == "None" { - Ok(()) - } else { - Err("Invalid value.") - }, - }) - .required(false) - .interact()?; - if value.is_empty() || value == "None" { - Ok("None".to_string()) + let selected_variant = { + let mut select = cli.select(format!("Select the value for the parameter: {}", arg.name)); + for option in &arg.options { + select = select.item(option, &option.name, &option.type_input); + } + select.interact()? + }; + + if !selected_variant.options.is_empty() { + let mut field_values = Vec::new(); + for field_arg in &selected_variant.options { + let field_value = prompt_argument(api, field_arg, cli)?; + field_values.push(field_value); + } + Ok(format!("{}({})", selected_variant.name, field_values.join(", "))) } else { - Ok(value) + Ok(selected_variant.name.clone()) } } -fn prompt_for_variant_value( - message: &str, - default_value: &str, + +fn prompt_for_composite( + api: &OnlineClient, + arg: &Arg, cli: &mut impl cli::traits::Cli, ) -> Result { - let mint_type: String = cli - .input(message) - .placeholder(&format!("e.g. {}", default_value)) - .default_input(default_value) - .required(true) - .interact()?; - Ok(mint_type) -} -fn prompt_for_collection_config(cli: &mut impl cli::traits::Cli) -> Result> { - let mut args: Vec = Vec::new(); - cli.info("Enter the Pallet NFT Collection Config:")?; - args.push(prompt_for_numeric_value("Collection's Settings", cli)?); - args.push(prompt_for_numeric_optional_value("Collection's Max Supply", cli)?); - cli.info("Enter the Mint Settings:")?; - args.push(prompt_for_variant_value("Who can mint?", "Issuer", cli)?); - args.push(prompt_for_numeric_optional_value("Price per mint", cli)?); - args.push(prompt_for_numeric_optional_value("When the mint starts", cli)?); - args.push(prompt_for_numeric_optional_value("When the mint ends", cli)?); - args.push(prompt_for_numeric_value("Default Item Settings", cli)?); - Ok(args) -} -fn prompt_for_witness_data(cli: &mut impl cli::traits::Cli) -> Result> { - let mut args: Vec = Vec::new(); - if cli - .confirm("Do you want to enter witness data for mint") - .initial_value(false) - .interact()? - { - args.push(prompt_for_numeric_optional_value( - "Id of the item in a required collection:", - cli, - )?); - args.push(prompt_for_numeric_optional_value("Mint price:", cli)?); - } else { - args.push("None".to_string()); + let mut field_values = Vec::new(); + for field_arg in &arg.options { + let field_value = prompt_argument(api, field_arg, cli)?; + field_values.push(field_value); } - Ok(args) + Ok(field_values.join(", ")) } diff --git a/crates/pop-parachains/src/call.rs b/crates/pop-parachains/src/call.rs index 93452638c..485234552 100644 --- a/crates/pop-parachains/src/call.rs +++ b/crates/pop-parachains/src/call.rs @@ -1,107 +1,26 @@ // SPDX-License-Identifier: GPL-3.0 -use crate::errors::Error; -use pop_common::{create_signer, parse_account}; -use strum::{EnumMessage as _, EnumProperty as _, VariantArray as _}; -use strum_macros::{AsRefStr, Display, EnumMessage, EnumProperty, EnumString, VariantArray}; +use crate::{errors::Error, utils::metadata::process_extrinsic_args}; +use pop_common::create_signer; use subxt::{ dynamic::Value, tx::{DynamicPayload, Payload}, OnlineClient, SubstrateConfig, }; -#[derive(AsRefStr, Clone, Debug, Display, EnumMessage, EnumString, Eq, PartialEq, VariantArray)] -pub enum Pallet { - #[strum(serialize = "Assets")] - Assets, - #[strum(serialize = "Balances")] - Balances, - #[strum(serialize = "Nfts")] - Nfts, -} - -#[derive( - AsRefStr, - Clone, - Debug, - Display, - EnumMessage, - EnumString, - EnumProperty, - Eq, - PartialEq, - VariantArray, -)] -pub enum Extrinsic { - #[strum( - serialize = "create", - message = "create", - detailed_message = "Create an Asset", - props(Pallet = "Assets") - )] - CreateAsset, - #[strum( - serialize = "mint", - message = "mint", - detailed_message = "Mint an Asset", - props(Pallet = "Assets") - )] - MintAsset, - #[strum( - serialize = "create_nft", - message = "create", - detailed_message = "Create a NFT Collection", - props(Pallet = "Nfts") - )] - CreateCollection, - #[strum( - serialize = "mint_nft", - message = "mint", - detailed_message = "Mint a NFT", - props(Pallet = "Nfts") - )] - MintNFT, - #[strum( - serialize = "transfer", - message = "transfer_allow_death", - detailed_message = "Transfer Balance", - props(Pallet = "Balances") - )] - Transfer, -} -impl Extrinsic { - /// Get the extrinsic's name. - pub fn extrinsic_name(&self) -> &str { - self.get_message().unwrap_or_default() - } - /// Get the description of the extrinsic. - pub fn description(&self) -> &str { - self.get_detailed_message().unwrap_or_default() - } - /// Get the pallet of the extrinsic. - pub fn pallet(&self) -> &str { - self.get_str("Pallet").unwrap_or_default() - } -} - pub async fn set_up_api(url: &str) -> Result, Error> { let api = OnlineClient::::from_url(url).await?; Ok(api) } -pub fn supported_extrinsics(api: &OnlineClient) -> Vec<&Extrinsic> { - Extrinsic::VARIANTS - .iter() - .filter(|t| extrinsic_is_supported(api, t.pallet(), t.extrinsic_name())) - .collect() -} - -pub fn construct_extrinsic( +pub async fn construct_extrinsic( + api: &OnlineClient, pallet_name: &str, extrinsic_name: &str, args: Vec, ) -> Result { - let parsed_args: Vec = parse_args(pallet_name, extrinsic_name, args)?; + let parsed_args: Vec = + process_extrinsic_args(api, pallet_name, extrinsic_name, args).await?; Ok(subxt::dynamic::tx(pallet_name, extrinsic_name, parsed_args)) } @@ -129,131 +48,3 @@ pub fn encode_call_data( let call_data = tx.encode_call_data(&api.metadata())?; Ok(format!("0x{}", hex::encode(call_data))) } - -fn parse_args( - pallet_name: &str, - extrinsic_name: &str, - raw_args: Vec, -) -> Result, Error> { - let mut args: Vec = Vec::new(); - let extrinsic = Extrinsic::VARIANTS - .iter() - .find(|t| t.pallet() == pallet_name && t.extrinsic_name() == extrinsic_name) - .ok_or(Error::ExtrinsicNotSupported(extrinsic_name.to_string()))?; - match extrinsic { - Extrinsic::CreateAsset => { - if raw_args.len() < 3 { - return Err(Error::ParsingArgsError); - } - args.push(parse_u128(&raw_args[0])?); - args.push(parse_account_id(&raw_args[1])?); - args.push(parse_u128(&raw_args[2])?); - }, - Extrinsic::MintAsset => { - if raw_args.len() < 3 { - return Err(Error::ParsingArgsError); - } - args.push(parse_u128(&raw_args[0])?); - args.push(parse_account_id(&raw_args[1])?); - args.push(parse_u128(&raw_args[2])?); - }, - Extrinsic::CreateCollection => { - if raw_args.len() < 7 { - return Err(Error::ParsingArgsError); - } - args.push(parse_account_id(&raw_args[0])?); - let mint_settings = Value::unnamed_composite(vec![ - Value::unnamed_variant(&raw_args[3], vec![]), - parse_optional_u128(&raw_args[4])?, - parse_optional_u128(&raw_args[5])?, - parse_optional_u128(&raw_args[6])?, - parse_u128(&raw_args[7])?, - ]); - let max_supply = parse_optional_u128(&raw_args[2])?; - args.push(Value::unnamed_composite(vec![ - parse_u128(&raw_args[1])?, - max_supply, - mint_settings, - ])) - }, - Extrinsic::MintNFT => { - println!("{:?}", raw_args.len()); - if raw_args.len() < 4 { - return Err(Error::ParsingArgsError); - } - args.push(parse_u128(&raw_args[0])?); - args.push(parse_u128(&raw_args[1])?); - args.push(parse_account_id(&raw_args[2])?); - if raw_args[3] == "None" && raw_args.len() == 4 { - args.push(Value::unnamed_variant("None", vec![])); - } else { - if raw_args.len() < 5 { - return Err(Error::ParsingArgsError); - } - let owned_item = parse_optional_u128(&raw_args[3])?; - let mint_price = parse_optional_u128(&raw_args[4])?; - args.push(Value::unnamed_variant( - "Some", - vec![Value::unnamed_composite(vec![owned_item, mint_price])], - )); - } - }, - Extrinsic::Transfer => { - if raw_args.len() < 2 { - return Err(Error::ParsingArgsError); - } - args.push(parse_account_id(&raw_args[0])?); - args.push(parse_u128(&raw_args[1])?); - }, - } - Ok(args) -} - -// Helper function to parse u128 -fn parse_u128(arg: &str) -> Result { - Ok(Value::u128(arg.parse::().map_err(|_| Error::ParsingArgsError)?)) -} -// Helper function to parse account id -fn parse_account_id(arg: &str) -> Result { - Ok(Value::unnamed_variant("Id", vec![Value::from_bytes(parse_account(arg)?)])) -} -// Helper function to handle "None" or Some(u128) values -fn parse_optional_u128(arg: &str) -> Result { - if arg == "None" { - Ok(Value::unnamed_variant("None", vec![])) - } else { - Ok(Value::unnamed_variant("Some", vec![parse_u128(arg)?])) - } -} - -fn extrinsic_is_supported( - api: &OnlineClient, - pallet_name: &str, - extrinsic: &str, -) -> bool { - let metadata = api.metadata(); - // Try to get the pallet metadata by name - let pallet_metadata = match metadata.pallet_by_name(pallet_name) { - Some(pallet) => pallet, - None => return false, // Return false if pallet is not found - }; - // Try to get the extrinsic metadata by name from the pallet - if pallet_metadata.call_variant_by_name(extrinsic).is_some() { - return true; - } - false -} - -#[cfg(test)] -mod tests { - use super::*; - use anyhow::Result; - - #[tokio::test] - async fn extrinsic_is_supported_works() -> Result<()> { - let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; - assert!(extrinsic_is_supported(&api, "Nfts", "mint")); - assert!(!extrinsic_is_supported(&api, "Nfts", "mint_no_exist")); - Ok(()) - } -} diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index 53f603b11..47ae30f5a 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -15,10 +15,7 @@ pub use build::{ binary_path, build_parachain, export_wasm_file, generate_genesis_state_file, generate_plain_chain_spec, generate_raw_chain_spec, is_supported, ChainSpec, }; -pub use call::{ - construct_extrinsic, encode_call_data, set_up_api, sign_and_submit_extrinsic, - supported_extrinsics, Extrinsic, Pallet, -}; +pub use call::{construct_extrinsic, encode_call_data, set_up_api, sign_and_submit_extrinsic}; pub use errors::Error; pub use indexmap::IndexSet; pub use new_pallet::{create_pallet_template, new_pallet_options::*, TemplatePalletConfig}; @@ -27,6 +24,9 @@ pub use new_parachain::instantiate_template_dir; pub use subxt::{dynamic::Value, tx::DynamicPayload, OnlineClient, SubstrateConfig}; pub use templates::{Config, Parachain, Provider}; pub use up::Zombienet; -pub use utils::helpers::is_initial_endowment_valid; +pub use utils::{ + helpers::is_initial_endowment_valid, + metadata::{find_pallet_by_name, parse_chain_metadata, process_prompt_arguments, Arg}, +}; /// Information about the Node. External export from Zombienet-SDK. pub use zombienet_sdk::NetworkNode; diff --git a/crates/pop-parachains/src/utils/metadata.rs b/crates/pop-parachains/src/utils/metadata.rs new file mode 100644 index 000000000..27b13f5a5 --- /dev/null +++ b/crates/pop-parachains/src/utils/metadata.rs @@ -0,0 +1,508 @@ +// SPDX-License-Identifier: GPL-3.0 + +use crate::errors::Error; +use pop_common::parse_account; +use scale_info::{ + form::PortableForm, Field, PortableRegistry, Type, TypeDef, TypeDefCompact, TypeDefComposite, + TypeDefPrimitive, TypeDefTuple, TypeDefVariant, Variant, +}; +use subxt::{dynamic::Value, Metadata, OnlineClient, SubstrateConfig}; + +#[derive(Clone, PartialEq, Eq)] +/// Describes a pallet with its extrinsics. +pub struct Pallet { + /// The name of the pallet. + pub name: String, + /// The documentation of the pallet. + pub docs: String, + // The extrinsics of the pallet. + pub extrinsics: Vec>, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Arg { + pub name: String, + pub type_input: String, + pub optional: bool, + pub options: Vec, + pub variant: bool, +} + +/// Parses the chain metadata to extract information about pallets and their extrinsics. +pub async fn parse_chain_metadata( + api: OnlineClient, +) -> Result, Error> { + let metadata: Metadata = api.metadata(); + Ok(metadata + .pallets() + .map(|pallet| { + let extrinsics = + pallet.call_variants().map(|variants| variants.to_vec()).unwrap_or_default(); + Pallet { name: pallet.name().to_string(), extrinsics, docs: pallet.docs().join(" ") } + }) + .collect()) +} + +pub async fn find_pallet_by_name( + api: &OnlineClient, + pallet_name: &str, +) -> Result { + let metadata: Metadata = api.metadata(); + for pallet in metadata.pallets() { + if pallet.name() == pallet_name { + let extrinsics = + pallet.call_variants().map(|variants| variants.to_vec()).unwrap_or_default(); + return Ok(Pallet { + name: pallet.name().to_string(), + extrinsics, + docs: pallet.docs().join(" "), + }); + } + } + Err(Error::PalletNotFound(pallet_name.to_string())) +} + +async fn find_extrinsic_by_name( + api: &OnlineClient, + pallet_name: &str, + extrinsic_name: &str, +) -> Result, Error> { + let pallet = find_pallet_by_name(api, pallet_name).await?; + // Check if the specified extrinsic exists within this pallet + if let Some(extrinsic) = pallet.extrinsics.iter().find(|&e| e.name == extrinsic_name) { + return Ok(extrinsic.clone()); + } else { + return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); + } +} + +pub async fn process_extrinsic_args( + api: &OnlineClient, + pallet_name: &str, + extrinsic_name: &str, + args: Vec, +) -> Result, Error> { + let metadata: Metadata = api.metadata(); + let registry = metadata.types(); + let extrinsic = find_extrinsic_by_name(&api, pallet_name, extrinsic_name).await?; + + let mut return_args: Vec = Vec::new(); + for (index, field) in extrinsic.fields.iter().enumerate() { + let arg_input = args.get(index).ok_or(Error::ParsingArgsError)?; + let type_info = registry.resolve(field.ty.id).ok_or(Error::ParsingArgsError)?; //Resolve with type_id + let arg_processed = process_value(arg_input, type_info, registry)?; + return_args.push(arg_processed); + } + Ok(return_args) +} + +pub fn process_value( + arg: &str, + ty: &Type, + registry: &PortableRegistry, +) -> Result { + let type_path = ty.path.segments.join("::"); + match type_path.as_str() { + "Option" => handle_option_type(arg, ty, registry), + "sp_core::crypto::AccountId32" => Ok(Value::from_bytes(parse_account(arg)?)), + _ => match &ty.type_def { + TypeDef::Primitive(primitive) => handle_primitive_type(arg, primitive), + TypeDef::Composite(composite) => handle_composite_type(arg, composite, registry), + TypeDef::Variant(variant_def) => handle_variant_type(arg, variant_def, registry), + TypeDef::Tuple(tuple) => handle_tuple_type(arg, tuple, registry), + TypeDef::Compact(compact) => handle_compact_type(arg, compact, registry), + TypeDef::Sequence(_) | TypeDef::Array(_) => Ok(Value::from_bytes(arg)), + _ => Err(Error::ParsingArgsError), + }, + } +} +fn handle_option_type( + arg: &str, + ty: &Type, + registry: &PortableRegistry, +) -> Result { + // Handle Option + if arg.trim() == "None" { + Ok(Value::unnamed_variant("None", vec![])) + } else if arg.trim().starts_with("Some(") && arg.trim().ends_with(')') { + let inner_arg = &arg.trim()[5..arg.trim().len() - 1]; + if let Some(inner_type_id) = ty.type_params.get(0).and_then(|param| param.ty) { + let inner_ty = registry.resolve(inner_type_id.id()).ok_or(Error::ParsingArgsError)?; + let inner_value = process_value(inner_arg.trim(), inner_ty, registry)?; + Ok(Value::unnamed_variant("Some", vec![inner_value])) + } else { + Err(Error::ParsingArgsError) + } + } else { + Err(Error::ParsingArgsError) + } +} + +fn handle_primitive_type(arg: &str, primitive: &TypeDefPrimitive) -> Result { + match primitive { + TypeDefPrimitive::Bool => { + Ok(Value::bool(arg.parse::().map_err(|_| Error::ParsingArgsError)?)) + }, + TypeDefPrimitive::Char => { + Ok(Value::char(arg.chars().next().ok_or(Error::ParsingArgsError)?)) + }, + TypeDefPrimitive::Str => Ok(Value::string(arg.to_string())), + TypeDefPrimitive::U8 + | TypeDefPrimitive::U16 + | TypeDefPrimitive::U32 + | TypeDefPrimitive::U64 + | TypeDefPrimitive::U128 + | TypeDefPrimitive::U256 => { + Ok(Value::u128(arg.parse::().map_err(|_| Error::ParsingArgsError)?)) + }, + TypeDefPrimitive::I8 + | TypeDefPrimitive::I16 + | TypeDefPrimitive::I32 + | TypeDefPrimitive::I64 + | TypeDefPrimitive::I128 + | TypeDefPrimitive::I256 => { + Ok(Value::i128(arg.parse::().map_err(|_| Error::ParsingArgsError)?)) + }, + } +} +fn handle_composite_type( + arg: &str, + composite: &TypeDefComposite, + registry: &PortableRegistry, +) -> Result { + let arg_trimmed = arg.trim(); + let inner = if arg_trimmed.starts_with('{') && arg_trimmed.ends_with('}') { + &arg_trimmed[1..arg_trimmed.len() - 1] + } else { + arg_trimmed + }; + let sub_args = split_top_level_commas(inner)?; + if sub_args.len() != composite.fields.len() { + return Err(Error::ParsingArgsError); + } + let mut values = Vec::new(); + for (field, sub_arg) in composite.fields.iter().zip(sub_args.iter()) { + let sub_ty = registry.resolve(field.ty.id).ok_or(Error::ParsingArgsError)?; + let value = process_value(sub_arg.trim(), sub_ty, registry)?; + let field_name = field.name.clone().unwrap_or_default(); + values.push((field_name, value)); + } + Ok(Value::named_composite(values)) +} +fn handle_variant_type( + arg: &str, + variant: &TypeDefVariant, + registry: &PortableRegistry, +) -> Result { + // Handle variants like Some(value1, value2, ...) + let input = arg.trim(); + let (variant_name, variant_data) = if let Some(start) = input.find('(') { + if !input.ends_with(')') { + return Err(Error::ParsingArgsError); + } + let name = input[..start].trim(); + let data_str = &input[start + 1..input.len() - 1]; + (name, Some(data_str)) + } else { + let name = input.trim(); + (name, None) + }; + + // Find the variant definition + let variant_def = variant + .variants + .iter() + .find(|v| v.name == variant_name) + .ok_or(Error::ParsingArgsError)?; + + // Handle variant fields + let fields_values = if let Some(data_str) = variant_data { + let inputs = split_top_level_commas(data_str)?; + if inputs.len() != variant_def.fields.len() { + return Err(Error::ParsingArgsError); + } + + let mut values = Vec::new(); + for (field_def, field_input) in variant_def.fields.iter().zip(inputs.iter()) { + let field_ty = registry.resolve(field_def.ty.id).ok_or(Error::ParsingArgsError)?; + let field_value = process_value(field_input.trim(), field_ty, registry)?; + values.push(field_value); + } + values + } else if variant_def.fields.is_empty() { + vec![] + } else { + // Variant has fields but no data provided + return Err(Error::ParsingArgsError); + }; + + Ok(Value::unnamed_variant(variant_name, fields_values)) +} +fn handle_tuple_type( + arg: &str, + tuple: &TypeDefTuple, + registry: &PortableRegistry, +) -> Result { + let arg_trimmed = arg.trim(); + let inner = if arg_trimmed.starts_with('(') && arg_trimmed.ends_with(')') { + &arg_trimmed[1..arg_trimmed.len() - 1] + } else { + arg_trimmed + }; + let sub_args = split_top_level_commas(inner)?; + if sub_args.len() != tuple.fields.len() { + return Err(Error::ParsingArgsError); + } + let mut values = Vec::new(); + for (sub_ty_id, sub_arg) in tuple.fields.iter().zip(sub_args.iter()) { + let sub_ty = registry.resolve(sub_ty_id.id()).ok_or(Error::ParsingArgsError)?; + let value = process_value(sub_arg.trim(), sub_ty, registry)?; + values.push(value); + } + Ok(Value::unnamed_composite(values)) +} +fn handle_compact_type( + arg: &str, + compact: &TypeDefCompact, + registry: &PortableRegistry, +) -> Result { + let inner_ty = registry.resolve(compact.type_param.id()).ok_or(Error::ParsingArgsError)?; + process_value(arg, inner_ty, registry) +} +fn split_top_level_commas(s: &str) -> Result, Error> { + let mut result = Vec::new(); + let mut brace_depth = 0; + let mut paren_depth = 0; + let mut last_index = 0; + for (i, c) in s.char_indices() { + match c { + '{' => brace_depth += 1, + '}' => brace_depth -= 1, + '(' => paren_depth += 1, + ')' => paren_depth -= 1, + ',' if brace_depth == 0 && paren_depth == 0 => { + result.push(&s[last_index..i]); + last_index = i + 1; + }, + _ => (), + } + } + if brace_depth != 0 || paren_depth != 0 { + return Err(Error::ParsingArgsError); + } + result.push(&s[last_index..]); + Ok(result) +} + +/// Processes an argument by constructing its `Arg` representation, including type information. +pub fn process_prompt_arguments( + api: &OnlineClient, + field: &Field, +) -> Result { + let type_id = field.ty().id(); + let metadata = api.metadata(); + let registry = metadata.types(); + let name = format!("{:?}", field.name()); + let type_name = field.type_name(); + parse_type(registry, type_id, name, type_name) +} + +fn parse_type( + registry: &PortableRegistry, + type_id: u32, + name: String, + type_name: Option<&String>, +) -> Result { + let type_info = registry.resolve(type_id).ok_or(Error::ParsingArgsError)?; + // Check if the type is Option by checking the path segments + if type_info.path.segments == ["Option"] { + // The type is Option + // Get the inner type T from type parameters + if let Some(inner_type_id) = type_info.type_params.get(0).and_then(|param| param.ty) { + let inner_arg = parse_type(registry, inner_type_id.id(), name.clone(), type_name)?; + Ok(Arg { + name, + type_input: inner_arg.type_input, + optional: true, + options: inner_arg.options, + variant: false, + }) + } else { + // Unable to get inner type + Err(Error::ParsingArgsError) + } + } else { + let type_input = get_type_name(registry, type_info); + match &type_info.type_def { + TypeDef::Primitive(_) => { + Ok(Arg { name, type_input, optional: false, options: vec![], variant: false }) + }, + TypeDef::Composite(composite) => { + let mut composite_fields = vec![]; + + for composite_field in composite.fields() { + if let Some(name) = composite_field.name() { + let field_name = format!("{:?}", composite_field.name()); + let field_type_id = composite_field.ty().id(); + let field_type_name = composite_field.type_name(); + + let field_arg = + parse_type(registry, field_type_id, field_name, field_type_name)?; + composite_fields.push(field_arg); + } + } + + Ok(Arg { + name, + type_input, + optional: false, + options: composite_fields, + variant: false, + }) + }, + TypeDef::Variant(variant) => { + // Regular enum handling for non-option variants + let mut variant_fields = vec![]; + for variant in variant.variants() { + let variant_name = variant.name().to_string(); + + let mut fields = vec![]; + for field in variant.fields() { + let field_name = format!("{:?}", field.name()); + let field_type_id = field.ty().id(); + let field_type_name = field.type_name(); + + let field_arg = parse_type( + registry, + field_type_id, + variant_name.clone(), + field_type_name, + )?; + fields.push(field_arg); + } + + variant_fields.push(Arg { + name: variant_name, + type_input: "".to_string(), + optional: false, + options: fields, + variant: false, + }); + } + + Ok(Arg { + name, + type_input, + optional: false, + options: variant_fields, + variant: true, + }) + }, + TypeDef::Array(_) | TypeDef::Sequence(_) | TypeDef::Tuple(_) | TypeDef::Compact(_) => { + Ok(Arg { name, type_input, optional: false, options: vec![], variant: false }) + }, + _ => Err(Error::ParsingArgsError), + } + } +} + +fn get_type_name(registry: &PortableRegistry, type_info: &Type) -> String { + if !type_info.path.segments.is_empty() { + type_info.path.segments.join("::") + } else { + match &type_info.type_def { + TypeDef::Primitive(primitive) => format!("{:?}", primitive), + TypeDef::Array(array) => { + // Get the inner type of Compact + if let Some(inner_type_info) = registry.resolve(array.type_param().id()) { + get_type_name(registry, inner_type_info) + } else { + "Compact".to_string() + } + }, + TypeDef::Sequence(sequence) => { + // Get the inner type of Compact + if let Some(inner_type_info) = registry.resolve(sequence.type_param().id()) { + get_type_name(registry, inner_type_info) + } else { + "Compact".to_string() + } + }, + TypeDef::Tuple(tuple) => { + let field_types: Vec = tuple + .fields() + .iter() + .map(|field| { + if let Some(field_type_info) = registry.resolve(field.id()) { + get_type_name(registry, field_type_info) + } else { + "Unknown".to_string() + } + }) + .collect(); + format!("({})", field_types.join(", ")) + }, + TypeDef::Compact(compact) => { + // Get the inner type of Compact + if let Some(inner_type_info) = registry.resolve(compact.type_param().id()) { + get_type_name(registry, inner_type_info) + } else { + "Compact".to_string() + } + }, + _ => "Unknown Type".to_string(), + } + } +} + +// #[cfg(test)] +// mod tests { +// use crate::set_up_api; + +// use super::*; +// use anyhow::Result; + +// #[tokio::test] +// async fn process_prompt_arguments_works() -> Result<()> { +// let api = set_up_api("ws://127.0.0.1:9944").await?; +// // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; +// let ex = find_extrinsic_by_name(&api, "Nfts", "mint").await?; +// let prompt_args1 = process_prompt_arguments(&api, &ex.fields()[2])?; + +// Ok(()) +// } + +// #[tokio::test] +// async fn process_extrinsic_args_works() -> Result<()> { +// let api = set_up_api("ws://127.0.0.1:9944").await?; +// // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; +// let ex = find_extrinsic_by_name(&api, "Nfts", "mint").await?; +// let args_parsed = process_extrinsic_args( +// &api, +// "Nfts", +// "mint", +// vec![ +// "1".to_string(), +// "1".to_string(), +// "Id(5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y)".to_string(), +// "Some(Some(1), Some(1))".to_string(), +// ], +// ) +// .await?; +// println!(" ARGS PARSER {:?}", args_parsed); + +// Ok(()) +// } + +// #[tokio::test] +// async fn process_extrinsic_args2_works() -> Result<()> { +// let api = set_up_api("ws://127.0.0.1:9944").await?; +// // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; +// let ex = find_extrinsic_by_name(&api, "Nfts", "mint").await?; +// let args_parsed = +// process_extrinsic_args(&api, "System", "remark", vec!["0x11".to_string()]).await?; +// println!(" ARGS PARSER {:?}", args_parsed); + +// Ok(()) +// } +// } diff --git a/crates/pop-parachains/src/utils/mod.rs b/crates/pop-parachains/src/utils/mod.rs index 265ebafd4..a9f3ad9a8 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 helpers; +pub mod metadata; From f046a9e4083956a546f2199c256fe8ebe1ba2a7e Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Wed, 20 Nov 2024 10:06:28 +0100 Subject: [PATCH 136/211] refactor: reorganize and clean metadata functions --- crates/pop-cli/src/commands/call/parachain.rs | 86 +-- crates/pop-common/Cargo.toml | 1 + crates/pop-common/src/lib.rs | 12 +- crates/pop-common/src/metadata.rs | 151 ++++++ crates/pop-contracts/src/utils/metadata.rs | 147 +---- crates/pop-parachains/src/lib.rs | 2 +- crates/pop-parachains/src/utils/metadata.rs | 508 ------------------ .../pop-parachains/src/utils/metadata/mod.rs | 296 ++++++++++ .../src/utils/metadata/type_parser.rs | 162 ++++++ 9 files changed, 664 insertions(+), 701 deletions(-) create mode 100644 crates/pop-common/src/metadata.rs delete mode 100644 crates/pop-parachains/src/utils/metadata.rs create mode 100644 crates/pop-parachains/src/utils/metadata/mod.rs create mode 100644 crates/pop-parachains/src/utils/metadata/type_parser.rs diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 30f54dc44..cbff1f231 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -4,9 +4,9 @@ use crate::cli::{self, traits::*}; use anyhow::{anyhow, Result}; use clap::Args; use pop_parachains::{ - construct_extrinsic, encode_call_data, find_pallet_by_name, parse_chain_metadata, - process_prompt_arguments, set_up_api, sign_and_submit_extrinsic, Arg, DynamicPayload, - OnlineClient, SubstrateConfig, + construct_extrinsic, encode_call_data, field_to_param, find_pallet_by_name, + parse_chain_metadata, set_up_api, sign_and_submit_extrinsic, DynamicPayload, OnlineClient, + Param, SubstrateConfig, }; const DEFAULT_URL: &str = "ws://localhost:9944/"; @@ -92,7 +92,7 @@ impl CallParachainCommand { }; // Parse metadata from url chain. let api = set_up_api(self.url.as_str()).await?; - let pallets = match parse_chain_metadata(api.clone()).await { + let pallets = match parse_chain_metadata(&api).await { Ok(pallets) => pallets, Err(e) => { return Err(anyhow!(format!( @@ -128,9 +128,9 @@ impl CallParachainCommand { self.extrinsic = Some(extrinsic.name); // Resolve message arguments. let mut contract_args = Vec::new(); - for arg in extrinsic.fields { - let arg_metadata = process_prompt_arguments(&api, &arg)?; - let input = prompt_argument(&api, &arg_metadata, cli)?; + for field in extrinsic.fields { + let param = field_to_param(&api, &field)?; + let input = prompt_for_param(&api, cli, ¶m)?; contract_args.push(input); } self.args = contract_args; @@ -253,70 +253,70 @@ fn display_message(message: &str, success: bool, cli: &mut impl cli::traits::Cli Ok(()) } -// Prompt the user for the proper arguments. -fn prompt_argument( +// Prompts the user for the value of a parameter. +fn prompt_for_param( api: &OnlineClient, - arg: &Arg, cli: &mut impl cli::traits::Cli, + param: &Param, ) -> Result { - Ok(if arg.optional { - // The argument is optional; prompt the user to decide whether to provide a value. + if param.is_optional { + // Prompt user for optional parameter decision. if !cli .confirm(format!( "Do you want to provide a value for the optional parameter: {}?", - arg.name + param.name )) .interact()? { return Ok("None".to_string()); } - let value = prompt_argument_value(api, arg, cli)?; - format!("Some({})", value) + let value = get_param_value(api, cli, param)?; + Ok(format!("Some({})", value)) } else { - // Non-optional argument. - prompt_argument_value(api, arg, cli)? - }) + // Handle non-optional parameters. + get_param_value(api, cli, param) + } } -fn prompt_argument_value( +// Resolves the value of a parameter based on its type. +fn get_param_value( api: &OnlineClient, - arg: &Arg, cli: &mut impl cli::traits::Cli, + param: &Param, ) -> Result { - if arg.options.is_empty() { - prompt_for_primitive(arg, cli) - } else if arg.variant { - prompt_for_variant(api, arg, cli) + if param.sub_params.is_empty() { + prompt_for_primitive_param(cli, param) + } else if param.is_variant { + prompt_for_variant_param(api, cli, param) } else { - prompt_for_composite(api, arg, cli) + prompt_for_composite_param(api, cli, param) } } -fn prompt_for_primitive(arg: &Arg, cli: &mut impl cli::traits::Cli) -> Result { - let user_input = cli - .input(format!("Enter the value for the parameter: {}", arg.name)) - .placeholder(&format!("Type required: {}", arg.type_input)) - .interact()?; - Ok(user_input) +fn prompt_for_primitive_param(cli: &mut impl cli::traits::Cli, param: &Param) -> Result { + Ok(cli + .input(format!("Enter the value for the parameter: {}", param.name)) + .placeholder(&format!("Type required: {}", param.type_name)) + .interact()?) } -fn prompt_for_variant( +fn prompt_for_variant_param( api: &OnlineClient, - arg: &Arg, cli: &mut impl cli::traits::Cli, + param: &Param, ) -> Result { let selected_variant = { - let mut select = cli.select(format!("Select the value for the parameter: {}", arg.name)); - for option in &arg.options { - select = select.item(option, &option.name, &option.type_input); + let mut select = cli.select(format!("Select the value for the parameter: {}", param.name)); + for option in ¶m.sub_params { + select = select.item(option, &option.name, &option.type_name); } select.interact()? }; - if !selected_variant.options.is_empty() { + if !selected_variant.sub_params.is_empty() { let mut field_values = Vec::new(); - for field_arg in &selected_variant.options { - let field_value = prompt_argument(api, field_arg, cli)?; + for field_arg in &selected_variant.sub_params { + let field_value = prompt_for_param(api, cli, field_arg)?; field_values.push(field_value); } Ok(format!("{}({})", selected_variant.name, field_values.join(", "))) @@ -325,14 +325,14 @@ fn prompt_for_variant( } } -fn prompt_for_composite( +fn prompt_for_composite_param( api: &OnlineClient, - arg: &Arg, cli: &mut impl cli::traits::Cli, + param: &Param, ) -> Result { let mut field_values = Vec::new(); - for field_arg in &arg.options { - let field_value = prompt_argument(api, field_arg, cli)?; + for field_arg in ¶m.sub_params { + let field_value = prompt_for_param(api, cli, field_arg)?; field_values.push(field_value); } Ok(field_values.join(", ")) diff --git a/crates/pop-common/Cargo.toml b/crates/pop-common/Cargo.toml index 5c24e35a3..824f82c6c 100644 --- a/crates/pop-common/Cargo.toml +++ b/crates/pop-common/Cargo.toml @@ -16,6 +16,7 @@ git2.workspace = true git2_credentials.workspace = true regex.workspace = true reqwest.workspace = true +scale-info.workspace = true serde_json.workspace = true serde.workspace = true strum.workspace = true diff --git a/crates/pop-common/src/lib.rs b/crates/pop-common/src/lib.rs index 1f527d15f..ebe88ff11 100644 --- a/crates/pop-common/src/lib.rs +++ b/crates/pop-common/src/lib.rs @@ -3,6 +3,7 @@ pub mod errors; pub mod git; pub mod helpers; pub mod manifest; +pub mod metadata; pub mod polkadot_sdk; pub mod signer; pub mod sourcing; @@ -13,6 +14,7 @@ pub use errors::Error; pub use git::{Git, GitHub, Release}; pub use helpers::{get_project_name_from_path, prefix_with_current_dir_if_needed, replace_in_file}; pub use manifest::{add_crate_to_workspace, find_workspace_toml}; +pub use metadata::format_type; pub use signer::{create_signer, parse_account}; pub use templates::extractor::extract_template_files; // External exports @@ -41,16 +43,18 @@ pub fn target() -> Result<&'static str, Error> { } match ARCH { - "aarch64" => + "aarch64" => { return match OS { "macos" => Ok("aarch64-apple-darwin"), _ => Ok("aarch64-unknown-linux-gnu"), - }, - "x86_64" | "x86" => + } + }, + "x86_64" | "x86" => { return match OS { "macos" => Ok("x86_64-apple-darwin"), _ => Ok("x86_64-unknown-linux-gnu"), - }, + } + }, &_ => {}, } Err(Error::UnsupportedPlatform { arch: ARCH, os: OS }) diff --git a/crates/pop-common/src/metadata.rs b/crates/pop-common/src/metadata.rs new file mode 100644 index 000000000..8c5675cf5 --- /dev/null +++ b/crates/pop-common/src/metadata.rs @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: GPL-3.0 + +use scale_info::{form::PortableForm, PortableRegistry, Type, TypeDef, TypeDefPrimitive}; + +/// Formats a specified type, using the registry to output its full type representation. +/// +/// # Arguments +/// * `ty`: A reference to the `Type` to be formatted. +/// * `registry`: A reference to the `PortableRegistry` to resolve type dependencies. +pub fn format_type(ty: &Type, registry: &PortableRegistry) -> String { + let mut name = ty + .path + .segments + .last() + .map(|s| s.to_owned()) + .unwrap_or_else(|| ty.path.to_string()); + + if !ty.type_params.is_empty() { + let params: Vec<_> = ty + .type_params + .iter() + .filter_map(|p| registry.resolve(p.ty.unwrap().id)) + .map(|t| format_type(t, registry)) + .collect(); + name = format!("{name}<{}>", params.join(",")); + } + + name = format!( + "{name}{}", + match &ty.type_def { + TypeDef::Composite(composite) => { + if composite.fields.is_empty() { + return "".to_string(); + } + + let mut named = false; + let fields: Vec<_> = composite + .fields + .iter() + .filter_map(|f| match f.name.as_ref() { + None => registry.resolve(f.ty.id).map(|t| format_type(t, registry)), + Some(field) => { + named = true; + f.type_name.as_ref().map(|t| format!("{field}: {t}")) + }, + }) + .collect(); + match named { + true => format!(" {{ {} }}", fields.join(", ")), + false => format!(" ({})", fields.join(", ")), + } + }, + TypeDef::Variant(variant) => { + let variants: Vec<_> = variant + .variants + .iter() + .map(|v| { + if v.fields.is_empty() { + return v.name.clone(); + } + + let name = v.name.as_str(); + let mut named = false; + let fields: Vec<_> = v + .fields + .iter() + .filter_map(|f| match f.name.as_ref() { + None => registry.resolve(f.ty.id).map(|t| format_type(t, registry)), + Some(field) => { + named = true; + f.type_name.as_ref().map(|t| format!("{field}: {t}")) + }, + }) + .collect(); + format!( + "{name}{}", + match named { + true => format!("{{ {} }}", fields.join(", ")), + false => format!("({})", fields.join(", ")), + } + ) + }) + .collect(); + format!(": {}", variants.join(", ")) + }, + TypeDef::Sequence(sequence) => { + format!( + "[{}]", + format_type( + registry.resolve(sequence.type_param.id).expect("sequence type not found"), + registry + ) + ) + }, + TypeDef::Array(array) => { + format!( + "[{};{}]", + format_type( + registry.resolve(array.type_param.id).expect("array type not found"), + registry + ), + array.len + ) + }, + TypeDef::Tuple(tuple) => { + let fields: Vec<_> = tuple + .fields + .iter() + .filter_map(|p| registry.resolve(p.id)) + .map(|t| format_type(t, registry)) + .collect(); + format!("({})", fields.join(",")) + }, + TypeDef::Primitive(primitive) => { + use TypeDefPrimitive::*; + match primitive { + Bool => "bool", + Char => "char", + Str => "str", + U8 => "u8", + U16 => "u16", + U32 => "u32", + U64 => "u64", + U128 => "u128", + U256 => "u256", + I8 => "i8", + I16 => "i16", + I32 => "i32", + I64 => "i64", + I128 => "i128", + I256 => "i256", + } + .to_string() + }, + TypeDef::Compact(compact) => { + format!( + "Compact<{}>", + format_type( + registry.resolve(compact.type_param.id).expect("compact type not found"), + registry + ) + ) + }, + TypeDef::BitSequence(_) => { + unimplemented!("bit sequence not currently supported") + }, + } + ); + + name +} diff --git a/crates/pop-contracts/src/utils/metadata.rs b/crates/pop-contracts/src/utils/metadata.rs index f0dd2b8e1..09c2d3220 100644 --- a/crates/pop-contracts/src/utils/metadata.rs +++ b/crates/pop-contracts/src/utils/metadata.rs @@ -3,7 +3,8 @@ use crate::errors::Error; use contract_extrinsics::ContractArtifacts; use contract_transcode::ink_metadata::MessageParamSpec; -use scale_info::{form::PortableForm, PortableRegistry, Type, TypeDef, TypeDefPrimitive}; +use pop_common::format_type; +use scale_info::{form::PortableForm, PortableRegistry}; use std::path::Path; /// Describes a parameter. @@ -140,150 +141,6 @@ fn process_args( args } -// Formats a specified type, using the registry to output its full type representation. -fn format_type(ty: &Type, registry: &PortableRegistry) -> String { - let mut name = ty - .path - .segments - .last() - .map(|s| s.to_owned()) - .unwrap_or_else(|| ty.path.to_string()); - - if !ty.type_params.is_empty() { - let params: Vec<_> = ty - .type_params - .iter() - .filter_map(|p| registry.resolve(p.ty.unwrap().id)) - .map(|t| format_type(t, registry)) - .collect(); - name = format!("{name}<{}>", params.join(",")); - } - - name = format!( - "{name}{}", - match &ty.type_def { - TypeDef::Composite(composite) => { - if composite.fields.is_empty() { - return "".to_string(); - } - - let mut named = false; - let fields: Vec<_> = composite - .fields - .iter() - .filter_map(|f| match f.name.as_ref() { - None => registry.resolve(f.ty.id).map(|t| format_type(t, registry)), - Some(field) => { - named = true; - f.type_name.as_ref().map(|t| format!("{field}: {t}")) - }, - }) - .collect(); - match named { - true => format!(" {{ {} }}", fields.join(", ")), - false => format!(" ({})", fields.join(", ")), - } - }, - TypeDef::Variant(variant) => { - let variants: Vec<_> = variant - .variants - .iter() - .map(|v| { - if v.fields.is_empty() { - return v.name.clone(); - } - - let name = v.name.as_str(); - let mut named = false; - let fields: Vec<_> = v - .fields - .iter() - .filter_map(|f| match f.name.as_ref() { - None => registry.resolve(f.ty.id).map(|t| format_type(t, registry)), - Some(field) => { - named = true; - f.type_name.as_ref().map(|t| format!("{field}: {t}")) - }, - }) - .collect(); - format!( - "{name}{}", - match named { - true => format!("{{ {} }}", fields.join(", ")), - false => format!("({})", fields.join(", ")), - } - ) - }) - .collect(); - format!(": {}", variants.join(", ")) - }, - TypeDef::Sequence(sequence) => { - format!( - "[{}]", - format_type( - registry.resolve(sequence.type_param.id).expect("sequence type not found"), - registry - ) - ) - }, - TypeDef::Array(array) => { - format!( - "[{};{}]", - format_type( - registry.resolve(array.type_param.id).expect("array type not found"), - registry - ), - array.len - ) - }, - TypeDef::Tuple(tuple) => { - let fields: Vec<_> = tuple - .fields - .iter() - .filter_map(|p| registry.resolve(p.id)) - .map(|t| format_type(t, registry)) - .collect(); - format!("({})", fields.join(",")) - }, - TypeDef::Primitive(primitive) => { - use TypeDefPrimitive::*; - match primitive { - Bool => "bool", - Char => "char", - Str => "str", - U8 => "u8", - U16 => "u16", - U32 => "u32", - U64 => "u64", - U128 => "u128", - U256 => "u256", - I8 => "i8", - I16 => "i16", - I32 => "i32", - I64 => "i64", - I128 => "i128", - I256 => "i256", - } - .to_string() - }, - TypeDef::Compact(compact) => { - format!( - "Compact<{}>", - format_type( - registry.resolve(compact.type_param.id).expect("compact type not found"), - registry - ) - ) - }, - TypeDef::BitSequence(_) => { - unimplemented!("bit sequence not currently supported") - }, - } - ); - - name -} - /// Processes a list of argument values for a specified contract function, /// wrapping each value in `Some(...)` or replacing it with `None` if the argument is optional /// diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index 47ae30f5a..d319c0097 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -26,7 +26,7 @@ pub use templates::{Config, Parachain, Provider}; pub use up::Zombienet; pub use utils::{ helpers::is_initial_endowment_valid, - metadata::{find_pallet_by_name, parse_chain_metadata, process_prompt_arguments, Arg}, + metadata::{field_to_param, find_pallet_by_name, parse_chain_metadata, Param}, }; /// Information about the Node. External export from Zombienet-SDK. pub use zombienet_sdk::NetworkNode; diff --git a/crates/pop-parachains/src/utils/metadata.rs b/crates/pop-parachains/src/utils/metadata.rs deleted file mode 100644 index 27b13f5a5..000000000 --- a/crates/pop-parachains/src/utils/metadata.rs +++ /dev/null @@ -1,508 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 - -use crate::errors::Error; -use pop_common::parse_account; -use scale_info::{ - form::PortableForm, Field, PortableRegistry, Type, TypeDef, TypeDefCompact, TypeDefComposite, - TypeDefPrimitive, TypeDefTuple, TypeDefVariant, Variant, -}; -use subxt::{dynamic::Value, Metadata, OnlineClient, SubstrateConfig}; - -#[derive(Clone, PartialEq, Eq)] -/// Describes a pallet with its extrinsics. -pub struct Pallet { - /// The name of the pallet. - pub name: String, - /// The documentation of the pallet. - pub docs: String, - // The extrinsics of the pallet. - pub extrinsics: Vec>, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Arg { - pub name: String, - pub type_input: String, - pub optional: bool, - pub options: Vec, - pub variant: bool, -} - -/// Parses the chain metadata to extract information about pallets and their extrinsics. -pub async fn parse_chain_metadata( - api: OnlineClient, -) -> Result, Error> { - let metadata: Metadata = api.metadata(); - Ok(metadata - .pallets() - .map(|pallet| { - let extrinsics = - pallet.call_variants().map(|variants| variants.to_vec()).unwrap_or_default(); - Pallet { name: pallet.name().to_string(), extrinsics, docs: pallet.docs().join(" ") } - }) - .collect()) -} - -pub async fn find_pallet_by_name( - api: &OnlineClient, - pallet_name: &str, -) -> Result { - let metadata: Metadata = api.metadata(); - for pallet in metadata.pallets() { - if pallet.name() == pallet_name { - let extrinsics = - pallet.call_variants().map(|variants| variants.to_vec()).unwrap_or_default(); - return Ok(Pallet { - name: pallet.name().to_string(), - extrinsics, - docs: pallet.docs().join(" "), - }); - } - } - Err(Error::PalletNotFound(pallet_name.to_string())) -} - -async fn find_extrinsic_by_name( - api: &OnlineClient, - pallet_name: &str, - extrinsic_name: &str, -) -> Result, Error> { - let pallet = find_pallet_by_name(api, pallet_name).await?; - // Check if the specified extrinsic exists within this pallet - if let Some(extrinsic) = pallet.extrinsics.iter().find(|&e| e.name == extrinsic_name) { - return Ok(extrinsic.clone()); - } else { - return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); - } -} - -pub async fn process_extrinsic_args( - api: &OnlineClient, - pallet_name: &str, - extrinsic_name: &str, - args: Vec, -) -> Result, Error> { - let metadata: Metadata = api.metadata(); - let registry = metadata.types(); - let extrinsic = find_extrinsic_by_name(&api, pallet_name, extrinsic_name).await?; - - let mut return_args: Vec = Vec::new(); - for (index, field) in extrinsic.fields.iter().enumerate() { - let arg_input = args.get(index).ok_or(Error::ParsingArgsError)?; - let type_info = registry.resolve(field.ty.id).ok_or(Error::ParsingArgsError)?; //Resolve with type_id - let arg_processed = process_value(arg_input, type_info, registry)?; - return_args.push(arg_processed); - } - Ok(return_args) -} - -pub fn process_value( - arg: &str, - ty: &Type, - registry: &PortableRegistry, -) -> Result { - let type_path = ty.path.segments.join("::"); - match type_path.as_str() { - "Option" => handle_option_type(arg, ty, registry), - "sp_core::crypto::AccountId32" => Ok(Value::from_bytes(parse_account(arg)?)), - _ => match &ty.type_def { - TypeDef::Primitive(primitive) => handle_primitive_type(arg, primitive), - TypeDef::Composite(composite) => handle_composite_type(arg, composite, registry), - TypeDef::Variant(variant_def) => handle_variant_type(arg, variant_def, registry), - TypeDef::Tuple(tuple) => handle_tuple_type(arg, tuple, registry), - TypeDef::Compact(compact) => handle_compact_type(arg, compact, registry), - TypeDef::Sequence(_) | TypeDef::Array(_) => Ok(Value::from_bytes(arg)), - _ => Err(Error::ParsingArgsError), - }, - } -} -fn handle_option_type( - arg: &str, - ty: &Type, - registry: &PortableRegistry, -) -> Result { - // Handle Option - if arg.trim() == "None" { - Ok(Value::unnamed_variant("None", vec![])) - } else if arg.trim().starts_with("Some(") && arg.trim().ends_with(')') { - let inner_arg = &arg.trim()[5..arg.trim().len() - 1]; - if let Some(inner_type_id) = ty.type_params.get(0).and_then(|param| param.ty) { - let inner_ty = registry.resolve(inner_type_id.id()).ok_or(Error::ParsingArgsError)?; - let inner_value = process_value(inner_arg.trim(), inner_ty, registry)?; - Ok(Value::unnamed_variant("Some", vec![inner_value])) - } else { - Err(Error::ParsingArgsError) - } - } else { - Err(Error::ParsingArgsError) - } -} - -fn handle_primitive_type(arg: &str, primitive: &TypeDefPrimitive) -> Result { - match primitive { - TypeDefPrimitive::Bool => { - Ok(Value::bool(arg.parse::().map_err(|_| Error::ParsingArgsError)?)) - }, - TypeDefPrimitive::Char => { - Ok(Value::char(arg.chars().next().ok_or(Error::ParsingArgsError)?)) - }, - TypeDefPrimitive::Str => Ok(Value::string(arg.to_string())), - TypeDefPrimitive::U8 - | TypeDefPrimitive::U16 - | TypeDefPrimitive::U32 - | TypeDefPrimitive::U64 - | TypeDefPrimitive::U128 - | TypeDefPrimitive::U256 => { - Ok(Value::u128(arg.parse::().map_err(|_| Error::ParsingArgsError)?)) - }, - TypeDefPrimitive::I8 - | TypeDefPrimitive::I16 - | TypeDefPrimitive::I32 - | TypeDefPrimitive::I64 - | TypeDefPrimitive::I128 - | TypeDefPrimitive::I256 => { - Ok(Value::i128(arg.parse::().map_err(|_| Error::ParsingArgsError)?)) - }, - } -} -fn handle_composite_type( - arg: &str, - composite: &TypeDefComposite, - registry: &PortableRegistry, -) -> Result { - let arg_trimmed = arg.trim(); - let inner = if arg_trimmed.starts_with('{') && arg_trimmed.ends_with('}') { - &arg_trimmed[1..arg_trimmed.len() - 1] - } else { - arg_trimmed - }; - let sub_args = split_top_level_commas(inner)?; - if sub_args.len() != composite.fields.len() { - return Err(Error::ParsingArgsError); - } - let mut values = Vec::new(); - for (field, sub_arg) in composite.fields.iter().zip(sub_args.iter()) { - let sub_ty = registry.resolve(field.ty.id).ok_or(Error::ParsingArgsError)?; - let value = process_value(sub_arg.trim(), sub_ty, registry)?; - let field_name = field.name.clone().unwrap_or_default(); - values.push((field_name, value)); - } - Ok(Value::named_composite(values)) -} -fn handle_variant_type( - arg: &str, - variant: &TypeDefVariant, - registry: &PortableRegistry, -) -> Result { - // Handle variants like Some(value1, value2, ...) - let input = arg.trim(); - let (variant_name, variant_data) = if let Some(start) = input.find('(') { - if !input.ends_with(')') { - return Err(Error::ParsingArgsError); - } - let name = input[..start].trim(); - let data_str = &input[start + 1..input.len() - 1]; - (name, Some(data_str)) - } else { - let name = input.trim(); - (name, None) - }; - - // Find the variant definition - let variant_def = variant - .variants - .iter() - .find(|v| v.name == variant_name) - .ok_or(Error::ParsingArgsError)?; - - // Handle variant fields - let fields_values = if let Some(data_str) = variant_data { - let inputs = split_top_level_commas(data_str)?; - if inputs.len() != variant_def.fields.len() { - return Err(Error::ParsingArgsError); - } - - let mut values = Vec::new(); - for (field_def, field_input) in variant_def.fields.iter().zip(inputs.iter()) { - let field_ty = registry.resolve(field_def.ty.id).ok_or(Error::ParsingArgsError)?; - let field_value = process_value(field_input.trim(), field_ty, registry)?; - values.push(field_value); - } - values - } else if variant_def.fields.is_empty() { - vec![] - } else { - // Variant has fields but no data provided - return Err(Error::ParsingArgsError); - }; - - Ok(Value::unnamed_variant(variant_name, fields_values)) -} -fn handle_tuple_type( - arg: &str, - tuple: &TypeDefTuple, - registry: &PortableRegistry, -) -> Result { - let arg_trimmed = arg.trim(); - let inner = if arg_trimmed.starts_with('(') && arg_trimmed.ends_with(')') { - &arg_trimmed[1..arg_trimmed.len() - 1] - } else { - arg_trimmed - }; - let sub_args = split_top_level_commas(inner)?; - if sub_args.len() != tuple.fields.len() { - return Err(Error::ParsingArgsError); - } - let mut values = Vec::new(); - for (sub_ty_id, sub_arg) in tuple.fields.iter().zip(sub_args.iter()) { - let sub_ty = registry.resolve(sub_ty_id.id()).ok_or(Error::ParsingArgsError)?; - let value = process_value(sub_arg.trim(), sub_ty, registry)?; - values.push(value); - } - Ok(Value::unnamed_composite(values)) -} -fn handle_compact_type( - arg: &str, - compact: &TypeDefCompact, - registry: &PortableRegistry, -) -> Result { - let inner_ty = registry.resolve(compact.type_param.id()).ok_or(Error::ParsingArgsError)?; - process_value(arg, inner_ty, registry) -} -fn split_top_level_commas(s: &str) -> Result, Error> { - let mut result = Vec::new(); - let mut brace_depth = 0; - let mut paren_depth = 0; - let mut last_index = 0; - for (i, c) in s.char_indices() { - match c { - '{' => brace_depth += 1, - '}' => brace_depth -= 1, - '(' => paren_depth += 1, - ')' => paren_depth -= 1, - ',' if brace_depth == 0 && paren_depth == 0 => { - result.push(&s[last_index..i]); - last_index = i + 1; - }, - _ => (), - } - } - if brace_depth != 0 || paren_depth != 0 { - return Err(Error::ParsingArgsError); - } - result.push(&s[last_index..]); - Ok(result) -} - -/// Processes an argument by constructing its `Arg` representation, including type information. -pub fn process_prompt_arguments( - api: &OnlineClient, - field: &Field, -) -> Result { - let type_id = field.ty().id(); - let metadata = api.metadata(); - let registry = metadata.types(); - let name = format!("{:?}", field.name()); - let type_name = field.type_name(); - parse_type(registry, type_id, name, type_name) -} - -fn parse_type( - registry: &PortableRegistry, - type_id: u32, - name: String, - type_name: Option<&String>, -) -> Result { - let type_info = registry.resolve(type_id).ok_or(Error::ParsingArgsError)?; - // Check if the type is Option by checking the path segments - if type_info.path.segments == ["Option"] { - // The type is Option - // Get the inner type T from type parameters - if let Some(inner_type_id) = type_info.type_params.get(0).and_then(|param| param.ty) { - let inner_arg = parse_type(registry, inner_type_id.id(), name.clone(), type_name)?; - Ok(Arg { - name, - type_input: inner_arg.type_input, - optional: true, - options: inner_arg.options, - variant: false, - }) - } else { - // Unable to get inner type - Err(Error::ParsingArgsError) - } - } else { - let type_input = get_type_name(registry, type_info); - match &type_info.type_def { - TypeDef::Primitive(_) => { - Ok(Arg { name, type_input, optional: false, options: vec![], variant: false }) - }, - TypeDef::Composite(composite) => { - let mut composite_fields = vec![]; - - for composite_field in composite.fields() { - if let Some(name) = composite_field.name() { - let field_name = format!("{:?}", composite_field.name()); - let field_type_id = composite_field.ty().id(); - let field_type_name = composite_field.type_name(); - - let field_arg = - parse_type(registry, field_type_id, field_name, field_type_name)?; - composite_fields.push(field_arg); - } - } - - Ok(Arg { - name, - type_input, - optional: false, - options: composite_fields, - variant: false, - }) - }, - TypeDef::Variant(variant) => { - // Regular enum handling for non-option variants - let mut variant_fields = vec![]; - for variant in variant.variants() { - let variant_name = variant.name().to_string(); - - let mut fields = vec![]; - for field in variant.fields() { - let field_name = format!("{:?}", field.name()); - let field_type_id = field.ty().id(); - let field_type_name = field.type_name(); - - let field_arg = parse_type( - registry, - field_type_id, - variant_name.clone(), - field_type_name, - )?; - fields.push(field_arg); - } - - variant_fields.push(Arg { - name: variant_name, - type_input: "".to_string(), - optional: false, - options: fields, - variant: false, - }); - } - - Ok(Arg { - name, - type_input, - optional: false, - options: variant_fields, - variant: true, - }) - }, - TypeDef::Array(_) | TypeDef::Sequence(_) | TypeDef::Tuple(_) | TypeDef::Compact(_) => { - Ok(Arg { name, type_input, optional: false, options: vec![], variant: false }) - }, - _ => Err(Error::ParsingArgsError), - } - } -} - -fn get_type_name(registry: &PortableRegistry, type_info: &Type) -> String { - if !type_info.path.segments.is_empty() { - type_info.path.segments.join("::") - } else { - match &type_info.type_def { - TypeDef::Primitive(primitive) => format!("{:?}", primitive), - TypeDef::Array(array) => { - // Get the inner type of Compact - if let Some(inner_type_info) = registry.resolve(array.type_param().id()) { - get_type_name(registry, inner_type_info) - } else { - "Compact".to_string() - } - }, - TypeDef::Sequence(sequence) => { - // Get the inner type of Compact - if let Some(inner_type_info) = registry.resolve(sequence.type_param().id()) { - get_type_name(registry, inner_type_info) - } else { - "Compact".to_string() - } - }, - TypeDef::Tuple(tuple) => { - let field_types: Vec = tuple - .fields() - .iter() - .map(|field| { - if let Some(field_type_info) = registry.resolve(field.id()) { - get_type_name(registry, field_type_info) - } else { - "Unknown".to_string() - } - }) - .collect(); - format!("({})", field_types.join(", ")) - }, - TypeDef::Compact(compact) => { - // Get the inner type of Compact - if let Some(inner_type_info) = registry.resolve(compact.type_param().id()) { - get_type_name(registry, inner_type_info) - } else { - "Compact".to_string() - } - }, - _ => "Unknown Type".to_string(), - } - } -} - -// #[cfg(test)] -// mod tests { -// use crate::set_up_api; - -// use super::*; -// use anyhow::Result; - -// #[tokio::test] -// async fn process_prompt_arguments_works() -> Result<()> { -// let api = set_up_api("ws://127.0.0.1:9944").await?; -// // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; -// let ex = find_extrinsic_by_name(&api, "Nfts", "mint").await?; -// let prompt_args1 = process_prompt_arguments(&api, &ex.fields()[2])?; - -// Ok(()) -// } - -// #[tokio::test] -// async fn process_extrinsic_args_works() -> Result<()> { -// let api = set_up_api("ws://127.0.0.1:9944").await?; -// // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; -// let ex = find_extrinsic_by_name(&api, "Nfts", "mint").await?; -// let args_parsed = process_extrinsic_args( -// &api, -// "Nfts", -// "mint", -// vec![ -// "1".to_string(), -// "1".to_string(), -// "Id(5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y)".to_string(), -// "Some(Some(1), Some(1))".to_string(), -// ], -// ) -// .await?; -// println!(" ARGS PARSER {:?}", args_parsed); - -// Ok(()) -// } - -// #[tokio::test] -// async fn process_extrinsic_args2_works() -> Result<()> { -// let api = set_up_api("ws://127.0.0.1:9944").await?; -// // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; -// let ex = find_extrinsic_by_name(&api, "Nfts", "mint").await?; -// let args_parsed = -// process_extrinsic_args(&api, "System", "remark", vec!["0x11".to_string()]).await?; -// println!(" ARGS PARSER {:?}", args_parsed); - -// Ok(()) -// } -// } diff --git a/crates/pop-parachains/src/utils/metadata/mod.rs b/crates/pop-parachains/src/utils/metadata/mod.rs new file mode 100644 index 000000000..12d494f2e --- /dev/null +++ b/crates/pop-parachains/src/utils/metadata/mod.rs @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-3.0 + +use crate::errors::Error; +use pop_common::{format_type, parse_account}; +use scale_info::{form::PortableForm, Field, PortableRegistry, TypeDef, Variant}; +use subxt::{dynamic::Value, Metadata, OnlineClient, SubstrateConfig}; +use type_parser::process_argument; + +mod type_parser; + +#[derive(Clone, PartialEq, Eq)] +/// Represents a pallet in the blockchain, including its extrinsics. +pub struct Pallet { + /// The name of the pallet. + pub name: String, + /// The documentation of the pallet. + pub docs: String, + // The extrinsics of the pallet. + pub extrinsics: Vec>, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +/// Describes a parameter of an extrinsic. +pub struct Param { + /// The name of the parameter. + pub name: String, + /// The type of the parameter. + pub type_name: String, + /// Indicates if the parameter is optional (`Option`). + pub is_optional: bool, + /// Nested parameters for composite or variants types. + pub sub_params: Vec, + /// Indicates if the parameter is a Variant. + pub is_variant: bool, +} + +/// Parses the chain metadata to extract information about pallets and their extrinsics. +/// +/// # Arguments +/// * `api`: Reference to an `OnlineClient` connected to the chain. +pub async fn parse_chain_metadata( + api: &OnlineClient, +) -> Result, Error> { + let metadata: Metadata = api.metadata(); + Ok(metadata + .pallets() + .map(|pallet| { + let extrinsics = + pallet.call_variants().map(|variants| variants.to_vec()).unwrap_or_default(); + Pallet { name: pallet.name().to_string(), extrinsics, docs: pallet.docs().join(" ") } + }) + .collect()) +} + +/// Finds a specific pallet by name and retrieves its details from metadata. +/// +/// # Arguments +/// * `api`: Reference to an `OnlineClient` connected to the chain. +/// * `pallet_name`: The name of the pallet to find. +pub async fn find_pallet_by_name( + api: &OnlineClient, + pallet_name: &str, +) -> Result { + let metadata: Metadata = api.metadata(); + for pallet in metadata.pallets() { + if pallet.name() == pallet_name { + let extrinsics = + pallet.call_variants().map(|variants| variants.to_vec()).unwrap_or_default(); + return Ok(Pallet { + name: pallet.name().to_string(), + extrinsics, + docs: pallet.docs().join(" "), + }); + } + } + Err(Error::PalletNotFound(pallet_name.to_string())) +} + +/// Transforms a metadata field into its `Param` representation. +/// +/// # Arguments +/// * `api`: Reference to an `OnlineClient` connected to the blockchain. +/// * `field`: A reference to a metadata field of the extrinsic. +pub fn field_to_param( + api: &OnlineClient, + field: &Field, +) -> Result { + let metadata: Metadata = api.metadata(); + let registry = metadata.types(); + let name = format!("{:?}", field.name); + type_to_param(name, registry, field.ty.id, &field.type_name) +} + +/// Converts a type's metadata into a `Param` representation. +/// +/// # Arguments +/// * `name`: The name of the parameter. +/// * `registry`: A reference to the `PortableRegistry` to resolve type dependencies. +/// * `type_id`: The ID of the type to be converted. +/// * `type_name`: An optional descriptive name for the type. +fn type_to_param( + name: String, + registry: &PortableRegistry, + type_id: u32, + type_name: &Option, +) -> Result { + let type_info = registry.resolve(type_id).ok_or(Error::ParsingArgsError)?; + if type_info.path.segments == ["Option"] { + if let Some(sub_type_id) = type_info.type_params.get(0).and_then(|param| param.ty) { + // Recursive for the sub parameters + let sub_param = type_to_param(name.clone(), registry, sub_type_id.id, type_name)?; + return Ok(Param { + name, + type_name: sub_param.type_name, + is_optional: true, + sub_params: sub_param.sub_params, + is_variant: false, + }); + } else { + Err(Error::ParsingArgsError) + } + } else { + // Determine the formatted type name. + let type_name = format_type(type_info, registry); + match &type_info.type_def { + TypeDef::Primitive(_) => Ok(Param { + name, + type_name, + is_optional: false, + sub_params: Vec::new(), + is_variant: false, + }), + TypeDef::Composite(composite) => { + let sub_params = composite + .fields + .iter() + .map(|field| { + // Recursive for the sub parameters of composite type. + type_to_param( + format!("{:?}", field.name), + registry, + field.ty.id, + &field.type_name, + ) + }) + .collect::, Error>>()?; + + Ok(Param { name, type_name, is_optional: false, sub_params, is_variant: false }) + }, + TypeDef::Variant(variant) => { + let variant_params = variant + .variants + .iter() + .map(|variant| { + let variant_sub_params = variant + .fields + .iter() + .map(|field| { + // Recursive for the sub parameters of variant type. + type_to_param( + format!("{:?}", field.name), + registry, + field.ty.id, + &field.type_name, + ) + }) + .collect::, Error>>()?; + Ok(Param { + name: variant.name.clone(), + type_name: "Variant".to_string(), + is_optional: false, + sub_params: variant_sub_params, + is_variant: true, + }) + }) + .collect::, Error>>()?; + + Ok(Param { + name, + type_name, + is_optional: false, + sub_params: variant_params, + is_variant: true, + }) + }, + TypeDef::Array(_) | TypeDef::Sequence(_) | TypeDef::Tuple(_) | TypeDef::Compact(_) => + Ok(Param { + name, + type_name, + is_optional: false, + sub_params: Vec::new(), + is_variant: false, + }), + _ => Err(Error::ParsingArgsError), + } + } +} + +/// Processes and maps parameters for a given pallet extrinsic based on its metadata. +/// +/// # Arguments +/// * `api`: Reference to an `OnlineClient` connected to the blockchain. +/// * `pallet_name`: Name of the pallet containing the extrinsic. +/// * `extrinsic_name`: Name of the extrinsic to process. +/// * `raw_params`: A vector of raw string arguments for the extrinsic. +pub async fn process_extrinsic_args( + api: &OnlineClient, + pallet_name: &str, + extrinsic_name: &str, + raw_params: Vec, +) -> Result, Error> { + let metadata: Metadata = api.metadata(); + let registry = metadata.types(); + let extrinsic = find_extrinsic_by_name(&api, pallet_name, extrinsic_name).await?; + + let mut processed_parameters: Vec = Vec::new(); + for (index, field) in extrinsic.fields.iter().enumerate() { + let raw_parameter = raw_params.get(index).ok_or(Error::ParsingArgsError)?; + let type_info = registry.resolve(field.ty.id).ok_or(Error::ParsingArgsError)?; //Resolve with type_id + let arg_processed = process_argument(raw_parameter, type_info, registry)?; + processed_parameters.push(arg_processed); + } + Ok(processed_parameters) +} + +/// Finds a specific extrinsic by name and retrieves its details from metadata. +/// +/// # Arguments +/// * `api`: Reference to an `OnlineClient` connected to the chain. +/// * `pallet_name`: The name of the pallet to find. +/// * `extrinsic_name`: Name of the extrinsic to locate. +async fn find_extrinsic_by_name( + api: &OnlineClient, + pallet_name: &str, + extrinsic_name: &str, +) -> Result, Error> { + let pallet = find_pallet_by_name(api, pallet_name).await?; + // Check if the specified extrinsic exists within this pallet + if let Some(extrinsic) = pallet.extrinsics.iter().find(|&e| e.name == extrinsic_name) { + return Ok(extrinsic.clone()); + } else { + return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); + } +} + +// #[cfg(test)] +// mod tests { +// use crate::set_up_api; + +// use super::*; +// use anyhow::Result; + +// #[tokio::test] +// async fn process_prompt_arguments_works() -> Result<()> { +// let api = set_up_api("ws://127.0.0.1:9944").await?; +// // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; +// let ex = find_extrinsic_by_name(&api, "Nfts", "mint").await?; +// let prompt_args1 = process_prompt_arguments(&api, &ex.fields()[2])?; + +// Ok(()) +// } + +// #[tokio::test] +// async fn process_extrinsic_args_works() -> Result<()> { +// let api = set_up_api("ws://127.0.0.1:9944").await?; +// // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; +// let ex = find_extrinsic_by_name(&api, "Nfts", "mint").await?; +// let args_parsed = process_extrinsic_args( +// &api, +// "Nfts", +// "mint", +// vec![ +// "1".to_string(), +// "1".to_string(), +// "Id(5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y)".to_string(), +// "Some(Some(1), Some(1))".to_string(), +// ], +// ) +// .await?; +// println!(" ARGS PARSER {:?}", args_parsed); + +// Ok(()) +// } + +// #[tokio::test] +// async fn process_extrinsic_args2_works() -> Result<()> { +// let api = set_up_api("ws://127.0.0.1:9944").await?; +// // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; +// let ex = find_extrinsic_by_name(&api, "Nfts", "mint").await?; +// let args_parsed = +// process_extrinsic_args(&api, "System", "remark", vec!["0x11".to_string()]).await?; +// println!(" ARGS PARSER {:?}", args_parsed); + +// Ok(()) +// } +// } diff --git a/crates/pop-parachains/src/utils/metadata/type_parser.rs b/crates/pop-parachains/src/utils/metadata/type_parser.rs new file mode 100644 index 000000000..0710fd2bf --- /dev/null +++ b/crates/pop-parachains/src/utils/metadata/type_parser.rs @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-3.0 + +use crate::errors::Error; +use scale_info::{ + form::PortableForm, PortableRegistry, Type, TypeDef, TypeDefArray, TypeDefCompact, + TypeDefComposite, TypeDefPrimitive, TypeDefSequence, TypeDefTuple, TypeDefVariant, +}; +use subxt::dynamic::Value; + +/// Parses an argument string into a `Value` based on its type definition. +/// +/// # Arguments +/// * `arg`: The string representation of the argument to parse. +/// * `ty`: A reference to the `Type` to be formatted. +/// * `registry`: A reference to the `PortableRegistry` to resolve type dependencies. +pub fn process_argument( + arg: &str, + ty: &Type, + registry: &PortableRegistry, +) -> Result { + match &ty.type_def { + TypeDef::Primitive(primitive) => primitive.parse(arg, registry), + TypeDef::Composite(composite) => composite.parse(arg, registry), + TypeDef::Variant(variant) => variant.parse(arg, registry), + TypeDef::Tuple(tuple) => tuple.parse(arg, registry), + TypeDef::Sequence(sequence) => sequence.parse(arg, registry), + TypeDef::Array(array) => array.parse(arg, registry), + TypeDef::Compact(compact) => compact.parse(arg, registry), + _ => Err(Error::ParsingArgsError), + } +} + +/// Trait to define how different type definitions parse a string argument into a `Value`. +pub trait TypeParser { + fn parse(&self, arg: &str, registry: &PortableRegistry) -> Result; +} + +impl TypeParser for TypeDefPrimitive { + fn parse(&self, arg: &str, _registry: &PortableRegistry) -> Result { + match self { + TypeDefPrimitive::Bool => + Ok(Value::bool(arg.parse::().map_err(|_| Error::ParsingArgsError)?)), + TypeDefPrimitive::Char => + Ok(Value::char(arg.chars().next().ok_or(Error::ParsingArgsError)?)), + TypeDefPrimitive::Str => Ok(Value::string(arg.to_string())), + TypeDefPrimitive::U8 | + TypeDefPrimitive::U16 | + TypeDefPrimitive::U32 | + TypeDefPrimitive::U64 | + TypeDefPrimitive::U128 | + TypeDefPrimitive::U256 => + Ok(Value::u128(arg.parse::().map_err(|_| Error::ParsingArgsError)?)), + TypeDefPrimitive::I8 | + TypeDefPrimitive::I16 | + TypeDefPrimitive::I32 | + TypeDefPrimitive::I64 | + TypeDefPrimitive::I128 | + TypeDefPrimitive::I256 => + Ok(Value::i128(arg.parse::().map_err(|_| Error::ParsingArgsError)?)), + } + } +} + +impl TypeParser for TypeDefVariant { + fn parse(&self, arg: &str, registry: &PortableRegistry) -> Result { + let input = arg.trim(); + // Parse variant with data (e.g., `Some(value1, value2, ...)`). + if let Some(start) = input.find('(') { + if !input.ends_with(')') { + return Err(Error::ParsingArgsError); + } + let name = input[..start].trim(); + let data_str = &input[start + 1..input.len() - 1]; + let variant_def = self + .variants + .iter() + .find(|v| v.name == name) + .ok_or_else(|| Error::ParsingArgsError)?; + + let mut values = Vec::new(); + for field in variant_def.fields.iter() { + let field_type_id = field.ty.id; + let field_ty = + registry.resolve(field_type_id).ok_or_else(|| Error::ParsingArgsError)?; + // Recursive for the sub parameters of variant type. + let field_value = process_argument(data_str, field_ty, registry)?; + values.push(field_value); + } + + Ok(Value::unnamed_variant(name.to_string(), values)) + } else { + // Parse variant without data (e.g., `None`). + let name = input.to_string(); + let variant_def = self + .variants + .iter() + .find(|v| v.name == name) + .ok_or_else(|| Error::ParsingArgsError)?; + if !variant_def.fields.is_empty() { + return Err(Error::ParsingArgsError); + } + Ok(Value::unnamed_variant(name, vec![])) + } + } +} + +impl TypeParser for TypeDefComposite { + // Example: {"a": true, "b": "hello"} + fn parse(&self, arg: &str, _registry: &PortableRegistry) -> Result { + let parsed: serde_json::Value = + serde_json::from_str(arg).map_err(|_| Error::ParsingArgsError)?; + let scale_val = + serde_json::from_value::>(parsed).map_err(|_| Error::ParsingArgsError)?; + Ok(scale_val) + } +} +impl TypeParser for TypeDefSequence { + // Example: [val1, val2, ...] + fn parse(&self, arg: &str, _registry: &PortableRegistry) -> Result { + Ok(Value::from_bytes(arg)) + } +} + +impl TypeParser for TypeDefArray { + // Example: [val1, val2, ...] + fn parse(&self, arg: &str, _registry: &PortableRegistry) -> Result { + Ok(Value::from_bytes(arg)) + } +} + +impl TypeParser for TypeDefTuple { + fn parse(&self, arg: &str, registry: &PortableRegistry) -> Result { + let input = arg.trim(); + // Extract tuple contents from parentheses (e.g., `(value1, value2, ...)`). + let tuple_content = if input.starts_with('(') && input.ends_with(')') { + &input[1..input.len() - 1] + } else { + input + }; + let tuple_values: Vec<&str> = tuple_content.split(',').map(|s| s.trim()).collect(); + if tuple_values.len() != self.fields.len() { + return Err(Error::ParsingArgsError); + } + let mut values = Vec::new(); + for (sub_ty_id, sub_arg) in self.fields.iter().zip(tuple_values.iter()) { + let sub_ty = registry.resolve(sub_ty_id.id).ok_or_else(|| Error::ParsingArgsError)?; + // Recursive for each value of the tuple. + let value = process_argument(sub_arg.trim(), sub_ty, registry)?; + values.push(value); + } + Ok(Value::unnamed_composite(values)) + } +} + +impl TypeParser for TypeDefCompact { + fn parse(&self, input: &str, registry: &PortableRegistry) -> Result { + // Parse compact types as their sub type (e.g., `Compact`). + let sub_ty = registry.resolve(self.type_param.id).ok_or_else(|| Error::ParsingArgsError)?; + // Recursive for the inner value. + process_argument(input, sub_ty, registry) + } +} From 0477ba08ee4c4d0824ef9bbfe925745cb90ec562 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Wed, 20 Nov 2024 14:59:10 +0100 Subject: [PATCH 137/211] feat: display specific use cases to the user --- crates/pop-cli/src/commands/call/parachain.rs | 47 ++++++++--- .../src/call/metadata/action.rs | 84 +++++++++++++++++++ .../src/{utils => call}/metadata/mod.rs | 3 +- .../{utils => call}/metadata/type_parser.rs | 0 .../src/{call.rs => call/mod.rs} | 6 +- crates/pop-parachains/src/lib.rs | 14 ++-- crates/pop-parachains/src/utils/mod.rs | 1 - 7 files changed, 134 insertions(+), 21 deletions(-) create mode 100644 crates/pop-parachains/src/call/metadata/action.rs rename crates/pop-parachains/src/{utils => call}/metadata/mod.rs (99%) rename crates/pop-parachains/src/{utils => call}/metadata/type_parser.rs (100%) rename crates/pop-parachains/src/{call.rs => call/mod.rs} (89%) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index cbff1f231..5edb2166b 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -4,9 +4,9 @@ use crate::cli::{self, traits::*}; use anyhow::{anyhow, Result}; use clap::Args; use pop_parachains::{ - construct_extrinsic, encode_call_data, field_to_param, find_pallet_by_name, - parse_chain_metadata, set_up_api, sign_and_submit_extrinsic, DynamicPayload, OnlineClient, - Param, SubstrateConfig, + construct_extrinsic, encode_call_data, field_to_param, find_extrinsic_by_name, + find_pallet_by_name, parse_chain_metadata, set_up_api, sign_and_submit_extrinsic, + supported_actions, Action, DynamicPayload, OnlineClient, Param, SubstrateConfig, }; const DEFAULT_URL: &str = "ws://localhost:9944/"; @@ -105,16 +105,25 @@ impl CallParachainCommand { let pallet = if let Some(ref pallet_name) = self.pallet { find_pallet_by_name(&api, pallet_name).await? } else { - let mut prompt = cli.select("Select the pallet to call:"); - for pallet_item in pallets { - prompt = prompt.item(pallet_item.clone(), &pallet_item.name, &pallet_item.docs); + // Specific predefined actions first. + let picked_action: Option = prompt_predefined_actions(&api, cli).await?; + if let Some(action) = picked_action { + self.extrinsic = Some(action.action_name().to_string()); + find_pallet_by_name(&api, action.pallet()).await? + } else { + let mut prompt = cli.select("Select the pallet to call:"); + for pallet_item in pallets { + prompt = prompt.item(pallet_item.clone(), &pallet_item.name, &pallet_item.docs); + } + let pallet_prompted = prompt.interact()?; + self.pallet = Some(pallet_prompted.name.clone()); + pallet_prompted } - let pallet_prompted = prompt.interact()?; - self.pallet = Some(pallet_prompted.name.clone()); - pallet_prompted }; // Resolve extrinsic. - let extrinsic = { + let extrinsic = if let Some(ref extrinsic_name) = self.extrinsic { + find_extrinsic_by_name(&api, &pallet.name, extrinsic_name).await? + } else { let mut prompt_extrinsic = cli.select("Select the extrinsic to call:"); for extrinsic in pallet.extrinsics { prompt_extrinsic = prompt_extrinsic.item( @@ -123,9 +132,10 @@ impl CallParachainCommand { &extrinsic.docs.concat(), ); } - prompt_extrinsic.interact()? + let extrinsic_prompted = prompt_extrinsic.interact()?; + self.extrinsic = Some(extrinsic_prompted.name.clone()); + extrinsic_prompted }; - self.extrinsic = Some(extrinsic.name); // Resolve message arguments. let mut contract_args = Vec::new(); for field in extrinsic.fields { @@ -253,6 +263,19 @@ fn display_message(message: &str, success: bool, cli: &mut impl cli::traits::Cli Ok(()) } +async fn prompt_predefined_actions( + api: &OnlineClient, + cli: &mut impl cli::traits::Cli, +) -> Result> { + let mut predefined_action = cli.select("What would you like to do?"); + for action in supported_actions(&api).await { + predefined_action = + predefined_action.item(Some(action.clone()), action.description(), action.pallet()); + } + predefined_action = predefined_action.item(None, "All", "Explore all pallets and extrinsics"); + Ok(predefined_action.interact()?) +} + // Prompts the user for the value of a parameter. fn prompt_for_param( api: &OnlineClient, diff --git a/crates/pop-parachains/src/call/metadata/action.rs b/crates/pop-parachains/src/call/metadata/action.rs new file mode 100644 index 000000000..a86447cf9 --- /dev/null +++ b/crates/pop-parachains/src/call/metadata/action.rs @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-3.0 + +use strum::{EnumMessage as _, EnumProperty as _, VariantArray as _}; +use strum_macros::{AsRefStr, Display, EnumMessage, EnumProperty, EnumString, VariantArray}; +use subxt::{OnlineClient, SubstrateConfig}; + +use super::find_extrinsic_by_name; + +#[derive( + AsRefStr, + Clone, + Debug, + Display, + EnumMessage, + EnumString, + EnumProperty, + Eq, + PartialEq, + VariantArray, +)] +pub enum Action { + #[strum( + serialize = "create", + message = "create", + detailed_message = "Create an Asset", + props(Pallet = "Assets") + )] + CreateAsset, + #[strum( + serialize = "mint", + message = "mint", + detailed_message = "Mint an Asset", + props(Pallet = "Assets") + )] + MintAsset, + #[strum( + serialize = "create_nft", + message = "create", + detailed_message = "Create an NFT Collection", + props(Pallet = "Nfts") + )] + CreateCollection, + #[strum( + serialize = "mint_nft", + message = "mint", + detailed_message = "Mint an NFT", + props(Pallet = "Nfts") + )] + MintNFT, + #[strum( + serialize = "transfer", + message = "transfer_allow_death", + detailed_message = "Transfer Balance", + props(Pallet = "Balances") + )] + Transfer, +} + +impl Action { + /// Get the action's name. + pub fn action_name(&self) -> &str { + self.get_message().unwrap_or_default() + } + + /// Get the description of the action. + pub fn description(&self) -> &str { + self.get_detailed_message().unwrap_or_default() + } + + /// Get the associated pallet for the action. + pub fn pallet(&self) -> &str { + self.get_str("Pallet").unwrap_or_default() + } +} + +pub async fn supported_actions(api: &OnlineClient) -> Vec { + let mut actions = Vec::new(); + for action in Action::VARIANTS.iter() { + if find_extrinsic_by_name(api, action.pallet(), action.action_name()).await.is_ok() { + actions.push(action.clone()); + } + } + actions +} diff --git a/crates/pop-parachains/src/utils/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs similarity index 99% rename from crates/pop-parachains/src/utils/metadata/mod.rs rename to crates/pop-parachains/src/call/metadata/mod.rs index 12d494f2e..09d63d1f9 100644 --- a/crates/pop-parachains/src/utils/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -6,6 +6,7 @@ use scale_info::{form::PortableForm, Field, PortableRegistry, TypeDef, Variant}; use subxt::{dynamic::Value, Metadata, OnlineClient, SubstrateConfig}; use type_parser::process_argument; +pub mod action; mod type_parser; #[derive(Clone, PartialEq, Eq)] @@ -229,7 +230,7 @@ pub async fn process_extrinsic_args( /// * `api`: Reference to an `OnlineClient` connected to the chain. /// * `pallet_name`: The name of the pallet to find. /// * `extrinsic_name`: Name of the extrinsic to locate. -async fn find_extrinsic_by_name( +pub async fn find_extrinsic_by_name( api: &OnlineClient, pallet_name: &str, extrinsic_name: &str, diff --git a/crates/pop-parachains/src/utils/metadata/type_parser.rs b/crates/pop-parachains/src/call/metadata/type_parser.rs similarity index 100% rename from crates/pop-parachains/src/utils/metadata/type_parser.rs rename to crates/pop-parachains/src/call/metadata/type_parser.rs diff --git a/crates/pop-parachains/src/call.rs b/crates/pop-parachains/src/call/mod.rs similarity index 89% rename from crates/pop-parachains/src/call.rs rename to crates/pop-parachains/src/call/mod.rs index 485234552..a487674c8 100644 --- a/crates/pop-parachains/src/call.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -use crate::{errors::Error, utils::metadata::process_extrinsic_args}; +use crate::errors::Error; use pop_common::create_signer; use subxt::{ dynamic::Value, @@ -8,6 +8,8 @@ use subxt::{ OnlineClient, SubstrateConfig, }; +pub mod metadata; + pub async fn set_up_api(url: &str) -> Result, Error> { let api = OnlineClient::::from_url(url).await?; Ok(api) @@ -20,7 +22,7 @@ pub async fn construct_extrinsic( args: Vec, ) -> Result { let parsed_args: Vec = - process_extrinsic_args(api, pallet_name, extrinsic_name, args).await?; + metadata::process_extrinsic_args(api, pallet_name, extrinsic_name, args).await?; Ok(subxt::dynamic::tx(pallet_name, extrinsic_name, parsed_args)) } diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index d319c0097..98f26090a 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -15,7 +15,14 @@ pub use build::{ binary_path, build_parachain, export_wasm_file, generate_genesis_state_file, generate_plain_chain_spec, generate_raw_chain_spec, is_supported, ChainSpec, }; -pub use call::{construct_extrinsic, encode_call_data, set_up_api, sign_and_submit_extrinsic}; +pub use call::{ + construct_extrinsic, encode_call_data, + metadata::{ + action::{supported_actions, Action}, + field_to_param, find_extrinsic_by_name, find_pallet_by_name, parse_chain_metadata, Param, + }, + set_up_api, sign_and_submit_extrinsic, +}; pub use errors::Error; pub use indexmap::IndexSet; pub use new_pallet::{create_pallet_template, new_pallet_options::*, TemplatePalletConfig}; @@ -24,9 +31,6 @@ pub use new_parachain::instantiate_template_dir; pub use subxt::{dynamic::Value, tx::DynamicPayload, OnlineClient, SubstrateConfig}; pub use templates::{Config, Parachain, Provider}; pub use up::Zombienet; -pub use utils::{ - helpers::is_initial_endowment_valid, - metadata::{field_to_param, find_pallet_by_name, parse_chain_metadata, Param}, -}; +pub use utils::helpers::is_initial_endowment_valid; /// Information about the Node. External export from Zombienet-SDK. pub use zombienet_sdk::NetworkNode; diff --git a/crates/pop-parachains/src/utils/mod.rs b/crates/pop-parachains/src/utils/mod.rs index a9f3ad9a8..265ebafd4 100644 --- a/crates/pop-parachains/src/utils/mod.rs +++ b/crates/pop-parachains/src/utils/mod.rs @@ -1,4 +1,3 @@ // SPDX-License-Identifier: GPL-3.0 pub mod helpers; -pub mod metadata; From e3d423bb45dc984dc1f390e6259ad9aca0d5f470 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Wed, 20 Nov 2024 15:17:25 +0100 Subject: [PATCH 138/211] refactor: predefined actions --- crates/pop-cli/src/commands/call/parachain.rs | 11 +++++---- .../src/call/metadata/action.rs | 23 +++++++++++-------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 5edb2166b..466f291e1 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -108,8 +108,8 @@ impl CallParachainCommand { // Specific predefined actions first. let picked_action: Option = prompt_predefined_actions(&api, cli).await?; if let Some(action) = picked_action { - self.extrinsic = Some(action.action_name().to_string()); - find_pallet_by_name(&api, action.pallet()).await? + self.extrinsic = Some(action.extrinsic_name().to_string()); + find_pallet_by_name(&api, action.pallet_name()).await? } else { let mut prompt = cli.select("Select the pallet to call:"); for pallet_item in pallets { @@ -269,8 +269,11 @@ async fn prompt_predefined_actions( ) -> Result> { let mut predefined_action = cli.select("What would you like to do?"); for action in supported_actions(&api).await { - predefined_action = - predefined_action.item(Some(action.clone()), action.description(), action.pallet()); + predefined_action = predefined_action.item( + Some(action.clone()), + action.description(), + action.pallet_name(), + ); } predefined_action = predefined_action.item(None, "All", "Explore all pallets and extrinsics"); Ok(predefined_action.interact()?) diff --git a/crates/pop-parachains/src/call/metadata/action.rs b/crates/pop-parachains/src/call/metadata/action.rs index a86447cf9..5b41650a1 100644 --- a/crates/pop-parachains/src/call/metadata/action.rs +++ b/crates/pop-parachains/src/call/metadata/action.rs @@ -1,11 +1,11 @@ // SPDX-License-Identifier: GPL-3.0 +use super::find_extrinsic_by_name; use strum::{EnumMessage as _, EnumProperty as _, VariantArray as _}; use strum_macros::{AsRefStr, Display, EnumMessage, EnumProperty, EnumString, VariantArray}; use subxt::{OnlineClient, SubstrateConfig}; -use super::find_extrinsic_by_name; - +/// Enum representing predefined actions. #[derive( AsRefStr, Clone, @@ -57,18 +57,18 @@ pub enum Action { } impl Action { - /// Get the action's name. - pub fn action_name(&self) -> &str { - self.get_message().unwrap_or_default() - } - /// Get the description of the action. pub fn description(&self) -> &str { self.get_detailed_message().unwrap_or_default() } - /// Get the associated pallet for the action. - pub fn pallet(&self) -> &str { + /// Get the the extrinsic name of the action. + pub fn extrinsic_name(&self) -> &str { + self.get_message().unwrap_or_default() + } + + /// Get the pallet name of the action. + pub fn pallet_name(&self) -> &str { self.get_str("Pallet").unwrap_or_default() } } @@ -76,7 +76,10 @@ impl Action { pub async fn supported_actions(api: &OnlineClient) -> Vec { let mut actions = Vec::new(); for action in Action::VARIANTS.iter() { - if find_extrinsic_by_name(api, action.pallet(), action.action_name()).await.is_ok() { + if find_extrinsic_by_name(api, action.pallet_name(), action.extrinsic_name()) + .await + .is_ok() + { actions.push(action.clone()); } } From 45eb5f9e321e1ba8883cc2e15f8f5b6f2f65140b Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 21 Nov 2024 11:26:23 +0100 Subject: [PATCH 139/211] fix: various fixes --- crates/pop-cli/src/commands/call/parachain.rs | 21 +-- crates/pop-common/src/metadata.rs | 8 +- .../pop-parachains/src/call/metadata/mod.rs | 41 +++--- .../src/call/metadata/type_parser.rs | 128 ++++++++++++++---- crates/pop-parachains/src/errors.rs | 6 +- 5 files changed, 148 insertions(+), 56 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 466f291e1..d3f385cfc 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -21,7 +21,7 @@ pub struct CallParachainCommand { #[clap(long, short)] extrinsic: Option, /// The constructor arguments, encoded as strings. - #[clap(long, num_args = 0..)] + #[clap(long, num_args = 0..,)] args: Vec, /// Websocket endpoint of a node. #[clap(name = "url", short = 'u', long, value_parser, default_value = DEFAULT_URL)] @@ -108,6 +108,7 @@ impl CallParachainCommand { // Specific predefined actions first. let picked_action: Option = prompt_predefined_actions(&api, cli).await?; if let Some(action) = picked_action { + self.pallet = Some(action.pallet_name().to_string()); self.extrinsic = Some(action.extrinsic_name().to_string()); find_pallet_by_name(&api, action.pallet_name()).await? } else { @@ -137,13 +138,15 @@ impl CallParachainCommand { extrinsic_prompted }; // Resolve message arguments. - let mut contract_args = Vec::new(); - for field in extrinsic.fields { - let param = field_to_param(&api, &field)?; - let input = prompt_for_param(&api, cli, ¶m)?; - contract_args.push(input); + if self.args.is_empty() { + let mut contract_args = Vec::new(); + for field in extrinsic.fields { + let param = field_to_param(&api, &extrinsic.name, &field)?; + let input = prompt_for_param(&api, cli, ¶m)?; + contract_args.push(input); + } + self.args = contract_args; } - self.args = contract_args; cli.info(self.display())?; Ok(api) @@ -200,7 +203,7 @@ impl CallParachainCommand { prompt_to_repeat_call: bool, cli: &mut impl cli::traits::Cli, ) -> Result<()> { - if self.suri.is_empty() { + if self.suri == DEFAULT_URI { self.suri = cli::Cli .input("Who is going to sign the extrinsic:") .placeholder("//Alice") @@ -225,7 +228,7 @@ impl CallParachainCommand { .await .map_err(|err| anyhow!("{} {}", "ERROR:", format!("{err:?}")))?; - display_message(&format!("Extrinsic submitted with hash: {:?}", result), true, cli)?; + spinner.stop(&format!("Extrinsic submitted with hash: {:?}", result)); // Prompt for any additional calls. if !prompt_to_repeat_call { diff --git a/crates/pop-common/src/metadata.rs b/crates/pop-common/src/metadata.rs index 8c5675cf5..3cff32d72 100644 --- a/crates/pop-common/src/metadata.rs +++ b/crates/pop-common/src/metadata.rs @@ -19,7 +19,13 @@ pub fn format_type(ty: &Type, registry: &PortableRegistry) -> Stri let params: Vec<_> = ty .type_params .iter() - .filter_map(|p| registry.resolve(p.ty.unwrap().id)) + .filter_map(|p| { + if let Some(ty) = p.ty { + registry.resolve(ty.id) + } else { + None // Ignore if p.ty is None + } + }) .map(|t| format_type(t, registry)) .collect(); name = format!("{name}<{}>", params.join(",")); diff --git a/crates/pop-parachains/src/call/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs index 09d63d1f9..503acf03f 100644 --- a/crates/pop-parachains/src/call/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 use crate::errors::Error; -use pop_common::{format_type, parse_account}; +use pop_common::format_type; use scale_info::{form::PortableForm, Field, PortableRegistry, TypeDef, Variant}; use subxt::{dynamic::Value, Metadata, OnlineClient, SubstrateConfig}; use type_parser::process_argument; @@ -84,12 +84,13 @@ pub async fn find_pallet_by_name( /// * `field`: A reference to a metadata field of the extrinsic. pub fn field_to_param( api: &OnlineClient, + extrinsic_name: &str, field: &Field, ) -> Result { let metadata: Metadata = api.metadata(); let registry = metadata.types(); - let name = format!("{:?}", field.name); - type_to_param(name, registry, field.ty.id, &field.type_name) + let name = field.name.clone().unwrap_or("Unnamed".to_string()); //It can be unnamed field + type_to_param(extrinsic_name, name, registry, field.ty.id, &field.type_name) } /// Converts a type's metadata into a `Param` representation. @@ -100,16 +101,23 @@ pub fn field_to_param( /// * `type_id`: The ID of the type to be converted. /// * `type_name`: An optional descriptive name for the type. fn type_to_param( + extrinsic_name: &str, name: String, registry: &PortableRegistry, type_id: u32, type_name: &Option, ) -> Result { - let type_info = registry.resolve(type_id).ok_or(Error::ParsingArgsError)?; + let type_info = registry.resolve(type_id).ok_or(Error::MetadataParsingError(name.clone()))?; + if let Some(last_segment) = type_info.path.segments.last() { + if last_segment == "RuntimeCall" { + return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); + } + } if type_info.path.segments == ["Option"] { if let Some(sub_type_id) = type_info.type_params.get(0).and_then(|param| param.ty) { // Recursive for the sub parameters - let sub_param = type_to_param(name.clone(), registry, sub_type_id.id, type_name)?; + let sub_param = + type_to_param(extrinsic_name, name.clone(), registry, sub_type_id.id, type_name)?; return Ok(Param { name, type_name: sub_param.type_name, @@ -118,7 +126,7 @@ fn type_to_param( is_variant: false, }); } else { - Err(Error::ParsingArgsError) + Err(Error::MetadataParsingError(name)) } } else { // Determine the formatted type name. @@ -138,7 +146,8 @@ fn type_to_param( .map(|field| { // Recursive for the sub parameters of composite type. type_to_param( - format!("{:?}", field.name), + extrinsic_name, + field.name.clone().unwrap_or(name.clone()), registry, field.ty.id, &field.type_name, @@ -152,14 +161,15 @@ fn type_to_param( let variant_params = variant .variants .iter() - .map(|variant| { - let variant_sub_params = variant + .map(|variant_param| { + let variant_sub_params = variant_param .fields .iter() .map(|field| { // Recursive for the sub parameters of variant type. type_to_param( - format!("{:?}", field.name), + extrinsic_name, + field.name.clone().unwrap_or(variant_param.name.clone()), registry, field.ty.id, &field.type_name, @@ -167,8 +177,8 @@ fn type_to_param( }) .collect::, Error>>()?; Ok(Param { - name: variant.name.clone(), - type_name: "Variant".to_string(), + name: variant_param.name.clone(), + type_name: "".to_string(), is_optional: false, sub_params: variant_sub_params, is_variant: true, @@ -192,7 +202,7 @@ fn type_to_param( sub_params: Vec::new(), is_variant: false, }), - _ => Err(Error::ParsingArgsError), + _ => Err(Error::MetadataParsingError(name)), } } } @@ -213,11 +223,10 @@ pub async fn process_extrinsic_args( let metadata: Metadata = api.metadata(); let registry = metadata.types(); let extrinsic = find_extrinsic_by_name(&api, pallet_name, extrinsic_name).await?; - let mut processed_parameters: Vec = Vec::new(); for (index, field) in extrinsic.fields.iter().enumerate() { - let raw_parameter = raw_params.get(index).ok_or(Error::ParsingArgsError)?; - let type_info = registry.resolve(field.ty.id).ok_or(Error::ParsingArgsError)?; //Resolve with type_id + let raw_parameter = raw_params.get(index).ok_or(Error::ParamProcessingError)?; + let type_info = registry.resolve(field.ty.id).ok_or(Error::ParamProcessingError)?; //Resolve with type_id let arg_processed = process_argument(raw_parameter, type_info, registry)?; processed_parameters.push(arg_processed); } diff --git a/crates/pop-parachains/src/call/metadata/type_parser.rs b/crates/pop-parachains/src/call/metadata/type_parser.rs index 0710fd2bf..fb2c8bdea 100644 --- a/crates/pop-parachains/src/call/metadata/type_parser.rs +++ b/crates/pop-parachains/src/call/metadata/type_parser.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 use crate::errors::Error; +use pop_common::parse_account; use scale_info::{ form::PortableForm, PortableRegistry, Type, TypeDef, TypeDefArray, TypeDefCompact, TypeDefComposite, TypeDefPrimitive, TypeDefSequence, TypeDefTuple, TypeDefVariant, @@ -18,15 +19,45 @@ pub fn process_argument( ty: &Type, registry: &PortableRegistry, ) -> Result { - match &ty.type_def { - TypeDef::Primitive(primitive) => primitive.parse(arg, registry), - TypeDef::Composite(composite) => composite.parse(arg, registry), - TypeDef::Variant(variant) => variant.parse(arg, registry), - TypeDef::Tuple(tuple) => tuple.parse(arg, registry), - TypeDef::Sequence(sequence) => sequence.parse(arg, registry), - TypeDef::Array(array) => array.parse(arg, registry), - TypeDef::Compact(compact) => compact.parse(arg, registry), - _ => Err(Error::ParsingArgsError), + let type_path = ty.path.segments.join("::"); + match type_path.as_str() { + "Option" => handle_option_type(arg, ty, registry), + "sp_core::crypto::AccountId32" => Ok(Value::from_bytes(parse_account(arg)?)), /* Specifically parse AccountId */ + _ => match &ty.type_def { + TypeDef::Primitive(primitive) => primitive.parse(arg, registry), + TypeDef::Composite(composite) => composite.parse(arg, registry), + TypeDef::Variant(variant) => variant.parse(arg, registry), + TypeDef::Tuple(tuple) => tuple.parse(arg, registry), + TypeDef::Sequence(sequence) => sequence.parse(arg, registry), + TypeDef::Array(array) => array.parse(arg, registry), + TypeDef::Compact(compact) => compact.parse(arg, registry), + _ => Err(Error::ParamProcessingError), + }, + } +} + +fn handle_option_type( + arg: &str, + ty: &Type, + registry: &PortableRegistry, +) -> Result { + // Handle Option + if arg.trim() == "None" { + Ok(Value::unnamed_variant("None", vec![])) + } else if arg.trim().starts_with("Some(") && arg.trim().ends_with(')') { + let sub_arg = &arg.trim()[5..arg.trim().len() - 1]; + if let Some(sub_arg_type_id) = ty.type_params.get(0).and_then(|param| param.ty) { + let sub_arg_ty = + registry.resolve(sub_arg_type_id.id).ok_or(Error::ParamProcessingError)?; + Ok(Value::unnamed_variant( + "Some", + vec![process_argument(sub_arg.trim(), sub_arg_ty, registry)?], + )) + } else { + Err(Error::ParamProcessingError) + } + } else { + Err(Error::ParamProcessingError) } } @@ -39,9 +70,9 @@ impl TypeParser for TypeDefPrimitive { fn parse(&self, arg: &str, _registry: &PortableRegistry) -> Result { match self { TypeDefPrimitive::Bool => - Ok(Value::bool(arg.parse::().map_err(|_| Error::ParsingArgsError)?)), + Ok(Value::bool(arg.parse::().map_err(|_| Error::ParamProcessingError)?)), TypeDefPrimitive::Char => - Ok(Value::char(arg.chars().next().ok_or(Error::ParsingArgsError)?)), + Ok(Value::char(arg.chars().next().ok_or(Error::ParamProcessingError)?)), TypeDefPrimitive::Str => Ok(Value::string(arg.to_string())), TypeDefPrimitive::U8 | TypeDefPrimitive::U16 | @@ -49,14 +80,14 @@ impl TypeParser for TypeDefPrimitive { TypeDefPrimitive::U64 | TypeDefPrimitive::U128 | TypeDefPrimitive::U256 => - Ok(Value::u128(arg.parse::().map_err(|_| Error::ParsingArgsError)?)), + Ok(Value::u128(arg.parse::().map_err(|_| Error::ParamProcessingError)?)), TypeDefPrimitive::I8 | TypeDefPrimitive::I16 | TypeDefPrimitive::I32 | TypeDefPrimitive::I64 | TypeDefPrimitive::I128 | TypeDefPrimitive::I256 => - Ok(Value::i128(arg.parse::().map_err(|_| Error::ParsingArgsError)?)), + Ok(Value::i128(arg.parse::().map_err(|_| Error::ParamProcessingError)?)), } } } @@ -67,7 +98,7 @@ impl TypeParser for TypeDefVariant { // Parse variant with data (e.g., `Some(value1, value2, ...)`). if let Some(start) = input.find('(') { if !input.ends_with(')') { - return Err(Error::ParsingArgsError); + return Err(Error::ParamProcessingError); } let name = input[..start].trim(); let data_str = &input[start + 1..input.len() - 1]; @@ -75,13 +106,13 @@ impl TypeParser for TypeDefVariant { .variants .iter() .find(|v| v.name == name) - .ok_or_else(|| Error::ParsingArgsError)?; + .ok_or_else(|| Error::ParamProcessingError)?; let mut values = Vec::new(); for field in variant_def.fields.iter() { let field_type_id = field.ty.id; let field_ty = - registry.resolve(field_type_id).ok_or_else(|| Error::ParsingArgsError)?; + registry.resolve(field_type_id).ok_or_else(|| Error::ParamProcessingError)?; // Recursive for the sub parameters of variant type. let field_value = process_argument(data_str, field_ty, registry)?; values.push(field_value); @@ -95,9 +126,9 @@ impl TypeParser for TypeDefVariant { .variants .iter() .find(|v| v.name == name) - .ok_or_else(|| Error::ParsingArgsError)?; + .ok_or_else(|| Error::ParamProcessingError)?; if !variant_def.fields.is_empty() { - return Err(Error::ParsingArgsError); + return Err(Error::ParamProcessingError); } Ok(Value::unnamed_variant(name, vec![])) } @@ -105,15 +136,53 @@ impl TypeParser for TypeDefVariant { } impl TypeParser for TypeDefComposite { - // Example: {"a": true, "b": "hello"} - fn parse(&self, arg: &str, _registry: &PortableRegistry) -> Result { - let parsed: serde_json::Value = - serde_json::from_str(arg).map_err(|_| Error::ParsingArgsError)?; - let scale_val = - serde_json::from_value::>(parsed).map_err(|_| Error::ParsingArgsError)?; - Ok(scale_val) + // Example: {"a": true, "b": "hello", "c": { "d": 42, e: "world" }} + fn parse(&self, arg: &str, registry: &PortableRegistry) -> Result { + let mut values: Vec<&str> = arg.split(',').map(str::trim).collect(); + + let mut field_values = Vec::new(); + for (index, field) in self.fields.iter().enumerate() { + let field_name = field + .name + .clone() + .or_else(|| field.type_name.clone()) + .unwrap_or_else(|| format!("unnamed_field_{}", index)); + + let field_type = registry.resolve(field.ty.id).ok_or(Error::ParamProcessingError)?; + if values.is_empty() { + return Err(Error::ParamProcessingError); + } + let value = match &field_type.type_def { + TypeDef::Composite(nested_composite) => { + if nested_composite.fields.is_empty() { + // Unnamed composite resolving to a primitive type + let raw_value = values.remove(0); + process_argument(raw_value, field_type, registry)? + } else { + // Named or unnamed nested composite + let nested_args_count = nested_composite.fields.len(); + if values.len() < nested_args_count { + return Err(Error::ParamProcessingError); + } + + let nested_args: Vec = + values.drain(..nested_args_count).map(String::from).collect(); + let nested_arg_str = nested_args.join(","); + nested_composite.parse(&nested_arg_str, registry)? + } + }, + _ => { + // Parse a single argument for non-composite fields + let raw_value = values.remove(0); + process_argument(raw_value, field_type, registry)? + }, + }; + field_values.push((field_name, value)); + } + Ok(Value::named_composite(field_values)) } } + impl TypeParser for TypeDefSequence { // Example: [val1, val2, ...] fn parse(&self, arg: &str, _registry: &PortableRegistry) -> Result { @@ -139,11 +208,12 @@ impl TypeParser for TypeDefTuple { }; let tuple_values: Vec<&str> = tuple_content.split(',').map(|s| s.trim()).collect(); if tuple_values.len() != self.fields.len() { - return Err(Error::ParsingArgsError); + return Err(Error::ParamProcessingError); } let mut values = Vec::new(); for (sub_ty_id, sub_arg) in self.fields.iter().zip(tuple_values.iter()) { - let sub_ty = registry.resolve(sub_ty_id.id).ok_or_else(|| Error::ParsingArgsError)?; + let sub_ty = + registry.resolve(sub_ty_id.id).ok_or_else(|| Error::ParamProcessingError)?; // Recursive for each value of the tuple. let value = process_argument(sub_arg.trim(), sub_ty, registry)?; values.push(value); @@ -155,7 +225,9 @@ impl TypeParser for TypeDefTuple { impl TypeParser for TypeDefCompact { fn parse(&self, input: &str, registry: &PortableRegistry) -> Result { // Parse compact types as their sub type (e.g., `Compact`). - let sub_ty = registry.resolve(self.type_param.id).ok_or_else(|| Error::ParsingArgsError)?; + let sub_ty = registry + .resolve(self.type_param.id) + .ok_or_else(|| Error::ParamProcessingError)?; // Recursive for the inner value. process_argument(input, sub_ty, registry) } diff --git a/crates/pop-parachains/src/errors.rs b/crates/pop-parachains/src/errors.rs index 2a54c4fed..244b70aa2 100644 --- a/crates/pop-parachains/src/errors.rs +++ b/crates/pop-parachains/src/errors.rs @@ -28,6 +28,8 @@ pub enum Error { JsonError(#[from] serde_json::Error), #[error("Metadata error: {0}")] MetadataError(#[from] subxt::error::MetadataError), + #[error("Error parsing metadata for parameter {0} conversion")] + MetadataParsingError(String), #[error("Missing binary: {0}")] MissingBinary(String), #[error("Missing chain spec file at: {0}")] @@ -42,8 +44,8 @@ pub enum Error { PalletMissing, #[error("Failed to find the pallet {0}")] PalletNotFound(String), - #[error("Failed to parse the arguments")] - ParsingArgsError, + #[error("Failed to process the arguments provided by the user.")] + ParamProcessingError, #[error("Failed to parse the response")] ParsingResponseError(#[from] scale_decode::Error), #[error("Invalid path")] From 8c657b18d5fef98499cbf1e443fd74a9acc9d840 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 21 Nov 2024 11:54:49 +0100 Subject: [PATCH 140/211] fix: error message not supported for complex types --- crates/pop-parachains/src/call/metadata/mod.rs | 5 +++++ .../src/call/metadata/type_parser.rs | 14 +++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/crates/pop-parachains/src/call/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs index 503acf03f..16dcb3e8e 100644 --- a/crates/pop-parachains/src/call/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -113,6 +113,11 @@ fn type_to_param( return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); } } + for param in &type_info.type_params { + if param.name == "RuntimeCall" { + return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); + } + } if type_info.path.segments == ["Option"] { if let Some(sub_type_id) = type_info.type_params.get(0).and_then(|param| param.ty) { // Recursive for the sub parameters diff --git a/crates/pop-parachains/src/call/metadata/type_parser.rs b/crates/pop-parachains/src/call/metadata/type_parser.rs index fb2c8bdea..7f214f510 100644 --- a/crates/pop-parachains/src/call/metadata/type_parser.rs +++ b/crates/pop-parachains/src/call/metadata/type_parser.rs @@ -136,7 +136,8 @@ impl TypeParser for TypeDefVariant { } impl TypeParser for TypeDefComposite { - // Example: {"a": true, "b": "hello", "c": { "d": 42, e: "world" }} + // Example: A composite type is input by the user [true, hello, 42, world], convert into proper + // composite type: {"a": true, "b": "hello", "c": { "d": 42, e: "world" }} fn parse(&self, arg: &str, registry: &PortableRegistry) -> Result { let mut values: Vec<&str> = arg.split(',').map(str::trim).collect(); @@ -155,16 +156,15 @@ impl TypeParser for TypeDefComposite { let value = match &field_type.type_def { TypeDef::Composite(nested_composite) => { if nested_composite.fields.is_empty() { - // Unnamed composite resolving to a primitive type + // Recursive for the sub parameters of nested composite type. let raw_value = values.remove(0); process_argument(raw_value, field_type, registry)? } else { - // Named or unnamed nested composite + // Parse nested composite type. let nested_args_count = nested_composite.fields.len(); if values.len() < nested_args_count { return Err(Error::ParamProcessingError); } - let nested_args: Vec = values.drain(..nested_args_count).map(String::from).collect(); let nested_arg_str = nested_args.join(","); @@ -172,7 +172,7 @@ impl TypeParser for TypeDefComposite { } }, _ => { - // Parse a single argument for non-composite fields + // Recursive for the sub parameters of the composite type. let raw_value = values.remove(0); process_argument(raw_value, field_type, registry)? }, @@ -223,12 +223,12 @@ impl TypeParser for TypeDefTuple { } impl TypeParser for TypeDefCompact { - fn parse(&self, input: &str, registry: &PortableRegistry) -> Result { + fn parse(&self, arg: &str, registry: &PortableRegistry) -> Result { // Parse compact types as their sub type (e.g., `Compact`). let sub_ty = registry .resolve(self.type_param.id) .ok_or_else(|| Error::ParamProcessingError)?; // Recursive for the inner value. - process_argument(input, sub_ty, registry) + process_argument(arg, sub_ty, registry) } } From 19e243ad75a3d1dfbe13ef248c2c79c091ada31c Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 21 Nov 2024 14:57:26 +0100 Subject: [PATCH 141/211] refactor: parse all metadata, including parameters at once --- crates/pop-cli/src/commands/call/parachain.rs | 40 ++-- .../src/call/metadata/action.rs | 7 +- .../pop-parachains/src/call/metadata/mod.rs | 219 ++++++++++++------ crates/pop-parachains/src/lib.rs | 2 +- 4 files changed, 170 insertions(+), 98 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index d3f385cfc..dde9d2a51 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -4,9 +4,9 @@ use crate::cli::{self, traits::*}; use anyhow::{anyhow, Result}; use clap::Args; use pop_parachains::{ - construct_extrinsic, encode_call_data, field_to_param, find_extrinsic_by_name, - find_pallet_by_name, parse_chain_metadata, set_up_api, sign_and_submit_extrinsic, - supported_actions, Action, DynamicPayload, OnlineClient, Param, SubstrateConfig, + construct_extrinsic, encode_call_data, find_extrinsic_by_name, find_pallet_by_name, + parse_chain_metadata, set_up_api, sign_and_submit_extrinsic, supported_actions, Action, + DynamicPayload, OnlineClient, Pallet, Param, SubstrateConfig, }; const DEFAULT_URL: &str = "ws://localhost:9944/"; @@ -103,17 +103,17 @@ impl CallParachainCommand { }; // Resolve pallet. let pallet = if let Some(ref pallet_name) = self.pallet { - find_pallet_by_name(&api, pallet_name).await? + find_pallet_by_name(&pallets, pallet_name).await? } else { // Specific predefined actions first. - let picked_action: Option = prompt_predefined_actions(&api, cli).await?; + let picked_action: Option = prompt_predefined_actions(&pallets, cli).await?; if let Some(action) = picked_action { self.pallet = Some(action.pallet_name().to_string()); self.extrinsic = Some(action.extrinsic_name().to_string()); - find_pallet_by_name(&api, action.pallet_name()).await? + find_pallet_by_name(&pallets, action.pallet_name()).await? } else { let mut prompt = cli.select("Select the pallet to call:"); - for pallet_item in pallets { + for pallet_item in &pallets { prompt = prompt.item(pallet_item.clone(), &pallet_item.name, &pallet_item.docs); } let pallet_prompted = prompt.interact()?; @@ -123,25 +123,31 @@ impl CallParachainCommand { }; // Resolve extrinsic. let extrinsic = if let Some(ref extrinsic_name) = self.extrinsic { - find_extrinsic_by_name(&api, &pallet.name, extrinsic_name).await? + find_extrinsic_by_name(&pallets, &pallet.name, extrinsic_name).await? } else { let mut prompt_extrinsic = cli.select("Select the extrinsic to call:"); for extrinsic in pallet.extrinsics { - prompt_extrinsic = prompt_extrinsic.item( - extrinsic.clone(), - &extrinsic.name, - &extrinsic.docs.concat(), - ); + prompt_extrinsic = + prompt_extrinsic.item(extrinsic.clone(), &extrinsic.name, &extrinsic.docs); } let extrinsic_prompted = prompt_extrinsic.interact()?; self.extrinsic = Some(extrinsic_prompted.name.clone()); extrinsic_prompted }; + if !extrinsic.is_supported { + cli.outro_cancel( + "The selected extrinsic is not supported. Please choose another one.", + )?; + // Reset specific items from the last call and repeat. + self.reset_for_new_call(); + Box::pin(self.configure(cli, true)).await?; + } + // Resolve message arguments. if self.args.is_empty() { let mut contract_args = Vec::new(); - for field in extrinsic.fields { - let param = field_to_param(&api, &extrinsic.name, &field)?; + for param in extrinsic.params { + //let param = field_to_param(&api, &extrinsic.name, &field)?; let input = prompt_for_param(&api, cli, ¶m)?; contract_args.push(input); } @@ -267,11 +273,11 @@ fn display_message(message: &str, success: bool, cli: &mut impl cli::traits::Cli } async fn prompt_predefined_actions( - api: &OnlineClient, + pallets: &[Pallet], cli: &mut impl cli::traits::Cli, ) -> Result> { let mut predefined_action = cli.select("What would you like to do?"); - for action in supported_actions(&api).await { + for action in supported_actions(&pallets).await { predefined_action = predefined_action.item( Some(action.clone()), action.description(), diff --git a/crates/pop-parachains/src/call/metadata/action.rs b/crates/pop-parachains/src/call/metadata/action.rs index 5b41650a1..e27a95b20 100644 --- a/crates/pop-parachains/src/call/metadata/action.rs +++ b/crates/pop-parachains/src/call/metadata/action.rs @@ -1,9 +1,8 @@ // SPDX-License-Identifier: GPL-3.0 -use super::find_extrinsic_by_name; +use super::{find_extrinsic_by_name, Pallet}; use strum::{EnumMessage as _, EnumProperty as _, VariantArray as _}; use strum_macros::{AsRefStr, Display, EnumMessage, EnumProperty, EnumString, VariantArray}; -use subxt::{OnlineClient, SubstrateConfig}; /// Enum representing predefined actions. #[derive( @@ -73,10 +72,10 @@ impl Action { } } -pub async fn supported_actions(api: &OnlineClient) -> Vec { +pub async fn supported_actions(pallets: &[Pallet]) -> Vec { let mut actions = Vec::new(); for action in Action::VARIANTS.iter() { - if find_extrinsic_by_name(api, action.pallet_name(), action.extrinsic_name()) + if find_extrinsic_by_name(pallets, action.pallet_name(), action.extrinsic_name()) .await .is_ok() { diff --git a/crates/pop-parachains/src/call/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs index 16dcb3e8e..37d1f5f1e 100644 --- a/crates/pop-parachains/src/call/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -17,7 +17,20 @@ pub struct Pallet { /// The documentation of the pallet. pub docs: String, // The extrinsics of the pallet. - pub extrinsics: Vec>, + pub extrinsics: Vec, +} + +#[derive(Clone, PartialEq, Eq, Debug)] +/// Represents an extrinsic in a pallet. +pub struct Extrinsic { + /// The name of the extrinsic. + pub name: String, + /// The documentation of the extrinsic. + pub docs: String, + /// The parameters of the extrinsic. + pub params: Vec, + /// Whether this extrinsic is supported (no recursive or unsupported types like `RuntimeCall`). + pub is_supported: bool, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -43,14 +56,60 @@ pub async fn parse_chain_metadata( api: &OnlineClient, ) -> Result, Error> { let metadata: Metadata = api.metadata(); - Ok(metadata + let registry = metadata.types(); + + let pallets = metadata .pallets() .map(|pallet| { - let extrinsics = - pallet.call_variants().map(|variants| variants.to_vec()).unwrap_or_default(); - Pallet { name: pallet.name().to_string(), extrinsics, docs: pallet.docs().join(" ") } + let extrinsics = pallet + .call_variants() + .map(|variants| { + variants + .iter() + .map(|variant| { + let mut is_supported = true; + + // Parse parameters for the extrinsic + let params = { + let mut parsed_params = Vec::new(); + for field in &variant.fields { + match field_to_param(api, &variant.name, field) { + Ok(param) => parsed_params.push(param), + Err(Error::ExtrinsicNotSupported(_)) => { + is_supported = false; + parsed_params.clear(); // Discard any already-parsed params + break; // Stop processing further fields + }, + Err(e) => return Err(e), // Propagate other errors + } + } + parsed_params + }; + + Ok(Extrinsic { + name: variant.name.clone(), + docs: if is_supported { + variant.docs.concat() + } else { + "Extrinsic Not Supported".to_string() + }, + params, + is_supported, + }) + }) + .collect::, Error>>() + }) + .unwrap_or_else(|| Ok(vec![]))?; + + Ok(Pallet { + name: pallet.name().to_string(), + docs: pallet.docs().join(" "), + extrinsics, + }) }) - .collect()) + .collect::, Error>>()?; + + Ok(pallets) } /// Finds a specific pallet by name and retrieves its details from metadata. @@ -58,23 +117,32 @@ pub async fn parse_chain_metadata( /// # Arguments /// * `api`: Reference to an `OnlineClient` connected to the chain. /// * `pallet_name`: The name of the pallet to find. -pub async fn find_pallet_by_name( - api: &OnlineClient, +pub async fn find_pallet_by_name(pallets: &[Pallet], pallet_name: &str) -> Result { + if let Some(pallet) = pallets.iter().find(|p| p.name == pallet_name) { + Ok(pallet.clone()) + } else { + Err(Error::PalletNotFound(pallet_name.to_string())) + } +} + +/// Finds a specific extrinsic by name and retrieves its details from metadata. +/// +/// # Arguments +/// * `api`: Reference to an `OnlineClient` connected to the chain. +/// * `pallet_name`: The name of the pallet to find. +/// * `extrinsic_name`: Name of the extrinsic to locate. +pub async fn find_extrinsic_by_name( + pallets: &[Pallet], pallet_name: &str, -) -> Result { - let metadata: Metadata = api.metadata(); - for pallet in metadata.pallets() { - if pallet.name() == pallet_name { - let extrinsics = - pallet.call_variants().map(|variants| variants.to_vec()).unwrap_or_default(); - return Ok(Pallet { - name: pallet.name().to_string(), - extrinsics, - docs: pallet.docs().join(" "), - }); - } + extrinsic_name: &str, +) -> Result { + let pallet = find_pallet_by_name(pallets, pallet_name).await?; + // Check if the specified extrinsic exists within this pallet + if let Some(extrinsic) = pallet.extrinsics.iter().find(|&e| e.name == extrinsic_name) { + return Ok(extrinsic.clone()); + } else { + return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); } - Err(Error::PalletNotFound(pallet_name.to_string())) } /// Transforms a metadata field into its `Param` representation. @@ -82,7 +150,7 @@ pub async fn find_pallet_by_name( /// # Arguments /// * `api`: Reference to an `OnlineClient` connected to the blockchain. /// * `field`: A reference to a metadata field of the extrinsic. -pub fn field_to_param( +fn field_to_param( api: &OnlineClient, extrinsic_name: &str, field: &Field, @@ -114,7 +182,10 @@ fn type_to_param( } } for param in &type_info.type_params { - if param.name == "RuntimeCall" { + if param.name == "RuntimeCall" || + param.name == "Vec" || + param.name == "Vec<::RuntimeCall>" + { return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); } } @@ -227,7 +298,7 @@ pub async fn process_extrinsic_args( ) -> Result, Error> { let metadata: Metadata = api.metadata(); let registry = metadata.types(); - let extrinsic = find_extrinsic_by_name(&api, pallet_name, extrinsic_name).await?; + let extrinsic = parse_extrinsic_by_name(&api, pallet_name, extrinsic_name).await?; let mut processed_parameters: Vec = Vec::new(); for (index, field) in extrinsic.fields.iter().enumerate() { let raw_parameter = raw_params.get(index).ok_or(Error::ParamProcessingError)?; @@ -244,68 +315,64 @@ pub async fn process_extrinsic_args( /// * `api`: Reference to an `OnlineClient` connected to the chain. /// * `pallet_name`: The name of the pallet to find. /// * `extrinsic_name`: Name of the extrinsic to locate. -pub async fn find_extrinsic_by_name( +async fn parse_extrinsic_by_name( api: &OnlineClient, pallet_name: &str, extrinsic_name: &str, ) -> Result, Error> { - let pallet = find_pallet_by_name(api, pallet_name).await?; - // Check if the specified extrinsic exists within this pallet - if let Some(extrinsic) = pallet.extrinsics.iter().find(|&e| e.name == extrinsic_name) { - return Ok(extrinsic.clone()); - } else { - return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); - } + let metadata: Metadata = api.metadata(); + let pallet = metadata + .pallets() + .into_iter() + .find(|p| p.name() == pallet_name) + .ok_or_else(|| Error::PalletNotFound(pallet_name.to_string()))?; + // Retrieve and check for the extrinsic within the pallet + let extrinsic = pallet + .call_variants() + .map(|variants| variants.iter().find(|e| e.name == extrinsic_name)) + .flatten() + .ok_or_else(|| Error::ExtrinsicNotSupported(extrinsic_name.to_string()))?; + + Ok(extrinsic.clone()) } -// #[cfg(test)] -// mod tests { -// use crate::set_up_api; +#[cfg(test)] +mod tests { + use crate::set_up_api; -// use super::*; -// use anyhow::Result; + use super::*; + use anyhow::Result; -// #[tokio::test] -// async fn process_prompt_arguments_works() -> Result<()> { -// let api = set_up_api("ws://127.0.0.1:9944").await?; -// // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; -// let ex = find_extrinsic_by_name(&api, "Nfts", "mint").await?; -// let prompt_args1 = process_prompt_arguments(&api, &ex.fields()[2])?; + // #[tokio::test] + // async fn process_prompt_arguments_works() -> Result<()> { + // let api = set_up_api("ws://127.0.0.1:9944").await?; + // // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; + // let ex = find_extrinsic_by_name(&api, "Nfts", "mint").await?; + // let prompt_args1 = process_prompt_arguments(&api, &ex.fields()[2])?; -// Ok(()) -// } + // Ok(()) + // } -// #[tokio::test] -// async fn process_extrinsic_args_works() -> Result<()> { -// let api = set_up_api("ws://127.0.0.1:9944").await?; -// // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; -// let ex = find_extrinsic_by_name(&api, "Nfts", "mint").await?; -// let args_parsed = process_extrinsic_args( -// &api, -// "Nfts", -// "mint", -// vec![ -// "1".to_string(), -// "1".to_string(), -// "Id(5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y)".to_string(), -// "Some(Some(1), Some(1))".to_string(), -// ], -// ) -// .await?; -// println!(" ARGS PARSER {:?}", args_parsed); + #[tokio::test] + async fn process_extrinsic_args_works() -> Result<()> { + let api = set_up_api("ws://127.0.0.1:9944").await?; + // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; + let ex = parse_extrinsic_by_name(&api, "Utility", "batch").await?; + println!("EXTRINSIC {:?}", ex); + println!(" ARGS PARSER {:?}", ex.fields); -// Ok(()) -// } + Ok(()) + } -// #[tokio::test] -// async fn process_extrinsic_args2_works() -> Result<()> { -// let api = set_up_api("ws://127.0.0.1:9944").await?; -// // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; -// let ex = find_extrinsic_by_name(&api, "Nfts", "mint").await?; -// let args_parsed = -// process_extrinsic_args(&api, "System", "remark", vec!["0x11".to_string()]).await?; -// println!(" ARGS PARSER {:?}", args_parsed); + // #[tokio::test] + // async fn process_extrinsic_args2_works() -> Result<()> { + // let api = set_up_api("ws://127.0.0.1:9944").await?; + // // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; + // let ex = find_extrinsic_by_name(&api, "Nfts", "mint").await?; + // let args_parsed = + // process_extrinsic_args(&api, "System", "remark", vec!["0x11".to_string()]).await?; + // println!(" ARGS PARSER {:?}", args_parsed); -// Ok(()) -// } -// } + // Ok(()) + // } +} diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index 98f26090a..58c76cc49 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -19,7 +19,7 @@ pub use call::{ construct_extrinsic, encode_call_data, metadata::{ action::{supported_actions, Action}, - field_to_param, find_extrinsic_by_name, find_pallet_by_name, parse_chain_metadata, Param, + find_extrinsic_by_name, find_pallet_by_name, parse_chain_metadata, Pallet, Param, }, set_up_api, sign_and_submit_extrinsic, }; From d3c48888172289a87ae2239a7e13143d950a61f1 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 22 Nov 2024 10:22:21 +0100 Subject: [PATCH 142/211] refactor: clean docs and move code --- crates/pop-cli/src/commands/call/parachain.rs | 77 ++++---- .../src/call/metadata/action.rs | 11 +- .../pop-parachains/src/call/metadata/mod.rs | 179 ++---------------- .../src/call/metadata/params.rs | 144 ++++++++++++++ .../src/call/metadata/type_parser.rs | 3 +- 5 files changed, 216 insertions(+), 198 deletions(-) create mode 100644 crates/pop-parachains/src/call/metadata/params.rs diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index dde9d2a51..9b6f9e5b0 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -14,13 +14,13 @@ const DEFAULT_URI: &str = "//Alice"; #[derive(Args, Clone)] pub struct CallParachainCommand { - /// The name of the pallet to call. + /// The pallet containing the extrinsic to execute. #[clap(long, short)] pallet: Option, - /// The name of the extrinsic to submit. + /// The extrinsic to execute within the chosen pallet. #[clap(long, short)] extrinsic: Option, - /// The constructor arguments, encoded as strings. + /// The extrinsic arguments, encoded as strings. #[clap(long, num_args = 0..,)] args: Vec, /// Websocket endpoint of a node. @@ -64,6 +64,22 @@ impl CallParachainCommand { Ok(()) } + fn display(&self) -> String { + let mut full_message = "pop call parachain".to_string(); + if let Some(pallet) = &self.pallet { + full_message.push_str(&format!(" --pallet {}", pallet)); + } + if let Some(extrinsic) = &self.extrinsic { + full_message.push_str(&format!(" --extrinsic {}", extrinsic)); + } + if !self.args.is_empty() { + let args: Vec<_> = self.args.iter().map(|a| format!("\"{a}\"")).collect(); + full_message.push_str(&format!(" --args {}", args.join(" "))); + } + full_message.push_str(&format!(" --url {} --suri {}", self.url, self.suri)); + full_message + } + /// Configure the call based on command line arguments/call UI. async fn configure( &mut self, @@ -74,11 +90,6 @@ impl CallParachainCommand { if !repeat { cli.intro("Call a parachain")?; } - // If extrinsic has been specified via command line arguments, return early. - // TODO: CALL DATA - // if self.extrinsic.is_some() { - // return Ok(()); - // } // Resolve url. if !repeat && self.url.as_str() == DEFAULT_URL { @@ -90,6 +101,7 @@ impl CallParachainCommand { .interact()?; self.url = url::Url::parse(&url)? }; + // Parse metadata from url chain. let api = set_up_api(self.url.as_str()).await?; let pallets = match parse_chain_metadata(&api).await { @@ -103,11 +115,12 @@ impl CallParachainCommand { }; // Resolve pallet. let pallet = if let Some(ref pallet_name) = self.pallet { + // Specified by the user. find_pallet_by_name(&pallets, pallet_name).await? } else { // Specific predefined actions first. - let picked_action: Option = prompt_predefined_actions(&pallets, cli).await?; - if let Some(action) = picked_action { + let action: Option = prompt_predefined_actions(&pallets, cli).await?; + if let Some(action) = action { self.pallet = Some(action.pallet_name().to_string()); self.extrinsic = Some(action.extrinsic_name().to_string()); find_pallet_by_name(&pallets, action.pallet_name()).await? @@ -134,9 +147,10 @@ impl CallParachainCommand { self.extrinsic = Some(extrinsic_prompted.name.clone()); extrinsic_prompted }; + // Certain extrinsics are not supported yet due to complexity. if !extrinsic.is_supported { cli.outro_cancel( - "The selected extrinsic is not supported. Please choose another one.", + "The selected extrinsic is not supported yet. Please choose another one.", )?; // Reset specific items from the last call and repeat. self.reset_for_new_call(); @@ -147,7 +161,6 @@ impl CallParachainCommand { if self.args.is_empty() { let mut contract_args = Vec::new(); for param in extrinsic.params { - //let param = field_to_param(&api, &extrinsic.name, &field)?; let input = prompt_for_param(&api, cli, ¶m)?; contract_args.push(input); } @@ -158,22 +171,6 @@ impl CallParachainCommand { Ok(api) } - fn display(&self) -> String { - let mut full_message = "pop call parachain".to_string(); - if let Some(pallet) = &self.pallet { - full_message.push_str(&format!(" --pallet {}", pallet)); - } - if let Some(extrinsic) = &self.extrinsic { - full_message.push_str(&format!(" --extrinsic {}", extrinsic)); - } - if !self.args.is_empty() { - let args: Vec<_> = self.args.iter().map(|a| format!("\"{a}\"")).collect(); - full_message.push_str(&format!(" --args {}", args.join(", "))); - } - full_message.push_str(&format!(" --url {} --suri {}", self.url, self.suri)); - full_message - } - /// Prepares the extrinsic or query. async fn prepare_extrinsic( &self, @@ -195,7 +192,7 @@ impl CallParachainCommand { let tx = match construct_extrinsic(api, &pallet, &extrinsic, self.args.clone()).await { Ok(tx) => tx, Err(e) => { - return Err(anyhow!("Error parsing the arguments: {}", e)); + return Err(anyhow!("Error: {}", e)); }, }; cli.info(format!("Encoded call data: {}", encode_call_data(api, &tx)?))?; @@ -210,14 +207,18 @@ impl CallParachainCommand { cli: &mut impl cli::traits::Cli, ) -> Result<()> { if self.suri == DEFAULT_URI { - self.suri = cli::Cli - .input("Who is going to sign the extrinsic:") + self.suri = cli + .input("Signer of the extrinsic:") .placeholder("//Alice") .default_input("//Alice") .interact()?; } cli.info(self.display())?; - if !cli.confirm("Do you want to submit the call?").initial_value(true).interact()? { + if !cli + .confirm("Do you want to submit the extrinsic?") + .initial_value(true) + .interact()? + { display_message( &format!( "Extrinsic {:?} was not submitted. Operation canceled by the user.", @@ -232,13 +233,13 @@ impl CallParachainCommand { spinner.start("Signing and submitting the extrinsic, please wait..."); let result = sign_and_submit_extrinsic(api.clone(), tx, &self.suri) .await - .map_err(|err| anyhow!("{} {}", "ERROR:", format!("{err:?}")))?; + .map_err(|err| anyhow!("{}", format!("{err:?}")))?; spinner.stop(&format!("Extrinsic submitted with hash: {:?}", result)); // Prompt for any additional calls. if !prompt_to_repeat_call { - display_message("Call completed successfully!", true, cli)?; + display_message("Extrinsic submitted successfully!", true, cli)?; return Ok(()); } if cli @@ -260,6 +261,7 @@ impl CallParachainCommand { fn reset_for_new_call(&mut self) { self.pallet = None; self.extrinsic = None; + self.args.clear(); } } @@ -272,6 +274,7 @@ fn display_message(message: &str, success: bool, cli: &mut impl cli::traits::Cli Ok(()) } +// Prompts the user for some predefined actions. async fn prompt_predefined_actions( pallets: &[Pallet], cli: &mut impl cli::traits::Cli, @@ -295,7 +298,6 @@ fn prompt_for_param( param: &Param, ) -> Result { if param.is_optional { - // Prompt user for optional parameter decision. if !cli .confirm(format!( "Do you want to provide a value for the optional parameter: {}?", @@ -308,7 +310,6 @@ fn prompt_for_param( let value = get_param_value(api, cli, param)?; Ok(format!("Some({})", value)) } else { - // Handle non-optional parameters. get_param_value(api, cli, param) } } @@ -328,6 +329,7 @@ fn get_param_value( } } +// Prompt for the value when is a primitive. fn prompt_for_primitive_param(cli: &mut impl cli::traits::Cli, param: &Param) -> Result { Ok(cli .input(format!("Enter the value for the parameter: {}", param.name)) @@ -335,6 +337,8 @@ fn prompt_for_primitive_param(cli: &mut impl cli::traits::Cli, param: &Param) -> .interact()?) } +// Prompt the user to select the value of the Variant parameter and recursively prompt for nested +// fields. Output example: Id(5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY) for the Id variant. fn prompt_for_variant_param( api: &OnlineClient, cli: &mut impl cli::traits::Cli, @@ -360,6 +364,7 @@ fn prompt_for_variant_param( } } +// Recursively prompt the user for all the nested fields in a Composite type. fn prompt_for_composite_param( api: &OnlineClient, cli: &mut impl cli::traits::Cli, diff --git a/crates/pop-parachains/src/call/metadata/action.rs b/crates/pop-parachains/src/call/metadata/action.rs index e27a95b20..a26e5c9e3 100644 --- a/crates/pop-parachains/src/call/metadata/action.rs +++ b/crates/pop-parachains/src/call/metadata/action.rs @@ -4,7 +4,7 @@ use super::{find_extrinsic_by_name, Pallet}; use strum::{EnumMessage as _, EnumProperty as _, VariantArray as _}; use strum_macros::{AsRefStr, Display, EnumMessage, EnumProperty, EnumString, VariantArray}; -/// Enum representing predefined actions. +/// Enum representing various predefined actions supported. #[derive( AsRefStr, Clone, @@ -61,17 +61,22 @@ impl Action { self.get_detailed_message().unwrap_or_default() } - /// Get the the extrinsic name of the action. + /// Get the extrinsic name corresponding to the action. pub fn extrinsic_name(&self) -> &str { self.get_message().unwrap_or_default() } - /// Get the pallet name of the action. + /// Get the associated pallet name for the action. pub fn pallet_name(&self) -> &str { self.get_str("Pallet").unwrap_or_default() } } +/// Fetch the list of supported actions based on available pallets. +/// +/// # Arguments +/// +/// * `pallets`: List of pallets availables in the chain. pub async fn supported_actions(pallets: &[Pallet]) -> Vec { let mut actions = Vec::new(); for action in Action::VARIANTS.iter() { diff --git a/crates/pop-parachains/src/call/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs index 37d1f5f1e..2ae213eae 100644 --- a/crates/pop-parachains/src/call/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -1,12 +1,12 @@ // SPDX-License-Identifier: GPL-3.0 use crate::errors::Error; -use pop_common::format_type; -use scale_info::{form::PortableForm, Field, PortableRegistry, TypeDef, Variant}; +use scale_info::{form::PortableForm, Variant}; use subxt::{dynamic::Value, Metadata, OnlineClient, SubstrateConfig}; use type_parser::process_argument; pub mod action; +mod params; mod type_parser; #[derive(Clone, PartialEq, Eq)] @@ -48,7 +48,8 @@ pub struct Param { pub is_variant: bool, } -/// Parses the chain metadata to extract information about pallets and their extrinsics. +/// Parses the chain metadata to extract information about pallets and their extrinsics with its +/// parameters. /// /// # Arguments /// * `api`: Reference to an `OnlineClient` connected to the chain. @@ -56,7 +57,6 @@ pub async fn parse_chain_metadata( api: &OnlineClient, ) -> Result, Error> { let metadata: Metadata = api.metadata(); - let registry = metadata.types(); let pallets = metadata .pallets() @@ -73,14 +73,15 @@ pub async fn parse_chain_metadata( let params = { let mut parsed_params = Vec::new(); for field in &variant.fields { - match field_to_param(api, &variant.name, field) { + match params::field_to_param(api, &variant.name, field) { Ok(param) => parsed_params.push(param), Err(Error::ExtrinsicNotSupported(_)) => { + // Unsupported extrinsic due to complex types is_supported = false; - parsed_params.clear(); // Discard any already-parsed params - break; // Stop processing further fields + parsed_params.clear(); + break; }, - Err(e) => return Err(e), // Propagate other errors + Err(e) => return Err(e), } } parsed_params @@ -91,6 +92,7 @@ pub async fn parse_chain_metadata( docs: if is_supported { variant.docs.concat() } else { + // To display the message in the UI "Extrinsic Not Supported".to_string() }, params, @@ -115,7 +117,7 @@ pub async fn parse_chain_metadata( /// Finds a specific pallet by name and retrieves its details from metadata. /// /// # Arguments -/// * `api`: Reference to an `OnlineClient` connected to the chain. +/// * `pallets`: List of pallets availables in the chain. /// * `pallet_name`: The name of the pallet to find. pub async fn find_pallet_by_name(pallets: &[Pallet], pallet_name: &str) -> Result { if let Some(pallet) = pallets.iter().find(|p| p.name == pallet_name) { @@ -128,7 +130,7 @@ pub async fn find_pallet_by_name(pallets: &[Pallet], pallet_name: &str) -> Resul /// Finds a specific extrinsic by name and retrieves its details from metadata. /// /// # Arguments -/// * `api`: Reference to an `OnlineClient` connected to the chain. +/// * `pallets`: List of pallets availables in the chain. /// * `pallet_name`: The name of the pallet to find. /// * `extrinsic_name`: Name of the extrinsic to locate. pub async fn find_extrinsic_by_name( @@ -137,7 +139,6 @@ pub async fn find_extrinsic_by_name( extrinsic_name: &str, ) -> Result { let pallet = find_pallet_by_name(pallets, pallet_name).await?; - // Check if the specified extrinsic exists within this pallet if let Some(extrinsic) = pallet.extrinsics.iter().find(|&e| e.name == extrinsic_name) { return Ok(extrinsic.clone()); } else { @@ -145,144 +146,6 @@ pub async fn find_extrinsic_by_name( } } -/// Transforms a metadata field into its `Param` representation. -/// -/// # Arguments -/// * `api`: Reference to an `OnlineClient` connected to the blockchain. -/// * `field`: A reference to a metadata field of the extrinsic. -fn field_to_param( - api: &OnlineClient, - extrinsic_name: &str, - field: &Field, -) -> Result { - let metadata: Metadata = api.metadata(); - let registry = metadata.types(); - let name = field.name.clone().unwrap_or("Unnamed".to_string()); //It can be unnamed field - type_to_param(extrinsic_name, name, registry, field.ty.id, &field.type_name) -} - -/// Converts a type's metadata into a `Param` representation. -/// -/// # Arguments -/// * `name`: The name of the parameter. -/// * `registry`: A reference to the `PortableRegistry` to resolve type dependencies. -/// * `type_id`: The ID of the type to be converted. -/// * `type_name`: An optional descriptive name for the type. -fn type_to_param( - extrinsic_name: &str, - name: String, - registry: &PortableRegistry, - type_id: u32, - type_name: &Option, -) -> Result { - let type_info = registry.resolve(type_id).ok_or(Error::MetadataParsingError(name.clone()))?; - if let Some(last_segment) = type_info.path.segments.last() { - if last_segment == "RuntimeCall" { - return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); - } - } - for param in &type_info.type_params { - if param.name == "RuntimeCall" || - param.name == "Vec" || - param.name == "Vec<::RuntimeCall>" - { - return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); - } - } - if type_info.path.segments == ["Option"] { - if let Some(sub_type_id) = type_info.type_params.get(0).and_then(|param| param.ty) { - // Recursive for the sub parameters - let sub_param = - type_to_param(extrinsic_name, name.clone(), registry, sub_type_id.id, type_name)?; - return Ok(Param { - name, - type_name: sub_param.type_name, - is_optional: true, - sub_params: sub_param.sub_params, - is_variant: false, - }); - } else { - Err(Error::MetadataParsingError(name)) - } - } else { - // Determine the formatted type name. - let type_name = format_type(type_info, registry); - match &type_info.type_def { - TypeDef::Primitive(_) => Ok(Param { - name, - type_name, - is_optional: false, - sub_params: Vec::new(), - is_variant: false, - }), - TypeDef::Composite(composite) => { - let sub_params = composite - .fields - .iter() - .map(|field| { - // Recursive for the sub parameters of composite type. - type_to_param( - extrinsic_name, - field.name.clone().unwrap_or(name.clone()), - registry, - field.ty.id, - &field.type_name, - ) - }) - .collect::, Error>>()?; - - Ok(Param { name, type_name, is_optional: false, sub_params, is_variant: false }) - }, - TypeDef::Variant(variant) => { - let variant_params = variant - .variants - .iter() - .map(|variant_param| { - let variant_sub_params = variant_param - .fields - .iter() - .map(|field| { - // Recursive for the sub parameters of variant type. - type_to_param( - extrinsic_name, - field.name.clone().unwrap_or(variant_param.name.clone()), - registry, - field.ty.id, - &field.type_name, - ) - }) - .collect::, Error>>()?; - Ok(Param { - name: variant_param.name.clone(), - type_name: "".to_string(), - is_optional: false, - sub_params: variant_sub_params, - is_variant: true, - }) - }) - .collect::, Error>>()?; - - Ok(Param { - name, - type_name, - is_optional: false, - sub_params: variant_params, - is_variant: true, - }) - }, - TypeDef::Array(_) | TypeDef::Sequence(_) | TypeDef::Tuple(_) | TypeDef::Compact(_) => - Ok(Param { - name, - type_name, - is_optional: false, - sub_params: Vec::new(), - is_variant: false, - }), - _ => Err(Error::MetadataParsingError(name)), - } - } -} - /// Processes and maps parameters for a given pallet extrinsic based on its metadata. /// /// # Arguments @@ -353,16 +216,16 @@ mod tests { // Ok(()) // } - #[tokio::test] - async fn process_extrinsic_args_works() -> Result<()> { - let api = set_up_api("ws://127.0.0.1:9944").await?; - // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; - let ex = parse_extrinsic_by_name(&api, "Utility", "batch").await?; - println!("EXTRINSIC {:?}", ex); - println!(" ARGS PARSER {:?}", ex.fields); + // #[tokio::test] + // async fn process_extrinsic_args_works() -> Result<()> { + // let api = set_up_api("ws://127.0.0.1:9944").await?; + // // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; + // let ex = parse_extrinsic_by_name(&api, "Utility", "batch").await?; + // println!("EXTRINSIC {:?}", ex); + // println!(" ARGS PARSER {:?}", ex.fields); - Ok(()) - } + // Ok(()) + // } // #[tokio::test] // async fn process_extrinsic_args2_works() -> Result<()> { diff --git a/crates/pop-parachains/src/call/metadata/params.rs b/crates/pop-parachains/src/call/metadata/params.rs new file mode 100644 index 000000000..b85efd597 --- /dev/null +++ b/crates/pop-parachains/src/call/metadata/params.rs @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-3.0 + +use crate::{errors::Error, Param}; +use pop_common::format_type; +use scale_info::{form::PortableForm, Field, PortableRegistry, TypeDef}; +use subxt::{Metadata, OnlineClient, SubstrateConfig}; + +/// Transforms a metadata field into its `Param` representation. +/// +/// # Arguments +/// * `api`: Reference to an `OnlineClient` connected to the blockchain. +/// * `field`: A reference to a metadata field of the extrinsic. +pub fn field_to_param( + api: &OnlineClient, + extrinsic_name: &str, + field: &Field, +) -> Result { + let metadata: Metadata = api.metadata(); + let registry = metadata.types(); + let name = field.name.clone().unwrap_or("Unnamed".to_string()); //It can be unnamed field + type_to_param(extrinsic_name, name, registry, field.ty.id, &field.type_name) +} + +/// Converts a type's metadata into a `Param` representation. +/// +/// # Arguments +/// * `name`: The name of the parameter. +/// * `registry`: A reference to the `PortableRegistry` to resolve type dependencies. +/// * `type_id`: The ID of the type to be converted. +/// * `type_name`: An optional descriptive name for the type. +fn type_to_param( + extrinsic_name: &str, + name: String, + registry: &PortableRegistry, + type_id: u32, + type_name: &Option, +) -> Result { + let type_info = registry.resolve(type_id).ok_or(Error::MetadataParsingError(name.clone()))?; + if let Some(last_segment) = type_info.path.segments.last() { + if last_segment == "RuntimeCall" { + return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); + } + } + for param in &type_info.type_params { + if param.name == "RuntimeCall" || + param.name == "Vec" || + param.name == "Vec<::RuntimeCall>" + { + return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); + } + } + if type_info.path.segments == ["Option"] { + if let Some(sub_type_id) = type_info.type_params.get(0).and_then(|param| param.ty) { + // Recursive for the sub parameters + let sub_param = + type_to_param(extrinsic_name, name.clone(), registry, sub_type_id.id, type_name)?; + return Ok(Param { + name, + type_name: sub_param.type_name, + is_optional: true, + sub_params: sub_param.sub_params, + is_variant: false, + }); + } else { + Err(Error::MetadataParsingError(name)) + } + } else { + // Determine the formatted type name. + let type_name = format_type(type_info, registry); + match &type_info.type_def { + TypeDef::Primitive(_) => Ok(Param { + name, + type_name, + is_optional: false, + sub_params: Vec::new(), + is_variant: false, + }), + TypeDef::Composite(composite) => { + let sub_params = composite + .fields + .iter() + .map(|field| { + // Recursive for the sub parameters of composite type. + type_to_param( + extrinsic_name, + field.name.clone().unwrap_or(name.clone()), + registry, + field.ty.id, + &field.type_name, + ) + }) + .collect::, Error>>()?; + + Ok(Param { name, type_name, is_optional: false, sub_params, is_variant: false }) + }, + TypeDef::Variant(variant) => { + let variant_params = variant + .variants + .iter() + .map(|variant_param| { + let variant_sub_params = variant_param + .fields + .iter() + .map(|field| { + // Recursive for the sub parameters of variant type. + type_to_param( + extrinsic_name, + field.name.clone().unwrap_or(variant_param.name.clone()), + registry, + field.ty.id, + &field.type_name, + ) + }) + .collect::, Error>>()?; + Ok(Param { + name: variant_param.name.clone(), + type_name: "".to_string(), + is_optional: false, + sub_params: variant_sub_params, + is_variant: true, + }) + }) + .collect::, Error>>()?; + + Ok(Param { + name, + type_name, + is_optional: false, + sub_params: variant_params, + is_variant: true, + }) + }, + TypeDef::Array(_) | TypeDef::Sequence(_) | TypeDef::Tuple(_) | TypeDef::Compact(_) => + Ok(Param { + name, + type_name, + is_optional: false, + sub_params: Vec::new(), + is_variant: false, + }), + _ => Err(Error::MetadataParsingError(name)), + } + } +} diff --git a/crates/pop-parachains/src/call/metadata/type_parser.rs b/crates/pop-parachains/src/call/metadata/type_parser.rs index 7f214f510..63b500db0 100644 --- a/crates/pop-parachains/src/call/metadata/type_parser.rs +++ b/crates/pop-parachains/src/call/metadata/type_parser.rs @@ -22,7 +22,8 @@ pub fn process_argument( let type_path = ty.path.segments.join("::"); match type_path.as_str() { "Option" => handle_option_type(arg, ty, registry), - "sp_core::crypto::AccountId32" => Ok(Value::from_bytes(parse_account(arg)?)), /* Specifically parse AccountId */ + // TODO: Handle other account types. + "sp_core::crypto::AccountId32" => Ok(Value::from_bytes(parse_account(arg)?)), _ => match &ty.type_def { TypeDef::Primitive(primitive) => primitive.parse(arg, registry), TypeDef::Composite(composite) => composite.parse(arg, registry), From b2bc2d716eb0fdd3c7a507e301a40de2e9bf3d7e Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 22 Nov 2024 10:38:32 +0100 Subject: [PATCH 143/211] fix: format_type --- crates/pop-common/src/metadata.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pop-common/src/metadata.rs b/crates/pop-common/src/metadata.rs index 3cff32d72..4ea58d806 100644 --- a/crates/pop-common/src/metadata.rs +++ b/crates/pop-common/src/metadata.rs @@ -148,7 +148,7 @@ pub fn format_type(ty: &Type, registry: &PortableRegistry) -> Stri ) }, TypeDef::BitSequence(_) => { - unimplemented!("bit sequence not currently supported") + "BitSequence".to_string() }, } ); From 12cfce8bd2f0105e94af9e0f9a70c1401db608d8 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 22 Nov 2024 22:35:53 +0100 Subject: [PATCH 144/211] test: fix unit test --- Cargo.lock | 11 ++++++----- crates/pop-cli/src/commands/up/contract.rs | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 07f824cd1..e5ee7d1c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4667,7 +4667,7 @@ dependencies = [ [[package]] name = "pop-cli" -version = "0.4.0" +version = "0.5.0" dependencies = [ "anyhow", "assert_cmd", @@ -4697,7 +4697,7 @@ dependencies = [ [[package]] name = "pop-common" -version = "0.4.0" +version = "0.5.0" dependencies = [ "anyhow", "cargo_toml", @@ -4724,7 +4724,7 @@ dependencies = [ [[package]] name = "pop-contracts" -version = "0.4.0" +version = "0.5.0" dependencies = [ "anyhow", "contract-build", @@ -4738,6 +4738,7 @@ dependencies = [ "mockito", "pop-common", "reqwest 0.12.7", + "scale-info", "sp-core", "sp-weights", "strum 0.26.3", @@ -4752,7 +4753,7 @@ dependencies = [ [[package]] name = "pop-parachains" -version = "0.4.0" +version = "0.5.0" dependencies = [ "anyhow", "askama", @@ -4784,7 +4785,7 @@ dependencies = [ [[package]] name = "pop-telemetry" -version = "0.4.0" +version = "0.5.0" dependencies = [ "dirs", "env_logger", diff --git a/crates/pop-cli/src/commands/up/contract.rs b/crates/pop-cli/src/commands/up/contract.rs index 9acbc2b63..435dab1d0 100644 --- a/crates/pop-cli/src/commands/up/contract.rs +++ b/crates/pop-cli/src/commands/up/contract.rs @@ -356,7 +356,7 @@ mod tests { UpOpts { path: None, constructor: "new".to_string(), - args: vec!["false".to_string()].to_vec(), + args: vec![].to_vec(), value: "0".to_string(), gas_limit: None, proof_size: None, From 31abf338e19f8ce49719cbfe691d0533da91159e Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Sun, 24 Nov 2024 17:23:13 +0100 Subject: [PATCH 145/211] refactor: clean the way to parse and prompt parameters --- Cargo.lock | 165 ++++++------ Cargo.toml | 1 + crates/pop-cli/src/commands/call/parachain.rs | 37 ++- crates/pop-common/src/lib.rs | 10 +- crates/pop-parachains/Cargo.toml | 1 + .../pop-parachains/src/call/metadata/mod.rs | 122 ++------- .../src/call/metadata/params.rs | 68 ++++- .../src/call/metadata/type_parser.rs | 235 ------------------ crates/pop-parachains/src/call/mod.rs | 4 +- crates/pop-parachains/src/lib.rs | 4 +- 10 files changed, 208 insertions(+), 439 deletions(-) delete mode 100644 crates/pop-parachains/src/call/metadata/type_parser.rs diff --git a/Cargo.lock b/Cargo.lock index e5ee7d1c3..2bf92fa68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -355,7 +355,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -533,7 +533,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -550,7 +550,7 @@ checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -848,7 +848,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", "syn_derive", ] @@ -1075,7 +1075,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1505,7 +1505,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1532,7 +1532,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1549,7 +1549,7 @@ checksum = "98532a60dedaebc4848cb2cba5023337cc9ea3af16a5b062633fabfd9f18fb60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1597,7 +1597,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1619,7 +1619,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core 0.20.10", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1687,7 +1687,7 @@ checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1698,7 +1698,7 @@ checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1709,7 +1709,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1722,7 +1722,27 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.77", + "syn 2.0.89", +] + +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", ] [[package]] @@ -1793,7 +1813,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1823,7 +1843,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.77", + "syn 2.0.89", "termcolor", "toml 0.8.19", "walkdir", @@ -2078,7 +2098,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -2310,7 +2330,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -3005,7 +3025,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f357e2e867f4e222ffc4015a6e61d1073548de89f70a4e36a8b0385562777fa" dependencies = [ "blake2", - "derive_more", + "derive_more 0.99.18", "ink_primitives", "pallet-contracts-uapi-next", "parity-scale-codec", @@ -3023,7 +3043,7 @@ dependencies = [ "blake2", "cfg-if", "const_env", - "derive_more", + "derive_more 0.99.18", "ink_allocator", "ink_engine", "ink_prelude", @@ -3050,7 +3070,7 @@ version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a98fcc0ff9292ff68c7ee7b84c93533c9ff13859ec3b148faa822e2da9954fe6" dependencies = [ - "derive_more", + "derive_more 0.99.18", "impl-serde", "ink_prelude", "ink_primitives", @@ -3076,7 +3096,7 @@ version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11ec35ef7f45e67a53b6142d7e7f18e6d9292d76c3a2a1da14cf8423e481813d" dependencies = [ - "derive_more", + "derive_more 0.99.18", "ink_prelude", "parity-scale-codec", "scale-decode 0.10.0", @@ -3804,7 +3824,7 @@ checksum = "cb26336e6dc7cc76e7927d2c9e7e3bb376d7af65a6f56a0b16c47d18a9b1abc5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4242,7 +4262,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4464,7 +4484,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4495,7 +4515,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4562,7 +4582,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6380dbe1fb03ecc74ad55d841cfc75480222d153ba69ddcb00977866cbdabdb8" dependencies = [ "polkavm-derive-impl 0.5.0", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4592,7 +4612,7 @@ dependencies = [ "polkavm-common 0.5.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4604,7 +4624,7 @@ dependencies = [ "polkavm-common 0.8.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4616,7 +4636,7 @@ dependencies = [ "polkavm-common 0.9.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4626,7 +4646,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15e85319a0d5129dc9f021c62607e0804f5fb777a05cdda44d750ac0732def66" dependencies = [ "polkavm-derive-impl 0.8.0", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4636,7 +4656,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" dependencies = [ "polkavm-derive-impl 0.9.0", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4762,11 +4782,12 @@ dependencies = [ "flate2", "glob", "hex", - "indexmap 2.4.0", + "indexmap 2.5.0", "mockito", "pop-common", - "reqwest 0.12.5", + "reqwest 0.12.7", "scale-info", + "scale-value", "serde_json", "strum 0.26.3", "strum_macros 0.26.4", @@ -4857,7 +4878,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" dependencies = [ "proc-macro2", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4918,9 +4939,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -5067,7 +5088,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -5488,7 +5509,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58c4eb8a81997cf040a091d1f7e1938aeab6749d3a0dfa73af43cdc32393483d" dependencies = [ "byteorder", - "derive_more", + "derive_more 0.99.18", "twox-hash", ] @@ -5546,7 +5567,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7caaf753f8ed1ab4752c6afb20174f03598c664724e0e32628e161c21000ff76" dependencies = [ - "derive_more", + "derive_more 0.99.18", "parity-scale-codec", "scale-bits 0.4.0", "scale-decode-derive 0.10.0", @@ -5560,7 +5581,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e98f3262c250d90e700bb802eb704e1f841e03331c2eb815e46516c4edbf5b27" dependencies = [ - "derive_more", + "derive_more 0.99.18", "parity-scale-codec", "primitive-types", "scale-bits 0.6.0", @@ -5600,7 +5621,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d70cb4b29360105483fac1ed567ff95d65224a14dd275b6303ed0a654c78de5" dependencies = [ - "derive_more", + "derive_more 0.99.18", "parity-scale-codec", "scale-encode-derive 0.5.0", "scale-info", @@ -5613,7 +5634,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ba0b9c48dc0eb20c60b083c29447c0c4617cb7c4a4c9fef72aa5c5bc539e15e" dependencies = [ - "derive_more", + "derive_more 0.99.18", "parity-scale-codec", "primitive-types", "scale-bits 0.6.0", @@ -5650,13 +5671,13 @@ dependencies = [ [[package]] name = "scale-info" -version = "2.11.3" +version = "2.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca070c12893629e2cc820a9761bedf6ce1dcddc9852984d1dc734b8bd9bd024" +checksum = "346a3b32eba2640d17a9cb5927056b08f3de90f65b72fe09402c2ad07d684d0b" dependencies = [ "bitvec", "cfg-if", - "derive_more", + "derive_more 1.0.0", "parity-scale-codec", "scale-info-derive", "schemars", @@ -5665,14 +5686,14 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.11.3" +version = "2.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d35494501194174bda522a32605929eefc9ecf7e0a326c26db1fdd85881eb62" +checksum = "c6630024bf739e2179b91fb424b28898baf819414262c5d376677dbff1fe7ebf" dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.89", ] [[package]] @@ -5694,7 +5715,7 @@ dependencies = [ "proc-macro2", "quote", "scale-info", - "syn 2.0.77", + "syn 2.0.89", "thiserror", ] @@ -5706,7 +5727,7 @@ checksum = "ba4d772cfb7569e03868400344a1695d16560bf62b86b918604773607d39ec84" dependencies = [ "base58", "blake2", - "derive_more", + "derive_more 0.99.18", "either", "frame-metadata 15.1.0", "parity-scale-codec", @@ -5749,7 +5770,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -5922,7 +5943,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -5933,7 +5954,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -5957,7 +5978,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -6227,7 +6248,7 @@ dependencies = [ "bs58", "chacha20", "crossbeam-queue", - "derive_more", + "derive_more 0.99.18", "ed25519-zebra 4.0.3", "either", "event-listener 4.0.3", @@ -6277,7 +6298,7 @@ dependencies = [ "async-lock", "base64 0.21.7", "blake2-rfc", - "derive_more", + "derive_more 0.99.18", "either", "event-listener 4.0.3", "fnv", @@ -6441,7 +6462,7 @@ checksum = "48d09fa0a5f7299fb81ee25ae3853d26200f7a348148aed6de76be905c007dbe" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -6562,7 +6583,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -6768,7 +6789,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -6842,7 +6863,7 @@ dependencies = [ "scale-info", "scale-typegen", "subxt-metadata", - "syn 2.0.77", + "syn 2.0.89", "thiserror", "tokio", ] @@ -6905,7 +6926,7 @@ dependencies = [ "quote", "scale-typegen", "subxt-codegen", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -6962,9 +6983,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ "proc-macro2", "quote", @@ -6980,7 +7001,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -7129,7 +7150,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -7224,7 +7245,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -7449,7 +7470,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -7811,7 +7832,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", "wasm-bindgen-shared", ] @@ -7845,7 +7866,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -8512,7 +8533,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -8532,7 +8553,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 2e1b68ae5..43b517c44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,7 @@ contract-build = "5.0.0-alpha" contract-extrinsics = "5.0.0-alpha" contract-transcode = "5.0.0-alpha" scale-info = { version = "2.11.3", default-features = false, features = ["derive"] } +scale-value = { version = "0.16.2", default-features = false, features = ["from-string", "parser-ss58"] } heck = "0.5.0" hex = { version = "0.4.3", default-features = false } diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 9b6f9e5b0..86f44bc9c 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -189,7 +189,7 @@ impl CallParachainCommand { return Err(anyhow!("Please specify the pallet.")); }, }; - let tx = match construct_extrinsic(api, &pallet, &extrinsic, self.args.clone()).await { + let tx = match construct_extrinsic(&pallet, &extrinsic, self.args.clone()).await { Ok(tx) => tx, Err(e) => { return Err(anyhow!("Error: {}", e)); @@ -305,7 +305,7 @@ fn prompt_for_param( )) .interact()? { - return Ok("None".to_string()); + return Ok("None()".to_string()); } let value = get_param_value(api, cli, param)?; Ok(format!("Some({})", value)) @@ -324,6 +324,8 @@ fn get_param_value( prompt_for_primitive_param(cli, param) } else if param.is_variant { prompt_for_variant_param(api, cli, param) + } else if param.is_tuple { + prompt_for_tuple_param(api, cli, param) } else { prompt_for_composite_param(api, cli, param) } @@ -360,7 +362,7 @@ fn prompt_for_variant_param( } Ok(format!("{}({})", selected_variant.name, field_values.join(", "))) } else { - Ok(selected_variant.name.clone()) + Ok(format!("{}()", selected_variant.name.clone())) } } @@ -373,7 +375,32 @@ fn prompt_for_composite_param( let mut field_values = Vec::new(); for field_arg in ¶m.sub_params { let field_value = prompt_for_param(api, cli, field_arg)?; - field_values.push(field_value); + // Example: Param { name: "Id", type_name: "AccountId32 ([u8;32])", is_optional: false, + // sub_params: [Param { name: "Id", type_name: "[u8;32]", is_optional: false, sub_params: + // [], is_variant: false }], is_variant: false } + if param.sub_params.len() == 1 && param.name == param.sub_params[0].name { + field_values.push(format!("{}", field_value)); + } else { + field_values.push(format!("{}: {}", field_arg.name, field_value)); + } + } + if param.sub_params.len() == 1 && param.name == param.sub_params[0].name { + Ok(format!("{}", field_values.join(", "))) + } else { + Ok(format!("{{{}}}", field_values.join(", "))) + } +} + +// Recursively prompt the user for the tuple values. +fn prompt_for_tuple_param( + api: &OnlineClient, + cli: &mut impl cli::traits::Cli, + param: &Param, +) -> Result { + let mut tuple_values = Vec::new(); + for (_index, tuple_param) in param.sub_params.iter().enumerate() { + let tuple_value = prompt_for_param(api, cli, tuple_param)?; + tuple_values.push(tuple_value); } - Ok(field_values.join(", ")) + Ok(format!("({})", tuple_values.join(", "))) } diff --git a/crates/pop-common/src/lib.rs b/crates/pop-common/src/lib.rs index ebe88ff11..be843ec85 100644 --- a/crates/pop-common/src/lib.rs +++ b/crates/pop-common/src/lib.rs @@ -43,18 +43,16 @@ pub fn target() -> Result<&'static str, Error> { } match ARCH { - "aarch64" => { + "aarch64" => return match OS { "macos" => Ok("aarch64-apple-darwin"), _ => Ok("aarch64-unknown-linux-gnu"), - } - }, - "x86_64" | "x86" => { + }, + "x86_64" | "x86" => return match OS { "macos" => Ok("x86_64-apple-darwin"), _ => Ok("x86_64-unknown-linux-gnu"), - } - }, + }, &_ => {}, } Err(Error::UnsupportedPlatform { arch: ARCH, os: OS }) diff --git a/crates/pop-parachains/Cargo.toml b/crates/pop-parachains/Cargo.toml index 7ec7be467..b6af7d0d7 100644 --- a/crates/pop-parachains/Cargo.toml +++ b/crates/pop-parachains/Cargo.toml @@ -28,6 +28,7 @@ askama.workspace = true indexmap.workspace = true reqwest.workspace = true scale-info.workspace = true +scale-value.workspace = true subxt.workspace = true symlink.workspace = true toml_edit.workspace = true diff --git a/crates/pop-parachains/src/call/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs index 2ae213eae..e7f863d0a 100644 --- a/crates/pop-parachains/src/call/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -1,13 +1,12 @@ // SPDX-License-Identifier: GPL-3.0 use crate::errors::Error; -use scale_info::{form::PortableForm, Variant}; +use params::Param; +use scale_value::stringify::custom_parsers; use subxt::{dynamic::Value, Metadata, OnlineClient, SubstrateConfig}; -use type_parser::process_argument; pub mod action; -mod params; -mod type_parser; +pub mod params; #[derive(Clone, PartialEq, Eq)] /// Represents a pallet in the blockchain, including its extrinsics. @@ -33,21 +32,6 @@ pub struct Extrinsic { pub is_supported: bool, } -#[derive(Debug, Clone, PartialEq, Eq)] -/// Describes a parameter of an extrinsic. -pub struct Param { - /// The name of the parameter. - pub name: String, - /// The type of the parameter. - pub type_name: String, - /// Indicates if the parameter is optional (`Option`). - pub is_optional: bool, - /// Nested parameters for composite or variants types. - pub sub_params: Vec, - /// Indicates if the parameter is a Variant. - pub is_variant: bool, -} - /// Parses the chain metadata to extract information about pallets and their extrinsics with its /// parameters. /// @@ -146,96 +130,20 @@ pub async fn find_extrinsic_by_name( } } -/// Processes and maps parameters for a given pallet extrinsic based on its metadata. +/// Parses and processes raw string parameters for an extrinsic, mapping them to `Value` types. /// /// # Arguments -/// * `api`: Reference to an `OnlineClient` connected to the blockchain. -/// * `pallet_name`: Name of the pallet containing the extrinsic. -/// * `extrinsic_name`: Name of the extrinsic to process. /// * `raw_params`: A vector of raw string arguments for the extrinsic. -pub async fn process_extrinsic_args( - api: &OnlineClient, - pallet_name: &str, - extrinsic_name: &str, - raw_params: Vec, -) -> Result, Error> { - let metadata: Metadata = api.metadata(); - let registry = metadata.types(); - let extrinsic = parse_extrinsic_by_name(&api, pallet_name, extrinsic_name).await?; - let mut processed_parameters: Vec = Vec::new(); - for (index, field) in extrinsic.fields.iter().enumerate() { - let raw_parameter = raw_params.get(index).ok_or(Error::ParamProcessingError)?; - let type_info = registry.resolve(field.ty.id).ok_or(Error::ParamProcessingError)?; //Resolve with type_id - let arg_processed = process_argument(raw_parameter, type_info, registry)?; - processed_parameters.push(arg_processed); +pub async fn parse_extrinsic_arguments(raw_params: Vec) -> Result, Error> { + let mut parsed_params: Vec = Vec::new(); + for raw_param in raw_params { + let parsed_value: Value = scale_value::stringify::from_str_custom() + .add_custom_parser(custom_parsers::parse_hex) + .add_custom_parser(custom_parsers::parse_ss58) + .parse(&raw_param) + .0 + .map_err(|_| Error::ParamProcessingError)?; + parsed_params.push(parsed_value); } - Ok(processed_parameters) -} - -/// Finds a specific extrinsic by name and retrieves its details from metadata. -/// -/// # Arguments -/// * `api`: Reference to an `OnlineClient` connected to the chain. -/// * `pallet_name`: The name of the pallet to find. -/// * `extrinsic_name`: Name of the extrinsic to locate. -async fn parse_extrinsic_by_name( - api: &OnlineClient, - pallet_name: &str, - extrinsic_name: &str, -) -> Result, Error> { - let metadata: Metadata = api.metadata(); - let pallet = metadata - .pallets() - .into_iter() - .find(|p| p.name() == pallet_name) - .ok_or_else(|| Error::PalletNotFound(pallet_name.to_string()))?; - // Retrieve and check for the extrinsic within the pallet - let extrinsic = pallet - .call_variants() - .map(|variants| variants.iter().find(|e| e.name == extrinsic_name)) - .flatten() - .ok_or_else(|| Error::ExtrinsicNotSupported(extrinsic_name.to_string()))?; - - Ok(extrinsic.clone()) -} - -#[cfg(test)] -mod tests { - use crate::set_up_api; - - use super::*; - use anyhow::Result; - - // #[tokio::test] - // async fn process_prompt_arguments_works() -> Result<()> { - // let api = set_up_api("ws://127.0.0.1:9944").await?; - // // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; - // let ex = find_extrinsic_by_name(&api, "Nfts", "mint").await?; - // let prompt_args1 = process_prompt_arguments(&api, &ex.fields()[2])?; - - // Ok(()) - // } - - // #[tokio::test] - // async fn process_extrinsic_args_works() -> Result<()> { - // let api = set_up_api("ws://127.0.0.1:9944").await?; - // // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; - // let ex = parse_extrinsic_by_name(&api, "Utility", "batch").await?; - // println!("EXTRINSIC {:?}", ex); - // println!(" ARGS PARSER {:?}", ex.fields); - - // Ok(()) - // } - - // #[tokio::test] - // async fn process_extrinsic_args2_works() -> Result<()> { - // let api = set_up_api("ws://127.0.0.1:9944").await?; - // // let ex = find_extrinsic_by_name(&api, "Balances", "transfer_allow_death").await?; - // let ex = find_extrinsic_by_name(&api, "Nfts", "mint").await?; - // let args_parsed = - // process_extrinsic_args(&api, "System", "remark", vec!["0x11".to_string()]).await?; - // println!(" ARGS PARSER {:?}", args_parsed); - - // Ok(()) - // } + Ok(parsed_params) } diff --git a/crates/pop-parachains/src/call/metadata/params.rs b/crates/pop-parachains/src/call/metadata/params.rs index b85efd597..9c98287ac 100644 --- a/crates/pop-parachains/src/call/metadata/params.rs +++ b/crates/pop-parachains/src/call/metadata/params.rs @@ -1,10 +1,27 @@ // SPDX-License-Identifier: GPL-3.0 -use crate::{errors::Error, Param}; +use crate::errors::Error; use pop_common::format_type; use scale_info::{form::PortableForm, Field, PortableRegistry, TypeDef}; use subxt::{Metadata, OnlineClient, SubstrateConfig}; +/// Describes a parameter of an extrinsic. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Param { + /// The name of the parameter. + pub name: String, + /// The type of the parameter. + pub type_name: String, + /// Nested parameters for composite, variants types or tuples. + pub sub_params: Vec, + /// Indicates if the parameter is optional (`Option`). + pub is_optional: bool, + /// Indicates if the parameter is a Tuple. + pub is_tuple: bool, + /// Indicates if the parameter is a Variant. + pub is_variant: bool, +} + /// Transforms a metadata field into its `Param` representation. /// /// # Arguments @@ -57,8 +74,9 @@ fn type_to_param( return Ok(Param { name, type_name: sub_param.type_name, - is_optional: true, sub_params: sub_param.sub_params, + is_optional: true, + is_tuple: false, is_variant: false, }); } else { @@ -68,11 +86,15 @@ fn type_to_param( // Determine the formatted type name. let type_name = format_type(type_info, registry); match &type_info.type_def { - TypeDef::Primitive(_) => Ok(Param { + TypeDef::Primitive(_) | + TypeDef::Array(_) | + TypeDef::Sequence(_) | + TypeDef::Compact(_) => Ok(Param { name, type_name, - is_optional: false, sub_params: Vec::new(), + is_optional: false, + is_tuple: false, is_variant: false, }), TypeDef::Composite(composite) => { @@ -91,7 +113,14 @@ fn type_to_param( }) .collect::, Error>>()?; - Ok(Param { name, type_name, is_optional: false, sub_params, is_variant: false }) + Ok(Param { + name, + type_name, + sub_params, + is_optional: false, + is_tuple: false, + is_variant: false, + }) }, TypeDef::Variant(variant) => { let variant_params = variant @@ -115,8 +144,9 @@ fn type_to_param( Ok(Param { name: variant_param.name.clone(), type_name: "".to_string(), - is_optional: false, sub_params: variant_sub_params, + is_optional: false, + is_tuple: false, is_variant: true, }) }) @@ -125,19 +155,37 @@ fn type_to_param( Ok(Param { name, type_name, - is_optional: false, sub_params: variant_params, + is_optional: false, + is_tuple: false, is_variant: true, }) }, - TypeDef::Array(_) | TypeDef::Sequence(_) | TypeDef::Tuple(_) | TypeDef::Compact(_) => + TypeDef::Tuple(tuple) => { + let sub_params = tuple + .fields + .iter() + .enumerate() + .map(|(index, field_id)| { + type_to_param( + extrinsic_name, + format!("Index {} of the tuple {}", index.to_string(), name), + registry, + field_id.id, + &None, + ) + }) + .collect::, Error>>()?; + Ok(Param { name, type_name, + sub_params, is_optional: false, - sub_params: Vec::new(), + is_tuple: true, is_variant: false, - }), + }) + }, _ => Err(Error::MetadataParsingError(name)), } } diff --git a/crates/pop-parachains/src/call/metadata/type_parser.rs b/crates/pop-parachains/src/call/metadata/type_parser.rs deleted file mode 100644 index 63b500db0..000000000 --- a/crates/pop-parachains/src/call/metadata/type_parser.rs +++ /dev/null @@ -1,235 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 - -use crate::errors::Error; -use pop_common::parse_account; -use scale_info::{ - form::PortableForm, PortableRegistry, Type, TypeDef, TypeDefArray, TypeDefCompact, - TypeDefComposite, TypeDefPrimitive, TypeDefSequence, TypeDefTuple, TypeDefVariant, -}; -use subxt::dynamic::Value; - -/// Parses an argument string into a `Value` based on its type definition. -/// -/// # Arguments -/// * `arg`: The string representation of the argument to parse. -/// * `ty`: A reference to the `Type` to be formatted. -/// * `registry`: A reference to the `PortableRegistry` to resolve type dependencies. -pub fn process_argument( - arg: &str, - ty: &Type, - registry: &PortableRegistry, -) -> Result { - let type_path = ty.path.segments.join("::"); - match type_path.as_str() { - "Option" => handle_option_type(arg, ty, registry), - // TODO: Handle other account types. - "sp_core::crypto::AccountId32" => Ok(Value::from_bytes(parse_account(arg)?)), - _ => match &ty.type_def { - TypeDef::Primitive(primitive) => primitive.parse(arg, registry), - TypeDef::Composite(composite) => composite.parse(arg, registry), - TypeDef::Variant(variant) => variant.parse(arg, registry), - TypeDef::Tuple(tuple) => tuple.parse(arg, registry), - TypeDef::Sequence(sequence) => sequence.parse(arg, registry), - TypeDef::Array(array) => array.parse(arg, registry), - TypeDef::Compact(compact) => compact.parse(arg, registry), - _ => Err(Error::ParamProcessingError), - }, - } -} - -fn handle_option_type( - arg: &str, - ty: &Type, - registry: &PortableRegistry, -) -> Result { - // Handle Option - if arg.trim() == "None" { - Ok(Value::unnamed_variant("None", vec![])) - } else if arg.trim().starts_with("Some(") && arg.trim().ends_with(')') { - let sub_arg = &arg.trim()[5..arg.trim().len() - 1]; - if let Some(sub_arg_type_id) = ty.type_params.get(0).and_then(|param| param.ty) { - let sub_arg_ty = - registry.resolve(sub_arg_type_id.id).ok_or(Error::ParamProcessingError)?; - Ok(Value::unnamed_variant( - "Some", - vec![process_argument(sub_arg.trim(), sub_arg_ty, registry)?], - )) - } else { - Err(Error::ParamProcessingError) - } - } else { - Err(Error::ParamProcessingError) - } -} - -/// Trait to define how different type definitions parse a string argument into a `Value`. -pub trait TypeParser { - fn parse(&self, arg: &str, registry: &PortableRegistry) -> Result; -} - -impl TypeParser for TypeDefPrimitive { - fn parse(&self, arg: &str, _registry: &PortableRegistry) -> Result { - match self { - TypeDefPrimitive::Bool => - Ok(Value::bool(arg.parse::().map_err(|_| Error::ParamProcessingError)?)), - TypeDefPrimitive::Char => - Ok(Value::char(arg.chars().next().ok_or(Error::ParamProcessingError)?)), - TypeDefPrimitive::Str => Ok(Value::string(arg.to_string())), - TypeDefPrimitive::U8 | - TypeDefPrimitive::U16 | - TypeDefPrimitive::U32 | - TypeDefPrimitive::U64 | - TypeDefPrimitive::U128 | - TypeDefPrimitive::U256 => - Ok(Value::u128(arg.parse::().map_err(|_| Error::ParamProcessingError)?)), - TypeDefPrimitive::I8 | - TypeDefPrimitive::I16 | - TypeDefPrimitive::I32 | - TypeDefPrimitive::I64 | - TypeDefPrimitive::I128 | - TypeDefPrimitive::I256 => - Ok(Value::i128(arg.parse::().map_err(|_| Error::ParamProcessingError)?)), - } - } -} - -impl TypeParser for TypeDefVariant { - fn parse(&self, arg: &str, registry: &PortableRegistry) -> Result { - let input = arg.trim(); - // Parse variant with data (e.g., `Some(value1, value2, ...)`). - if let Some(start) = input.find('(') { - if !input.ends_with(')') { - return Err(Error::ParamProcessingError); - } - let name = input[..start].trim(); - let data_str = &input[start + 1..input.len() - 1]; - let variant_def = self - .variants - .iter() - .find(|v| v.name == name) - .ok_or_else(|| Error::ParamProcessingError)?; - - let mut values = Vec::new(); - for field in variant_def.fields.iter() { - let field_type_id = field.ty.id; - let field_ty = - registry.resolve(field_type_id).ok_or_else(|| Error::ParamProcessingError)?; - // Recursive for the sub parameters of variant type. - let field_value = process_argument(data_str, field_ty, registry)?; - values.push(field_value); - } - - Ok(Value::unnamed_variant(name.to_string(), values)) - } else { - // Parse variant without data (e.g., `None`). - let name = input.to_string(); - let variant_def = self - .variants - .iter() - .find(|v| v.name == name) - .ok_or_else(|| Error::ParamProcessingError)?; - if !variant_def.fields.is_empty() { - return Err(Error::ParamProcessingError); - } - Ok(Value::unnamed_variant(name, vec![])) - } - } -} - -impl TypeParser for TypeDefComposite { - // Example: A composite type is input by the user [true, hello, 42, world], convert into proper - // composite type: {"a": true, "b": "hello", "c": { "d": 42, e: "world" }} - fn parse(&self, arg: &str, registry: &PortableRegistry) -> Result { - let mut values: Vec<&str> = arg.split(',').map(str::trim).collect(); - - let mut field_values = Vec::new(); - for (index, field) in self.fields.iter().enumerate() { - let field_name = field - .name - .clone() - .or_else(|| field.type_name.clone()) - .unwrap_or_else(|| format!("unnamed_field_{}", index)); - - let field_type = registry.resolve(field.ty.id).ok_or(Error::ParamProcessingError)?; - if values.is_empty() { - return Err(Error::ParamProcessingError); - } - let value = match &field_type.type_def { - TypeDef::Composite(nested_composite) => { - if nested_composite.fields.is_empty() { - // Recursive for the sub parameters of nested composite type. - let raw_value = values.remove(0); - process_argument(raw_value, field_type, registry)? - } else { - // Parse nested composite type. - let nested_args_count = nested_composite.fields.len(); - if values.len() < nested_args_count { - return Err(Error::ParamProcessingError); - } - let nested_args: Vec = - values.drain(..nested_args_count).map(String::from).collect(); - let nested_arg_str = nested_args.join(","); - nested_composite.parse(&nested_arg_str, registry)? - } - }, - _ => { - // Recursive for the sub parameters of the composite type. - let raw_value = values.remove(0); - process_argument(raw_value, field_type, registry)? - }, - }; - field_values.push((field_name, value)); - } - Ok(Value::named_composite(field_values)) - } -} - -impl TypeParser for TypeDefSequence { - // Example: [val1, val2, ...] - fn parse(&self, arg: &str, _registry: &PortableRegistry) -> Result { - Ok(Value::from_bytes(arg)) - } -} - -impl TypeParser for TypeDefArray { - // Example: [val1, val2, ...] - fn parse(&self, arg: &str, _registry: &PortableRegistry) -> Result { - Ok(Value::from_bytes(arg)) - } -} - -impl TypeParser for TypeDefTuple { - fn parse(&self, arg: &str, registry: &PortableRegistry) -> Result { - let input = arg.trim(); - // Extract tuple contents from parentheses (e.g., `(value1, value2, ...)`). - let tuple_content = if input.starts_with('(') && input.ends_with(')') { - &input[1..input.len() - 1] - } else { - input - }; - let tuple_values: Vec<&str> = tuple_content.split(',').map(|s| s.trim()).collect(); - if tuple_values.len() != self.fields.len() { - return Err(Error::ParamProcessingError); - } - let mut values = Vec::new(); - for (sub_ty_id, sub_arg) in self.fields.iter().zip(tuple_values.iter()) { - let sub_ty = - registry.resolve(sub_ty_id.id).ok_or_else(|| Error::ParamProcessingError)?; - // Recursive for each value of the tuple. - let value = process_argument(sub_arg.trim(), sub_ty, registry)?; - values.push(value); - } - Ok(Value::unnamed_composite(values)) - } -} - -impl TypeParser for TypeDefCompact { - fn parse(&self, arg: &str, registry: &PortableRegistry) -> Result { - // Parse compact types as their sub type (e.g., `Compact`). - let sub_ty = registry - .resolve(self.type_param.id) - .ok_or_else(|| Error::ParamProcessingError)?; - // Recursive for the inner value. - process_argument(arg, sub_ty, registry) - } -} diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs index a487674c8..fd55d1baa 100644 --- a/crates/pop-parachains/src/call/mod.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -16,13 +16,11 @@ pub async fn set_up_api(url: &str) -> Result, Erro } pub async fn construct_extrinsic( - api: &OnlineClient, pallet_name: &str, extrinsic_name: &str, args: Vec, ) -> Result { - let parsed_args: Vec = - metadata::process_extrinsic_args(api, pallet_name, extrinsic_name, args).await?; + let parsed_args: Vec = metadata::parse_extrinsic_arguments(args).await?; Ok(subxt::dynamic::tx(pallet_name, extrinsic_name, parsed_args)) } diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index 58c76cc49..de4934869 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -19,7 +19,9 @@ pub use call::{ construct_extrinsic, encode_call_data, metadata::{ action::{supported_actions, Action}, - find_extrinsic_by_name, find_pallet_by_name, parse_chain_metadata, Pallet, Param, + find_extrinsic_by_name, find_pallet_by_name, + params::Param, + parse_chain_metadata, Pallet, }, set_up_api, sign_and_submit_extrinsic, }; From 23c311834fca76b69949b27d1ca58aa30425a49b Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Sun, 24 Nov 2024 17:30:10 +0100 Subject: [PATCH 146/211] feat: add Purchase on-demand coretime use cases --- crates/pop-parachains/src/call/metadata/action.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/pop-parachains/src/call/metadata/action.rs b/crates/pop-parachains/src/call/metadata/action.rs index a26e5c9e3..ec089099d 100644 --- a/crates/pop-parachains/src/call/metadata/action.rs +++ b/crates/pop-parachains/src/call/metadata/action.rs @@ -46,6 +46,13 @@ pub enum Action { props(Pallet = "Nfts") )] MintNFT, + #[strum( + serialize = "place_order_allow_death", + message = "place_order_allow_death", + detailed_message = "Purchase on-demand coretime", + props(Pallet = "OnDemand") + )] + PurchaseOnDemandCoretime, #[strum( serialize = "transfer", message = "transfer_allow_death", From bbe810ac5cccc591cd5fa789c798b10bbe6b70ac Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 26 Nov 2024 11:32:18 +0100 Subject: [PATCH 147/211] test: add skip_confirm, move when prompt for the signer and create the integration test --- crates/pop-cli/src/commands/call/parachain.rs | 34 ++++++++----- crates/pop-cli/tests/parachain.rs | 49 +++++++++++++++++++ 2 files changed, 71 insertions(+), 12 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 86f44bc9c..220224fbf 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -33,6 +33,9 @@ pub struct CallParachainCommand { /// - with a password "//Alice///SECRET_PASSWORD" #[clap(name = "suri", long, short, default_value = DEFAULT_URI)] suri: String, + /// Automatically signs and submits the extrinsic without asking for confirmation. + #[clap(short('y'), long)] + skip_confirm: bool, } impl CallParachainCommand { @@ -91,6 +94,11 @@ impl CallParachainCommand { cli.intro("Call a parachain")?; } + // If extrinsic has been specified via command line arguments, return early. + if self.extrinsic.is_some() { + return Ok(set_up_api(self.url.as_str()).await?); + } + // Resolve url. if !repeat && self.url.as_str() == DEFAULT_URL { // Prompt for url. @@ -167,6 +175,15 @@ impl CallParachainCommand { self.args = contract_args; } + // Resolve who is sigining the extrinsic. + if self.suri == DEFAULT_URI { + self.suri = cli + .input("Signer of the extrinsic:") + .placeholder("//Alice") + .default_input("//Alice") + .interact()?; + } + cli.info(self.display())?; Ok(api) } @@ -199,6 +216,7 @@ impl CallParachainCommand { Ok(tx) } + /// Sign an submit an extrinsic. async fn send_extrinsic( &mut self, api: OnlineClient, @@ -206,18 +224,10 @@ impl CallParachainCommand { prompt_to_repeat_call: bool, cli: &mut impl cli::traits::Cli, ) -> Result<()> { - if self.suri == DEFAULT_URI { - self.suri = cli - .input("Signer of the extrinsic:") - .placeholder("//Alice") - .default_input("//Alice") - .interact()?; - } - cli.info(self.display())?; - if !cli - .confirm("Do you want to submit the extrinsic?") - .initial_value(true) - .interact()? + if !self.skip_confirm && + !cli.confirm("Do you want to submit the extrinsic?") + .initial_value(true) + .interact()? { display_message( &format!( diff --git a/crates/pop-cli/tests/parachain.rs b/crates/pop-cli/tests/parachain.rs index 0e17d1017..1cd6b2468 100644 --- a/crates/pop-cli/tests/parachain.rs +++ b/crates/pop-cli/tests/parachain.rs @@ -87,12 +87,61 @@ async fn parachain_lifecycle() -> Result<()> { assert!(content.contains("\"relay_chain\": \"paseo-local\"")); assert!(content.contains("\"protocolId\": \"pop-protocol\"")); + // Custom network to manually set the port + let network_toml_path = temp_parachain_dir.join("network.toml"); + fs::create_dir_all(&temp_parachain_dir)?; + fs::write( + &network_toml_path, + r#"[relaychain] +chain = "paseo-local" + +[[relaychain.nodes]] +name = "alice" +rpc_port = 8833 +validator = true + +[[relaychain.nodes]] +name = "bob" +validator = true + +[[parachains]] +id = 2000 +default_command = "./target/release/parachain-template-node" + +[[parachains.collators]] +name = "collator-01" +"#, + )?; + // pop up parachain -p "./test_parachain" let mut cmd = Cmd::new(cargo_bin("pop")) .current_dir(&temp_parachain_dir) .args(&["up", "parachain", "-f", "./network.toml", "--skip-confirm"]) .spawn() .unwrap(); + + // pop call parachain --pallet System --extrinsic remark --args "0x11" --url + // ws://127.0.0.1:8833/ --suri //Alice --skip-confirm + Command::cargo_bin("pop") + .unwrap() + .args(&[ + "call", + "parachain", + "--pallet", + "System", + "--extrinsic", + "remark", + "--args", + "0x11", + "--url", + "ws://127.0.0.1:8833/", + "--suri", + "//Alice", + "--skip-confirm", + ]) + .assert() + .success(); + // If after 20 secs is still running probably execution is ok, or waiting for user response sleep(Duration::from_secs(20)).await; From e19a69db41935aa368dfbfcfa30f417dc2ce90d1 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Wed, 27 Nov 2024 09:46:27 +0100 Subject: [PATCH 148/211] test: call parachain ui unit test --- crates/pop-cli/src/commands/call/parachain.rs | 75 ++++++++++++++++++- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 220224fbf..afe4dc756 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -59,7 +59,6 @@ impl CallParachainCommand { return Ok(()); }, }; - // TODO: If call_data, go directly here. // Finally execute the call. if let Err(e) = self.send_extrinsic(api, tx, prompt_to_repeat_call, &mut cli::Cli).await { display_message(&e.to_string(), false, &mut cli::Cli)?; @@ -224,8 +223,9 @@ impl CallParachainCommand { prompt_to_repeat_call: bool, cli: &mut impl cli::traits::Cli, ) -> Result<()> { - if !self.skip_confirm && - !cli.confirm("Do you want to submit the extrinsic?") + if !self.skip_confirm + && !cli + .confirm("Do you want to submit the extrinsic?") .initial_value(true) .interact()? { @@ -414,3 +414,72 @@ fn prompt_for_tuple_param( } Ok(format!("({})", tuple_values.join(", "))) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::cli::MockCli; + use url::Url; + + // This test only covers the interactive portion of the call parachain command, without actually + // submitting any extrinsic. + #[tokio::test] + async fn guide_user_to_call_parachain_works() -> Result<()> { + let mut cli = MockCli::new().expect_intro("Call a parachain"); + + let mut call_config = CallParachainCommand { + pallet: None, + extrinsic: Some("Test".to_string()), + args: vec![].to_vec(), + url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, + suri: DEFAULT_URI.to_string(), + skip_confirm: false, + }; + // If the extrinsic has been specified via command line arguments, return early. + call_config.configure(&mut cli, false).await?; + cli.verify()?; + + // Test all process specifying pallet, and see the prompted extrinsics. + call_config.extrinsic = None; + call_config.pallet = Some("System".to_string()); + call_config.url = Url::parse(DEFAULT_URL)?; + + cli = MockCli::new() + .expect_intro("Call a parachain") + .expect_input("Signer of the extrinsic:", "//Bob".into()) + .expect_input("Enter the value for the parameter: remark", "0x11".into()) + .expect_input("Which chain would you like to interact with?", "wss://rpc1.paseo.popnetwork.xyz".into()) + .expect_select::( + "Select the extrinsic to call:", + Some(true), + true, + Some( + [ + ("remark".to_string(), "Make some on-chain remark.Can be executed by every `origin`.".to_string()), + ("set_heap_pages".to_string(), "Set the number of pages in the WebAssembly environment's heap.".to_string()), + ("set_code".to_string(), "Set the new runtime code.".to_string()), + ("set_code_without_checks".to_string(), "Set the new runtime code without doing any checks of the given `code`.Note that runtime upgrades will not run if this is called with a not-increasing specversion!".to_string()), + ("set_storage".to_string(), "Set some items of storage.".to_string()), + ("kill_storage".to_string(), "Kill some items from storage.".to_string()), + ("kill_prefix".to_string(), "Kill all storage items with a key that starts with the given prefix.**NOTE:** We rely on the Root origin to provide us the number of subkeys underthe prefix we are removing to accurately calculate the weight of this function.".to_string()), + ("remark_with_event".to_string(), "Make some on-chain remark and emit event.".to_string()), + ("authorize_upgrade".to_string(), "Authorize an upgrade to a given `code_hash` for the runtime. The runtime can be suppliedlater.This call requires Root origin.".to_string()), + ("authorize_upgrade_without_checks".to_string(), "Authorize an upgrade to a given `code_hash` for the runtime. The runtime can be suppliedlater.WARNING: This authorizes an upgrade that will take place without any safety checks, forexample that the spec name remains the same and that the version number increases. Notrecommended for normal use. Use `authorize_upgrade` instead.This call requires Root origin.".to_string()), + ("apply_authorized_upgrade".to_string(), "Provide the preimage (runtime binary) `code` for an upgrade that has been authorized.If the authorization required a version check, this call will ensure the spec nameremains unchanged and that the spec version has increased.Depending on the runtime's `OnSetCode` configuration, this function may directly applythe new `code` in the same block or attempt to schedule the upgrade.All origins are allowed.".to_string()), + ] + .to_vec(), + ), + 0, // "remark" extrinsic + ).expect_info("pop call parachain --pallet System --extrinsic remark --args \"0x11\" --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Bob"); + + call_config.configure(&mut cli, false).await?; + + assert_eq!(call_config.pallet, Some("System".to_string())); + assert_eq!(call_config.extrinsic, Some("remark".to_string())); + assert_eq!(call_config.args, ["0x11".to_string()].to_vec()); + assert_eq!(call_config.url, Url::parse("wss://rpc1.paseo.popnetwork.xyz")?); + assert_eq!(call_config.suri, "//Bob".to_string()); + assert_eq!(call_config.display(), "pop call parachain --pallet System --extrinsic remark --args \"0x11\" --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Bob"); + cli.verify() + } +} From f471eeef3270efbab9e9fb760aeef1f34cce069d Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 28 Nov 2024 12:03:53 +0100 Subject: [PATCH 149/211] test: pop-cli unit testing --- crates/pop-cli/src/commands/call/parachain.rs | 243 +++++++++++++++++- 1 file changed, 236 insertions(+), 7 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index afe4dc756..505798b2a 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -223,17 +223,13 @@ impl CallParachainCommand { prompt_to_repeat_call: bool, cli: &mut impl cli::traits::Cli, ) -> Result<()> { - if !self.skip_confirm - && !cli - .confirm("Do you want to submit the extrinsic?") + if !self.skip_confirm && + !cli.confirm("Do you want to submit the extrinsic?") .initial_value(true) .interact()? { display_message( - &format!( - "Extrinsic {:?} was not submitted. Operation canceled by the user.", - self.extrinsic - ), + &format!("Extrinsic not submitted. Operation canceled by the user."), false, cli, )?; @@ -482,4 +478,237 @@ mod tests { assert_eq!(call_config.display(), "pop call parachain --pallet System --extrinsic remark --args \"0x11\" --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Bob"); cli.verify() } + + // This test only covers the interactive portion of the call parachain command selecting one of + // the predefined actions, without actually submitting any extrinsic. + #[tokio::test] + async fn guide_user_to_configure_predefined_action_works() -> Result<()> { + let mut call_config = CallParachainCommand { + pallet: None, + extrinsic: None, + args: vec![].to_vec(), + url: Url::parse(DEFAULT_URL)?, + suri: DEFAULT_URI.to_string(), + skip_confirm: false, + }; + + let mut cli = MockCli::new() + .expect_intro("Call a parachain") + .expect_input("Signer of the extrinsic:", "//Bob".into()) + .expect_input("Enter the value for the parameter: para_id", "2000".into()) + .expect_input("Enter the value for the parameter: max_amount", "10000".into()) + .expect_input("Which chain would you like to interact with?", "wss://polkadot-rpc.publicnode.com".into()) + .expect_select::( + "What would you like to do?", + Some(true), + true, + Some( + [ + ("Purchase on-demand coretime".to_string(), "OnDemand".to_string()), + ("Transfer Balance".to_string(), "Balances".to_string()), + ("All".to_string(), "Explore all pallets and extrinsics".to_string()), + ] + .to_vec(), + ), + 0, // "Purchase on-demand coretime" action + ).expect_info("pop call parachain --pallet OnDemand --extrinsic place_order_allow_death --args \"10000\" \"2000\" --url wss://polkadot-rpc.publicnode.com/ --suri //Bob"); + + call_config.configure(&mut cli, false).await?; + + assert_eq!(call_config.pallet, Some("OnDemand".to_string())); + assert_eq!(call_config.extrinsic, Some("place_order_allow_death".to_string())); + assert_eq!(call_config.args, ["10000".to_string(), "2000".to_string()].to_vec()); + assert_eq!(call_config.url, Url::parse("wss://polkadot-rpc.publicnode.com")?); + assert_eq!(call_config.suri, "//Bob".to_string()); + assert_eq!(call_config.display(), "pop call parachain --pallet OnDemand --extrinsic place_order_allow_death --args \"10000\" \"2000\" --url wss://polkadot-rpc.publicnode.com/ --suri //Bob"); + cli.verify() + } + + #[tokio::test] + async fn prepare_extrinsic_works() -> Result<()> { + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let mut call_config = CallParachainCommand { + pallet: None, + extrinsic: None, + args: vec!["0x11".to_string()].to_vec(), + url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, + suri: DEFAULT_URI.to_string(), + skip_confirm: false, + }; + let mut cli = MockCli::new(); + // Error, no extrinsic specified. + assert!( + matches!(call_config.prepare_extrinsic(&api, &mut cli).await, Err(message) if message.to_string().contains("Please specify the extrinsic.")) + ); + call_config.extrinsic = Some("remark".to_string()); + // Error, no pallet specified. + assert!( + matches!(call_config.prepare_extrinsic(&api, &mut cli).await, Err(message) if message.to_string().contains("Please specify the pallet.")) + ); + call_config.pallet = Some("WrongName".to_string()); + // Error, no extrinsic specified. + assert!( + matches!(call_config.prepare_extrinsic(&api, &mut cli).await, Err(message) if message.to_string().contains("Metadata Error: Pallet with name WrongName not found")) + ); + // Success, extrinsic and pallet specified. + cli = MockCli::new().expect_info("Encoded call data: 0x00000411"); + call_config.pallet = Some("System".to_string()); + call_config.prepare_extrinsic(&api, &mut cli).await?; + + cli.verify() + } + + #[tokio::test] + async fn user_cancel_send_extrinsic_works() -> Result<()> { + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let mut call_config = CallParachainCommand { + pallet: Some("System".to_string()), + extrinsic: Some("remark".to_string()), + args: vec!["0x11".to_string()].to_vec(), + url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, + suri: DEFAULT_URI.to_string(), + skip_confirm: false, + }; + let mut cli = MockCli::new() + .expect_confirm("Do you want to submit the extrinsic?", false) + .expect_outro_cancel("Extrinsic not submitted. Operation canceled by the user."); + let tx = call_config.prepare_extrinsic(&api, &mut cli).await?; + call_config.send_extrinsic(api, tx, false, &mut cli).await?; + call_config.extrinsic = Some("remark".to_string()); + + cli.verify() + } + + #[test] + fn reset_for_new_call_works() -> Result<()> { + let mut call_config = CallParachainCommand { + pallet: Some("System".to_string()), + extrinsic: Some("remark".to_string()), + args: vec!["0x11".to_string()].to_vec(), + url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, + suri: DEFAULT_URI.to_string(), + skip_confirm: false, + }; + call_config.reset_for_new_call(); + assert_eq!(call_config.pallet, None); + assert_eq!(call_config.extrinsic, None); + assert_eq!(call_config.args.len(), 0); + Ok(()) + } + + #[test] + fn display_message_works() -> Result<()> { + let mut cli = MockCli::new().expect_outro(&"Call completed successfully!"); + display_message("Call completed successfully!", true, &mut cli)?; + cli.verify()?; + let mut cli = MockCli::new().expect_outro_cancel("Call failed."); + display_message("Call failed.", false, &mut cli)?; + cli.verify() + } + + #[tokio::test] + async fn prompt_predefined_actions_works() -> Result<()> { + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let pallets = parse_chain_metadata(&api).await?; + let mut cli = MockCli::new().expect_select::( + "What would you like to do?", + Some(true), + true, + Some( + [ + ("Create an Asset".to_string(), "Assets".to_string()), + ("Mint an Asset".to_string(), "Assets".to_string()), + ("Create an NFT Collection".to_string(), "Nfts".to_string()), + ("Mint an NFT".to_string(), "Nfts".to_string()), + ("Transfer Balance".to_string(), "Balances".to_string()), + ("All".to_string(), "Explore all pallets and extrinsics".to_string()), + ] + .to_vec(), + ), + 1, // "Mint an Asset" action + ); + let action = prompt_predefined_actions(&pallets, &mut cli).await?; + assert_eq!(action, Some(Action::MintAsset)); + cli.verify() + } + + #[tokio::test] + async fn prompt_for_param_works() -> Result<()> { + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let pallets = parse_chain_metadata(&api).await?; + // Using NFT mint extrinsic to test the majority of subfunctions + let extrinsic = find_extrinsic_by_name(&pallets, "Nfts", "mint").await?; + let mut cli = MockCli::new() + .expect_input("Enter the value for the parameter: mint_price", "1000".into()) + .expect_input( + "Enter the value for the parameter: Id", + "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty".into(), + ) + .expect_input("Enter the value for the parameter: item", "0".into()) + .expect_input("Enter the value for the parameter: collection", "0".into()) + .expect_select::( + "Select the value for the parameter: mint_to", + Some(true), + true, + Some( + [ + ("Id".to_string(), "".to_string()), + ("Index".to_string(), "".to_string()), + ("Raw".to_string(), "".to_string()), + ("Address32".to_string(), "".to_string()), + ("Address20".to_string(), "".to_string()), + ] + .to_vec(), + ), + 0, // "Id" action + ) + .expect_confirm( + "Do you want to provide a value for the optional parameter: mint_price?", + true, + ) + .expect_confirm( + "Do you want to provide a value for the optional parameter: owned_item?", + false, + ) + .expect_confirm( + "Do you want to provide a value for the optional parameter: witness_data?", + true, + ); + // Test all the extrinsic params + let mut params: Vec = Vec::new(); + for param in extrinsic.params { + params.push(prompt_for_param(&api, &mut cli, ¶m)?); + } + assert_eq!(params.len(), 4); + assert_eq!(params[0], "0".to_string()); // collection: test primitive + assert_eq!(params[1], "0".to_string()); // item: test primitive + assert_eq!(params[2], "Id(5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty)".to_string()); // mint_to: test variant + assert_eq!(params[3], "Some({owned_item: None(), mint_price: Some(1000)})".to_string()); // witness_data: test composite + cli.verify()?; + + // Using Scheduler set_retry extrinsic to test the tuple params + let extrinsic = find_extrinsic_by_name(&pallets, "Scheduler", "set_retry").await?; + let mut cli = MockCli::new() + .expect_input("Enter the value for the parameter: period", "0".into()) + .expect_input("Enter the value for the parameter: retries", "0".into()) + .expect_input( + "Enter the value for the parameter: Index 1 of the tuple task", + "0".into(), + ) + .expect_input( + "Enter the value for the parameter: Index 0 of the tuple task", + "0".into(), + ); + + // Test all the extrinsic params + let mut params: Vec = Vec::new(); + for param in extrinsic.params { + params.push(prompt_for_param(&api, &mut cli, ¶m)?); + } + assert_eq!(params.len(), 3); + assert_eq!(params[0], "(0, 0)".to_string()); // task: test tuples + assert_eq!(params[1], "0".to_string()); // retries: test primitive + assert_eq!(params[2], "0".to_string()); // period: test primitive + cli.verify() + } } From 0344ea256edcc0b06db51327ccd57f76fe76dfd3 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 28 Nov 2024 12:34:02 +0100 Subject: [PATCH 150/211] test: pop-common unit tests --- crates/pop-common/src/metadata.rs | 62 ++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/crates/pop-common/src/metadata.rs b/crates/pop-common/src/metadata.rs index 4ea58d806..7aae66132 100644 --- a/crates/pop-common/src/metadata.rs +++ b/crates/pop-common/src/metadata.rs @@ -30,7 +30,6 @@ pub fn format_type(ty: &Type, registry: &PortableRegistry) -> Stri .collect(); name = format!("{name}<{}>", params.join(",")); } - name = format!( "{name}{}", match &ty.type_def { @@ -155,3 +154,64 @@ pub fn format_type(ty: &Type, registry: &PortableRegistry) -> Stri name } + +#[cfg(test)] +mod tests { + use super::*; + + use anyhow::Result; + use subxt::{OnlineClient, SubstrateConfig}; + + #[tokio::test] + async fn format_type_works() -> Result<()> { + let api = + OnlineClient::::from_url("wss://rpc1.paseo.popnetwork.xyz").await?; + let metadata = api.metadata(); + let registry = metadata.types(); + // Extrinsic Nfts::mint to test the majority of expresions. + let mut extrinsic = + metadata.pallet_by_name("Nfts").unwrap().call_variant_by_name("mint").unwrap(); + let mut types_formatted = Vec::new(); + for field in &extrinsic.fields { + let type_info = registry.resolve(field.ty.id).unwrap(); + types_formatted.push(format_type(&type_info, ®istry)); + } + assert_eq!(types_formatted.len(), 4); + assert_eq!(types_formatted[0], "u32"); // collection + assert_eq!(types_formatted[1], "u32"); // item + assert_eq!(types_formatted[2], "MultiAddress: Id(AccountId32 ([u8;32])), Index(Compact<()>), Raw([u8]), Address32([u8;32]), Address20([u8;20])"); // mint_to + assert_eq!(types_formatted[3], "Option { owned_item: Option, mint_price: Option }>: None, Some(MintWitness { owned_item: Option, mint_price: Option })"); // witness_data + + // Extrinsic Sytem::remark to testing sequences. + extrinsic = metadata + .pallet_by_name("System") + .unwrap() + .call_variant_by_name("remark") + .unwrap(); + types_formatted.clear(); + for field in &extrinsic.fields { + let type_info = registry.resolve(field.ty.id).unwrap(); + types_formatted.push(format_type(&type_info, ®istry)); + } + assert_eq!(types_formatted.len(), 1); + assert_eq!(types_formatted[0], "[u8]"); // remark + + // Extrinsic Scheduler::set_retry to test tuples. + extrinsic = metadata + .pallet_by_name("Scheduler") + .unwrap() + .call_variant_by_name("set_retry") + .unwrap(); + types_formatted.clear(); + for field in &extrinsic.fields { + let type_info = registry.resolve(field.ty.id).unwrap(); + types_formatted.push(format_type(&type_info, ®istry)); + } + assert_eq!(types_formatted.len(), 3); + assert_eq!(types_formatted[0], "(u32,u32)"); // task + assert_eq!(types_formatted[1], "u8"); // retries + assert_eq!(types_formatted[2], "u32"); // period + + Ok(()) + } +} From 5e4ae23a04a75d27ef18613f0ee7b7fddc61c9e9 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 28 Nov 2024 15:17:20 +0100 Subject: [PATCH 151/211] test: parse metadata unit tests --- crates/pop-common/src/metadata.rs | 1 - .../src/call/metadata/action.rs | 77 +++++++++++ .../pop-parachains/src/call/metadata/mod.rs | 123 ++++++++++++++++++ 3 files changed, 200 insertions(+), 1 deletion(-) diff --git a/crates/pop-common/src/metadata.rs b/crates/pop-common/src/metadata.rs index 7aae66132..82851c8f7 100644 --- a/crates/pop-common/src/metadata.rs +++ b/crates/pop-common/src/metadata.rs @@ -158,7 +158,6 @@ pub fn format_type(ty: &Type, registry: &PortableRegistry) -> Stri #[cfg(test)] mod tests { use super::*; - use anyhow::Result; use subxt::{OnlineClient, SubstrateConfig}; diff --git a/crates/pop-parachains/src/call/metadata/action.rs b/crates/pop-parachains/src/call/metadata/action.rs index ec089099d..73e152b9e 100644 --- a/crates/pop-parachains/src/call/metadata/action.rs +++ b/crates/pop-parachains/src/call/metadata/action.rs @@ -14,6 +14,7 @@ use strum_macros::{AsRefStr, Display, EnumMessage, EnumProperty, EnumString, Var EnumString, EnumProperty, Eq, + Hash, PartialEq, VariantArray, )] @@ -96,3 +97,79 @@ pub async fn supported_actions(pallets: &[Pallet]) -> Vec { } actions } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{parse_chain_metadata, set_up_api}; + use anyhow::Result; + use std::collections::HashMap; + + #[test] + fn action_descriptions_are_correct() { + let descriptions = HashMap::from([ + (Action::CreateAsset, "Create an Asset"), + (Action::MintAsset, "Mint an Asset"), + (Action::CreateCollection, "Create an NFT Collection"), + (Action::MintNFT, "Mint an NFT"), + (Action::PurchaseOnDemandCoretime, "Purchase on-demand coretime"), + (Action::Transfer, "Transfer Balance"), + ]); + + for action in Action::VARIANTS.iter() { + assert_eq!(&action.description(), descriptions.get(action).unwrap()); + } + } + + #[test] + fn pallet_names_are_correct() { + let pallets = HashMap::from([ + (Action::CreateAsset, "Assets"), + (Action::MintAsset, "Assets"), + (Action::CreateCollection, "Nfts"), + (Action::MintNFT, "Nfts"), + (Action::PurchaseOnDemandCoretime, "OnDemand"), + (Action::Transfer, "Balances"), + ]); + + for action in Action::VARIANTS.iter() { + assert_eq!(&action.pallet_name(), pallets.get(action).unwrap(),); + } + } + + #[test] + fn extrinsic_names_are_correct() { + let pallets = HashMap::from([ + (Action::CreateAsset, "create"), + (Action::MintAsset, "mint"), + (Action::CreateCollection, "create"), + (Action::MintNFT, "mint"), + (Action::PurchaseOnDemandCoretime, "place_order_allow_death"), + (Action::Transfer, "transfer_allow_death"), + ]); + + for action in Action::VARIANTS.iter() { + assert_eq!(&action.extrinsic_name(), pallets.get(action).unwrap(),); + } + } + + #[tokio::test] + async fn supported_actions_works() -> Result<()> { + // Test Pop Parachain. + let mut api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let mut actions = supported_actions(&parse_chain_metadata(&api).await?).await; + assert_eq!(actions.len(), 5); + assert_eq!(actions[0], Action::CreateAsset); + assert_eq!(actions[1], Action::MintAsset); + assert_eq!(actions[2], Action::CreateCollection); + assert_eq!(actions[3], Action::MintNFT); + assert_eq!(actions[4], Action::Transfer); + // Test Polkadot Relay Chain. + api = set_up_api("wss://polkadot-rpc.publicnode.com").await?; + actions = supported_actions(&parse_chain_metadata(&api).await?).await; + assert_eq!(actions.len(), 2); + assert_eq!(actions[0], Action::PurchaseOnDemandCoretime); + assert_eq!(actions[1], Action::Transfer); + Ok(()) + } +} diff --git a/crates/pop-parachains/src/call/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs index e7f863d0a..26890a7d1 100644 --- a/crates/pop-parachains/src/call/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -147,3 +147,126 @@ pub async fn parse_extrinsic_arguments(raw_params: Vec) -> Result Result<()> { + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let pallets = parse_chain_metadata(&api).await?; + // Test the first pallet is parsed correctly + let first_pallet = pallets.first().unwrap(); + assert_eq!(first_pallet.name, "System"); + assert_eq!(first_pallet.docs, ""); + assert_eq!(first_pallet.extrinsics.len(), 11); + let first_extrinsic = first_pallet.extrinsics.first().unwrap(); + assert_eq!(first_extrinsic.name, "remark"); + assert_eq!( + first_extrinsic.docs, + "Make some on-chain remark.Can be executed by every `origin`." + ); + assert!(first_extrinsic.is_supported); + assert_eq!(first_extrinsic.params.first().unwrap().name, "remark"); + assert_eq!(first_extrinsic.params.first().unwrap().type_name, "[u8]"); + assert_eq!(first_extrinsic.params.first().unwrap().sub_params.len(), 0); + assert!(!first_extrinsic.params.first().unwrap().is_optional); + assert!(!first_extrinsic.params.first().unwrap().is_tuple); + assert!(!first_extrinsic.params.first().unwrap().is_variant); + Ok(()) + } + + #[tokio::test] + async fn find_pallet_by_name_works() -> Result<()> { + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let pallets = parse_chain_metadata(&api).await?; + assert!(matches!( + find_pallet_by_name(&pallets, "WrongName").await, + Err(Error::PalletNotFound(pallet)) if pallet == "WrongName".to_string())); + let pallet = find_pallet_by_name(&pallets, "Balances").await?; + assert_eq!(pallet.name, "Balances"); + assert_eq!(pallet.extrinsics.len(), 9); + Ok(()) + } + + #[tokio::test] + async fn find_extrinsic_by_name_works() -> Result<()> { + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let pallets = parse_chain_metadata(&api).await?; + assert!(matches!( + find_extrinsic_by_name(&pallets, "WrongName", "wrong_extrinsic").await, + Err(Error::PalletNotFound(pallet)) if pallet == "WrongName".to_string())); + assert!(matches!( + find_extrinsic_by_name(&pallets, "Balances", "wrong_extrinsic").await, + Err(Error::ExtrinsicNotSupported(extrinsic)) if extrinsic == "wrong_extrinsic".to_string())); + let extrinsic = find_extrinsic_by_name(&pallets, "Balances", "force_transfer").await?; + assert_eq!(extrinsic.name, "force_transfer"); + assert_eq!(extrinsic.docs, "Exactly as `transfer_allow_death`, except the origin must be root and the source accountmay be specified."); + assert_eq!(extrinsic.is_supported, true); + assert_eq!(extrinsic.params.len(), 3); + Ok(()) + } + + #[tokio::test] + async fn parse_extrinsic_arguments_works() -> Result<()> { + // Values for testing from: https://docs.rs/scale-value/0.18.0/scale_value/stringify/fn.from_str.html + // and https://docs.rs/scale-value/0.18.0/scale_value/stringify/fn.from_str_custom.html + let args = [ + "1".to_string(), + "-1".to_string(), + "true".to_string(), + "'a'".to_string(), + "\"hi\"".to_string(), + "{ a: true, b: \"hello\" }".to_string(), + "MyVariant { a: true, b: \"hello\" }".to_string(), + "<0101>".to_string(), + "(1,2,0x030405)".to_string(), + r#"{ + name: "Alice", + address: 5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty + }"# + .to_string(), + ] + .to_vec(); + let addr: Vec<_> = + hex::decode("8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48") + .unwrap() + .into_iter() + .map(|b| Value::u128(b as u128)) + .collect(); + assert_eq!( + parse_extrinsic_arguments(args).await?, + [ + Value::u128(1), + Value::i128(-1), + Value::bool(true), + Value::char('a'), + Value::string("hi"), + Value::named_composite(vec![ + ("a", Value::bool(true)), + ("b", Value::string("hello")) + ]), + Value::named_variant( + "MyVariant", + vec![("a", Value::bool(true)), ("b", Value::string("hello"))] + ), + Value::bit_sequence(scale_bits::Bits::from_iter([false, true, false, true])), + Value::unnamed_composite(vec![ + Value::u128(1), + Value::u128(2), + Value::unnamed_composite(vec![Value::u128(3), Value::u128(4), Value::u128(5),]) + ]), + Value::named_composite(vec![ + ("name", Value::string("Alice")), + ("address", Value::unnamed_composite(addr)) + ]) + ] + ); + Ok(()) + } +} From d2170bab0d7e0cee1ebac32ce086ac8d092f0683 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 28 Nov 2024 16:58:12 +0100 Subject: [PATCH 152/211] test: refactor and test processing parameters --- .../pop-parachains/src/call/metadata/mod.rs | 15 ++-- .../src/call/metadata/params.rs | 77 ++++++++++++++++--- crates/pop-parachains/src/errors.rs | 4 +- 3 files changed, 77 insertions(+), 19 deletions(-) diff --git a/crates/pop-parachains/src/call/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs index 26890a7d1..7d30bb9b3 100644 --- a/crates/pop-parachains/src/call/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -57,15 +57,15 @@ pub async fn parse_chain_metadata( let params = { let mut parsed_params = Vec::new(); for field in &variant.fields { - match params::field_to_param(api, &variant.name, field) { + match params::field_to_param(api, field) { Ok(param) => parsed_params.push(param), - Err(Error::ExtrinsicNotSupported(_)) => { - // Unsupported extrinsic due to complex types + Err(_) => { + // If an error occurs while parsing the values, mark the + // extrinsic as unsupported rather than error. is_supported = false; parsed_params.clear(); break; }, - Err(e) => return Err(e), } } parsed_params @@ -126,7 +126,7 @@ pub async fn find_extrinsic_by_name( if let Some(extrinsic) = pallet.extrinsics.iter().find(|&e| e.name == extrinsic_name) { return Ok(extrinsic.clone()); } else { - return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); + return Err(Error::ExtrinsicNotSupported); } } @@ -202,8 +202,9 @@ mod tests { find_extrinsic_by_name(&pallets, "WrongName", "wrong_extrinsic").await, Err(Error::PalletNotFound(pallet)) if pallet == "WrongName".to_string())); assert!(matches!( - find_extrinsic_by_name(&pallets, "Balances", "wrong_extrinsic").await, - Err(Error::ExtrinsicNotSupported(extrinsic)) if extrinsic == "wrong_extrinsic".to_string())); + find_extrinsic_by_name(&pallets, "Balances", "wrong_extrinsic").await, + Err(Error::ExtrinsicNotSupported) + )); let extrinsic = find_extrinsic_by_name(&pallets, "Balances", "force_transfer").await?; assert_eq!(extrinsic.name, "force_transfer"); assert_eq!(extrinsic.docs, "Exactly as `transfer_allow_death`, except the origin must be root and the source accountmay be specified."); diff --git a/crates/pop-parachains/src/call/metadata/params.rs b/crates/pop-parachains/src/call/metadata/params.rs index 9c98287ac..cec262768 100644 --- a/crates/pop-parachains/src/call/metadata/params.rs +++ b/crates/pop-parachains/src/call/metadata/params.rs @@ -29,13 +29,12 @@ pub struct Param { /// * `field`: A reference to a metadata field of the extrinsic. pub fn field_to_param( api: &OnlineClient, - extrinsic_name: &str, field: &Field, ) -> Result { let metadata: Metadata = api.metadata(); let registry = metadata.types(); let name = field.name.clone().unwrap_or("Unnamed".to_string()); //It can be unnamed field - type_to_param(extrinsic_name, name, registry, field.ty.id, &field.type_name) + type_to_param(name, registry, field.ty.id, &field.type_name) } /// Converts a type's metadata into a `Param` representation. @@ -46,7 +45,6 @@ pub fn field_to_param( /// * `type_id`: The ID of the type to be converted. /// * `type_name`: An optional descriptive name for the type. fn type_to_param( - extrinsic_name: &str, name: String, registry: &PortableRegistry, type_id: u32, @@ -55,7 +53,7 @@ fn type_to_param( let type_info = registry.resolve(type_id).ok_or(Error::MetadataParsingError(name.clone()))?; if let Some(last_segment) = type_info.path.segments.last() { if last_segment == "RuntimeCall" { - return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); + return Err(Error::ExtrinsicNotSupported); } } for param in &type_info.type_params { @@ -63,14 +61,13 @@ fn type_to_param( param.name == "Vec" || param.name == "Vec<::RuntimeCall>" { - return Err(Error::ExtrinsicNotSupported(extrinsic_name.to_string())); + return Err(Error::ExtrinsicNotSupported); } } if type_info.path.segments == ["Option"] { if let Some(sub_type_id) = type_info.type_params.get(0).and_then(|param| param.ty) { // Recursive for the sub parameters - let sub_param = - type_to_param(extrinsic_name, name.clone(), registry, sub_type_id.id, type_name)?; + let sub_param = type_to_param(name.clone(), registry, sub_type_id.id, type_name)?; return Ok(Param { name, type_name: sub_param.type_name, @@ -104,7 +101,6 @@ fn type_to_param( .map(|field| { // Recursive for the sub parameters of composite type. type_to_param( - extrinsic_name, field.name.clone().unwrap_or(name.clone()), registry, field.ty.id, @@ -133,7 +129,6 @@ fn type_to_param( .map(|field| { // Recursive for the sub parameters of variant type. type_to_param( - extrinsic_name, field.name.clone().unwrap_or(variant_param.name.clone()), registry, field.ty.id, @@ -168,7 +163,6 @@ fn type_to_param( .enumerate() .map(|(index, field_id)| { type_to_param( - extrinsic_name, format!("Index {} of the tuple {}", index.to_string(), name), registry, field_id.id, @@ -190,3 +184,66 @@ fn type_to_param( } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::set_up_api; + use anyhow::Result; + + #[tokio::test] + async fn field_to_param_works() -> Result<()> { + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let metadata = api.metadata(); + // Test a supported extrinsic + let extrinsic = metadata + .pallet_by_name("Balances") + .unwrap() + .call_variant_by_name("force_transfer") + .unwrap(); + let mut params = Vec::new(); + for field in &extrinsic.fields { + params.push(field_to_param(&api, field)?) + } + assert_eq!(params.len(), 3); + assert_eq!(params.first().unwrap().name, "source"); + assert_eq!(params.first().unwrap().type_name, "MultiAddress: Id(AccountId32 ([u8;32])), Index(Compact<()>), Raw([u8]), Address32([u8;32]), Address20([u8;20])"); + assert_eq!(params.first().unwrap().sub_params.len(), 5); + assert_eq!(params.first().unwrap().sub_params.first().unwrap().name, "Id"); + assert_eq!(params.first().unwrap().sub_params.first().unwrap().type_name, ""); + assert_eq!( + params + .first() + .unwrap() + .sub_params + .first() + .unwrap() + .sub_params + .first() + .unwrap() + .name, + "Id" + ); + assert_eq!( + params + .first() + .unwrap() + .sub_params + .first() + .unwrap() + .sub_params + .first() + .unwrap() + .type_name, + "AccountId32 ([u8;32])" + ); + // Test a extrinsic not supported + let extrinsic = + metadata.pallet_by_name("Sudo").unwrap().call_variant_by_name("sudo").unwrap(); + assert!(matches!( + field_to_param(&api, &extrinsic.fields.first().unwrap()), + Err(Error::ExtrinsicNotSupported) + )); + Ok(()) + } +} diff --git a/crates/pop-parachains/src/errors.rs b/crates/pop-parachains/src/errors.rs index 244b70aa2..9ca07a8ac 100644 --- a/crates/pop-parachains/src/errors.rs +++ b/crates/pop-parachains/src/errors.rs @@ -18,8 +18,8 @@ pub enum Error { CurrentDirAccess, #[error("Failed to parse the endowment value")] EndowmentError, - #[error("The extrinsic {0} is not supported")] - ExtrinsicNotSupported(String), + #[error("The extrinsic is not supported")] + ExtrinsicNotSupported, #[error("Failed decode a value")] FromHexError(#[from] hex::FromHexError), #[error("IO error: {0}")] From 6e1050c5fc508a60fd13aa99de72d501260693d5 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 28 Nov 2024 17:18:54 +0100 Subject: [PATCH 153/211] test: comments and unit test in call functions --- crates/pop-parachains/src/call/mod.rs | 71 +++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs index fd55d1baa..a347fea22 100644 --- a/crates/pop-parachains/src/call/mod.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -10,11 +10,21 @@ use subxt::{ pub mod metadata; +/// Sets up an OnlineClient instance for connecting to a blockchain. +/// +/// # Arguments +/// * `url` - Endpoint of the node. pub async fn set_up_api(url: &str) -> Result, Error> { let api = OnlineClient::::from_url(url).await?; Ok(api) } +/// Constructs a dynamic extrinsic payload for a specified pallet and extrinsic. +/// +/// # Arguments +/// * `pallet_name` - The name of the pallet containing the extrinsic. +/// * `extrinsic_name` - The specific extrinsic name within the pallet. +/// * `args` - A vector of string arguments to be passed to the extrinsic. pub async fn construct_extrinsic( pallet_name: &str, extrinsic_name: &str, @@ -24,6 +34,12 @@ pub async fn construct_extrinsic( Ok(subxt::dynamic::tx(pallet_name, extrinsic_name, parsed_args)) } +/// Signs and submits a given extrinsic to the blockchain. +/// +/// # Arguments +/// * `api` - Reference to an `OnlineClient` connected to the chain. +/// * `tx` - The transaction to be signed and submitted. +/// * `suri` - The secret URI (e.g., mnemonic or private key) for signing the extrinsic. pub async fn sign_and_submit_extrinsic( api: OnlineClient, tx: DynamicPayload, @@ -41,6 +57,11 @@ pub async fn sign_and_submit_extrinsic( Ok(format!("{:?}", result.extrinsic_hash())) } +/// Encodes the call data for a given extrinsic into a hexadecimal string. +/// +/// # Arguments +/// * `api` - Reference to an `OnlineClient` connected to the chain. +/// * `tx` - The transaction whose call data will be encoded and returned. pub fn encode_call_data( api: &OnlineClient, tx: &DynamicPayload, @@ -48,3 +69,53 @@ pub fn encode_call_data( let call_data = tx.encode_call_data(&api.metadata())?; Ok(format!("0x{}", hex::encode(call_data))) } + +#[cfg(test)] +mod tests { + use super::*; + + use crate::set_up_api; + use anyhow::Result; + + #[tokio::test] + async fn set_up_api_works() -> Result<()> { + assert!(matches!(set_up_api("wss://wronguri.xyz").await, Err(Error::SubxtError(_)))); + set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + Ok(()) + } + + #[tokio::test] + async fn construct_extrinsic_works() -> Result<()> { + // Wrong parameters + assert!(matches!( + construct_extrinsic( + "Balances", + "transfer_allow_death", + vec!["Bob".to_string(), "100".to_string()], + ) + .await, + Err(Error::ParamProcessingError) + )); + // Valid parameters + let extrinsic = construct_extrinsic( + "Balances", + "transfer_allow_death", + vec![ + "Id(5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty)".to_string(), + "100".to_string(), + ], + ) + .await?; + assert_eq!(extrinsic.call_name(), "transfer_allow_death"); + assert_eq!(extrinsic.pallet_name(), "Balances"); + Ok(()) + } + + #[tokio::test] + async fn encode_call_data_works() -> Result<()> { + let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let extrinsic = construct_extrinsic("System", "remark", vec!["0x11".to_string()]).await?; + assert_eq!(encode_call_data(&api, &extrinsic)?, "0x00000411"); + Ok(()) + } +} From cba8ef329baec657c95745ee448b0846b43c8aec Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 29 Nov 2024 12:32:40 +0100 Subject: [PATCH 154/211] fix: clippy warnings --- crates/pop-cli/src/commands/call/contract.rs | 2 +- crates/pop-cli/src/commands/call/parachain.rs | 17 ++++----- .../pop-parachains/src/call/metadata/mod.rs | 4 +-- .../src/call/metadata/params.rs | 36 ++++++++----------- 4 files changed, 26 insertions(+), 33 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index d9e604b1e..d08908981 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -200,7 +200,7 @@ impl CallContractCommand { }; // Resolve contract address. - if let None = self.contract { + if self.contract.is_none() { // Prompt for contract address. let contract_address: String = cli .input("Paste the on-chain contract address:") diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 505798b2a..ed48c1b11 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -223,13 +223,14 @@ impl CallParachainCommand { prompt_to_repeat_call: bool, cli: &mut impl cli::traits::Cli, ) -> Result<()> { - if !self.skip_confirm && - !cli.confirm("Do you want to submit the extrinsic?") + if !self.skip_confirm + && !cli + .confirm("Do you want to submit the extrinsic?") .initial_value(true) .interact()? { display_message( - &format!("Extrinsic not submitted. Operation canceled by the user."), + "Extrinsic not submitted. Operation canceled by the user.", false, cli, )?; @@ -241,7 +242,7 @@ impl CallParachainCommand { .await .map_err(|err| anyhow!("{}", format!("{err:?}")))?; - spinner.stop(&format!("Extrinsic submitted with hash: {:?}", result)); + spinner.stop(format!("Extrinsic submitted with hash: {:?}", result)); // Prompt for any additional calls. if !prompt_to_repeat_call { @@ -286,7 +287,7 @@ async fn prompt_predefined_actions( cli: &mut impl cli::traits::Cli, ) -> Result> { let mut predefined_action = cli.select("What would you like to do?"); - for action in supported_actions(&pallets).await { + for action in supported_actions(pallets).await { predefined_action = predefined_action.item( Some(action.clone()), action.description(), @@ -385,13 +386,13 @@ fn prompt_for_composite_param( // sub_params: [Param { name: "Id", type_name: "[u8;32]", is_optional: false, sub_params: // [], is_variant: false }], is_variant: false } if param.sub_params.len() == 1 && param.name == param.sub_params[0].name { - field_values.push(format!("{}", field_value)); + field_values.push(field_value); } else { field_values.push(format!("{}: {}", field_arg.name, field_value)); } } if param.sub_params.len() == 1 && param.name == param.sub_params[0].name { - Ok(format!("{}", field_values.join(", "))) + Ok(field_values.join(", ").to_string()) } else { Ok(format!("{{{}}}", field_values.join(", "))) } @@ -404,7 +405,7 @@ fn prompt_for_tuple_param( param: &Param, ) -> Result { let mut tuple_values = Vec::new(); - for (_index, tuple_param) in param.sub_params.iter().enumerate() { + for tuple_param in param.sub_params.iter() { let tuple_value = prompt_for_param(api, cli, tuple_param)?; tuple_values.push(tuple_value); } diff --git a/crates/pop-parachains/src/call/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs index 7d30bb9b3..3d38f249e 100644 --- a/crates/pop-parachains/src/call/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -124,9 +124,9 @@ pub async fn find_extrinsic_by_name( ) -> Result { let pallet = find_pallet_by_name(pallets, pallet_name).await?; if let Some(extrinsic) = pallet.extrinsics.iter().find(|&e| e.name == extrinsic_name) { - return Ok(extrinsic.clone()); + Ok(extrinsic.clone()) } else { - return Err(Error::ExtrinsicNotSupported); + Err(Error::ExtrinsicNotSupported) } } diff --git a/crates/pop-parachains/src/call/metadata/params.rs b/crates/pop-parachains/src/call/metadata/params.rs index cec262768..073814cd0 100644 --- a/crates/pop-parachains/src/call/metadata/params.rs +++ b/crates/pop-parachains/src/call/metadata/params.rs @@ -34,7 +34,7 @@ pub fn field_to_param( let metadata: Metadata = api.metadata(); let registry = metadata.types(); let name = field.name.clone().unwrap_or("Unnamed".to_string()); //It can be unnamed field - type_to_param(name, registry, field.ty.id, &field.type_name) + type_to_param(name, registry, field.ty.id) } /// Converts a type's metadata into a `Param` representation. @@ -44,12 +44,7 @@ pub fn field_to_param( /// * `registry`: A reference to the `PortableRegistry` to resolve type dependencies. /// * `type_id`: The ID of the type to be converted. /// * `type_name`: An optional descriptive name for the type. -fn type_to_param( - name: String, - registry: &PortableRegistry, - type_id: u32, - type_name: &Option, -) -> Result { +fn type_to_param(name: String, registry: &PortableRegistry, type_id: u32) -> Result { let type_info = registry.resolve(type_id).ok_or(Error::MetadataParsingError(name.clone()))?; if let Some(last_segment) = type_info.path.segments.last() { if last_segment == "RuntimeCall" { @@ -57,25 +52,25 @@ fn type_to_param( } } for param in &type_info.type_params { - if param.name == "RuntimeCall" || - param.name == "Vec" || - param.name == "Vec<::RuntimeCall>" + if param.name == "RuntimeCall" + || param.name == "Vec" + || param.name == "Vec<::RuntimeCall>" { return Err(Error::ExtrinsicNotSupported); } } if type_info.path.segments == ["Option"] { - if let Some(sub_type_id) = type_info.type_params.get(0).and_then(|param| param.ty) { + if let Some(sub_type_id) = type_info.type_params.first().and_then(|param| param.ty) { // Recursive for the sub parameters - let sub_param = type_to_param(name.clone(), registry, sub_type_id.id, type_name)?; - return Ok(Param { + let sub_param = type_to_param(name.clone(), registry, sub_type_id.id)?; + Ok(Param { name, type_name: sub_param.type_name, sub_params: sub_param.sub_params, is_optional: true, is_tuple: false, is_variant: false, - }); + }) } else { Err(Error::MetadataParsingError(name)) } @@ -83,10 +78,10 @@ fn type_to_param( // Determine the formatted type name. let type_name = format_type(type_info, registry); match &type_info.type_def { - TypeDef::Primitive(_) | - TypeDef::Array(_) | - TypeDef::Sequence(_) | - TypeDef::Compact(_) => Ok(Param { + TypeDef::Primitive(_) + | TypeDef::Array(_) + | TypeDef::Sequence(_) + | TypeDef::Compact(_) => Ok(Param { name, type_name, sub_params: Vec::new(), @@ -104,7 +99,6 @@ fn type_to_param( field.name.clone().unwrap_or(name.clone()), registry, field.ty.id, - &field.type_name, ) }) .collect::, Error>>()?; @@ -132,7 +126,6 @@ fn type_to_param( field.name.clone().unwrap_or(variant_param.name.clone()), registry, field.ty.id, - &field.type_name, ) }) .collect::, Error>>()?; @@ -163,10 +156,9 @@ fn type_to_param( .enumerate() .map(|(index, field_id)| { type_to_param( - format!("Index {} of the tuple {}", index.to_string(), name), + format!("Index {index} of the tuple {name}"), registry, field_id.id, - &None, ) }) .collect::, Error>>()?; From 3b2bcc059544ed6744f697b022728af8e2a970ab Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 29 Nov 2024 13:06:27 +0100 Subject: [PATCH 155/211] chore: fmt --- crates/pop-cli/src/commands/call/parachain.rs | 5 ++--- crates/pop-parachains/src/call/metadata/params.rs | 14 +++++++------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index ed48c1b11..d6c99982b 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -223,9 +223,8 @@ impl CallParachainCommand { prompt_to_repeat_call: bool, cli: &mut impl cli::traits::Cli, ) -> Result<()> { - if !self.skip_confirm - && !cli - .confirm("Do you want to submit the extrinsic?") + if !self.skip_confirm && + !cli.confirm("Do you want to submit the extrinsic?") .initial_value(true) .interact()? { diff --git a/crates/pop-parachains/src/call/metadata/params.rs b/crates/pop-parachains/src/call/metadata/params.rs index 073814cd0..feb73039b 100644 --- a/crates/pop-parachains/src/call/metadata/params.rs +++ b/crates/pop-parachains/src/call/metadata/params.rs @@ -52,9 +52,9 @@ fn type_to_param(name: String, registry: &PortableRegistry, type_id: u32) -> Res } } for param in &type_info.type_params { - if param.name == "RuntimeCall" - || param.name == "Vec" - || param.name == "Vec<::RuntimeCall>" + if param.name == "RuntimeCall" || + param.name == "Vec" || + param.name == "Vec<::RuntimeCall>" { return Err(Error::ExtrinsicNotSupported); } @@ -78,10 +78,10 @@ fn type_to_param(name: String, registry: &PortableRegistry, type_id: u32) -> Res // Determine the formatted type name. let type_name = format_type(type_info, registry); match &type_info.type_def { - TypeDef::Primitive(_) - | TypeDef::Array(_) - | TypeDef::Sequence(_) - | TypeDef::Compact(_) => Ok(Param { + TypeDef::Primitive(_) | + TypeDef::Array(_) | + TypeDef::Sequence(_) | + TypeDef::Compact(_) => Ok(Param { name, type_name, sub_params: Vec::new(), From 81b7f04bc013976aa772cbb44804fc7f23189baf Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Sun, 1 Dec 2024 12:18:04 +0100 Subject: [PATCH 156/211] feat: repeat call only if using guide UI --- crates/pop-cli/src/commands/call/parachain.rs | 64 +++++++++++++++++-- crates/pop-cli/src/common/build.rs | 48 -------------- crates/pop-cli/src/common/mod.rs | 2 - 3 files changed, 60 insertions(+), 54 deletions(-) delete mode 100644 crates/pop-cli/src/common/build.rs diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 4cf4f2a02..a4b7d3b89 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -43,6 +43,8 @@ impl CallParachainCommand { /// Executes the command. pub(crate) async fn execute(mut self) -> Result<()> { let mut cli = cli::Cli; + // Check if all fields are specified via command line argument. + let prompt_to_repeat_call = self.requires_user_input(); // Configure the chain. let chain = self.configure_chain(&mut cli).await?; loop { @@ -64,18 +66,23 @@ impl CallParachainCommand { break; }, }; + // Send the extrinsic. if let Err(e) = call.send_extrinsic(&chain.api, tx, &mut cli).await { display_message(&e.to_string(), false, &mut cli)?; + break; } - if !cli - .confirm("Do you want to perform another call?") - .initial_value(false) - .interact()? + + if !prompt_to_repeat_call + || !cli + .confirm("Do you want to perform another call?") + .initial_value(false) + .interact()? { display_message("Parachain calling complete.", true, &mut cli)?; break; } + self.reset_for_new_call(); } Ok(()) } @@ -182,6 +189,22 @@ impl CallParachainCommand { }); } } + + /// Resets specific fields to default values for a new call. + fn reset_for_new_call(&mut self) { + self.pallet = None; + self.extrinsic = None; + self.args.clear(); + } + + // Function to check if all required fields are specified + fn requires_user_input(&self) -> bool { + self.pallet.is_none() + || self.extrinsic.is_none() + || self.args.is_empty() + || self.url.is_none() + || self.suri.is_none() + } } struct Chain { @@ -612,6 +635,39 @@ mod tests { cli.verify() } + #[test] + fn reset_for_new_call_works() -> Result<()> { + let mut call_config = CallParachainCommand { + pallet: Some("System".to_string()), + extrinsic: Some("remark".to_string()), + args: vec!["0x11".to_string()].to_vec(), + url: Some(Url::parse("wss://rpc1.paseo.popnetwork.xyz")?), + suri: Some(DEFAULT_URI.to_string()), + skip_confirm: false, + }; + call_config.reset_for_new_call(); + assert_eq!(call_config.pallet, None); + assert_eq!(call_config.extrinsic, None); + assert_eq!(call_config.args.len(), 0); + Ok(()) + } + + #[test] + fn requires_user_input_works() -> Result<()> { + let mut call_config = CallParachainCommand { + pallet: Some("System".to_string()), + extrinsic: Some("remark".to_string()), + args: vec!["0x11".to_string()].to_vec(), + url: Some(Url::parse("wss://rpc1.paseo.popnetwork.xyz")?), + suri: Some(DEFAULT_URI.to_string()), + skip_confirm: false, + }; + assert!(!call_config.requires_user_input()); + call_config.pallet = None; + assert!(call_config.requires_user_input()); + Ok(()) + } + #[test] fn display_message_works() -> Result<()> { let mut cli = MockCli::new().expect_outro(&"Call completed successfully!"); diff --git a/crates/pop-cli/src/common/build.rs b/crates/pop-cli/src/common/build.rs deleted file mode 100644 index 6bc7fd4d2..000000000 --- a/crates/pop-cli/src/common/build.rs +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 - -use pop_common::manifest::from_path; -use std::path::Path; -/// Checks if a contract has been built by verifying the existence of the build directory and the -/// .contract file. -/// -/// # Arguments -/// * `path` - An optional path to the project directory. If no path is provided, the current -/// directory is used. -pub fn has_contract_been_built(path: Option<&Path>) -> bool { - let project_path = path.unwrap_or_else(|| Path::new("./")); - let manifest = match from_path(Some(project_path)) { - Ok(manifest) => manifest, - Err(_) => return false, - }; - let contract_name = manifest.package().name(); - project_path.join("target/ink").exists() && - project_path.join(format!("target/ink/{}.contract", contract_name)).exists() -} - -#[cfg(test)] -mod tests { - use super::*; - use duct::cmd; - use std::fs::{self, File}; - - #[test] - fn has_contract_been_built_works() -> anyhow::Result<()> { - let temp_dir = tempfile::tempdir()?; - let path = temp_dir.path(); - - // Standard rust project - let name = "hello_world"; - cmd("cargo", ["new", name]).dir(&path).run()?; - let contract_path = path.join(name); - assert!(!has_contract_been_built(Some(&contract_path))); - - cmd("cargo", ["build"]).dir(&contract_path).run()?; - // Mock build directory - fs::create_dir(&contract_path.join("target/ink"))?; - assert!(!has_contract_been_built(Some(&path.join(name)))); - // Create a mocked .contract file inside the target directory - File::create(contract_path.join(format!("target/ink/{}.contract", name)))?; - assert!(has_contract_been_built(Some(&path.join(name)))); - Ok(()) - } -} diff --git a/crates/pop-cli/src/common/mod.rs b/crates/pop-cli/src/common/mod.rs index 0469db60a..1cb3ee579 100644 --- a/crates/pop-cli/src/common/mod.rs +++ b/crates/pop-cli/src/common/mod.rs @@ -1,7 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -#[cfg(feature = "contract")] -pub mod build; #[cfg(feature = "contract")] pub mod contracts; pub mod helpers; From 6b963a449a4c0dab31499ff62838a4fc89b539f8 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Sun, 1 Dec 2024 12:19:28 +0100 Subject: [PATCH 157/211] fix: clippy --- crates/pop-cli/src/commands/call/contract.rs | 2 +- crates/pop-cli/src/commands/call/parachain.rs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 782629c65..b7837564d 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -166,7 +166,7 @@ impl CallContractCommand { fn is_contract_build_required(&self) -> bool { self.path .as_ref() - .map(|p| p.is_dir() && !has_contract_been_built(Some(&p))) + .map(|p| p.is_dir() && !has_contract_been_built(Some(p))) .unwrap_or_default() } diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index a4b7d3b89..6ae5db857 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -103,7 +103,7 @@ impl CallParachainCommand { }; // Parse metadata from chain url. - let api = set_up_api(&url.as_str()).await?; + let api = set_up_api(url.as_str()).await?; let pallets = parse_chain_metadata(&api).await.map_err(|e| { anyhow!(format!("Unable to fetch the chain metadata: {}", e.to_string())) })?; @@ -164,7 +164,7 @@ impl CallParachainCommand { let args = if self.clone().args.is_empty() { let mut args = Vec::new(); for param in &extrinsic.params { - let input = prompt_for_param(&chain.api, cli, ¶m)?; + let input = prompt_for_param(&chain.api, cli, param)?; args.push(input); } args @@ -239,8 +239,8 @@ impl CallParachain { cli: &mut impl Cli, ) -> Result { let tx = match construct_extrinsic( - &self.pallet.name.as_str(), - &self.extrinsic.name.as_str(), + self.pallet.name.as_str(), + self.extrinsic.name.as_str(), self.args.clone(), ) .await @@ -283,7 +283,7 @@ impl CallParachain { .await .map_err(|err| anyhow!("{}", format!("{err:?}")))?; - spinner.stop(&format!("Extrinsic submitted with hash: {:?}", result)); + spinner.stop(format!("Extrinsic submitted with hash: {:?}", result)); Ok(()) } fn display(&self, chain: &Chain) -> String { From 4ad7144b84037d4e9cad34900a10a4b74db3be6f Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Sun, 1 Dec 2024 19:38:34 +0100 Subject: [PATCH 158/211] refactor: various improvements --- crates/pop-cli/src/commands/call/parachain.rs | 44 +++++++++---------- crates/pop-common/src/errors.rs | 4 +- crates/pop-parachains/src/call/mod.rs | 6 ++- crates/pop-parachains/src/errors.rs | 11 +---- crates/pop-parachains/src/lib.rs | 2 +- 5 files changed, 29 insertions(+), 38 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 6ae5db857..489fc3fb5 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -16,25 +16,25 @@ const DEFAULT_URI: &str = "//Alice"; #[derive(Args, Clone)] pub struct CallParachainCommand { /// The pallet containing the extrinsic to execute. - #[arg(long)] + #[arg(short, long)] pallet: Option, /// The extrinsic to execute within the chosen pallet. - #[arg(long)] + #[arg(short, long)] extrinsic: Option, /// The extrinsic arguments, encoded as strings. - #[arg(long, num_args = 0..,)] + #[arg(short, long, num_args = 0..,)] args: Vec, /// Websocket endpoint of a node. - #[arg(short = 'u', long, value_parser)] + #[arg(short, long, value_parser)] url: Option, /// Secret key URI for the account signing the extrinsic. /// /// e.g. /// - for a dev account "//Alice" /// - with a password "//Alice///SECRET_PASSWORD" - #[arg(long)] + #[arg(short, long)] suri: Option, - /// Automatically signs and submits the extrinsic without asking for confirmation. + /// Automatically signs and submits the extrinsic without prompting for confirmation. #[arg(short('y'), long)] skip_confirm: bool, } @@ -73,9 +73,8 @@ impl CallParachainCommand { break; } - if !prompt_to_repeat_call - || !cli - .confirm("Do you want to perform another call?") + if !prompt_to_repeat_call || + !cli.confirm("Do you want to perform another call?") .initial_value(false) .interact()? { @@ -137,9 +136,8 @@ impl CallParachainCommand { // Resolve extrinsic. let extrinsic = match self.extrinsic { - Some(ref extrinsic_name) => { - find_extrinsic_by_name(&chain.pallets, &pallet.name, extrinsic_name).await? - }, + Some(ref extrinsic_name) => + find_extrinsic_by_name(&chain.pallets, &pallet.name, extrinsic_name).await?, None => { let mut prompt_extrinsic = cli.select("Select the extrinsic to call:"); for extrinsic in &pallet.extrinsics { @@ -175,9 +173,8 @@ impl CallParachainCommand { // Resolve who is signing the extrinsic. let suri = match self.clone().suri { Some(suri) => suri, - None => { - cli.input("Signer of the extrinsic:").default_input(DEFAULT_URI).interact()? - }, + None => + cli.input("Signer of the extrinsic:").default_input(DEFAULT_URI).interact()?, }; return Ok(CallParachain { @@ -199,11 +196,11 @@ impl CallParachainCommand { // Function to check if all required fields are specified fn requires_user_input(&self) -> bool { - self.pallet.is_none() - || self.extrinsic.is_none() - || self.args.is_empty() - || self.url.is_none() - || self.suri.is_none() + self.pallet.is_none() || + self.extrinsic.is_none() || + self.args.is_empty() || + self.url.is_none() || + self.suri.is_none() } } @@ -227,7 +224,7 @@ struct CallParachain { /// - for a dev account "//Alice" /// - with a password "//Alice///SECRET_PASSWORD" suri: String, - /// Whether to automatically sign and submit the extrinsic without asking for confirmation. + /// Whether to automatically sign and submit the extrinsic without prompting for confirmation. skip_confirm: bool, } @@ -261,9 +258,8 @@ impl CallParachain { tx: DynamicPayload, cli: &mut impl Cli, ) -> Result<()> { - if !self.skip_confirm - && !cli - .confirm("Do you want to submit the extrinsic?") + if !self.skip_confirm && + !cli.confirm("Do you want to submit the extrinsic?") .initial_value(true) .interact()? { diff --git a/crates/pop-common/src/errors.rs b/crates/pop-common/src/errors.rs index 5a0066add..0ddc78284 100644 --- a/crates/pop-common/src/errors.rs +++ b/crates/pop-common/src/errors.rs @@ -17,10 +17,10 @@ pub enum Error { IO(#[from] std::io::Error), #[error("Failed to create keypair from URI: {0}")] KeyPairCreation(String), - #[error("Failed to get manifest path: {0}")] - ManifestPath(String), #[error("Manifest error: {0}")] ManifestError(#[from] cargo_toml::Error), + #[error("Failed to get manifest path: {0}")] + ManifestPath(String), #[error("ParseError error: {0}")] ParseError(#[from] url::ParseError), #[error("Failed to parse secret URI: {0}")] diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs index 89acd8861..794bc0bb8 100644 --- a/crates/pop-parachains/src/call/mod.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -49,9 +49,11 @@ pub async fn sign_and_submit_extrinsic( let result = api .tx() .sign_and_submit_then_watch_default(&tx, &signer) - .await? + .await + .map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))? .wait_for_finalized_success() - .await?; + .await + .map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))?; Ok(format!("{:?}", result.extrinsic_hash())) } diff --git a/crates/pop-parachains/src/errors.rs b/crates/pop-parachains/src/errors.rs index 9ca07a8ac..5031a9345 100644 --- a/crates/pop-parachains/src/errors.rs +++ b/crates/pop-parachains/src/errors.rs @@ -1,6 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -use subxt::ext::scale_decode; use thiserror::Error; use zombienet_sdk::OrchestratorError; @@ -20,14 +19,12 @@ pub enum Error { EndowmentError, #[error("The extrinsic is not supported")] ExtrinsicNotSupported, - #[error("Failed decode a value")] - FromHexError(#[from] hex::FromHexError), + #[error("Extrinsic submission error: {0}")] + ExtrinsicSubmissionError(String), #[error("IO error: {0}")] IO(#[from] std::io::Error), #[error("JSON error: {0}")] JsonError(#[from] serde_json::Error), - #[error("Metadata error: {0}")] - MetadataError(#[from] subxt::error::MetadataError), #[error("Error parsing metadata for parameter {0} conversion")] MetadataParsingError(String), #[error("Missing binary: {0}")] @@ -40,14 +37,10 @@ pub enum Error { OrchestratorError(#[from] OrchestratorError), #[error("Failed to create pallet directory")] PalletDirCreation, - #[error("The `Pallet` property is missing from the call variant")] - PalletMissing, #[error("Failed to find the pallet {0}")] PalletNotFound(String), #[error("Failed to process the arguments provided by the user.")] ParamProcessingError, - #[error("Failed to parse the response")] - ParsingResponseError(#[from] scale_decode::Error), #[error("Invalid path")] PathError, #[error("Failed to execute rustfmt")] diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index 637547a21..b1e48a599 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -30,7 +30,7 @@ pub use indexmap::IndexSet; pub use new_pallet::{create_pallet_template, new_pallet_options::*, TemplatePalletConfig}; pub use new_parachain::instantiate_template_dir; // External export from subxt. -pub use subxt::{dynamic::Value, tx::DynamicPayload, OnlineClient, SubstrateConfig}; +pub use subxt::{tx::DynamicPayload, OnlineClient, SubstrateConfig}; pub use templates::{Config, Parachain, Provider}; pub use up::Zombienet; pub use utils::helpers::is_initial_endowment_valid; From f5897ed63d4cde08aa75c679f9dd9ad2f5208f54 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Mon, 2 Dec 2024 14:51:19 +0100 Subject: [PATCH 159/211] chore: parser for pallet and extrinsic input names --- crates/pop-cli/src/commands/call/contract.rs | 2 +- crates/pop-cli/src/commands/call/parachain.rs | 34 +++++++++++++++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index b7837564d..782629c65 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -166,7 +166,7 @@ impl CallContractCommand { fn is_contract_build_required(&self) -> bool { self.path .as_ref() - .map(|p| p.is_dir() && !has_contract_been_built(Some(p))) + .map(|p| p.is_dir() && !has_contract_been_built(Some(&p))) .unwrap_or_default() } diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 489fc3fb5..0117ad0d2 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -16,10 +16,10 @@ const DEFAULT_URI: &str = "//Alice"; #[derive(Args, Clone)] pub struct CallParachainCommand { /// The pallet containing the extrinsic to execute. - #[arg(short, long)] + #[arg(short, long, value_parser = parse_pallet_name)] pallet: Option, /// The extrinsic to execute within the chosen pallet. - #[arg(short, long)] + #[arg(short, long, value_parser = parse_extrinsic_name)] extrinsic: Option, /// The extrinsic arguments, encoded as strings. #[arg(short, long, num_args = 0..,)] @@ -435,6 +435,20 @@ fn prompt_for_tuple_param( Ok(format!("({})", tuple_values.join(", "))) } +/// Parser to capitalize the first letter of the pallet name. +fn parse_pallet_name(name: &str) -> Result { + let mut chars = name.chars(); + match chars.next() { + Some(c) => Ok(c.to_ascii_uppercase().to_string() + chars.as_str()), + None => Err("Pallet cannot be empty".to_string()), + } +} + +/// Parser to convert the extrinsic name to lowercase. +fn parse_extrinsic_name(name: &str) -> Result { + Ok(name.to_ascii_lowercase()) +} + #[cfg(test)] mod tests { use super::*; @@ -779,4 +793,20 @@ mod tests { assert_eq!(params[2], "0".to_string()); // period: test primitive cli.verify() } + + #[test] + fn parse_pallet_name_works() -> Result<()> { + assert_eq!(parse_pallet_name("system").unwrap(), "System"); + assert_eq!(parse_pallet_name("balances").unwrap(), "Balances"); + assert_eq!(parse_pallet_name("nfts").unwrap(), "Nfts"); + Ok(()) + } + + #[test] + fn parse_extrinsic_name_works() -> Result<()> { + assert_eq!(parse_extrinsic_name("Remark").unwrap(), "remark"); + assert_eq!(parse_extrinsic_name("Force_transfer").unwrap(), "force_transfer"); + assert_eq!(parse_extrinsic_name("MINT").unwrap(), "mint"); + Ok(()) + } } From e58952b1d5f39f36abec59fc14df47aa5ed09fdf Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Mon, 2 Dec 2024 15:18:11 +0100 Subject: [PATCH 160/211] refactor: only move to pop_common the needed functions --- crates/pop-cli/src/commands/call/contract.rs | 3 +-- crates/pop-common/src/errors.rs | 2 -- crates/pop-common/src/lib.rs | 2 +- crates/pop-common/src/signer.rs | 24 +------------------- crates/pop-contracts/src/call.rs | 4 ++-- crates/pop-contracts/src/errors.rs | 2 ++ crates/pop-contracts/src/lib.rs | 2 +- crates/pop-contracts/src/utils/mod.rs | 22 ++++++++++++++++++ 8 files changed, 30 insertions(+), 31 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 782629c65..4e00e37b5 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -7,10 +7,9 @@ use crate::{ use anyhow::{anyhow, Result}; use clap::Args; use cliclack::spinner; -use pop_common::parse_account; use pop_contracts::{ build_smart_contract, call_smart_contract, dry_run_call, dry_run_gas_estimate_call, - get_messages, set_up_call, CallOpts, Verbosity, + get_messages, parse_account, set_up_call, CallOpts, Verbosity, }; use sp_weights::Weight; use std::path::PathBuf; diff --git a/crates/pop-common/src/errors.rs b/crates/pop-common/src/errors.rs index 0ddc78284..e8924290f 100644 --- a/crates/pop-common/src/errors.rs +++ b/crates/pop-common/src/errors.rs @@ -7,8 +7,6 @@ use thiserror::Error; pub enum Error { #[error("Anyhow error: {0}")] AnyhowError(#[from] anyhow::Error), - #[error("Failed to parse account address: {0}")] - AccountAddressParsing(String), #[error("Configuration error: {0}")] Config(String), #[error("a git error occurred: {0}")] diff --git a/crates/pop-common/src/lib.rs b/crates/pop-common/src/lib.rs index be843ec85..b78af4420 100644 --- a/crates/pop-common/src/lib.rs +++ b/crates/pop-common/src/lib.rs @@ -15,7 +15,7 @@ pub use git::{Git, GitHub, Release}; pub use helpers::{get_project_name_from_path, prefix_with_current_dir_if_needed, replace_in_file}; pub use manifest::{add_crate_to_workspace, find_workspace_toml}; pub use metadata::format_type; -pub use signer::{create_signer, parse_account}; +pub use signer::create_signer; pub use templates::extractor::extract_template_files; // External exports pub use subxt::{Config, PolkadotConfig as DefaultConfig}; diff --git a/crates/pop-common/src/signer.rs b/crates/pop-common/src/signer.rs index 8cc3e9e25..79da4a283 100644 --- a/crates/pop-common/src/signer.rs +++ b/crates/pop-common/src/signer.rs @@ -1,14 +1,8 @@ // SPDX-License-Identifier: GPL-3.0 -use crate::{errors::Error, Config, DefaultConfig}; -use std::str::FromStr; +use crate::errors::Error; use subxt_signer::{sr25519::Keypair, SecretUri}; -pub fn parse_account(account: &str) -> Result<::AccountId, Error> { - ::AccountId::from_str(account) - .map_err(|e| Error::AccountAddressParsing(format!("{}", e))) -} - /// Create a Signer from a secret URI. pub fn create_signer(suri: &str) -> Result { let uri = ::from_str(suri) @@ -37,20 +31,4 @@ mod tests { assert!(matches!(create_signer("11111"), Err(Error::KeyPairCreation(..)))); Ok(()) } - - #[test] - fn parse_account_works() -> Result<(), Error> { - let account = parse_account("5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A")?; - assert_eq!(account.to_string(), "5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A"); - Ok(()) - } - - #[test] - fn parse_account_fails_wrong_value() -> Result<(), Error> { - assert!(matches!( - parse_account("wrongaccount"), - Err(super::Error::AccountAddressParsing(..)) - )); - Ok(()) - } } diff --git a/crates/pop-contracts/src/call.rs b/crates/pop-contracts/src/call.rs index 3ccfe76e0..316ba1ed6 100644 --- a/crates/pop-contracts/src/call.rs +++ b/crates/pop-contracts/src/call.rs @@ -5,7 +5,7 @@ use crate::{ utils::{ get_manifest_path, metadata::{process_function_args, FunctionType}, - parse_balance, + parse_account, parse_balance, }, }; use anyhow::Context; @@ -15,7 +15,7 @@ use contract_extrinsics::{ ExtrinsicOptsBuilder, TokenMetadata, }; use ink_env::{DefaultEnvironment, Environment}; -use pop_common::{create_signer, parse_account, Config, DefaultConfig, Keypair}; +use pop_common::{create_signer, Config, DefaultConfig, Keypair}; use sp_weights::Weight; use std::path::PathBuf; use url::Url; diff --git a/crates/pop-contracts/src/errors.rs b/crates/pop-contracts/src/errors.rs index beed52e27..bd387abf0 100644 --- a/crates/pop-contracts/src/errors.rs +++ b/crates/pop-contracts/src/errors.rs @@ -6,6 +6,8 @@ use thiserror::Error; #[derive(Error, Debug)] #[allow(clippy::enum_variant_names)] pub enum Error { + #[error("Failed to parse account address: {0}")] + AccountAddressParsing(String), #[error("Anyhow error: {0}")] AnyhowError(#[from] anyhow::Error), #[error("Failed to parse balance: {0}")] diff --git a/crates/pop-contracts/src/lib.rs b/crates/pop-contracts/src/lib.rs index fe512a56f..c5e76e2f1 100644 --- a/crates/pop-contracts/src/lib.rs +++ b/crates/pop-contracts/src/lib.rs @@ -27,5 +27,5 @@ pub use up::{ }; pub use utils::{ metadata::{get_messages, ContractFunction}, - parse_hex_bytes, + parse_account, parse_hex_bytes, }; diff --git a/crates/pop-contracts/src/utils/mod.rs b/crates/pop-contracts/src/utils/mod.rs index 12c7813ad..3aa266d93 100644 --- a/crates/pop-contracts/src/utils/mod.rs +++ b/crates/pop-contracts/src/utils/mod.rs @@ -4,6 +4,7 @@ use crate::errors::Error; use contract_build::{util::decode_hex, ManifestPath}; use contract_extrinsics::BalanceVariant; use ink_env::{DefaultEnvironment, Environment}; +use pop_common::{Config, DefaultConfig}; use sp_core::Bytes; use std::{ path::{Path, PathBuf}, @@ -29,6 +30,11 @@ pub fn parse_balance( BalanceVariant::from_str(balance).map_err(|e| Error::BalanceParsing(format!("{}", e))) } +pub fn parse_account(account: &str) -> Result<::AccountId, Error> { + ::AccountId::from_str(account) + .map_err(|e| Error::AccountAddressParsing(format!("{}", e))) +} + /// Parse hex encoded bytes. pub fn parse_hex_bytes(input: &str) -> Result { let bytes = decode_hex(input).map_err(|e| Error::HexParsing(format!("{}", e)))?; @@ -97,6 +103,22 @@ mod tests { Ok(()) } + #[test] + fn parse_account_works() -> Result<(), Error> { + let account = parse_account("5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A")?; + assert_eq!(account.to_string(), "5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A"); + Ok(()) + } + + #[test] + fn parse_account_fails_wrong_value() -> Result<(), Error> { + assert!(matches!( + parse_account("wrongaccount"), + Err(super::Error::AccountAddressParsing(..)) + )); + Ok(()) + } + #[test] fn parse_hex_bytes_works() -> Result<(), Error> { let input_in_hex = "48656c6c6f"; From fd0d6a6f8c453c850d5c33df01849f9557bb9724 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Mon, 2 Dec 2024 16:18:50 +0100 Subject: [PATCH 161/211] refactor: improve test, docs and errors --- crates/pop-cli/tests/parachain.rs | 2 +- crates/pop-common/src/metadata.rs | 74 +++++++++++-------- crates/pop-contracts/src/utils/metadata.rs | 5 +- crates/pop-contracts/src/utils/mod.rs | 16 +++- crates/pop-parachains/Cargo.toml | 2 +- .../src/call/metadata/action.rs | 32 +++++--- .../pop-parachains/src/call/metadata/mod.rs | 4 +- .../src/call/metadata/params.rs | 1 - crates/pop-parachains/src/call/mod.rs | 7 +- crates/pop-parachains/src/errors.rs | 8 +- 10 files changed, 93 insertions(+), 58 deletions(-) diff --git a/crates/pop-cli/tests/parachain.rs b/crates/pop-cli/tests/parachain.rs index 1cd6b2468..1676bdef9 100644 --- a/crates/pop-cli/tests/parachain.rs +++ b/crates/pop-cli/tests/parachain.rs @@ -87,7 +87,7 @@ async fn parachain_lifecycle() -> Result<()> { assert!(content.contains("\"relay_chain\": \"paseo-local\"")); assert!(content.contains("\"protocolId\": \"pop-protocol\"")); - // Custom network to manually set the port + // Overwrite the config file to manually set the port to test pop call parachain. let network_toml_path = temp_parachain_dir.join("network.toml"); fs::create_dir_all(&temp_parachain_dir)?; fs::write( diff --git a/crates/pop-common/src/metadata.rs b/crates/pop-common/src/metadata.rs index 82851c8f7..53ff8dffc 100644 --- a/crates/pop-common/src/metadata.rs +++ b/crates/pop-common/src/metadata.rs @@ -167,49 +167,59 @@ mod tests { OnlineClient::::from_url("wss://rpc1.paseo.popnetwork.xyz").await?; let metadata = api.metadata(); let registry = metadata.types(); - // Extrinsic Nfts::mint to test the majority of expresions. - let mut extrinsic = + + // Validate `Nfts::mint` extrinsic types cover most of cases. + let nfts_mint_extrinsic = metadata.pallet_by_name("Nfts").unwrap().call_variant_by_name("mint").unwrap(); - let mut types_formatted = Vec::new(); - for field in &extrinsic.fields { - let type_info = registry.resolve(field.ty.id).unwrap(); - types_formatted.push(format_type(&type_info, ®istry)); - } - assert_eq!(types_formatted.len(), 4); - assert_eq!(types_formatted[0], "u32"); // collection - assert_eq!(types_formatted[1], "u32"); // item - assert_eq!(types_formatted[2], "MultiAddress: Id(AccountId32 ([u8;32])), Index(Compact<()>), Raw([u8]), Address32([u8;32]), Address20([u8;20])"); // mint_to - assert_eq!(types_formatted[3], "Option { owned_item: Option, mint_price: Option }>: None, Some(MintWitness { owned_item: Option, mint_price: Option })"); // witness_data + let nfts_mint_types: Vec = nfts_mint_extrinsic + .fields + .iter() + .map(|field| { + let type_info = registry.resolve(field.ty.id).unwrap(); + format_type(&type_info, registry) + }) + .collect(); + assert_eq!(nfts_mint_types.len(), 4); + assert_eq!(nfts_mint_types[0], "u32"); // collection + assert_eq!(nfts_mint_types[1], "u32"); // item + assert_eq!(nfts_mint_types[2], "MultiAddress: Id(AccountId32 ([u8;32])), Index(Compact<()>), Raw([u8]), Address32([u8;32]), Address20([u8;20])"); // mint_to + assert_eq!(nfts_mint_types[3], "Option { owned_item: Option, mint_price: Option }>: None, Some(MintWitness { owned_item: Option, mint_price: Option })"); // witness_data - // Extrinsic Sytem::remark to testing sequences. - extrinsic = metadata + // Validate `System::remark` to cover Sequences. + let system_remark_extrinsic = metadata .pallet_by_name("System") .unwrap() .call_variant_by_name("remark") .unwrap(); - types_formatted.clear(); - for field in &extrinsic.fields { - let type_info = registry.resolve(field.ty.id).unwrap(); - types_formatted.push(format_type(&type_info, ®istry)); - } - assert_eq!(types_formatted.len(), 1); - assert_eq!(types_formatted[0], "[u8]"); // remark + let system_remark_types: Vec = system_remark_extrinsic + .fields + .iter() + .map(|field| { + let type_info = registry.resolve(field.ty.id).unwrap(); + format_type(&type_info, registry) + }) + .collect(); + assert_eq!(system_remark_types.len(), 1); + assert_eq!(system_remark_types[0], "[u8]"); // remark - // Extrinsic Scheduler::set_retry to test tuples. - extrinsic = metadata + // Extrinsic Scheduler::set_retry, cover tuples. + let scheduler_set_retry_extrinsic = metadata .pallet_by_name("Scheduler") .unwrap() .call_variant_by_name("set_retry") .unwrap(); - types_formatted.clear(); - for field in &extrinsic.fields { - let type_info = registry.resolve(field.ty.id).unwrap(); - types_formatted.push(format_type(&type_info, ®istry)); - } - assert_eq!(types_formatted.len(), 3); - assert_eq!(types_formatted[0], "(u32,u32)"); // task - assert_eq!(types_formatted[1], "u8"); // retries - assert_eq!(types_formatted[2], "u32"); // period + let scheduler_set_retry_types: Vec = scheduler_set_retry_extrinsic + .fields + .iter() + .map(|field| { + let type_info = registry.resolve(field.ty.id).unwrap(); + format_type(&type_info, registry) + }) + .collect(); + assert_eq!(scheduler_set_retry_types.len(), 3); + assert_eq!(scheduler_set_retry_types[0], "(u32,u32)"); // task + assert_eq!(scheduler_set_retry_types[1], "u8"); // retries + assert_eq!(scheduler_set_retry_types[2], "u32"); // period Ok(()) } diff --git a/crates/pop-contracts/src/utils/metadata.rs b/crates/pop-contracts/src/utils/metadata.rs index ad1738f60..7e538450c 100644 --- a/crates/pop-contracts/src/utils/metadata.rs +++ b/crates/pop-contracts/src/utils/metadata.rs @@ -158,12 +158,13 @@ fn process_args( } /// Processes a list of argument values for a specified contract function, -/// wrapping each value in `Some(...)` or replacing it with `None` if the argument is optional +/// wrapping each value in `Some(...)` or replacing it with `None` if the argument is optional. /// /// # Arguments -/// * `path` - Location path of the project. +/// * `path` - Location path of the project or contract artifact. /// * `label` - Label of the contract message to retrieve. /// * `args` - Argument values provided by the user. +/// * `function_type` - Specifies whether to process arguments of messages or constructors pub fn process_function_args

( path: P, label: &str, diff --git a/crates/pop-contracts/src/utils/mod.rs b/crates/pop-contracts/src/utils/mod.rs index 3aa266d93..a3a99323b 100644 --- a/crates/pop-contracts/src/utils/mod.rs +++ b/crates/pop-contracts/src/utils/mod.rs @@ -13,6 +13,10 @@ use std::{ pub mod metadata; +/// Retrieves the manifest path for a contract project. +/// +/// # Arguments +/// * `path` - An optional path to the project directory. pub fn get_manifest_path(path: Option<&Path>) -> Result { if let Some(path) = path { let full_path = PathBuf::from(path.to_string_lossy().to_string() + "/Cargo.toml"); @@ -24,18 +28,29 @@ pub fn get_manifest_path(path: Option<&Path>) -> Result { } } +/// Parses a balance value from a string representation. +/// +/// # Arguments +/// * `balance` - A string representing the balance value to parse. pub fn parse_balance( balance: &str, ) -> Result::Balance>, Error> { BalanceVariant::from_str(balance).map_err(|e| Error::BalanceParsing(format!("{}", e))) } +/// Parses an account ID from its string representation. +/// +/// # Arguments +/// * `account` - A string representing the account ID to parse. pub fn parse_account(account: &str) -> Result<::AccountId, Error> { ::AccountId::from_str(account) .map_err(|e| Error::AccountAddressParsing(format!("{}", e))) } /// Parse hex encoded bytes. +/// +/// # Arguments +/// * `input` - A string containing hex-encoded bytes. pub fn parse_hex_bytes(input: &str) -> Result { let bytes = decode_hex(input).map_err(|e| Error::HexParsing(format!("{}", e)))?; Ok(bytes.into()) @@ -44,7 +59,6 @@ pub fn parse_hex_bytes(input: &str) -> Result { /// Canonicalizes the given path to ensure consistency and resolve any symbolic links. /// /// # Arguments -/// /// * `target` - A reference to the `Path` to be canonicalized. pub fn canonicalized_path(target: &Path) -> Result { // Canonicalize the target path to ensure consistency and resolve any symbolic links. diff --git a/crates/pop-parachains/Cargo.toml b/crates/pop-parachains/Cargo.toml index b6af7d0d7..64bef2d67 100644 --- a/crates/pop-parachains/Cargo.toml +++ b/crates/pop-parachains/Cargo.toml @@ -22,9 +22,9 @@ tempfile.workspace = true thiserror.workspace = true tokio.workspace = true url.workspace = true -hex.workspace = true askama.workspace = true +hex.workspace = true indexmap.workspace = true reqwest.workspace = true scale-info.workspace = true diff --git a/crates/pop-parachains/src/call/metadata/action.rs b/crates/pop-parachains/src/call/metadata/action.rs index 63d49ee84..7ee4440df 100644 --- a/crates/pop-parachains/src/call/metadata/action.rs +++ b/crates/pop-parachains/src/call/metadata/action.rs @@ -19,34 +19,39 @@ use strum_macros::{AsRefStr, Display, EnumMessage, EnumProperty, EnumString, Var VariantArray, )] pub enum Action { + /// Transfer balance. #[strum( serialize = "transfer", message = "transfer_allow_death", - detailed_message = "Transfer Balance", + detailed_message = "Transfer balance", props(Pallet = "Balances") )] Transfer, + /// Create an asset. #[strum( serialize = "create", message = "create", - detailed_message = "Create an Asset", + detailed_message = "Create an asset", props(Pallet = "Assets") )] CreateAsset, + /// Mint an asset. #[strum( serialize = "mint", message = "mint", - detailed_message = "Mint an Asset", + detailed_message = "Mint an asset", props(Pallet = "Assets") )] MintAsset, + /// Create an NFT collection. #[strum( serialize = "create_nft", message = "create", - detailed_message = "Create an NFT Collection", + detailed_message = "Create an NFT collection", props(Pallet = "Nfts") )] CreateCollection, + /// Mint an NFT. #[strum( serialize = "mint_nft", message = "mint", @@ -54,6 +59,7 @@ pub enum Action { props(Pallet = "Nfts") )] MintNFT, + /// Purchase on-demand coretime. #[strum( serialize = "place_order_allow_death", message = "place_order_allow_death", @@ -61,17 +67,19 @@ pub enum Action { props(Pallet = "OnDemand") )] PurchaseOnDemandCoretime, + /// Reserve a parachain ID. #[strum( serialize = "reserve", message = "reserve", - detailed_message = "Reserve para id", + detailed_message = "Reserve a parachain ID", props(Pallet = "Registrar") )] Reserve, + /// Register a parachain ID with genesis state and code. #[strum( serialize = "register", message = "register", - detailed_message = "Register para id with genesis state and code", + detailed_message = "Register a parachain ID with genesis state and code", props(Pallet = "Registrar") )] Register, @@ -122,14 +130,14 @@ mod tests { #[test] fn action_descriptions_are_correct() { let descriptions = HashMap::from([ - (Action::CreateAsset, "Create an Asset"), - (Action::MintAsset, "Mint an Asset"), - (Action::CreateCollection, "Create an NFT Collection"), + (Action::CreateAsset, "Create an asset"), + (Action::MintAsset, "Mint an asset"), + (Action::CreateCollection, "Create an NFT collection"), (Action::MintNFT, "Mint an NFT"), (Action::PurchaseOnDemandCoretime, "Purchase on-demand coretime"), - (Action::Transfer, "Transfer Balance"), - (Action::Register, "Register para id with genesis state and code"), - (Action::Reserve, "Reserve para id"), + (Action::Transfer, "Transfer balance"), + (Action::Register, "Register a parachain ID with genesis state and code"), + (Action::Reserve, "Reserve a parachain ID"), ]); for action in Action::VARIANTS.iter() { diff --git a/crates/pop-parachains/src/call/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs index 2d6d6b0eb..30902676d 100644 --- a/crates/pop-parachains/src/call/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -9,8 +9,8 @@ use subxt::{dynamic::Value, Metadata, OnlineClient, SubstrateConfig}; pub mod action; pub mod params; -#[derive(Clone, PartialEq, Eq)] /// Represents a pallet in the blockchain, including its extrinsics. +#[derive(Clone, PartialEq, Eq)] pub struct Pallet { /// The name of the pallet. pub name: String, @@ -26,8 +26,8 @@ impl Display for Pallet { } } -#[derive(Clone, PartialEq, Eq, Debug)] /// Represents an extrinsic in a pallet. +#[derive(Clone, PartialEq, Eq, Debug)] pub struct Extrinsic { /// The name of the extrinsic. pub name: String, diff --git a/crates/pop-parachains/src/call/metadata/params.rs b/crates/pop-parachains/src/call/metadata/params.rs index feb73039b..c5b04007f 100644 --- a/crates/pop-parachains/src/call/metadata/params.rs +++ b/crates/pop-parachains/src/call/metadata/params.rs @@ -43,7 +43,6 @@ pub fn field_to_param( /// * `name`: The name of the parameter. /// * `registry`: A reference to the `PortableRegistry` to resolve type dependencies. /// * `type_id`: The ID of the type to be converted. -/// * `type_name`: An optional descriptive name for the type. fn type_to_param(name: String, registry: &PortableRegistry, type_id: u32) -> Result { let type_info = registry.resolve(type_id).ok_or(Error::MetadataParsingError(name.clone()))?; if let Some(last_segment) = type_info.path.segments.last() { diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs index 794bc0bb8..d42bb5a00 100644 --- a/crates/pop-parachains/src/call/mod.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -15,7 +15,9 @@ pub mod metadata; /// # Arguments /// * `url` - Endpoint of the node. pub async fn set_up_api(url: &str) -> Result, Error> { - let api = OnlineClient::::from_url(url).await?; + let api = OnlineClient::::from_url(url) + .await + .map_err(|e| Error::ApiConnectionFailure(e.to_string()))?; Ok(api) } @@ -66,7 +68,8 @@ pub fn encode_call_data( api: &OnlineClient, tx: &DynamicPayload, ) -> Result { - let call_data = tx.encode_call_data(&api.metadata())?; + let call_data = + tx.encode_call_data(&api.metadata()).map_err(|_| Error::CallDataEncodingError)?; Ok(format!("0x{}", hex::encode(call_data))) } diff --git a/crates/pop-parachains/src/errors.rs b/crates/pop-parachains/src/errors.rs index 5031a9345..585e9f782 100644 --- a/crates/pop-parachains/src/errors.rs +++ b/crates/pop-parachains/src/errors.rs @@ -9,6 +9,10 @@ pub enum Error { Aborted, #[error("Anyhow error: {0}")] AnyhowError(#[from] anyhow::Error), + #[error("Failed to establish a connection to the API: {0}")] + ApiConnectionFailure(String), + #[error("Failed to encode call data for the extrinsic.")] + CallDataEncodingError, #[error("{0}")] CommonError(#[from] pop_common::Error), #[error("Configuration error: {0}")] @@ -47,10 +51,6 @@ pub enum Error { RustfmtError(std::io::Error), #[error("Template error: {0}")] SourcingError(#[from] pop_common::sourcing::Error), - #[error("{0}")] - SubxtError(#[from] subxt::Error), - #[error("{0}")] - SubxtExternalError(#[from] subxt::ext::subxt_core::Error), #[error("Toml error: {0}")] TomlError(#[from] toml_edit::de::Error), #[error("Unsupported command: {0}")] From 6d7ea24e2d21254c0679b90d5d4d91bca322211c Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Mon, 2 Dec 2024 17:46:23 +0100 Subject: [PATCH 162/211] test: fix unit tests --- crates/pop-cli/src/commands/call/parachain.rs | 18 +++++++++--------- crates/pop-parachains/src/call/mod.rs | 10 +++++++--- crates/pop-parachains/src/errors.rs | 4 ++-- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 0117ad0d2..68b08bb1d 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -556,11 +556,11 @@ mod tests { true, Some( [ - ("Transfer Balance".to_string(), "Balances".to_string()), + ("Transfer balance".to_string(), "Balances".to_string()), ("Purchase on-demand coretime".to_string(), "OnDemand".to_string()), - ("Reserve para id".to_string(), "Registrar".to_string()), + ("Reserve a parachain ID".to_string(), "Registrar".to_string()), ( - "Register para id with genesis state and code".to_string(), + "Register a parachain ID with genesis state and code".to_string(), "Registrar".to_string(), ), ("All".to_string(), "Explore all pallets and extrinsics".to_string()), @@ -605,13 +605,13 @@ mod tests { let mut cli = MockCli::new(); // Error, wrong name of the pallet. assert!( - matches!(call_config.prepare_extrinsic(&api, &mut cli).await, Err(message) if message.to_string().contains("Metadata Error: Pallet with name WrongName not found")) + matches!(call_config.prepare_extrinsic(&api, &mut cli).await, Err(message) if message.to_string().contains("Failed to encode call data. Metadata Error: Pallet with name WrongName not found")) ); let pallets = parse_chain_metadata(&api).await?; call_config.pallet = find_pallet_by_name(&pallets, "System").await?; // Error, wrong name of the extrinsic. assert!( - matches!(call_config.prepare_extrinsic(&api, &mut cli).await, Err(message) if message.to_string().contains("Metadata Error: Call with name WrongName not found")) + matches!(call_config.prepare_extrinsic(&api, &mut cli).await, Err(message) if message.to_string().contains("Failed to encode call data. Metadata Error: Call with name WrongName not found")) ); // Success, extrinsic and pallet specified. cli = MockCli::new().expect_info("Encoded call data: 0x00000411"); @@ -698,10 +698,10 @@ mod tests { true, Some( [ - ("Transfer Balance".to_string(), "Balances".to_string()), - ("Create an Asset".to_string(), "Assets".to_string()), - ("Mint an Asset".to_string(), "Assets".to_string()), - ("Create an NFT Collection".to_string(), "Nfts".to_string()), + ("Transfer balance".to_string(), "Balances".to_string()), + ("Create an asset".to_string(), "Assets".to_string()), + ("Mint an asset".to_string(), "Assets".to_string()), + ("Create an NFT collection".to_string(), "Nfts".to_string()), ("Mint an NFT".to_string(), "Nfts".to_string()), ("All".to_string(), "Explore all pallets and extrinsics".to_string()), ] diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs index d42bb5a00..73a351663 100644 --- a/crates/pop-parachains/src/call/mod.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -68,8 +68,9 @@ pub fn encode_call_data( api: &OnlineClient, tx: &DynamicPayload, ) -> Result { - let call_data = - tx.encode_call_data(&api.metadata()).map_err(|_| Error::CallDataEncodingError)?; + let call_data = tx + .encode_call_data(&api.metadata()) + .map_err(|e| Error::CallDataEncodingError(e.to_string()))?; Ok(format!("0x{}", hex::encode(call_data))) } @@ -82,7 +83,10 @@ mod tests { #[tokio::test] async fn set_up_api_works() -> Result<()> { - assert!(matches!(set_up_api("wss://wronguri.xyz").await, Err(Error::SubxtError(_)))); + assert!(matches!( + set_up_api("wss://wronguri.xyz").await, + Err(Error::ApiConnectionFailure(_)) + )); set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; Ok(()) } diff --git a/crates/pop-parachains/src/errors.rs b/crates/pop-parachains/src/errors.rs index 585e9f782..cadc8572e 100644 --- a/crates/pop-parachains/src/errors.rs +++ b/crates/pop-parachains/src/errors.rs @@ -11,8 +11,8 @@ pub enum Error { AnyhowError(#[from] anyhow::Error), #[error("Failed to establish a connection to the API: {0}")] ApiConnectionFailure(String), - #[error("Failed to encode call data for the extrinsic.")] - CallDataEncodingError, + #[error("Failed to encode call data. {0}")] + CallDataEncodingError(String), #[error("{0}")] CommonError(#[from] pop_common::Error), #[error("Configuration error: {0}")] From 7b8d3b617888314d8c19835d400adeaf814d4fe3 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 3 Dec 2024 11:08:44 +0100 Subject: [PATCH 163/211] fix: reset_for_new_call when extrinisc is not supported --- crates/pop-cli/src/commands/call/parachain.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 68b08bb1d..0b5356b95 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -155,6 +155,7 @@ impl CallParachainCommand { cli.outro_cancel( "The selected extrinsic is not supported yet. Please choose another one.", )?; + self.reset_for_new_call(); continue; } From 568f1ce5cc9dd40bd6c2c4f81c38e17b2c88c464 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 3 Dec 2024 12:57:01 +0100 Subject: [PATCH 164/211] fix: build with parachain features --- crates/pop-cli/src/commands/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/pop-cli/src/commands/mod.rs b/crates/pop-cli/src/commands/mod.rs index 9f00a8bdb..63b6104c6 100644 --- a/crates/pop-cli/src/commands/mod.rs +++ b/crates/pop-cli/src/commands/mod.rs @@ -26,9 +26,9 @@ pub(crate) enum Command { #[clap(alias = "b", about = about_build())] #[cfg(any(feature = "parachain", feature = "contract"))] Build(build::BuildArgs), - /// Call a smart contract. + /// Call a parachain or a smart contract. #[clap(alias = "c")] - #[cfg(feature = "contract")] + #[cfg(any(feature = "parachain", feature = "contract"))] Call(call::CallArgs), /// Launch a local network or deploy a smart contract. #[clap(alias = "u")] @@ -96,6 +96,7 @@ impl Command { build::Command::Spec(cmd) => cmd.execute().await.map(|_| Value::Null), }, }, + #[cfg(any(feature = "parachain", feature = "contract"))] Self::Call(args) => match args.command { #[cfg(feature = "parachain")] call::Command::Parachain(cmd) => cmd.execute().await.map(|_| Value::Null), From 60a0d092da38b38702d78715a21ad86135d43fa1 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 3 Dec 2024 15:04:58 +0100 Subject: [PATCH 165/211] test: wait before call parachain in integration test --- crates/pop-cli/tests/parachain.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/pop-cli/tests/parachain.rs b/crates/pop-cli/tests/parachain.rs index 1676bdef9..293c7509a 100644 --- a/crates/pop-cli/tests/parachain.rs +++ b/crates/pop-cli/tests/parachain.rs @@ -120,6 +120,11 @@ name = "collator-01" .spawn() .unwrap(); + // If after 20 secs is still running probably execution is ok, or waiting for user response + sleep(Duration::from_secs(20)).await; + + assert!(cmd.try_wait().unwrap().is_none(), "the process should still be running"); + // pop call parachain --pallet System --extrinsic remark --args "0x11" --url // ws://127.0.0.1:8833/ --suri //Alice --skip-confirm Command::cargo_bin("pop") @@ -142,10 +147,6 @@ name = "collator-01" .assert() .success(); - // If after 20 secs is still running probably execution is ok, or waiting for user response - sleep(Duration::from_secs(20)).await; - - assert!(cmd.try_wait().unwrap().is_none(), "the process should still be running"); // Stop the process Cmd::new("kill").args(["-s", "TERM", &cmd.id().to_string()]).spawn()?; From b97f6083af57526ea1ca9a3faef231e788e89c68 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 3 Dec 2024 15:17:14 +0100 Subject: [PATCH 166/211] docs: minor improvements --- crates/pop-contracts/src/utils/metadata.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pop-contracts/src/utils/metadata.rs b/crates/pop-contracts/src/utils/metadata.rs index 7e538450c..111468f0e 100644 --- a/crates/pop-contracts/src/utils/metadata.rs +++ b/crates/pop-contracts/src/utils/metadata.rs @@ -164,7 +164,7 @@ fn process_args( /// * `path` - Location path of the project or contract artifact. /// * `label` - Label of the contract message to retrieve. /// * `args` - Argument values provided by the user. -/// * `function_type` - Specifies whether to process arguments of messages or constructors +/// * `function_type` - Specifies whether to process arguments of messages or constructors. pub fn process_function_args

( path: P, label: &str, From e3778305b0f80aa1b3cc17ccbe9722761ab1f83a Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 3 Dec 2024 20:42:59 +0100 Subject: [PATCH 167/211] test: migrate find_free_port to pop_common --- crates/pop-cli/tests/parachain.rs | 34 +++++++++++++++++++++++----- crates/pop-common/src/lib.rs | 11 +++++++++ crates/pop-contracts/src/call.rs | 4 ++-- crates/pop-contracts/src/lib.rs | 2 +- crates/pop-contracts/src/node/mod.rs | 3 +-- crates/pop-contracts/src/testing.rs | 10 -------- crates/pop-contracts/src/up.rs | 4 ++-- 7 files changed, 45 insertions(+), 23 deletions(-) diff --git a/crates/pop-cli/tests/parachain.rs b/crates/pop-cli/tests/parachain.rs index 293c7509a..fb65aef57 100644 --- a/crates/pop-cli/tests/parachain.rs +++ b/crates/pop-cli/tests/parachain.rs @@ -2,13 +2,13 @@ use anyhow::Result; use assert_cmd::{cargo::cargo_bin, Command}; -use pop_common::templates::Template; +use pop_common::{find_free_port, templates::Template}; use pop_parachains::Parachain; use std::{fs, path::Path, process::Command as Cmd}; use strum::VariantArray; use tokio::time::{sleep, Duration}; -/// Test the parachain lifecycle: new, build, up +/// Test the parachain lifecycle: new, build, up, call #[tokio::test] async fn parachain_lifecycle() -> Result<()> { let temp = tempfile::tempdir().unwrap(); @@ -90,14 +90,17 @@ async fn parachain_lifecycle() -> Result<()> { // Overwrite the config file to manually set the port to test pop call parachain. let network_toml_path = temp_parachain_dir.join("network.toml"); fs::create_dir_all(&temp_parachain_dir)?; + let random_port = find_free_port(); + let localhost_url = format!("ws://127.0.0.1:{}", random_port); fs::write( &network_toml_path, - r#"[relaychain] + format!( + r#"[relaychain] chain = "paseo-local" [[relaychain.nodes]] name = "alice" -rpc_port = 8833 +rpc_port = {} validator = true [[relaychain.nodes]] @@ -111,6 +114,8 @@ default_command = "./target/release/parachain-template-node" [[parachains.collators]] name = "collator-01" "#, + random_port + ), )?; // pop up parachain -p "./test_parachain" @@ -126,7 +131,7 @@ name = "collator-01" assert!(cmd.try_wait().unwrap().is_none(), "the process should still be running"); // pop call parachain --pallet System --extrinsic remark --args "0x11" --url - // ws://127.0.0.1:8833/ --suri //Alice --skip-confirm + // ws://127.0.0.1:random_port --suri //Alice --skip-confirm Command::cargo_bin("pop") .unwrap() .args(&[ @@ -139,7 +144,24 @@ name = "collator-01" "--args", "0x11", "--url", - "ws://127.0.0.1:8833/", + &localhost_url, + "--suri", + "//Alice", + "--skip-confirm", + ]) + .assert() + .success(); + + // pop call parachain --call 0x00000411 --url ws://127.0.0.1:8833 --suri //Alice --skip-confirm + Command::cargo_bin("pop") + .unwrap() + .args(&[ + "call", + "parachain", + "--call", + "0x00000411", + "--url", + "ws://127.0.0.1:8833", "--suri", "//Alice", "--skip-confirm", diff --git a/crates/pop-common/src/lib.rs b/crates/pop-common/src/lib.rs index 55c049ae2..5866a0971 100644 --- a/crates/pop-common/src/lib.rs +++ b/crates/pop-common/src/lib.rs @@ -9,6 +9,8 @@ pub mod signer; pub mod sourcing; pub mod templates; +use std::net::TcpListener; + pub use build::Profile; pub use errors::Error; pub use git::{Git, GitHub, Release}; @@ -59,6 +61,15 @@ pub fn target() -> Result<&'static str, Error> { Err(Error::UnsupportedPlatform { arch: ARCH, os: OS }) } +/// Finds an available port by binding to port 0 and retrieving the assigned port. +pub fn find_free_port() -> u16 { + TcpListener::bind("127.0.0.1:0") + .expect("Failed to bind to an available port") + .local_addr() + .expect("Failed to retrieve local address") + .port() +} + #[cfg(test)] mod test { use super::*; diff --git a/crates/pop-contracts/src/call.rs b/crates/pop-contracts/src/call.rs index ab4230cd1..03effcb0a 100644 --- a/crates/pop-contracts/src/call.rs +++ b/crates/pop-contracts/src/call.rs @@ -179,10 +179,10 @@ mod tests { use crate::{ contracts_node_generator, dry_run_gas_estimate_instantiate, errors::Error, instantiate_smart_contract, mock_build_process, new_environment, run_contracts_node, - set_up_deployment, testing::find_free_port, UpOpts, + set_up_deployment, UpOpts, }; use anyhow::Result; - use pop_common::set_executable_permission; + use pop_common::{find_free_port, set_executable_permission}; use sp_core::Bytes; use std::{env, process::Command, time::Duration}; use tokio::time::sleep; diff --git a/crates/pop-contracts/src/lib.rs b/crates/pop-contracts/src/lib.rs index 84e576608..c5e76e2f1 100644 --- a/crates/pop-contracts/src/lib.rs +++ b/crates/pop-contracts/src/lib.rs @@ -20,7 +20,7 @@ pub use new::{create_smart_contract, is_valid_contract_name}; pub use node::{contracts_node_generator, is_chain_alive, run_contracts_node}; pub use templates::{Contract, ContractType}; pub use test::{test_e2e_smart_contract, test_smart_contract}; -pub use testing::{find_free_port, mock_build_process, new_environment}; +pub use testing::{mock_build_process, new_environment}; pub use up::{ dry_run_gas_estimate_instantiate, dry_run_upload, instantiate_smart_contract, set_up_deployment, set_up_upload, upload_smart_contract, UpOpts, diff --git a/crates/pop-contracts/src/node/mod.rs b/crates/pop-contracts/src/node/mod.rs index 77ac97cd5..be5680a04 100644 --- a/crates/pop-contracts/src/node/mod.rs +++ b/crates/pop-contracts/src/node/mod.rs @@ -166,10 +166,9 @@ fn release_directory_by_target(tag: Option<&str>) -> Result<&'static str, Error> #[cfg(test)] mod tests { - use crate::testing::find_free_port; - use super::*; use anyhow::{Error, Result}; + use pop_common::find_free_port; use std::process::Command; #[tokio::test] diff --git a/crates/pop-contracts/src/testing.rs b/crates/pop-contracts/src/testing.rs index c10bd4e5f..6a8abfcd9 100644 --- a/crates/pop-contracts/src/testing.rs +++ b/crates/pop-contracts/src/testing.rs @@ -4,7 +4,6 @@ use crate::{create_smart_contract, Contract}; use anyhow::Result; use std::{ fs::{copy, create_dir}, - net::TcpListener, path::Path, }; @@ -38,12 +37,3 @@ where copy(metadata_file, target_contract_dir.join("ink/testing.json"))?; Ok(()) } - -/// Finds an available port by binding to port 0 and retrieving the assigned port. -pub fn find_free_port() -> u16 { - TcpListener::bind("127.0.0.1:0") - .expect("Failed to bind to an available port") - .local_addr() - .expect("Failed to retrieve local address") - .port() -} diff --git a/crates/pop-contracts/src/up.rs b/crates/pop-contracts/src/up.rs index 287fca3c0..bed7dfa45 100644 --- a/crates/pop-contracts/src/up.rs +++ b/crates/pop-contracts/src/up.rs @@ -223,10 +223,10 @@ mod tests { use super::*; use crate::{ contracts_node_generator, errors::Error, mock_build_process, new_environment, - run_contracts_node, testing::find_free_port, + run_contracts_node, }; use anyhow::Result; - use pop_common::set_executable_permission; + use pop_common::{find_free_port, set_executable_permission}; use std::{env, process::Command, time::Duration}; use tokio::time::sleep; use url::Url; From 1c032bc79fa41571cf710e5789fe819fee3b1582 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Wed, 4 Dec 2024 11:28:10 +0100 Subject: [PATCH 168/211] test: fix increase waiting time --- crates/pop-cli/tests/parachain.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/pop-cli/tests/parachain.rs b/crates/pop-cli/tests/parachain.rs index fb65aef57..0a0743051 100644 --- a/crates/pop-cli/tests/parachain.rs +++ b/crates/pop-cli/tests/parachain.rs @@ -118,17 +118,15 @@ name = "collator-01" ), )?; - // pop up parachain -p "./test_parachain" + // pop up parachain -f ./network.toml --skip-confirm let mut cmd = Cmd::new(cargo_bin("pop")) .current_dir(&temp_parachain_dir) .args(&["up", "parachain", "-f", "./network.toml", "--skip-confirm"]) .spawn() .unwrap(); - // If after 20 secs is still running probably execution is ok, or waiting for user response - sleep(Duration::from_secs(20)).await; - - assert!(cmd.try_wait().unwrap().is_none(), "the process should still be running"); + // Wait for the networks to initialize. Increased timeout to accommodate CI environment delays. + sleep(Duration::from_secs(50)).await; // pop call parachain --pallet System --extrinsic remark --args "0x11" --url // ws://127.0.0.1:random_port --suri //Alice --skip-confirm @@ -152,7 +150,8 @@ name = "collator-01" .assert() .success(); - // pop call parachain --call 0x00000411 --url ws://127.0.0.1:8833 --suri //Alice --skip-confirm + // pop call parachain --call 0x00000411 --url ws://127.0.0.1:random_port --suri //Alice + // --skip-confirm Command::cargo_bin("pop") .unwrap() .args(&[ @@ -161,7 +160,7 @@ name = "collator-01" "--call", "0x00000411", "--url", - "ws://127.0.0.1:8833", + &localhost_url, "--suri", "//Alice", "--skip-confirm", @@ -169,6 +168,7 @@ name = "collator-01" .assert() .success(); + assert!(cmd.try_wait().unwrap().is_none(), "the process should still be running"); // Stop the process Cmd::new("kill").args(["-s", "TERM", &cmd.id().to_string()]).spawn()?; From b7c01692128f3c990178ac5b0e53914da6156509 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 5 Dec 2024 10:22:09 +0100 Subject: [PATCH 169/211] test: remove unnecesary test case --- crates/pop-cli/tests/parachain.rs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/crates/pop-cli/tests/parachain.rs b/crates/pop-cli/tests/parachain.rs index 0a0743051..ade5745a4 100644 --- a/crates/pop-cli/tests/parachain.rs +++ b/crates/pop-cli/tests/parachain.rs @@ -150,24 +150,6 @@ name = "collator-01" .assert() .success(); - // pop call parachain --call 0x00000411 --url ws://127.0.0.1:random_port --suri //Alice - // --skip-confirm - Command::cargo_bin("pop") - .unwrap() - .args(&[ - "call", - "parachain", - "--call", - "0x00000411", - "--url", - &localhost_url, - "--suri", - "//Alice", - "--skip-confirm", - ]) - .assert() - .success(); - assert!(cmd.try_wait().unwrap().is_none(), "the process should still be running"); // Stop the process Cmd::new("kill").args(["-s", "TERM", &cmd.id().to_string()]).spawn()?; From 0dce5f1e07887e9e7e42d60e7a78729a6710e965 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 5 Dec 2024 10:50:01 +0100 Subject: [PATCH 170/211] refactor: rename api with client --- crates/pop-cli/src/commands/call/parachain.rs | 82 +++++++++---------- crates/pop-common/src/metadata.rs | 4 +- .../src/call/metadata/action.rs | 11 +-- .../pop-parachains/src/call/metadata/mod.rs | 22 ++--- .../src/call/metadata/params.rs | 16 ++-- crates/pop-parachains/src/call/mod.rs | 33 ++++---- crates/pop-parachains/src/errors.rs | 4 +- crates/pop-parachains/src/lib.rs | 2 +- 8 files changed, 87 insertions(+), 87 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 0b5356b95..168e2be6a 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -5,7 +5,7 @@ use anyhow::{anyhow, Result}; use clap::Args; use pop_parachains::{ construct_extrinsic, encode_call_data, find_extrinsic_by_name, find_pallet_by_name, - parse_chain_metadata, set_up_api, sign_and_submit_extrinsic, supported_actions, Action, + parse_chain_metadata, set_up_client, sign_and_submit_extrinsic, supported_actions, Action, DynamicPayload, Extrinsic, OnlineClient, Pallet, Param, SubstrateConfig, }; use url::Url; @@ -59,8 +59,8 @@ impl CallParachainCommand { // Display the configured call. cli.info(call.display(&chain))?; // Prepare the extrinsic. - let tx = match call.prepare_extrinsic(&chain.api, &mut cli).await { - Ok(api) => api, + let tx = match call.prepare_extrinsic(&chain.client, &mut cli).await { + Ok(payload) => payload, Err(e) => { display_message(&e.to_string(), false, &mut cli)?; break; @@ -68,7 +68,7 @@ impl CallParachainCommand { }; // Send the extrinsic. - if let Err(e) = call.send_extrinsic(&chain.api, tx, &mut cli).await { + if let Err(e) = call.send_extrinsic(&chain.client, tx, &mut cli).await { display_message(&e.to_string(), false, &mut cli)?; break; } @@ -102,11 +102,11 @@ impl CallParachainCommand { }; // Parse metadata from chain url. - let api = set_up_api(url.as_str()).await?; - let pallets = parse_chain_metadata(&api).await.map_err(|e| { + let client = set_up_client(url.as_str()).await?; + let pallets = parse_chain_metadata(&client).await.map_err(|e| { anyhow!(format!("Unable to fetch the chain metadata: {}", e.to_string())) })?; - Ok(Chain { url, api, pallets }) + Ok(Chain { url, client, pallets }) } /// Configure the call based on command line arguments/call UI. @@ -163,7 +163,7 @@ impl CallParachainCommand { let args = if self.clone().args.is_empty() { let mut args = Vec::new(); for param in &extrinsic.params { - let input = prompt_for_param(&chain.api, cli, param)?; + let input = prompt_for_param(&chain.client, cli, param)?; args.push(input); } args @@ -207,7 +207,7 @@ impl CallParachainCommand { struct Chain { url: Url, - api: OnlineClient, + client: OnlineClient, pallets: Vec, } @@ -233,7 +233,7 @@ impl CallParachain { // Prepares the extrinsic or query. async fn prepare_extrinsic( &self, - api: &OnlineClient, + client: &OnlineClient, cli: &mut impl Cli, ) -> Result { let tx = match construct_extrinsic( @@ -248,14 +248,14 @@ impl CallParachain { return Err(anyhow!("Error: {}", e)); }, }; - cli.info(format!("Encoded call data: {}", encode_call_data(api, &tx)?))?; + cli.info(format!("Encoded call data: {}", encode_call_data(client, &tx)?))?; Ok(tx) } // Sign and submit an extrinsic. async fn send_extrinsic( &mut self, - api: &OnlineClient, + client: &OnlineClient, tx: DynamicPayload, cli: &mut impl Cli, ) -> Result<()> { @@ -276,7 +276,7 @@ impl CallParachain { } let spinner = cliclack::spinner(); spinner.start("Signing and submitting the extrinsic, please wait..."); - let result = sign_and_submit_extrinsic(api.clone(), tx, &self.suri) + let result = sign_and_submit_extrinsic(client.clone(), tx, &self.suri) .await .map_err(|err| anyhow!("{}", format!("{err:?}")))?; @@ -324,7 +324,7 @@ async fn prompt_predefined_actions( // Prompts the user for the value of a parameter. fn prompt_for_param( - api: &OnlineClient, + client: &OnlineClient, cli: &mut impl Cli, param: &Param, ) -> Result { @@ -338,27 +338,27 @@ fn prompt_for_param( { return Ok("None()".to_string()); } - let value = get_param_value(api, cli, param)?; + let value = get_param_value(client, cli, param)?; Ok(format!("Some({})", value)) } else { - get_param_value(api, cli, param) + get_param_value(client, cli, param) } } // Resolves the value of a parameter based on its type. fn get_param_value( - api: &OnlineClient, + client: &OnlineClient, cli: &mut impl Cli, param: &Param, ) -> Result { if param.sub_params.is_empty() { prompt_for_primitive_param(cli, param) } else if param.is_variant { - prompt_for_variant_param(api, cli, param) + prompt_for_variant_param(client, cli, param) } else if param.is_tuple { - prompt_for_tuple_param(api, cli, param) + prompt_for_tuple_param(client, cli, param) } else { - prompt_for_composite_param(api, cli, param) + prompt_for_composite_param(client, cli, param) } } @@ -373,7 +373,7 @@ fn prompt_for_primitive_param(cli: &mut impl Cli, param: &Param) -> Result, + client: &OnlineClient, cli: &mut impl Cli, param: &Param, ) -> Result { @@ -388,7 +388,7 @@ fn prompt_for_variant_param( if !selected_variant.sub_params.is_empty() { let mut field_values = Vec::new(); for field_arg in &selected_variant.sub_params { - let field_value = prompt_for_param(api, cli, field_arg)?; + let field_value = prompt_for_param(client, cli, field_arg)?; field_values.push(field_value); } Ok(format!("{}({})", selected_variant.name, field_values.join(", "))) @@ -399,13 +399,13 @@ fn prompt_for_variant_param( // Recursively prompt the user for all the nested fields in a Composite type. fn prompt_for_composite_param( - api: &OnlineClient, + client: &OnlineClient, cli: &mut impl Cli, param: &Param, ) -> Result { let mut field_values = Vec::new(); for field_arg in ¶m.sub_params { - let field_value = prompt_for_param(api, cli, field_arg)?; + let field_value = prompt_for_param(client, cli, field_arg)?; // Example: Param { name: "Id", type_name: "AccountId32 ([u8;32])", is_optional: false, // sub_params: [Param { name: "Id", type_name: "[u8;32]", is_optional: false, sub_params: // [], is_variant: false }], is_variant: false } @@ -424,13 +424,13 @@ fn prompt_for_composite_param( // Recursively prompt the user for the tuple values. fn prompt_for_tuple_param( - api: &OnlineClient, + client: &OnlineClient, cli: &mut impl Cli, param: &Param, ) -> Result { let mut tuple_values = Vec::new(); for tuple_param in param.sub_params.iter() { - let tuple_value = prompt_for_param(api, cli, tuple_param)?; + let tuple_value = prompt_for_param(client, cli, tuple_param)?; tuple_values.push(tuple_value); } Ok(format!("({})", tuple_values.join(", "))) @@ -586,7 +586,7 @@ mod tests { #[tokio::test] async fn prepare_extrinsic_works() -> Result<()> { - let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; let mut call_config = CallParachain { pallet: Pallet { name: "WrongName".to_string(), @@ -606,18 +606,18 @@ mod tests { let mut cli = MockCli::new(); // Error, wrong name of the pallet. assert!( - matches!(call_config.prepare_extrinsic(&api, &mut cli).await, Err(message) if message.to_string().contains("Failed to encode call data. Metadata Error: Pallet with name WrongName not found")) + matches!(call_config.prepare_extrinsic(&client, &mut cli).await, Err(message) if message.to_string().contains("Failed to encode call data. Metadata Error: Pallet with name WrongName not found")) ); - let pallets = parse_chain_metadata(&api).await?; + let pallets = parse_chain_metadata(&client).await?; call_config.pallet = find_pallet_by_name(&pallets, "System").await?; // Error, wrong name of the extrinsic. assert!( - matches!(call_config.prepare_extrinsic(&api, &mut cli).await, Err(message) if message.to_string().contains("Failed to encode call data. Metadata Error: Call with name WrongName not found")) + matches!(call_config.prepare_extrinsic(&client, &mut cli).await, Err(message) if message.to_string().contains("Failed to encode call data. Metadata Error: Call with name WrongName not found")) ); // Success, extrinsic and pallet specified. cli = MockCli::new().expect_info("Encoded call data: 0x00000411"); call_config.extrinsic = find_extrinsic_by_name(&pallets, "System", "remark").await?; - let tx = call_config.prepare_extrinsic(&api, &mut cli).await?; + let tx = call_config.prepare_extrinsic(&client, &mut cli).await?; assert_eq!(tx.call_name(), "remark"); assert_eq!(tx.pallet_name(), "System"); @@ -626,8 +626,8 @@ mod tests { #[tokio::test] async fn user_cancel_send_extrinsic_works() -> Result<()> { - let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; - let pallets = parse_chain_metadata(&api).await?; + let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; + let pallets = parse_chain_metadata(&client).await?; let mut call_config = CallParachain { pallet: find_pallet_by_name(&pallets, "System").await?, extrinsic: find_extrinsic_by_name(&pallets, "System", "remark").await?, @@ -640,8 +640,8 @@ mod tests { .expect_outro_cancel( "Extrinsic remark was not submitted. Operation canceled by the user.", ); - let tx = call_config.prepare_extrinsic(&api, &mut cli).await?; - call_config.send_extrinsic(&api, tx, &mut cli).await?; + let tx = call_config.prepare_extrinsic(&client, &mut cli).await?; + call_config.send_extrinsic(&client, tx, &mut cli).await?; cli.verify() } @@ -691,8 +691,8 @@ mod tests { #[tokio::test] async fn prompt_predefined_actions_works() -> Result<()> { - let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; - let pallets = parse_chain_metadata(&api).await?; + let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; + let pallets = parse_chain_metadata(&client).await?; let mut cli = MockCli::new().expect_select::( "What would you like to do?", Some(true), @@ -717,8 +717,8 @@ mod tests { #[tokio::test] async fn prompt_for_param_works() -> Result<()> { - let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; - let pallets = parse_chain_metadata(&api).await?; + let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; + let pallets = parse_chain_metadata(&client).await?; // Using NFT mint extrinsic to test the majority of subfunctions let extrinsic = find_extrinsic_by_name(&pallets, "Nfts", "mint").await?; let mut cli = MockCli::new() @@ -760,7 +760,7 @@ mod tests { // Test all the extrinsic params let mut params: Vec = Vec::new(); for param in extrinsic.params { - params.push(prompt_for_param(&api, &mut cli, ¶m)?); + params.push(prompt_for_param(&client, &mut cli, ¶m)?); } assert_eq!(params.len(), 4); assert_eq!(params[0], "0".to_string()); // collection: test primitive @@ -786,7 +786,7 @@ mod tests { // Test all the extrinsic params let mut params: Vec = Vec::new(); for param in extrinsic.params { - params.push(prompt_for_param(&api, &mut cli, ¶m)?); + params.push(prompt_for_param(&client, &mut cli, ¶m)?); } assert_eq!(params.len(), 3); assert_eq!(params[0], "(0, 0)".to_string()); // task: test tuples diff --git a/crates/pop-common/src/metadata.rs b/crates/pop-common/src/metadata.rs index 53ff8dffc..6a4f79a85 100644 --- a/crates/pop-common/src/metadata.rs +++ b/crates/pop-common/src/metadata.rs @@ -163,9 +163,9 @@ mod tests { #[tokio::test] async fn format_type_works() -> Result<()> { - let api = + let client = OnlineClient::::from_url("wss://rpc1.paseo.popnetwork.xyz").await?; - let metadata = api.metadata(); + let metadata = client.metadata(); let registry = metadata.types(); // Validate `Nfts::mint` extrinsic types cover most of cases. diff --git a/crates/pop-parachains/src/call/metadata/action.rs b/crates/pop-parachains/src/call/metadata/action.rs index 7ee4440df..8fd1d21f4 100644 --- a/crates/pop-parachains/src/call/metadata/action.rs +++ b/crates/pop-parachains/src/call/metadata/action.rs @@ -123,7 +123,7 @@ pub async fn supported_actions(pallets: &[Pallet]) -> Vec { #[cfg(test)] mod tests { use super::*; - use crate::{parse_chain_metadata, set_up_api}; + use crate::{parse_chain_metadata, set_up_client}; use anyhow::Result; use std::collections::HashMap; @@ -184,8 +184,9 @@ mod tests { #[tokio::test] async fn supported_actions_works() -> Result<()> { // Test Pop Parachain. - let mut api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; - let mut actions = supported_actions(&parse_chain_metadata(&api).await?).await; + let mut client: subxt::OnlineClient = + set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; + let mut actions = supported_actions(&parse_chain_metadata(&client).await?).await; assert_eq!(actions.len(), 5); assert_eq!(actions[0], Action::Transfer); assert_eq!(actions[1], Action::CreateAsset); @@ -194,8 +195,8 @@ mod tests { assert_eq!(actions[4], Action::MintNFT); // Test Polkadot Relay Chain. - api = set_up_api("wss://polkadot-rpc.publicnode.com").await?; - actions = supported_actions(&parse_chain_metadata(&api).await?).await; + client = set_up_client("wss://polkadot-rpc.publicnode.com").await?; + actions = supported_actions(&parse_chain_metadata(&client).await?).await; assert_eq!(actions.len(), 4); assert_eq!(actions[0], Action::Transfer); assert_eq!(actions[1], Action::PurchaseOnDemandCoretime); diff --git a/crates/pop-parachains/src/call/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs index 30902676d..4c8b1addb 100644 --- a/crates/pop-parachains/src/call/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -49,11 +49,11 @@ impl Display for Extrinsic { /// parameters. /// /// # Arguments -/// * `api`: Reference to an `OnlineClient` connected to the chain. +/// * `client`: Reference to an `OnlineClient` connected to the chain. pub async fn parse_chain_metadata( - api: &OnlineClient, + client: &OnlineClient, ) -> Result, Error> { - let metadata: Metadata = api.metadata(); + let metadata: Metadata = client.metadata(); let pallets = metadata .pallets() @@ -70,7 +70,7 @@ pub async fn parse_chain_metadata( let params = { let mut parsed_params = Vec::new(); for field in &variant.fields { - match params::field_to_param(api, field) { + match params::field_to_param(client, field) { Ok(param) => parsed_params.push(param), Err(_) => { // If an error occurs while parsing the values, mark the @@ -165,14 +165,14 @@ pub async fn parse_extrinsic_arguments(raw_params: Vec) -> Result Result<()> { - let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; - let pallets = parse_chain_metadata(&api).await?; + let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; + let pallets = parse_chain_metadata(&client).await?; // Test the first pallet is parsed correctly let first_pallet = pallets.first().unwrap(); assert_eq!(first_pallet.name, "System"); @@ -196,8 +196,8 @@ mod tests { #[tokio::test] async fn find_pallet_by_name_works() -> Result<()> { - let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; - let pallets = parse_chain_metadata(&api).await?; + let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; + let pallets = parse_chain_metadata(&client).await?; assert!(matches!( find_pallet_by_name(&pallets, "WrongName").await, Err(Error::PalletNotFound(pallet)) if pallet == "WrongName".to_string())); @@ -209,8 +209,8 @@ mod tests { #[tokio::test] async fn find_extrinsic_by_name_works() -> Result<()> { - let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; - let pallets = parse_chain_metadata(&api).await?; + let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; + let pallets = parse_chain_metadata(&client).await?; assert!(matches!( find_extrinsic_by_name(&pallets, "WrongName", "wrong_extrinsic").await, Err(Error::PalletNotFound(pallet)) if pallet == "WrongName".to_string())); diff --git a/crates/pop-parachains/src/call/metadata/params.rs b/crates/pop-parachains/src/call/metadata/params.rs index c5b04007f..a3b3033c6 100644 --- a/crates/pop-parachains/src/call/metadata/params.rs +++ b/crates/pop-parachains/src/call/metadata/params.rs @@ -25,13 +25,13 @@ pub struct Param { /// Transforms a metadata field into its `Param` representation. /// /// # Arguments -/// * `api`: Reference to an `OnlineClient` connected to the blockchain. +/// * `client`: Reference to an `OnlineClient` connected to the blockchain. /// * `field`: A reference to a metadata field of the extrinsic. pub fn field_to_param( - api: &OnlineClient, + client: &OnlineClient, field: &Field, ) -> Result { - let metadata: Metadata = api.metadata(); + let metadata: Metadata = client.metadata(); let registry = metadata.types(); let name = field.name.clone().unwrap_or("Unnamed".to_string()); //It can be unnamed field type_to_param(name, registry, field.ty.id) @@ -179,13 +179,13 @@ fn type_to_param(name: String, registry: &PortableRegistry, type_id: u32) -> Res #[cfg(test)] mod tests { use super::*; - use crate::set_up_api; + use crate::set_up_client; use anyhow::Result; #[tokio::test] async fn field_to_param_works() -> Result<()> { - let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; - let metadata = api.metadata(); + let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; + let metadata = client.metadata(); // Test a supported extrinsic let extrinsic = metadata .pallet_by_name("Balances") @@ -194,7 +194,7 @@ mod tests { .unwrap(); let mut params = Vec::new(); for field in &extrinsic.fields { - params.push(field_to_param(&api, field)?) + params.push(field_to_param(&client, field)?) } assert_eq!(params.len(), 3); assert_eq!(params.first().unwrap().name, "source"); @@ -232,7 +232,7 @@ mod tests { let extrinsic = metadata.pallet_by_name("Sudo").unwrap().call_variant_by_name("sudo").unwrap(); assert!(matches!( - field_to_param(&api, &extrinsic.fields.first().unwrap()), + field_to_param(&client, &extrinsic.fields.first().unwrap()), Err(Error::ExtrinsicNotSupported) )); Ok(()) diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs index 73a351663..040d8f8d5 100644 --- a/crates/pop-parachains/src/call/mod.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -14,11 +14,10 @@ pub mod metadata; /// /// # Arguments /// * `url` - Endpoint of the node. -pub async fn set_up_api(url: &str) -> Result, Error> { - let api = OnlineClient::::from_url(url) +pub async fn set_up_client(url: &str) -> Result, Error> { + OnlineClient::::from_url(url) .await - .map_err(|e| Error::ApiConnectionFailure(e.to_string()))?; - Ok(api) + .map_err(|e| Error::ConnectionFailure(e.to_string())) } /// Constructs a dynamic extrinsic payload for a specified pallet and extrinsic. @@ -39,16 +38,16 @@ pub async fn construct_extrinsic( /// Signs and submits a given extrinsic to the blockchain. /// /// # Arguments -/// * `api` - Reference to an `OnlineClient` connected to the chain. +/// * `client` - Reference to an `OnlineClient` connected to the chain. /// * `tx` - The transaction to be signed and submitted. /// * `suri` - The secret URI (e.g., mnemonic or private key) for signing the extrinsic. pub async fn sign_and_submit_extrinsic( - api: OnlineClient, + client: OnlineClient, tx: DynamicPayload, suri: &str, ) -> Result { let signer = create_signer(suri)?; - let result = api + let result = client .tx() .sign_and_submit_then_watch_default(&tx, &signer) .await @@ -62,14 +61,14 @@ pub async fn sign_and_submit_extrinsic( /// Encodes the call data for a given extrinsic into a hexadecimal string. /// /// # Arguments -/// * `api` - Reference to an `OnlineClient` connected to the chain. +/// * `client` - Reference to an `OnlineClient` connected to the chain. /// * `tx` - The transaction whose call data will be encoded and returned. pub fn encode_call_data( - api: &OnlineClient, + client: &OnlineClient, tx: &DynamicPayload, ) -> Result { let call_data = tx - .encode_call_data(&api.metadata()) + .encode_call_data(&client.metadata()) .map_err(|e| Error::CallDataEncodingError(e.to_string()))?; Ok(format!("0x{}", hex::encode(call_data))) } @@ -78,16 +77,16 @@ pub fn encode_call_data( mod tests { use super::*; - use crate::set_up_api; + use crate::set_up_client; use anyhow::Result; #[tokio::test] - async fn set_up_api_works() -> Result<()> { + async fn set_up_client_works() -> Result<()> { assert!(matches!( - set_up_api("wss://wronguri.xyz").await, - Err(Error::ApiConnectionFailure(_)) + set_up_client("wss://wronguri.xyz").await, + Err(Error::ConnectionFailure(_)) )); - set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; Ok(()) } @@ -120,9 +119,9 @@ mod tests { #[tokio::test] async fn encode_call_data_works() -> Result<()> { - let api = set_up_api("wss://rpc1.paseo.popnetwork.xyz").await?; + let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; let extrinsic = construct_extrinsic("System", "remark", vec!["0x11".to_string()]).await?; - assert_eq!(encode_call_data(&api, &extrinsic)?, "0x00000411"); + assert_eq!(encode_call_data(&client, &extrinsic)?, "0x00000411"); Ok(()) } } diff --git a/crates/pop-parachains/src/errors.rs b/crates/pop-parachains/src/errors.rs index cadc8572e..5348d7b43 100644 --- a/crates/pop-parachains/src/errors.rs +++ b/crates/pop-parachains/src/errors.rs @@ -9,12 +9,12 @@ pub enum Error { Aborted, #[error("Anyhow error: {0}")] AnyhowError(#[from] anyhow::Error), - #[error("Failed to establish a connection to the API: {0}")] - ApiConnectionFailure(String), #[error("Failed to encode call data. {0}")] CallDataEncodingError(String), #[error("{0}")] CommonError(#[from] pop_common::Error), + #[error("Failed to establish a connection to: {0}")] + ConnectionFailure(String), #[error("Configuration error: {0}")] Config(String), #[error("Failed to access the current directory")] diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index b1e48a599..4f9a4e7ac 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -23,7 +23,7 @@ pub use call::{ params::Param, parse_chain_metadata, Extrinsic, Pallet, }, - set_up_api, sign_and_submit_extrinsic, + set_up_client, sign_and_submit_extrinsic, }; pub use errors::Error; pub use indexmap::IndexSet; From 3f60dcd525073930982aa716004ac8a5838d9c4d Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Thu, 5 Dec 2024 11:04:02 +0100 Subject: [PATCH 171/211] refactor: naming and docs --- crates/pop-cli/src/commands/call/parachain.rs | 8 ++++---- crates/pop-common/src/errors.rs | 3 +++ crates/pop-parachains/src/errors.rs | 9 ++++++++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 168e2be6a..7d2484206 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -68,7 +68,7 @@ impl CallParachainCommand { }; // Send the extrinsic. - if let Err(e) = call.send_extrinsic(&chain.client, tx, &mut cli).await { + if let Err(e) = call.submit_extrinsic(&chain.client, tx, &mut cli).await { display_message(&e.to_string(), false, &mut cli)?; break; } @@ -253,7 +253,7 @@ impl CallParachain { } // Sign and submit an extrinsic. - async fn send_extrinsic( + async fn submit_extrinsic( &mut self, client: &OnlineClient, tx: DynamicPayload, @@ -625,7 +625,7 @@ mod tests { } #[tokio::test] - async fn user_cancel_send_extrinsic_works() -> Result<()> { + async fn user_cancel_submit_extrinsic_works() -> Result<()> { let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; let pallets = parse_chain_metadata(&client).await?; let mut call_config = CallParachain { @@ -641,7 +641,7 @@ mod tests { "Extrinsic remark was not submitted. Operation canceled by the user.", ); let tx = call_config.prepare_extrinsic(&client, &mut cli).await?; - call_config.send_extrinsic(&client, tx, &mut cli).await?; + call_config.submit_extrinsic(&client, tx, &mut cli).await?; cli.verify() } diff --git a/crates/pop-common/src/errors.rs b/crates/pop-common/src/errors.rs index e8924290f..89aa48f66 100644 --- a/crates/pop-common/src/errors.rs +++ b/crates/pop-common/src/errors.rs @@ -13,14 +13,17 @@ pub enum Error { Git(String), #[error("IO error: {0}")] IO(#[from] std::io::Error), + /// An error occurred while attempting to create a keypair from the provided URI. #[error("Failed to create keypair from URI: {0}")] KeyPairCreation(String), #[error("Manifest error: {0}")] ManifestError(#[from] cargo_toml::Error), + /// An error occurred while attempting to retrieve the manifest path. #[error("Failed to get manifest path: {0}")] ManifestPath(String), #[error("ParseError error: {0}")] ParseError(#[from] url::ParseError), + /// An error occurred while parsing the provided secret URI. #[error("Failed to parse secret URI: {0}")] ParseSecretURI(String), #[error("SourceError error: {0}")] diff --git a/crates/pop-parachains/src/errors.rs b/crates/pop-parachains/src/errors.rs index 5348d7b43..a8f3c3e7c 100644 --- a/crates/pop-parachains/src/errors.rs +++ b/crates/pop-parachains/src/errors.rs @@ -9,10 +9,12 @@ pub enum Error { Aborted, #[error("Anyhow error: {0}")] AnyhowError(#[from] anyhow::Error), + /// An error occurred while encoding the call data. #[error("Failed to encode call data. {0}")] CallDataEncodingError(String), #[error("{0}")] CommonError(#[from] pop_common::Error), + /// An error occurred while attempting to establish a connection to the endpoint. #[error("Failed to establish a connection to: {0}")] ConnectionFailure(String), #[error("Configuration error: {0}")] @@ -21,15 +23,18 @@ pub enum Error { CurrentDirAccess, #[error("Failed to parse the endowment value")] EndowmentError, + /// The extrinsic is not supported. #[error("The extrinsic is not supported")] ExtrinsicNotSupported, + /// An error occurred during the submission of an extrinsic. #[error("Extrinsic submission error: {0}")] ExtrinsicSubmissionError(String), #[error("IO error: {0}")] IO(#[from] std::io::Error), #[error("JSON error: {0}")] JsonError(#[from] serde_json::Error), - #[error("Error parsing metadata for parameter {0} conversion")] + /// An error occurred while parsing metadata of a parameter. + #[error("Error parsing metadata for parameter {0}")] MetadataParsingError(String), #[error("Missing binary: {0}")] MissingBinary(String), @@ -41,8 +46,10 @@ pub enum Error { OrchestratorError(#[from] OrchestratorError), #[error("Failed to create pallet directory")] PalletDirCreation, + /// The specified pallet could not be found. #[error("Failed to find the pallet {0}")] PalletNotFound(String), + /// An error occurred while processing the arguments provided by the user. #[error("Failed to process the arguments provided by the user.")] ParamProcessingError, #[error("Invalid path")] From 7d7df02deee57a437203a1de8f2dbfe52b38b4d7 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 6 Dec 2024 09:03:09 +0100 Subject: [PATCH 172/211] docs: improve docs and missing comments --- crates/pop-cli/src/commands/call/mod.rs | 2 +- crates/pop-cli/src/commands/call/parachain.rs | 55 +++++++++++++------ crates/pop-cli/tests/parachain.rs | 8 +-- crates/pop-common/src/errors.rs | 1 + crates/pop-contracts/src/errors.rs | 1 + .../src/call/metadata/action.rs | 1 - .../src/call/metadata/params.rs | 2 +- crates/pop-parachains/src/errors.rs | 1 + 8 files changed, 46 insertions(+), 25 deletions(-) diff --git a/crates/pop-cli/src/commands/call/mod.rs b/crates/pop-cli/src/commands/call/mod.rs index 241ebbdb9..5f28ab899 100644 --- a/crates/pop-cli/src/commands/call/mod.rs +++ b/crates/pop-cli/src/commands/call/mod.rs @@ -18,7 +18,7 @@ pub(crate) struct CallArgs { /// Call a smart contract. #[derive(Subcommand)] pub(crate) enum Command { - /// Call a parachain + /// Call a parachain. #[cfg(feature = "parachain")] #[clap(alias = "p")] Parachain(parachain::CallParachainCommand), diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 7d2484206..cfc784958 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -13,6 +13,7 @@ use url::Url; const DEFAULT_URL: &str = "ws://localhost:9944/"; const DEFAULT_URI: &str = "//Alice"; +/// Command to execute extrinsics with configurable pallets, arguments, and signing options. #[derive(Args, Clone)] pub struct CallParachainCommand { /// The pallet containing the extrinsic to execute. @@ -67,7 +68,7 @@ impl CallParachainCommand { }, }; - // Send the extrinsic. + // Sign and submit the extrinsic. if let Err(e) = call.submit_extrinsic(&chain.client, tx, &mut cli).await { display_message(&e.to_string(), false, &mut cli)?; break; @@ -78,7 +79,7 @@ impl CallParachainCommand { .initial_value(false) .interact()? { - display_message("Parachain calling complete.", true, &mut cli)?; + display_message("Call complete.", true, &mut cli)?; break; } self.reset_for_new_call(); @@ -86,6 +87,7 @@ impl CallParachainCommand { Ok(()) } + // Configures the chain by resolving the URL and fetching its metadata. async fn configure_chain(&self, cli: &mut impl Cli) -> Result { cli.intro("Call a parachain")?; // Resolve url. @@ -109,7 +111,7 @@ impl CallParachainCommand { Ok(Chain { url, client, pallets }) } - /// Configure the call based on command line arguments/call UI. + // Configure the call based on command line arguments/call UI. async fn configure_call(&mut self, chain: &Chain, cli: &mut impl Cli) -> Result { loop { // Resolve pallet. @@ -188,14 +190,14 @@ impl CallParachainCommand { } } - /// Resets specific fields to default values for a new call. + // Resets specific fields to default values for a new call. fn reset_for_new_call(&mut self) { self.pallet = None; self.extrinsic = None; self.args.clear(); } - // Function to check if all required fields are specified + // Function to check if all required fields are specified. fn requires_user_input(&self) -> bool { self.pallet.is_none() || self.extrinsic.is_none() || @@ -205,12 +207,18 @@ impl CallParachainCommand { } } +/// Represents a chain, including its URL, client connection, and available pallets. struct Chain { + /// Websocket endpoint of the node. url: Url, + /// The client used to interact with the chain. client: OnlineClient, + /// A list of pallets available on the chain. pallets: Vec, } +/// Represents a configured extrinsic call, including the pallet, extrinsic, arguments, and signing +/// options. #[derive(Clone)] struct CallParachain { /// The pallet of the extrinsic. @@ -296,6 +304,7 @@ impl CallParachain { } } +// 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 { cli.outro(message)?; @@ -362,7 +371,7 @@ fn get_param_value( } } -// Prompt for the value when is a primitive. +// Prompt for the value when it is a primitive. fn prompt_for_primitive_param(cli: &mut impl Cli, param: &Param) -> Result { Ok(cli .input(format!("Enter the value for the parameter: {}", param.name)) @@ -370,8 +379,9 @@ fn prompt_for_primitive_param(cli: &mut impl Cli, param: &Param) -> Result, cli: &mut impl Cli, @@ -397,7 +407,23 @@ fn prompt_for_variant_param( } } -// Recursively prompt the user for all the nested fields in a Composite type. +// Recursively prompt the user for all the nested fields in a composite type. +// Example of a composite definition: +// Param { +// name: "Id", +// type_name: "AccountId32 ([u8;32])", +// is_optional: false, +// sub_params: [ +// Param { +// name: "Id", +// type_name: "[u8;32]", +// is_optional: false, +// sub_params: [], +// is_variant: false +// } +// ], +// is_variant: false +// } fn prompt_for_composite_param( client: &OnlineClient, cli: &mut impl Cli, @@ -406,9 +432,6 @@ fn prompt_for_composite_param( let mut field_values = Vec::new(); for field_arg in ¶m.sub_params { let field_value = prompt_for_param(client, cli, field_arg)?; - // Example: Param { name: "Id", type_name: "AccountId32 ([u8;32])", is_optional: false, - // sub_params: [Param { name: "Id", type_name: "[u8;32]", is_optional: false, sub_params: - // [], is_variant: false }], is_variant: false } if param.sub_params.len() == 1 && param.name == param.sub_params[0].name { field_values.push(field_value); } else { @@ -436,7 +459,7 @@ fn prompt_for_tuple_param( Ok(format!("({})", tuple_values.join(", "))) } -/// Parser to capitalize the first letter of the pallet name. +// Parser to capitalize the first letter of the pallet name. fn parse_pallet_name(name: &str) -> Result { let mut chars = name.chars(); match chars.next() { @@ -445,7 +468,7 @@ fn parse_pallet_name(name: &str) -> Result { } } -/// Parser to convert the extrinsic name to lowercase. +// Parser to convert the extrinsic name to lowercase. fn parse_extrinsic_name(name: &str) -> Result { Ok(name.to_ascii_lowercase()) } @@ -475,8 +498,6 @@ mod tests { cli.verify() } - // This test only covers the interactive portion of the call parachain command, without actually - // submitting any extrinsic. #[tokio::test] async fn guide_user_to_call_parachain_works() -> Result<()> { // Test all process specifying pallet, and see the prompted extrinsics. @@ -529,8 +550,6 @@ mod tests { cli.verify() } - // This test only covers the interactive portion of the call parachain command selecting one of - // the predefined actions, without actually submitting any extrinsic. #[tokio::test] async fn guide_user_to_configure_predefined_action_works() -> Result<()> { let mut call_config = CallParachainCommand { diff --git a/crates/pop-cli/tests/parachain.rs b/crates/pop-cli/tests/parachain.rs index ade5745a4..2aa87c31c 100644 --- a/crates/pop-cli/tests/parachain.rs +++ b/crates/pop-cli/tests/parachain.rs @@ -8,7 +8,7 @@ use std::{fs, path::Path, process::Command as Cmd}; use strum::VariantArray; use tokio::time::{sleep, Duration}; -/// Test the parachain lifecycle: new, build, up, call +/// Test the parachain lifecycle: new, build, up, call. #[tokio::test] async fn parachain_lifecycle() -> Result<()> { let temp = tempfile::tempdir().unwrap(); @@ -118,7 +118,7 @@ name = "collator-01" ), )?; - // pop up parachain -f ./network.toml --skip-confirm + // `pop up parachain -f ./network.toml --skip-confirm` let mut cmd = Cmd::new(cargo_bin("pop")) .current_dir(&temp_parachain_dir) .args(&["up", "parachain", "-f", "./network.toml", "--skip-confirm"]) @@ -128,8 +128,8 @@ name = "collator-01" // Wait for the networks to initialize. Increased timeout to accommodate CI environment delays. sleep(Duration::from_secs(50)).await; - // pop call parachain --pallet System --extrinsic remark --args "0x11" --url - // ws://127.0.0.1:random_port --suri //Alice --skip-confirm + // `pop call parachain --pallet System --extrinsic remark --args "0x11" --url + // ws://127.0.0.1:random_port --suri //Alice --skip-confirm` Command::cargo_bin("pop") .unwrap() .args(&[ diff --git a/crates/pop-common/src/errors.rs b/crates/pop-common/src/errors.rs index 89aa48f66..ceef11500 100644 --- a/crates/pop-common/src/errors.rs +++ b/crates/pop-common/src/errors.rs @@ -3,6 +3,7 @@ use crate::{sourcing, templates}; use thiserror::Error; +/// Represents the various errors that can occur in the crate. #[derive(Error, Debug)] pub enum Error { #[error("Anyhow error: {0}")] diff --git a/crates/pop-contracts/src/errors.rs b/crates/pop-contracts/src/errors.rs index bd387abf0..e7ddec21b 100644 --- a/crates/pop-contracts/src/errors.rs +++ b/crates/pop-contracts/src/errors.rs @@ -3,6 +3,7 @@ use pop_common::sourcing::Error as SourcingError; use thiserror::Error; +/// Represents the various errors that can occur in the crate. #[derive(Error, Debug)] #[allow(clippy::enum_variant_names)] pub enum Error { diff --git a/crates/pop-parachains/src/call/metadata/action.rs b/crates/pop-parachains/src/call/metadata/action.rs index 8fd1d21f4..108f3afaa 100644 --- a/crates/pop-parachains/src/call/metadata/action.rs +++ b/crates/pop-parachains/src/call/metadata/action.rs @@ -105,7 +105,6 @@ impl Action { /// Fetch the list of supported actions based on available pallets. /// /// # Arguments -/// /// * `pallets`: List of pallets availables in the chain. pub async fn supported_actions(pallets: &[Pallet]) -> Vec { let mut actions = Vec::new(); diff --git a/crates/pop-parachains/src/call/metadata/params.rs b/crates/pop-parachains/src/call/metadata/params.rs index a3b3033c6..2419fbf04 100644 --- a/crates/pop-parachains/src/call/metadata/params.rs +++ b/crates/pop-parachains/src/call/metadata/params.rs @@ -228,7 +228,7 @@ mod tests { .type_name, "AccountId32 ([u8;32])" ); - // Test a extrinsic not supported + // Test an extrinsic that is not supported. let extrinsic = metadata.pallet_by_name("Sudo").unwrap().call_variant_by_name("sudo").unwrap(); assert!(matches!( diff --git a/crates/pop-parachains/src/errors.rs b/crates/pop-parachains/src/errors.rs index a8f3c3e7c..fdef8ac79 100644 --- a/crates/pop-parachains/src/errors.rs +++ b/crates/pop-parachains/src/errors.rs @@ -3,6 +3,7 @@ use thiserror::Error; use zombienet_sdk::OrchestratorError; +/// Represents the various errors that can occur in the crate. #[derive(Error, Debug)] pub enum Error { #[error("User aborted due to existing target directory.")] From 31f3b8d90bf1b6d334eb9bb437349b6d7b3152b3 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 6 Dec 2024 10:10:06 +0100 Subject: [PATCH 173/211] test: remove unnecesary verbose --- crates/pop-cli/src/commands/call/parachain.rs | 59 ++++++++++--------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 0e2e46ae5..11cfedf4f 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -500,7 +500,6 @@ mod tests { #[tokio::test] async fn guide_user_to_call_parachain_works() -> Result<()> { - // Test all process specifying pallet, and see the prompted extrinsics. let mut call_config = CallParachainCommand { pallet: Some("System".to_string()), extrinsic: None, @@ -561,28 +560,31 @@ mod tests { skip_confirm: false, }; + let mut cli = MockCli::new().expect_intro("Call a parachain").expect_input( + "Which chain would you like to interact with?", + "wss://polkadot-rpc.publicnode.com".into(), + ); + let chain = call_config.configure_chain(&mut cli).await?; + assert_eq!(chain.url, Url::parse("wss://polkadot-rpc.publicnode.com")?); + cli.verify()?; + let mut cli = MockCli::new() - .expect_intro("Call a parachain") - .expect_input( - "Which chain would you like to interact with?", - "wss://polkadot-rpc.publicnode.com".into(), - ) .expect_select::( "What would you like to do?", Some(true), true, Some( - [ - ("Transfer balance".to_string(), "Balances".to_string()), - ("Purchase on-demand coretime".to_string(), "OnDemand".to_string()), - ("Reserve a parachain ID".to_string(), "Registrar".to_string()), - ( - "Register a parachain ID with genesis state and code".to_string(), - "Registrar".to_string(), - ), - ("All".to_string(), "Explore all pallets and extrinsics".to_string()), - ] - .to_vec(), + supported_actions(&chain.pallets) + .await + .into_iter() + .map(|action| { + (action.description().to_string(), action.pallet_name().to_string()) + }) + .chain(std::iter::once(( + "All".to_string(), + "Explore all pallets and extrinsics".to_string(), + ))) + .collect::>(), ), 1, // "Purchase on-demand coretime" action ) @@ -590,9 +592,6 @@ mod tests { .expect_input("Enter the value for the parameter: para_id", "2000".into()) .expect_input("Signer of the extrinsic:", "//Bob".into()); - let chain = call_config.configure_chain(&mut cli).await?; - assert_eq!(chain.url, Url::parse("wss://polkadot-rpc.publicnode.com")?); - let call_parachain = call_config.configure_call(&chain, &mut cli).await?; assert_eq!(call_parachain.pallet.name, "OnDemand"); @@ -717,15 +716,17 @@ mod tests { Some(true), true, Some( - [ - ("Transfer balance".to_string(), "Balances".to_string()), - ("Create an asset".to_string(), "Assets".to_string()), - ("Mint an asset".to_string(), "Assets".to_string()), - ("Create an NFT collection".to_string(), "Nfts".to_string()), - ("Mint an NFT".to_string(), "Nfts".to_string()), - ("All".to_string(), "Explore all pallets and extrinsics".to_string()), - ] - .to_vec(), + supported_actions(&pallets) + .await + .into_iter() + .map(|action| { + (action.description().to_string(), action.pallet_name().to_string()) + }) + .chain(std::iter::once(( + "All".to_string(), + "Explore all pallets and extrinsics".to_string(), + ))) + .collect::>(), ), 2, // "Mint an Asset" action ); From 9801a7b7a5fd499ff9b13076f0ba35d8324d62c0 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 6 Dec 2024 13:36:30 +0100 Subject: [PATCH 174/211] test: find_free_port --- crates/pop-common/src/lib.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/pop-common/src/lib.rs b/crates/pop-common/src/lib.rs index 5866a0971..7663a9f2e 100644 --- a/crates/pop-common/src/lib.rs +++ b/crates/pop-common/src/lib.rs @@ -89,4 +89,14 @@ mod test { assert_eq!(target()?, target_expected); Ok(()) } + + #[test] + fn find_free_port_works() -> Result<()> { + let port = find_free_port(); + let addr = format!("127.0.0.1:{}", port); + // Constructs the TcpListener from the above port + let listener = TcpListener::bind(&addr); + assert!(listener.is_ok()); + Ok(()) + } } From e70bc7a6dbefd8e37165f102654d012605c51a37 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 6 Dec 2024 14:41:54 +0100 Subject: [PATCH 175/211] docs: improve parameter documentation --- crates/pop-common/src/metadata.rs | 5 +++-- crates/pop-parachains/src/call/metadata/mod.rs | 2 +- crates/pop-parachains/src/call/metadata/params.rs | 2 +- crates/pop-parachains/src/call/mod.rs | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/pop-common/src/metadata.rs b/crates/pop-common/src/metadata.rs index 6a4f79a85..e8a407c9b 100644 --- a/crates/pop-common/src/metadata.rs +++ b/crates/pop-common/src/metadata.rs @@ -5,8 +5,9 @@ use scale_info::{form::PortableForm, PortableRegistry, Type, TypeDef, TypeDefPri /// Formats a specified type, using the registry to output its full type representation. /// /// # Arguments -/// * `ty`: A reference to the `Type` to be formatted. -/// * `registry`: A reference to the `PortableRegistry` to resolve type dependencies. +/// * `ty`: The type to format, containing metadata like name, parameters, and definition. +/// * `registry`: The registry used to resolve type dependencies and provides details for complex +/// types. pub fn format_type(ty: &Type, registry: &PortableRegistry) -> String { let mut name = ty .path diff --git a/crates/pop-parachains/src/call/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs index 4c8b1addb..360e67197 100644 --- a/crates/pop-parachains/src/call/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -49,7 +49,7 @@ impl Display for Extrinsic { /// parameters. /// /// # Arguments -/// * `client`: Reference to an `OnlineClient` connected to the chain. +/// * `client`:`client` - The client used to interact with the chain. pub async fn parse_chain_metadata( client: &OnlineClient, ) -> Result, Error> { diff --git a/crates/pop-parachains/src/call/metadata/params.rs b/crates/pop-parachains/src/call/metadata/params.rs index 2419fbf04..4e8655382 100644 --- a/crates/pop-parachains/src/call/metadata/params.rs +++ b/crates/pop-parachains/src/call/metadata/params.rs @@ -25,7 +25,7 @@ pub struct Param { /// Transforms a metadata field into its `Param` representation. /// /// # Arguments -/// * `client`: Reference to an `OnlineClient` connected to the blockchain. +/// * `client`: `client` - The client used to interact with the chain. /// * `field`: A reference to a metadata field of the extrinsic. pub fn field_to_param( client: &OnlineClient, diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs index 040d8f8d5..cf1273cbd 100644 --- a/crates/pop-parachains/src/call/mod.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -38,7 +38,7 @@ pub async fn construct_extrinsic( /// Signs and submits a given extrinsic to the blockchain. /// /// # Arguments -/// * `client` - Reference to an `OnlineClient` connected to the chain. +/// * `client` - The client used to interact with the chain. /// * `tx` - The transaction to be signed and submitted. /// * `suri` - The secret URI (e.g., mnemonic or private key) for signing the extrinsic. pub async fn sign_and_submit_extrinsic( @@ -61,7 +61,7 @@ pub async fn sign_and_submit_extrinsic( /// Encodes the call data for a given extrinsic into a hexadecimal string. /// /// # Arguments -/// * `client` - Reference to an `OnlineClient` connected to the chain. +/// * `client` - The client used to interact with the chain. /// * `tx` - The transaction whose call data will be encoded and returned. pub fn encode_call_data( client: &OnlineClient, From 0d2551adb4958e4589b50f5391d362193fc22d1e Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 6 Dec 2024 14:48:07 +0100 Subject: [PATCH 176/211] test: add missing test to sign_and_submit_extrinsic --- crates/pop-parachains/src/call/mod.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs index cf1273cbd..9046207f8 100644 --- a/crates/pop-parachains/src/call/mod.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -124,4 +124,16 @@ mod tests { assert_eq!(encode_call_data(&client, &extrinsic)?, "0x00000411"); Ok(()) } + + #[tokio::test] + async fn sign_and_submit_wrong_extrinsic_fails() -> Result<()> { + let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; + let tx = + construct_extrinsic("WrongPallet", "wrongExtrinsic", vec!["0x11".to_string()]).await?; + assert!(matches!( + sign_and_submit_extrinsic(client, tx, "//Alice").await, + Err(Error::ExtrinsicSubmissionError(message)) if message.contains("PalletNameNotFound(\"WrongPallet\"))") + )); + Ok(()) + } } From 2bff2c1c8b9a65e70c609fcc534a4fcaafe3a449 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 6 Dec 2024 14:58:03 +0100 Subject: [PATCH 177/211] fix: apply feedback from auxiliar PRs, remove unnecesary clones --- crates/pop-cli/src/commands/call/parachain.rs | 23 ++++++++----------- crates/pop-parachains/src/call/mod.rs | 2 +- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 11cfedf4f..9a9d73d89 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -91,8 +91,8 @@ impl CallParachainCommand { async fn configure_chain(&self, cli: &mut impl Cli) -> Result { cli.intro("Call a parachain")?; // Resolve url. - let url = match self.clone().url { - Some(url) => url, + let url = match &self.url { + Some(url) => url.clone(), None => { // Prompt for url. let url: String = cli @@ -162,7 +162,7 @@ impl CallParachainCommand { } // Resolve message arguments. - let args = if self.clone().args.is_empty() { + let args = if self.args.is_empty() { let mut args = Vec::new(); for param in &extrinsic.params { let input = prompt_for_param(&chain.client, cli, param)?; @@ -170,12 +170,12 @@ impl CallParachainCommand { } args } else { - self.clone().args + self.args.clone() }; // Resolve who is signing the extrinsic. - let suri = match self.clone().suri { - Some(suri) => suri, + let suri = match self.suri.as_ref() { + Some(suri) => suri.clone(), None => cli.input("Signer of the extrinsic:").default_input(DEFAULT_URI).interact()?, }; @@ -273,10 +273,7 @@ impl CallParachain { .interact()? { display_message( - &format!( - "Extrinsic {} was not submitted. Operation canceled by the user.", - self.extrinsic.name - ), + &format!("Extrinsic {} was not submitted.", self.extrinsic.name), false, cli, )?; @@ -403,7 +400,7 @@ fn prompt_for_variant_param( } Ok(format!("{}({})", selected_variant.name, field_values.join(", "))) } else { - Ok(format!("{}()", selected_variant.name.clone())) + Ok(format!("{}()", selected_variant.name)) } } @@ -655,9 +652,7 @@ mod tests { }; let mut cli = MockCli::new() .expect_confirm("Do you want to submit the extrinsic?", false) - .expect_outro_cancel( - "Extrinsic remark was not submitted. Operation canceled by the user.", - ); + .expect_outro_cancel("Extrinsic remark was not submitted."); let tx = call_config.prepare_extrinsic(&client, &mut cli).await?; call_config.submit_extrinsic(&client, tx, &mut cli).await?; diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs index 9046207f8..5ca10cd0d 100644 --- a/crates/pop-parachains/src/call/mod.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -35,7 +35,7 @@ pub async fn construct_extrinsic( Ok(subxt::dynamic::tx(pallet_name, extrinsic_name, parsed_args)) } -/// Signs and submits a given extrinsic to the blockchain. +/// Signs and submits a given extrinsic. /// /// # Arguments /// * `client` - The client used to interact with the chain. From 44c1d4ebd330320e6e2efba183aeebbe28cb51ec Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 6 Dec 2024 15:34:46 +0100 Subject: [PATCH 178/211] docs: public modules --- crates/pop-common/src/lib.rs | 2 ++ crates/pop-parachains/src/lib.rs | 1 + 2 files changed, 3 insertions(+) diff --git a/crates/pop-common/src/lib.rs b/crates/pop-common/src/lib.rs index 7663a9f2e..dd48f5f22 100644 --- a/crates/pop-common/src/lib.rs +++ b/crates/pop-common/src/lib.rs @@ -3,8 +3,10 @@ pub mod errors; pub mod git; pub mod helpers; pub mod manifest; +/// Provides functionality for formatting and resolving metadata types. pub mod metadata; pub mod polkadot_sdk; +/// Provides functionality for creating a signer from a secret URI. pub mod signer; pub mod sourcing; pub mod templates; diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index 4f9a4e7ac..3e6da06a7 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -2,6 +2,7 @@ #![doc = include_str!("../README.md")] mod build; +/// Provides functionality to construct, encode, sign, and submit chain extrinsics. mod call; mod errors; mod generator; From 28743bd44c1f222880222c2a0aa29363275f1f29 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 6 Dec 2024 16:37:09 +0100 Subject: [PATCH 179/211] refactor: clean unused params --- crates/pop-cli/src/commands/call/parachain.rs | 52 ++++++------------- 1 file changed, 16 insertions(+), 36 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 9a9d73d89..38b72f306 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -165,7 +165,7 @@ impl CallParachainCommand { let args = if self.args.is_empty() { let mut args = Vec::new(); for param in &extrinsic.params { - let input = prompt_for_param(&chain.client, cli, param)?; + let input = prompt_for_param(cli, param)?; args.push(input); } args @@ -329,11 +329,7 @@ async fn prompt_predefined_actions( } // Prompts the user for the value of a parameter. -fn prompt_for_param( - client: &OnlineClient, - cli: &mut impl Cli, - param: &Param, -) -> Result { +fn prompt_for_param(cli: &mut impl Cli, param: &Param) -> Result { if param.is_optional { if !cli .confirm(format!( @@ -344,27 +340,23 @@ fn prompt_for_param( { return Ok("None()".to_string()); } - let value = get_param_value(client, cli, param)?; + let value = get_param_value(cli, param)?; Ok(format!("Some({})", value)) } else { - get_param_value(client, cli, param) + get_param_value(cli, param) } } // Resolves the value of a parameter based on its type. -fn get_param_value( - client: &OnlineClient, - cli: &mut impl Cli, - param: &Param, -) -> Result { +fn get_param_value(cli: &mut impl Cli, param: &Param) -> Result { if param.sub_params.is_empty() { prompt_for_primitive_param(cli, param) } else if param.is_variant { - prompt_for_variant_param(client, cli, param) + prompt_for_variant_param(cli, param) } else if param.is_tuple { - prompt_for_tuple_param(client, cli, param) + prompt_for_tuple_param(cli, param) } else { - prompt_for_composite_param(client, cli, param) + prompt_for_composite_param(cli, param) } } @@ -379,11 +371,7 @@ fn prompt_for_primitive_param(cli: &mut impl Cli, param: &Param) -> Result, - cli: &mut impl Cli, - param: &Param, -) -> Result { +fn prompt_for_variant_param(cli: &mut impl Cli, param: &Param) -> Result { let selected_variant = { let mut select = cli.select(format!("Select the value for the parameter: {}", param.name)); for option in ¶m.sub_params { @@ -395,7 +383,7 @@ fn prompt_for_variant_param( if !selected_variant.sub_params.is_empty() { let mut field_values = Vec::new(); for field_arg in &selected_variant.sub_params { - let field_value = prompt_for_param(client, cli, field_arg)?; + let field_value = prompt_for_param(cli, field_arg)?; field_values.push(field_value); } Ok(format!("{}({})", selected_variant.name, field_values.join(", "))) @@ -421,14 +409,10 @@ fn prompt_for_variant_param( // ], // is_variant: false // } -fn prompt_for_composite_param( - client: &OnlineClient, - cli: &mut impl Cli, - param: &Param, -) -> Result { +fn prompt_for_composite_param(cli: &mut impl Cli, param: &Param) -> Result { let mut field_values = Vec::new(); for field_arg in ¶m.sub_params { - let field_value = prompt_for_param(client, cli, field_arg)?; + let field_value = prompt_for_param(cli, field_arg)?; if param.sub_params.len() == 1 && param.name == param.sub_params[0].name { field_values.push(field_value); } else { @@ -443,14 +427,10 @@ fn prompt_for_composite_param( } // Recursively prompt the user for the tuple values. -fn prompt_for_tuple_param( - client: &OnlineClient, - cli: &mut impl Cli, - param: &Param, -) -> Result { +fn prompt_for_tuple_param(cli: &mut impl Cli, param: &Param) -> Result { let mut tuple_values = Vec::new(); for tuple_param in param.sub_params.iter() { - let tuple_value = prompt_for_param(client, cli, tuple_param)?; + let tuple_value = prompt_for_param(cli, tuple_param)?; tuple_values.push(tuple_value); } Ok(format!("({})", tuple_values.join(", "))) @@ -776,7 +756,7 @@ mod tests { // Test all the extrinsic params let mut params: Vec = Vec::new(); for param in extrinsic.params { - params.push(prompt_for_param(&client, &mut cli, ¶m)?); + params.push(prompt_for_param(&mut cli, ¶m)?); } assert_eq!(params.len(), 4); assert_eq!(params[0], "0".to_string()); // collection: test primitive @@ -802,7 +782,7 @@ mod tests { // Test all the extrinsic params let mut params: Vec = Vec::new(); for param in extrinsic.params { - params.push(prompt_for_param(&client, &mut cli, ¶m)?); + params.push(prompt_for_param(&mut cli, ¶m)?); } assert_eq!(params.len(), 3); assert_eq!(params[0], "(0, 0)".to_string()); // task: test tuples From 98aaae97d28c74a91e8b55f2e33f0081f1f97383 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Sat, 7 Dec 2024 22:19:04 +0100 Subject: [PATCH 180/211] fix: mark all extrinsics that uses calls as parameter as unsupported --- .../src/call/metadata/params.rs | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/crates/pop-parachains/src/call/metadata/params.rs b/crates/pop-parachains/src/call/metadata/params.rs index 4e8655382..da369a4f4 100644 --- a/crates/pop-parachains/src/call/metadata/params.rs +++ b/crates/pop-parachains/src/call/metadata/params.rs @@ -33,6 +33,11 @@ pub fn field_to_param( ) -> Result { let metadata: Metadata = client.metadata(); let registry = metadata.types(); + if let Some(name) = field.type_name.as_deref() { + if name.contains("RuntimeCall") { + return Err(Error::ExtrinsicNotSupported); + } + } let name = field.name.clone().unwrap_or("Unnamed".to_string()); //It can be unnamed field type_to_param(name, registry, field.ty.id) } @@ -45,16 +50,8 @@ pub fn field_to_param( /// * `type_id`: The ID of the type to be converted. fn type_to_param(name: String, registry: &PortableRegistry, type_id: u32) -> Result { let type_info = registry.resolve(type_id).ok_or(Error::MetadataParsingError(name.clone()))?; - if let Some(last_segment) = type_info.path.segments.last() { - if last_segment == "RuntimeCall" { - return Err(Error::ExtrinsicNotSupported); - } - } for param in &type_info.type_params { - if param.name == "RuntimeCall" || - param.name == "Vec" || - param.name == "Vec<::RuntimeCall>" - { + if param.name.contains("RuntimeCall") { return Err(Error::ExtrinsicNotSupported); } } @@ -228,13 +225,32 @@ mod tests { .type_name, "AccountId32 ([u8;32])" ); - // Test an extrinsic that is not supported. + // Test some extrinsics that are not supported. let extrinsic = metadata.pallet_by_name("Sudo").unwrap().call_variant_by_name("sudo").unwrap(); assert!(matches!( field_to_param(&client, &extrinsic.fields.first().unwrap()), Err(Error::ExtrinsicNotSupported) )); + let extrinsic = metadata + .pallet_by_name("Utility") + .unwrap() + .call_variant_by_name("batch") + .unwrap(); + assert!(matches!( + field_to_param(&client, &extrinsic.fields.first().unwrap()), + Err(Error::ExtrinsicNotSupported) + )); + let extrinsic = metadata + .pallet_by_name("PolkadotXcm") + .unwrap() + .call_variant_by_name("execute") + .unwrap(); + assert!(matches!( + field_to_param(&client, &extrinsic.fields.first().unwrap()), + Err(Error::ExtrinsicNotSupported) + )); + Ok(()) } } From 2eb51839bf8f55a3d6ea92d76ec4b9d5b297df3f Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Mon, 9 Dec 2024 09:52:47 +0100 Subject: [PATCH 181/211] test: fix expect_select --- crates/pop-cli/src/cli.rs | 2 +- crates/pop-cli/src/commands/build/spec.rs | 12 ++++++------ crates/pop-cli/src/commands/call/contract.rs | 8 ++++---- crates/pop-cli/src/commands/call/parachain.rs | 8 ++++---- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/crates/pop-cli/src/cli.rs b/crates/pop-cli/src/cli.rs index ee32b5a0c..6c6bb3bce 100644 --- a/crates/pop-cli/src/cli.rs +++ b/crates/pop-cli/src/cli.rs @@ -292,7 +292,7 @@ pub(crate) mod tests { self } - pub(crate) fn expect_select( + pub(crate) fn expect_select( mut self, prompt: impl Display, required: Option, diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index cb193b5eb..3cde4c61e 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -634,19 +634,19 @@ mod tests { "What parachain ID should be used?", para_id.to_string()) .expect_input( "Enter the protocol ID that will identify your network:", protocol_id.to_string()) - .expect_select::<&str>( + .expect_select( "Choose the chain type: ", Some(false), true, Some(chain_types()), chain_type.clone() as usize, - ).expect_select::<&str>( + ).expect_select( "Choose the relay your chain will be connecting to: ", Some(false), true, Some(relays()), relay.clone() as usize, - ).expect_select::<&str>( + ).expect_select( "Choose the build profile of the binary that should be used: ", Some(false), true, @@ -732,7 +732,7 @@ mod tests { ); } if build_spec_cmd.chain_type.is_none() { - cli = cli.expect_select::<&str>( + cli = cli.expect_select( "Choose the chain type: ", Some(false), true, @@ -741,7 +741,7 @@ mod tests { ); } if build_spec_cmd.relay.is_none() { - cli = cli.expect_select::<&str>( + cli = cli.expect_select( "Choose the relay your chain will be connecting to: ", Some(false), true, @@ -750,7 +750,7 @@ mod tests { ); } if build_spec_cmd.profile.is_none() { - cli = cli.expect_select::<&str>( + cli = cli.expect_select( "Choose the build profile of the binary that should be used: ", Some(false), true, diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index a5e9d8359..0b2815a85 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -612,7 +612,7 @@ mod tests { "Do you want to perform another call using the existing smart contract?", false, ) - .expect_select::<&str>( + .expect_select( "Select the message to call:", Some(false), true, @@ -669,7 +669,7 @@ mod tests { ]; // The inputs are processed in reverse order. let mut cli = MockCli::new() - .expect_select::<&str>( + .expect_select( "Select the message to call:", Some(false), true, @@ -751,7 +751,7 @@ mod tests { // The inputs are processed in reverse order. let mut cli = MockCli::new() .expect_confirm("Do you want to execute the call? (Selecting 'No' will perform a dry run)", true) - .expect_select::<&str>( + .expect_select( "Select the message to call:", Some(false), true, @@ -839,7 +839,7 @@ mod tests { ]; // The inputs are processed in reverse order. let mut cli = MockCli::new() - .expect_select::<&str>( + .expect_select( "Select the message to call:", Some(false), true, diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 38b72f306..c2938b99c 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -489,7 +489,7 @@ mod tests { let mut cli = MockCli::new() .expect_intro("Call a parachain") .expect_input("Which chain would you like to interact with?", "wss://rpc1.paseo.popnetwork.xyz".into()) - .expect_select::( + .expect_select( "Select the extrinsic to call:", Some(true), true, @@ -546,7 +546,7 @@ mod tests { cli.verify()?; let mut cli = MockCli::new() - .expect_select::( + .expect_select( "What would you like to do?", Some(true), true, @@ -686,7 +686,7 @@ mod tests { async fn prompt_predefined_actions_works() -> Result<()> { let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; let pallets = parse_chain_metadata(&client).await?; - let mut cli = MockCli::new().expect_select::( + let mut cli = MockCli::new().expect_select( "What would you like to do?", Some(true), true, @@ -719,7 +719,7 @@ mod tests { let mut cli = MockCli::new() .expect_input("Enter the value for the parameter: collection", "0".into()) .expect_input("Enter the value for the parameter: item", "0".into()) - .expect_select::( + .expect_select( "Select the value for the parameter: mint_to", Some(true), true, From 69410bd555b44836b1808486e5cebe67a3dde109 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Mon, 9 Dec 2024 10:43:57 +0100 Subject: [PATCH 182/211] docs: improve documentation --- crates/pop-cli/src/commands/call/parachain.rs | 10 +++++----- crates/pop-common/src/signer.rs | 3 +++ crates/pop-parachains/src/call/metadata/action.rs | 2 +- crates/pop-parachains/src/call/metadata/mod.rs | 4 ++-- crates/pop-parachains/src/call/metadata/params.rs | 6 +++--- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index c2938b99c..a6503d6e2 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -44,7 +44,7 @@ impl CallParachainCommand { /// Executes the command. pub(crate) async fn execute(mut self) -> Result<()> { let mut cli = cli::Cli; - // Check if all fields are specified via command line argument. + // Check if all fields are specified via the command line. let prompt_to_repeat_call = self.requires_user_input(); // Configure the chain. let chain = self.configure_chain(&mut cli).await?; @@ -207,13 +207,13 @@ impl CallParachainCommand { } } -/// Represents a chain, including its URL, client connection, and available pallets. +// Represents a chain, including its URL, client connection, and available pallets. struct Chain { - /// Websocket endpoint of the node. + // Websocket endpoint of the node. url: Url, - /// The client used to interact with the chain. + // The client used to interact with the chain. client: OnlineClient, - /// A list of pallets available on the chain. + // A list of pallets available on the chain. pallets: Vec, } diff --git a/crates/pop-common/src/signer.rs b/crates/pop-common/src/signer.rs index 79da4a283..31f0abeec 100644 --- a/crates/pop-common/src/signer.rs +++ b/crates/pop-common/src/signer.rs @@ -4,6 +4,9 @@ use crate::errors::Error; use subxt_signer::{sr25519::Keypair, SecretUri}; /// Create a Signer from a secret URI. +/// +/// # Arguments +/// `suri` - Secret URI string used to generate the `Keypair`. pub fn create_signer(suri: &str) -> Result { let uri = ::from_str(suri) .map_err(|e| Error::ParseSecretURI(format!("{}", e)))?; diff --git a/crates/pop-parachains/src/call/metadata/action.rs b/crates/pop-parachains/src/call/metadata/action.rs index 108f3afaa..d687ec355 100644 --- a/crates/pop-parachains/src/call/metadata/action.rs +++ b/crates/pop-parachains/src/call/metadata/action.rs @@ -105,7 +105,7 @@ impl Action { /// Fetch the list of supported actions based on available pallets. /// /// # Arguments -/// * `pallets`: List of pallets availables in the chain. +/// * `pallets`: Supported pallets. pub async fn supported_actions(pallets: &[Pallet]) -> Vec { let mut actions = Vec::new(); for action in Action::VARIANTS.iter() { diff --git a/crates/pop-parachains/src/call/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs index 360e67197..89edd5c23 100644 --- a/crates/pop-parachains/src/call/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -26,7 +26,7 @@ impl Display for Pallet { } } -/// Represents an extrinsic in a pallet. +/// Represents an extrinsic. #[derive(Clone, PartialEq, Eq, Debug)] pub struct Extrinsic { /// The name of the extrinsic. @@ -49,7 +49,7 @@ impl Display for Extrinsic { /// parameters. /// /// # Arguments -/// * `client`:`client` - The client used to interact with the chain. +/// * `client`: The client to interact with the chain. pub async fn parse_chain_metadata( client: &OnlineClient, ) -> Result, Error> { diff --git a/crates/pop-parachains/src/call/metadata/params.rs b/crates/pop-parachains/src/call/metadata/params.rs index da369a4f4..12fd72731 100644 --- a/crates/pop-parachains/src/call/metadata/params.rs +++ b/crates/pop-parachains/src/call/metadata/params.rs @@ -25,8 +25,8 @@ pub struct Param { /// Transforms a metadata field into its `Param` representation. /// /// # Arguments -/// * `client`: `client` - The client used to interact with the chain. -/// * `field`: A reference to a metadata field of the extrinsic. +/// * `client`: The client to interact with the chain. +/// * `field`: A parameter of an extrinsic as struct field. pub fn field_to_param( client: &OnlineClient, field: &Field, @@ -46,7 +46,7 @@ pub fn field_to_param( /// /// # Arguments /// * `name`: The name of the parameter. -/// * `registry`: A reference to the `PortableRegistry` to resolve type dependencies. +/// * `registry`: Type registry containing all types used in the metadata. /// * `type_id`: The ID of the type to be converted. fn type_to_param(name: String, registry: &PortableRegistry, type_id: u32) -> Result { let type_info = registry.resolve(type_id).ok_or(Error::MetadataParsingError(name.clone()))?; From c37580f9bb3262396a855ed17bceca37fefb8572 Mon Sep 17 00:00:00 2001 From: Alex Bean Date: Mon, 9 Dec 2024 15:57:44 +0100 Subject: [PATCH 183/211] feat: submit extrinsic from call_data (#348) * feat: submit extrinsic from call_data * test: unit test for initialize_api_client * test: unit test for send_extrinsic_from_call_data * fix: CallData struct * fix: skip_confirm for send_extrinsic_from_call_data * chore: clippy * chore: fmt * refactor: minor doc and naming changes * refactor: remove unnecesary clones and return early when submit_extrinsic_from_call_data * chore: fmt * refactor: split decode_call_data logic outside sign_and_submit_extrinsic_with_call_data --- crates/pop-cli/src/commands/call/parachain.rs | 87 ++++++++++++++++++- crates/pop-cli/tests/parachain.rs | 18 ++++ crates/pop-parachains/src/call/mod.rs | 58 +++++++++++++ crates/pop-parachains/src/errors.rs | 3 + crates/pop-parachains/src/lib.rs | 4 +- 5 files changed, 165 insertions(+), 5 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index a6503d6e2..9ed95da16 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -4,9 +4,10 @@ use crate::cli::{self, traits::*}; use anyhow::{anyhow, Result}; use clap::Args; use pop_parachains::{ - construct_extrinsic, encode_call_data, find_extrinsic_by_name, find_pallet_by_name, - parse_chain_metadata, set_up_client, sign_and_submit_extrinsic, supported_actions, Action, - DynamicPayload, Extrinsic, OnlineClient, Pallet, Param, SubstrateConfig, + construct_extrinsic, decode_call_data, encode_call_data, find_extrinsic_by_name, + find_pallet_by_name, parse_chain_metadata, set_up_client, sign_and_submit_extrinsic, + sign_and_submit_extrinsic_with_call_data, supported_actions, Action, DynamicPayload, Extrinsic, + OnlineClient, Pallet, Param, SubstrateConfig, }; use url::Url; @@ -35,6 +36,9 @@ pub struct CallParachainCommand { /// - with a password "//Alice///SECRET_PASSWORD" #[arg(short, long)] suri: Option, + /// SCALE encoded bytes representing the call data of the extrinsic. + #[arg(name = "call", long, conflicts_with_all = ["pallet", "extrinsic", "args"])] + call_data: Option, /// Automatically signs and submits the extrinsic without prompting for confirmation. #[arg(short('y'), long)] skip_confirm: bool, @@ -48,6 +52,16 @@ impl CallParachainCommand { let prompt_to_repeat_call = self.requires_user_input(); // Configure the chain. let chain = self.configure_chain(&mut cli).await?; + // Execute the call if call_data is provided. + if let Some(call_data) = self.call_data.as_ref() { + if let Err(e) = self + .submit_extrinsic_from_call_data(&chain.client, call_data, &mut cli::Cli) + .await + { + display_message(&e.to_string(), false, &mut cli::Cli)?; + } + return Ok(()); + } loop { // Configure the call based on command line arguments/call UI. let mut call = match self.configure_call(&chain, &mut cli).await { @@ -190,6 +204,45 @@ impl CallParachainCommand { } } + // Submits an extrinsic to the chain using the provided call data. + async fn submit_extrinsic_from_call_data( + &self, + client: &OnlineClient, + call_data: &str, + cli: &mut impl Cli, + ) -> Result<()> { + // Resolve who is signing the extrinsic. + let suri = match self.suri.as_ref() { + Some(suri) => suri, + None => &cli.input("Signer of the extrinsic:").default_input(DEFAULT_URI).interact()?, + }; + cli.info(format!("Encoded call data: {}", call_data))?; + if !self.skip_confirm && + !cli.confirm("Do you want to submit the extrinsic?") + .initial_value(true) + .interact()? + { + display_message( + &format!("Extrinsic with call data {call_data} was not submitted."), + false, + cli, + )?; + return Ok(()); + } + let spinner = cliclack::spinner(); + spinner.start("Signing and submitting the extrinsic, please wait..."); + let call_data_bytes = + decode_call_data(call_data).map_err(|err| anyhow!("{}", format!("{err:?}")))?; + let result = + sign_and_submit_extrinsic_with_call_data(client.clone(), call_data_bytes, suri) + .await + .map_err(|err| anyhow!("{}", format!("{err:?}")))?; + + spinner.stop(format!("Extrinsic submitted successfully with hash: {:?}", result)); + display_message("Call complete.", true, cli)?; + Ok(()) + } + // Resets specific fields to default values for a new call. fn reset_for_new_call(&mut self) { self.pallet = None; @@ -465,6 +518,7 @@ mod tests { url: None, suri: Some(DEFAULT_URI.to_string()), skip_confirm: false, + call_data: None, }; let mut cli = MockCli::new().expect_intro("Call a parachain").expect_input( "Which chain would you like to interact with?", @@ -484,6 +538,7 @@ mod tests { url: None, suri: None, skip_confirm: false, + call_data: None, }; let mut cli = MockCli::new() @@ -535,6 +590,7 @@ mod tests { url: None, suri: None, skip_confirm: false, + call_data: None, }; let mut cli = MockCli::new().expect_intro("Call a parachain").expect_input( @@ -639,6 +695,29 @@ mod tests { cli.verify() } + #[tokio::test] + async fn user_cancel_submit_extrinsic_from_call_data_works() -> Result<()> { + let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; + let call_config = CallParachainCommand { + pallet: None, + extrinsic: None, + args: vec![].to_vec(), + url: Some(Url::parse("wss://rpc1.paseo.popnetwork.xyz")?), + suri: None, + skip_confirm: false, + call_data: Some("0x00000411".to_string()), + }; + let mut cli = MockCli::new() + .expect_input("Signer of the extrinsic:", "//Bob".into()) + .expect_confirm("Do you want to submit the extrinsic?", false) + .expect_outro_cancel("Extrinsic with call data 0x00000411 was not submitted."); + call_config + .submit_extrinsic_from_call_data(&client, "0x00000411", &mut cli) + .await?; + + cli.verify() + } + #[test] fn reset_for_new_call_works() -> Result<()> { let mut call_config = CallParachainCommand { @@ -648,6 +727,7 @@ mod tests { url: Some(Url::parse("wss://rpc1.paseo.popnetwork.xyz")?), suri: Some(DEFAULT_URI.to_string()), skip_confirm: false, + call_data: None, }; call_config.reset_for_new_call(); assert_eq!(call_config.pallet, None); @@ -665,6 +745,7 @@ mod tests { url: Some(Url::parse("wss://rpc1.paseo.popnetwork.xyz")?), suri: Some(DEFAULT_URI.to_string()), skip_confirm: false, + call_data: None, }; assert!(!call_config.requires_user_input()); call_config.pallet = None; diff --git a/crates/pop-cli/tests/parachain.rs b/crates/pop-cli/tests/parachain.rs index 62c252696..5fabadce7 100644 --- a/crates/pop-cli/tests/parachain.rs +++ b/crates/pop-cli/tests/parachain.rs @@ -155,6 +155,24 @@ name = "collator-01" .assert() .success(); + // pop call parachain --call 0x00000411 --url ws://127.0.0.1:random_port --suri //Alice + // --skip-confirm + Command::cargo_bin("pop") + .unwrap() + .args(&[ + "call", + "parachain", + "--call", + "0x00000411", + "--url", + &localhost_url, + "--suri", + "//Alice", + "--skip-confirm", + ]) + .assert() + .success(); + assert!(cmd.try_wait().unwrap().is_none(), "the process should still be running"); // Stop the process Cmd::new("kill").args(["-s", "TERM", &cmd.id().to_string()]).spawn()?; diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs index 5ca10cd0d..2368c31f7 100644 --- a/crates/pop-parachains/src/call/mod.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -73,6 +73,54 @@ pub fn encode_call_data( Ok(format!("0x{}", hex::encode(call_data))) } +/// Decodes a hex-encoded string into a vector of bytes representing the call data. +/// +/// # Arguments +/// * `call_data` - The hex-encoded string representing call data. +pub fn decode_call_data(call_data: &str) -> Result, Error> { + hex::decode(call_data.trim_start_matches("0x")) + .map_err(|e| Error::CallDataDecodingError(e.to_string())) +} + +// This struct implements the [`Payload`] trait and is used to submit +// pre-encoded SCALE call data directly, without the dynamic construction of transactions. +struct CallData(Vec); + +impl Payload for CallData { + fn encode_call_data_to( + &self, + _: &subxt::Metadata, + out: &mut Vec, + ) -> Result<(), subxt::ext::subxt_core::Error> { + out.extend_from_slice(&self.0); + Ok(()) + } +} + +/// Signs and submits a given extrinsic. +/// +/// # Arguments +/// * `client` - Reference to an `OnlineClient` connected to the chain. +/// * `call_data` - SCALE encoded bytes representing the extrinsic's call data. +/// * `suri` - The secret URI (e.g., mnemonic or private key) for signing the extrinsic. +pub async fn sign_and_submit_extrinsic_with_call_data( + client: OnlineClient, + call_data: Vec, + suri: &str, +) -> Result { + let signer = create_signer(suri)?; + let payload = CallData(call_data); + let result = client + .tx() + .sign_and_submit_then_watch_default(&payload, &signer) + .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())) +} + #[cfg(test)] mod tests { use super::*; @@ -125,6 +173,16 @@ mod tests { Ok(()) } + #[tokio::test] + async fn decode_call_data_works() -> Result<()> { + assert!(matches!(decode_call_data("wrongcalldata"), Err(Error::CallDataDecodingError(..)))); + let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; + let extrinsic = construct_extrinsic("System", "remark", vec!["0x11".to_string()]).await?; + let expected_call_data = extrinsic.encode_call_data(&client.metadata())?; + assert_eq!(decode_call_data("0x00000411")?, expected_call_data); + Ok(()) + } + #[tokio::test] async fn sign_and_submit_wrong_extrinsic_fails() -> Result<()> { let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; diff --git a/crates/pop-parachains/src/errors.rs b/crates/pop-parachains/src/errors.rs index fdef8ac79..ac9fe0f97 100644 --- a/crates/pop-parachains/src/errors.rs +++ b/crates/pop-parachains/src/errors.rs @@ -10,6 +10,9 @@ pub enum Error { Aborted, #[error("Anyhow error: {0}")] AnyhowError(#[from] anyhow::Error), + /// An error occurred while decoding the call data. + #[error("Failed to decode call data. {0}")] + CallDataDecodingError(String), /// An error occurred while encoding the call data. #[error("Failed to encode call data. {0}")] CallDataEncodingError(String), diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index 3e6da06a7..00f249751 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -17,14 +17,14 @@ pub use build::{ generate_plain_chain_spec, generate_raw_chain_spec, is_supported, ChainSpec, }; pub use call::{ - construct_extrinsic, encode_call_data, + construct_extrinsic, decode_call_data, encode_call_data, metadata::{ action::{supported_actions, Action}, find_extrinsic_by_name, find_pallet_by_name, params::Param, parse_chain_metadata, Extrinsic, Pallet, }, - set_up_client, sign_and_submit_extrinsic, + set_up_client, sign_and_submit_extrinsic, sign_and_submit_extrinsic_with_call_data, }; pub use errors::Error; pub use indexmap::IndexSet; From 275ae43f076f48e9633ae7bea4f1d03a31f61ef4 Mon Sep 17 00:00:00 2001 From: Alex Bean Date: Mon, 9 Dec 2024 16:21:58 +0100 Subject: [PATCH 184/211] feat: parse files when the argument values are very big (#363) * feat: parse files when the argument values are very big * test: unit test * chore: fmt * feat: file logic using the command line * fix: sequence arguments * test: fix unit test * refactor: remove prompting the user if input is file or value * refactor: parse_extrinsic_arguments * fix: CI deny * refactor: reorder Param derive macros --- Cargo.lock | 265 +++++++++++++++++- Cargo.toml | 2 +- crates/pop-cli/src/commands/call/parachain.rs | 125 ++++++++- .../pop-parachains/src/call/metadata/mod.rs | 51 +++- .../src/call/metadata/params.rs | 24 +- crates/pop-parachains/src/call/mod.rs | 37 ++- deny.toml | 1 + 7 files changed, 458 insertions(+), 47 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4d6d8bd7d..6ff7331d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2917,6 +2917,124 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -2925,12 +3043,23 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.5.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] @@ -3839,6 +3968,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + [[package]] name = "lock_api" version = "0.4.12" @@ -7021,6 +7156,17 @@ dependencies = [ "futures-core", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -7196,6 +7342,16 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -7629,12 +7785,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" - [[package]] name = "unicode-ident" version = "1.0.13" @@ -7698,9 +7848,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.2" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", @@ -7714,6 +7864,18 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -8482,6 +8644,18 @@ version = "0.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "wyz" version = "0.5.1" @@ -8526,6 +8700,30 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff4524214bc4629eba08d78ceb1d6507070cc0bcbbed23af74e19e6e924a24cf" +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -8547,6 +8745,27 @@ dependencies = [ "syn 2.0.89", ] +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", + "synstructure", +] + [[package]] name = "zeroize" version = "1.8.1" @@ -8567,6 +8786,28 @@ dependencies = [ "syn 2.0.89", ] +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + [[package]] name = "zip" version = "2.2.0" diff --git a/Cargo.toml b/Cargo.toml index e33ae6d5c..30c181caa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ toml = "0.5.0" # networking reqwest = { version = "0.12", features = ["json"] } tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } -url = "2.5" +url = "2.5.4" # contracts subxt-signer = { version = "0.37.0", features = ["subxt", "sr25519"] } diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 9ed95da16..37e702c8a 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -1,5 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 +use std::path::Path; + use crate::cli::{self, traits::*}; use anyhow::{anyhow, Result}; use clap::Args; @@ -13,6 +15,7 @@ use url::Url; const DEFAULT_URL: &str = "ws://localhost:9944/"; const DEFAULT_URI: &str = "//Alice"; +const ENCODED_CALL_DATA_MAX_LEN: usize = 500; // Maximum length of encoded call data to display. /// Command to execute extrinsics with configurable pallets, arguments, and signing options. #[derive(Args, Clone)] @@ -184,7 +187,7 @@ impl CallParachainCommand { } args } else { - self.args.clone() + self.expand_file_arguments()? }; // Resolve who is signing the extrinsic. @@ -258,6 +261,21 @@ impl CallParachainCommand { self.url.is_none() || self.suri.is_none() } + + /// Replaces file arguments with their contents, leaving other arguments unchanged. + fn expand_file_arguments(&self) -> Result> { + self.args + .iter() + .map(|arg| { + if std::fs::metadata(arg).map(|m| m.is_file()).unwrap_or(false) { + std::fs::read_to_string(arg) + .map_err(|err| anyhow!("Failed to read file {}", err.to_string())) + } else { + Ok(arg.clone()) + } + }) + .collect() + } } // Represents a chain, including its URL, client connection, and available pallets. @@ -299,7 +317,7 @@ impl CallParachain { ) -> Result { let tx = match construct_extrinsic( self.pallet.name.as_str(), - self.extrinsic.name.as_str(), + &self.extrinsic, self.args.clone(), ) .await @@ -309,7 +327,11 @@ impl CallParachain { return Err(anyhow!("Error: {}", e)); }, }; - cli.info(format!("Encoded call data: {}", encode_call_data(client, &tx)?))?; + let encoded_data = encode_call_data(client, &tx)?; + // If the encoded call data is too long, don't display it all. + if encoded_data.len() < ENCODED_CALL_DATA_MAX_LEN { + cli.info(format!("Encoded call data: {}", encode_call_data(client, &tx)?))?; + } Ok(tx) } @@ -346,7 +368,18 @@ impl CallParachain { full_message.push_str(&format!(" --pallet {}", self.pallet)); full_message.push_str(&format!(" --extrinsic {}", self.extrinsic)); if !self.args.is_empty() { - let args: Vec<_> = self.args.iter().map(|a| format!("\"{a}\"")).collect(); + let args: Vec<_> = self + .args + .iter() + .map(|a| { + // If the argument is too long, don't show it all, truncate it. + if a.len() > ENCODED_CALL_DATA_MAX_LEN { + format!("\"{}...{}\"", &a[..20], &a[a.len() - 20..]) + } else { + format!("\"{a}\"") + } + }) + .collect(); full_message.push_str(&format!(" --args {}", args.join(" "))); } full_message.push_str(&format!(" --url {} --suri {}", chain.url, self.suri)); @@ -402,7 +435,9 @@ fn prompt_for_param(cli: &mut impl Cli, param: &Param) -> Result { // Resolves the value of a parameter based on its type. fn get_param_value(cli: &mut impl Cli, param: &Param) -> Result { - if param.sub_params.is_empty() { + if param.is_sequence { + prompt_for_sequence_param(cli, param) + } else if param.sub_params.is_empty() { prompt_for_primitive_param(cli, param) } else if param.is_variant { prompt_for_variant_param(cli, param) @@ -413,6 +448,25 @@ fn get_param_value(cli: &mut impl Cli, param: &Param) -> Result { } } +// Prompt for the value when it is a sequence. +fn prompt_for_sequence_param(cli: &mut impl Cli, param: &Param) -> Result { + let input_value = cli + .input(format!( + "The value for `{}` might be too large to enter. You may enter the path to a file instead.", + param.name + )) + .placeholder(&format!( + "Enter a value of type {} or provide a file path (e.g., /path/to/your/file.json)", + param.type_name + )) + .interact()?; + if Path::new(&input_value).is_file() { + return std::fs::read_to_string(&input_value) + .map_err(|err| anyhow!("Failed to read file {}", err.to_string())); + } + Ok(input_value) +} + // Prompt for the value when it is a primitive. fn prompt_for_primitive_param(cli: &mut impl Cli, param: &Param) -> Result { Ok(cli @@ -507,6 +561,7 @@ fn parse_extrinsic_name(name: &str) -> Result { mod tests { use super::*; use crate::cli::MockCli; + use tempfile::tempdir; use url::Url; #[tokio::test] @@ -566,7 +621,7 @@ mod tests { ), 0, // "remark" extrinsic ) - .expect_input("Enter the value for the parameter: remark", "0x11".into()) + .expect_input("The value for `remark` might be too large to enter. You may enter the path to a file instead.", "0x11".into()) .expect_input("Signer of the extrinsic:", "//Bob".into()); let chain = call_config.configure_chain(&mut cli).await?; @@ -753,6 +808,42 @@ mod tests { Ok(()) } + #[test] + fn expand_file_arguments_works() -> Result<()> { + let mut call_config = CallParachainCommand { + pallet: Some("Registrar".to_string()), + extrinsic: Some("register".to_string()), + args: vec!["2000".to_string(), "0x1".to_string(), "0x12".to_string()].to_vec(), + url: Some(Url::parse("wss://rpc1.paseo.popnetwork.xyz")?), + suri: Some(DEFAULT_URI.to_string()), + skip_confirm: false, + }; + assert_eq!( + call_config.expand_file_arguments()?, + vec!["2000".to_string(), "0x1".to_string(), "0x12".to_string()] + ); + // Temporal file for testing when the input is a file. + let temp_dir = tempdir()?; + let genesis_file = temp_dir.path().join("genesis_file.json"); + std::fs::write(&genesis_file, "genesis_file_content")?; + let wasm_file = temp_dir.path().join("wasm_file.json"); + std::fs::write(&wasm_file, "wasm_file_content")?; + call_config.args = vec![ + "2000".to_string(), + genesis_file.display().to_string(), + wasm_file.display().to_string(), + ]; + assert_eq!( + call_config.expand_file_arguments()?, + vec![ + "2000".to_string(), + "genesis_file_content".to_string(), + "wasm_file_content".to_string() + ] + ); + Ok(()) + } + #[test] fn display_message_works() -> Result<()> { let mut cli = MockCli::new().expect_outro(&"Call completed successfully!"); @@ -869,6 +960,28 @@ mod tests { assert_eq!(params[0], "(0, 0)".to_string()); // task: test tuples assert_eq!(params[1], "0".to_string()); // retries: test primitive assert_eq!(params[2], "0".to_string()); // period: test primitive + cli.verify()?; + + // Using System remark extrinsic to test the sequence params + let extrinsic = find_extrinsic_by_name(&pallets, "System", "remark").await?; + // Temporal file for testing the input. + let temp_dir = tempdir()?; + let file = temp_dir.path().join("file.json"); + std::fs::write(&file, "testing")?; + + let mut cli = MockCli::new() + .expect_input( + "The value for `remark` might be too large to enter. You may enter the path to a file instead.", + file.display().to_string(), + ); + + // Test all the extrinsic params + let mut params: Vec = Vec::new(); + for param in extrinsic.params { + params.push(prompt_for_param(&mut cli, ¶m)?); + } + assert_eq!(params.len(), 1); + assert_eq!(params[0], "testing".to_string()); // remark: test sequence from file cli.verify() } diff --git a/crates/pop-parachains/src/call/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs index 89edd5c23..d3a057d45 100644 --- a/crates/pop-parachains/src/call/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -146,19 +146,30 @@ pub async fn find_extrinsic_by_name( /// Parses and processes raw string parameters for an extrinsic, mapping them to `Value` types. /// /// # Arguments +/// * `params`: The metadata definition for each parameter of the extrinsic. /// * `raw_params`: A vector of raw string arguments for the extrinsic. -pub async fn parse_extrinsic_arguments(raw_params: Vec) -> Result, Error> { - let mut parsed_params: Vec = Vec::new(); - for raw_param in raw_params { - let parsed_value: Value = scale_value::stringify::from_str_custom() - .add_custom_parser(custom_parsers::parse_hex) - .add_custom_parser(custom_parsers::parse_ss58) - .parse(&raw_param) - .0 - .map_err(|_| Error::ParamProcessingError)?; - parsed_params.push(parsed_value); - } - Ok(parsed_params) +pub async fn parse_extrinsic_arguments( + params: &[Param], + raw_params: Vec, +) -> Result, Error> { + params + .iter() + .zip(raw_params) + .map(|(param, raw_param)| { + // Convert sequence parameters to hex if is_sequence + let processed_param = if param.is_sequence && !raw_param.starts_with("0x") { + format!("0x{}", hex::encode(raw_param)) + } else { + raw_param + }; + scale_value::stringify::from_str_custom() + .add_custom_parser(custom_parsers::parse_hex) + .add_custom_parser(custom_parsers::parse_ss58) + .parse(&processed_param) + .0 + .map_err(|_| Error::ParamProcessingError) + }) + .collect() } #[cfg(test)] @@ -191,6 +202,7 @@ mod tests { assert!(!first_extrinsic.params.first().unwrap().is_optional); assert!(!first_extrinsic.params.first().unwrap().is_tuple); assert!(!first_extrinsic.params.first().unwrap().is_variant); + assert!(first_extrinsic.params.first().unwrap().is_sequence); Ok(()) } @@ -253,8 +265,21 @@ mod tests { .into_iter() .map(|b| Value::u128(b as u128)) .collect(); + // Define mock extrinsic parameters for testing. + let params = vec![ + Param { type_name: "u128".to_string(), ..Default::default() }, + Param { type_name: "i128".to_string(), ..Default::default() }, + Param { type_name: "bool".to_string(), ..Default::default() }, + Param { type_name: "char".to_string(), ..Default::default() }, + Param { type_name: "string".to_string(), ..Default::default() }, + Param { type_name: "compostie".to_string(), ..Default::default() }, + Param { type_name: "variant".to_string(), is_variant: true, ..Default::default() }, + Param { type_name: "bit_sequence".to_string(), ..Default::default() }, + Param { type_name: "tuple".to_string(), is_tuple: true, ..Default::default() }, + Param { type_name: "composite".to_string(), ..Default::default() }, + ]; assert_eq!( - parse_extrinsic_arguments(args).await?, + parse_extrinsic_arguments(¶ms, args).await?, [ Value::u128(1), Value::i128(-1), diff --git a/crates/pop-parachains/src/call/metadata/params.rs b/crates/pop-parachains/src/call/metadata/params.rs index 12fd72731..077d27dde 100644 --- a/crates/pop-parachains/src/call/metadata/params.rs +++ b/crates/pop-parachains/src/call/metadata/params.rs @@ -6,7 +6,7 @@ use scale_info::{form::PortableForm, Field, PortableRegistry, TypeDef}; use subxt::{Metadata, OnlineClient, SubstrateConfig}; /// Describes a parameter of an extrinsic. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct Param { /// The name of the parameter. pub name: String, @@ -20,6 +20,8 @@ pub struct Param { pub is_tuple: bool, /// Indicates if the parameter is a Variant. pub is_variant: bool, + /// Indicates if the parameter is a Sequence. + pub is_sequence: bool, } /// Transforms a metadata field into its `Param` representation. @@ -66,6 +68,7 @@ fn type_to_param(name: String, registry: &PortableRegistry, type_id: u32) -> Res is_optional: true, is_tuple: false, is_variant: false, + is_sequence: false, }) } else { Err(Error::MetadataParsingError(name)) @@ -74,16 +77,14 @@ fn type_to_param(name: String, registry: &PortableRegistry, type_id: u32) -> Res // Determine the formatted type name. let type_name = format_type(type_info, registry); match &type_info.type_def { - TypeDef::Primitive(_) | - TypeDef::Array(_) | - TypeDef::Sequence(_) | - TypeDef::Compact(_) => Ok(Param { + TypeDef::Primitive(_) | TypeDef::Array(_) | TypeDef::Compact(_) => Ok(Param { name, type_name, sub_params: Vec::new(), is_optional: false, is_tuple: false, is_variant: false, + is_sequence: false, }), TypeDef::Composite(composite) => { let sub_params = composite @@ -106,6 +107,7 @@ fn type_to_param(name: String, registry: &PortableRegistry, type_id: u32) -> Res is_optional: false, is_tuple: false, is_variant: false, + is_sequence: false, }) }, TypeDef::Variant(variant) => { @@ -132,6 +134,7 @@ fn type_to_param(name: String, registry: &PortableRegistry, type_id: u32) -> Res is_optional: false, is_tuple: false, is_variant: true, + is_sequence: false, }) }) .collect::, Error>>()?; @@ -143,8 +146,18 @@ fn type_to_param(name: String, registry: &PortableRegistry, type_id: u32) -> Res is_optional: false, is_tuple: false, is_variant: true, + is_sequence: false, }) }, + TypeDef::Sequence(_) => Ok(Param { + name, + type_name, + sub_params: Vec::new(), + is_optional: false, + is_tuple: false, + is_variant: false, + is_sequence: true, + }), TypeDef::Tuple(tuple) => { let sub_params = tuple .fields @@ -166,6 +179,7 @@ fn type_to_param(name: String, registry: &PortableRegistry, type_id: u32) -> Res is_optional: false, is_tuple: true, is_variant: false, + is_sequence: false, }) }, _ => Err(Error::MetadataParsingError(name)), diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs index 2368c31f7..971b88db2 100644 --- a/crates/pop-parachains/src/call/mod.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -use crate::errors::Error; +use crate::{errors::Error, Extrinsic}; use pop_common::create_signer; use subxt::{ dynamic::Value, @@ -28,11 +28,12 @@ pub async fn set_up_client(url: &str) -> Result, E /// * `args` - A vector of string arguments to be passed to the extrinsic. pub async fn construct_extrinsic( pallet_name: &str, - extrinsic_name: &str, + extrinsic: &Extrinsic, args: Vec, ) -> Result { - let parsed_args: Vec = metadata::parse_extrinsic_arguments(args).await?; - Ok(subxt::dynamic::tx(pallet_name, extrinsic_name, parsed_args)) + let parsed_args: Vec = + metadata::parse_extrinsic_arguments(&extrinsic.params, args).await?; + Ok(subxt::dynamic::tx(pallet_name, extrinsic.name.clone(), parsed_args)) } /// Signs and submits a given extrinsic. @@ -125,7 +126,7 @@ pub async fn sign_and_submit_extrinsic_with_call_data( mod tests { use super::*; - use crate::set_up_client; + use crate::{find_extrinsic_by_name, parse_chain_metadata, set_up_client}; use anyhow::Result; #[tokio::test] @@ -140,11 +141,16 @@ mod tests { #[tokio::test] async fn construct_extrinsic_works() -> Result<()> { + let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; + let pallets = parse_chain_metadata(&client).await?; + let transfer_allow_death = + find_extrinsic_by_name(&pallets, "Balances", "transfer_allow_death").await?; + // Wrong parameters assert!(matches!( construct_extrinsic( "Balances", - "transfer_allow_death", + &transfer_allow_death, vec!["Bob".to_string(), "100".to_string()], ) .await, @@ -153,7 +159,7 @@ mod tests { // Valid parameters let extrinsic = construct_extrinsic( "Balances", - "transfer_allow_death", + &transfer_allow_death, vec![ "Id(5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty)".to_string(), "100".to_string(), @@ -168,8 +174,14 @@ mod tests { #[tokio::test] async fn encode_call_data_works() -> Result<()> { let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; - let extrinsic = construct_extrinsic("System", "remark", vec!["0x11".to_string()]).await?; + let pallets = parse_chain_metadata(&client).await?; + let remark = find_extrinsic_by_name(&pallets, "System", "remark").await?; + let extrinsic = construct_extrinsic("System", &remark, vec!["0x11".to_string()]).await?; assert_eq!(encode_call_data(&client, &extrinsic)?, "0x00000411"); + let extrinsic = construct_extrinsic("System", &remark, vec!["123".to_string()]).await?; + assert_eq!(encode_call_data(&client, &extrinsic)?, "0x00000c313233"); + let extrinsic = construct_extrinsic("System", &remark, vec!["test".to_string()]).await?; + assert_eq!(encode_call_data(&client, &extrinsic)?, "0x00001074657374"); Ok(()) } @@ -186,8 +198,13 @@ mod tests { #[tokio::test] async fn sign_and_submit_wrong_extrinsic_fails() -> Result<()> { let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; - let tx = - construct_extrinsic("WrongPallet", "wrongExtrinsic", vec!["0x11".to_string()]).await?; + let extrinsic = Extrinsic { + name: "wrong_extrinsic".to_string(), + docs: "documentation".to_string(), + params: vec![], + is_supported: true, + }; + let tx = construct_extrinsic("WrongPallet", &extrinsic, vec!["0x11".to_string()]).await?; assert!(matches!( sign_and_submit_extrinsic(client, tx, "//Alice").await, Err(Error::ExtrinsicSubmissionError(message)) if message.contains("PalletNameNotFound(\"WrongPallet\"))") diff --git a/deny.toml b/deny.toml index 5c295de57..53c6d37e6 100644 --- a/deny.toml +++ b/deny.toml @@ -20,6 +20,7 @@ allow = [ "GPL-3.0", "MIT", "MPL-2.0", + "Unicode-3.0", "Unicode-DFS-2016", "Unlicense" ] From 48bfda91909f727298709b8c75006138b66cdee5 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Mon, 9 Dec 2024 16:39:29 +0100 Subject: [PATCH 185/211] test: fix decode_call_data_works unit test --- crates/pop-cli/src/commands/call/parachain.rs | 1 + crates/pop-parachains/src/call/mod.rs | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 37e702c8a..d4bdecbf7 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -817,6 +817,7 @@ mod tests { url: Some(Url::parse("wss://rpc1.paseo.popnetwork.xyz")?), suri: Some(DEFAULT_URI.to_string()), skip_confirm: false, + call_data: None, }; assert_eq!( call_config.expand_file_arguments()?, diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs index 971b88db2..53ed0ff9b 100644 --- a/crates/pop-parachains/src/call/mod.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -189,7 +189,9 @@ mod tests { async fn decode_call_data_works() -> Result<()> { assert!(matches!(decode_call_data("wrongcalldata"), Err(Error::CallDataDecodingError(..)))); let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; - let extrinsic = construct_extrinsic("System", "remark", vec!["0x11".to_string()]).await?; + let pallets = parse_chain_metadata(&client).await?; + let remark = find_extrinsic_by_name(&pallets, "System", "remark").await?; + let extrinsic = construct_extrinsic("System", &remark, vec!["0x11".to_string()]).await?; let expected_call_data = extrinsic.encode_call_data(&client.metadata())?; assert_eq!(decode_call_data("0x00000411")?, expected_call_data); Ok(()) From 06622ec1e6349f7ed287b0ba258f5f6b4a7c4806 Mon Sep 17 00:00:00 2001 From: Alex Bean Date: Mon, 9 Dec 2024 18:18:41 +0100 Subject: [PATCH 186/211] refactor: use Default derive macro and define constants for test values (#366) * feat: parse files when the argument values are very big * chore: fmt * feat: file logic using the command line * fix: sequence arguments * refactor: parse_extrinsic_arguments * refactor: use Default in pop_parachain structs * refactor: use Default in CallParachainCommand struct * refactor: use constant in tests * chore: fmt and small refactor --- crates/pop-cli/src/commands/call/parachain.rs | 89 +++++++------------ crates/pop-common/src/metadata.rs | 5 +- .../src/call/metadata/action.rs | 7 +- .../pop-parachains/src/call/metadata/mod.rs | 12 +-- .../src/call/metadata/params.rs | 58 +++--------- crates/pop-parachains/src/call/mod.rs | 18 ++-- 6 files changed, 68 insertions(+), 121 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index d4bdecbf7..db0e7c412 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -18,7 +18,7 @@ const DEFAULT_URI: &str = "//Alice"; const ENCODED_CALL_DATA_MAX_LEN: usize = 500; // Maximum length of encoded call data to display. /// Command to execute extrinsics with configurable pallets, arguments, and signing options. -#[derive(Args, Clone)] +#[derive(Args, Clone, Default)] pub struct CallParachainCommand { /// The pallet containing the extrinsic to execute. #[arg(short, long, value_parser = parse_pallet_name)] @@ -564,41 +564,31 @@ mod tests { use tempfile::tempdir; use url::Url; + const BOB_SURI: &str = "//Bob"; + const POP_NETWORK_TESTNET_URL: &str = "wss://rpc1.paseo.popnetwork.xyz"; + const POLKADOT_NETWORK_URL: &str = "wss://polkadot-rpc.publicnode.com"; + #[tokio::test] async fn configure_chain_works() -> Result<()> { - let call_config = CallParachainCommand { - pallet: None, - extrinsic: None, - args: vec![].to_vec(), - url: None, - suri: Some(DEFAULT_URI.to_string()), - skip_confirm: false, - call_data: None, - }; + let call_config = + CallParachainCommand { suri: Some(DEFAULT_URI.to_string()), ..Default::default() }; let mut cli = MockCli::new().expect_intro("Call a parachain").expect_input( "Which chain would you like to interact with?", - "wss://rpc1.paseo.popnetwork.xyz".into(), + POP_NETWORK_TESTNET_URL.into(), ); let chain = call_config.configure_chain(&mut cli).await?; - assert_eq!(chain.url, Url::parse("wss://rpc1.paseo.popnetwork.xyz")?); + assert_eq!(chain.url, Url::parse(POP_NETWORK_TESTNET_URL)?); cli.verify() } #[tokio::test] async fn guide_user_to_call_parachain_works() -> Result<()> { - let mut call_config = CallParachainCommand { - pallet: Some("System".to_string()), - extrinsic: None, - args: vec![].to_vec(), - url: None, - suri: None, - skip_confirm: false, - call_data: None, - }; + let mut call_config = + CallParachainCommand { pallet: Some("System".to_string()), ..Default::default() }; let mut cli = MockCli::new() .expect_intro("Call a parachain") - .expect_input("Which chain would you like to interact with?", "wss://rpc1.paseo.popnetwork.xyz".into()) + .expect_input("Which chain would you like to interact with?", POP_NETWORK_TESTNET_URL.into()) .expect_select( "Select the extrinsic to call:", Some(true), @@ -622,38 +612,30 @@ mod tests { 0, // "remark" extrinsic ) .expect_input("The value for `remark` might be too large to enter. You may enter the path to a file instead.", "0x11".into()) - .expect_input("Signer of the extrinsic:", "//Bob".into()); + .expect_input("Signer of the extrinsic:", BOB_SURI.into()); let chain = call_config.configure_chain(&mut cli).await?; - assert_eq!(chain.url, Url::parse("wss://rpc1.paseo.popnetwork.xyz")?); + assert_eq!(chain.url, Url::parse(POP_NETWORK_TESTNET_URL)?); let call_parachain = call_config.configure_call(&chain, &mut cli).await?; assert_eq!(call_parachain.pallet.name, "System"); assert_eq!(call_parachain.extrinsic.name, "remark"); assert_eq!(call_parachain.args, ["0x11".to_string()].to_vec()); - assert_eq!(call_parachain.suri, "//Bob"); - assert_eq!(call_parachain.display(&chain), "pop call parachain --pallet System --extrinsic remark --args \"0x11\" --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Bob"); + assert_eq!(call_parachain.suri, BOB_SURI); + assert_eq!(call_parachain.display(&chain), format!("pop call parachain --pallet System --extrinsic remark --args \"0x11\" --url {}/ --suri {}", POP_NETWORK_TESTNET_URL, BOB_SURI)); cli.verify() } #[tokio::test] async fn guide_user_to_configure_predefined_action_works() -> Result<()> { - let mut call_config = CallParachainCommand { - pallet: None, - extrinsic: None, - args: vec![].to_vec(), - url: None, - suri: None, - skip_confirm: false, - call_data: None, - }; + let mut call_config = CallParachainCommand::default(); let mut cli = MockCli::new().expect_intro("Call a parachain").expect_input( "Which chain would you like to interact with?", - "wss://polkadot-rpc.publicnode.com".into(), + POLKADOT_NETWORK_URL.into(), ); let chain = call_config.configure_chain(&mut cli).await?; - assert_eq!(chain.url, Url::parse("wss://polkadot-rpc.publicnode.com")?); + assert_eq!(chain.url, Url::parse(POLKADOT_NETWORK_URL)?); cli.verify()?; let mut cli = MockCli::new() @@ -678,33 +660,24 @@ mod tests { ) .expect_input("Enter the value for the parameter: max_amount", "10000".into()) .expect_input("Enter the value for the parameter: para_id", "2000".into()) - .expect_input("Signer of the extrinsic:", "//Bob".into()); + .expect_input("Signer of the extrinsic:", BOB_SURI.into()); let call_parachain = call_config.configure_call(&chain, &mut cli).await?; assert_eq!(call_parachain.pallet.name, "OnDemand"); assert_eq!(call_parachain.extrinsic.name, "place_order_allow_death"); assert_eq!(call_parachain.args, ["10000".to_string(), "2000".to_string()].to_vec()); - assert_eq!(call_parachain.suri, "//Bob"); - assert_eq!(call_parachain.display(&chain), "pop call parachain --pallet OnDemand --extrinsic place_order_allow_death --args \"10000\" \"2000\" --url wss://polkadot-rpc.publicnode.com/ --suri //Bob"); + assert_eq!(call_parachain.suri, BOB_SURI); + assert_eq!(call_parachain.display(&chain), format!("pop call parachain --pallet OnDemand --extrinsic place_order_allow_death --args \"10000\" \"2000\" --url {}/ --suri {}", POLKADOT_NETWORK_URL, BOB_SURI)); cli.verify() } #[tokio::test] async fn prepare_extrinsic_works() -> Result<()> { - let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; + let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; let mut call_config = CallParachain { - pallet: Pallet { - name: "WrongName".to_string(), - docs: "".to_string(), - extrinsics: vec![], - }, - extrinsic: Extrinsic { - name: "WrongName".to_string(), - docs: "".to_string(), - is_supported: false, - params: vec![], - }, + pallet: Pallet { name: "WrongName".to_string(), ..Default::default() }, + extrinsic: Extrinsic { name: "WrongName".to_string(), ..Default::default() }, args: vec!["0x11".to_string()].to_vec(), suri: DEFAULT_URI.to_string(), skip_confirm: false, @@ -732,7 +705,7 @@ mod tests { #[tokio::test] async fn user_cancel_submit_extrinsic_works() -> Result<()> { - let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; + let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; let pallets = parse_chain_metadata(&client).await?; let mut call_config = CallParachain { pallet: find_pallet_by_name(&pallets, "System").await?, @@ -779,7 +752,7 @@ mod tests { pallet: Some("System".to_string()), extrinsic: Some("remark".to_string()), args: vec!["0x11".to_string()].to_vec(), - url: Some(Url::parse("wss://rpc1.paseo.popnetwork.xyz")?), + url: Some(Url::parse(POP_NETWORK_TESTNET_URL)?), suri: Some(DEFAULT_URI.to_string()), skip_confirm: false, call_data: None, @@ -797,7 +770,7 @@ mod tests { pallet: Some("System".to_string()), extrinsic: Some("remark".to_string()), args: vec!["0x11".to_string()].to_vec(), - url: Some(Url::parse("wss://rpc1.paseo.popnetwork.xyz")?), + url: Some(Url::parse(POP_NETWORK_TESTNET_URL)?), suri: Some(DEFAULT_URI.to_string()), skip_confirm: false, call_data: None, @@ -814,7 +787,7 @@ mod tests { pallet: Some("Registrar".to_string()), extrinsic: Some("register".to_string()), args: vec!["2000".to_string(), "0x1".to_string(), "0x12".to_string()].to_vec(), - url: Some(Url::parse("wss://rpc1.paseo.popnetwork.xyz")?), + url: Some(Url::parse(POP_NETWORK_TESTNET_URL)?), suri: Some(DEFAULT_URI.to_string()), skip_confirm: false, call_data: None, @@ -857,7 +830,7 @@ mod tests { #[tokio::test] async fn prompt_predefined_actions_works() -> Result<()> { - let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; + let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; let pallets = parse_chain_metadata(&client).await?; let mut cli = MockCli::new().expect_select( "What would you like to do?", @@ -885,7 +858,7 @@ mod tests { #[tokio::test] async fn prompt_for_param_works() -> Result<()> { - let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; + let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; let pallets = parse_chain_metadata(&client).await?; // Using NFT mint extrinsic to test the majority of subfunctions let extrinsic = find_extrinsic_by_name(&pallets, "Nfts", "mint").await?; diff --git a/crates/pop-common/src/metadata.rs b/crates/pop-common/src/metadata.rs index e8a407c9b..48f46da28 100644 --- a/crates/pop-common/src/metadata.rs +++ b/crates/pop-common/src/metadata.rs @@ -162,10 +162,11 @@ mod tests { use anyhow::Result; use subxt::{OnlineClient, SubstrateConfig}; + const POP_NETWORK_TESTNET_URL: &str = "wss://rpc1.paseo.popnetwork.xyz"; + #[tokio::test] async fn format_type_works() -> Result<()> { - let client = - OnlineClient::::from_url("wss://rpc1.paseo.popnetwork.xyz").await?; + let client = OnlineClient::::from_url(POP_NETWORK_TESTNET_URL).await?; let metadata = client.metadata(); let registry = metadata.types(); diff --git a/crates/pop-parachains/src/call/metadata/action.rs b/crates/pop-parachains/src/call/metadata/action.rs index d687ec355..608c3c227 100644 --- a/crates/pop-parachains/src/call/metadata/action.rs +++ b/crates/pop-parachains/src/call/metadata/action.rs @@ -126,6 +126,9 @@ mod tests { use anyhow::Result; use std::collections::HashMap; + const POP_NETWORK_TESTNET_URL: &str = "wss://rpc1.paseo.popnetwork.xyz"; + const POLKADOT_NETWORK_URL: &str = "wss://polkadot-rpc.publicnode.com"; + #[test] fn action_descriptions_are_correct() { let descriptions = HashMap::from([ @@ -184,7 +187,7 @@ mod tests { async fn supported_actions_works() -> Result<()> { // Test Pop Parachain. let mut client: subxt::OnlineClient = - set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; + set_up_client(POP_NETWORK_TESTNET_URL).await?; let mut actions = supported_actions(&parse_chain_metadata(&client).await?).await; assert_eq!(actions.len(), 5); assert_eq!(actions[0], Action::Transfer); @@ -194,7 +197,7 @@ mod tests { assert_eq!(actions[4], Action::MintNFT); // Test Polkadot Relay Chain. - client = set_up_client("wss://polkadot-rpc.publicnode.com").await?; + client = set_up_client(POLKADOT_NETWORK_URL).await?; actions = supported_actions(&parse_chain_metadata(&client).await?).await; assert_eq!(actions.len(), 4); assert_eq!(actions[0], Action::Transfer); diff --git a/crates/pop-parachains/src/call/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs index d3a057d45..26e8d9276 100644 --- a/crates/pop-parachains/src/call/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -10,7 +10,7 @@ pub mod action; pub mod params; /// Represents a pallet in the blockchain, including its extrinsics. -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct Pallet { /// The name of the pallet. pub name: String, @@ -27,7 +27,7 @@ impl Display for Pallet { } /// Represents an extrinsic. -#[derive(Clone, PartialEq, Eq, Debug)] +#[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct Extrinsic { /// The name of the extrinsic. pub name: String, @@ -180,9 +180,11 @@ mod tests { use anyhow::Result; use subxt::ext::scale_bits; + const POP_NETWORK_TESTNET_URL: &str = "wss://rpc1.paseo.popnetwork.xyz"; + #[tokio::test] async fn parse_chain_metadata_works() -> Result<()> { - let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; + let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; let pallets = parse_chain_metadata(&client).await?; // Test the first pallet is parsed correctly let first_pallet = pallets.first().unwrap(); @@ -208,7 +210,7 @@ mod tests { #[tokio::test] async fn find_pallet_by_name_works() -> Result<()> { - let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; + let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; let pallets = parse_chain_metadata(&client).await?; assert!(matches!( find_pallet_by_name(&pallets, "WrongName").await, @@ -221,7 +223,7 @@ mod tests { #[tokio::test] async fn find_extrinsic_by_name_works() -> Result<()> { - let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; + let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; let pallets = parse_chain_metadata(&client).await?; assert!(matches!( find_extrinsic_by_name(&pallets, "WrongName", "wrong_extrinsic").await, diff --git a/crates/pop-parachains/src/call/metadata/params.rs b/crates/pop-parachains/src/call/metadata/params.rs index 077d27dde..232e4d6b1 100644 --- a/crates/pop-parachains/src/call/metadata/params.rs +++ b/crates/pop-parachains/src/call/metadata/params.rs @@ -66,9 +66,7 @@ fn type_to_param(name: String, registry: &PortableRegistry, type_id: u32) -> Res type_name: sub_param.type_name, sub_params: sub_param.sub_params, is_optional: true, - is_tuple: false, - is_variant: false, - is_sequence: false, + ..Default::default() }) } else { Err(Error::MetadataParsingError(name)) @@ -77,15 +75,8 @@ fn type_to_param(name: String, registry: &PortableRegistry, type_id: u32) -> Res // Determine the formatted type name. let type_name = format_type(type_info, registry); match &type_info.type_def { - TypeDef::Primitive(_) | TypeDef::Array(_) | TypeDef::Compact(_) => Ok(Param { - name, - type_name, - sub_params: Vec::new(), - is_optional: false, - is_tuple: false, - is_variant: false, - is_sequence: false, - }), + TypeDef::Primitive(_) | TypeDef::Array(_) | TypeDef::Compact(_) => + Ok(Param { name, type_name, ..Default::default() }), TypeDef::Composite(composite) => { let sub_params = composite .fields @@ -100,15 +91,7 @@ fn type_to_param(name: String, registry: &PortableRegistry, type_id: u32) -> Res }) .collect::, Error>>()?; - Ok(Param { - name, - type_name, - sub_params, - is_optional: false, - is_tuple: false, - is_variant: false, - is_sequence: false, - }) + Ok(Param { name, type_name, sub_params, ..Default::default() }) }, TypeDef::Variant(variant) => { let variant_params = variant @@ -131,10 +114,8 @@ fn type_to_param(name: String, registry: &PortableRegistry, type_id: u32) -> Res name: variant_param.name.clone(), type_name: "".to_string(), sub_params: variant_sub_params, - is_optional: false, - is_tuple: false, is_variant: true, - is_sequence: false, + ..Default::default() }) }) .collect::, Error>>()?; @@ -143,21 +124,12 @@ fn type_to_param(name: String, registry: &PortableRegistry, type_id: u32) -> Res name, type_name, sub_params: variant_params, - is_optional: false, - is_tuple: false, is_variant: true, - is_sequence: false, + ..Default::default() }) }, - TypeDef::Sequence(_) => Ok(Param { - name, - type_name, - sub_params: Vec::new(), - is_optional: false, - is_tuple: false, - is_variant: false, - is_sequence: true, - }), + TypeDef::Sequence(_) => + Ok(Param { name, type_name, is_sequence: true, ..Default::default() }), TypeDef::Tuple(tuple) => { let sub_params = tuple .fields @@ -172,15 +144,7 @@ fn type_to_param(name: String, registry: &PortableRegistry, type_id: u32) -> Res }) .collect::, Error>>()?; - Ok(Param { - name, - type_name, - sub_params, - is_optional: false, - is_tuple: true, - is_variant: false, - is_sequence: false, - }) + Ok(Param { name, type_name, sub_params, is_tuple: true, ..Default::default() }) }, _ => Err(Error::MetadataParsingError(name)), } @@ -193,9 +157,11 @@ mod tests { use crate::set_up_client; use anyhow::Result; + const POP_NETWORK_TESTNET_URL: &str = "wss://rpc1.paseo.popnetwork.xyz"; + #[tokio::test] async fn field_to_param_works() -> Result<()> { - let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; + let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; let metadata = client.metadata(); // Test a supported extrinsic let extrinsic = metadata diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs index 53ed0ff9b..136d0d2d9 100644 --- a/crates/pop-parachains/src/call/mod.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -125,23 +125,25 @@ pub async fn sign_and_submit_extrinsic_with_call_data( #[cfg(test)] mod tests { use super::*; - use crate::{find_extrinsic_by_name, parse_chain_metadata, set_up_client}; use anyhow::Result; + const ALICE_SURI: &str = "//Alice"; + const POP_NETWORK_TESTNET_URL: &str = "wss://rpc1.paseo.popnetwork.xyz"; + #[tokio::test] async fn set_up_client_works() -> Result<()> { assert!(matches!( set_up_client("wss://wronguri.xyz").await, Err(Error::ConnectionFailure(_)) )); - set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; + set_up_client(POP_NETWORK_TESTNET_URL).await?; Ok(()) } #[tokio::test] async fn construct_extrinsic_works() -> Result<()> { - let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; + let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; let pallets = parse_chain_metadata(&client).await?; let transfer_allow_death = find_extrinsic_by_name(&pallets, "Balances", "transfer_allow_death").await?; @@ -151,7 +153,7 @@ mod tests { construct_extrinsic( "Balances", &transfer_allow_death, - vec!["Bob".to_string(), "100".to_string()], + vec![ALICE_SURI.to_string(), "100".to_string()], ) .await, Err(Error::ParamProcessingError) @@ -173,7 +175,7 @@ mod tests { #[tokio::test] async fn encode_call_data_works() -> Result<()> { - let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; + let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; let pallets = parse_chain_metadata(&client).await?; let remark = find_extrinsic_by_name(&pallets, "System", "remark").await?; let extrinsic = construct_extrinsic("System", &remark, vec!["0x11".to_string()]).await?; @@ -199,16 +201,16 @@ mod tests { #[tokio::test] async fn sign_and_submit_wrong_extrinsic_fails() -> Result<()> { - let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; + let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; let extrinsic = Extrinsic { name: "wrong_extrinsic".to_string(), docs: "documentation".to_string(), - params: vec![], is_supported: true, + ..Default::default() }; let tx = construct_extrinsic("WrongPallet", &extrinsic, vec!["0x11".to_string()]).await?; assert!(matches!( - sign_and_submit_extrinsic(client, tx, "//Alice").await, + sign_and_submit_extrinsic(client, tx, ALICE_SURI).await, Err(Error::ExtrinsicSubmissionError(message)) if message.contains("PalletNameNotFound(\"WrongPallet\"))") )); Ok(()) From 562e7834f0d75a8d9a54bd0de8891611ce3cbdb5 Mon Sep 17 00:00:00 2001 From: Alex Bean Date: Mon, 9 Dec 2024 18:37:11 +0100 Subject: [PATCH 187/211] feat: flag sudo to wrap extrinsic (#349) * feat: submit extrinsic from call_data * test: unit test for initialize_api_client * feat: wrap call into a sudo call * test: add unit test to the new logic * fix: skip_confirm for send_extrinsic_from_call_data * chore: clippy * docs: renaming and improve docs * test: use force_transfer for testing * fix: check if sudo exist before prompt the user * chore: fmt * chore: fmt * test: fix wrong assert * docs: improve comments and output messages * refactor: split decode_call_data logic outside sign_and_submit_extrinsic_with_call_data * fix: test construct_sudo_extrinsic_works and formatting --- crates/pop-cli/src/commands/call/parachain.rs | 106 ++++++++++++++++-- crates/pop-parachains/src/call/mod.rs | 30 +++++ crates/pop-parachains/src/lib.rs | 2 +- 3 files changed, 128 insertions(+), 10 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index db0e7c412..d14787fdd 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -6,10 +6,10 @@ use crate::cli::{self, traits::*}; use anyhow::{anyhow, Result}; use clap::Args; use pop_parachains::{ - construct_extrinsic, decode_call_data, encode_call_data, find_extrinsic_by_name, - find_pallet_by_name, parse_chain_metadata, set_up_client, sign_and_submit_extrinsic, - sign_and_submit_extrinsic_with_call_data, supported_actions, Action, DynamicPayload, Extrinsic, - OnlineClient, Pallet, Param, SubstrateConfig, + construct_extrinsic, construct_sudo_extrinsic, decode_call_data, encode_call_data, + find_extrinsic_by_name, find_pallet_by_name, parse_chain_metadata, set_up_client, + sign_and_submit_extrinsic, sign_and_submit_extrinsic_with_call_data, supported_actions, Action, + DynamicPayload, Extrinsic, OnlineClient, Pallet, Param, SubstrateConfig, }; use url::Url; @@ -42,6 +42,9 @@ pub struct CallParachainCommand { /// SCALE encoded bytes representing the call data of the extrinsic. #[arg(name = "call", long, conflicts_with_all = ["pallet", "extrinsic", "args"])] call_data: Option, + /// Authenticates the sudo key and dispatches a function call with `Root` origin. + #[arg(short = 'S', long)] + sudo: bool, /// Automatically signs and submits the extrinsic without prompting for confirmation. #[arg(short('y'), long)] skip_confirm: bool, @@ -190,6 +193,10 @@ impl CallParachainCommand { self.expand_file_arguments()? }; + // If chain has sudo prompt the user to confirm if they want to execute the call via + // sudo. + self.configure_sudo(chain, cli).await?; + // Resolve who is signing the extrinsic. let suri = match self.suri.as_ref() { Some(suri) => suri.clone(), @@ -203,6 +210,7 @@ impl CallParachainCommand { args, suri, skip_confirm: self.skip_confirm, + sudo: self.sudo, }); } } @@ -246,11 +254,36 @@ impl CallParachainCommand { Ok(()) } + // Checks if the chain has the Sudo pallet and prompt the user to confirm if they want to + // execute the call via sudo. + async fn configure_sudo(&mut self, chain: &Chain, cli: &mut impl Cli) -> Result<()> { + match find_extrinsic_by_name(&chain.pallets, "Sudo", "sudo").await { + Ok(_) => + if !self.sudo { + self.sudo = cli + .confirm( + "Would you like to dispatch this function call with `Root` origin?", + ) + .initial_value(false) + .interact()?; + }, + Err(_) => + if self.sudo { + cli.warning( + "NOTE: sudo is not supported by the chain. Ignoring `--sudo` flag.", + )?; + self.sudo = false; + }, + } + Ok(()) + } + // Resets specific fields to default values for a new call. fn reset_for_new_call(&mut self) { self.pallet = None; self.extrinsic = None; self.args.clear(); + self.sudo = false; } // Function to check if all required fields are specified. @@ -306,6 +339,8 @@ struct CallParachain { suri: String, /// Whether to automatically sign and submit the extrinsic without prompting for confirmation. skip_confirm: bool, + /// Whether to dispatch the function call with `Root` origin. + sudo: bool, } impl CallParachain { @@ -327,6 +362,8 @@ impl CallParachain { return Err(anyhow!("Error: {}", e)); }, }; + // If sudo is required, wrap the call in a sudo call. + let tx = if self.sudo { construct_sudo_extrinsic(tx).await? } else { tx }; let encoded_data = encode_call_data(client, &tx)?; // If the encoded call data is too long, don't display it all. if encoded_data.len() < ENCODED_CALL_DATA_MAX_LEN { @@ -383,6 +420,9 @@ impl CallParachain { full_message.push_str(&format!(" --args {}", args.join(" "))); } full_message.push_str(&format!(" --url {} --suri {}", chain.url, self.suri)); + if self.sudo { + full_message.push_str(" --sudo"); + } full_message } } @@ -612,7 +652,8 @@ mod tests { 0, // "remark" extrinsic ) .expect_input("The value for `remark` might be too large to enter. You may enter the path to a file instead.", "0x11".into()) - .expect_input("Signer of the extrinsic:", BOB_SURI.into()); + .expect_confirm("Would you like to dispatch this function call with `Root` origin?", true) + .expect_input("Signer of the extrinsic:", "//Bob".into()); let chain = call_config.configure_chain(&mut cli).await?; assert_eq!(chain.url, Url::parse(POP_NETWORK_TESTNET_URL)?); @@ -621,8 +662,9 @@ mod tests { assert_eq!(call_parachain.pallet.name, "System"); assert_eq!(call_parachain.extrinsic.name, "remark"); assert_eq!(call_parachain.args, ["0x11".to_string()].to_vec()); - assert_eq!(call_parachain.suri, BOB_SURI); - assert_eq!(call_parachain.display(&chain), format!("pop call parachain --pallet System --extrinsic remark --args \"0x11\" --url {}/ --suri {}", POP_NETWORK_TESTNET_URL, BOB_SURI)); + assert_eq!(call_parachain.suri, "//Bob"); + assert!(call_parachain.sudo); + assert_eq!(call_parachain.display(&chain), "pop call parachain --pallet System --extrinsic remark --args \"0x11\" --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Bob --sudo"); cli.verify() } @@ -667,8 +709,9 @@ mod tests { assert_eq!(call_parachain.pallet.name, "OnDemand"); assert_eq!(call_parachain.extrinsic.name, "place_order_allow_death"); assert_eq!(call_parachain.args, ["10000".to_string(), "2000".to_string()].to_vec()); - assert_eq!(call_parachain.suri, BOB_SURI); - assert_eq!(call_parachain.display(&chain), format!("pop call parachain --pallet OnDemand --extrinsic place_order_allow_death --args \"10000\" \"2000\" --url {}/ --suri {}", POLKADOT_NETWORK_URL, BOB_SURI)); + assert_eq!(call_parachain.suri, "//Bob"); + assert!(!call_parachain.sudo); + assert_eq!(call_parachain.display(&chain), "pop call parachain --pallet OnDemand --extrinsic place_order_allow_death --args \"10000\" \"2000\" --url wss://polkadot-rpc.publicnode.com/ --suri //Bob"); cli.verify() } @@ -681,6 +724,7 @@ mod tests { args: vec!["0x11".to_string()].to_vec(), suri: DEFAULT_URI.to_string(), skip_confirm: false, + sudo: false, }; let mut cli = MockCli::new(); // Error, wrong name of the pallet. @@ -700,6 +744,11 @@ mod tests { assert_eq!(tx.call_name(), "remark"); assert_eq!(tx.pallet_name(), "System"); + // Prepare extrinsic wrapped in sudo works. + cli = MockCli::new().expect_info("Encoded call data: 0x0f0000000411"); + call_config.sudo = true; + call_config.prepare_extrinsic(&client, &mut cli).await?; + cli.verify() } @@ -713,6 +762,7 @@ mod tests { args: vec!["0x11".to_string()].to_vec(), suri: DEFAULT_URI.to_string(), skip_confirm: false, + sudo: false, }; let mut cli = MockCli::new() .expect_confirm("Do you want to submit the extrinsic?", false) @@ -734,6 +784,7 @@ mod tests { suri: None, skip_confirm: false, call_data: Some("0x00000411".to_string()), + sudo: false, }; let mut cli = MockCli::new() .expect_input("Signer of the extrinsic:", "//Bob".into()) @@ -746,6 +797,39 @@ mod tests { cli.verify() } + #[tokio::test] + async fn configure_sudo_works() -> Result<()> { + // Test when sudo pallet doesn't exist. + let mut call_config = CallParachainCommand { + pallet: None, + extrinsic: None, + args: vec![].to_vec(), + url: Some(Url::parse("wss://polkadot-rpc.publicnode.com")?), + suri: Some("//Alice".to_string()), + skip_confirm: false, + call_data: Some("0x00000411".to_string()), + sudo: true, + }; + let mut cli = MockCli::new() + .expect_intro("Call a parachain") + .expect_warning("NOTE: sudo is not supported by the chain. Ignoring `--sudo` flag."); + let chain = call_config.configure_chain(&mut cli).await?; + call_config.configure_sudo(&chain, &mut cli).await?; + assert!(!call_config.sudo); + cli.verify()?; + + // Test when sudo pallet exist. + cli = MockCli::new().expect_intro("Call a parachain").expect_confirm( + "Would you like to dispatch this function call with `Root` origin?", + true, + ); + call_config.url = Some(Url::parse("wss://rpc1.paseo.popnetwork.xyz")?); + let chain = call_config.configure_chain(&mut cli).await?; + call_config.configure_sudo(&chain, &mut cli).await?; + assert!(call_config.sudo); + cli.verify() + } + #[test] fn reset_for_new_call_works() -> Result<()> { let mut call_config = CallParachainCommand { @@ -756,11 +840,13 @@ mod tests { suri: Some(DEFAULT_URI.to_string()), skip_confirm: false, call_data: None, + sudo: true, }; call_config.reset_for_new_call(); assert_eq!(call_config.pallet, None); assert_eq!(call_config.extrinsic, None); assert_eq!(call_config.args.len(), 0); + assert!(!call_config.sudo); Ok(()) } @@ -774,6 +860,7 @@ mod tests { suri: Some(DEFAULT_URI.to_string()), skip_confirm: false, call_data: None, + sudo: false, }; assert!(!call_config.requires_user_input()); call_config.pallet = None; @@ -791,6 +878,7 @@ mod tests { suri: Some(DEFAULT_URI.to_string()), skip_confirm: false, call_data: None, + sudo: false, }; assert_eq!( call_config.expand_file_arguments()?, diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs index 136d0d2d9..967e8b744 100644 --- a/crates/pop-parachains/src/call/mod.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -36,6 +36,15 @@ pub async fn construct_extrinsic( Ok(subxt::dynamic::tx(pallet_name, extrinsic.name.clone(), parsed_args)) } +/// Constructs a Sudo extrinsic. +/// +/// # Arguments +/// * `tx`: The transaction payload representing the function call to be dispatched with `Root` +/// privileges. +pub async fn construct_sudo_extrinsic(tx: DynamicPayload) -> Result { + Ok(subxt::dynamic::tx("Sudo", "sudo", [tx.into_value()].to_vec())) +} + /// Signs and submits a given extrinsic. /// /// # Arguments @@ -215,4 +224,25 @@ mod tests { )); Ok(()) } + + #[tokio::test] + async fn construct_sudo_extrinsic_works() -> Result<()> { + let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; + let pallets = parse_chain_metadata(&client).await?; + let force_transfer = find_extrinsic_by_name(&pallets, "Balances", "force_transfer").await?; + let extrinsic = construct_extrinsic( + "Balances", + &force_transfer, + vec![ + "Id(5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty)".to_string(), + "Id(5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy)".to_string(), + "100".to_string(), + ], + ) + .await?; + let sudo_extrinsic = construct_sudo_extrinsic(extrinsic).await?; + assert_eq!(sudo_extrinsic.call_name(), "sudo"); + assert_eq!(sudo_extrinsic.pallet_name(), "Sudo"); + Ok(()) + } } diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index 00f249751..b2bbfa649 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -17,7 +17,7 @@ pub use build::{ generate_plain_chain_spec, generate_raw_chain_spec, is_supported, ChainSpec, }; pub use call::{ - construct_extrinsic, decode_call_data, encode_call_data, + construct_extrinsic, construct_sudo_extrinsic, decode_call_data, encode_call_data, metadata::{ action::{supported_actions, Action}, find_extrinsic_by_name, find_pallet_by_name, From 9903944ce46460345dc501e2f84a54df2b497009 Mon Sep 17 00:00:00 2001 From: Frank Bell <60948618+evilrobot-01@users.noreply.github.com> Date: Mon, 9 Dec 2024 18:27:27 +0000 Subject: [PATCH 188/211] refactor: various fixes and improvements (#367) * refactor: sort pallets/dispatchables * refactor: remove unnecessary async * fix: resolve issue after rebase * fix: more async issues after rebase * refactor: use single constant --- crates/pop-cli/src/commands/call/parachain.rs | 131 ++++++++---------- .../src/call/metadata/action.rs | 14 +- .../pop-parachains/src/call/metadata/mod.rs | 76 ++++++---- .../src/call/metadata/params.rs | 4 +- crates/pop-parachains/src/call/mod.rs | 51 ++++--- 5 files changed, 137 insertions(+), 139 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index d14787fdd..9641cb24b 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -70,7 +70,7 @@ impl CallParachainCommand { } loop { // Configure the call based on command line arguments/call UI. - let mut call = match self.configure_call(&chain, &mut cli).await { + let mut call = match self.configure_call(&chain, &mut cli) { Ok(call) => call, Err(e) => { display_message(&e.to_string(), false, &mut cli)?; @@ -80,7 +80,7 @@ impl CallParachainCommand { // Display the configured call. cli.info(call.display(&chain))?; // Prepare the extrinsic. - let tx = match call.prepare_extrinsic(&chain.client, &mut cli).await { + let tx = match call.prepare_extrinsic(&chain.client, &mut cli) { Ok(payload) => payload, Err(e) => { display_message(&e.to_string(), false, &mut cli)?; @@ -125,31 +125,32 @@ impl CallParachainCommand { // Parse metadata from chain url. let client = set_up_client(url.as_str()).await?; - let pallets = parse_chain_metadata(&client).await.map_err(|e| { + 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.extrinsics.sort_by(|a, b| a.name.cmp(&b.name))); Ok(Chain { url, client, pallets }) } // Configure the call based on command line arguments/call UI. - async fn configure_call(&mut self, chain: &Chain, cli: &mut impl Cli) -> Result { + fn configure_call(&mut self, chain: &Chain, cli: &mut impl Cli) -> Result { loop { // Resolve pallet. let pallet = match self.pallet { - Some(ref pallet_name) => find_pallet_by_name(&chain.pallets, pallet_name).await?, + Some(ref pallet_name) => find_pallet_by_name(&chain.pallets, pallet_name)?, None => { // Specific predefined actions first. - if let Some(action) = prompt_predefined_actions(&chain.pallets, cli).await? { + if let Some(action) = prompt_predefined_actions(&chain.pallets, cli)? { self.extrinsic = Some(action.extrinsic_name().to_string()); - find_pallet_by_name(&chain.pallets, action.pallet_name()).await? + find_pallet_by_name(&chain.pallets, action.pallet_name())? } else { let mut prompt = cli.select("Select the pallet to call:"); for pallet_item in &chain.pallets { - prompt = prompt.item( - pallet_item.clone(), - &pallet_item.name, - &pallet_item.docs, - ); + prompt = prompt.item(pallet_item, &pallet_item.name, &pallet_item.docs); } prompt.interact()? } @@ -159,15 +160,12 @@ impl CallParachainCommand { // Resolve extrinsic. let extrinsic = match self.extrinsic { Some(ref extrinsic_name) => - find_extrinsic_by_name(&chain.pallets, &pallet.name, extrinsic_name).await?, + find_extrinsic_by_name(&chain.pallets, &pallet.name, extrinsic_name)?, None => { let mut prompt_extrinsic = cli.select("Select the extrinsic to call:"); for extrinsic in &pallet.extrinsics { - prompt_extrinsic = prompt_extrinsic.item( - extrinsic.clone(), - &extrinsic.name, - &extrinsic.docs, - ); + prompt_extrinsic = + prompt_extrinsic.item(extrinsic, &extrinsic.name, &extrinsic.docs); } prompt_extrinsic.interact()? }, @@ -195,7 +193,7 @@ impl CallParachainCommand { // If chain has sudo prompt the user to confirm if they want to execute the call via // sudo. - self.configure_sudo(chain, cli).await?; + self.configure_sudo(chain, cli)?; // Resolve who is signing the extrinsic. let suri = match self.suri.as_ref() { @@ -205,8 +203,8 @@ impl CallParachainCommand { }; return Ok(CallParachain { - pallet, - extrinsic, + pallet: pallet.clone(), + extrinsic: extrinsic.clone(), args, suri, skip_confirm: self.skip_confirm, @@ -256,8 +254,8 @@ impl CallParachainCommand { // Checks if the chain has the Sudo pallet and prompt the user to confirm if they want to // execute the call via sudo. - async fn configure_sudo(&mut self, chain: &Chain, cli: &mut impl Cli) -> Result<()> { - match find_extrinsic_by_name(&chain.pallets, "Sudo", "sudo").await { + fn configure_sudo(&mut self, chain: &Chain, cli: &mut impl Cli) -> Result<()> { + match find_extrinsic_by_name(&chain.pallets, "Sudo", "sudo") { Ok(_) => if !self.sudo { self.sudo = cli @@ -345,7 +343,7 @@ struct CallParachain { impl CallParachain { // Prepares the extrinsic or query. - async fn prepare_extrinsic( + fn prepare_extrinsic( &self, client: &OnlineClient, cli: &mut impl Cli, @@ -354,16 +352,14 @@ impl CallParachain { self.pallet.name.as_str(), &self.extrinsic, self.args.clone(), - ) - .await - { + ) { Ok(tx) => tx, Err(e) => { return Err(anyhow!("Error: {}", e)); }, }; // If sudo is required, wrap the call in a sudo call. - let tx = if self.sudo { construct_sudo_extrinsic(tx).await? } else { tx }; + let tx = if self.sudo { construct_sudo_extrinsic(tx)? } else { tx }; let encoded_data = encode_call_data(client, &tx)?; // If the encoded call data is too long, don't display it all. if encoded_data.len() < ENCODED_CALL_DATA_MAX_LEN { @@ -438,12 +434,9 @@ fn display_message(message: &str, success: bool, cli: &mut impl Cli) -> Result<( } // Prompts the user for some predefined actions. -async fn prompt_predefined_actions( - pallets: &[Pallet], - cli: &mut impl Cli, -) -> Result> { +fn prompt_predefined_actions(pallets: &[Pallet], cli: &mut impl Cli) -> Result> { let mut predefined_action = cli.select("What would you like to do?"); - for action in supported_actions(pallets).await { + for action in supported_actions(pallets) { predefined_action = predefined_action.item( Some(action.clone()), action.description(), @@ -635,21 +628,21 @@ mod tests { true, Some( [ - ("remark".to_string(), "Make some on-chain remark.Can be executed by every `origin`.".to_string()), - ("set_heap_pages".to_string(), "Set the number of pages in the WebAssembly environment's heap.".to_string()), - ("set_code".to_string(), "Set the new runtime code.".to_string()), - ("set_code_without_checks".to_string(), "Set the new runtime code without doing any checks of the given `code`.Note that runtime upgrades will not run if this is called with a not-increasing specversion!".to_string()), - ("set_storage".to_string(), "Set some items of storage.".to_string()), + ("apply_authorized_upgrade".to_string(), "Provide the preimage (runtime binary) `code` for an upgrade that has been authorized. If the authorization required a version check, this call will ensure the spec name remains unchanged and that the spec version has increased. Depending on the runtime's `OnSetCode` configuration, this function may directly apply the new `code` in the same block or attempt to schedule the upgrade. All origins are allowed.".to_string()), + ("authorize_upgrade".to_string(), "Authorize an upgrade to a given `code_hash` for the runtime. The runtime can be supplied later. This call requires Root origin.".to_string()), + ("authorize_upgrade_without_checks".to_string(), "Authorize an upgrade to a given `code_hash` for the runtime. The runtime can be supplied later. WARNING: This authorizes an upgrade that will take place without any safety checks, for example that the spec name remains the same and that the version number increases. Not recommended for normal use. Use `authorize_upgrade` instead. This call requires Root origin.".to_string()), + ("kill_prefix".to_string(), "Kill all storage items with a key that starts with the given prefix. **NOTE:** We rely on the Root origin to provide us the number of subkeys under the prefix we are removing to accurately calculate the weight of this function.".to_string()), ("kill_storage".to_string(), "Kill some items from storage.".to_string()), - ("kill_prefix".to_string(), "Kill all storage items with a key that starts with the given prefix.**NOTE:** We rely on the Root origin to provide us the number of subkeys underthe prefix we are removing to accurately calculate the weight of this function.".to_string()), + ("remark".to_string(), "Make some on-chain remark. Can be executed by every `origin`.".to_string()), ("remark_with_event".to_string(), "Make some on-chain remark and emit event.".to_string()), - ("authorize_upgrade".to_string(), "Authorize an upgrade to a given `code_hash` for the runtime. The runtime can be suppliedlater.This call requires Root origin.".to_string()), - ("authorize_upgrade_without_checks".to_string(), "Authorize an upgrade to a given `code_hash` for the runtime. The runtime can be suppliedlater.WARNING: This authorizes an upgrade that will take place without any safety checks, forexample that the spec name remains the same and that the version number increases. Notrecommended for normal use. Use `authorize_upgrade` instead.This call requires Root origin.".to_string()), - ("apply_authorized_upgrade".to_string(), "Provide the preimage (runtime binary) `code` for an upgrade that has been authorized.If the authorization required a version check, this call will ensure the spec nameremains unchanged and that the spec version has increased.Depending on the runtime's `OnSetCode` configuration, this function may directly applythe new `code` in the same block or attempt to schedule the upgrade.All origins are allowed.".to_string()), + ("set_code".to_string(), "Set the new runtime code.".to_string()), + ("set_code_without_checks".to_string(), "Set the new runtime code without doing any checks of the given `code`. Note that runtime upgrades will not run if this is called with a not-increasing spec version!".to_string()), + ("set_heap_pages".to_string(), "Set the number of pages in the WebAssembly environment's heap.".to_string()), + ("set_storage".to_string(), "Set some items of storage.".to_string()), ] .to_vec(), ), - 0, // "remark" extrinsic + 5, // "remark" extrinsic ) .expect_input("The value for `remark` might be too large to enter. You may enter the path to a file instead.", "0x11".into()) .expect_confirm("Would you like to dispatch this function call with `Root` origin?", true) @@ -658,7 +651,7 @@ mod tests { let chain = call_config.configure_chain(&mut cli).await?; assert_eq!(chain.url, Url::parse(POP_NETWORK_TESTNET_URL)?); - let call_parachain = call_config.configure_call(&chain, &mut cli).await?; + let call_parachain = call_config.configure_call(&chain, &mut cli)?; assert_eq!(call_parachain.pallet.name, "System"); assert_eq!(call_parachain.extrinsic.name, "remark"); assert_eq!(call_parachain.args, ["0x11".to_string()].to_vec()); @@ -687,7 +680,6 @@ mod tests { true, Some( supported_actions(&chain.pallets) - .await .into_iter() .map(|action| { (action.description().to_string(), action.pallet_name().to_string()) @@ -704,7 +696,7 @@ mod tests { .expect_input("Enter the value for the parameter: para_id", "2000".into()) .expect_input("Signer of the extrinsic:", BOB_SURI.into()); - let call_parachain = call_config.configure_call(&chain, &mut cli).await?; + let call_parachain = call_config.configure_call(&chain, &mut cli)?; assert_eq!(call_parachain.pallet.name, "OnDemand"); assert_eq!(call_parachain.extrinsic.name, "place_order_allow_death"); @@ -729,25 +721,25 @@ mod tests { let mut cli = MockCli::new(); // Error, wrong name of the pallet. assert!( - matches!(call_config.prepare_extrinsic(&client, &mut cli).await, Err(message) if message.to_string().contains("Failed to encode call data. Metadata Error: Pallet with name WrongName not found")) + matches!(call_config.prepare_extrinsic(&client, &mut cli), Err(message) if message.to_string().contains("Failed to encode call data. Metadata Error: Pallet with name WrongName not found")) ); - let pallets = parse_chain_metadata(&client).await?; - call_config.pallet = find_pallet_by_name(&pallets, "System").await?; + let pallets = parse_chain_metadata(&client)?; + call_config.pallet = find_pallet_by_name(&pallets, "System")?.clone(); // Error, wrong name of the extrinsic. assert!( - matches!(call_config.prepare_extrinsic(&client, &mut cli).await, Err(message) if message.to_string().contains("Failed to encode call data. Metadata Error: Call with name WrongName not found")) + matches!(call_config.prepare_extrinsic(&client, &mut cli), Err(message) if message.to_string().contains("Failed to encode call data. Metadata Error: Call with name WrongName not found")) ); // Success, extrinsic and pallet specified. cli = MockCli::new().expect_info("Encoded call data: 0x00000411"); - call_config.extrinsic = find_extrinsic_by_name(&pallets, "System", "remark").await?; - let tx = call_config.prepare_extrinsic(&client, &mut cli).await?; + call_config.extrinsic = find_extrinsic_by_name(&pallets, "System", "remark")?.clone(); + let tx = call_config.prepare_extrinsic(&client, &mut cli)?; assert_eq!(tx.call_name(), "remark"); assert_eq!(tx.pallet_name(), "System"); // Prepare extrinsic wrapped in sudo works. cli = MockCli::new().expect_info("Encoded call data: 0x0f0000000411"); call_config.sudo = true; - call_config.prepare_extrinsic(&client, &mut cli).await?; + call_config.prepare_extrinsic(&client, &mut cli)?; cli.verify() } @@ -755,10 +747,10 @@ mod tests { #[tokio::test] async fn user_cancel_submit_extrinsic_works() -> Result<()> { let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; - let pallets = parse_chain_metadata(&client).await?; + let pallets = parse_chain_metadata(&client)?; let mut call_config = CallParachain { - pallet: find_pallet_by_name(&pallets, "System").await?, - extrinsic: find_extrinsic_by_name(&pallets, "System", "remark").await?, + pallet: find_pallet_by_name(&pallets, "System")?.clone(), + extrinsic: find_extrinsic_by_name(&pallets, "System", "remark")?.clone(), args: vec!["0x11".to_string()].to_vec(), suri: DEFAULT_URI.to_string(), skip_confirm: false, @@ -767,7 +759,7 @@ mod tests { let mut cli = MockCli::new() .expect_confirm("Do you want to submit the extrinsic?", false) .expect_outro_cancel("Extrinsic remark was not submitted."); - let tx = call_config.prepare_extrinsic(&client, &mut cli).await?; + let tx = call_config.prepare_extrinsic(&client, &mut cli)?; call_config.submit_extrinsic(&client, tx, &mut cli).await?; cli.verify() @@ -814,7 +806,7 @@ mod tests { .expect_intro("Call a parachain") .expect_warning("NOTE: sudo is not supported by the chain. Ignoring `--sudo` flag."); let chain = call_config.configure_chain(&mut cli).await?; - call_config.configure_sudo(&chain, &mut cli).await?; + call_config.configure_sudo(&chain, &mut cli)?; assert!(!call_config.sudo); cli.verify()?; @@ -825,7 +817,7 @@ mod tests { ); call_config.url = Some(Url::parse("wss://rpc1.paseo.popnetwork.xyz")?); let chain = call_config.configure_chain(&mut cli).await?; - call_config.configure_sudo(&chain, &mut cli).await?; + call_config.configure_sudo(&chain, &mut cli)?; assert!(call_config.sudo); cli.verify() } @@ -876,8 +868,8 @@ mod tests { args: vec!["2000".to_string(), "0x1".to_string(), "0x12".to_string()].to_vec(), url: Some(Url::parse(POP_NETWORK_TESTNET_URL)?), suri: Some(DEFAULT_URI.to_string()), - skip_confirm: false, call_data: None, + skip_confirm: false, sudo: false, }; assert_eq!( @@ -919,14 +911,13 @@ mod tests { #[tokio::test] async fn prompt_predefined_actions_works() -> Result<()> { let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; - let pallets = parse_chain_metadata(&client).await?; + let pallets = parse_chain_metadata(&client)?; let mut cli = MockCli::new().expect_select( "What would you like to do?", Some(true), true, Some( supported_actions(&pallets) - .await .into_iter() .map(|action| { (action.description().to_string(), action.pallet_name().to_string()) @@ -939,7 +930,7 @@ mod tests { ), 2, // "Mint an Asset" action ); - let action = prompt_predefined_actions(&pallets, &mut cli).await?; + let action = prompt_predefined_actions(&pallets, &mut cli)?; assert_eq!(action, Some(Action::MintAsset)); cli.verify() } @@ -947,9 +938,9 @@ mod tests { #[tokio::test] async fn prompt_for_param_works() -> Result<()> { let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; - let pallets = parse_chain_metadata(&client).await?; + let pallets = parse_chain_metadata(&client)?; // Using NFT mint extrinsic to test the majority of subfunctions - let extrinsic = find_extrinsic_by_name(&pallets, "Nfts", "mint").await?; + let extrinsic = find_extrinsic_by_name(&pallets, "Nfts", "mint")?; let mut cli = MockCli::new() .expect_input("Enter the value for the parameter: collection", "0".into()) .expect_input("Enter the value for the parameter: item", "0".into()) @@ -989,7 +980,7 @@ mod tests { // Test all the extrinsic params let mut params: Vec = Vec::new(); - for param in extrinsic.params { + for param in &extrinsic.params { params.push(prompt_for_param(&mut cli, ¶m)?); } assert_eq!(params.len(), 4); @@ -1000,7 +991,7 @@ mod tests { cli.verify()?; // Using Scheduler set_retry extrinsic to test the tuple params - let extrinsic = find_extrinsic_by_name(&pallets, "Scheduler", "set_retry").await?; + let extrinsic = find_extrinsic_by_name(&pallets, "Scheduler", "set_retry")?; let mut cli = MockCli::new() .expect_input( "Enter the value for the parameter: Index 0 of the tuple task", @@ -1015,7 +1006,7 @@ mod tests { // Test all the extrinsic params let mut params: Vec = Vec::new(); - for param in extrinsic.params { + for param in &extrinsic.params { params.push(prompt_for_param(&mut cli, ¶m)?); } assert_eq!(params.len(), 3); @@ -1025,7 +1016,7 @@ mod tests { cli.verify()?; // Using System remark extrinsic to test the sequence params - let extrinsic = find_extrinsic_by_name(&pallets, "System", "remark").await?; + let extrinsic = find_extrinsic_by_name(&pallets, "System", "remark")?; // Temporal file for testing the input. let temp_dir = tempdir()?; let file = temp_dir.path().join("file.json"); @@ -1039,7 +1030,7 @@ mod tests { // Test all the extrinsic params let mut params: Vec = Vec::new(); - for param in extrinsic.params { + for param in &extrinsic.params { params.push(prompt_for_param(&mut cli, ¶m)?); } assert_eq!(params.len(), 1); diff --git a/crates/pop-parachains/src/call/metadata/action.rs b/crates/pop-parachains/src/call/metadata/action.rs index 608c3c227..c4e0a97f7 100644 --- a/crates/pop-parachains/src/call/metadata/action.rs +++ b/crates/pop-parachains/src/call/metadata/action.rs @@ -106,13 +106,10 @@ impl Action { /// /// # Arguments /// * `pallets`: Supported pallets. -pub async fn supported_actions(pallets: &[Pallet]) -> Vec { +pub fn supported_actions(pallets: &[Pallet]) -> Vec { let mut actions = Vec::new(); for action in Action::VARIANTS.iter() { - if find_extrinsic_by_name(pallets, action.pallet_name(), action.extrinsic_name()) - .await - .is_ok() - { + if find_extrinsic_by_name(pallets, action.pallet_name(), action.extrinsic_name()).is_ok() { actions.push(action.clone()); } } @@ -122,11 +119,10 @@ pub async fn supported_actions(pallets: &[Pallet]) -> Vec { #[cfg(test)] mod tests { use super::*; - use crate::{parse_chain_metadata, set_up_client}; + use crate::{call::tests::POP_NETWORK_TESTNET_URL, parse_chain_metadata, set_up_client}; use anyhow::Result; use std::collections::HashMap; - const POP_NETWORK_TESTNET_URL: &str = "wss://rpc1.paseo.popnetwork.xyz"; const POLKADOT_NETWORK_URL: &str = "wss://polkadot-rpc.publicnode.com"; #[test] @@ -188,7 +184,7 @@ mod tests { // Test Pop Parachain. let mut client: subxt::OnlineClient = set_up_client(POP_NETWORK_TESTNET_URL).await?; - let mut actions = supported_actions(&parse_chain_metadata(&client).await?).await; + let mut actions = supported_actions(&parse_chain_metadata(&client)?); assert_eq!(actions.len(), 5); assert_eq!(actions[0], Action::Transfer); assert_eq!(actions[1], Action::CreateAsset); @@ -198,7 +194,7 @@ mod tests { // Test Polkadot Relay Chain. client = set_up_client(POLKADOT_NETWORK_URL).await?; - actions = supported_actions(&parse_chain_metadata(&client).await?).await; + actions = supported_actions(&parse_chain_metadata(&client)?); assert_eq!(actions.len(), 4); assert_eq!(actions[0], Action::Transfer); assert_eq!(actions[1], Action::PurchaseOnDemandCoretime); diff --git a/crates/pop-parachains/src/call/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs index 26e8d9276..a5cfde676 100644 --- a/crates/pop-parachains/src/call/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -14,6 +14,8 @@ pub mod params; pub struct Pallet { /// The name of the pallet. pub name: String, + /// The index of the pallet within the runtime. + pub index: u8, /// The documentation of the pallet. pub docs: String, /// The extrinsics of the pallet. @@ -31,6 +33,8 @@ impl Display for Pallet { pub struct Extrinsic { /// The name of the extrinsic. pub name: String, + /// The index of the extrinsic within the pallet. + pub index: u8, /// The documentation of the extrinsic. pub docs: String, /// The parameters of the extrinsic. @@ -50,9 +54,9 @@ impl Display for Extrinsic { /// /// # Arguments /// * `client`: The client to interact with the chain. -pub async fn parse_chain_metadata( - client: &OnlineClient, -) -> Result, Error> { +/// +/// NOTE: pallets are ordered by their index within the runtime by default. +pub fn parse_chain_metadata(client: &OnlineClient) -> Result, Error> { let metadata: Metadata = client.metadata(); let pallets = metadata @@ -86,8 +90,16 @@ pub async fn parse_chain_metadata( Ok(Extrinsic { name: variant.name.clone(), + index: variant.index, docs: if is_supported { - variant.docs.concat() + // Filter out blank lines and then flatten into a single value. + variant + .docs + .iter() + .filter(|l| !l.is_empty()) + .cloned() + .collect::>() + .join(" ") } else { // To display the message in the UI "Extrinsic Not Supported".to_string() @@ -102,6 +114,7 @@ pub async fn parse_chain_metadata( Ok(Pallet { name: pallet.name().to_string(), + index: pallet.index(), docs: pallet.docs().join(" "), extrinsics, }) @@ -114,11 +127,14 @@ pub async fn parse_chain_metadata( /// Finds a specific pallet by name and retrieves its details from metadata. /// /// # Arguments -/// * `pallets`: List of pallets availables in the chain. +/// * `pallets`: List of pallets available in the chain. /// * `pallet_name`: The name of the pallet to find. -pub async fn find_pallet_by_name(pallets: &[Pallet], pallet_name: &str) -> Result { +pub fn find_pallet_by_name<'a>( + pallets: &'a [Pallet], + pallet_name: &str, +) -> Result<&'a Pallet, Error> { if let Some(pallet) = pallets.iter().find(|p| p.name == pallet_name) { - Ok(pallet.clone()) + Ok(pallet) } else { Err(Error::PalletNotFound(pallet_name.to_string())) } @@ -127,17 +143,17 @@ pub async fn find_pallet_by_name(pallets: &[Pallet], pallet_name: &str) -> Resul /// Finds a specific extrinsic by name and retrieves its details from metadata. /// /// # Arguments -/// * `pallets`: List of pallets availables in the chain. +/// * `pallets`: List of pallets available in the chain. /// * `pallet_name`: The name of the pallet to find. /// * `extrinsic_name`: Name of the extrinsic to locate. -pub async fn find_extrinsic_by_name( - pallets: &[Pallet], +pub fn find_extrinsic_by_name<'a>( + pallets: &'a [Pallet], pallet_name: &str, extrinsic_name: &str, -) -> Result { - let pallet = find_pallet_by_name(pallets, pallet_name).await?; +) -> Result<&'a Extrinsic, Error> { + let pallet = find_pallet_by_name(pallets, pallet_name)?; if let Some(extrinsic) = pallet.extrinsics.iter().find(|&e| e.name == extrinsic_name) { - Ok(extrinsic.clone()) + Ok(extrinsic) } else { Err(Error::ExtrinsicNotSupported) } @@ -148,7 +164,7 @@ pub async fn find_extrinsic_by_name( /// # Arguments /// * `params`: The metadata definition for each parameter of the extrinsic. /// * `raw_params`: A vector of raw string arguments for the extrinsic. -pub async fn parse_extrinsic_arguments( +pub fn parse_extrinsic_arguments( params: &[Param], raw_params: Vec, ) -> Result, Error> { @@ -176,26 +192,26 @@ pub async fn parse_extrinsic_arguments( mod tests { use super::*; - use crate::set_up_client; + use crate::{call::tests::POP_NETWORK_TESTNET_URL, set_up_client}; use anyhow::Result; use subxt::ext::scale_bits; - const POP_NETWORK_TESTNET_URL: &str = "wss://rpc1.paseo.popnetwork.xyz"; - #[tokio::test] async fn parse_chain_metadata_works() -> Result<()> { let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; - let pallets = parse_chain_metadata(&client).await?; + let pallets = parse_chain_metadata(&client)?; // Test the first pallet is parsed correctly let first_pallet = pallets.first().unwrap(); assert_eq!(first_pallet.name, "System"); + assert_eq!(first_pallet.index, 0); assert_eq!(first_pallet.docs, ""); assert_eq!(first_pallet.extrinsics.len(), 11); let first_extrinsic = first_pallet.extrinsics.first().unwrap(); assert_eq!(first_extrinsic.name, "remark"); + assert_eq!(first_extrinsic.index, 0); assert_eq!( first_extrinsic.docs, - "Make some on-chain remark.Can be executed by every `origin`." + "Make some on-chain remark. Can be executed by every `origin`." ); assert!(first_extrinsic.is_supported); assert_eq!(first_extrinsic.params.first().unwrap().name, "remark"); @@ -211,11 +227,11 @@ mod tests { #[tokio::test] async fn find_pallet_by_name_works() -> Result<()> { let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; - let pallets = parse_chain_metadata(&client).await?; + let pallets = parse_chain_metadata(&client)?; assert!(matches!( - find_pallet_by_name(&pallets, "WrongName").await, + find_pallet_by_name(&pallets, "WrongName"), Err(Error::PalletNotFound(pallet)) if pallet == "WrongName".to_string())); - let pallet = find_pallet_by_name(&pallets, "Balances").await?; + let pallet = find_pallet_by_name(&pallets, "Balances")?; assert_eq!(pallet.name, "Balances"); assert_eq!(pallet.extrinsics.len(), 9); Ok(()) @@ -224,24 +240,24 @@ mod tests { #[tokio::test] async fn find_extrinsic_by_name_works() -> Result<()> { let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; - let pallets = parse_chain_metadata(&client).await?; + let pallets = parse_chain_metadata(&client)?; assert!(matches!( - find_extrinsic_by_name(&pallets, "WrongName", "wrong_extrinsic").await, + find_extrinsic_by_name(&pallets, "WrongName", "wrong_extrinsic"), Err(Error::PalletNotFound(pallet)) if pallet == "WrongName".to_string())); assert!(matches!( - find_extrinsic_by_name(&pallets, "Balances", "wrong_extrinsic").await, + find_extrinsic_by_name(&pallets, "Balances", "wrong_extrinsic"), Err(Error::ExtrinsicNotSupported) )); - let extrinsic = find_extrinsic_by_name(&pallets, "Balances", "force_transfer").await?; + let extrinsic = find_extrinsic_by_name(&pallets, "Balances", "force_transfer")?; assert_eq!(extrinsic.name, "force_transfer"); - assert_eq!(extrinsic.docs, "Exactly as `transfer_allow_death`, except the origin must be root and the source accountmay be specified."); + assert_eq!(extrinsic.docs, "Exactly as `transfer_allow_death`, except the origin must be root and the source account may be specified."); assert_eq!(extrinsic.is_supported, true); assert_eq!(extrinsic.params.len(), 3); Ok(()) } - #[tokio::test] - async fn parse_extrinsic_arguments_works() -> Result<()> { + #[test] + fn parse_extrinsic_arguments_works() -> Result<()> { // Values for testing from: https://docs.rs/scale-value/0.18.0/scale_value/stringify/fn.from_str.html // and https://docs.rs/scale-value/0.18.0/scale_value/stringify/fn.from_str_custom.html let args = [ @@ -281,7 +297,7 @@ mod tests { Param { type_name: "composite".to_string(), ..Default::default() }, ]; assert_eq!( - parse_extrinsic_arguments(¶ms, args).await?, + parse_extrinsic_arguments(¶ms, args)?, [ Value::u128(1), Value::i128(-1), diff --git a/crates/pop-parachains/src/call/metadata/params.rs b/crates/pop-parachains/src/call/metadata/params.rs index 232e4d6b1..23b699217 100644 --- a/crates/pop-parachains/src/call/metadata/params.rs +++ b/crates/pop-parachains/src/call/metadata/params.rs @@ -154,11 +154,9 @@ fn type_to_param(name: String, registry: &PortableRegistry, type_id: u32) -> Res #[cfg(test)] mod tests { use super::*; - use crate::set_up_client; + use crate::{call::tests::POP_NETWORK_TESTNET_URL, set_up_client}; use anyhow::Result; - const POP_NETWORK_TESTNET_URL: &str = "wss://rpc1.paseo.popnetwork.xyz"; - #[tokio::test] async fn field_to_param_works() -> Result<()> { let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs index 967e8b744..b88e33786 100644 --- a/crates/pop-parachains/src/call/mod.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -26,13 +26,12 @@ pub async fn set_up_client(url: &str) -> Result, E /// * `pallet_name` - The name of the pallet containing the extrinsic. /// * `extrinsic_name` - The specific extrinsic name within the pallet. /// * `args` - A vector of string arguments to be passed to the extrinsic. -pub async fn construct_extrinsic( +pub fn construct_extrinsic( pallet_name: &str, extrinsic: &Extrinsic, args: Vec, ) -> Result { - let parsed_args: Vec = - metadata::parse_extrinsic_arguments(&extrinsic.params, args).await?; + let parsed_args: Vec = metadata::parse_extrinsic_arguments(&extrinsic.params, args)?; Ok(subxt::dynamic::tx(pallet_name, extrinsic.name.clone(), parsed_args)) } @@ -41,7 +40,7 @@ pub async fn construct_extrinsic( /// # Arguments /// * `tx`: The transaction payload representing the function call to be dispatched with `Root` /// privileges. -pub async fn construct_sudo_extrinsic(tx: DynamicPayload) -> Result { +pub fn construct_sudo_extrinsic(tx: DynamicPayload) -> Result { Ok(subxt::dynamic::tx("Sudo", "sudo", [tx.into_value()].to_vec())) } @@ -138,7 +137,7 @@ mod tests { use anyhow::Result; const ALICE_SURI: &str = "//Alice"; - const POP_NETWORK_TESTNET_URL: &str = "wss://rpc1.paseo.popnetwork.xyz"; + pub(crate) const POP_NETWORK_TESTNET_URL: &str = "wss://rpc1.paseo.popnetwork.xyz"; #[tokio::test] async fn set_up_client_works() -> Result<()> { @@ -153,9 +152,9 @@ mod tests { #[tokio::test] async fn construct_extrinsic_works() -> Result<()> { let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; - let pallets = parse_chain_metadata(&client).await?; + let pallets = parse_chain_metadata(&client)?; let transfer_allow_death = - find_extrinsic_by_name(&pallets, "Balances", "transfer_allow_death").await?; + find_extrinsic_by_name(&pallets, "Balances", "transfer_allow_death")?; // Wrong parameters assert!(matches!( @@ -163,8 +162,7 @@ mod tests { "Balances", &transfer_allow_death, vec![ALICE_SURI.to_string(), "100".to_string()], - ) - .await, + ), Err(Error::ParamProcessingError) )); // Valid parameters @@ -175,8 +173,7 @@ mod tests { "Id(5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty)".to_string(), "100".to_string(), ], - ) - .await?; + )?; assert_eq!(extrinsic.call_name(), "transfer_allow_death"); assert_eq!(extrinsic.pallet_name(), "Balances"); Ok(()) @@ -185,13 +182,13 @@ mod tests { #[tokio::test] async fn encode_call_data_works() -> Result<()> { let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; - let pallets = parse_chain_metadata(&client).await?; - let remark = find_extrinsic_by_name(&pallets, "System", "remark").await?; - let extrinsic = construct_extrinsic("System", &remark, vec!["0x11".to_string()]).await?; + let pallets = parse_chain_metadata(&client)?; + let remark = find_extrinsic_by_name(&pallets, "System", "remark")?; + let extrinsic = construct_extrinsic("System", &remark, vec!["0x11".to_string()])?; assert_eq!(encode_call_data(&client, &extrinsic)?, "0x00000411"); - let extrinsic = construct_extrinsic("System", &remark, vec!["123".to_string()]).await?; + let extrinsic = construct_extrinsic("System", &remark, vec!["123".to_string()])?; assert_eq!(encode_call_data(&client, &extrinsic)?, "0x00000c313233"); - let extrinsic = construct_extrinsic("System", &remark, vec!["test".to_string()]).await?; + let extrinsic = construct_extrinsic("System", &remark, vec!["test".to_string()])?; assert_eq!(encode_call_data(&client, &extrinsic)?, "0x00001074657374"); Ok(()) } @@ -199,10 +196,10 @@ mod tests { #[tokio::test] async fn decode_call_data_works() -> Result<()> { assert!(matches!(decode_call_data("wrongcalldata"), Err(Error::CallDataDecodingError(..)))); - let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; - let pallets = parse_chain_metadata(&client).await?; - let remark = find_extrinsic_by_name(&pallets, "System", "remark").await?; - let extrinsic = construct_extrinsic("System", &remark, vec!["0x11".to_string()]).await?; + let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; + let pallets = parse_chain_metadata(&client)?; + let remark = find_extrinsic_by_name(&pallets, "System", "remark")?; + let extrinsic = construct_extrinsic("System", &remark, vec!["0x11".to_string()])?; let expected_call_data = extrinsic.encode_call_data(&client.metadata())?; assert_eq!(decode_call_data("0x00000411")?, expected_call_data); Ok(()) @@ -213,11 +210,12 @@ mod tests { let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; let extrinsic = Extrinsic { name: "wrong_extrinsic".to_string(), + index: 0, docs: "documentation".to_string(), is_supported: true, ..Default::default() }; - let tx = construct_extrinsic("WrongPallet", &extrinsic, vec!["0x11".to_string()]).await?; + let tx = construct_extrinsic("WrongPallet", &extrinsic, vec!["0x11".to_string()])?; assert!(matches!( sign_and_submit_extrinsic(client, tx, ALICE_SURI).await, Err(Error::ExtrinsicSubmissionError(message)) if message.contains("PalletNameNotFound(\"WrongPallet\"))") @@ -227,9 +225,9 @@ mod tests { #[tokio::test] async fn construct_sudo_extrinsic_works() -> Result<()> { - let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; - let pallets = parse_chain_metadata(&client).await?; - let force_transfer = find_extrinsic_by_name(&pallets, "Balances", "force_transfer").await?; + let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; + let pallets = parse_chain_metadata(&client)?; + let force_transfer = find_extrinsic_by_name(&pallets, "Balances", "force_transfer")?; let extrinsic = construct_extrinsic( "Balances", &force_transfer, @@ -238,9 +236,8 @@ mod tests { "Id(5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy)".to_string(), "100".to_string(), ], - ) - .await?; - let sudo_extrinsic = construct_sudo_extrinsic(extrinsic).await?; + )?; + let sudo_extrinsic = construct_sudo_extrinsic(extrinsic)?; assert_eq!(sudo_extrinsic.call_name(), "sudo"); assert_eq!(sudo_extrinsic.pallet_name(), "Sudo"); Ok(()) From b19ece8b6ac877740bc512a0fa2e8a0d7e492b83 Mon Sep 17 00:00:00 2001 From: Frank Bell <60948618+evilrobot-01@users.noreply.github.com> Date: Mon, 9 Dec 2024 22:03:47 +0000 Subject: [PATCH 189/211] refactor: terminology (#368) * refactor: terminology * refactor: simply pallet/function relationship * fix: amend call_data conflicts after refactor --- crates/pop-cli/src/commands/call/parachain.rs | 216 +++++++++--------- crates/pop-cli/tests/parachain.rs | 4 +- .../src/call/metadata/action.rs | 13 +- .../pop-parachains/src/call/metadata/mod.rs | 123 +++++----- .../src/call/metadata/params.rs | 34 +-- crates/pop-parachains/src/call/mod.rs | 78 +++---- crates/pop-parachains/src/errors.rs | 6 +- crates/pop-parachains/src/lib.rs | 4 +- 8 files changed, 237 insertions(+), 241 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 9641cb24b..132f25048 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -7,9 +7,9 @@ use anyhow::{anyhow, Result}; use clap::Args; use pop_parachains::{ construct_extrinsic, construct_sudo_extrinsic, decode_call_data, encode_call_data, - find_extrinsic_by_name, find_pallet_by_name, parse_chain_metadata, set_up_client, + find_dispatchable_by_name, find_pallet_by_name, parse_chain_metadata, set_up_client, sign_and_submit_extrinsic, sign_and_submit_extrinsic_with_call_data, supported_actions, Action, - DynamicPayload, Extrinsic, OnlineClient, Pallet, Param, SubstrateConfig, + DynamicPayload, Function, OnlineClient, Pallet, Param, SubstrateConfig, }; use url::Url; @@ -17,16 +17,17 @@ const DEFAULT_URL: &str = "ws://localhost:9944/"; const DEFAULT_URI: &str = "//Alice"; const ENCODED_CALL_DATA_MAX_LEN: usize = 500; // Maximum length of encoded call data to display. -/// Command to execute extrinsics with configurable pallets, arguments, and signing options. +/// Command to construct and execute extrinsics with configurable pallets, functions, arguments, and +/// signing options. #[derive(Args, Clone, Default)] pub struct CallParachainCommand { - /// The pallet containing the extrinsic to execute. + /// The pallet containing the dispatchable function to execute. #[arg(short, long, value_parser = parse_pallet_name)] pallet: Option, - /// The extrinsic to execute within the chosen pallet. - #[arg(short, long, value_parser = parse_extrinsic_name)] - extrinsic: Option, - /// The extrinsic arguments, encoded as strings. + /// The dispatchable function to execute within the specified pallet. + #[arg(short, long, value_parser = parse_function_name)] + function: Option, + /// The dispatchable function arguments, encoded as strings. #[arg(short, long, num_args = 0..,)] args: Vec, /// Websocket endpoint of a node. @@ -40,7 +41,7 @@ pub struct CallParachainCommand { #[arg(short, long)] suri: Option, /// SCALE encoded bytes representing the call data of the extrinsic. - #[arg(name = "call", long, conflicts_with_all = ["pallet", "extrinsic", "args"])] + #[arg(name = "call", long, conflicts_with_all = ["pallet", "function", "args"])] call_data: Option, /// Authenticates the sudo key and dispatches a function call with `Root` origin. #[arg(short = 'S', long)] @@ -80,7 +81,7 @@ impl CallParachainCommand { // Display the configured call. cli.info(call.display(&chain))?; // Prepare the extrinsic. - let tx = match call.prepare_extrinsic(&chain.client, &mut cli) { + let xt = match call.prepare_extrinsic(&chain.client, &mut cli) { Ok(payload) => payload, Err(e) => { display_message(&e.to_string(), false, &mut cli)?; @@ -89,7 +90,7 @@ impl CallParachainCommand { }; // Sign and submit the extrinsic. - if let Err(e) = call.submit_extrinsic(&chain.client, tx, &mut cli).await { + if let Err(e) = call.submit_extrinsic(&chain.client, xt, &mut cli).await { display_message(&e.to_string(), false, &mut cli)?; break; } @@ -130,9 +131,7 @@ impl CallParachainCommand { })?; // Sort by name for display. pallets.sort_by(|a, b| a.name.cmp(&b.name)); - pallets - .iter_mut() - .for_each(|p| p.extrinsics.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 }) } @@ -145,7 +144,7 @@ impl CallParachainCommand { None => { // Specific predefined actions first. if let Some(action) = prompt_predefined_actions(&chain.pallets, cli)? { - self.extrinsic = Some(action.extrinsic_name().to_string()); + self.function = Some(action.function_name().to_string()); find_pallet_by_name(&chain.pallets, action.pallet_name())? } else { let mut prompt = cli.select("Select the pallet to call:"); @@ -157,32 +156,30 @@ impl CallParachainCommand { }, }; - // Resolve extrinsic. - let extrinsic = match self.extrinsic { - Some(ref extrinsic_name) => - find_extrinsic_by_name(&chain.pallets, &pallet.name, extrinsic_name)?, + // Resolve dispatchable function. + let function = match self.function { + Some(ref name) => find_dispatchable_by_name(&chain.pallets, &pallet.name, name)?, None => { - let mut prompt_extrinsic = cli.select("Select the extrinsic to call:"); - for extrinsic in &pallet.extrinsics { - prompt_extrinsic = - prompt_extrinsic.item(extrinsic, &extrinsic.name, &extrinsic.docs); + let mut prompt = cli.select("Select the function to call:"); + for function in &pallet.functions { + prompt = prompt.item(function, &function.name, &function.docs); } - prompt_extrinsic.interact()? + prompt.interact()? }, }; - // Certain extrinsics are not supported yet due to complexity. - if !extrinsic.is_supported { + // Certain dispatchable functions are not supported yet due to complexity. + if !function.is_supported { cli.outro_cancel( - "The selected extrinsic is not supported yet. Please choose another one.", + "The selected function is not supported yet. Please choose another one.", )?; self.reset_for_new_call(); continue; } - // Resolve message arguments. + // Resolve dispatchable function arguments. let args = if self.args.is_empty() { let mut args = Vec::new(); - for param in &extrinsic.params { + for param in &function.params { let input = prompt_for_param(cli, param)?; args.push(input); } @@ -203,8 +200,7 @@ impl CallParachainCommand { }; return Ok(CallParachain { - pallet: pallet.clone(), - extrinsic: extrinsic.clone(), + function: function.clone(), args, suri, skip_confirm: self.skip_confirm, @@ -213,7 +209,7 @@ impl CallParachainCommand { } } - // Submits an extrinsic to the chain using the provided call data. + // Submits an extrinsic to the chain using the provided encoded call data. async fn submit_extrinsic_from_call_data( &self, client: &OnlineClient, @@ -252,10 +248,10 @@ impl CallParachainCommand { Ok(()) } - // Checks if the chain has the Sudo pallet and prompt the user to confirm if they want to - // execute the call via sudo. + // Checks if the chain has the Sudo pallet and prompts the user to confirm if they want to + // execute the call via `sudo`. fn configure_sudo(&mut self, chain: &Chain, cli: &mut impl Cli) -> Result<()> { - match find_extrinsic_by_name(&chain.pallets, "Sudo", "sudo") { + match find_dispatchable_by_name(&chain.pallets, "Sudo", "sudo") { Ok(_) => if !self.sudo { self.sudo = cli @@ -279,7 +275,7 @@ impl CallParachainCommand { // Resets specific fields to default values for a new call. fn reset_for_new_call(&mut self) { self.pallet = None; - self.extrinsic = None; + self.function = None; self.args.clear(); self.sudo = false; } @@ -287,7 +283,7 @@ impl CallParachainCommand { // Function to check if all required fields are specified. fn requires_user_input(&self) -> bool { self.pallet.is_none() || - self.extrinsic.is_none() || + self.function.is_none() || self.args.is_empty() || self.url.is_none() || self.suri.is_none() @@ -319,15 +315,13 @@ struct Chain { pallets: Vec, } -/// Represents a configured extrinsic call, including the pallet, extrinsic, arguments, and signing -/// options. +/// Represents a configured dispatchable function call, including the pallet, function, arguments, +/// and signing options. #[derive(Clone)] struct CallParachain { - /// The pallet of the extrinsic. - pallet: Pallet, - /// The extrinsic to execute. - extrinsic: Extrinsic, - /// The extrinsic arguments, encoded as strings. + /// The dispatchable function to execute. + function: Function, + /// The dispatchable function arguments, encoded as strings. args: Vec, /// Secret key URI for the account signing the extrinsic. /// @@ -342,30 +336,26 @@ struct CallParachain { } impl CallParachain { - // Prepares the extrinsic or query. + // Prepares the extrinsic. fn prepare_extrinsic( &self, client: &OnlineClient, cli: &mut impl Cli, ) -> Result { - let tx = match construct_extrinsic( - self.pallet.name.as_str(), - &self.extrinsic, - self.args.clone(), - ) { + let xt = match construct_extrinsic(&self.function, self.args.clone()) { Ok(tx) => tx, Err(e) => { return Err(anyhow!("Error: {}", e)); }, }; // If sudo is required, wrap the call in a sudo call. - let tx = if self.sudo { construct_sudo_extrinsic(tx)? } else { tx }; - let encoded_data = encode_call_data(client, &tx)?; + let xt = if self.sudo { construct_sudo_extrinsic(xt)? } else { xt }; + let encoded_data = encode_call_data(client, &xt)?; // If the encoded call data is too long, don't display it all. if encoded_data.len() < ENCODED_CALL_DATA_MAX_LEN { - cli.info(format!("Encoded call data: {}", encode_call_data(client, &tx)?))?; + cli.info(format!("Encoded call data: {}", encode_call_data(client, &xt)?))?; } - Ok(tx) + Ok(xt) } // Sign and submit an extrinsic. @@ -381,7 +371,7 @@ impl CallParachain { .interact()? { display_message( - &format!("Extrinsic {} was not submitted.", self.extrinsic.name), + &format!("Extrinsic for `{}` was not submitted.", self.function.name), false, cli, )?; @@ -398,8 +388,8 @@ impl CallParachain { } fn display(&self, chain: &Chain) -> String { let mut full_message = "pop call parachain".to_string(); - full_message.push_str(&format!(" --pallet {}", self.pallet)); - full_message.push_str(&format!(" --extrinsic {}", self.extrinsic)); + full_message.push_str(&format!(" --pallet {}", self.function.pallet)); + full_message.push_str(&format!(" --function {}", self.function)); if !self.args.is_empty() { let args: Vec<_> = self .args @@ -443,7 +433,7 @@ fn prompt_predefined_actions(pallets: &[Pallet], cli: &mut impl Cli) -> Result Result { } } -// Parser to convert the extrinsic name to lowercase. -fn parse_extrinsic_name(name: &str) -> Result { +// Parser to convert the function name to lowercase. +fn parse_function_name(name: &str) -> Result { Ok(name.to_ascii_lowercase()) } @@ -623,7 +613,7 @@ mod tests { .expect_intro("Call a parachain") .expect_input("Which chain would you like to interact with?", POP_NETWORK_TESTNET_URL.into()) .expect_select( - "Select the extrinsic to call:", + "Select the function to call:", Some(true), true, Some( @@ -642,7 +632,7 @@ mod tests { ] .to_vec(), ), - 5, // "remark" extrinsic + 5, // "remark" dispatchable function ) .expect_input("The value for `remark` might be too large to enter. You may enter the path to a file instead.", "0x11".into()) .expect_confirm("Would you like to dispatch this function call with `Root` origin?", true) @@ -652,12 +642,12 @@ mod tests { assert_eq!(chain.url, Url::parse(POP_NETWORK_TESTNET_URL)?); let call_parachain = call_config.configure_call(&chain, &mut cli)?; - assert_eq!(call_parachain.pallet.name, "System"); - assert_eq!(call_parachain.extrinsic.name, "remark"); + assert_eq!(call_parachain.function.pallet, "System"); + assert_eq!(call_parachain.function.name, "remark"); assert_eq!(call_parachain.args, ["0x11".to_string()].to_vec()); assert_eq!(call_parachain.suri, "//Bob"); assert!(call_parachain.sudo); - assert_eq!(call_parachain.display(&chain), "pop call parachain --pallet System --extrinsic remark --args \"0x11\" --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Bob --sudo"); + assert_eq!(call_parachain.display(&chain), "pop call parachain --pallet System --function remark --args \"0x11\" --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Bob --sudo"); cli.verify() } @@ -686,7 +676,7 @@ mod tests { }) .chain(std::iter::once(( "All".to_string(), - "Explore all pallets and extrinsics".to_string(), + "Explore all pallets and functions".to_string(), ))) .collect::>(), ), @@ -698,12 +688,12 @@ mod tests { let call_parachain = call_config.configure_call(&chain, &mut cli)?; - assert_eq!(call_parachain.pallet.name, "OnDemand"); - assert_eq!(call_parachain.extrinsic.name, "place_order_allow_death"); + assert_eq!(call_parachain.function.pallet, "OnDemand"); + assert_eq!(call_parachain.function.name, "place_order_allow_death"); assert_eq!(call_parachain.args, ["10000".to_string(), "2000".to_string()].to_vec()); assert_eq!(call_parachain.suri, "//Bob"); assert!(!call_parachain.sudo); - assert_eq!(call_parachain.display(&chain), "pop call parachain --pallet OnDemand --extrinsic place_order_allow_death --args \"10000\" \"2000\" --url wss://polkadot-rpc.publicnode.com/ --suri //Bob"); + assert_eq!(call_parachain.display(&chain), "pop call parachain --pallet OnDemand --function place_order_allow_death --args \"10000\" \"2000\" --url wss://polkadot-rpc.publicnode.com/ --suri //Bob"); cli.verify() } @@ -711,8 +701,11 @@ mod tests { async fn prepare_extrinsic_works() -> Result<()> { let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; let mut call_config = CallParachain { - pallet: Pallet { name: "WrongName".to_string(), ..Default::default() }, - extrinsic: Extrinsic { name: "WrongName".to_string(), ..Default::default() }, + function: Function { + pallet: "WrongName".to_string(), + name: "WrongName".to_string(), + ..Default::default() + }, args: vec!["0x11".to_string()].to_vec(), suri: DEFAULT_URI.to_string(), skip_confirm: false, @@ -720,21 +713,23 @@ mod tests { }; let mut cli = MockCli::new(); // Error, wrong name of the pallet. - assert!( - matches!(call_config.prepare_extrinsic(&client, &mut cli), Err(message) if message.to_string().contains("Failed to encode call data. Metadata Error: Pallet with name WrongName not found")) - ); + assert!(matches!( + call_config.prepare_extrinsic(&client, &mut cli), + Err(message) + if message.to_string().contains("Failed to encode call data. Metadata Error: Pallet with name WrongName not found"))); let pallets = parse_chain_metadata(&client)?; - call_config.pallet = find_pallet_by_name(&pallets, "System")?.clone(); - // Error, wrong name of the extrinsic. - assert!( - matches!(call_config.prepare_extrinsic(&client, &mut cli), Err(message) if message.to_string().contains("Failed to encode call data. Metadata Error: Call with name WrongName not found")) - ); - // Success, extrinsic and pallet specified. + call_config.function.pallet = "System".to_string(); + // Error, wrong name of the function. + assert!(matches!( + call_config.prepare_extrinsic(&client, &mut cli), + Err(message) + if message.to_string().contains("Failed to encode call data. Metadata Error: Call with name WrongName not found"))); + // Success, pallet and dispatchable function specified. cli = MockCli::new().expect_info("Encoded call data: 0x00000411"); - call_config.extrinsic = find_extrinsic_by_name(&pallets, "System", "remark")?.clone(); - let tx = call_config.prepare_extrinsic(&client, &mut cli)?; - assert_eq!(tx.call_name(), "remark"); - assert_eq!(tx.pallet_name(), "System"); + call_config.function = find_dispatchable_by_name(&pallets, "System", "remark")?.clone(); + let xt = call_config.prepare_extrinsic(&client, &mut cli)?; + assert_eq!(xt.call_name(), "remark"); + assert_eq!(xt.pallet_name(), "System"); // Prepare extrinsic wrapped in sudo works. cli = MockCli::new().expect_info("Encoded call data: 0x0f0000000411"); @@ -749,8 +744,7 @@ mod tests { let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; let pallets = parse_chain_metadata(&client)?; let mut call_config = CallParachain { - pallet: find_pallet_by_name(&pallets, "System")?.clone(), - extrinsic: find_extrinsic_by_name(&pallets, "System", "remark")?.clone(), + function: find_dispatchable_by_name(&pallets, "System", "remark")?.clone(), args: vec!["0x11".to_string()].to_vec(), suri: DEFAULT_URI.to_string(), skip_confirm: false, @@ -758,9 +752,9 @@ mod tests { }; let mut cli = MockCli::new() .expect_confirm("Do you want to submit the extrinsic?", false) - .expect_outro_cancel("Extrinsic remark was not submitted."); - let tx = call_config.prepare_extrinsic(&client, &mut cli)?; - call_config.submit_extrinsic(&client, tx, &mut cli).await?; + .expect_outro_cancel("Extrinsic for `remark` was not submitted."); + let xt = call_config.prepare_extrinsic(&client, &mut cli)?; + call_config.submit_extrinsic(&client, xt, &mut cli).await?; cli.verify() } @@ -770,7 +764,7 @@ mod tests { let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; let call_config = CallParachainCommand { pallet: None, - extrinsic: None, + function: None, args: vec![].to_vec(), url: Some(Url::parse("wss://rpc1.paseo.popnetwork.xyz")?), suri: None, @@ -794,7 +788,7 @@ mod tests { // Test when sudo pallet doesn't exist. let mut call_config = CallParachainCommand { pallet: None, - extrinsic: None, + function: None, args: vec![].to_vec(), url: Some(Url::parse("wss://polkadot-rpc.publicnode.com")?), suri: Some("//Alice".to_string()), @@ -826,7 +820,7 @@ mod tests { fn reset_for_new_call_works() -> Result<()> { let mut call_config = CallParachainCommand { pallet: Some("System".to_string()), - extrinsic: Some("remark".to_string()), + function: Some("remark".to_string()), args: vec!["0x11".to_string()].to_vec(), url: Some(Url::parse(POP_NETWORK_TESTNET_URL)?), suri: Some(DEFAULT_URI.to_string()), @@ -836,7 +830,7 @@ mod tests { }; call_config.reset_for_new_call(); assert_eq!(call_config.pallet, None); - assert_eq!(call_config.extrinsic, None); + assert_eq!(call_config.function, None); assert_eq!(call_config.args.len(), 0); assert!(!call_config.sudo); Ok(()) @@ -846,7 +840,7 @@ mod tests { fn requires_user_input_works() -> Result<()> { let mut call_config = CallParachainCommand { pallet: Some("System".to_string()), - extrinsic: Some("remark".to_string()), + function: Some("remark".to_string()), args: vec!["0x11".to_string()].to_vec(), url: Some(Url::parse(POP_NETWORK_TESTNET_URL)?), suri: Some(DEFAULT_URI.to_string()), @@ -864,7 +858,7 @@ mod tests { fn expand_file_arguments_works() -> Result<()> { let mut call_config = CallParachainCommand { pallet: Some("Registrar".to_string()), - extrinsic: Some("register".to_string()), + function: Some("register".to_string()), args: vec!["2000".to_string(), "0x1".to_string(), "0x12".to_string()].to_vec(), url: Some(Url::parse(POP_NETWORK_TESTNET_URL)?), suri: Some(DEFAULT_URI.to_string()), @@ -924,7 +918,7 @@ mod tests { }) .chain(std::iter::once(( "All".to_string(), - "Explore all pallets and extrinsics".to_string(), + "Explore all pallets and functions".to_string(), ))) .collect::>(), ), @@ -939,8 +933,8 @@ mod tests { async fn prompt_for_param_works() -> Result<()> { let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; let pallets = parse_chain_metadata(&client)?; - // Using NFT mint extrinsic to test the majority of subfunctions - let extrinsic = find_extrinsic_by_name(&pallets, "Nfts", "mint")?; + // Using NFT mint dispatchable function to test the majority of sub-functions. + let function = find_dispatchable_by_name(&pallets, "Nfts", "mint")?; let mut cli = MockCli::new() .expect_input("Enter the value for the parameter: collection", "0".into()) .expect_input("Enter the value for the parameter: item", "0".into()) @@ -978,9 +972,9 @@ mod tests { ) .expect_input("Enter the value for the parameter: mint_price", "1000".into()); - // Test all the extrinsic params + // Test all the function params. let mut params: Vec = Vec::new(); - for param in &extrinsic.params { + for param in &function.params { params.push(prompt_for_param(&mut cli, ¶m)?); } assert_eq!(params.len(), 4); @@ -990,8 +984,8 @@ mod tests { assert_eq!(params[3], "Some({owned_item: None(), mint_price: Some(1000)})".to_string()); // witness_data: test composite cli.verify()?; - // Using Scheduler set_retry extrinsic to test the tuple params - let extrinsic = find_extrinsic_by_name(&pallets, "Scheduler", "set_retry")?; + // Using Scheduler set_retry dispatchable function to test the tuple params. + let function = find_dispatchable_by_name(&pallets, "Scheduler", "set_retry")?; let mut cli = MockCli::new() .expect_input( "Enter the value for the parameter: Index 0 of the tuple task", @@ -1006,7 +1000,7 @@ mod tests { // Test all the extrinsic params let mut params: Vec = Vec::new(); - for param in &extrinsic.params { + for param in &function.params { params.push(prompt_for_param(&mut cli, ¶m)?); } assert_eq!(params.len(), 3); @@ -1015,8 +1009,8 @@ mod tests { assert_eq!(params[2], "0".to_string()); // period: test primitive cli.verify()?; - // Using System remark extrinsic to test the sequence params - let extrinsic = find_extrinsic_by_name(&pallets, "System", "remark")?; + // Using System remark dispatchable function to test the sequence params. + let function = find_dispatchable_by_name(&pallets, "System", "remark")?; // Temporal file for testing the input. let temp_dir = tempdir()?; let file = temp_dir.path().join("file.json"); @@ -1028,9 +1022,9 @@ mod tests { file.display().to_string(), ); - // Test all the extrinsic params + // Test all the function params let mut params: Vec = Vec::new(); - for param in &extrinsic.params { + for param in &function.params { params.push(prompt_for_param(&mut cli, ¶m)?); } assert_eq!(params.len(), 1); @@ -1047,10 +1041,10 @@ mod tests { } #[test] - fn parse_extrinsic_name_works() -> Result<()> { - assert_eq!(parse_extrinsic_name("Remark").unwrap(), "remark"); - assert_eq!(parse_extrinsic_name("Force_transfer").unwrap(), "force_transfer"); - assert_eq!(parse_extrinsic_name("MINT").unwrap(), "mint"); + fn parse_function_name_works() -> Result<()> { + assert_eq!(parse_function_name("Remark").unwrap(), "remark"); + assert_eq!(parse_function_name("Force_transfer").unwrap(), "force_transfer"); + assert_eq!(parse_function_name("MINT").unwrap(), "mint"); Ok(()) } } diff --git a/crates/pop-cli/tests/parachain.rs b/crates/pop-cli/tests/parachain.rs index 5fabadce7..0c5623ac9 100644 --- a/crates/pop-cli/tests/parachain.rs +++ b/crates/pop-cli/tests/parachain.rs @@ -133,7 +133,7 @@ name = "collator-01" // Wait for the networks to initialize. Increased timeout to accommodate CI environment delays. sleep(Duration::from_secs(50)).await; - // `pop call parachain --pallet System --extrinsic remark --args "0x11" --url + // `pop call parachain --pallet System --function remark --args "0x11" --url // ws://127.0.0.1:random_port --suri //Alice --skip-confirm` Command::cargo_bin("pop") .unwrap() @@ -142,7 +142,7 @@ name = "collator-01" "parachain", "--pallet", "System", - "--extrinsic", + "--function", "remark", "--args", "0x11", diff --git a/crates/pop-parachains/src/call/metadata/action.rs b/crates/pop-parachains/src/call/metadata/action.rs index c4e0a97f7..cd47e1279 100644 --- a/crates/pop-parachains/src/call/metadata/action.rs +++ b/crates/pop-parachains/src/call/metadata/action.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -use super::{find_extrinsic_by_name, Pallet}; +use super::{find_dispatchable_by_name, Pallet}; use strum::{EnumMessage as _, EnumProperty as _, VariantArray as _}; use strum_macros::{AsRefStr, Display, EnumMessage, EnumProperty, EnumString, VariantArray}; @@ -91,8 +91,8 @@ impl Action { self.get_detailed_message().unwrap_or_default() } - /// Get the extrinsic name corresponding to the action. - pub fn extrinsic_name(&self) -> &str { + /// Get the dispatchable function name corresponding to the action. + pub fn function_name(&self) -> &str { self.get_message().unwrap_or_default() } @@ -109,7 +109,8 @@ impl Action { pub fn supported_actions(pallets: &[Pallet]) -> Vec { let mut actions = Vec::new(); for action in Action::VARIANTS.iter() { - if find_extrinsic_by_name(pallets, action.pallet_name(), action.extrinsic_name()).is_ok() { + if find_dispatchable_by_name(pallets, action.pallet_name(), action.function_name()).is_ok() + { actions.push(action.clone()); } } @@ -162,7 +163,7 @@ mod tests { } #[test] - fn extrinsic_names_are_correct() { + fn function_names_are_correct() { let pallets = HashMap::from([ (Action::CreateAsset, "create"), (Action::MintAsset, "mint"), @@ -175,7 +176,7 @@ mod tests { ]); for action in Action::VARIANTS.iter() { - assert_eq!(&action.extrinsic_name(), pallets.get(action).unwrap(),); + assert_eq!(&action.function_name(), pallets.get(action).unwrap(),); } } diff --git a/crates/pop-parachains/src/call/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs index a5cfde676..e9a696150 100644 --- a/crates/pop-parachains/src/call/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -9,7 +9,7 @@ use subxt::{dynamic::Value, Metadata, OnlineClient, SubstrateConfig}; pub mod action; pub mod params; -/// Represents a pallet in the blockchain, including its extrinsics. +/// Represents a pallet in the blockchain, including its dispatchable functions. #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct Pallet { /// The name of the pallet. @@ -18,8 +18,8 @@ pub struct Pallet { pub index: u8, /// The documentation of the pallet. pub docs: String, - /// The extrinsics of the pallet. - pub extrinsics: Vec, + /// The dispatchable functions of the pallet. + pub functions: Vec, } impl Display for Pallet { @@ -28,29 +28,30 @@ impl Display for Pallet { } } -/// Represents an extrinsic. +/// Represents a dispatchable function. #[derive(Clone, Debug, Default, Eq, PartialEq)] -pub struct Extrinsic { - /// The name of the extrinsic. +pub struct Function { + /// The pallet containing the dispatchable function. + pub pallet: String, + /// The name of the function. pub name: String, - /// The index of the extrinsic within the pallet. + /// The index of the function within the pallet. pub index: u8, - /// The documentation of the extrinsic. + /// The documentation of the function. pub docs: String, - /// The parameters of the extrinsic. + /// The parameters of the function. pub params: Vec, - /// Whether this extrinsic is supported (no recursive or unsupported types like `RuntimeCall`). + /// Whether this function is supported (no recursive or unsupported types like `RuntimeCall`). pub is_supported: bool, } -impl Display for Extrinsic { +impl Display for Function { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.name) } } -/// Parses the chain metadata to extract information about pallets and their extrinsics with its -/// parameters. +/// Parses the chain metadata to extract information about pallets and their dispatchable functions. /// /// # Arguments /// * `client`: The client to interact with the chain. @@ -62,7 +63,7 @@ pub fn parse_chain_metadata(client: &OnlineClient) -> Result) -> Result) -> Result parsed_params.push(param), Err(_) => { // If an error occurs while parsing the values, mark the - // extrinsic as unsupported rather than error. + // dispatchable function as unsupported rather than + // error. is_supported = false; parsed_params.clear(); break; @@ -88,7 +90,8 @@ pub fn parse_chain_metadata(client: &OnlineClient) -> Result) -> Result, Error>>() + .collect::, Error>>() }) .unwrap_or_else(|| Ok(vec![]))?; @@ -116,7 +119,7 @@ pub fn parse_chain_metadata(client: &OnlineClient) -> Result, Error>>()?; @@ -140,31 +143,33 @@ pub fn find_pallet_by_name<'a>( } } -/// Finds a specific extrinsic by name and retrieves its details from metadata. +/// Finds a specific dispatchable function by name and retrieves its details from metadata. /// /// # Arguments /// * `pallets`: List of pallets available in the chain. -/// * `pallet_name`: The name of the pallet to find. -/// * `extrinsic_name`: Name of the extrinsic to locate. -pub fn find_extrinsic_by_name<'a>( +/// * `pallet_name`: The name of the pallet. +/// * `function_name`: Name of the dispatchable function to locate. +pub fn find_dispatchable_by_name<'a>( pallets: &'a [Pallet], pallet_name: &str, - extrinsic_name: &str, -) -> Result<&'a Extrinsic, Error> { + function_name: &str, +) -> Result<&'a Function, Error> { let pallet = find_pallet_by_name(pallets, pallet_name)?; - if let Some(extrinsic) = pallet.extrinsics.iter().find(|&e| e.name == extrinsic_name) { - Ok(extrinsic) + if let Some(function) = pallet.functions.iter().find(|&e| e.name == function_name) { + Ok(function) } else { - Err(Error::ExtrinsicNotSupported) + Err(Error::FunctionNotSupported) } } -/// Parses and processes raw string parameters for an extrinsic, mapping them to `Value` types. +/// Parses and processes raw string parameter values for a dispatchable function, mapping them to +/// `Value` types. /// /// # Arguments -/// * `params`: The metadata definition for each parameter of the extrinsic. -/// * `raw_params`: A vector of raw string arguments for the extrinsic. -pub fn parse_extrinsic_arguments( +/// * `params`: The metadata definition for each parameter of the corresponding dispatchable +/// function. +/// * `raw_params`: A vector of raw string arguments for the dispatchable function. +pub fn parse_dispatchable_arguments( params: &[Param], raw_params: Vec, ) -> Result, Error> { @@ -205,22 +210,22 @@ mod tests { assert_eq!(first_pallet.name, "System"); assert_eq!(first_pallet.index, 0); assert_eq!(first_pallet.docs, ""); - assert_eq!(first_pallet.extrinsics.len(), 11); - let first_extrinsic = first_pallet.extrinsics.first().unwrap(); - assert_eq!(first_extrinsic.name, "remark"); - assert_eq!(first_extrinsic.index, 0); + assert_eq!(first_pallet.functions.len(), 11); + let first_function = first_pallet.functions.first().unwrap(); + assert_eq!(first_function.name, "remark"); + assert_eq!(first_function.index, 0); assert_eq!( - first_extrinsic.docs, + first_function.docs, "Make some on-chain remark. Can be executed by every `origin`." ); - assert!(first_extrinsic.is_supported); - assert_eq!(first_extrinsic.params.first().unwrap().name, "remark"); - assert_eq!(first_extrinsic.params.first().unwrap().type_name, "[u8]"); - assert_eq!(first_extrinsic.params.first().unwrap().sub_params.len(), 0); - assert!(!first_extrinsic.params.first().unwrap().is_optional); - assert!(!first_extrinsic.params.first().unwrap().is_tuple); - assert!(!first_extrinsic.params.first().unwrap().is_variant); - assert!(first_extrinsic.params.first().unwrap().is_sequence); + assert!(first_function.is_supported); + assert_eq!(first_function.params.first().unwrap().name, "remark"); + assert_eq!(first_function.params.first().unwrap().type_name, "[u8]"); + assert_eq!(first_function.params.first().unwrap().sub_params.len(), 0); + assert!(!first_function.params.first().unwrap().is_optional); + assert!(!first_function.params.first().unwrap().is_tuple); + assert!(!first_function.params.first().unwrap().is_variant); + assert!(first_function.params.first().unwrap().is_sequence); Ok(()) } @@ -233,31 +238,31 @@ mod tests { Err(Error::PalletNotFound(pallet)) if pallet == "WrongName".to_string())); let pallet = find_pallet_by_name(&pallets, "Balances")?; assert_eq!(pallet.name, "Balances"); - assert_eq!(pallet.extrinsics.len(), 9); + assert_eq!(pallet.functions.len(), 9); Ok(()) } #[tokio::test] - async fn find_extrinsic_by_name_works() -> Result<()> { + async fn find_dispatchable_by_name_works() -> Result<()> { let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; let pallets = parse_chain_metadata(&client)?; assert!(matches!( - find_extrinsic_by_name(&pallets, "WrongName", "wrong_extrinsic"), + find_dispatchable_by_name(&pallets, "WrongName", "wrong_name"), Err(Error::PalletNotFound(pallet)) if pallet == "WrongName".to_string())); assert!(matches!( - find_extrinsic_by_name(&pallets, "Balances", "wrong_extrinsic"), - Err(Error::ExtrinsicNotSupported) + find_dispatchable_by_name(&pallets, "Balances", "wrong_name"), + Err(Error::FunctionNotSupported) )); - let extrinsic = find_extrinsic_by_name(&pallets, "Balances", "force_transfer")?; - assert_eq!(extrinsic.name, "force_transfer"); - assert_eq!(extrinsic.docs, "Exactly as `transfer_allow_death`, except the origin must be root and the source account may be specified."); - assert_eq!(extrinsic.is_supported, true); - assert_eq!(extrinsic.params.len(), 3); + let function = find_dispatchable_by_name(&pallets, "Balances", "force_transfer")?; + assert_eq!(function.name, "force_transfer"); + assert_eq!(function.docs, "Exactly as `transfer_allow_death`, except the origin must be root and the source account may be specified."); + assert_eq!(function.is_supported, true); + assert_eq!(function.params.len(), 3); Ok(()) } #[test] - fn parse_extrinsic_arguments_works() -> Result<()> { + fn parse_dispatchable_arguments_works() -> Result<()> { // Values for testing from: https://docs.rs/scale-value/0.18.0/scale_value/stringify/fn.from_str.html // and https://docs.rs/scale-value/0.18.0/scale_value/stringify/fn.from_str_custom.html let args = [ @@ -283,7 +288,7 @@ mod tests { .into_iter() .map(|b| Value::u128(b as u128)) .collect(); - // Define mock extrinsic parameters for testing. + // Define mock dispatchable function parameters for testing. let params = vec![ Param { type_name: "u128".to_string(), ..Default::default() }, Param { type_name: "i128".to_string(), ..Default::default() }, @@ -297,7 +302,7 @@ mod tests { Param { type_name: "composite".to_string(), ..Default::default() }, ]; assert_eq!( - parse_extrinsic_arguments(¶ms, args)?, + parse_dispatchable_arguments(¶ms, args)?, [ Value::u128(1), Value::i128(-1), diff --git a/crates/pop-parachains/src/call/metadata/params.rs b/crates/pop-parachains/src/call/metadata/params.rs index 23b699217..d1293a32f 100644 --- a/crates/pop-parachains/src/call/metadata/params.rs +++ b/crates/pop-parachains/src/call/metadata/params.rs @@ -5,7 +5,7 @@ use pop_common::format_type; use scale_info::{form::PortableForm, Field, PortableRegistry, TypeDef}; use subxt::{Metadata, OnlineClient, SubstrateConfig}; -/// Describes a parameter of an extrinsic. +/// Describes a parameter of a dispatchable function. #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct Param { /// The name of the parameter. @@ -28,7 +28,7 @@ pub struct Param { /// /// # Arguments /// * `client`: The client to interact with the chain. -/// * `field`: A parameter of an extrinsic as struct field. +/// * `field`: A parameter of a dispatchable function (as [Field]). pub fn field_to_param( client: &OnlineClient, field: &Field, @@ -37,7 +37,7 @@ pub fn field_to_param( let registry = metadata.types(); if let Some(name) = field.type_name.as_deref() { if name.contains("RuntimeCall") { - return Err(Error::ExtrinsicNotSupported); + return Err(Error::FunctionNotSupported); } } let name = field.name.clone().unwrap_or("Unnamed".to_string()); //It can be unnamed field @@ -54,7 +54,7 @@ fn type_to_param(name: String, registry: &PortableRegistry, type_id: u32) -> Res let type_info = registry.resolve(type_id).ok_or(Error::MetadataParsingError(name.clone()))?; for param in &type_info.type_params { if param.name.contains("RuntimeCall") { - return Err(Error::ExtrinsicNotSupported); + return Err(Error::FunctionNotSupported); } } if type_info.path.segments == ["Option"] { @@ -161,14 +161,14 @@ mod tests { async fn field_to_param_works() -> Result<()> { let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; let metadata = client.metadata(); - // Test a supported extrinsic - let extrinsic = metadata + // Test a supported dispatchable function. + let function = metadata .pallet_by_name("Balances") .unwrap() .call_variant_by_name("force_transfer") .unwrap(); let mut params = Vec::new(); - for field in &extrinsic.fields { + for field in &function.fields { params.push(field_to_param(&client, field)?) } assert_eq!(params.len(), 3); @@ -203,30 +203,30 @@ mod tests { .type_name, "AccountId32 ([u8;32])" ); - // Test some extrinsics that are not supported. - let extrinsic = + // Test some dispatchable functions that are not supported. + let function = metadata.pallet_by_name("Sudo").unwrap().call_variant_by_name("sudo").unwrap(); assert!(matches!( - field_to_param(&client, &extrinsic.fields.first().unwrap()), - Err(Error::ExtrinsicNotSupported) + field_to_param(&client, &function.fields.first().unwrap()), + Err(Error::FunctionNotSupported) )); - let extrinsic = metadata + let function = metadata .pallet_by_name("Utility") .unwrap() .call_variant_by_name("batch") .unwrap(); assert!(matches!( - field_to_param(&client, &extrinsic.fields.first().unwrap()), - Err(Error::ExtrinsicNotSupported) + field_to_param(&client, &function.fields.first().unwrap()), + Err(Error::FunctionNotSupported) )); - let extrinsic = metadata + let function = metadata .pallet_by_name("PolkadotXcm") .unwrap() .call_variant_by_name("execute") .unwrap(); assert!(matches!( - field_to_param(&client, &extrinsic.fields.first().unwrap()), - Err(Error::ExtrinsicNotSupported) + field_to_param(&client, &function.fields.first().unwrap()), + Err(Error::FunctionNotSupported) )); Ok(()) diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs index b88e33786..4d9164dbe 100644 --- a/crates/pop-parachains/src/call/mod.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -use crate::{errors::Error, Extrinsic}; +use crate::{errors::Error, Function}; use pop_common::create_signer; use subxt::{ dynamic::Value, @@ -20,45 +20,43 @@ pub async fn set_up_client(url: &str) -> Result, E .map_err(|e| Error::ConnectionFailure(e.to_string())) } -/// Constructs a dynamic extrinsic payload for a specified pallet and extrinsic. +/// Constructs a dynamic extrinsic payload for a specified dispatchable function. /// /// # Arguments -/// * `pallet_name` - The name of the pallet containing the extrinsic. -/// * `extrinsic_name` - The specific extrinsic name within the pallet. -/// * `args` - A vector of string arguments to be passed to the extrinsic. +/// * `function` - A dispatchable function. +/// * `args` - A vector of string arguments to be passed to construct the extrinsic. pub fn construct_extrinsic( - pallet_name: &str, - extrinsic: &Extrinsic, + function: &Function, args: Vec, ) -> Result { - let parsed_args: Vec = metadata::parse_extrinsic_arguments(&extrinsic.params, args)?; - Ok(subxt::dynamic::tx(pallet_name, extrinsic.name.clone(), parsed_args)) + let parsed_args: Vec = metadata::parse_dispatchable_arguments(&function.params, args)?; + Ok(subxt::dynamic::tx(function.pallet.clone(), function.name.clone(), parsed_args)) } /// Constructs a Sudo extrinsic. /// /// # Arguments -/// * `tx`: The transaction payload representing the function call to be dispatched with `Root` +/// * `xt`: The extrinsic representing the dispatchable function call to be dispatched with `Root` /// privileges. -pub fn construct_sudo_extrinsic(tx: DynamicPayload) -> Result { - Ok(subxt::dynamic::tx("Sudo", "sudo", [tx.into_value()].to_vec())) +pub fn construct_sudo_extrinsic(xt: DynamicPayload) -> Result { + Ok(subxt::dynamic::tx("Sudo", "sudo", [xt.into_value()].to_vec())) } /// Signs and submits a given extrinsic. /// /// # Arguments /// * `client` - The client used to interact with the chain. -/// * `tx` - The transaction to be signed and submitted. +/// * `xt` - The extrinsic to be signed and submitted. /// * `suri` - The secret URI (e.g., mnemonic or private key) for signing the extrinsic. pub async fn sign_and_submit_extrinsic( client: OnlineClient, - tx: DynamicPayload, + xt: DynamicPayload, suri: &str, ) -> Result { let signer = create_signer(suri)?; let result = client .tx() - .sign_and_submit_then_watch_default(&tx, &signer) + .sign_and_submit_then_watch_default(&xt, &signer) .await .map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))? .wait_for_finalized_success() @@ -133,7 +131,7 @@ pub async fn sign_and_submit_extrinsic_with_call_data( #[cfg(test)] mod tests { use super::*; - use crate::{find_extrinsic_by_name, parse_chain_metadata, set_up_client}; + use crate::{find_dispatchable_by_name, parse_chain_metadata, set_up_client}; use anyhow::Result; const ALICE_SURI: &str = "//Alice"; @@ -154,28 +152,26 @@ mod tests { let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; let pallets = parse_chain_metadata(&client)?; let transfer_allow_death = - find_extrinsic_by_name(&pallets, "Balances", "transfer_allow_death")?; + find_dispatchable_by_name(&pallets, "Balances", "transfer_allow_death")?; // Wrong parameters assert!(matches!( construct_extrinsic( - "Balances", &transfer_allow_death, vec![ALICE_SURI.to_string(), "100".to_string()], ), Err(Error::ParamProcessingError) )); // Valid parameters - let extrinsic = construct_extrinsic( - "Balances", + let xt = construct_extrinsic( &transfer_allow_death, vec![ "Id(5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty)".to_string(), "100".to_string(), ], )?; - assert_eq!(extrinsic.call_name(), "transfer_allow_death"); - assert_eq!(extrinsic.pallet_name(), "Balances"); + assert_eq!(xt.call_name(), "transfer_allow_death"); + assert_eq!(xt.pallet_name(), "Balances"); Ok(()) } @@ -183,13 +179,13 @@ mod tests { async fn encode_call_data_works() -> Result<()> { let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; let pallets = parse_chain_metadata(&client)?; - let remark = find_extrinsic_by_name(&pallets, "System", "remark")?; - let extrinsic = construct_extrinsic("System", &remark, vec!["0x11".to_string()])?; - assert_eq!(encode_call_data(&client, &extrinsic)?, "0x00000411"); - let extrinsic = construct_extrinsic("System", &remark, vec!["123".to_string()])?; - assert_eq!(encode_call_data(&client, &extrinsic)?, "0x00000c313233"); - let extrinsic = construct_extrinsic("System", &remark, vec!["test".to_string()])?; - assert_eq!(encode_call_data(&client, &extrinsic)?, "0x00001074657374"); + let remark = find_dispatchable_by_name(&pallets, "System", "remark")?; + let xt = construct_extrinsic(&remark, vec!["0x11".to_string()])?; + assert_eq!(encode_call_data(&client, &xt)?, "0x00000411"); + let xt = construct_extrinsic(&remark, vec!["123".to_string()])?; + assert_eq!(encode_call_data(&client, &xt)?, "0x00000c313233"); + let xt = construct_extrinsic(&remark, vec!["test".to_string()])?; + assert_eq!(encode_call_data(&client, &xt)?, "0x00001074657374"); Ok(()) } @@ -198,9 +194,9 @@ mod tests { assert!(matches!(decode_call_data("wrongcalldata"), Err(Error::CallDataDecodingError(..)))); let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; let pallets = parse_chain_metadata(&client)?; - let remark = find_extrinsic_by_name(&pallets, "System", "remark")?; - let extrinsic = construct_extrinsic("System", &remark, vec!["0x11".to_string()])?; - let expected_call_data = extrinsic.encode_call_data(&client.metadata())?; + let remark = find_dispatchable_by_name(&pallets, "System", "remark")?; + let xt = construct_extrinsic(&remark, vec!["0x11".to_string()])?; + let expected_call_data = xt.encode_call_data(&client.metadata())?; assert_eq!(decode_call_data("0x00000411")?, expected_call_data); Ok(()) } @@ -208,16 +204,17 @@ mod tests { #[tokio::test] async fn sign_and_submit_wrong_extrinsic_fails() -> Result<()> { let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; - let extrinsic = Extrinsic { + let function = Function { + pallet: "WrongPallet".to_string(), name: "wrong_extrinsic".to_string(), index: 0, docs: "documentation".to_string(), is_supported: true, ..Default::default() }; - let tx = construct_extrinsic("WrongPallet", &extrinsic, vec!["0x11".to_string()])?; + let xt = construct_extrinsic(&function, vec!["0x11".to_string()])?; assert!(matches!( - sign_and_submit_extrinsic(client, tx, ALICE_SURI).await, + sign_and_submit_extrinsic(client, xt, ALICE_SURI).await, Err(Error::ExtrinsicSubmissionError(message)) if message.contains("PalletNameNotFound(\"WrongPallet\"))") )); Ok(()) @@ -227,9 +224,8 @@ mod tests { async fn construct_sudo_extrinsic_works() -> Result<()> { let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; let pallets = parse_chain_metadata(&client)?; - let force_transfer = find_extrinsic_by_name(&pallets, "Balances", "force_transfer")?; - let extrinsic = construct_extrinsic( - "Balances", + let force_transfer = find_dispatchable_by_name(&pallets, "Balances", "force_transfer")?; + let xt = construct_extrinsic( &force_transfer, vec![ "Id(5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty)".to_string(), @@ -237,9 +233,9 @@ mod tests { "100".to_string(), ], )?; - let sudo_extrinsic = construct_sudo_extrinsic(extrinsic)?; - assert_eq!(sudo_extrinsic.call_name(), "sudo"); - assert_eq!(sudo_extrinsic.pallet_name(), "Sudo"); + let xt = construct_sudo_extrinsic(xt)?; + assert_eq!(xt.call_name(), "sudo"); + assert_eq!(xt.pallet_name(), "Sudo"); Ok(()) } } diff --git a/crates/pop-parachains/src/errors.rs b/crates/pop-parachains/src/errors.rs index ac9fe0f97..8bd97434a 100644 --- a/crates/pop-parachains/src/errors.rs +++ b/crates/pop-parachains/src/errors.rs @@ -27,12 +27,12 @@ pub enum Error { CurrentDirAccess, #[error("Failed to parse the endowment value")] EndowmentError, - /// The extrinsic is not supported. - #[error("The extrinsic is not supported")] - ExtrinsicNotSupported, /// An error occurred during the submission of an extrinsic. #[error("Extrinsic submission error: {0}")] ExtrinsicSubmissionError(String), + /// The dispatchable function is not supported. + #[error("The dispatchable function is not supported")] + FunctionNotSupported, #[error("IO error: {0}")] IO(#[from] std::io::Error), #[error("JSON error: {0}")] diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index b2bbfa649..31a86ade7 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -20,9 +20,9 @@ pub use call::{ construct_extrinsic, construct_sudo_extrinsic, decode_call_data, encode_call_data, metadata::{ action::{supported_actions, Action}, - find_extrinsic_by_name, find_pallet_by_name, + find_dispatchable_by_name, find_pallet_by_name, params::Param, - parse_chain_metadata, Extrinsic, Pallet, + parse_chain_metadata, Function, Pallet, }, set_up_client, sign_and_submit_extrinsic, sign_and_submit_extrinsic_with_call_data, }; From b3810b25bf589085e6ae5b7892a81ed91473cc84 Mon Sep 17 00:00:00 2001 From: Frank Bell <60948618+evilrobot-01@users.noreply.github.com> Date: Tue, 10 Dec 2024 09:43:20 +0000 Subject: [PATCH 190/211] refactor: improvements (#370) * fix: add missing short arg option * refactor: note that extrinsic wait includes finalization * refactor: remove clones * style: formatting * refactor: make file prompt more generic * refactor: add missing license headers * style: formatting * docs: comments * docs: comments * docs: comments * refactor: reuse existing metadata * refactor: minimise clones * docs: comments * refactor: naming * docs: fix parameter doc comments * refactor: address clippy warnings --- crates/pop-cli/src/commands/call/parachain.rs | 18 ++--- crates/pop-common/src/build.rs | 2 + crates/pop-common/src/lib.rs | 29 ++++---- crates/pop-common/src/signer.rs | 2 +- .../pop-parachains/src/call/metadata/mod.rs | 6 +- .../src/call/metadata/params.rs | 68 ++++++++++--------- crates/pop-parachains/src/call/mod.rs | 14 ++-- 7 files changed, 73 insertions(+), 66 deletions(-) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/parachain.rs index 132f25048..dedefa24e 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/parachain.rs @@ -41,7 +41,7 @@ pub struct CallParachainCommand { #[arg(short, long)] suri: Option, /// SCALE encoded bytes representing the call data of the extrinsic. - #[arg(name = "call", long, conflicts_with_all = ["pallet", "function", "args"])] + #[arg(name = "call", short, long, conflicts_with_all = ["pallet", "function", "args"])] call_data: Option, /// Authenticates the sudo key and dispatches a function call with `Root` origin. #[arg(short = 'S', long)] @@ -235,13 +235,12 @@ impl CallParachainCommand { return Ok(()); } let spinner = cliclack::spinner(); - spinner.start("Signing and submitting the extrinsic, please wait..."); + spinner.start("Signing and submitting the extrinsic and then waiting for finalization, please be patient..."); let call_data_bytes = decode_call_data(call_data).map_err(|err| anyhow!("{}", format!("{err:?}")))?; - let result = - sign_and_submit_extrinsic_with_call_data(client.clone(), call_data_bytes, suri) - .await - .map_err(|err| anyhow!("{}", format!("{err:?}")))?; + let result = sign_and_submit_extrinsic_with_call_data(client, call_data_bytes, suri) + .await + .map_err(|err| anyhow!("{}", format!("{err:?}")))?; spinner.stop(format!("Extrinsic submitted successfully with hash: {:?}", result)); display_message("Call complete.", true, cli)?; @@ -378,14 +377,15 @@ impl CallParachain { return Ok(()); } let spinner = cliclack::spinner(); - spinner.start("Signing and submitting the extrinsic, please wait..."); - let result = sign_and_submit_extrinsic(client.clone(), tx, &self.suri) + spinner.start("Signing and submitting the extrinsic and then waiting for finalization, please be patient..."); + let result = sign_and_submit_extrinsic(client, tx, &self.suri) .await .map_err(|err| anyhow!("{}", format!("{err:?}")))?; spinner.stop(format!("Extrinsic submitted with hash: {:?}", result)); Ok(()) } + fn display(&self, chain: &Chain) -> String { let mut full_message = "pop call parachain".to_string(); full_message.push_str(&format!(" --pallet {}", self.function.pallet)); @@ -479,7 +479,7 @@ fn prompt_for_sequence_param(cli: &mut impl Cli, param: &Param) -> Result) -> Result parsed_params.push(param), Err(_) => { // If an error occurs while parsing the values, mark the @@ -130,7 +130,7 @@ pub fn parse_chain_metadata(client: &OnlineClient) -> Result( pallets: &'a [Pallet], @@ -146,7 +146,7 @@ pub fn find_pallet_by_name<'a>( /// Finds a specific dispatchable function by name and retrieves its details from metadata. /// /// # Arguments -/// * `pallets`: List of pallets available in the chain. +/// * `pallets`: List of pallets available within the chain's runtime. /// * `pallet_name`: The name of the pallet. /// * `function_name`: Name of the dispatchable function to locate. pub fn find_dispatchable_by_name<'a>( diff --git a/crates/pop-parachains/src/call/metadata/params.rs b/crates/pop-parachains/src/call/metadata/params.rs index d1293a32f..9d8521f9c 100644 --- a/crates/pop-parachains/src/call/metadata/params.rs +++ b/crates/pop-parachains/src/call/metadata/params.rs @@ -3,7 +3,7 @@ use crate::errors::Error; use pop_common::format_type; use scale_info::{form::PortableForm, Field, PortableRegistry, TypeDef}; -use subxt::{Metadata, OnlineClient, SubstrateConfig}; +use subxt::Metadata; /// Describes a parameter of a dispatchable function. #[derive(Clone, Debug, Default, Eq, PartialEq)] @@ -12,7 +12,7 @@ pub struct Param { pub name: String, /// The type of the parameter. pub type_name: String, - /// Nested parameters for composite, variants types or tuples. + /// Nested parameters for composite, variants, types or tuples. pub sub_params: Vec, /// Indicates if the parameter is optional (`Option`). pub is_optional: bool, @@ -27,20 +27,16 @@ pub struct Param { /// Transforms a metadata field into its `Param` representation. /// /// # Arguments -/// * `client`: The client to interact with the chain. +/// * `metadata`: The chain metadata. /// * `field`: A parameter of a dispatchable function (as [Field]). -pub fn field_to_param( - client: &OnlineClient, - field: &Field, -) -> Result { - let metadata: Metadata = client.metadata(); +pub fn field_to_param(metadata: &Metadata, field: &Field) -> Result { let registry = metadata.types(); if let Some(name) = field.type_name.as_deref() { if name.contains("RuntimeCall") { return Err(Error::FunctionNotSupported); } } - let name = field.name.clone().unwrap_or("Unnamed".to_string()); //It can be unnamed field + let name = field.name.as_deref().unwrap_or("Unnamed"); //It can be unnamed field type_to_param(name, registry, field.ty.id) } @@ -50,8 +46,10 @@ pub fn field_to_param( /// * `name`: The name of the parameter. /// * `registry`: Type registry containing all types used in the metadata. /// * `type_id`: The ID of the type to be converted. -fn type_to_param(name: String, registry: &PortableRegistry, type_id: u32) -> Result { - let type_info = registry.resolve(type_id).ok_or(Error::MetadataParsingError(name.clone()))?; +fn type_to_param(name: &str, registry: &PortableRegistry, type_id: u32) -> Result { + let type_info = registry + .resolve(type_id) + .ok_or_else(|| Error::MetadataParsingError(name.to_string()))?; for param in &type_info.type_params { if param.name.contains("RuntimeCall") { return Err(Error::FunctionNotSupported); @@ -60,38 +58,34 @@ fn type_to_param(name: String, registry: &PortableRegistry, type_id: u32) -> Res if type_info.path.segments == ["Option"] { if let Some(sub_type_id) = type_info.type_params.first().and_then(|param| param.ty) { // Recursive for the sub parameters - let sub_param = type_to_param(name.clone(), registry, sub_type_id.id)?; + let sub_param = type_to_param(name, registry, sub_type_id.id)?; Ok(Param { - name, + name: name.to_string(), type_name: sub_param.type_name, sub_params: sub_param.sub_params, is_optional: true, ..Default::default() }) } else { - Err(Error::MetadataParsingError(name)) + Err(Error::MetadataParsingError(name.to_string())) } } else { // Determine the formatted type name. let type_name = format_type(type_info, registry); match &type_info.type_def { TypeDef::Primitive(_) | TypeDef::Array(_) | TypeDef::Compact(_) => - Ok(Param { name, type_name, ..Default::default() }), + Ok(Param { name: name.to_string(), type_name, ..Default::default() }), TypeDef::Composite(composite) => { let sub_params = composite .fields .iter() .map(|field| { // Recursive for the sub parameters of composite type. - type_to_param( - field.name.clone().unwrap_or(name.clone()), - registry, - field.ty.id, - ) + type_to_param(field.name.as_deref().unwrap_or(name), registry, field.ty.id) }) .collect::, Error>>()?; - Ok(Param { name, type_name, sub_params, ..Default::default() }) + Ok(Param { name: name.to_string(), type_name, sub_params, ..Default::default() }) }, TypeDef::Variant(variant) => { let variant_params = variant @@ -104,7 +98,7 @@ fn type_to_param(name: String, registry: &PortableRegistry, type_id: u32) -> Res .map(|field| { // Recursive for the sub parameters of variant type. type_to_param( - field.name.clone().unwrap_or(variant_param.name.clone()), + field.name.as_deref().unwrap_or(&variant_param.name), registry, field.ty.id, ) @@ -121,15 +115,19 @@ fn type_to_param(name: String, registry: &PortableRegistry, type_id: u32) -> Res .collect::, Error>>()?; Ok(Param { - name, + name: name.to_string(), type_name, sub_params: variant_params, is_variant: true, ..Default::default() }) }, - TypeDef::Sequence(_) => - Ok(Param { name, type_name, is_sequence: true, ..Default::default() }), + TypeDef::Sequence(_) => Ok(Param { + name: name.to_string(), + type_name, + is_sequence: true, + ..Default::default() + }), TypeDef::Tuple(tuple) => { let sub_params = tuple .fields @@ -137,16 +135,22 @@ fn type_to_param(name: String, registry: &PortableRegistry, type_id: u32) -> Res .enumerate() .map(|(index, field_id)| { type_to_param( - format!("Index {index} of the tuple {name}"), + &format!("Index {index} of the tuple {name}"), registry, field_id.id, ) }) .collect::, Error>>()?; - Ok(Param { name, type_name, sub_params, is_tuple: true, ..Default::default() }) + Ok(Param { + name: name.to_string(), + type_name, + sub_params, + is_tuple: true, + ..Default::default() + }) }, - _ => Err(Error::MetadataParsingError(name)), + _ => Err(Error::MetadataParsingError(name.to_string())), } } } @@ -169,7 +173,7 @@ mod tests { .unwrap(); let mut params = Vec::new(); for field in &function.fields { - params.push(field_to_param(&client, field)?) + params.push(field_to_param(&metadata, field)?) } assert_eq!(params.len(), 3); assert_eq!(params.first().unwrap().name, "source"); @@ -207,7 +211,7 @@ mod tests { let function = metadata.pallet_by_name("Sudo").unwrap().call_variant_by_name("sudo").unwrap(); assert!(matches!( - field_to_param(&client, &function.fields.first().unwrap()), + field_to_param(&metadata, &function.fields.first().unwrap()), Err(Error::FunctionNotSupported) )); let function = metadata @@ -216,7 +220,7 @@ mod tests { .call_variant_by_name("batch") .unwrap(); assert!(matches!( - field_to_param(&client, &function.fields.first().unwrap()), + field_to_param(&metadata, &function.fields.first().unwrap()), Err(Error::FunctionNotSupported) )); let function = metadata @@ -225,7 +229,7 @@ mod tests { .call_variant_by_name("execute") .unwrap(); assert!(matches!( - field_to_param(&client, &function.fields.first().unwrap()), + field_to_param(&metadata, &function.fields.first().unwrap()), Err(Error::FunctionNotSupported) )); diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs index 4d9164dbe..be9b4f69f 100644 --- a/crates/pop-parachains/src/call/mod.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -10,7 +10,7 @@ use subxt::{ pub mod metadata; -/// Sets up an OnlineClient instance for connecting to a blockchain. +/// Sets up an [OnlineClient] instance for connecting to a blockchain. /// /// # Arguments /// * `url` - Endpoint of the node. @@ -49,7 +49,7 @@ pub fn construct_sudo_extrinsic(xt: DynamicPayload) -> Result, + client: &OnlineClient, xt: DynamicPayload, suri: &str, ) -> Result { @@ -69,12 +69,12 @@ pub async fn sign_and_submit_extrinsic( /// /// # Arguments /// * `client` - The client used to interact with the chain. -/// * `tx` - The transaction whose call data will be encoded and returned. +/// * `xt` - The extrinsic whose call data will be encoded and returned. pub fn encode_call_data( client: &OnlineClient, - tx: &DynamicPayload, + xt: &DynamicPayload, ) -> Result { - let call_data = tx + let call_data = xt .encode_call_data(&client.metadata()) .map_err(|e| Error::CallDataEncodingError(e.to_string()))?; Ok(format!("0x{}", hex::encode(call_data))) @@ -111,7 +111,7 @@ impl Payload for CallData { /// * `call_data` - SCALE encoded bytes representing the extrinsic's call data. /// * `suri` - The secret URI (e.g., mnemonic or private key) for signing the extrinsic. pub async fn sign_and_submit_extrinsic_with_call_data( - client: OnlineClient, + client: &OnlineClient, call_data: Vec, suri: &str, ) -> Result { @@ -214,7 +214,7 @@ mod tests { }; let xt = construct_extrinsic(&function, vec!["0x11".to_string()])?; assert!(matches!( - sign_and_submit_extrinsic(client, xt, ALICE_SURI).await, + sign_and_submit_extrinsic(&client, xt, ALICE_SURI).await, Err(Error::ExtrinsicSubmissionError(message)) if message.contains("PalletNameNotFound(\"WrongPallet\"))") )); Ok(()) From f1e4aed0e597116895af3d8b044ae1603e5998ef Mon Sep 17 00:00:00 2001 From: Alex Bean Date: Tue, 10 Dec 2024 13:24:01 +0100 Subject: [PATCH 191/211] refactor: rename parachain with chain as the primary command and retain parachain as an alias (#373) * refactor: rename parachain with chain in visible messages * refactor: rename parachain with chain internal code * chore: solve fmt after rebase * refactor: small fix, use alias instead aliases * refactor: rename CallParachain struct into Call --- .../commands/call/{parachain.rs => chain.rs} | 76 +++++++++---------- crates/pop-cli/src/commands/call/mod.rs | 12 +-- crates/pop-cli/src/commands/mod.rs | 4 +- crates/pop-cli/tests/parachain.rs | 8 +- 4 files changed, 50 insertions(+), 50 deletions(-) rename crates/pop-cli/src/commands/call/{parachain.rs => chain.rs} (93%) diff --git a/crates/pop-cli/src/commands/call/parachain.rs b/crates/pop-cli/src/commands/call/chain.rs similarity index 93% rename from crates/pop-cli/src/commands/call/parachain.rs rename to crates/pop-cli/src/commands/call/chain.rs index dedefa24e..ce7050a29 100644 --- a/crates/pop-cli/src/commands/call/parachain.rs +++ b/crates/pop-cli/src/commands/call/chain.rs @@ -20,7 +20,7 @@ const ENCODED_CALL_DATA_MAX_LEN: usize = 500; // Maximum length of encoded call /// Command to construct and execute extrinsics with configurable pallets, functions, arguments, and /// signing options. #[derive(Args, Clone, Default)] -pub struct CallParachainCommand { +pub struct CallChainCommand { /// The pallet containing the dispatchable function to execute. #[arg(short, long, value_parser = parse_pallet_name)] pallet: Option, @@ -51,7 +51,7 @@ pub struct CallParachainCommand { skip_confirm: bool, } -impl CallParachainCommand { +impl CallChainCommand { /// Executes the command. pub(crate) async fn execute(mut self) -> Result<()> { let mut cli = cli::Cli; @@ -110,7 +110,7 @@ impl CallParachainCommand { // Configures the chain by resolving the URL and fetching its metadata. async fn configure_chain(&self, cli: &mut impl Cli) -> Result { - cli.intro("Call a parachain")?; + cli.intro("Call a chain")?; // Resolve url. let url = match &self.url { Some(url) => url.clone(), @@ -136,7 +136,7 @@ impl CallParachainCommand { } // Configure the call based on command line arguments/call UI. - fn configure_call(&mut self, chain: &Chain, cli: &mut impl Cli) -> Result { + fn configure_call(&mut self, chain: &Chain, cli: &mut impl Cli) -> Result { loop { // Resolve pallet. let pallet = match self.pallet { @@ -199,7 +199,7 @@ impl CallParachainCommand { cli.input("Signer of the extrinsic:").default_input(DEFAULT_URI).interact()?, }; - return Ok(CallParachain { + return Ok(Call { function: function.clone(), args, suri, @@ -317,7 +317,7 @@ struct Chain { /// Represents a configured dispatchable function call, including the pallet, function, arguments, /// and signing options. #[derive(Clone)] -struct CallParachain { +struct Call { /// The dispatchable function to execute. function: Function, /// The dispatchable function arguments, encoded as strings. @@ -334,7 +334,7 @@ struct CallParachain { sudo: bool, } -impl CallParachain { +impl Call { // Prepares the extrinsic. fn prepare_extrinsic( &self, @@ -387,7 +387,7 @@ impl CallParachain { } fn display(&self, chain: &Chain) -> String { - let mut full_message = "pop call parachain".to_string(); + let mut full_message = "pop call chain".to_string(); full_message.push_str(&format!(" --pallet {}", self.function.pallet)); full_message.push_str(&format!(" --function {}", self.function)); if !self.args.is_empty() { @@ -594,8 +594,8 @@ mod tests { #[tokio::test] async fn configure_chain_works() -> Result<()> { let call_config = - CallParachainCommand { suri: Some(DEFAULT_URI.to_string()), ..Default::default() }; - let mut cli = MockCli::new().expect_intro("Call a parachain").expect_input( + CallChainCommand { suri: Some(DEFAULT_URI.to_string()), ..Default::default() }; + let mut cli = MockCli::new().expect_intro("Call a chain").expect_input( "Which chain would you like to interact with?", POP_NETWORK_TESTNET_URL.into(), ); @@ -605,12 +605,12 @@ mod tests { } #[tokio::test] - async fn guide_user_to_call_parachain_works() -> Result<()> { + async fn guide_user_to_call_chain_works() -> Result<()> { let mut call_config = - CallParachainCommand { pallet: Some("System".to_string()), ..Default::default() }; + CallChainCommand { pallet: Some("System".to_string()), ..Default::default() }; let mut cli = MockCli::new() - .expect_intro("Call a parachain") + .expect_intro("Call a chain") .expect_input("Which chain would you like to interact with?", POP_NETWORK_TESTNET_URL.into()) .expect_select( "Select the function to call:", @@ -641,21 +641,21 @@ mod tests { let chain = call_config.configure_chain(&mut cli).await?; assert_eq!(chain.url, Url::parse(POP_NETWORK_TESTNET_URL)?); - let call_parachain = call_config.configure_call(&chain, &mut cli)?; - assert_eq!(call_parachain.function.pallet, "System"); - assert_eq!(call_parachain.function.name, "remark"); - assert_eq!(call_parachain.args, ["0x11".to_string()].to_vec()); - assert_eq!(call_parachain.suri, "//Bob"); - assert!(call_parachain.sudo); - assert_eq!(call_parachain.display(&chain), "pop call parachain --pallet System --function remark --args \"0x11\" --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Bob --sudo"); + let call_chain = call_config.configure_call(&chain, &mut cli)?; + assert_eq!(call_chain.function.pallet, "System"); + assert_eq!(call_chain.function.name, "remark"); + assert_eq!(call_chain.args, ["0x11".to_string()].to_vec()); + assert_eq!(call_chain.suri, "//Bob"); + assert!(call_chain.sudo); + assert_eq!(call_chain.display(&chain), "pop call chain --pallet System --function remark --args \"0x11\" --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Bob --sudo"); cli.verify() } #[tokio::test] async fn guide_user_to_configure_predefined_action_works() -> Result<()> { - let mut call_config = CallParachainCommand::default(); + let mut call_config = CallChainCommand::default(); - let mut cli = MockCli::new().expect_intro("Call a parachain").expect_input( + let mut cli = MockCli::new().expect_intro("Call a chain").expect_input( "Which chain would you like to interact with?", POLKADOT_NETWORK_URL.into(), ); @@ -686,21 +686,21 @@ mod tests { .expect_input("Enter the value for the parameter: para_id", "2000".into()) .expect_input("Signer of the extrinsic:", BOB_SURI.into()); - let call_parachain = call_config.configure_call(&chain, &mut cli)?; + let call_chain = call_config.configure_call(&chain, &mut cli)?; - assert_eq!(call_parachain.function.pallet, "OnDemand"); - assert_eq!(call_parachain.function.name, "place_order_allow_death"); - assert_eq!(call_parachain.args, ["10000".to_string(), "2000".to_string()].to_vec()); - assert_eq!(call_parachain.suri, "//Bob"); - assert!(!call_parachain.sudo); - assert_eq!(call_parachain.display(&chain), "pop call parachain --pallet OnDemand --function place_order_allow_death --args \"10000\" \"2000\" --url wss://polkadot-rpc.publicnode.com/ --suri //Bob"); + assert_eq!(call_chain.function.pallet, "OnDemand"); + assert_eq!(call_chain.function.name, "place_order_allow_death"); + assert_eq!(call_chain.args, ["10000".to_string(), "2000".to_string()].to_vec()); + assert_eq!(call_chain.suri, "//Bob"); + assert!(!call_chain.sudo); + assert_eq!(call_chain.display(&chain), "pop call chain --pallet OnDemand --function place_order_allow_death --args \"10000\" \"2000\" --url wss://polkadot-rpc.publicnode.com/ --suri //Bob"); cli.verify() } #[tokio::test] async fn prepare_extrinsic_works() -> Result<()> { let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; - let mut call_config = CallParachain { + let mut call_config = Call { function: Function { pallet: "WrongName".to_string(), name: "WrongName".to_string(), @@ -743,7 +743,7 @@ mod tests { async fn user_cancel_submit_extrinsic_works() -> Result<()> { let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; let pallets = parse_chain_metadata(&client)?; - let mut call_config = CallParachain { + let mut call_config = Call { function: find_dispatchable_by_name(&pallets, "System", "remark")?.clone(), args: vec!["0x11".to_string()].to_vec(), suri: DEFAULT_URI.to_string(), @@ -762,7 +762,7 @@ mod tests { #[tokio::test] async fn user_cancel_submit_extrinsic_from_call_data_works() -> Result<()> { let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; - let call_config = CallParachainCommand { + let call_config = CallChainCommand { pallet: None, function: None, args: vec![].to_vec(), @@ -786,7 +786,7 @@ mod tests { #[tokio::test] async fn configure_sudo_works() -> Result<()> { // Test when sudo pallet doesn't exist. - let mut call_config = CallParachainCommand { + let mut call_config = CallChainCommand { pallet: None, function: None, args: vec![].to_vec(), @@ -797,7 +797,7 @@ mod tests { sudo: true, }; let mut cli = MockCli::new() - .expect_intro("Call a parachain") + .expect_intro("Call a chain") .expect_warning("NOTE: sudo is not supported by the chain. Ignoring `--sudo` flag."); let chain = call_config.configure_chain(&mut cli).await?; call_config.configure_sudo(&chain, &mut cli)?; @@ -805,7 +805,7 @@ mod tests { cli.verify()?; // Test when sudo pallet exist. - cli = MockCli::new().expect_intro("Call a parachain").expect_confirm( + cli = MockCli::new().expect_intro("Call a chain").expect_confirm( "Would you like to dispatch this function call with `Root` origin?", true, ); @@ -818,7 +818,7 @@ mod tests { #[test] fn reset_for_new_call_works() -> Result<()> { - let mut call_config = CallParachainCommand { + let mut call_config = CallChainCommand { pallet: Some("System".to_string()), function: Some("remark".to_string()), args: vec!["0x11".to_string()].to_vec(), @@ -838,7 +838,7 @@ mod tests { #[test] fn requires_user_input_works() -> Result<()> { - let mut call_config = CallParachainCommand { + let mut call_config = CallChainCommand { pallet: Some("System".to_string()), function: Some("remark".to_string()), args: vec!["0x11".to_string()].to_vec(), @@ -856,7 +856,7 @@ mod tests { #[test] fn expand_file_arguments_works() -> Result<()> { - let mut call_config = CallParachainCommand { + let mut call_config = CallChainCommand { pallet: Some("Registrar".to_string()), function: Some("register".to_string()), args: vec!["2000".to_string(), "0x1".to_string(), "0x12".to_string()].to_vec(), diff --git a/crates/pop-cli/src/commands/call/mod.rs b/crates/pop-cli/src/commands/call/mod.rs index 5f28ab899..dd1a2318c 100644 --- a/crates/pop-cli/src/commands/call/mod.rs +++ b/crates/pop-cli/src/commands/call/mod.rs @@ -2,10 +2,10 @@ use clap::{Args, Subcommand}; +#[cfg(feature = "parachain")] +pub(crate) mod chain; #[cfg(feature = "contract")] pub(crate) mod contract; -#[cfg(feature = "parachain")] -pub(crate) mod parachain; /// Arguments for calling a smart contract. #[derive(Args)] @@ -15,13 +15,13 @@ pub(crate) struct CallArgs { pub command: Command, } -/// Call a smart contract. +/// Call a chain or a smart contract. #[derive(Subcommand)] pub(crate) enum Command { - /// Call a parachain. + /// Call a chain #[cfg(feature = "parachain")] - #[clap(alias = "p")] - Parachain(parachain::CallParachainCommand), + #[clap(alias = "p", visible_aliases = ["parachain"])] + Chain(chain::CallChainCommand), /// Call a contract #[cfg(feature = "contract")] #[clap(alias = "c")] diff --git a/crates/pop-cli/src/commands/mod.rs b/crates/pop-cli/src/commands/mod.rs index 31d779a51..9af86e3f4 100644 --- a/crates/pop-cli/src/commands/mod.rs +++ b/crates/pop-cli/src/commands/mod.rs @@ -26,7 +26,7 @@ pub(crate) enum Command { #[clap(alias = "b", about = about_build())] #[cfg(any(feature = "parachain", feature = "contract"))] Build(build::BuildArgs), - /// Call a parachain or a smart contract. + /// Call a chain or a smart contract. #[clap(alias = "c")] #[cfg(any(feature = "parachain", feature = "contract"))] Call(call::CallArgs), @@ -99,7 +99,7 @@ impl Command { #[cfg(any(feature = "parachain", feature = "contract"))] Self::Call(args) => match args.command { #[cfg(feature = "parachain")] - call::Command::Parachain(cmd) => cmd.execute().await.map(|_| Value::Null), + call::Command::Chain(cmd) => cmd.execute().await.map(|_| Value::Null), #[cfg(feature = "contract")] call::Command::Contract(cmd) => cmd.execute().await.map(|_| Value::Null), }, diff --git a/crates/pop-cli/tests/parachain.rs b/crates/pop-cli/tests/parachain.rs index 0c5623ac9..67132e6d4 100644 --- a/crates/pop-cli/tests/parachain.rs +++ b/crates/pop-cli/tests/parachain.rs @@ -133,13 +133,13 @@ name = "collator-01" // Wait for the networks to initialize. Increased timeout to accommodate CI environment delays. sleep(Duration::from_secs(50)).await; - // `pop call parachain --pallet System --function remark --args "0x11" --url + // `pop call chain --pallet System --function remark --args "0x11" --url // ws://127.0.0.1:random_port --suri //Alice --skip-confirm` Command::cargo_bin("pop") .unwrap() .args(&[ "call", - "parachain", + "chain", "--pallet", "System", "--function", @@ -155,13 +155,13 @@ name = "collator-01" .assert() .success(); - // pop call parachain --call 0x00000411 --url ws://127.0.0.1:random_port --suri //Alice + // pop call chain --call 0x00000411 --url ws://127.0.0.1:random_port --suri //Alice // --skip-confirm Command::cargo_bin("pop") .unwrap() .args(&[ "call", - "parachain", + "chain", "--call", "0x00000411", "--url", From 719821b9301dda1de6edb8465d01a064f7b8c996 Mon Sep 17 00:00:00 2001 From: Daanvdplas Date: Tue, 10 Dec 2024 09:32:12 +0100 Subject: [PATCH 192/211] feat: events call parachain --- crates/pop-cli/src/commands/call/chain.rs | 3 +-- crates/pop-parachains/src/call/mod.rs | 20 +++++++++++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/crates/pop-cli/src/commands/call/chain.rs b/crates/pop-cli/src/commands/call/chain.rs index ce7050a29..cfabb1b1b 100644 --- a/crates/pop-cli/src/commands/call/chain.rs +++ b/crates/pop-cli/src/commands/call/chain.rs @@ -381,8 +381,7 @@ impl Call { let result = sign_and_submit_extrinsic(client, tx, &self.suri) .await .map_err(|err| anyhow!("{}", format!("{err:?}")))?; - - spinner.stop(format!("Extrinsic submitted with hash: {:?}", result)); + spinner.stop(result); Ok(()) } diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs index be9b4f69f..ac98d0fcd 100644 --- a/crates/pop-parachains/src/call/mod.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -62,7 +62,25 @@ pub async fn sign_and_submit_extrinsic( .wait_for_finalized_success() .await .map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))?; - Ok(format!("{:?}", result.extrinsic_hash())) + + // // Obtain events related to the extrinsic. + let mut events = Vec::new(); + for event in result.iter() { + let event = event.map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))?; + let pallet = + if event.pallet_name() == xt.pallet_name() { event.pallet_name() } else { continue }; + let variant = event.variant_name(); + let field_values = event + .field_values() + .map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))?; + events.push(format!("{pallet}::{variant}: {field_values}")); + } + + Ok(format!( + "Extrinsic Submitted with hash: {:?}\n\nEvent(s):\n\n{}", + result.extrinsic_hash(), + events.join("\n\n") + )) } /// Encodes the call data for a given extrinsic into a hexadecimal string. From 3a7062e2faa7eb18c995aedce46ef2d415dfe8f5 Mon Sep 17 00:00:00 2001 From: Daanvdplas Date: Tue, 10 Dec 2024 13:39:26 +0100 Subject: [PATCH 193/211] docs: add comment --- crates/pop-parachains/src/call/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs index ac98d0fcd..a21f577f9 100644 --- a/crates/pop-parachains/src/call/mod.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -63,10 +63,11 @@ pub async fn sign_and_submit_extrinsic( .await .map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))?; - // // Obtain events related to the extrinsic. + // Obtain events related to the extrinsic. let mut events = Vec::new(); for event in result.iter() { let event = event.map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))?; + // Filter events that are from a different pallet. let pallet = if event.pallet_name() == xt.pallet_name() { event.pallet_name() } else { continue }; let variant = event.variant_name(); From e99f7198e478b24ca7c6203d614ef60d6a3d37a0 Mon Sep 17 00:00:00 2001 From: Alex Bean Date: Tue, 10 Dec 2024 18:28:29 +0100 Subject: [PATCH 194/211] feat: guide user to call a parachain (#316) * feat: guide user for calling a contract * feat: get metadata contract from the contract path * refactor: refactor test and validate address input * fix: apply feedback * feat: prompt to have another call and skip questions for queries * refactor: use Cli module instead of cliclack * test: unit test pop-cli crate * test: unit contracts crate * chore: format * test: refactor and improve test cases * fix: fix todos and refactor * test: fix unit test * feat: parse types of parameters and display it to the user in the placeholder * refactor: error handling for pop call * refactor: display call to be executed after guide and reorder * refactor: when repeat call use same contract values and dont clean screen * test: add dry-run test * test: refactor and add more test coverage * test: more coverage * fix: unit test * feat: dev mode to skip certain user prompts * refactor: test functions, renaming and fix clippy * refactor: improve devex of pop call contract * test: adjust tests to refactor * chore: reset_for_new_call fields * fix: build contract if has not been built * refactor: use command state (#338) Merged set_up_call_config and guide_user_to_call_contract into a single function. Also adds short symbols for arguments. * fix: automatically add some or none to Option argument * test: refactor and tests * refactor: improve code and comments * fix: renaming and clean code * chore: option params not mandatory * fix: parse user inputs for Option arguments in constructor (#335) * fix: automatically add some or none to Option argument * fix: tests * refactor: process_function_args * test: update tests accordingly last changes * fix: issue with delimiter * test: fix unit test * refactor: renaming and fix comments * refactor: format types (#339) Shows the full type representation, making it easier to see the entry format of parameter values. * fix: logo doesn't show in README * feat: pop call parachain prototype * feat: dispaly arguments of extrinsic * refactor: structure similar to pop call contract * feat: parse all values for extrinsic/storage * refactor: signer in common * refactor: improve messages * feat: call parachain ui * fix: calls working * refactor: remove unused code * refactor: remove unused code * refactor: various fixes * refactor: various fixes * feat: add option to include params from command line * refactor: clean docs and refactor code * fix: tests * refactor: parse all the metadata again * refactor: reorganize and clean metadata functions * feat: display specific use cases to the user * refactor: predefined actions * fix: various fixes * fix: error message not supported for complex types * refactor: parse all metadata, including parameters at once * refactor: clean docs and move code * fix: format_type * fix: parse user inputs for Option arguments (#332) * fix: automatically add some or none to Option argument * test: refactor and tests * refactor: improve code and comments * fix: renaming and clean code * chore: option params not mandatory * fix: parse user inputs for Option arguments in constructor (#335) * fix: automatically add some or none to Option argument * fix: tests * refactor: process_function_args * test: update tests accordingly last changes * fix: issue with delimiter * test: fix unit test * refactor: renaming and fix comments * refactor: format types (#339) Shows the full type representation, making it easier to see the entry format of parameter values. * fix: logo doesn't show in README --------- Co-authored-by: Frank Bell <60948618+evilrobot-01@users.noreply.github.com> Co-authored-by: Alejandro Martinez Andres <11448715+al3mart@users.noreply.github.com> * test: fix unit test * refactor: clean the way to parse and prompt parameters * feat: add Purchase on-demand coretime use cases * test: add skip_confirm, move when prompt for the signer and create the integration test * test: call parachain ui unit test * refactor: separate structs * fmt * test: pop-cli unit testing * test: pop-common unit tests * test: parse metadata unit tests * test: refactor and test processing parameters * test: comments and unit test in call functions * fix: clippy warnings * chore: fmt * fix: solve conflicts and unit tests (#359) * test: call parachain ui unit test * test: pop-cli unit testing * test: pop-common unit tests * test: parse metadata unit tests * test: refactor and test processing parameters * test: comments and unit test in call functions * fix: clippy warnings * chore: fmt * fix: conflicts and unit tests * test: remove test and improve test * feat: guide user for calling a contract * feat: get metadata contract from the contract path * refactor: refactor test and validate address input * fix: apply feedback * feat: prompt to have another call and skip questions for queries * refactor: use Cli module instead of cliclack * test: unit test pop-cli crate * test: unit contracts crate * chore: format * test: refactor and improve test cases * fix: fix todos and refactor * test: fix unit test * feat: parse types of parameters and display it to the user in the placeholder * refactor: error handling for pop call * refactor: display call to be executed after guide and reorder * refactor: when repeat call use same contract values and dont clean screen * test: add dry-run test * test: refactor and add more test coverage * test: more coverage * fix: unit test * feat: dev mode to skip certain user prompts * refactor: test functions, renaming and fix clippy * refactor: improve devex of pop call contract * test: adjust tests to refactor * chore: reset_for_new_call fields * fix: build contract if has not been built * refactor: use command state (#338) Merged set_up_call_config and guide_user_to_call_contract into a single function. Also adds short symbols for arguments. * fix: automatically add some or none to Option argument * test: refactor and tests * refactor: improve code and comments * fix: renaming and clean code * chore: option params not mandatory * fix: parse user inputs for Option arguments in constructor (#335) * fix: automatically add some or none to Option argument * fix: tests * refactor: process_function_args * test: update tests accordingly last changes * fix: issue with delimiter * test: fix unit test * refactor: renaming and fix comments * refactor: format types (#339) Shows the full type representation, making it easier to see the entry format of parameter values. * feat: pop call parachain prototype * feat: dispaly arguments of extrinsic * refactor: structure similar to pop call contract * feat: parse all values for extrinsic/storage * refactor: signer in common * refactor: improve messages * feat: call parachain ui * fix: calls working * refactor: remove unused code * refactor: remove unused code * refactor: various fixes * refactor: various fixes * feat: add option to include params from command line * refactor: clean docs and refactor code * fix: tests * refactor: parse all the metadata again * refactor: reorganize and clean metadata functions * feat: display specific use cases to the user * refactor: predefined actions * fix: various fixes * fix: error message not supported for complex types * refactor: parse all metadata, including parameters at once * refactor: clean docs and move code * fix: format_type * test: fix unit test * refactor: clean the way to parse and prompt parameters * feat: add Purchase on-demand coretime use cases * test: add skip_confirm, move when prompt for the signer and create the integration test * test: call parachain ui unit test * test: pop-cli unit testing * test: pop-common unit tests * test: parse metadata unit tests * test: refactor and test processing parameters * test: comments and unit test in call functions * fix: clippy warnings * chore: fmt * feat: repeat call only if using guide UI * fix: clippy * refactor: various improvements * chore: parser for pallet and extrinsic input names * refactor: only move to pop_common the needed functions * refactor: improve test, docs and errors * test: fix unit tests * fix: reset_for_new_call when extrinisc is not supported * fix: build with parachain features * test: wait before call parachain in integration test * docs: minor improvements * test: migrate find_free_port to pop_common * test: fix increase waiting time * test: remove unnecesary test case * refactor: rename api with client * refactor: naming and docs * docs: improve docs and missing comments * test: remove unnecesary verbose * test: find_free_port * docs: improve parameter documentation * test: add missing test to sign_and_submit_extrinsic * fix: apply feedback from auxiliar PRs, remove unnecesary clones * docs: public modules * refactor: clean unused params * fix: mark all extrinsics that uses calls as parameter as unsupported * test: fix expect_select * docs: improve documentation * feat: submit extrinsic from call_data (#348) * feat: submit extrinsic from call_data * test: unit test for initialize_api_client * test: unit test for send_extrinsic_from_call_data * fix: CallData struct * fix: skip_confirm for send_extrinsic_from_call_data * chore: clippy * chore: fmt * refactor: minor doc and naming changes * refactor: remove unnecesary clones and return early when submit_extrinsic_from_call_data * chore: fmt * refactor: split decode_call_data logic outside sign_and_submit_extrinsic_with_call_data * feat: parse files when the argument values are very big (#363) * feat: parse files when the argument values are very big * test: unit test * chore: fmt * feat: file logic using the command line * fix: sequence arguments * test: fix unit test * refactor: remove prompting the user if input is file or value * refactor: parse_extrinsic_arguments * fix: CI deny * refactor: reorder Param derive macros * test: fix decode_call_data_works unit test * refactor: use Default derive macro and define constants for test values (#366) * feat: parse files when the argument values are very big * chore: fmt * feat: file logic using the command line * fix: sequence arguments * refactor: parse_extrinsic_arguments * refactor: use Default in pop_parachain structs * refactor: use Default in CallParachainCommand struct * refactor: use constant in tests * chore: fmt and small refactor * feat: flag sudo to wrap extrinsic (#349) * feat: submit extrinsic from call_data * test: unit test for initialize_api_client * feat: wrap call into a sudo call * test: add unit test to the new logic * fix: skip_confirm for send_extrinsic_from_call_data * chore: clippy * docs: renaming and improve docs * test: use force_transfer for testing * fix: check if sudo exist before prompt the user * chore: fmt * chore: fmt * test: fix wrong assert * docs: improve comments and output messages * refactor: split decode_call_data logic outside sign_and_submit_extrinsic_with_call_data * fix: test construct_sudo_extrinsic_works and formatting * refactor: various fixes and improvements (#367) * refactor: sort pallets/dispatchables * refactor: remove unnecessary async * fix: resolve issue after rebase * fix: more async issues after rebase * refactor: use single constant * refactor: terminology (#368) * refactor: terminology * refactor: simply pallet/function relationship * fix: amend call_data conflicts after refactor * refactor: improvements (#370) * fix: add missing short arg option * refactor: note that extrinsic wait includes finalization * refactor: remove clones * style: formatting * refactor: make file prompt more generic * refactor: add missing license headers * style: formatting * docs: comments * docs: comments * docs: comments * refactor: reuse existing metadata * refactor: minimise clones * docs: comments * refactor: naming * docs: fix parameter doc comments * refactor: address clippy warnings * refactor: rename parachain with chain as the primary command and retain parachain as an alias (#373) * refactor: rename parachain with chain in visible messages * refactor: rename parachain with chain internal code * chore: solve fmt after rebase * refactor: small fix, use alias instead aliases * refactor: rename CallParachain struct into Call --------- Co-authored-by: Frank Bell <60948618+evilrobot-01@users.noreply.github.com> Co-authored-by: Alejandro Martinez Andres <11448715+al3mart@users.noreply.github.com> Co-authored-by: Daanvdplas --- Cargo.lock | 434 +++++-- Cargo.toml | 4 +- crates/pop-cli/src/commands/call/chain.rs | 1050 +++++++++++++++++ crates/pop-cli/src/commands/call/mod.rs | 8 +- crates/pop-cli/src/commands/mod.rs | 9 +- crates/pop-cli/tests/parachain.rs | 82 +- crates/pop-common/Cargo.toml | 3 + crates/pop-common/src/build.rs | 2 + crates/pop-common/src/errors.rs | 12 +- crates/pop-common/src/lib.rs | 47 +- crates/pop-common/src/metadata.rs | 228 ++++ .../src/utils => pop-common/src}/signer.rs | 29 +- crates/pop-contracts/Cargo.toml | 2 - crates/pop-contracts/src/build.rs | 2 +- crates/pop-contracts/src/call.rs | 11 +- crates/pop-contracts/src/errors.rs | 9 +- crates/pop-contracts/src/lib.rs | 5 +- crates/pop-contracts/src/new.rs | 2 +- crates/pop-contracts/src/node/mod.rs | 3 +- crates/pop-contracts/src/testing.rs | 10 - crates/pop-contracts/src/up.rs | 11 +- crates/pop-contracts/src/utils/helpers.rs | 112 -- crates/pop-contracts/src/utils/metadata.rs | 147 +-- crates/pop-contracts/src/utils/mod.rs | 148 ++- crates/pop-parachains/Cargo.toml | 4 + .../src/call/metadata/action.rs | 207 ++++ .../pop-parachains/src/call/metadata/mod.rs | 334 ++++++ .../src/call/metadata/params.rs | 238 ++++ crates/pop-parachains/src/call/mod.rs | 241 ++++ crates/pop-parachains/src/errors.rs | 25 + crates/pop-parachains/src/lib.rs | 14 + deny.toml | 1 + 32 files changed, 3010 insertions(+), 424 deletions(-) create mode 100644 crates/pop-cli/src/commands/call/chain.rs create mode 100644 crates/pop-common/src/metadata.rs rename crates/{pop-contracts/src/utils => pop-common/src}/signer.rs (52%) delete mode 100644 crates/pop-contracts/src/utils/helpers.rs create mode 100644 crates/pop-parachains/src/call/metadata/action.rs create mode 100644 crates/pop-parachains/src/call/metadata/mod.rs create mode 100644 crates/pop-parachains/src/call/metadata/params.rs create mode 100644 crates/pop-parachains/src/call/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 220042045..6ff7331d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -355,7 +355,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -533,7 +533,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -550,7 +550,7 @@ checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -848,7 +848,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", "syn_derive", ] @@ -1075,7 +1075,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1505,7 +1505,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1532,7 +1532,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1549,7 +1549,7 @@ checksum = "98532a60dedaebc4848cb2cba5023337cc9ea3af16a5b062633fabfd9f18fb60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1597,7 +1597,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1619,7 +1619,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core 0.20.10", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1687,7 +1687,7 @@ checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1698,7 +1698,7 @@ checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1709,7 +1709,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1722,7 +1722,27 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.77", + "syn 2.0.89", +] + +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", ] [[package]] @@ -1793,7 +1813,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1823,7 +1843,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.77", + "syn 2.0.89", "termcolor", "toml 0.8.19", "walkdir", @@ -2078,7 +2098,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -2310,7 +2330,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -2897,6 +2917,124 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -2905,12 +3043,23 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.5.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] @@ -3005,7 +3154,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f357e2e867f4e222ffc4015a6e61d1073548de89f70a4e36a8b0385562777fa" dependencies = [ "blake2", - "derive_more", + "derive_more 0.99.18", "ink_primitives", "pallet-contracts-uapi-next", "parity-scale-codec", @@ -3023,7 +3172,7 @@ dependencies = [ "blake2", "cfg-if", "const_env", - "derive_more", + "derive_more 0.99.18", "ink_allocator", "ink_engine", "ink_prelude", @@ -3050,7 +3199,7 @@ version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a98fcc0ff9292ff68c7ee7b84c93533c9ff13859ec3b148faa822e2da9954fe6" dependencies = [ - "derive_more", + "derive_more 0.99.18", "impl-serde", "ink_prelude", "ink_primitives", @@ -3076,7 +3225,7 @@ version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11ec35ef7f45e67a53b6142d7e7f18e6d9292d76c3a2a1da14cf8423e481813d" dependencies = [ - "derive_more", + "derive_more 0.99.18", "ink_prelude", "parity-scale-codec", "scale-decode 0.10.0", @@ -3804,7 +3953,7 @@ checksum = "cb26336e6dc7cc76e7927d2c9e7e3bb376d7af65a6f56a0b16c47d18a9b1abc5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -3819,6 +3968,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + [[package]] name = "lock_api" version = "0.4.12" @@ -4242,7 +4397,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4464,7 +4619,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4495,7 +4650,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4562,7 +4717,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6380dbe1fb03ecc74ad55d841cfc75480222d153ba69ddcb00977866cbdabdb8" dependencies = [ "polkavm-derive-impl 0.5.0", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4592,7 +4747,7 @@ dependencies = [ "polkavm-common 0.5.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4604,7 +4759,7 @@ dependencies = [ "polkavm-common 0.8.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4616,7 +4771,7 @@ dependencies = [ "polkavm-common 0.9.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4626,7 +4781,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15e85319a0d5129dc9f021c62607e0804f5fb777a05cdda44d750ac0732def66" dependencies = [ "polkavm-derive-impl 0.8.0", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4636,7 +4791,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" dependencies = [ "polkavm-derive-impl 0.9.0", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4708,10 +4863,13 @@ dependencies = [ "mockito", "regex", "reqwest 0.12.7", + "scale-info", "serde", "serde_json", "strum 0.26.3", "strum_macros 0.26.4", + "subxt", + "subxt-signer", "tar", "tempfile", "thiserror", @@ -4742,8 +4900,6 @@ dependencies = [ "sp-weights", "strum 0.26.3", "strum_macros 0.26.4", - "subxt", - "subxt-signer", "tar", "tempfile", "thiserror", @@ -4762,13 +4918,17 @@ dependencies = [ "duct", "flate2", "glob", + "hex", "indexmap 2.5.0", "mockito", "pop-common", "reqwest 0.12.7", + "scale-info", + "scale-value", "serde_json", "strum 0.26.3", "strum_macros 0.26.4", + "subxt", "symlink", "tar", "tempfile", @@ -4855,7 +5015,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" dependencies = [ "proc-macro2", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4916,9 +5076,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -5065,7 +5225,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -5486,7 +5646,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58c4eb8a81997cf040a091d1f7e1938aeab6749d3a0dfa73af43cdc32393483d" dependencies = [ "byteorder", - "derive_more", + "derive_more 0.99.18", "twox-hash", ] @@ -5544,7 +5704,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7caaf753f8ed1ab4752c6afb20174f03598c664724e0e32628e161c21000ff76" dependencies = [ - "derive_more", + "derive_more 0.99.18", "parity-scale-codec", "scale-bits 0.4.0", "scale-decode-derive 0.10.0", @@ -5558,7 +5718,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e98f3262c250d90e700bb802eb704e1f841e03331c2eb815e46516c4edbf5b27" dependencies = [ - "derive_more", + "derive_more 0.99.18", "parity-scale-codec", "primitive-types", "scale-bits 0.6.0", @@ -5598,7 +5758,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d70cb4b29360105483fac1ed567ff95d65224a14dd275b6303ed0a654c78de5" dependencies = [ - "derive_more", + "derive_more 0.99.18", "parity-scale-codec", "scale-encode-derive 0.5.0", "scale-info", @@ -5611,7 +5771,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ba0b9c48dc0eb20c60b083c29447c0c4617cb7c4a4c9fef72aa5c5bc539e15e" dependencies = [ - "derive_more", + "derive_more 0.99.18", "parity-scale-codec", "primitive-types", "scale-bits 0.6.0", @@ -5648,13 +5808,13 @@ dependencies = [ [[package]] name = "scale-info" -version = "2.11.3" +version = "2.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca070c12893629e2cc820a9761bedf6ce1dcddc9852984d1dc734b8bd9bd024" +checksum = "346a3b32eba2640d17a9cb5927056b08f3de90f65b72fe09402c2ad07d684d0b" dependencies = [ "bitvec", "cfg-if", - "derive_more", + "derive_more 1.0.0", "parity-scale-codec", "scale-info-derive", "schemars", @@ -5663,14 +5823,14 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.11.3" +version = "2.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d35494501194174bda522a32605929eefc9ecf7e0a326c26db1fdd85881eb62" +checksum = "c6630024bf739e2179b91fb424b28898baf819414262c5d376677dbff1fe7ebf" dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.89", ] [[package]] @@ -5692,7 +5852,7 @@ dependencies = [ "proc-macro2", "quote", "scale-info", - "syn 2.0.77", + "syn 2.0.89", "thiserror", ] @@ -5704,7 +5864,7 @@ checksum = "ba4d772cfb7569e03868400344a1695d16560bf62b86b918604773607d39ec84" dependencies = [ "base58", "blake2", - "derive_more", + "derive_more 0.99.18", "either", "frame-metadata 15.1.0", "parity-scale-codec", @@ -5747,7 +5907,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -5920,7 +6080,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -5931,7 +6091,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -5955,7 +6115,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -6225,7 +6385,7 @@ dependencies = [ "bs58", "chacha20", "crossbeam-queue", - "derive_more", + "derive_more 0.99.18", "ed25519-zebra 4.0.3", "either", "event-listener 4.0.3", @@ -6275,7 +6435,7 @@ dependencies = [ "async-lock", "base64 0.21.7", "blake2-rfc", - "derive_more", + "derive_more 0.99.18", "either", "event-listener 4.0.3", "fnv", @@ -6439,7 +6599,7 @@ checksum = "48d09fa0a5f7299fb81ee25ae3853d26200f7a348148aed6de76be905c007dbe" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -6560,7 +6720,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -6766,7 +6926,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -6840,7 +7000,7 @@ dependencies = [ "scale-info", "scale-typegen", "subxt-metadata", - "syn 2.0.77", + "syn 2.0.89", "thiserror", "tokio", ] @@ -6903,7 +7063,7 @@ dependencies = [ "quote", "scale-typegen", "subxt-codegen", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -6960,9 +7120,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ "proc-macro2", "quote", @@ -6978,7 +7138,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -6996,6 +7156,17 @@ dependencies = [ "futures-core", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -7127,7 +7298,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -7171,6 +7342,16 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -7222,7 +7403,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -7456,7 +7637,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -7604,12 +7785,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" - [[package]] name = "unicode-ident" version = "1.0.13" @@ -7673,9 +7848,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.2" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", @@ -7689,6 +7864,18 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -7818,7 +8005,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", "wasm-bindgen-shared", ] @@ -7852,7 +8039,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -8457,6 +8644,18 @@ version = "0.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "wyz" version = "0.5.1" @@ -8501,6 +8700,30 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff4524214bc4629eba08d78ceb1d6507070cc0bcbbed23af74e19e6e924a24cf" +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -8519,7 +8742,28 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", + "synstructure", ] [[package]] @@ -8539,7 +8783,29 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index a5a8c7c1e..30c181caa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ toml = "0.5.0" # networking reqwest = { version = "0.12", features = ["json"] } tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } -url = "2.5" +url = "2.5.4" # contracts subxt-signer = { version = "0.37.0", features = ["subxt", "sr25519"] } @@ -53,7 +53,9 @@ contract-build = "5.0.0-alpha" contract-extrinsics = "5.0.0-alpha" contract-transcode = "5.0.0-alpha" scale-info = { version = "2.11.3", default-features = false, features = ["derive"] } +scale-value = { version = "0.16.2", default-features = false, features = ["from-string", "parser-ss58"] } heck = "0.5.0" +hex = { version = "0.4.3", default-features = false } # parachains askama = "0.12" diff --git a/crates/pop-cli/src/commands/call/chain.rs b/crates/pop-cli/src/commands/call/chain.rs new file mode 100644 index 000000000..ce7050a29 --- /dev/null +++ b/crates/pop-cli/src/commands/call/chain.rs @@ -0,0 +1,1050 @@ +// SPDX-License-Identifier: GPL-3.0 + +use std::path::Path; + +use crate::cli::{self, traits::*}; +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, sign_and_submit_extrinsic_with_call_data, supported_actions, Action, + DynamicPayload, Function, OnlineClient, Pallet, Param, SubstrateConfig, +}; +use url::Url; + +const DEFAULT_URL: &str = "ws://localhost:9944/"; +const DEFAULT_URI: &str = "//Alice"; +const ENCODED_CALL_DATA_MAX_LEN: usize = 500; // Maximum length of encoded call data to display. + +/// Command to construct and execute extrinsics with configurable pallets, functions, arguments, and +/// signing options. +#[derive(Args, Clone, Default)] +pub struct CallChainCommand { + /// The pallet containing the dispatchable function to execute. + #[arg(short, long, value_parser = parse_pallet_name)] + pallet: Option, + /// The dispatchable function to execute within the specified pallet. + #[arg(short, long, value_parser = parse_function_name)] + function: Option, + /// The dispatchable function arguments, encoded as strings. + #[arg(short, long, num_args = 0..,)] + args: Vec, + /// Websocket endpoint of a node. + #[arg(short, long, value_parser)] + url: Option, + /// Secret key URI for the account signing the extrinsic. + /// + /// e.g. + /// - for a dev account "//Alice" + /// - with a password "//Alice///SECRET_PASSWORD" + #[arg(short, long)] + suri: Option, + /// SCALE encoded bytes representing the call data of the extrinsic. + #[arg(name = "call", short, long, conflicts_with_all = ["pallet", "function", "args"])] + call_data: Option, + /// Authenticates the sudo key and dispatches a function call with `Root` origin. + #[arg(short = 'S', long)] + sudo: bool, + /// Automatically signs and submits the extrinsic without prompting for confirmation. + #[arg(short('y'), long)] + skip_confirm: bool, +} + +impl CallChainCommand { + /// Executes the command. + pub(crate) async fn execute(mut self) -> Result<()> { + let mut cli = cli::Cli; + // Check if all fields are specified via the command line. + let prompt_to_repeat_call = self.requires_user_input(); + // Configure the chain. + let chain = self.configure_chain(&mut cli).await?; + // Execute the call if call_data is provided. + if let Some(call_data) = self.call_data.as_ref() { + if let Err(e) = self + .submit_extrinsic_from_call_data(&chain.client, call_data, &mut cli::Cli) + .await + { + display_message(&e.to_string(), false, &mut cli::Cli)?; + } + return Ok(()); + } + loop { + // Configure the call based on command line arguments/call UI. + let mut call = match self.configure_call(&chain, &mut cli) { + Ok(call) => call, + Err(e) => { + display_message(&e.to_string(), false, &mut cli)?; + break; + }, + }; + // Display the configured call. + cli.info(call.display(&chain))?; + // Prepare the extrinsic. + let xt = match call.prepare_extrinsic(&chain.client, &mut cli) { + Ok(payload) => payload, + Err(e) => { + display_message(&e.to_string(), false, &mut cli)?; + break; + }, + }; + + // Sign and submit the extrinsic. + if let Err(e) = call.submit_extrinsic(&chain.client, xt, &mut cli).await { + display_message(&e.to_string(), false, &mut cli)?; + break; + } + + if !prompt_to_repeat_call || + !cli.confirm("Do you want to perform another call?") + .initial_value(false) + .interact()? + { + display_message("Call complete.", true, &mut cli)?; + break; + } + self.reset_for_new_call(); + } + Ok(()) + } + + // Configures the chain by resolving the URL and fetching its metadata. + async fn configure_chain(&self, cli: &mut impl Cli) -> Result { + cli.intro("Call a chain")?; + // Resolve url. + let url = match &self.url { + Some(url) => url.clone(), + None => { + // Prompt for url. + let url: String = cli + .input("Which chain would you like to interact with?") + .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 }) + } + + // Configure the call based on command line arguments/call UI. + fn configure_call(&mut self, chain: &Chain, cli: &mut impl Cli) -> Result { + loop { + // Resolve pallet. + let pallet = match self.pallet { + Some(ref pallet_name) => find_pallet_by_name(&chain.pallets, pallet_name)?, + None => { + // Specific predefined actions first. + if let Some(action) = prompt_predefined_actions(&chain.pallets, cli)? { + self.function = Some(action.function_name().to_string()); + find_pallet_by_name(&chain.pallets, action.pallet_name())? + } else { + let mut prompt = cli.select("Select the pallet to call:"); + for pallet_item in &chain.pallets { + prompt = prompt.item(pallet_item, &pallet_item.name, &pallet_item.docs); + } + prompt.interact()? + } + }, + }; + + // Resolve dispatchable function. + let function = match self.function { + Some(ref name) => find_dispatchable_by_name(&chain.pallets, &pallet.name, name)?, + None => { + let mut prompt = cli.select("Select the function to call:"); + for function in &pallet.functions { + prompt = prompt.item(function, &function.name, &function.docs); + } + prompt.interact()? + }, + }; + // Certain dispatchable functions are not supported yet due to complexity. + if !function.is_supported { + cli.outro_cancel( + "The selected function is not supported yet. Please choose another one.", + )?; + self.reset_for_new_call(); + continue; + } + + // Resolve dispatchable function arguments. + let args = if self.args.is_empty() { + let mut args = Vec::new(); + for param in &function.params { + let input = prompt_for_param(cli, param)?; + args.push(input); + } + args + } else { + self.expand_file_arguments()? + }; + + // If chain has sudo prompt the user to confirm if they want to execute the call via + // sudo. + self.configure_sudo(chain, cli)?; + + // Resolve who is signing the extrinsic. + let suri = match self.suri.as_ref() { + Some(suri) => suri.clone(), + None => + cli.input("Signer of the extrinsic:").default_input(DEFAULT_URI).interact()?, + }; + + return Ok(Call { + function: function.clone(), + args, + suri, + skip_confirm: self.skip_confirm, + sudo: self.sudo, + }); + } + } + + // Submits an extrinsic to the chain using the provided encoded call data. + async fn submit_extrinsic_from_call_data( + &self, + client: &OnlineClient, + call_data: &str, + cli: &mut impl Cli, + ) -> Result<()> { + // Resolve who is signing the extrinsic. + let suri = match self.suri.as_ref() { + Some(suri) => suri, + None => &cli.input("Signer of the extrinsic:").default_input(DEFAULT_URI).interact()?, + }; + cli.info(format!("Encoded call data: {}", call_data))?; + if !self.skip_confirm && + !cli.confirm("Do you want to submit the extrinsic?") + .initial_value(true) + .interact()? + { + display_message( + &format!("Extrinsic with call data {call_data} was not submitted."), + false, + cli, + )?; + return Ok(()); + } + let spinner = cliclack::spinner(); + spinner.start("Signing and submitting the extrinsic and then waiting for finalization, please be patient..."); + let call_data_bytes = + decode_call_data(call_data).map_err(|err| anyhow!("{}", format!("{err:?}")))?; + let result = sign_and_submit_extrinsic_with_call_data(client, call_data_bytes, suri) + .await + .map_err(|err| anyhow!("{}", format!("{err:?}")))?; + + spinner.stop(format!("Extrinsic submitted successfully with hash: {:?}", result)); + display_message("Call complete.", true, cli)?; + Ok(()) + } + + // Checks if the chain has the Sudo pallet and prompts the user to confirm if they want to + // execute the call via `sudo`. + fn configure_sudo(&mut self, chain: &Chain, cli: &mut impl Cli) -> Result<()> { + match find_dispatchable_by_name(&chain.pallets, "Sudo", "sudo") { + Ok(_) => + if !self.sudo { + self.sudo = cli + .confirm( + "Would you like to dispatch this function call with `Root` origin?", + ) + .initial_value(false) + .interact()?; + }, + Err(_) => + if self.sudo { + cli.warning( + "NOTE: sudo is not supported by the chain. Ignoring `--sudo` flag.", + )?; + self.sudo = false; + }, + } + Ok(()) + } + + // Resets specific fields to default values for a new call. + fn reset_for_new_call(&mut self) { + self.pallet = None; + self.function = None; + self.args.clear(); + self.sudo = false; + } + + // Function to check if all required fields are specified. + fn requires_user_input(&self) -> bool { + self.pallet.is_none() || + self.function.is_none() || + self.args.is_empty() || + self.url.is_none() || + self.suri.is_none() + } + + /// Replaces file arguments with their contents, leaving other arguments unchanged. + fn expand_file_arguments(&self) -> Result> { + self.args + .iter() + .map(|arg| { + if std::fs::metadata(arg).map(|m| m.is_file()).unwrap_or(false) { + std::fs::read_to_string(arg) + .map_err(|err| anyhow!("Failed to read file {}", err.to_string())) + } else { + Ok(arg.clone()) + } + }) + .collect() + } +} + +// Represents a chain, including its URL, client connection, and available pallets. +struct Chain { + // Websocket endpoint of the node. + url: Url, + // The client used to interact with the chain. + client: OnlineClient, + // A list of pallets available on the chain. + pallets: Vec, +} + +/// Represents a configured dispatchable function call, including the pallet, function, arguments, +/// and signing options. +#[derive(Clone)] +struct Call { + /// The dispatchable function to execute. + function: Function, + /// The dispatchable function arguments, encoded as strings. + args: Vec, + /// Secret key URI for the account signing the extrinsic. + /// + /// e.g. + /// - for a dev account "//Alice" + /// - with a password "//Alice///SECRET_PASSWORD" + suri: String, + /// Whether to automatically sign and submit the extrinsic without prompting for confirmation. + skip_confirm: bool, + /// Whether to dispatch the function call with `Root` origin. + sudo: bool, +} + +impl Call { + // Prepares the extrinsic. + fn prepare_extrinsic( + &self, + client: &OnlineClient, + cli: &mut impl Cli, + ) -> Result { + let xt = match construct_extrinsic(&self.function, self.args.clone()) { + Ok(tx) => tx, + Err(e) => { + return Err(anyhow!("Error: {}", e)); + }, + }; + // If sudo is required, wrap the call in a sudo call. + let xt = if self.sudo { construct_sudo_extrinsic(xt)? } else { xt }; + let encoded_data = encode_call_data(client, &xt)?; + // If the encoded call data is too long, don't display it all. + if encoded_data.len() < ENCODED_CALL_DATA_MAX_LEN { + cli.info(format!("Encoded call data: {}", encode_call_data(client, &xt)?))?; + } + Ok(xt) + } + + // Sign and submit an extrinsic. + async fn submit_extrinsic( + &mut self, + client: &OnlineClient, + tx: DynamicPayload, + cli: &mut impl Cli, + ) -> Result<()> { + if !self.skip_confirm && + !cli.confirm("Do you want to submit the extrinsic?") + .initial_value(true) + .interact()? + { + display_message( + &format!("Extrinsic for `{}` was not submitted.", self.function.name), + false, + cli, + )?; + return Ok(()); + } + let spinner = cliclack::spinner(); + spinner.start("Signing and submitting the extrinsic and then waiting for finalization, please be patient..."); + let result = sign_and_submit_extrinsic(client, tx, &self.suri) + .await + .map_err(|err| anyhow!("{}", format!("{err:?}")))?; + + spinner.stop(format!("Extrinsic submitted with hash: {:?}", result)); + Ok(()) + } + + fn display(&self, chain: &Chain) -> String { + let mut full_message = "pop call chain".to_string(); + full_message.push_str(&format!(" --pallet {}", self.function.pallet)); + full_message.push_str(&format!(" --function {}", self.function)); + if !self.args.is_empty() { + let args: Vec<_> = self + .args + .iter() + .map(|a| { + // If the argument is too long, don't show it all, truncate it. + if a.len() > ENCODED_CALL_DATA_MAX_LEN { + format!("\"{}...{}\"", &a[..20], &a[a.len() - 20..]) + } else { + format!("\"{a}\"") + } + }) + .collect(); + full_message.push_str(&format!(" --args {}", args.join(" "))); + } + full_message.push_str(&format!(" --url {} --suri {}", chain.url, self.suri)); + if self.sudo { + full_message.push_str(" --sudo"); + } + full_message + } +} + +// 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 { + cli.outro(message)?; + } else { + cli.outro_cancel(message)?; + } + Ok(()) +} + +// Prompts the user for some predefined actions. +fn prompt_predefined_actions(pallets: &[Pallet], cli: &mut impl Cli) -> Result> { + let mut predefined_action = cli.select("What would you like to do?"); + for action in supported_actions(pallets) { + predefined_action = predefined_action.item( + Some(action.clone()), + action.description(), + action.pallet_name(), + ); + } + predefined_action = predefined_action.item(None, "All", "Explore all pallets and functions"); + Ok(predefined_action.interact()?) +} + +// Prompts the user for the value of a parameter. +fn prompt_for_param(cli: &mut impl Cli, param: &Param) -> Result { + if param.is_optional { + if !cli + .confirm(format!( + "Do you want to provide a value for the optional parameter: {}?", + param.name + )) + .interact()? + { + return Ok("None()".to_string()); + } + let value = get_param_value(cli, param)?; + Ok(format!("Some({})", value)) + } else { + get_param_value(cli, param) + } +} + +// Resolves the value of a parameter based on its type. +fn get_param_value(cli: &mut impl Cli, param: &Param) -> Result { + if param.is_sequence { + prompt_for_sequence_param(cli, param) + } else if param.sub_params.is_empty() { + prompt_for_primitive_param(cli, param) + } else if param.is_variant { + prompt_for_variant_param(cli, param) + } else if param.is_tuple { + prompt_for_tuple_param(cli, param) + } else { + prompt_for_composite_param(cli, param) + } +} + +// Prompt for the value when it is a sequence. +fn prompt_for_sequence_param(cli: &mut impl Cli, param: &Param) -> Result { + let input_value = cli + .input(format!( + "The value for `{}` might be too large to enter. You may enter the path to a file instead.", + param.name + )) + .placeholder(&format!( + "Enter a value of type {} or provide a file path (e.g. /path/to/your/file)", + param.type_name + )) + .interact()?; + if Path::new(&input_value).is_file() { + return std::fs::read_to_string(&input_value) + .map_err(|err| anyhow!("Failed to read file {}", err.to_string())); + } + Ok(input_value) +} + +// Prompt for the value when it is a primitive. +fn prompt_for_primitive_param(cli: &mut impl Cli, param: &Param) -> Result { + Ok(cli + .input(format!("Enter the value for the parameter: {}", param.name)) + .placeholder(&format!("Type required: {}", param.type_name)) + .interact()?) +} + +// Prompt the user to select the value of the variant parameter and recursively prompt for nested +// fields. Output example: `Id(5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY)` for the `Id` +// variant. +fn prompt_for_variant_param(cli: &mut impl Cli, param: &Param) -> Result { + let selected_variant = { + let mut select = cli.select(format!("Select the value for the parameter: {}", param.name)); + for option in ¶m.sub_params { + select = select.item(option, &option.name, &option.type_name); + } + select.interact()? + }; + + if !selected_variant.sub_params.is_empty() { + let mut field_values = Vec::new(); + for field_arg in &selected_variant.sub_params { + let field_value = prompt_for_param(cli, field_arg)?; + field_values.push(field_value); + } + Ok(format!("{}({})", selected_variant.name, field_values.join(", "))) + } else { + Ok(format!("{}()", selected_variant.name)) + } +} + +// Recursively prompt the user for all the nested fields in a composite type. +// Example of a composite definition: +// Param { +// name: "Id", +// type_name: "AccountId32 ([u8;32])", +// is_optional: false, +// sub_params: [ +// Param { +// name: "Id", +// type_name: "[u8;32]", +// is_optional: false, +// sub_params: [], +// is_variant: false +// } +// ], +// is_variant: false +// } +fn prompt_for_composite_param(cli: &mut impl Cli, param: &Param) -> Result { + let mut field_values = Vec::new(); + for field_arg in ¶m.sub_params { + let field_value = prompt_for_param(cli, field_arg)?; + if param.sub_params.len() == 1 && param.name == param.sub_params[0].name { + field_values.push(field_value); + } else { + field_values.push(format!("{}: {}", field_arg.name, field_value)); + } + } + if param.sub_params.len() == 1 && param.name == param.sub_params[0].name { + Ok(field_values.join(", ").to_string()) + } else { + Ok(format!("{{{}}}", field_values.join(", "))) + } +} + +// Recursively prompt the user for the tuple values. +fn prompt_for_tuple_param(cli: &mut impl Cli, param: &Param) -> Result { + let mut tuple_values = Vec::new(); + for tuple_param in param.sub_params.iter() { + let tuple_value = prompt_for_param(cli, tuple_param)?; + tuple_values.push(tuple_value); + } + Ok(format!("({})", tuple_values.join(", "))) +} + +// Parser to capitalize the first letter of the pallet name. +fn parse_pallet_name(name: &str) -> Result { + let mut chars = name.chars(); + match chars.next() { + Some(c) => Ok(c.to_ascii_uppercase().to_string() + chars.as_str()), + None => Err("Pallet cannot be empty".to_string()), + } +} + +// Parser to convert the function name to lowercase. +fn parse_function_name(name: &str) -> Result { + Ok(name.to_ascii_lowercase()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::cli::MockCli; + use tempfile::tempdir; + use url::Url; + + const BOB_SURI: &str = "//Bob"; + const POP_NETWORK_TESTNET_URL: &str = "wss://rpc1.paseo.popnetwork.xyz"; + const POLKADOT_NETWORK_URL: &str = "wss://polkadot-rpc.publicnode.com"; + + #[tokio::test] + async fn configure_chain_works() -> Result<()> { + let call_config = + CallChainCommand { suri: Some(DEFAULT_URI.to_string()), ..Default::default() }; + let mut cli = MockCli::new().expect_intro("Call a chain").expect_input( + "Which chain would you like to interact with?", + POP_NETWORK_TESTNET_URL.into(), + ); + let chain = call_config.configure_chain(&mut cli).await?; + assert_eq!(chain.url, Url::parse(POP_NETWORK_TESTNET_URL)?); + cli.verify() + } + + #[tokio::test] + async fn guide_user_to_call_chain_works() -> Result<()> { + let mut call_config = + CallChainCommand { pallet: Some("System".to_string()), ..Default::default() }; + + let mut cli = MockCli::new() + .expect_intro("Call a chain") + .expect_input("Which chain would you like to interact with?", POP_NETWORK_TESTNET_URL.into()) + .expect_select( + "Select the function to call:", + Some(true), + true, + Some( + [ + ("apply_authorized_upgrade".to_string(), "Provide the preimage (runtime binary) `code` for an upgrade that has been authorized. If the authorization required a version check, this call will ensure the spec name remains unchanged and that the spec version has increased. Depending on the runtime's `OnSetCode` configuration, this function may directly apply the new `code` in the same block or attempt to schedule the upgrade. All origins are allowed.".to_string()), + ("authorize_upgrade".to_string(), "Authorize an upgrade to a given `code_hash` for the runtime. The runtime can be supplied later. This call requires Root origin.".to_string()), + ("authorize_upgrade_without_checks".to_string(), "Authorize an upgrade to a given `code_hash` for the runtime. The runtime can be supplied later. WARNING: This authorizes an upgrade that will take place without any safety checks, for example that the spec name remains the same and that the version number increases. Not recommended for normal use. Use `authorize_upgrade` instead. This call requires Root origin.".to_string()), + ("kill_prefix".to_string(), "Kill all storage items with a key that starts with the given prefix. **NOTE:** We rely on the Root origin to provide us the number of subkeys under the prefix we are removing to accurately calculate the weight of this function.".to_string()), + ("kill_storage".to_string(), "Kill some items from storage.".to_string()), + ("remark".to_string(), "Make some on-chain remark. Can be executed by every `origin`.".to_string()), + ("remark_with_event".to_string(), "Make some on-chain remark and emit event.".to_string()), + ("set_code".to_string(), "Set the new runtime code.".to_string()), + ("set_code_without_checks".to_string(), "Set the new runtime code without doing any checks of the given `code`. Note that runtime upgrades will not run if this is called with a not-increasing spec version!".to_string()), + ("set_heap_pages".to_string(), "Set the number of pages in the WebAssembly environment's heap.".to_string()), + ("set_storage".to_string(), "Set some items of storage.".to_string()), + ] + .to_vec(), + ), + 5, // "remark" dispatchable function + ) + .expect_input("The value for `remark` might be too large to enter. You may enter the path to a file instead.", "0x11".into()) + .expect_confirm("Would you like to dispatch this function call with `Root` origin?", true) + .expect_input("Signer of the extrinsic:", "//Bob".into()); + + let chain = call_config.configure_chain(&mut cli).await?; + assert_eq!(chain.url, Url::parse(POP_NETWORK_TESTNET_URL)?); + + let call_chain = call_config.configure_call(&chain, &mut cli)?; + assert_eq!(call_chain.function.pallet, "System"); + assert_eq!(call_chain.function.name, "remark"); + assert_eq!(call_chain.args, ["0x11".to_string()].to_vec()); + assert_eq!(call_chain.suri, "//Bob"); + assert!(call_chain.sudo); + assert_eq!(call_chain.display(&chain), "pop call chain --pallet System --function remark --args \"0x11\" --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Bob --sudo"); + cli.verify() + } + + #[tokio::test] + async fn guide_user_to_configure_predefined_action_works() -> Result<()> { + let mut call_config = CallChainCommand::default(); + + let mut cli = MockCli::new().expect_intro("Call a chain").expect_input( + "Which chain would you like to interact with?", + POLKADOT_NETWORK_URL.into(), + ); + let chain = call_config.configure_chain(&mut cli).await?; + assert_eq!(chain.url, Url::parse(POLKADOT_NETWORK_URL)?); + cli.verify()?; + + let mut cli = MockCli::new() + .expect_select( + "What would you like to do?", + Some(true), + true, + Some( + supported_actions(&chain.pallets) + .into_iter() + .map(|action| { + (action.description().to_string(), action.pallet_name().to_string()) + }) + .chain(std::iter::once(( + "All".to_string(), + "Explore all pallets and functions".to_string(), + ))) + .collect::>(), + ), + 1, // "Purchase on-demand coretime" action + ) + .expect_input("Enter the value for the parameter: max_amount", "10000".into()) + .expect_input("Enter the value for the parameter: para_id", "2000".into()) + .expect_input("Signer of the extrinsic:", BOB_SURI.into()); + + let call_chain = call_config.configure_call(&chain, &mut cli)?; + + assert_eq!(call_chain.function.pallet, "OnDemand"); + assert_eq!(call_chain.function.name, "place_order_allow_death"); + assert_eq!(call_chain.args, ["10000".to_string(), "2000".to_string()].to_vec()); + assert_eq!(call_chain.suri, "//Bob"); + assert!(!call_chain.sudo); + assert_eq!(call_chain.display(&chain), "pop call chain --pallet OnDemand --function place_order_allow_death --args \"10000\" \"2000\" --url wss://polkadot-rpc.publicnode.com/ --suri //Bob"); + cli.verify() + } + + #[tokio::test] + async fn prepare_extrinsic_works() -> Result<()> { + let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; + let mut call_config = Call { + function: Function { + pallet: "WrongName".to_string(), + name: "WrongName".to_string(), + ..Default::default() + }, + args: vec!["0x11".to_string()].to_vec(), + suri: DEFAULT_URI.to_string(), + skip_confirm: false, + sudo: false, + }; + let mut cli = MockCli::new(); + // Error, wrong name of the pallet. + assert!(matches!( + call_config.prepare_extrinsic(&client, &mut cli), + Err(message) + if message.to_string().contains("Failed to encode call data. Metadata Error: Pallet with name WrongName not found"))); + let pallets = parse_chain_metadata(&client)?; + call_config.function.pallet = "System".to_string(); + // Error, wrong name of the function. + assert!(matches!( + call_config.prepare_extrinsic(&client, &mut cli), + Err(message) + if message.to_string().contains("Failed to encode call data. Metadata Error: Call with name WrongName not found"))); + // Success, pallet and dispatchable function specified. + cli = MockCli::new().expect_info("Encoded call data: 0x00000411"); + call_config.function = find_dispatchable_by_name(&pallets, "System", "remark")?.clone(); + let xt = call_config.prepare_extrinsic(&client, &mut cli)?; + assert_eq!(xt.call_name(), "remark"); + assert_eq!(xt.pallet_name(), "System"); + + // Prepare extrinsic wrapped in sudo works. + cli = MockCli::new().expect_info("Encoded call data: 0x0f0000000411"); + call_config.sudo = true; + call_config.prepare_extrinsic(&client, &mut cli)?; + + cli.verify() + } + + #[tokio::test] + async fn user_cancel_submit_extrinsic_works() -> Result<()> { + let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; + let pallets = parse_chain_metadata(&client)?; + let mut call_config = Call { + function: find_dispatchable_by_name(&pallets, "System", "remark")?.clone(), + args: vec!["0x11".to_string()].to_vec(), + suri: DEFAULT_URI.to_string(), + skip_confirm: false, + sudo: false, + }; + let mut cli = MockCli::new() + .expect_confirm("Do you want to submit the extrinsic?", false) + .expect_outro_cancel("Extrinsic for `remark` was not submitted."); + let xt = call_config.prepare_extrinsic(&client, &mut cli)?; + call_config.submit_extrinsic(&client, xt, &mut cli).await?; + + cli.verify() + } + + #[tokio::test] + async fn user_cancel_submit_extrinsic_from_call_data_works() -> Result<()> { + let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; + let call_config = CallChainCommand { + pallet: None, + function: None, + args: vec![].to_vec(), + url: Some(Url::parse("wss://rpc1.paseo.popnetwork.xyz")?), + suri: None, + skip_confirm: false, + call_data: Some("0x00000411".to_string()), + sudo: false, + }; + let mut cli = MockCli::new() + .expect_input("Signer of the extrinsic:", "//Bob".into()) + .expect_confirm("Do you want to submit the extrinsic?", false) + .expect_outro_cancel("Extrinsic with call data 0x00000411 was not submitted."); + call_config + .submit_extrinsic_from_call_data(&client, "0x00000411", &mut cli) + .await?; + + cli.verify() + } + + #[tokio::test] + async fn configure_sudo_works() -> Result<()> { + // Test when sudo pallet doesn't exist. + let mut call_config = CallChainCommand { + pallet: None, + function: None, + args: vec![].to_vec(), + url: Some(Url::parse("wss://polkadot-rpc.publicnode.com")?), + suri: Some("//Alice".to_string()), + skip_confirm: false, + call_data: Some("0x00000411".to_string()), + sudo: true, + }; + let mut cli = MockCli::new() + .expect_intro("Call a chain") + .expect_warning("NOTE: sudo is not supported by the chain. Ignoring `--sudo` flag."); + let chain = call_config.configure_chain(&mut cli).await?; + call_config.configure_sudo(&chain, &mut cli)?; + assert!(!call_config.sudo); + cli.verify()?; + + // Test when sudo pallet exist. + cli = MockCli::new().expect_intro("Call a chain").expect_confirm( + "Would you like to dispatch this function call with `Root` origin?", + true, + ); + call_config.url = Some(Url::parse("wss://rpc1.paseo.popnetwork.xyz")?); + let chain = call_config.configure_chain(&mut cli).await?; + call_config.configure_sudo(&chain, &mut cli)?; + assert!(call_config.sudo); + cli.verify() + } + + #[test] + fn reset_for_new_call_works() -> Result<()> { + let mut call_config = CallChainCommand { + pallet: Some("System".to_string()), + function: Some("remark".to_string()), + args: vec!["0x11".to_string()].to_vec(), + url: Some(Url::parse(POP_NETWORK_TESTNET_URL)?), + suri: Some(DEFAULT_URI.to_string()), + skip_confirm: false, + call_data: None, + sudo: true, + }; + call_config.reset_for_new_call(); + assert_eq!(call_config.pallet, None); + assert_eq!(call_config.function, None); + assert_eq!(call_config.args.len(), 0); + assert!(!call_config.sudo); + Ok(()) + } + + #[test] + fn requires_user_input_works() -> Result<()> { + let mut call_config = CallChainCommand { + pallet: Some("System".to_string()), + function: Some("remark".to_string()), + args: vec!["0x11".to_string()].to_vec(), + url: Some(Url::parse(POP_NETWORK_TESTNET_URL)?), + suri: Some(DEFAULT_URI.to_string()), + skip_confirm: false, + call_data: None, + sudo: false, + }; + assert!(!call_config.requires_user_input()); + call_config.pallet = None; + assert!(call_config.requires_user_input()); + Ok(()) + } + + #[test] + fn expand_file_arguments_works() -> Result<()> { + let mut call_config = CallChainCommand { + pallet: Some("Registrar".to_string()), + function: Some("register".to_string()), + args: vec!["2000".to_string(), "0x1".to_string(), "0x12".to_string()].to_vec(), + url: Some(Url::parse(POP_NETWORK_TESTNET_URL)?), + suri: Some(DEFAULT_URI.to_string()), + call_data: None, + skip_confirm: false, + sudo: false, + }; + assert_eq!( + call_config.expand_file_arguments()?, + vec!["2000".to_string(), "0x1".to_string(), "0x12".to_string()] + ); + // Temporal file for testing when the input is a file. + let temp_dir = tempdir()?; + let genesis_file = temp_dir.path().join("genesis_file.json"); + std::fs::write(&genesis_file, "genesis_file_content")?; + let wasm_file = temp_dir.path().join("wasm_file.json"); + std::fs::write(&wasm_file, "wasm_file_content")?; + call_config.args = vec![ + "2000".to_string(), + genesis_file.display().to_string(), + wasm_file.display().to_string(), + ]; + assert_eq!( + call_config.expand_file_arguments()?, + vec![ + "2000".to_string(), + "genesis_file_content".to_string(), + "wasm_file_content".to_string() + ] + ); + Ok(()) + } + + #[test] + fn display_message_works() -> Result<()> { + let mut cli = MockCli::new().expect_outro(&"Call completed successfully!"); + display_message("Call completed successfully!", true, &mut cli)?; + cli.verify()?; + let mut cli = MockCli::new().expect_outro_cancel("Call failed."); + display_message("Call failed.", false, &mut cli)?; + cli.verify() + } + + #[tokio::test] + async fn prompt_predefined_actions_works() -> Result<()> { + let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; + let pallets = parse_chain_metadata(&client)?; + let mut cli = MockCli::new().expect_select( + "What would you like to do?", + Some(true), + true, + Some( + supported_actions(&pallets) + .into_iter() + .map(|action| { + (action.description().to_string(), action.pallet_name().to_string()) + }) + .chain(std::iter::once(( + "All".to_string(), + "Explore all pallets and functions".to_string(), + ))) + .collect::>(), + ), + 2, // "Mint an Asset" action + ); + let action = prompt_predefined_actions(&pallets, &mut cli)?; + assert_eq!(action, Some(Action::MintAsset)); + cli.verify() + } + + #[tokio::test] + async fn prompt_for_param_works() -> Result<()> { + let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; + let pallets = parse_chain_metadata(&client)?; + // Using NFT mint dispatchable function to test the majority of sub-functions. + let function = find_dispatchable_by_name(&pallets, "Nfts", "mint")?; + let mut cli = MockCli::new() + .expect_input("Enter the value for the parameter: collection", "0".into()) + .expect_input("Enter the value for the parameter: item", "0".into()) + .expect_select( + "Select the value for the parameter: mint_to", + Some(true), + true, + Some( + [ + ("Id".to_string(), "".to_string()), + ("Index".to_string(), "".to_string()), + ("Raw".to_string(), "".to_string()), + ("Address32".to_string(), "".to_string()), + ("Address20".to_string(), "".to_string()), + ] + .to_vec(), + ), + 0, // "Id" action + ) + .expect_input( + "Enter the value for the parameter: Id", + "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty".into(), + ) + .expect_confirm( + "Do you want to provide a value for the optional parameter: witness_data?", + true, + ) + .expect_confirm( + "Do you want to provide a value for the optional parameter: owned_item?", + false, + ) + .expect_confirm( + "Do you want to provide a value for the optional parameter: mint_price?", + true, + ) + .expect_input("Enter the value for the parameter: mint_price", "1000".into()); + + // Test all the function params. + let mut params: Vec = Vec::new(); + for param in &function.params { + params.push(prompt_for_param(&mut cli, ¶m)?); + } + assert_eq!(params.len(), 4); + assert_eq!(params[0], "0".to_string()); // collection: test primitive + assert_eq!(params[1], "0".to_string()); // item: test primitive + assert_eq!(params[2], "Id(5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty)".to_string()); // mint_to: test variant + assert_eq!(params[3], "Some({owned_item: None(), mint_price: Some(1000)})".to_string()); // witness_data: test composite + cli.verify()?; + + // Using Scheduler set_retry dispatchable function to test the tuple params. + let function = find_dispatchable_by_name(&pallets, "Scheduler", "set_retry")?; + let mut cli = MockCli::new() + .expect_input( + "Enter the value for the parameter: Index 0 of the tuple task", + "0".into(), + ) + .expect_input( + "Enter the value for the parameter: Index 1 of the tuple task", + "0".into(), + ) + .expect_input("Enter the value for the parameter: retries", "0".into()) + .expect_input("Enter the value for the parameter: period", "0".into()); + + // Test all the extrinsic params + let mut params: Vec = Vec::new(); + for param in &function.params { + params.push(prompt_for_param(&mut cli, ¶m)?); + } + assert_eq!(params.len(), 3); + assert_eq!(params[0], "(0, 0)".to_string()); // task: test tuples + assert_eq!(params[1], "0".to_string()); // retries: test primitive + assert_eq!(params[2], "0".to_string()); // period: test primitive + cli.verify()?; + + // Using System remark dispatchable function to test the sequence params. + let function = find_dispatchable_by_name(&pallets, "System", "remark")?; + // Temporal file for testing the input. + let temp_dir = tempdir()?; + let file = temp_dir.path().join("file.json"); + std::fs::write(&file, "testing")?; + + let mut cli = MockCli::new() + .expect_input( + "The value for `remark` might be too large to enter. You may enter the path to a file instead.", + file.display().to_string(), + ); + + // Test all the function params + let mut params: Vec = Vec::new(); + for param in &function.params { + params.push(prompt_for_param(&mut cli, ¶m)?); + } + assert_eq!(params.len(), 1); + assert_eq!(params[0], "testing".to_string()); // remark: test sequence from file + cli.verify() + } + + #[test] + fn parse_pallet_name_works() -> Result<()> { + assert_eq!(parse_pallet_name("system").unwrap(), "System"); + assert_eq!(parse_pallet_name("balances").unwrap(), "Balances"); + assert_eq!(parse_pallet_name("nfts").unwrap(), "Nfts"); + Ok(()) + } + + #[test] + fn parse_function_name_works() -> Result<()> { + assert_eq!(parse_function_name("Remark").unwrap(), "remark"); + assert_eq!(parse_function_name("Force_transfer").unwrap(), "force_transfer"); + assert_eq!(parse_function_name("MINT").unwrap(), "mint"); + Ok(()) + } +} diff --git a/crates/pop-cli/src/commands/call/mod.rs b/crates/pop-cli/src/commands/call/mod.rs index 6cb137f4b..dd1a2318c 100644 --- a/crates/pop-cli/src/commands/call/mod.rs +++ b/crates/pop-cli/src/commands/call/mod.rs @@ -2,6 +2,8 @@ use clap::{Args, Subcommand}; +#[cfg(feature = "parachain")] +pub(crate) mod chain; #[cfg(feature = "contract")] pub(crate) mod contract; @@ -13,9 +15,13 @@ pub(crate) struct CallArgs { pub command: Command, } -/// Call a smart contract. +/// Call a chain or a smart contract. #[derive(Subcommand)] pub(crate) enum Command { + /// Call a chain + #[cfg(feature = "parachain")] + #[clap(alias = "p", visible_aliases = ["parachain"])] + Chain(chain::CallChainCommand), /// Call a contract #[cfg(feature = "contract")] #[clap(alias = "c")] diff --git a/crates/pop-cli/src/commands/mod.rs b/crates/pop-cli/src/commands/mod.rs index aa385f28e..9af86e3f4 100644 --- a/crates/pop-cli/src/commands/mod.rs +++ b/crates/pop-cli/src/commands/mod.rs @@ -26,9 +26,9 @@ pub(crate) enum Command { #[clap(alias = "b", about = about_build())] #[cfg(any(feature = "parachain", feature = "contract"))] Build(build::BuildArgs), - /// Call a smart contract. + /// Call a chain or a smart contract. #[clap(alias = "c")] - #[cfg(feature = "contract")] + #[cfg(any(feature = "parachain", feature = "contract"))] Call(call::CallArgs), /// Launch a local network or deploy a smart contract. #[clap(alias = "u")] @@ -96,8 +96,11 @@ impl Command { build::Command::Spec(cmd) => cmd.execute().await.map(|_| Value::Null), }, }, - #[cfg(feature = "contract")] + #[cfg(any(feature = "parachain", feature = "contract"))] Self::Call(args) => match args.command { + #[cfg(feature = "parachain")] + call::Command::Chain(cmd) => cmd.execute().await.map(|_| Value::Null), + #[cfg(feature = "contract")] call::Command::Contract(cmd) => cmd.execute().await.map(|_| Value::Null), }, #[cfg(any(feature = "parachain", feature = "contract"))] diff --git a/crates/pop-cli/tests/parachain.rs b/crates/pop-cli/tests/parachain.rs index b9baaac58..67132e6d4 100644 --- a/crates/pop-cli/tests/parachain.rs +++ b/crates/pop-cli/tests/parachain.rs @@ -2,13 +2,13 @@ use anyhow::Result; use assert_cmd::{cargo::cargo_bin, Command}; -use pop_common::templates::Template; +use pop_common::{find_free_port, templates::Template}; use pop_parachains::Parachain; use std::{fs, path::Path, process::Command as Cmd}; use strum::VariantArray; use tokio::time::{sleep, Duration}; -/// Test the parachain lifecycle: new, build, up +/// Test the parachain lifecycle: new, build, up, call. #[tokio::test] async fn parachain_lifecycle() -> Result<()> { let temp = tempfile::tempdir().unwrap(); @@ -92,14 +92,86 @@ async fn parachain_lifecycle() -> Result<()> { assert!(content.contains("\"protocolId\": \"pop-protocol\"")); assert!(content.contains("\"id\": \"local_testnet\"")); - // pop up parachain -p "./test_parachain" + // Overwrite the config file to manually set the port to test pop call parachain. + let network_toml_path = temp_parachain_dir.join("network.toml"); + fs::create_dir_all(&temp_parachain_dir)?; + let random_port = find_free_port(); + let localhost_url = format!("ws://127.0.0.1:{}", random_port); + fs::write( + &network_toml_path, + format!( + r#"[relaychain] +chain = "paseo-local" + +[[relaychain.nodes]] +name = "alice" +rpc_port = {} +validator = true + +[[relaychain.nodes]] +name = "bob" +validator = true + +[[parachains]] +id = 2000 +default_command = "./target/release/parachain-template-node" + +[[parachains.collators]] +name = "collator-01" +"#, + random_port + ), + )?; + + // `pop up parachain -f ./network.toml --skip-confirm` let mut cmd = Cmd::new(cargo_bin("pop")) .current_dir(&temp_parachain_dir) .args(&["up", "parachain", "-f", "./network.toml", "--skip-confirm"]) .spawn() .unwrap(); - // If after 20 secs is still running probably execution is ok, or waiting for user response - sleep(Duration::from_secs(20)).await; + + // Wait for the networks to initialize. Increased timeout to accommodate CI environment delays. + sleep(Duration::from_secs(50)).await; + + // `pop call chain --pallet System --function remark --args "0x11" --url + // ws://127.0.0.1:random_port --suri //Alice --skip-confirm` + Command::cargo_bin("pop") + .unwrap() + .args(&[ + "call", + "chain", + "--pallet", + "System", + "--function", + "remark", + "--args", + "0x11", + "--url", + &localhost_url, + "--suri", + "//Alice", + "--skip-confirm", + ]) + .assert() + .success(); + + // pop call chain --call 0x00000411 --url ws://127.0.0.1:random_port --suri //Alice + // --skip-confirm + Command::cargo_bin("pop") + .unwrap() + .args(&[ + "call", + "chain", + "--call", + "0x00000411", + "--url", + &localhost_url, + "--suri", + "//Alice", + "--skip-confirm", + ]) + .assert() + .success(); assert!(cmd.try_wait().unwrap().is_none(), "the process should still be running"); // Stop the process diff --git a/crates/pop-common/Cargo.toml b/crates/pop-common/Cargo.toml index 87074a601..03e5035d7 100644 --- a/crates/pop-common/Cargo.toml +++ b/crates/pop-common/Cargo.toml @@ -16,10 +16,13 @@ git2.workspace = true git2_credentials.workspace = true regex.workspace = true reqwest.workspace = true +scale-info.workspace = true serde_json.workspace = true serde.workspace = true strum.workspace = true strum_macros.workspace = true +subxt.workspace = true +subxt-signer.workspace = true tar.workspace = true tempfile.workspace = true thiserror.workspace = true diff --git a/crates/pop-common/src/build.rs b/crates/pop-common/src/build.rs index f871078ad..85d1eba5a 100644 --- a/crates/pop-common/src/build.rs +++ b/crates/pop-common/src/build.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: GPL-3.0 + use std::{ fmt, path::{Path, PathBuf}, diff --git a/crates/pop-common/src/errors.rs b/crates/pop-common/src/errors.rs index 02fb0c9b1..ceef11500 100644 --- a/crates/pop-common/src/errors.rs +++ b/crates/pop-common/src/errors.rs @@ -3,6 +3,7 @@ use crate::{sourcing, templates}; use thiserror::Error; +/// Represents the various errors that can occur in the crate. #[derive(Error, Debug)] pub enum Error { #[error("Anyhow error: {0}")] @@ -13,12 +14,19 @@ pub enum Error { Git(String), #[error("IO error: {0}")] IO(#[from] std::io::Error), - #[error("Failed to get manifest path: {0}")] - ManifestPath(String), + /// An error occurred while attempting to create a keypair from the provided URI. + #[error("Failed to create keypair from URI: {0}")] + KeyPairCreation(String), #[error("Manifest error: {0}")] ManifestError(#[from] cargo_toml::Error), + /// An error occurred while attempting to retrieve the manifest path. + #[error("Failed to get manifest path: {0}")] + ManifestPath(String), #[error("ParseError error: {0}")] ParseError(#[from] url::ParseError), + /// An error occurred while parsing the provided secret URI. + #[error("Failed to parse secret URI: {0}")] + ParseSecretURI(String), #[error("SourceError error: {0}")] SourceError(#[from] sourcing::Error), #[error("TemplateError error: {0}")] diff --git a/crates/pop-common/src/lib.rs b/crates/pop-common/src/lib.rs index b70dd121b..6f1991960 100644 --- a/crates/pop-common/src/lib.rs +++ b/crates/pop-common/src/lib.rs @@ -1,20 +1,32 @@ -pub mod build; -pub mod errors; -pub mod git; -pub mod helpers; -pub mod manifest; -pub mod polkadot_sdk; -pub mod sourcing; -pub mod templates; +// SPDX-License-Identifier: GPL-3.0 + +use std::net::TcpListener; pub use build::Profile; pub use errors::Error; pub use git::{Git, GitHub, Release}; pub use helpers::{get_project_name_from_path, prefix_with_current_dir_if_needed, replace_in_file}; pub use manifest::{add_crate_to_workspace, find_workspace_toml}; +pub use metadata::format_type; +pub use signer::create_signer; pub use sourcing::set_executable_permission; +pub use subxt::{Config, PolkadotConfig as DefaultConfig}; +pub use subxt_signer::sr25519::Keypair; pub use templates::extractor::extract_template_files; +pub mod build; +pub mod errors; +pub mod git; +pub mod helpers; +pub mod manifest; +/// Provides functionality for formatting and resolving metadata types. +pub mod metadata; +pub mod polkadot_sdk; +/// Provides functionality for creating a signer from a secret URI. +pub mod signer; +pub mod sourcing; +pub mod templates; + static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); /// Trait for observing status updates. @@ -52,6 +64,15 @@ pub fn target() -> Result<&'static str, Error> { Err(Error::UnsupportedPlatform { arch: ARCH, os: OS }) } +/// Finds an available port by binding to port 0 and retrieving the assigned port. +pub fn find_free_port() -> u16 { + TcpListener::bind("127.0.0.1:0") + .expect("Failed to bind to an available port") + .local_addr() + .expect("Failed to retrieve local address") + .port() +} + #[cfg(test)] mod test { use super::*; @@ -71,4 +92,14 @@ mod test { assert_eq!(target()?, target_expected); Ok(()) } + + #[test] + fn find_free_port_works() -> Result<()> { + let port = find_free_port(); + let addr = format!("127.0.0.1:{}", port); + // Constructs the TcpListener from the above port + let listener = TcpListener::bind(&addr); + assert!(listener.is_ok()); + Ok(()) + } } diff --git a/crates/pop-common/src/metadata.rs b/crates/pop-common/src/metadata.rs new file mode 100644 index 000000000..48f46da28 --- /dev/null +++ b/crates/pop-common/src/metadata.rs @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: GPL-3.0 + +use scale_info::{form::PortableForm, PortableRegistry, Type, TypeDef, TypeDefPrimitive}; + +/// Formats a specified type, using the registry to output its full type representation. +/// +/// # Arguments +/// * `ty`: The type to format, containing metadata like name, parameters, and definition. +/// * `registry`: The registry used to resolve type dependencies and provides details for complex +/// types. +pub fn format_type(ty: &Type, registry: &PortableRegistry) -> String { + let mut name = ty + .path + .segments + .last() + .map(|s| s.to_owned()) + .unwrap_or_else(|| ty.path.to_string()); + + if !ty.type_params.is_empty() { + let params: Vec<_> = ty + .type_params + .iter() + .filter_map(|p| { + if let Some(ty) = p.ty { + registry.resolve(ty.id) + } else { + None // Ignore if p.ty is None + } + }) + .map(|t| format_type(t, registry)) + .collect(); + name = format!("{name}<{}>", params.join(",")); + } + name = format!( + "{name}{}", + match &ty.type_def { + TypeDef::Composite(composite) => { + if composite.fields.is_empty() { + return "".to_string(); + } + + let mut named = false; + let fields: Vec<_> = composite + .fields + .iter() + .filter_map(|f| match f.name.as_ref() { + None => registry.resolve(f.ty.id).map(|t| format_type(t, registry)), + Some(field) => { + named = true; + f.type_name.as_ref().map(|t| format!("{field}: {t}")) + }, + }) + .collect(); + match named { + true => format!(" {{ {} }}", fields.join(", ")), + false => format!(" ({})", fields.join(", ")), + } + }, + TypeDef::Variant(variant) => { + let variants: Vec<_> = variant + .variants + .iter() + .map(|v| { + if v.fields.is_empty() { + return v.name.clone(); + } + + let name = v.name.as_str(); + let mut named = false; + let fields: Vec<_> = v + .fields + .iter() + .filter_map(|f| match f.name.as_ref() { + None => registry.resolve(f.ty.id).map(|t| format_type(t, registry)), + Some(field) => { + named = true; + f.type_name.as_ref().map(|t| format!("{field}: {t}")) + }, + }) + .collect(); + format!( + "{name}{}", + match named { + true => format!("{{ {} }}", fields.join(", ")), + false => format!("({})", fields.join(", ")), + } + ) + }) + .collect(); + format!(": {}", variants.join(", ")) + }, + TypeDef::Sequence(sequence) => { + format!( + "[{}]", + format_type( + registry.resolve(sequence.type_param.id).expect("sequence type not found"), + registry + ) + ) + }, + TypeDef::Array(array) => { + format!( + "[{};{}]", + format_type( + registry.resolve(array.type_param.id).expect("array type not found"), + registry + ), + array.len + ) + }, + TypeDef::Tuple(tuple) => { + let fields: Vec<_> = tuple + .fields + .iter() + .filter_map(|p| registry.resolve(p.id)) + .map(|t| format_type(t, registry)) + .collect(); + format!("({})", fields.join(",")) + }, + TypeDef::Primitive(primitive) => { + use TypeDefPrimitive::*; + match primitive { + Bool => "bool", + Char => "char", + Str => "str", + U8 => "u8", + U16 => "u16", + U32 => "u32", + U64 => "u64", + U128 => "u128", + U256 => "u256", + I8 => "i8", + I16 => "i16", + I32 => "i32", + I64 => "i64", + I128 => "i128", + I256 => "i256", + } + .to_string() + }, + TypeDef::Compact(compact) => { + format!( + "Compact<{}>", + format_type( + registry.resolve(compact.type_param.id).expect("compact type not found"), + registry + ) + ) + }, + TypeDef::BitSequence(_) => { + "BitSequence".to_string() + }, + } + ); + + name +} + +#[cfg(test)] +mod tests { + use super::*; + use anyhow::Result; + use subxt::{OnlineClient, SubstrateConfig}; + + const POP_NETWORK_TESTNET_URL: &str = "wss://rpc1.paseo.popnetwork.xyz"; + + #[tokio::test] + async fn format_type_works() -> Result<()> { + let client = OnlineClient::::from_url(POP_NETWORK_TESTNET_URL).await?; + let metadata = client.metadata(); + let registry = metadata.types(); + + // Validate `Nfts::mint` extrinsic types cover most of cases. + let nfts_mint_extrinsic = + metadata.pallet_by_name("Nfts").unwrap().call_variant_by_name("mint").unwrap(); + let nfts_mint_types: Vec = nfts_mint_extrinsic + .fields + .iter() + .map(|field| { + let type_info = registry.resolve(field.ty.id).unwrap(); + format_type(&type_info, registry) + }) + .collect(); + assert_eq!(nfts_mint_types.len(), 4); + assert_eq!(nfts_mint_types[0], "u32"); // collection + assert_eq!(nfts_mint_types[1], "u32"); // item + assert_eq!(nfts_mint_types[2], "MultiAddress: Id(AccountId32 ([u8;32])), Index(Compact<()>), Raw([u8]), Address32([u8;32]), Address20([u8;20])"); // mint_to + assert_eq!(nfts_mint_types[3], "Option { owned_item: Option, mint_price: Option }>: None, Some(MintWitness { owned_item: Option, mint_price: Option })"); // witness_data + + // Validate `System::remark` to cover Sequences. + let system_remark_extrinsic = metadata + .pallet_by_name("System") + .unwrap() + .call_variant_by_name("remark") + .unwrap(); + let system_remark_types: Vec = system_remark_extrinsic + .fields + .iter() + .map(|field| { + let type_info = registry.resolve(field.ty.id).unwrap(); + format_type(&type_info, registry) + }) + .collect(); + assert_eq!(system_remark_types.len(), 1); + assert_eq!(system_remark_types[0], "[u8]"); // remark + + // Extrinsic Scheduler::set_retry, cover tuples. + let scheduler_set_retry_extrinsic = metadata + .pallet_by_name("Scheduler") + .unwrap() + .call_variant_by_name("set_retry") + .unwrap(); + let scheduler_set_retry_types: Vec = scheduler_set_retry_extrinsic + .fields + .iter() + .map(|field| { + let type_info = registry.resolve(field.ty.id).unwrap(); + format_type(&type_info, registry) + }) + .collect(); + assert_eq!(scheduler_set_retry_types.len(), 3); + assert_eq!(scheduler_set_retry_types[0], "(u32,u32)"); // task + assert_eq!(scheduler_set_retry_types[1], "u8"); // retries + assert_eq!(scheduler_set_retry_types[2], "u32"); // period + + Ok(()) + } +} diff --git a/crates/pop-contracts/src/utils/signer.rs b/crates/pop-common/src/signer.rs similarity index 52% rename from crates/pop-contracts/src/utils/signer.rs rename to crates/pop-common/src/signer.rs index 51fc44b0e..e542d0417 100644 --- a/crates/pop-contracts/src/utils/signer.rs +++ b/crates/pop-common/src/signer.rs @@ -1,24 +1,19 @@ // SPDX-License-Identifier: GPL-3.0 use crate::errors::Error; -use contract_build::util::decode_hex; -use sp_core::Bytes; use subxt_signer::{sr25519::Keypair, SecretUri}; -/// Create a Signer from a secret URI. -pub(crate) fn create_signer(suri: &str) -> Result { +/// Create a keypair from a secret URI. +/// +/// # Arguments +/// `suri` - Secret URI string used to generate the `Keypair`. +pub fn create_signer(suri: &str) -> Result { let uri = ::from_str(suri) .map_err(|e| Error::ParseSecretURI(format!("{}", e)))?; let keypair = Keypair::from_uri(&uri).map_err(|e| Error::KeyPairCreation(format!("{}", e)))?; Ok(keypair) } -/// Parse hex encoded bytes. -pub fn parse_hex_bytes(input: &str) -> Result { - let bytes = decode_hex(input).map_err(|e| Error::HexParsing(format!("{}", e)))?; - Ok(bytes.into()) -} - #[cfg(test)] mod tests { use super::*; @@ -39,18 +34,4 @@ mod tests { assert!(matches!(create_signer("11111"), Err(Error::KeyPairCreation(..)))); Ok(()) } - - #[test] - fn parse_hex_bytes_works() -> Result<(), Error> { - let input_in_hex = "48656c6c6f"; - let result = parse_hex_bytes(input_in_hex)?; - assert_eq!(result, Bytes(vec![72, 101, 108, 108, 111])); - Ok(()) - } - - #[test] - fn parse_hex_bytes_fails_wrong_input() -> Result<(), Error> { - assert!(matches!(parse_hex_bytes("wronghexvalue"), Err(Error::HexParsing(..)))); - Ok(()) - } } diff --git a/crates/pop-contracts/Cargo.toml b/crates/pop-contracts/Cargo.toml index 3d4f15fb1..b3ab82cb8 100644 --- a/crates/pop-contracts/Cargo.toml +++ b/crates/pop-contracts/Cargo.toml @@ -27,8 +27,6 @@ sp-core.workspace = true sp-weights.workspace = true strum.workspace = true strum_macros.workspace = true -subxt-signer.workspace = true -subxt.workspace = true # cargo-contracts contract-build.workspace = true diff --git a/crates/pop-contracts/src/build.rs b/crates/pop-contracts/src/build.rs index dcbec25a8..df9cc4a3f 100644 --- a/crates/pop-contracts/src/build.rs +++ b/crates/pop-contracts/src/build.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -use crate::{errors::Error, utils::helpers::get_manifest_path}; +use crate::{errors::Error, utils::get_manifest_path}; pub use contract_build::Verbosity; use contract_build::{execute, BuildMode, BuildResult, ExecuteArgs}; use std::path::Path; diff --git a/crates/pop-contracts/src/call.rs b/crates/pop-contracts/src/call.rs index 1886f3be2..03effcb0a 100644 --- a/crates/pop-contracts/src/call.rs +++ b/crates/pop-contracts/src/call.rs @@ -3,9 +3,9 @@ use crate::{ errors::Error, utils::{ - helpers::{get_manifest_path, parse_account, parse_balance}, + get_manifest_path, metadata::{process_function_args, FunctionType}, - signer::create_signer, + parse_account, parse_balance, }, }; use anyhow::Context; @@ -15,10 +15,9 @@ use contract_extrinsics::{ ExtrinsicOptsBuilder, TokenMetadata, }; use ink_env::{DefaultEnvironment, Environment}; +use pop_common::{create_signer, Config, DefaultConfig, Keypair}; use sp_weights::Weight; use std::path::PathBuf; -use subxt::{Config, PolkadotConfig as DefaultConfig}; -use subxt_signer::sr25519::Keypair; use url::Url; /// Attributes for the `call` command. @@ -180,10 +179,10 @@ mod tests { use crate::{ contracts_node_generator, dry_run_gas_estimate_instantiate, errors::Error, instantiate_smart_contract, mock_build_process, new_environment, run_contracts_node, - set_up_deployment, testing::find_free_port, UpOpts, + set_up_deployment, UpOpts, }; use anyhow::Result; - use pop_common::set_executable_permission; + use pop_common::{find_free_port, set_executable_permission}; use sp_core::Bytes; use std::{env, process::Command, time::Duration}; use tokio::time::sleep; diff --git a/crates/pop-contracts/src/errors.rs b/crates/pop-contracts/src/errors.rs index b59031a06..e7ddec21b 100644 --- a/crates/pop-contracts/src/errors.rs +++ b/crates/pop-contracts/src/errors.rs @@ -3,13 +3,14 @@ use pop_common::sourcing::Error as SourcingError; use thiserror::Error; +/// Represents the various errors that can occur in the crate. #[derive(Error, Debug)] #[allow(clippy::enum_variant_names)] pub enum Error { - #[error("Anyhow error: {0}")] - AnyhowError(#[from] anyhow::Error), #[error("Failed to parse account address: {0}")] AccountAddressParsing(String), + #[error("Anyhow error: {0}")] + AnyhowError(#[from] anyhow::Error), #[error("Failed to parse balance: {0}")] BalanceParsing(String), #[error("{0}")] @@ -38,8 +39,6 @@ pub enum Error { InvalidName(String), #[error("IO error: {0}")] IO(#[from] std::io::Error), - #[error("Failed to create keypair from URI: {0}")] - KeyPairCreation(String), #[error("Failed to get manifest path: {0}")] ManifestPath(String), #[error("Argument {0} is required")] @@ -48,8 +47,6 @@ pub enum Error { NewContract(String), #[error("ParseError error: {0}")] ParseError(#[from] url::ParseError), - #[error("Failed to parse secret URI: {0}")] - ParseSecretURI(String), #[error("The `Repository` property is missing from the template variant")] RepositoryMissing, #[error("Sourcing error {0}")] diff --git a/crates/pop-contracts/src/lib.rs b/crates/pop-contracts/src/lib.rs index 005ec91ac..c5e76e2f1 100644 --- a/crates/pop-contracts/src/lib.rs +++ b/crates/pop-contracts/src/lib.rs @@ -20,13 +20,12 @@ pub use new::{create_smart_contract, is_valid_contract_name}; pub use node::{contracts_node_generator, is_chain_alive, run_contracts_node}; pub use templates::{Contract, ContractType}; pub use test::{test_e2e_smart_contract, test_smart_contract}; -pub use testing::{find_free_port, mock_build_process, new_environment}; +pub use testing::{mock_build_process, new_environment}; pub use up::{ dry_run_gas_estimate_instantiate, dry_run_upload, instantiate_smart_contract, set_up_deployment, set_up_upload, upload_smart_contract, UpOpts, }; pub use utils::{ - helpers::parse_account, metadata::{get_messages, ContractFunction}, - signer::parse_hex_bytes, + parse_account, parse_hex_bytes, }; diff --git a/crates/pop-contracts/src/new.rs b/crates/pop-contracts/src/new.rs index 8e11fbd16..6d4d7f125 100644 --- a/crates/pop-contracts/src/new.rs +++ b/crates/pop-contracts/src/new.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -use crate::{errors::Error, utils::helpers::canonicalized_path, Contract}; +use crate::{errors::Error, utils::canonicalized_path, Contract}; use anyhow::Result; use contract_build::new_contract_project; use heck::ToUpperCamelCase; diff --git a/crates/pop-contracts/src/node/mod.rs b/crates/pop-contracts/src/node/mod.rs index 77ac97cd5..be5680a04 100644 --- a/crates/pop-contracts/src/node/mod.rs +++ b/crates/pop-contracts/src/node/mod.rs @@ -166,10 +166,9 @@ fn release_directory_by_target(tag: Option<&str>) -> Result<&'static str, Error> #[cfg(test)] mod tests { - use crate::testing::find_free_port; - use super::*; use anyhow::{Error, Result}; + use pop_common::find_free_port; use std::process::Command; #[tokio::test] diff --git a/crates/pop-contracts/src/testing.rs b/crates/pop-contracts/src/testing.rs index c10bd4e5f..6a8abfcd9 100644 --- a/crates/pop-contracts/src/testing.rs +++ b/crates/pop-contracts/src/testing.rs @@ -4,7 +4,6 @@ use crate::{create_smart_contract, Contract}; use anyhow::Result; use std::{ fs::{copy, create_dir}, - net::TcpListener, path::Path, }; @@ -38,12 +37,3 @@ where copy(metadata_file, target_contract_dir.join("ink/testing.json"))?; Ok(()) } - -/// Finds an available port by binding to port 0 and retrieving the assigned port. -pub fn find_free_port() -> u16 { - TcpListener::bind("127.0.0.1:0") - .expect("Failed to bind to an available port") - .local_addr() - .expect("Failed to retrieve local address") - .port() -} diff --git a/crates/pop-contracts/src/up.rs b/crates/pop-contracts/src/up.rs index 90e0b35a3..bed7dfa45 100644 --- a/crates/pop-contracts/src/up.rs +++ b/crates/pop-contracts/src/up.rs @@ -2,9 +2,9 @@ use crate::{ errors::Error, utils::{ - helpers::{get_manifest_path, parse_balance}, + get_manifest_path, metadata::{process_function_args, FunctionType}, - signer::create_signer, + parse_balance, }, }; use contract_extrinsics::{ @@ -12,11 +12,10 @@ use contract_extrinsics::{ TokenMetadata, UploadCommandBuilder, UploadExec, }; use ink_env::{DefaultEnvironment, Environment}; +use pop_common::{create_signer, DefaultConfig, Keypair}; use sp_core::Bytes; use sp_weights::Weight; use std::{fmt::Write, path::PathBuf}; -use subxt::PolkadotConfig as DefaultConfig; -use subxt_signer::sr25519::Keypair; /// Attributes for the `up` command #[derive(Debug, PartialEq)] @@ -224,10 +223,10 @@ mod tests { use super::*; use crate::{ contracts_node_generator, errors::Error, mock_build_process, new_environment, - run_contracts_node, testing::find_free_port, + run_contracts_node, }; use anyhow::Result; - use pop_common::set_executable_permission; + use pop_common::{find_free_port, set_executable_permission}; use std::{env, process::Command, time::Duration}; use tokio::time::sleep; use url::Url; diff --git a/crates/pop-contracts/src/utils/helpers.rs b/crates/pop-contracts/src/utils/helpers.rs deleted file mode 100644 index d0797015c..000000000 --- a/crates/pop-contracts/src/utils/helpers.rs +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 - -use crate::errors::Error; -use contract_build::ManifestPath; -use contract_extrinsics::BalanceVariant; -use ink_env::{DefaultEnvironment, Environment}; -use std::{ - path::{Path, PathBuf}, - str::FromStr, -}; -use subxt::{Config, PolkadotConfig as DefaultConfig}; - -pub fn get_manifest_path(path: Option<&Path>) -> Result { - if let Some(path) = path { - let full_path = PathBuf::from(path.to_string_lossy().to_string() + "/Cargo.toml"); - ManifestPath::try_from(Some(full_path)) - .map_err(|e| Error::ManifestPath(format!("Failed to get manifest path: {}", e))) - } else { - ManifestPath::try_from(path.as_ref()) - .map_err(|e| Error::ManifestPath(format!("Failed to get manifest path: {}", e))) - } -} - -pub fn parse_balance( - balance: &str, -) -> Result::Balance>, Error> { - BalanceVariant::from_str(balance).map_err(|e| Error::BalanceParsing(format!("{}", e))) -} - -pub fn parse_account(account: &str) -> Result<::AccountId, Error> { - ::AccountId::from_str(account) - .map_err(|e| Error::AccountAddressParsing(format!("{}", e))) -} - -/// Canonicalizes the given path to ensure consistency and resolve any symbolic links. -/// -/// # Arguments -/// -/// * `target` - A reference to the `Path` to be canonicalized. -pub fn canonicalized_path(target: &Path) -> Result { - // Canonicalize the target path to ensure consistency and resolve any symbolic links. - target - .canonicalize() - // If an I/O error occurs during canonicalization, convert it into an Error enum variant. - .map_err(Error::IO) -} - -#[cfg(test)] -mod tests { - use super::*; - use anyhow::{Error, Result}; - use std::fs; - - fn setup_test_environment() -> Result { - let temp_dir = tempfile::tempdir().expect("Could not create temp dir"); - let temp_contract_dir = temp_dir.path().join("test_contract"); - fs::create_dir(&temp_contract_dir)?; - crate::create_smart_contract( - "test_contract", - temp_contract_dir.as_path(), - &crate::Contract::Standard, - )?; - Ok(temp_dir) - } - - #[test] - fn test_get_manifest_path() -> Result<(), Error> { - let temp_dir = setup_test_environment()?; - get_manifest_path(Some(&PathBuf::from(temp_dir.path().join("test_contract"))))?; - Ok(()) - } - - #[test] - fn test_canonicalized_path() -> Result<(), Error> { - let temp_dir = tempfile::tempdir()?; - // Error case - let error_directory = canonicalized_path(&temp_dir.path().join("my_directory")); - assert!(error_directory.is_err()); - // Success case - canonicalized_path(temp_dir.path())?; - Ok(()) - } - - #[test] - fn parse_balance_works() -> Result<(), Error> { - let balance = parse_balance("100000")?; - assert_eq!(balance, BalanceVariant::Default(100000)); - Ok(()) - } - - #[test] - fn parse_balance_fails_wrong_balance() -> Result<(), Error> { - assert!(matches!(parse_balance("wrongbalance"), Err(super::Error::BalanceParsing(..)))); - Ok(()) - } - - #[test] - fn parse_account_works() -> Result<(), Error> { - let account = parse_account("5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A")?; - assert_eq!(account.to_string(), "5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A"); - Ok(()) - } - - #[test] - fn parse_account_fails_wrong_value() -> Result<(), Error> { - assert!(matches!( - parse_account("wrongaccount"), - Err(super::Error::AccountAddressParsing(..)) - )); - Ok(()) - } -} diff --git a/crates/pop-contracts/src/utils/metadata.rs b/crates/pop-contracts/src/utils/metadata.rs index 38cdc12d7..111468f0e 100644 --- a/crates/pop-contracts/src/utils/metadata.rs +++ b/crates/pop-contracts/src/utils/metadata.rs @@ -3,7 +3,8 @@ use crate::errors::Error; use contract_extrinsics::ContractArtifacts; use contract_transcode::ink_metadata::MessageParamSpec; -use scale_info::{form::PortableForm, PortableRegistry, Type, TypeDef, TypeDefPrimitive}; +use pop_common::format_type; +use scale_info::{form::PortableForm, PortableRegistry}; use std::path::Path; /// Describes a parameter. @@ -156,150 +157,6 @@ fn process_args( args } -// Formats a specified type, using the registry to output its full type representation. -fn format_type(ty: &Type, registry: &PortableRegistry) -> String { - let mut name = ty - .path - .segments - .last() - .map(|s| s.to_owned()) - .unwrap_or_else(|| ty.path.to_string()); - - if !ty.type_params.is_empty() { - let params: Vec<_> = ty - .type_params - .iter() - .filter_map(|p| registry.resolve(p.ty.unwrap().id)) - .map(|t| format_type(t, registry)) - .collect(); - name = format!("{name}<{}>", params.join(",")); - } - - name = format!( - "{name}{}", - match &ty.type_def { - TypeDef::Composite(composite) => { - if composite.fields.is_empty() { - return "".to_string(); - } - - let mut named = false; - let fields: Vec<_> = composite - .fields - .iter() - .filter_map(|f| match f.name.as_ref() { - None => registry.resolve(f.ty.id).map(|t| format_type(t, registry)), - Some(field) => { - named = true; - f.type_name.as_ref().map(|t| format!("{field}: {t}")) - }, - }) - .collect(); - match named { - true => format!(" {{ {} }}", fields.join(", ")), - false => format!(" ({})", fields.join(", ")), - } - }, - TypeDef::Variant(variant) => { - let variants: Vec<_> = variant - .variants - .iter() - .map(|v| { - if v.fields.is_empty() { - return v.name.clone(); - } - - let name = v.name.as_str(); - let mut named = false; - let fields: Vec<_> = v - .fields - .iter() - .filter_map(|f| match f.name.as_ref() { - None => registry.resolve(f.ty.id).map(|t| format_type(t, registry)), - Some(field) => { - named = true; - f.type_name.as_ref().map(|t| format!("{field}: {t}")) - }, - }) - .collect(); - format!( - "{name}{}", - match named { - true => format!("{{ {} }}", fields.join(", ")), - false => format!("({})", fields.join(", ")), - } - ) - }) - .collect(); - format!(": {}", variants.join(", ")) - }, - TypeDef::Sequence(sequence) => { - format!( - "[{}]", - format_type( - registry.resolve(sequence.type_param.id).expect("sequence type not found"), - registry - ) - ) - }, - TypeDef::Array(array) => { - format!( - "[{};{}]", - format_type( - registry.resolve(array.type_param.id).expect("array type not found"), - registry - ), - array.len - ) - }, - TypeDef::Tuple(tuple) => { - let fields: Vec<_> = tuple - .fields - .iter() - .filter_map(|p| registry.resolve(p.id)) - .map(|t| format_type(t, registry)) - .collect(); - format!("({})", fields.join(",")) - }, - TypeDef::Primitive(primitive) => { - use TypeDefPrimitive::*; - match primitive { - Bool => "bool", - Char => "char", - Str => "str", - U8 => "u8", - U16 => "u16", - U32 => "u32", - U64 => "u64", - U128 => "u128", - U256 => "u256", - I8 => "i8", - I16 => "i16", - I32 => "i32", - I64 => "i64", - I128 => "i128", - I256 => "i256", - } - .to_string() - }, - TypeDef::Compact(compact) => { - format!( - "Compact<{}>", - format_type( - registry.resolve(compact.type_param.id).expect("compact type not found"), - registry - ) - ) - }, - TypeDef::BitSequence(_) => { - unimplemented!("bit sequence not currently supported") - }, - } - ); - - name -} - /// Processes a list of argument values for a specified contract function, /// wrapping each value in `Some(...)` or replacing it with `None` if the argument is optional. /// diff --git a/crates/pop-contracts/src/utils/mod.rs b/crates/pop-contracts/src/utils/mod.rs index ad49e2dc6..a3a99323b 100644 --- a/crates/pop-contracts/src/utils/mod.rs +++ b/crates/pop-contracts/src/utils/mod.rs @@ -1,5 +1,149 @@ // SPDX-License-Identifier: GPL-3.0 -pub mod helpers; +use crate::errors::Error; +use contract_build::{util::decode_hex, ManifestPath}; +use contract_extrinsics::BalanceVariant; +use ink_env::{DefaultEnvironment, Environment}; +use pop_common::{Config, DefaultConfig}; +use sp_core::Bytes; +use std::{ + path::{Path, PathBuf}, + str::FromStr, +}; + pub mod metadata; -pub mod signer; + +/// Retrieves the manifest path for a contract project. +/// +/// # Arguments +/// * `path` - An optional path to the project directory. +pub fn get_manifest_path(path: Option<&Path>) -> Result { + if let Some(path) = path { + let full_path = PathBuf::from(path.to_string_lossy().to_string() + "/Cargo.toml"); + ManifestPath::try_from(Some(full_path)) + .map_err(|e| Error::ManifestPath(format!("Failed to get manifest path: {}", e))) + } else { + ManifestPath::try_from(path.as_ref()) + .map_err(|e| Error::ManifestPath(format!("Failed to get manifest path: {}", e))) + } +} + +/// Parses a balance value from a string representation. +/// +/// # Arguments +/// * `balance` - A string representing the balance value to parse. +pub fn parse_balance( + balance: &str, +) -> Result::Balance>, Error> { + BalanceVariant::from_str(balance).map_err(|e| Error::BalanceParsing(format!("{}", e))) +} + +/// Parses an account ID from its string representation. +/// +/// # Arguments +/// * `account` - A string representing the account ID to parse. +pub fn parse_account(account: &str) -> Result<::AccountId, Error> { + ::AccountId::from_str(account) + .map_err(|e| Error::AccountAddressParsing(format!("{}", e))) +} + +/// Parse hex encoded bytes. +/// +/// # Arguments +/// * `input` - A string containing hex-encoded bytes. +pub fn parse_hex_bytes(input: &str) -> Result { + let bytes = decode_hex(input).map_err(|e| Error::HexParsing(format!("{}", e)))?; + Ok(bytes.into()) +} + +/// Canonicalizes the given path to ensure consistency and resolve any symbolic links. +/// +/// # Arguments +/// * `target` - A reference to the `Path` to be canonicalized. +pub fn canonicalized_path(target: &Path) -> Result { + // Canonicalize the target path to ensure consistency and resolve any symbolic links. + target + .canonicalize() + // If an I/O error occurs during canonicalization, convert it into an Error enum variant. + .map_err(Error::IO) +} + +#[cfg(test)] +mod tests { + use super::*; + use anyhow::Result; + use std::fs; + + fn setup_test_environment() -> Result { + let temp_dir = tempfile::tempdir().expect("Could not create temp dir"); + let temp_contract_dir = temp_dir.path().join("test_contract"); + fs::create_dir(&temp_contract_dir)?; + crate::create_smart_contract( + "test_contract", + temp_contract_dir.as_path(), + &crate::Contract::Standard, + )?; + Ok(temp_dir) + } + + #[test] + fn test_get_manifest_path() -> Result<(), Error> { + let temp_dir = setup_test_environment()?; + get_manifest_path(Some(&PathBuf::from(temp_dir.path().join("test_contract"))))?; + Ok(()) + } + + #[test] + fn test_canonicalized_path() -> Result<(), Error> { + let temp_dir = tempfile::tempdir()?; + // Error case + let error_directory = canonicalized_path(&temp_dir.path().join("my_directory")); + assert!(error_directory.is_err()); + // Success case + canonicalized_path(temp_dir.path())?; + Ok(()) + } + + #[test] + fn parse_balance_works() -> Result<(), Error> { + let balance = parse_balance("100000")?; + assert_eq!(balance, BalanceVariant::Default(100000)); + Ok(()) + } + + #[test] + fn parse_balance_fails_wrong_balance() -> Result<(), Error> { + assert!(matches!(parse_balance("wrongbalance"), Err(super::Error::BalanceParsing(..)))); + Ok(()) + } + + #[test] + fn parse_account_works() -> Result<(), Error> { + let account = parse_account("5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A")?; + assert_eq!(account.to_string(), "5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A"); + Ok(()) + } + + #[test] + fn parse_account_fails_wrong_value() -> Result<(), Error> { + assert!(matches!( + parse_account("wrongaccount"), + Err(super::Error::AccountAddressParsing(..)) + )); + Ok(()) + } + + #[test] + fn parse_hex_bytes_works() -> Result<(), Error> { + let input_in_hex = "48656c6c6f"; + let result = parse_hex_bytes(input_in_hex)?; + assert_eq!(result, Bytes(vec![72, 101, 108, 108, 111])); + Ok(()) + } + + #[test] + fn parse_hex_bytes_fails_wrong_input() -> Result<()> { + assert!(matches!(parse_hex_bytes("wronghexvalue"), Err(Error::HexParsing(..)))); + Ok(()) + } +} diff --git a/crates/pop-parachains/Cargo.toml b/crates/pop-parachains/Cargo.toml index dd6406824..64bef2d67 100644 --- a/crates/pop-parachains/Cargo.toml +++ b/crates/pop-parachains/Cargo.toml @@ -24,8 +24,12 @@ tokio.workspace = true url.workspace = true askama.workspace = true +hex.workspace = true indexmap.workspace = true reqwest.workspace = true +scale-info.workspace = true +scale-value.workspace = true +subxt.workspace = true symlink.workspace = true toml_edit.workspace = true walkdir.workspace = true diff --git a/crates/pop-parachains/src/call/metadata/action.rs b/crates/pop-parachains/src/call/metadata/action.rs new file mode 100644 index 000000000..cd47e1279 --- /dev/null +++ b/crates/pop-parachains/src/call/metadata/action.rs @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: GPL-3.0 + +use super::{find_dispatchable_by_name, Pallet}; +use strum::{EnumMessage as _, EnumProperty as _, VariantArray as _}; +use strum_macros::{AsRefStr, Display, EnumMessage, EnumProperty, EnumString, VariantArray}; + +/// Enum representing various predefined actions supported. +#[derive( + AsRefStr, + Clone, + Debug, + Display, + EnumMessage, + EnumString, + EnumProperty, + Eq, + Hash, + PartialEq, + VariantArray, +)] +pub enum Action { + /// Transfer balance. + #[strum( + serialize = "transfer", + message = "transfer_allow_death", + detailed_message = "Transfer balance", + props(Pallet = "Balances") + )] + Transfer, + /// Create an asset. + #[strum( + serialize = "create", + message = "create", + detailed_message = "Create an asset", + props(Pallet = "Assets") + )] + CreateAsset, + /// Mint an asset. + #[strum( + serialize = "mint", + message = "mint", + detailed_message = "Mint an asset", + props(Pallet = "Assets") + )] + MintAsset, + /// Create an NFT collection. + #[strum( + serialize = "create_nft", + message = "create", + detailed_message = "Create an NFT collection", + props(Pallet = "Nfts") + )] + CreateCollection, + /// Mint an NFT. + #[strum( + serialize = "mint_nft", + message = "mint", + detailed_message = "Mint an NFT", + props(Pallet = "Nfts") + )] + MintNFT, + /// Purchase on-demand coretime. + #[strum( + serialize = "place_order_allow_death", + message = "place_order_allow_death", + detailed_message = "Purchase on-demand coretime", + props(Pallet = "OnDemand") + )] + PurchaseOnDemandCoretime, + /// Reserve a parachain ID. + #[strum( + serialize = "reserve", + message = "reserve", + detailed_message = "Reserve a parachain ID", + props(Pallet = "Registrar") + )] + Reserve, + /// Register a parachain ID with genesis state and code. + #[strum( + serialize = "register", + message = "register", + detailed_message = "Register a parachain ID with genesis state and code", + props(Pallet = "Registrar") + )] + Register, +} + +impl Action { + /// Get the description of the action. + pub fn description(&self) -> &str { + self.get_detailed_message().unwrap_or_default() + } + + /// Get the dispatchable function name corresponding to the action. + pub fn function_name(&self) -> &str { + self.get_message().unwrap_or_default() + } + + /// Get the associated pallet name for the action. + pub fn pallet_name(&self) -> &str { + self.get_str("Pallet").unwrap_or_default() + } +} + +/// Fetch the list of supported actions based on available pallets. +/// +/// # Arguments +/// * `pallets`: Supported pallets. +pub fn supported_actions(pallets: &[Pallet]) -> Vec { + let mut actions = Vec::new(); + for action in Action::VARIANTS.iter() { + if find_dispatchable_by_name(pallets, action.pallet_name(), action.function_name()).is_ok() + { + actions.push(action.clone()); + } + } + actions +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{call::tests::POP_NETWORK_TESTNET_URL, parse_chain_metadata, set_up_client}; + use anyhow::Result; + use std::collections::HashMap; + + const POLKADOT_NETWORK_URL: &str = "wss://polkadot-rpc.publicnode.com"; + + #[test] + fn action_descriptions_are_correct() { + let descriptions = HashMap::from([ + (Action::CreateAsset, "Create an asset"), + (Action::MintAsset, "Mint an asset"), + (Action::CreateCollection, "Create an NFT collection"), + (Action::MintNFT, "Mint an NFT"), + (Action::PurchaseOnDemandCoretime, "Purchase on-demand coretime"), + (Action::Transfer, "Transfer balance"), + (Action::Register, "Register a parachain ID with genesis state and code"), + (Action::Reserve, "Reserve a parachain ID"), + ]); + + for action in Action::VARIANTS.iter() { + assert_eq!(&action.description(), descriptions.get(action).unwrap()); + } + } + + #[test] + fn pallet_names_are_correct() { + let pallets = HashMap::from([ + (Action::CreateAsset, "Assets"), + (Action::MintAsset, "Assets"), + (Action::CreateCollection, "Nfts"), + (Action::MintNFT, "Nfts"), + (Action::PurchaseOnDemandCoretime, "OnDemand"), + (Action::Transfer, "Balances"), + (Action::Register, "Registrar"), + (Action::Reserve, "Registrar"), + ]); + + for action in Action::VARIANTS.iter() { + assert_eq!(&action.pallet_name(), pallets.get(action).unwrap(),); + } + } + + #[test] + fn function_names_are_correct() { + let pallets = HashMap::from([ + (Action::CreateAsset, "create"), + (Action::MintAsset, "mint"), + (Action::CreateCollection, "create"), + (Action::MintNFT, "mint"), + (Action::PurchaseOnDemandCoretime, "place_order_allow_death"), + (Action::Transfer, "transfer_allow_death"), + (Action::Register, "register"), + (Action::Reserve, "reserve"), + ]); + + for action in Action::VARIANTS.iter() { + assert_eq!(&action.function_name(), pallets.get(action).unwrap(),); + } + } + + #[tokio::test] + async fn supported_actions_works() -> Result<()> { + // Test Pop Parachain. + let mut client: subxt::OnlineClient = + set_up_client(POP_NETWORK_TESTNET_URL).await?; + let mut actions = supported_actions(&parse_chain_metadata(&client)?); + assert_eq!(actions.len(), 5); + assert_eq!(actions[0], Action::Transfer); + assert_eq!(actions[1], Action::CreateAsset); + assert_eq!(actions[2], Action::MintAsset); + assert_eq!(actions[3], Action::CreateCollection); + assert_eq!(actions[4], Action::MintNFT); + + // Test Polkadot Relay Chain. + client = set_up_client(POLKADOT_NETWORK_URL).await?; + actions = supported_actions(&parse_chain_metadata(&client)?); + assert_eq!(actions.len(), 4); + assert_eq!(actions[0], Action::Transfer); + assert_eq!(actions[1], Action::PurchaseOnDemandCoretime); + assert_eq!(actions[2], Action::Reserve); + assert_eq!(actions[3], Action::Register); + + Ok(()) + } +} diff --git a/crates/pop-parachains/src/call/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs new file mode 100644 index 000000000..1f53f523c --- /dev/null +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -0,0 +1,334 @@ +// SPDX-License-Identifier: GPL-3.0 + +use crate::errors::Error; +use params::Param; +use scale_value::stringify::custom_parsers; +use std::fmt::{Display, Formatter}; +use subxt::{dynamic::Value, Metadata, OnlineClient, SubstrateConfig}; + +pub mod action; +pub mod params; + +/// Represents a pallet in the blockchain, including its dispatchable functions. +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct Pallet { + /// The name of the pallet. + pub name: String, + /// The index of the pallet within the runtime. + pub index: u8, + /// The documentation of the pallet. + pub docs: String, + /// The dispatchable functions of the pallet. + pub functions: Vec, +} + +impl Display for Pallet { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name) + } +} + +/// Represents a dispatchable function. +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct Function { + /// The pallet containing the dispatchable function. + pub pallet: String, + /// The name of the function. + pub name: String, + /// The index of the function within the pallet. + pub index: u8, + /// The documentation of the function. + pub docs: String, + /// The parameters of the function. + pub params: Vec, + /// Whether this function is supported (no recursive or unsupported types like `RuntimeCall`). + pub is_supported: bool, +} + +impl Display for Function { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name) + } +} + +/// Parses the chain metadata to extract information about pallets and their dispatchable functions. +/// +/// # Arguments +/// * `client`: The client to interact with the chain. +/// +/// NOTE: pallets are ordered by their index within the runtime by default. +pub fn parse_chain_metadata(client: &OnlineClient) -> Result, Error> { + let metadata: Metadata = client.metadata(); + + let pallets = metadata + .pallets() + .map(|pallet| { + let functions = pallet + .call_variants() + .map(|variants| { + variants + .iter() + .map(|variant| { + let mut is_supported = true; + + // Parse parameters for the dispatchable function. + let params = { + let mut parsed_params = Vec::new(); + for field in &variant.fields { + match params::field_to_param(&metadata, field) { + Ok(param) => parsed_params.push(param), + Err(_) => { + // If an error occurs while parsing the values, mark the + // dispatchable function as unsupported rather than + // error. + is_supported = false; + parsed_params.clear(); + break; + }, + } + } + parsed_params + }; + + Ok(Function { + pallet: pallet.name().to_string(), + name: variant.name.clone(), + index: variant.index, + docs: if is_supported { + // Filter out blank lines and then flatten into a single value. + variant + .docs + .iter() + .filter(|l| !l.is_empty()) + .cloned() + .collect::>() + .join(" ") + } else { + // To display the message in the UI + "Function Not Supported".to_string() + }, + params, + is_supported, + }) + }) + .collect::, Error>>() + }) + .unwrap_or_else(|| Ok(vec![]))?; + + Ok(Pallet { + name: pallet.name().to_string(), + index: pallet.index(), + docs: pallet.docs().join(" "), + functions, + }) + }) + .collect::, Error>>()?; + + Ok(pallets) +} + +/// Finds a specific pallet by name and retrieves its details from metadata. +/// +/// # Arguments +/// * `pallets`: List of pallets available within the chain's runtime. +/// * `pallet_name`: The name of the pallet to find. +pub fn find_pallet_by_name<'a>( + pallets: &'a [Pallet], + pallet_name: &str, +) -> Result<&'a Pallet, Error> { + if let Some(pallet) = pallets.iter().find(|p| p.name == pallet_name) { + Ok(pallet) + } else { + Err(Error::PalletNotFound(pallet_name.to_string())) + } +} + +/// Finds a specific dispatchable function by name and retrieves its details from metadata. +/// +/// # Arguments +/// * `pallets`: List of pallets available within the chain's runtime. +/// * `pallet_name`: The name of the pallet. +/// * `function_name`: Name of the dispatchable function to locate. +pub fn find_dispatchable_by_name<'a>( + pallets: &'a [Pallet], + pallet_name: &str, + function_name: &str, +) -> Result<&'a Function, Error> { + let pallet = find_pallet_by_name(pallets, pallet_name)?; + if let Some(function) = pallet.functions.iter().find(|&e| e.name == function_name) { + Ok(function) + } else { + Err(Error::FunctionNotSupported) + } +} + +/// Parses and processes raw string parameter values for a dispatchable function, mapping them to +/// `Value` types. +/// +/// # Arguments +/// * `params`: The metadata definition for each parameter of the corresponding dispatchable +/// function. +/// * `raw_params`: A vector of raw string arguments for the dispatchable function. +pub fn parse_dispatchable_arguments( + params: &[Param], + raw_params: Vec, +) -> Result, Error> { + params + .iter() + .zip(raw_params) + .map(|(param, raw_param)| { + // Convert sequence parameters to hex if is_sequence + let processed_param = if param.is_sequence && !raw_param.starts_with("0x") { + format!("0x{}", hex::encode(raw_param)) + } else { + raw_param + }; + scale_value::stringify::from_str_custom() + .add_custom_parser(custom_parsers::parse_hex) + .add_custom_parser(custom_parsers::parse_ss58) + .parse(&processed_param) + .0 + .map_err(|_| Error::ParamProcessingError) + }) + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::{call::tests::POP_NETWORK_TESTNET_URL, set_up_client}; + use anyhow::Result; + use subxt::ext::scale_bits; + + #[tokio::test] + async fn parse_chain_metadata_works() -> Result<()> { + let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; + let pallets = parse_chain_metadata(&client)?; + // Test the first pallet is parsed correctly + let first_pallet = pallets.first().unwrap(); + assert_eq!(first_pallet.name, "System"); + assert_eq!(first_pallet.index, 0); + assert_eq!(first_pallet.docs, ""); + assert_eq!(first_pallet.functions.len(), 11); + let first_function = first_pallet.functions.first().unwrap(); + assert_eq!(first_function.name, "remark"); + assert_eq!(first_function.index, 0); + assert_eq!( + first_function.docs, + "Make some on-chain remark. Can be executed by every `origin`." + ); + assert!(first_function.is_supported); + assert_eq!(first_function.params.first().unwrap().name, "remark"); + assert_eq!(first_function.params.first().unwrap().type_name, "[u8]"); + assert_eq!(first_function.params.first().unwrap().sub_params.len(), 0); + assert!(!first_function.params.first().unwrap().is_optional); + assert!(!first_function.params.first().unwrap().is_tuple); + assert!(!first_function.params.first().unwrap().is_variant); + assert!(first_function.params.first().unwrap().is_sequence); + Ok(()) + } + + #[tokio::test] + async fn find_pallet_by_name_works() -> Result<()> { + let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; + let pallets = parse_chain_metadata(&client)?; + assert!(matches!( + find_pallet_by_name(&pallets, "WrongName"), + Err(Error::PalletNotFound(pallet)) if pallet == "WrongName".to_string())); + let pallet = find_pallet_by_name(&pallets, "Balances")?; + assert_eq!(pallet.name, "Balances"); + assert_eq!(pallet.functions.len(), 9); + Ok(()) + } + + #[tokio::test] + async fn find_dispatchable_by_name_works() -> Result<()> { + let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; + let pallets = parse_chain_metadata(&client)?; + assert!(matches!( + find_dispatchable_by_name(&pallets, "WrongName", "wrong_name"), + Err(Error::PalletNotFound(pallet)) if pallet == "WrongName".to_string())); + assert!(matches!( + find_dispatchable_by_name(&pallets, "Balances", "wrong_name"), + Err(Error::FunctionNotSupported) + )); + let function = find_dispatchable_by_name(&pallets, "Balances", "force_transfer")?; + assert_eq!(function.name, "force_transfer"); + assert_eq!(function.docs, "Exactly as `transfer_allow_death`, except the origin must be root and the source account may be specified."); + assert_eq!(function.is_supported, true); + assert_eq!(function.params.len(), 3); + Ok(()) + } + + #[test] + fn parse_dispatchable_arguments_works() -> Result<()> { + // Values for testing from: https://docs.rs/scale-value/0.18.0/scale_value/stringify/fn.from_str.html + // and https://docs.rs/scale-value/0.18.0/scale_value/stringify/fn.from_str_custom.html + let args = [ + "1".to_string(), + "-1".to_string(), + "true".to_string(), + "'a'".to_string(), + "\"hi\"".to_string(), + "{ a: true, b: \"hello\" }".to_string(), + "MyVariant { a: true, b: \"hello\" }".to_string(), + "<0101>".to_string(), + "(1,2,0x030405)".to_string(), + r#"{ + name: "Alice", + address: 5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty + }"# + .to_string(), + ] + .to_vec(); + let addr: Vec<_> = + hex::decode("8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48") + .unwrap() + .into_iter() + .map(|b| Value::u128(b as u128)) + .collect(); + // Define mock dispatchable function parameters for testing. + let params = vec![ + Param { type_name: "u128".to_string(), ..Default::default() }, + Param { type_name: "i128".to_string(), ..Default::default() }, + Param { type_name: "bool".to_string(), ..Default::default() }, + Param { type_name: "char".to_string(), ..Default::default() }, + Param { type_name: "string".to_string(), ..Default::default() }, + Param { type_name: "compostie".to_string(), ..Default::default() }, + Param { type_name: "variant".to_string(), is_variant: true, ..Default::default() }, + Param { type_name: "bit_sequence".to_string(), ..Default::default() }, + Param { type_name: "tuple".to_string(), is_tuple: true, ..Default::default() }, + Param { type_name: "composite".to_string(), ..Default::default() }, + ]; + assert_eq!( + parse_dispatchable_arguments(¶ms, args)?, + [ + Value::u128(1), + Value::i128(-1), + Value::bool(true), + Value::char('a'), + Value::string("hi"), + Value::named_composite(vec![ + ("a", Value::bool(true)), + ("b", Value::string("hello")) + ]), + Value::named_variant( + "MyVariant", + vec![("a", Value::bool(true)), ("b", Value::string("hello"))] + ), + Value::bit_sequence(scale_bits::Bits::from_iter([false, true, false, true])), + Value::unnamed_composite(vec![ + Value::u128(1), + Value::u128(2), + Value::unnamed_composite(vec![Value::u128(3), Value::u128(4), Value::u128(5),]) + ]), + Value::named_composite(vec![ + ("name", Value::string("Alice")), + ("address", Value::unnamed_composite(addr)) + ]) + ] + ); + Ok(()) + } +} diff --git a/crates/pop-parachains/src/call/metadata/params.rs b/crates/pop-parachains/src/call/metadata/params.rs new file mode 100644 index 000000000..9d8521f9c --- /dev/null +++ b/crates/pop-parachains/src/call/metadata/params.rs @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: GPL-3.0 + +use crate::errors::Error; +use pop_common::format_type; +use scale_info::{form::PortableForm, Field, PortableRegistry, TypeDef}; +use subxt::Metadata; + +/// Describes a parameter of a dispatchable function. +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct Param { + /// The name of the parameter. + pub name: String, + /// The type of the parameter. + pub type_name: String, + /// Nested parameters for composite, variants, types or tuples. + pub sub_params: Vec, + /// Indicates if the parameter is optional (`Option`). + pub is_optional: bool, + /// Indicates if the parameter is a Tuple. + pub is_tuple: bool, + /// Indicates if the parameter is a Variant. + pub is_variant: bool, + /// Indicates if the parameter is a Sequence. + pub is_sequence: bool, +} + +/// Transforms a metadata field into its `Param` representation. +/// +/// # Arguments +/// * `metadata`: The chain metadata. +/// * `field`: A parameter of a dispatchable function (as [Field]). +pub fn field_to_param(metadata: &Metadata, field: &Field) -> Result { + let registry = metadata.types(); + if let Some(name) = field.type_name.as_deref() { + if name.contains("RuntimeCall") { + return Err(Error::FunctionNotSupported); + } + } + let name = field.name.as_deref().unwrap_or("Unnamed"); //It can be unnamed field + type_to_param(name, registry, field.ty.id) +} + +/// Converts a type's metadata into a `Param` representation. +/// +/// # Arguments +/// * `name`: The name of the parameter. +/// * `registry`: Type registry containing all types used in the metadata. +/// * `type_id`: The ID of the type to be converted. +fn type_to_param(name: &str, registry: &PortableRegistry, type_id: u32) -> Result { + let type_info = registry + .resolve(type_id) + .ok_or_else(|| Error::MetadataParsingError(name.to_string()))?; + for param in &type_info.type_params { + if param.name.contains("RuntimeCall") { + return Err(Error::FunctionNotSupported); + } + } + if type_info.path.segments == ["Option"] { + if let Some(sub_type_id) = type_info.type_params.first().and_then(|param| param.ty) { + // Recursive for the sub parameters + let sub_param = type_to_param(name, registry, sub_type_id.id)?; + Ok(Param { + name: name.to_string(), + type_name: sub_param.type_name, + sub_params: sub_param.sub_params, + is_optional: true, + ..Default::default() + }) + } else { + Err(Error::MetadataParsingError(name.to_string())) + } + } else { + // Determine the formatted type name. + let type_name = format_type(type_info, registry); + match &type_info.type_def { + TypeDef::Primitive(_) | TypeDef::Array(_) | TypeDef::Compact(_) => + Ok(Param { name: name.to_string(), type_name, ..Default::default() }), + TypeDef::Composite(composite) => { + let sub_params = composite + .fields + .iter() + .map(|field| { + // Recursive for the sub parameters of composite type. + type_to_param(field.name.as_deref().unwrap_or(name), registry, field.ty.id) + }) + .collect::, Error>>()?; + + Ok(Param { name: name.to_string(), type_name, sub_params, ..Default::default() }) + }, + TypeDef::Variant(variant) => { + let variant_params = variant + .variants + .iter() + .map(|variant_param| { + let variant_sub_params = variant_param + .fields + .iter() + .map(|field| { + // Recursive for the sub parameters of variant type. + type_to_param( + field.name.as_deref().unwrap_or(&variant_param.name), + registry, + field.ty.id, + ) + }) + .collect::, Error>>()?; + Ok(Param { + name: variant_param.name.clone(), + type_name: "".to_string(), + sub_params: variant_sub_params, + is_variant: true, + ..Default::default() + }) + }) + .collect::, Error>>()?; + + Ok(Param { + name: name.to_string(), + type_name, + sub_params: variant_params, + is_variant: true, + ..Default::default() + }) + }, + TypeDef::Sequence(_) => Ok(Param { + name: name.to_string(), + type_name, + is_sequence: true, + ..Default::default() + }), + TypeDef::Tuple(tuple) => { + let sub_params = tuple + .fields + .iter() + .enumerate() + .map(|(index, field_id)| { + type_to_param( + &format!("Index {index} of the tuple {name}"), + registry, + field_id.id, + ) + }) + .collect::, Error>>()?; + + Ok(Param { + name: name.to_string(), + type_name, + sub_params, + is_tuple: true, + ..Default::default() + }) + }, + _ => Err(Error::MetadataParsingError(name.to_string())), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{call::tests::POP_NETWORK_TESTNET_URL, set_up_client}; + use anyhow::Result; + + #[tokio::test] + async fn field_to_param_works() -> Result<()> { + let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; + let metadata = client.metadata(); + // Test a supported dispatchable function. + let function = metadata + .pallet_by_name("Balances") + .unwrap() + .call_variant_by_name("force_transfer") + .unwrap(); + let mut params = Vec::new(); + for field in &function.fields { + params.push(field_to_param(&metadata, field)?) + } + assert_eq!(params.len(), 3); + assert_eq!(params.first().unwrap().name, "source"); + assert_eq!(params.first().unwrap().type_name, "MultiAddress: Id(AccountId32 ([u8;32])), Index(Compact<()>), Raw([u8]), Address32([u8;32]), Address20([u8;20])"); + assert_eq!(params.first().unwrap().sub_params.len(), 5); + assert_eq!(params.first().unwrap().sub_params.first().unwrap().name, "Id"); + assert_eq!(params.first().unwrap().sub_params.first().unwrap().type_name, ""); + assert_eq!( + params + .first() + .unwrap() + .sub_params + .first() + .unwrap() + .sub_params + .first() + .unwrap() + .name, + "Id" + ); + assert_eq!( + params + .first() + .unwrap() + .sub_params + .first() + .unwrap() + .sub_params + .first() + .unwrap() + .type_name, + "AccountId32 ([u8;32])" + ); + // Test some dispatchable functions that are not supported. + let function = + metadata.pallet_by_name("Sudo").unwrap().call_variant_by_name("sudo").unwrap(); + assert!(matches!( + field_to_param(&metadata, &function.fields.first().unwrap()), + Err(Error::FunctionNotSupported) + )); + let function = metadata + .pallet_by_name("Utility") + .unwrap() + .call_variant_by_name("batch") + .unwrap(); + assert!(matches!( + field_to_param(&metadata, &function.fields.first().unwrap()), + Err(Error::FunctionNotSupported) + )); + let function = metadata + .pallet_by_name("PolkadotXcm") + .unwrap() + .call_variant_by_name("execute") + .unwrap(); + assert!(matches!( + field_to_param(&metadata, &function.fields.first().unwrap()), + Err(Error::FunctionNotSupported) + )); + + Ok(()) + } +} diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs new file mode 100644 index 000000000..be9b4f69f --- /dev/null +++ b/crates/pop-parachains/src/call/mod.rs @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-3.0 + +use crate::{errors::Error, Function}; +use pop_common::create_signer; +use subxt::{ + dynamic::Value, + tx::{DynamicPayload, Payload}, + OnlineClient, SubstrateConfig, +}; + +pub mod metadata; + +/// Sets up an [OnlineClient] instance for connecting to a blockchain. +/// +/// # Arguments +/// * `url` - Endpoint of the node. +pub async fn set_up_client(url: &str) -> Result, Error> { + OnlineClient::::from_url(url) + .await + .map_err(|e| Error::ConnectionFailure(e.to_string())) +} + +/// Constructs a dynamic extrinsic payload for a specified dispatchable function. +/// +/// # Arguments +/// * `function` - A dispatchable function. +/// * `args` - A vector of string arguments to be passed to construct the extrinsic. +pub fn construct_extrinsic( + function: &Function, + args: Vec, +) -> Result { + let parsed_args: Vec = metadata::parse_dispatchable_arguments(&function.params, args)?; + Ok(subxt::dynamic::tx(function.pallet.clone(), function.name.clone(), parsed_args)) +} + +/// Constructs a Sudo extrinsic. +/// +/// # Arguments +/// * `xt`: The extrinsic representing the dispatchable function call to be dispatched with `Root` +/// privileges. +pub fn construct_sudo_extrinsic(xt: DynamicPayload) -> Result { + Ok(subxt::dynamic::tx("Sudo", "sudo", [xt.into_value()].to_vec())) +} + +/// Signs and submits a given extrinsic. +/// +/// # Arguments +/// * `client` - The client used to interact with the chain. +/// * `xt` - The extrinsic to be signed and submitted. +/// * `suri` - The secret URI (e.g., mnemonic or private key) for signing the extrinsic. +pub async fn sign_and_submit_extrinsic( + client: &OnlineClient, + xt: DynamicPayload, + suri: &str, +) -> Result { + let signer = create_signer(suri)?; + let result = client + .tx() + .sign_and_submit_then_watch_default(&xt, &signer) + .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())) +} + +/// Encodes the call data for a given extrinsic into a hexadecimal string. +/// +/// # Arguments +/// * `client` - The client used to interact with the chain. +/// * `xt` - The extrinsic whose call data will be encoded and returned. +pub fn encode_call_data( + client: &OnlineClient, + xt: &DynamicPayload, +) -> Result { + let call_data = xt + .encode_call_data(&client.metadata()) + .map_err(|e| Error::CallDataEncodingError(e.to_string()))?; + Ok(format!("0x{}", hex::encode(call_data))) +} + +/// Decodes a hex-encoded string into a vector of bytes representing the call data. +/// +/// # Arguments +/// * `call_data` - The hex-encoded string representing call data. +pub fn decode_call_data(call_data: &str) -> Result, Error> { + hex::decode(call_data.trim_start_matches("0x")) + .map_err(|e| Error::CallDataDecodingError(e.to_string())) +} + +// This struct implements the [`Payload`] trait and is used to submit +// pre-encoded SCALE call data directly, without the dynamic construction of transactions. +struct CallData(Vec); + +impl Payload for CallData { + fn encode_call_data_to( + &self, + _: &subxt::Metadata, + out: &mut Vec, + ) -> Result<(), subxt::ext::subxt_core::Error> { + out.extend_from_slice(&self.0); + Ok(()) + } +} + +/// Signs and submits a given extrinsic. +/// +/// # Arguments +/// * `client` - Reference to an `OnlineClient` connected to the chain. +/// * `call_data` - SCALE encoded bytes representing the extrinsic's call data. +/// * `suri` - The secret URI (e.g., mnemonic or private key) for signing the extrinsic. +pub async fn sign_and_submit_extrinsic_with_call_data( + client: &OnlineClient, + call_data: Vec, + suri: &str, +) -> Result { + let signer = create_signer(suri)?; + let payload = CallData(call_data); + let result = client + .tx() + .sign_and_submit_then_watch_default(&payload, &signer) + .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())) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{find_dispatchable_by_name, parse_chain_metadata, set_up_client}; + use anyhow::Result; + + const ALICE_SURI: &str = "//Alice"; + pub(crate) const POP_NETWORK_TESTNET_URL: &str = "wss://rpc1.paseo.popnetwork.xyz"; + + #[tokio::test] + async fn set_up_client_works() -> Result<()> { + assert!(matches!( + set_up_client("wss://wronguri.xyz").await, + Err(Error::ConnectionFailure(_)) + )); + set_up_client(POP_NETWORK_TESTNET_URL).await?; + Ok(()) + } + + #[tokio::test] + async fn construct_extrinsic_works() -> Result<()> { + let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; + let pallets = parse_chain_metadata(&client)?; + let transfer_allow_death = + find_dispatchable_by_name(&pallets, "Balances", "transfer_allow_death")?; + + // Wrong parameters + assert!(matches!( + construct_extrinsic( + &transfer_allow_death, + vec![ALICE_SURI.to_string(), "100".to_string()], + ), + Err(Error::ParamProcessingError) + )); + // Valid parameters + let xt = construct_extrinsic( + &transfer_allow_death, + vec![ + "Id(5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty)".to_string(), + "100".to_string(), + ], + )?; + assert_eq!(xt.call_name(), "transfer_allow_death"); + assert_eq!(xt.pallet_name(), "Balances"); + Ok(()) + } + + #[tokio::test] + async fn encode_call_data_works() -> Result<()> { + let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; + let pallets = parse_chain_metadata(&client)?; + let remark = find_dispatchable_by_name(&pallets, "System", "remark")?; + let xt = construct_extrinsic(&remark, vec!["0x11".to_string()])?; + assert_eq!(encode_call_data(&client, &xt)?, "0x00000411"); + let xt = construct_extrinsic(&remark, vec!["123".to_string()])?; + assert_eq!(encode_call_data(&client, &xt)?, "0x00000c313233"); + let xt = construct_extrinsic(&remark, vec!["test".to_string()])?; + assert_eq!(encode_call_data(&client, &xt)?, "0x00001074657374"); + Ok(()) + } + + #[tokio::test] + async fn decode_call_data_works() -> Result<()> { + assert!(matches!(decode_call_data("wrongcalldata"), Err(Error::CallDataDecodingError(..)))); + let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; + let pallets = parse_chain_metadata(&client)?; + let remark = find_dispatchable_by_name(&pallets, "System", "remark")?; + let xt = construct_extrinsic(&remark, vec!["0x11".to_string()])?; + let expected_call_data = xt.encode_call_data(&client.metadata())?; + assert_eq!(decode_call_data("0x00000411")?, expected_call_data); + Ok(()) + } + + #[tokio::test] + async fn sign_and_submit_wrong_extrinsic_fails() -> Result<()> { + let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; + let function = Function { + pallet: "WrongPallet".to_string(), + name: "wrong_extrinsic".to_string(), + index: 0, + docs: "documentation".to_string(), + is_supported: true, + ..Default::default() + }; + let xt = construct_extrinsic(&function, vec!["0x11".to_string()])?; + assert!(matches!( + sign_and_submit_extrinsic(&client, xt, ALICE_SURI).await, + Err(Error::ExtrinsicSubmissionError(message)) if message.contains("PalletNameNotFound(\"WrongPallet\"))") + )); + Ok(()) + } + + #[tokio::test] + async fn construct_sudo_extrinsic_works() -> Result<()> { + let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; + let pallets = parse_chain_metadata(&client)?; + let force_transfer = find_dispatchable_by_name(&pallets, "Balances", "force_transfer")?; + let xt = construct_extrinsic( + &force_transfer, + vec![ + "Id(5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty)".to_string(), + "Id(5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy)".to_string(), + "100".to_string(), + ], + )?; + let xt = construct_sudo_extrinsic(xt)?; + assert_eq!(xt.call_name(), "sudo"); + assert_eq!(xt.pallet_name(), "Sudo"); + Ok(()) + } +} diff --git a/crates/pop-parachains/src/errors.rs b/crates/pop-parachains/src/errors.rs index 3eff7bbfc..8bd97434a 100644 --- a/crates/pop-parachains/src/errors.rs +++ b/crates/pop-parachains/src/errors.rs @@ -3,24 +3,43 @@ use thiserror::Error; use zombienet_sdk::OrchestratorError; +/// Represents the various errors that can occur in the crate. #[derive(Error, Debug)] pub enum Error { #[error("User aborted due to existing target directory.")] Aborted, #[error("Anyhow error: {0}")] AnyhowError(#[from] anyhow::Error), + /// An error occurred while decoding the call data. + #[error("Failed to decode call data. {0}")] + CallDataDecodingError(String), + /// An error occurred while encoding the call data. + #[error("Failed to encode call data. {0}")] + CallDataEncodingError(String), #[error("{0}")] CommonError(#[from] pop_common::Error), + /// An error occurred while attempting to establish a connection to the endpoint. + #[error("Failed to establish a connection to: {0}")] + ConnectionFailure(String), #[error("Configuration error: {0}")] Config(String), #[error("Failed to access the current directory")] CurrentDirAccess, #[error("Failed to parse the endowment value")] EndowmentError, + /// An error occurred during the submission of an extrinsic. + #[error("Extrinsic submission error: {0}")] + ExtrinsicSubmissionError(String), + /// The dispatchable function is not supported. + #[error("The dispatchable function is not supported")] + FunctionNotSupported, #[error("IO error: {0}")] IO(#[from] std::io::Error), #[error("JSON error: {0}")] JsonError(#[from] serde_json::Error), + /// An error occurred while parsing metadata of a parameter. + #[error("Error parsing metadata for parameter {0}")] + MetadataParsingError(String), #[error("Missing binary: {0}")] MissingBinary(String), #[error("Missing chain spec file at: {0}")] @@ -31,6 +50,12 @@ pub enum Error { OrchestratorError(#[from] OrchestratorError), #[error("Failed to create pallet directory")] PalletDirCreation, + /// The specified pallet could not be found. + #[error("Failed to find the pallet {0}")] + PalletNotFound(String), + /// An error occurred while processing the arguments provided by the user. + #[error("Failed to process the arguments provided by the user.")] + ParamProcessingError, #[error("Invalid path")] PathError, #[error("Failed to execute rustfmt")] diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index 06bee8f4a..31a86ade7 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -2,6 +2,8 @@ #![doc = include_str!("../README.md")] mod build; +/// Provides functionality to construct, encode, sign, and submit chain extrinsics. +mod call; mod errors; mod generator; mod new_pallet; @@ -14,10 +16,22 @@ pub use build::{ binary_path, build_parachain, export_wasm_file, generate_genesis_state_file, generate_plain_chain_spec, generate_raw_chain_spec, is_supported, ChainSpec, }; +pub use call::{ + construct_extrinsic, construct_sudo_extrinsic, decode_call_data, encode_call_data, + metadata::{ + action::{supported_actions, Action}, + find_dispatchable_by_name, find_pallet_by_name, + params::Param, + parse_chain_metadata, Function, Pallet, + }, + set_up_client, sign_and_submit_extrinsic, sign_and_submit_extrinsic_with_call_data, +}; pub use errors::Error; pub use indexmap::IndexSet; pub use new_pallet::{create_pallet_template, new_pallet_options::*, TemplatePalletConfig}; pub use new_parachain::instantiate_template_dir; +// External export from subxt. +pub use subxt::{tx::DynamicPayload, OnlineClient, SubstrateConfig}; pub use templates::{Config, Parachain, Provider}; pub use up::Zombienet; pub use utils::helpers::is_initial_endowment_valid; diff --git a/deny.toml b/deny.toml index 5c295de57..53c6d37e6 100644 --- a/deny.toml +++ b/deny.toml @@ -20,6 +20,7 @@ allow = [ "GPL-3.0", "MIT", "MPL-2.0", + "Unicode-3.0", "Unicode-DFS-2016", "Unlicense" ] From 600a019834619c61061644471a0428353673e36a Mon Sep 17 00:00:00 2001 From: Daanvdplas Date: Wed, 11 Dec 2024 13:25:17 +0100 Subject: [PATCH 195/211] show alex --- Cargo.lock | 3 + crates/pop-cli/src/commands/call/chain.rs | 5 +- crates/pop-common/Cargo.toml | 4 + crates/pop-common/src/lib.rs | 6 ++ crates/pop-parachains/src/call/mod.rs | 93 +++++++++++++++++++---- 5 files changed, 93 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6ff7331d9..e20a8beb5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4856,10 +4856,13 @@ version = "0.5.0" dependencies = [ "anyhow", "cargo_toml", + "contract-build", + "contract-extrinsics", "duct", "flate2", "git2", "git2_credentials", + "ink_env", "mockito", "regex", "reqwest 0.12.7", diff --git a/crates/pop-cli/src/commands/call/chain.rs b/crates/pop-cli/src/commands/call/chain.rs index cfabb1b1b..24af276ad 100644 --- a/crates/pop-cli/src/commands/call/chain.rs +++ b/crates/pop-cli/src/commands/call/chain.rs @@ -90,7 +90,7 @@ impl CallChainCommand { }; // Sign and submit the extrinsic. - if let Err(e) = call.submit_extrinsic(&chain.client, xt, &mut cli).await { + if let Err(e) = call.submit_extrinsic(&chain.client, &chain.url, xt, &mut cli).await { display_message(&e.to_string(), false, &mut cli)?; break; } @@ -361,6 +361,7 @@ impl Call { async fn submit_extrinsic( &mut self, client: &OnlineClient, + url: &Url, tx: DynamicPayload, cli: &mut impl Cli, ) -> Result<()> { @@ -378,7 +379,7 @@ impl Call { } let spinner = cliclack::spinner(); spinner.start("Signing and submitting the extrinsic and then waiting for finalization, please be patient..."); - let result = sign_and_submit_extrinsic(client, tx, &self.suri) + let result = sign_and_submit_extrinsic(client, url, tx, &self.suri) .await .map_err(|err| anyhow!("{}", format!("{err:?}")))?; spinner.stop(result); diff --git a/crates/pop-common/Cargo.toml b/crates/pop-common/Cargo.toml index 03e5035d7..52b1c34ad 100644 --- a/crates/pop-common/Cargo.toml +++ b/crates/pop-common/Cargo.toml @@ -31,6 +31,10 @@ toml_edit.workspace = true url.workspace = true toml.workspace = true +contract-extrinsics.workspace = true +contract-build.workspace = true +ink_env.workspace = true + [dev-dependencies] mockito.workspace = true tempfile.workspace = true diff --git a/crates/pop-common/src/lib.rs b/crates/pop-common/src/lib.rs index 6f1991960..4ae9b9e78 100644 --- a/crates/pop-common/src/lib.rs +++ b/crates/pop-common/src/lib.rs @@ -73,6 +73,12 @@ pub fn find_free_port() -> u16 { .port() } +pub mod call { + pub use contract_extrinsics::{DisplayEvents, TokenMetadata}; + pub use ink_env::{DefaultEnvironment}; + pub use contract_build::Verbosity; +} + #[cfg(test)] mod test { use super::*; diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs index a21f577f9..82ce22d23 100644 --- a/crates/pop-parachains/src/call/mod.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -1,12 +1,13 @@ // SPDX-License-Identifier: GPL-3.0 use crate::{errors::Error, Function}; -use pop_common::create_signer; +use pop_common::{create_signer}; use subxt::{ dynamic::Value, tx::{DynamicPayload, Payload}, OnlineClient, SubstrateConfig, }; +use pop_common::call::TokenMetadata; pub mod metadata; @@ -50,6 +51,7 @@ pub fn construct_sudo_extrinsic(xt: DynamicPayload) -> Result, + url: &url::Url, xt: DynamicPayload, suri: &str, ) -> Result { @@ -63,24 +65,17 @@ pub async fn sign_and_submit_extrinsic( .await .map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))?; - // Obtain events related to the extrinsic. - let mut events = Vec::new(); - for event in result.iter() { - let event = event.map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))?; - // Filter events that are from a different pallet. - let pallet = - if event.pallet_name() == xt.pallet_name() { event.pallet_name() } else { continue }; - let variant = event.variant_name(); - let field_values = event - .field_values() - .map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))?; - events.push(format!("{pallet}::{variant}: {field_values}")); - } + let metadata = client.metadata(); + let display_events = + pop_common::call::DisplayEvents::from_events::(&result, None, &metadata)?; + let token_metadata = TokenMetadata::query::(url).await?; + let events = + display_events.display_events::(pop_common::call::Verbosity::Default, &token_metadata)?; Ok(format!( - "Extrinsic Submitted with hash: {:?}\n\nEvent(s):\n\n{}", + "Extrinsic Submitted with hash: {:?}\n\n{}", result.extrinsic_hash(), - events.join("\n\n") + events, )) } @@ -152,6 +147,72 @@ mod tests { use super::*; use crate::{find_dispatchable_by_name, parse_chain_metadata, set_up_client}; use anyhow::Result; + use scale_info::scale::Compact; + use scale_info::TypeInfo; + + //---- + use subxt::events::{Events, EventDetails, Phase}; + use subxt::ext::codec::{Encode, Decode}; + use subxt::ext::{scale_decode, scale_encode}; + use subxt::Metadata; + use subxt::utils::H256; + use pop_common::Config; + + #[derive( + Encode, + Decode, + TypeInfo, + Clone, + Debug, + PartialEq, + Eq, + scale_encode::EncodeAsType, + scale_decode::DecodeAsType, + )] + pub enum AllEvents { + Test(Ev), + } + #[derive(Encode)] + pub struct EventRecord { + phase: Phase, + event: AllEvents, + topics: Vec<::Hash>, + } + + pub fn events_raw( + metadata: Metadata, + event_bytes: Vec, + num_events: u32, + ) -> Events { + // Prepend compact encoded length to event bytes: + let mut all_event_bytes = Compact(num_events).encode(); + all_event_bytes.extend(event_bytes); + Events::decode_from(all_event_bytes, metadata) + } + + fn events( + metadata: Metadata, + event_records: Vec>, + ) -> Events { + let num_events = event_records.len() as u32; + let mut event_bytes = Vec::new(); + for ev in event_records { + ev.encode_to(&mut event_bytes); + } + events_raw(metadata, event_bytes, num_events) + } + + #[test] + fn test_filter_events() { + let events = ExtrinsicEvents:: { + ext_hash: H256(), + idx: 0, + events: events::() + + }; + } + //---- + const ALICE_SURI: &str = "//Alice"; pub(crate) const POP_NETWORK_TESTNET_URL: &str = "wss://rpc1.paseo.popnetwork.xyz"; From b787e083eebd5b09d163a5388b3b6ea88dc78e0f Mon Sep 17 00:00:00 2001 From: Daanvdplas Date: Wed, 11 Dec 2024 13:27:20 +0100 Subject: [PATCH 196/211] remove old test code --- crates/pop-parachains/src/call/mod.rs | 66 --------------------------- 1 file changed, 66 deletions(-) diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs index 82ce22d23..e49605e8d 100644 --- a/crates/pop-parachains/src/call/mod.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -147,72 +147,6 @@ mod tests { use super::*; use crate::{find_dispatchable_by_name, parse_chain_metadata, set_up_client}; use anyhow::Result; - use scale_info::scale::Compact; - use scale_info::TypeInfo; - - //---- - use subxt::events::{Events, EventDetails, Phase}; - use subxt::ext::codec::{Encode, Decode}; - use subxt::ext::{scale_decode, scale_encode}; - use subxt::Metadata; - use subxt::utils::H256; - use pop_common::Config; - - #[derive( - Encode, - Decode, - TypeInfo, - Clone, - Debug, - PartialEq, - Eq, - scale_encode::EncodeAsType, - scale_decode::DecodeAsType, - )] - pub enum AllEvents { - Test(Ev), - } - #[derive(Encode)] - pub struct EventRecord { - phase: Phase, - event: AllEvents, - topics: Vec<::Hash>, - } - - pub fn events_raw( - metadata: Metadata, - event_bytes: Vec, - num_events: u32, - ) -> Events { - // Prepend compact encoded length to event bytes: - let mut all_event_bytes = Compact(num_events).encode(); - all_event_bytes.extend(event_bytes); - Events::decode_from(all_event_bytes, metadata) - } - - fn events( - metadata: Metadata, - event_records: Vec>, - ) -> Events { - let num_events = event_records.len() as u32; - let mut event_bytes = Vec::new(); - for ev in event_records { - ev.encode_to(&mut event_bytes); - } - events_raw(metadata, event_bytes, num_events) - } - - #[test] - fn test_filter_events() { - let events = ExtrinsicEvents:: { - ext_hash: H256(), - idx: 0, - events: events::() - - }; - } - //---- - const ALICE_SURI: &str = "//Alice"; pub(crate) const POP_NETWORK_TESTNET_URL: &str = "wss://rpc1.paseo.popnetwork.xyz"; From 780ef4b2ff3ba61249f6a7ef68ba0385861899c7 Mon Sep 17 00:00:00 2001 From: Daanvdplas Date: Wed, 11 Dec 2024 17:02:21 +0100 Subject: [PATCH 197/211] refactor: generic sign and submit --- crates/pop-cli/src/commands/call/chain.rs | 31 ++++++++--- crates/pop-common/Cargo.toml | 9 ++- crates/pop-common/src/lib.rs | 7 ++- crates/pop-parachains/src/call/mod.rs | 67 +++++++++-------------- crates/pop-parachains/src/lib.rs | 2 +- 5 files changed, 59 insertions(+), 57 deletions(-) diff --git a/crates/pop-cli/src/commands/call/chain.rs b/crates/pop-cli/src/commands/call/chain.rs index 24af276ad..6e61f30c9 100644 --- a/crates/pop-cli/src/commands/call/chain.rs +++ b/crates/pop-cli/src/commands/call/chain.rs @@ -8,8 +8,8 @@ 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, sign_and_submit_extrinsic_with_call_data, supported_actions, Action, - DynamicPayload, Function, OnlineClient, Pallet, Param, SubstrateConfig, + sign_and_submit_extrinsic, supported_actions, Action, CallData, DynamicPayload, Function, + OnlineClient, Pallet, Param, SubstrateConfig, }; use url::Url; @@ -62,7 +62,12 @@ impl CallChainCommand { // Execute the call if call_data is provided. if let Some(call_data) = self.call_data.as_ref() { if let Err(e) = self - .submit_extrinsic_from_call_data(&chain.client, call_data, &mut cli::Cli) + .submit_extrinsic_from_call_data( + &chain.client, + &chain.url, + call_data, + &mut cli::Cli, + ) .await { display_message(&e.to_string(), false, &mut cli::Cli)?; @@ -213,6 +218,7 @@ impl CallChainCommand { async fn submit_extrinsic_from_call_data( &self, client: &OnlineClient, + url: &Url, call_data: &str, cli: &mut impl Cli, ) -> Result<()> { @@ -238,7 +244,7 @@ impl CallChainCommand { spinner.start("Signing and submitting the extrinsic and then waiting for finalization, please be patient..."); let call_data_bytes = decode_call_data(call_data).map_err(|err| anyhow!("{}", format!("{err:?}")))?; - let result = sign_and_submit_extrinsic_with_call_data(client, call_data_bytes, suri) + let result = sign_and_submit_extrinsic(client, &url, CallData::new(call_data_bytes), suri) .await .map_err(|err| anyhow!("{}", format!("{err:?}")))?; @@ -754,19 +760,21 @@ mod tests { .expect_confirm("Do you want to submit the extrinsic?", false) .expect_outro_cancel("Extrinsic for `remark` was not submitted."); let xt = call_config.prepare_extrinsic(&client, &mut cli)?; - call_config.submit_extrinsic(&client, xt, &mut cli).await?; + call_config + .submit_extrinsic(&client, &Url::parse(POP_NETWORK_TESTNET_URL)?, xt, &mut cli) + .await?; cli.verify() } #[tokio::test] async fn user_cancel_submit_extrinsic_from_call_data_works() -> Result<()> { - let client = set_up_client("wss://rpc1.paseo.popnetwork.xyz").await?; + let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; let call_config = CallChainCommand { pallet: None, function: None, args: vec![].to_vec(), - url: Some(Url::parse("wss://rpc1.paseo.popnetwork.xyz")?), + url: Some(Url::parse(POP_NETWORK_TESTNET_URL)?), suri: None, skip_confirm: false, call_data: Some("0x00000411".to_string()), @@ -777,7 +785,12 @@ mod tests { .expect_confirm("Do you want to submit the extrinsic?", false) .expect_outro_cancel("Extrinsic with call data 0x00000411 was not submitted."); call_config - .submit_extrinsic_from_call_data(&client, "0x00000411", &mut cli) + .submit_extrinsic_from_call_data( + &client, + &Url::parse(POP_NETWORK_TESTNET_URL)?, + "0x00000411", + &mut cli, + ) .await?; cli.verify() @@ -809,7 +822,7 @@ mod tests { "Would you like to dispatch this function call with `Root` origin?", true, ); - call_config.url = Some(Url::parse("wss://rpc1.paseo.popnetwork.xyz")?); + call_config.url = Some(Url::parse(POP_NETWORK_TESTNET_URL)?); let chain = call_config.configure_chain(&mut cli).await?; call_config.configure_sudo(&chain, &mut cli)?; assert!(call_config.sudo); diff --git a/crates/pop-common/Cargo.toml b/crates/pop-common/Cargo.toml index 52b1c34ad..f81f29827 100644 --- a/crates/pop-common/Cargo.toml +++ b/crates/pop-common/Cargo.toml @@ -10,10 +10,13 @@ repository.workspace = true [dependencies] anyhow.workspace = true cargo_toml.workspace = true +contract-build.workspace = true +contract-extrinsics.workspace = true duct.workspace = true flate2.workspace = true git2.workspace = true git2_credentials.workspace = true +ink_env.workspace = true regex.workspace = true reqwest.workspace = true scale-info.workspace = true @@ -27,13 +30,9 @@ tar.workspace = true tempfile.workspace = true thiserror.workspace = true tokio.workspace = true +toml.workspace = true toml_edit.workspace = true url.workspace = true -toml.workspace = true - -contract-extrinsics.workspace = true -contract-build.workspace = true -ink_env.workspace = true [dev-dependencies] mockito.workspace = true diff --git a/crates/pop-common/src/lib.rs b/crates/pop-common/src/lib.rs index 4ae9b9e78..1e967654b 100644 --- a/crates/pop-common/src/lib.rs +++ b/crates/pop-common/src/lib.rs @@ -73,10 +73,13 @@ pub fn find_free_port() -> u16 { .port() } +/// Provides functionality for making calls to parachains or smart contracts. pub mod call { - pub use contract_extrinsics::{DisplayEvents, TokenMetadata}; - pub use ink_env::{DefaultEnvironment}; + // Note: parsing events after calling a chain is done using cargo contract logic. This could be + // refactored in the future. pub use contract_build::Verbosity; + pub use contract_extrinsics::{DisplayEvents, TokenMetadata}; + pub use ink_env::DefaultEnvironment; } #[cfg(test)] diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs index e49605e8d..9fd4e444c 100644 --- a/crates/pop-parachains/src/call/mod.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -1,13 +1,15 @@ // SPDX-License-Identifier: GPL-3.0 use crate::{errors::Error, Function}; -use pop_common::{create_signer}; +use pop_common::{ + call::{DefaultEnvironment, DisplayEvents, TokenMetadata, Verbosity}, + create_signer, +}; use subxt::{ dynamic::Value, tx::{DynamicPayload, Payload}, OnlineClient, SubstrateConfig, }; -use pop_common::call::TokenMetadata; pub mod metadata; @@ -47,12 +49,13 @@ pub fn construct_sudo_extrinsic(xt: DynamicPayload) -> Result( client: &OnlineClient, url: &url::Url, - xt: DynamicPayload, + xt: C, suri: &str, ) -> Result { let signer = create_signer(suri)?; @@ -65,18 +68,18 @@ pub async fn sign_and_submit_extrinsic( .await .map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))?; + // Obtain required metadata and parse events. The following is using existing logic from + // `cargo-contract`, also used in calling contracts, due to simplicity and can be refactored in + // the future. let metadata = client.metadata(); - let display_events = - pop_common::call::DisplayEvents::from_events::(&result, None, &metadata)?; let token_metadata = TokenMetadata::query::(url).await?; + let events = DisplayEvents::from_events::( + &result, None, &metadata, + )?; let events = - display_events.display_events::(pop_common::call::Verbosity::Default, &token_metadata)?; + events.display_events::(Verbosity::Default, &token_metadata)?; - Ok(format!( - "Extrinsic Submitted with hash: {:?}\n\n{}", - result.extrinsic_hash(), - events, - )) + Ok(format!("Extrinsic Submitted with hash: {:?}\n\n{}", result.extrinsic_hash(), events,)) } /// Encodes the call data for a given extrinsic into a hexadecimal string. @@ -103,9 +106,16 @@ pub fn decode_call_data(call_data: &str) -> Result, Error> { .map_err(|e| Error::CallDataDecodingError(e.to_string())) } -// This struct implements the [`Payload`] trait and is used to submit -// pre-encoded SCALE call data directly, without the dynamic construction of transactions. -struct CallData(Vec); +/// This struct implements the [`Payload`] trait and is used to submit +/// pre-encoded SCALE call data directly, without the dynamic construction of transactions. +pub struct CallData(Vec); + +impl CallData { + /// Create a new instance of `CallData`. + pub fn new(data: Vec) -> CallData { + CallData(data) + } +} impl Payload for CallData { fn encode_call_data_to( @@ -118,35 +128,12 @@ impl Payload for CallData { } } -/// Signs and submits a given extrinsic. -/// -/// # Arguments -/// * `client` - Reference to an `OnlineClient` connected to the chain. -/// * `call_data` - SCALE encoded bytes representing the extrinsic's call data. -/// * `suri` - The secret URI (e.g., mnemonic or private key) for signing the extrinsic. -pub async fn sign_and_submit_extrinsic_with_call_data( - client: &OnlineClient, - call_data: Vec, - suri: &str, -) -> Result { - let signer = create_signer(suri)?; - let payload = CallData(call_data); - let result = client - .tx() - .sign_and_submit_then_watch_default(&payload, &signer) - .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())) -} - #[cfg(test)] mod tests { use super::*; use crate::{find_dispatchable_by_name, parse_chain_metadata, set_up_client}; use anyhow::Result; + use url::Url; const ALICE_SURI: &str = "//Alice"; pub(crate) const POP_NETWORK_TESTNET_URL: &str = "wss://rpc1.paseo.popnetwork.xyz"; @@ -228,7 +215,7 @@ mod tests { }; let xt = construct_extrinsic(&function, vec!["0x11".to_string()])?; assert!(matches!( - sign_and_submit_extrinsic(&client, xt, ALICE_SURI).await, + sign_and_submit_extrinsic(&client, &Url::parse(POP_NETWORK_TESTNET_URL)?, xt, ALICE_SURI).await, Err(Error::ExtrinsicSubmissionError(message)) if message.contains("PalletNameNotFound(\"WrongPallet\"))") )); Ok(()) diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index 31a86ade7..5bba81542 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -24,7 +24,7 @@ pub use call::{ params::Param, parse_chain_metadata, Function, Pallet, }, - set_up_client, sign_and_submit_extrinsic, sign_and_submit_extrinsic_with_call_data, + set_up_client, sign_and_submit_extrinsic, CallData, }; pub use errors::Error; pub use indexmap::IndexSet; From b2e822177a3f50bf44c5464e0a55e169aa57a7c6 Mon Sep 17 00:00:00 2001 From: Daanvdplas Date: Thu, 12 Dec 2024 09:33:17 +0100 Subject: [PATCH 198/211] refactor: remove unnecessary function --- crates/pop-common/src/lib.rs | 5 +++-- crates/pop-parachains/src/call/mod.rs | 26 +------------------------- 2 files changed, 4 insertions(+), 27 deletions(-) diff --git a/crates/pop-common/src/lib.rs b/crates/pop-common/src/lib.rs index f75ab8b49..70dbcc8f8 100644 --- a/crates/pop-common/src/lib.rs +++ b/crates/pop-common/src/lib.rs @@ -75,8 +75,9 @@ pub fn find_free_port() -> u16 { /// Provides functionality for making calls to parachains or smart contracts. pub mod call { - // Note: cargo contract logic is used for parsing events after calling a chain. This could be refactored - // in the future so that we don't have to use cargo contract code in `pop-parachains`. + // Note: cargo contract logic is used for parsing events after calling a chain. This could be + // refactored in the future so that we don't have to use cargo contract code in + // `pop-parachains`. pub use contract_build::Verbosity; pub use contract_extrinsics::{DisplayEvents, TokenMetadata}; pub use ink_env::DefaultEnvironment; diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs index a5126139d..d7d53dba5 100644 --- a/crates/pop-parachains/src/call/mod.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -79,7 +79,7 @@ pub async fn sign_and_submit_extrinsic( let events = events.display_events::(Verbosity::Default, &token_metadata)?; - Ok(format!("Extrinsic Submitted with hash: {:?}\n\n{}", result.extrinsic_hash(), events,)) + Ok(format!("Extrinsic Submitted with hash: {:?}\n\n{}", result.extrinsic_hash(), events)) } /// Encodes the call data for a given extrinsic into a hexadecimal string. @@ -128,30 +128,6 @@ impl Payload for CallData { } } -/// Signs and submits a given extrinsic. -/// -/// # Arguments -/// * `client` - Reference to an `OnlineClient` connected to the chain. -/// * `call_data` - SCALE encoded bytes representing the extrinsic's call data. -/// * `suri` - The secret URI (e.g., mnemonic or private key) for signing the extrinsic. -pub async fn sign_and_submit_extrinsic_with_call_data( - client: &OnlineClient, - call_data: Vec, - suri: &str, -) -> Result { - let signer = create_signer(suri)?; - let payload = CallData(call_data); - let result = client - .tx() - .sign_and_submit_then_watch_default(&payload, &signer) - .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())) -} - #[cfg(test)] mod tests { use super::*; From d3df01efe53a8b3cf182b962ab497b247c723887 Mon Sep 17 00:00:00 2001 From: Daanvdplas Date: Thu, 12 Dec 2024 10:36:30 +0100 Subject: [PATCH 199/211] style: generic name --- crates/pop-cli/src/commands/call/chain.rs | 2 +- crates/pop-parachains/src/call/mod.rs | 6 +++--- 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 6e61f30c9..16a8f8006 100644 --- a/crates/pop-cli/src/commands/call/chain.rs +++ b/crates/pop-cli/src/commands/call/chain.rs @@ -244,7 +244,7 @@ impl CallChainCommand { spinner.start("Signing and submitting the extrinsic and then waiting for finalization, please be patient..."); let call_data_bytes = decode_call_data(call_data).map_err(|err| anyhow!("{}", format!("{err:?}")))?; - let result = sign_and_submit_extrinsic(client, &url, CallData::new(call_data_bytes), suri) + let result = sign_and_submit_extrinsic(client, url, CallData::new(call_data_bytes), suri) .await .map_err(|err| anyhow!("{}", format!("{err:?}")))?; diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs index d7d53dba5..1f7e61b22 100644 --- a/crates/pop-parachains/src/call/mod.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -50,12 +50,12 @@ pub fn construct_sudo_extrinsic(xt: DynamicPayload) -> Result( +pub async fn sign_and_submit_extrinsic( client: &OnlineClient, url: &url::Url, - xt: C, + xt: Xt, suri: &str, ) -> Result { let signer = create_signer(suri)?; From 9a35f88d7108590b72f9217b9fbc25605959bcad Mon Sep 17 00:00:00 2001 From: Daanvdplas Date: Thu, 12 Dec 2024 11:17:23 +0100 Subject: [PATCH 200/211] fix: spinner --- crates/pop-cli/src/commands/call/chain.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pop-cli/src/commands/call/chain.rs b/crates/pop-cli/src/commands/call/chain.rs index 16a8f8006..9751b7dd9 100644 --- a/crates/pop-cli/src/commands/call/chain.rs +++ b/crates/pop-cli/src/commands/call/chain.rs @@ -248,7 +248,7 @@ impl CallChainCommand { .await .map_err(|err| anyhow!("{}", format!("{err:?}")))?; - spinner.stop(format!("Extrinsic submitted successfully with hash: {:?}", result)); + spinner.stop(result); display_message("Call complete.", true, cli)?; Ok(()) } From 110b9bf7a984393e7cc207487294105ac76da923 Mon Sep 17 00:00:00 2001 From: Javier Viola <363911+pepoviola@users.noreply.github.com> Date: Fri, 13 Dec 2024 08:22:46 -0300 Subject: [PATCH 201/211] chore: bump zombienet version to `v0.2.18` (#352) * bump zombienet version * bump zombienet version * update lock * update deps --- Cargo.lock | 11374 +++++++++++++++++++++++++++++++++++++++++---------- Cargo.toml | 20 +- deny.toml | 5 +- 3 files changed, 9278 insertions(+), 2121 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e20a8beb5..74d393d5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,11 +23,11 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ - "gimli 0.31.0", + "gimli 0.31.1", ] [[package]] @@ -46,6 +46,31 @@ dependencies = [ "generic-array", ] +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "ahash" version = "0.7.8" @@ -81,9 +106,68 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.18" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "alloy-primitives" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0628ec0ba5b98b3370bb6be17b12f23bfce8ee4ad83823325a20546d9b03b78" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more 0.99.18", + "hex-literal", + "itoa", + "proptest", + "rand", + "ruint", + "serde", + "tiny-keccak", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f542548a609dca89fcd72b3b9f355928cf844d4363c5eed9c5273a3dd225e097" +dependencies = [ + "arrayvec 0.7.6", + "bytes", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a98ad1696a2e17f010ae8e43e9f2a1e930ed176a8e3ff77acfeff6dfb07b42c" +dependencies = [ + "const-hex", + "dunce", + "heck 0.4.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.90", + "syn-solidity", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-types" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +checksum = "98d7107bed88e8f09f0ddcc3335622d87bfb6821f3e0c7473329fb1cfad5e015" +dependencies = [ + "alloy-primitives", + "alloy-sol-macro", + "const-hex", + "serde", +] [[package]] name = "android-tzdata" @@ -111,9 +195,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -126,49 +210,72 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.87" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "aquamarine" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8" +checksum = "21cc1548309245035eb18aa7f0967da6bc65587005170c56e6ef2788a4cf3f4e" +dependencies = [ + "include_dir", + "itertools 0.10.5", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.90", +] [[package]] name = "arbitrary" -version = "1.3.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" dependencies = [ "derive_arbitrary", ] @@ -180,8 +287,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb00293ba84f51ce3bd026bd0de55899c4e68f0a39a5728cebae3a73ffdc0a4f" dependencies = [ "ark-ec", - "ark-ff", - "ark-std", + "ark-ff 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-bls12-377-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c7021f180a0cbea0380eba97c2af3c57074cdaffe0eef7e840e1c9f2841e55" +dependencies = [ + "ark-bls12-377", + "ark-ec", + "ark-models-ext", + "ark-std 0.4.0", ] [[package]] @@ -191,9 +310,48 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488" dependencies = [ "ark-ec", - "ark-ff", - "ark-serialize", - "ark-std", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-bls12-381-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1dc4b3d08f19e8ec06e949712f95b8361e43f1391d94f65e4234df03480631c" +dependencies = [ + "ark-bls12-381", + "ark-ec", + "ark-ff 0.4.2", + "ark-models-ext", + "ark-serialize 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-bw6-761" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e0605daf0cc5aa2034b78d008aaf159f56901d92a52ee4f6ecdfdac4f426700" +dependencies = [ + "ark-bls12-377", + "ark-ec", + "ark-ff 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-bw6-761-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccee5fba47266f460067588ee1bf070a9c760bf2050c1c509982c5719aadb4f2" +dependencies = [ + "ark-bw6-761", + "ark-ec", + "ark-ff 0.4.2", + "ark-models-ext", + "ark-std 0.4.0", ] [[package]] @@ -202,14 +360,83 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" dependencies = [ - "ark-ff", + "ark-ff 0.4.2", "ark-poly", - "ark-serialize", - "ark-std", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "derivative", "hashbrown 0.13.2", "itertools 0.10.5", "num-traits", + "rayon", + "zeroize", +] + +[[package]] +name = "ark-ed-on-bls12-377" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b10d901b9ac4b38f9c32beacedfadcdd64e46f8d7f8e88c1ae1060022cf6f6c6" +dependencies = [ + "ark-bls12-377", + "ark-ec", + "ark-ff 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-ed-on-bls12-377-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524a4fb7540df2e1a8c2e67a83ba1d1e6c3947f4f9342cc2359fc2e789ad731d" +dependencies = [ + "ark-ec", + "ark-ed-on-bls12-377", + "ark-ff 0.4.2", + "ark-models-ext", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-ed-on-bls12-381-bandersnatch" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9cde0f2aa063a2a5c28d39b47761aa102bda7c13c84fc118a61b87c7b2f785c" +dependencies = [ + "ark-bls12-381", + "ark-ec", + "ark-ff 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-ed-on-bls12-381-bandersnatch-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15185f1acb49a07ff8cbe5f11a1adc5a93b19e211e325d826ae98e98e124346" +dependencies = [ + "ark-ec", + "ark-ed-on-bls12-381-bandersnatch", + "ark-ff 0.4.2", + "ark-models-ext", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.3.3", "zeroize", ] @@ -219,20 +446,30 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" dependencies = [ - "ark-ff-asm", - "ark-ff-macros", - "ark-serialize", - "ark-std", + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "derivative", "digest 0.10.7", "itertools 0.10.5", "num-bigint", "num-traits", "paste", - "rustc_version", + "rustc_version 0.4.1", "zeroize", ] +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote", + "syn 1.0.109", +] + [[package]] name = "ark-ff-asm" version = "0.4.2" @@ -243,6 +480,18 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint", + "num-traits", + "quote", + "syn 1.0.109", +] + [[package]] name = "ark-ff-macros" version = "0.4.2" @@ -256,19 +505,56 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-models-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e9eab5d4b5ff2f228b763d38442adc9b084b0a465409b059fac5c2308835ec2" +dependencies = [ + "ark-ec", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", +] + [[package]] name = "ark-poly" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" dependencies = [ - "ark-ff", - "ark-serialize", - "ark-std", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "derivative", "hashbrown 0.13.2", ] +[[package]] +name = "ark-scale" +version = "0.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f69c00b3b529be29528a6f2fd5fa7b1790f8bed81b9cdca17e326538545a179" +dependencies = [ + "ark-ec", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + [[package]] name = "ark-serialize" version = "0.4.2" @@ -276,7 +562,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" dependencies = [ "ark-serialize-derive", - "ark-std", + "ark-std 0.4.0", "digest 0.10.7", "num-bigint", ] @@ -292,6 +578,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand", +] + [[package]] name = "ark-std" version = "0.4.0" @@ -300,6 +596,7 @@ checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" dependencies = [ "num-traits", "rand", + "rayon", ] [[package]] @@ -310,9 +607,9 @@ checksum = "5d5dde061bd34119e902bbb2d9b90c5692635cf59fb91d582c2b68043f1b8293" [[package]] name = "arrayref" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" @@ -355,7 +652,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -399,6 +696,66 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "assert_matches" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" + +[[package]] +name = "asset-test-utils" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0324df9ce91a9840632e865dd3272bd20162023856f1b189b7ae58afa5c6b61" +dependencies = [ + "cumulus-pallet-parachain-system", + "cumulus-pallet-xcmp-queue", + "cumulus-primitives-core", + "frame-support", + "frame-system", + "pallet-assets", + "pallet-balances", + "pallet-collator-selection", + "pallet-session", + "pallet-timestamp", + "pallet-xcm", + "pallet-xcm-bridge-hub-router", + "parachains-common", + "parachains-runtimes-test-utils", + "parity-scale-codec", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "staging-parachain-info", + "staging-xcm 14.2.0", + "staging-xcm-builder", + "staging-xcm-executor", + "substrate-wasm-builder", +] + +[[package]] +name = "assets-common" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93438e31a4449fbeab87210931edc8cd156292354f1fc15f17d819ecded6bf25" +dependencies = [ + "cumulus-primitives-core", + "frame-support", + "impl-trait-for-tuples", + "log", + "pallet-asset-conversion", + "pallet-assets", + "pallet-xcm", + "parachains-common", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-runtime 39.0.2", + "staging-xcm 14.2.0", + "staging-xcm-builder", + "staging-xcm-executor", + "substrate-wasm-builder", +] + [[package]] name = "async-channel" version = "2.3.1" @@ -437,9 +794,9 @@ dependencies = [ [[package]] name = "async-io" -version = "2.3.4" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" +checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" dependencies = [ "async-lock", "cfg-if", @@ -448,7 +805,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix 0.38.36", + "rustix 0.38.42", "slab", "tracing", "windows-sys 0.59.0", @@ -478,9 +835,9 @@ dependencies = [ [[package]] name = "async-process" -version = "2.2.4" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a07789659a4d385b79b18b9127fc27e1a59e1e89117c78c5ea3b806f016374" +checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" dependencies = [ "async-channel", "async-io", @@ -491,9 +848,8 @@ dependencies = [ "cfg-if", "event-listener 5.3.1", "futures-lite", - "rustix 0.38.36", + "rustix 0.38.42", "tracing", - "windows-sys 0.59.0", ] [[package]] @@ -508,7 +864,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 0.38.36", + "rustix 0.38.42", "signal-hook-registry", "slab", "windows-sys 0.59.0", @@ -516,9 +872,9 @@ dependencies = [ [[package]] name = "async-stream" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" dependencies = [ "async-stream-impl", "futures-core", @@ -527,13 +883,13 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -544,13 +900,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.82" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -565,11 +921,22 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "auto_impl" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backoff" @@ -588,11 +955,11 @@ version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ - "addr2line 0.24.1", + "addr2line 0.24.2", "cfg-if", "libc", "miniz_oxide", - "object 0.36.4", + "object 0.36.5", "rustc-demangle", "windows-targets 0.52.6", ] @@ -657,6 +1024,16 @@ dependencies = [ "serde", ] +[[package]] +name = "binary-merkle-tree" +version = "15.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "336bf780dd7526a9a4bc1521720b25c1994dc132cccd59553431923fa4d1a693" +dependencies = [ + "hash-db", + "log", +] + [[package]] name = "bincode" version = "1.3.3" @@ -668,15 +1045,30 @@ dependencies = [ [[package]] name = "bip39" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f" +checksum = "33415e24172c1b7d6066f6d999545375ab8e1d95421d6784bdfff9496f292387" dependencies = [ - "bitcoin_hashes 0.11.0", + "bitcoin_hashes 0.13.0", "serde", "unicode-normalization", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitcoin-internals" version = "0.2.0" @@ -684,10 +1076,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" [[package]] -name = "bitcoin_hashes" -version = "0.11.0" +name = "bitcoin-io" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" +checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" [[package]] name = "bitcoin_hashes" @@ -696,7 +1088,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" dependencies = [ "bitcoin-internals", - "hex-conservative", + "hex-conservative 0.1.2", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +dependencies = [ + "bitcoin-io", + "hex-conservative 0.2.1", ] [[package]] @@ -719,6 +1121,7 @@ checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ "funty", "radium", + "serde", "tap", "wyz", ] @@ -786,9 +1189,9 @@ dependencies = [ [[package]] name = "bollard" -version = "0.16.1" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aed08d3adb6ebe0eff737115056652670ae290f177759aac19c30456135f94c" +checksum = "97ccca1260af6a459d75994ad5acc1651bcabcbdbc41467cc9786519ab854c30" dependencies = [ "base64 0.22.1", "bollard-stubs", @@ -796,12 +1199,12 @@ dependencies = [ "futures-core", "futures-util", "hex", - "http 1.1.0", + "http 1.2.0", "http-body-util", - "hyper 1.4.1", + "hyper 1.5.1", "hyper-named-pipe", "hyper-util", - "hyperlocal-next", + "hyperlocal", "log", "pin-project-lite", "serde", @@ -809,7 +1212,7 @@ dependencies = [ "serde_json", "serde_repr", "serde_urlencoded", - "thiserror", + "thiserror 2.0.6", "tokio", "tokio-util", "tower-service", @@ -819,9 +1222,9 @@ dependencies = [ [[package]] name = "bollard-stubs" -version = "1.44.0-rc.2" +version = "1.47.1-rc.27.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709d9aa1c37abb89d40f19f5d0ad6f0d88cb1581264e571c9350fc5bb89cf1c5" +checksum = "3f179cfbddb6e77a5472703d4b30436bff32929c0aa8a9008ecf23d1d3cdd0da" dependencies = [ "serde", "serde_repr", @@ -830,9 +1233,9 @@ dependencies = [ [[package]] name = "borsh" -version = "1.5.1" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" +checksum = "2506947f73ad44e344215ccd6403ac2ae18cd8e046e581a441bf8d199f257f03" dependencies = [ "borsh-derive", "cfg_aliases", @@ -840,28 +1243,302 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.5.1" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" +checksum = "c2593a3b8b938bd68373196c9832f516be11fa487ef4ae745eb282e6a56a7244" dependencies = [ "once_cell", "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.89", - "syn_derive", + "syn 2.0.90", ] [[package]] name = "bounded-collections" -version = "0.2.0" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d077619e9c237a5d1875166f5e8033e8f6bff0c96f8caf81e1c2d7738c431bf" +dependencies = [ + "log", + "parity-scale-codec", + "scale-info", + "serde", +] + +[[package]] +name = "bp-header-chain" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "890df97cea17ee61ff982466bb9e90cb6b1462adb45380999019388d05e4b92d" +dependencies = [ + "bp-runtime", + "finality-grandpa", + "frame-support", + "parity-scale-codec", + "scale-info", + "serde", + "sp-consensus-grandpa", + "sp-core 34.0.0", + "sp-runtime 39.0.2", + "sp-std", +] + +[[package]] +name = "bp-messages" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7efabf94339950b914ba87249497f1a0e35a73849934d164fecae4b275928cf6" +dependencies = [ + "bp-header-chain", + "bp-runtime", + "frame-support", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-std", +] + +[[package]] +name = "bp-parachains" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9011e5c12c15caf3c4129a98f4f4916ea9165db8daf6ed85867c3106075f40df" +dependencies = [ + "bp-header-chain", + "bp-polkadot-core", + "bp-runtime", + "frame-support", + "impl-trait-for-tuples", + "parity-scale-codec", + "scale-info", + "sp-core 34.0.0", + "sp-runtime 39.0.2", + "sp-std", +] + +[[package]] +name = "bp-polkadot" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa6277dd4333917ecfbcc35e9332a9f11682e0a506e76b617c336224660fce33" +dependencies = [ + "bp-header-chain", + "bp-polkadot-core", + "bp-runtime", + "frame-support", + "sp-api", + "sp-std", +] + +[[package]] +name = "bp-polkadot-core" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "345cf472bac11ef79d403e4846a666b7d22a13cd16d9c85b62cd6b5e16c4a042" +dependencies = [ + "bp-messages", + "bp-runtime", + "frame-support", + "frame-system", + "parity-scale-codec", + "parity-util-mem", + "scale-info", + "serde", + "sp-core 34.0.0", + "sp-runtime 39.0.2", + "sp-std", +] + +[[package]] +name = "bp-relayers" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9465ad727e466d67d64244a1aa7bb19933a297913fdde34b8e9bda0a341bdeb" +dependencies = [ + "bp-header-chain", + "bp-messages", + "bp-parachains", + "bp-runtime", + "frame-support", + "frame-system", + "pallet-utility", + "parity-scale-codec", + "scale-info", + "sp-runtime 39.0.2", + "sp-std", +] + +[[package]] +name = "bp-runtime" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32385ecb91a31bddaf908e8dcf4a15aef1bcd3913cc03ebfad02ff6d568abc1" +checksum = "746d9464f912b278f8a5e2400f10541f95da7fc6c7d688a2788b9a46296146ee" dependencies = [ + "frame-support", + "frame-system", + "hash-db", + "impl-trait-for-tuples", "log", + "num-traits", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-state-machine 0.43.0", + "sp-std", + "sp-trie 37.0.0", + "trie-db 0.29.1", +] + +[[package]] +name = "bp-test-utils" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e659078b54c0b6bd79896738212a305842ad37168976363233516754337826" +dependencies = [ + "bp-header-chain", + "bp-parachains", + "bp-polkadot-core", + "bp-runtime", + "ed25519-dalek", + "finality-grandpa", + "parity-scale-codec", + "sp-application-crypto 38.0.0", + "sp-consensus-grandpa", + "sp-core 34.0.0", + "sp-runtime 39.0.2", + "sp-std", + "sp-trie 37.0.0", +] + +[[package]] +name = "bp-xcm-bridge-hub" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6909117ca87cb93703742939d5f0c4c93e9646d9cda22262e9709d68c929999b" +dependencies = [ + "bp-messages", + "bp-runtime", + "frame-support", "parity-scale-codec", "scale-info", "serde", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-std", + "staging-xcm 14.2.0", +] + +[[package]] +name = "bp-xcm-bridge-hub-router" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9284820ca704f5c065563cad77d2e3d069a23cc9cb3a29db9c0de8dd3b173a87" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-core 34.0.0", + "sp-runtime 39.0.2", + "staging-xcm 14.2.0", +] + +[[package]] +name = "bridge-hub-common" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b53c53d627e2da38f8910807944bf3121e154b5c0ac9e122995af9dfb13ed" +dependencies = [ + "cumulus-primitives-core", + "frame-support", + "pallet-message-queue", + "parity-scale-codec", + "scale-info", + "snowbridge-core", + "sp-core 34.0.0", + "sp-runtime 39.0.2", + "sp-std", + "staging-xcm 14.2.0", +] + +[[package]] +name = "bridge-hub-test-utils" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0b3aa5fd8481a06ca16e47fd3d2d9c6abe76b27d922ec8980a853f242173b3" +dependencies = [ + "asset-test-utils", + "bp-header-chain", + "bp-messages", + "bp-parachains", + "bp-polkadot-core", + "bp-relayers", + "bp-runtime", + "bp-test-utils", + "bp-xcm-bridge-hub", + "bridge-runtime-common", + "cumulus-pallet-parachain-system", + "cumulus-pallet-xcmp-queue", + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "log", + "pallet-balances", + "pallet-bridge-grandpa", + "pallet-bridge-messages", + "pallet-bridge-parachains", + "pallet-bridge-relayers", + "pallet-timestamp", + "pallet-utility", + "pallet-xcm", + "pallet-xcm-bridge-hub", + "parachains-common", + "parachains-runtimes-test-utils", + "parity-scale-codec", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-keyring", + "sp-runtime 39.0.2", + "sp-tracing 17.0.1", + "staging-xcm 14.2.0", + "staging-xcm-builder", + "staging-xcm-executor", +] + +[[package]] +name = "bridge-runtime-common" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c639aa22de6e904156a3e8b0e6b9e6af790cb27a1299688cc07997e1ffe5b648" +dependencies = [ + "bp-header-chain", + "bp-messages", + "bp-parachains", + "bp-polkadot-core", + "bp-relayers", + "bp-runtime", + "bp-xcm-bridge-hub", + "frame-support", + "frame-system", + "log", + "pallet-bridge-grandpa", + "pallet-bridge-messages", + "pallet-bridge-parachains", + "pallet-bridge-relayers", + "pallet-transaction-payment", + "pallet-utility", + "parity-scale-codec", + "scale-info", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-std", + "sp-trie 37.0.0", + "staging-xcm 14.2.0", + "tuplex", ] [[package]] @@ -884,15 +1561,24 @@ dependencies = [ [[package]] name = "bstr" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" dependencies = [ "memchr", - "regex-automata 0.4.7", + "regex-automata 0.4.9", "serde", ] +[[package]] +name = "build-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdce191bf3fa4995ce948c8c83b4640a1745457a149e73c6db75b4ffe36aad5f" +dependencies = [ + "semver 0.6.0", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -927,6 +1613,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "bytemuck" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" + [[package]] name = "byteorder" version = "1.5.0" @@ -935,9 +1627,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "camino" @@ -950,32 +1642,46 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" dependencies = [ "serde", ] [[package]] name = "cargo_metadata" -version = "0.18.1" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" +dependencies = [ + "camino", + "cargo-platform", + "semver 1.0.23", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +checksum = "8769706aad5d996120af43197bf46ef6ad0fda35216b4505f926a365a232d924" dependencies = [ "camino", "cargo-platform", - "semver", + "semver 1.0.23", "serde", "serde_json", - "thiserror", + "thiserror 2.0.6", ] [[package]] name = "cargo_toml" -version = "0.20.4" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad639525b1c67b6a298f378417b060fbc04618bea559482a8484381cce27d965" +checksum = "88da5a13c620b4ca0078845707ea9c3faf11edbc3ffd8497d11d686211cd1ac0" dependencies = [ "serde", "toml 0.8.19", @@ -983,9 +1689,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.18" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" +checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d" dependencies = [ "jobserver", "libc", @@ -998,6 +1704,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -1023,9 +1738,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", @@ -1042,13 +1757,14 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", + "zeroize", ] [[package]] name = "clap" -version = "4.5.17" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" dependencies = [ "clap_builder", "clap_derive", @@ -1056,9 +1772,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.17" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" dependencies = [ "anstream", "anstyle", @@ -1068,27 +1784,27 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.13" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "cliclack" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c827ccada848b64fba073b64518a7416d605ad70c594b5450b5ed1d97e3b5d4" +checksum = "6a80570d35684e725e9d2d4aaaf32bc0cbfcfb8539898f9afea3da0d2e5189e4" dependencies = [ "console", "indicatif", @@ -1105,14 +1821,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" dependencies = [ "termcolor", - "unicode-width", + "unicode-width 0.1.14", ] [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "colored" @@ -1158,16 +1874,49 @@ dependencies = [ "encode_unicode", "lazy_static", "libc", - "unicode-width", + "unicode-width 0.1.14", "windows-sys 0.52.0", ] +[[package]] +name = "const-hex" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b0485bab839b018a8f1723fc5391819fea5f8f0f32288ef8a735fd096b6160c" +dependencies = [ + "cfg-if", + "cpufeatures", + "hex", + "proptest", + "serde", +] + [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom", + "once_cell", + "tiny-keccak", +] + [[package]] name = "const_env" version = "0.1.2" @@ -1208,14 +1957,14 @@ checksum = "cd7e35aee659887cbfb97aaf227ac12cad1a9d7c71e55ff3376839ed4e282d08" [[package]] name = "contract-build" -version = "5.0.0-alpha" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d12277cde43657b5a7a36c9bb6e9ecf12b757234a867f9f38ce78d386a328281" +checksum = "857769855bf40d230e41baf6575cc44bd5e6869f69f88f45f6791da793f49a0c" dependencies = [ "anyhow", "blake2", "bollard", - "cargo_metadata", + "cargo_metadata 0.19.1", "clap", "colored", "contract-metadata", @@ -1223,11 +1972,11 @@ dependencies = [ "duct", "heck 0.5.0", "hex", - "impl-serde", + "impl-serde 0.5.0", "parity-scale-codec", "regex", - "rustc_version", - "semver", + "rustc_version 0.4.1", + "semver 1.0.23", "serde", "serde_json", "strum 0.26.3", @@ -1242,16 +1991,16 @@ dependencies = [ "walkdir", "wasm-encoder", "wasm-opt", - "wasmparser 0.207.0", + "wasmparser 0.220.0", "which", "zip", ] [[package]] name = "contract-extrinsics" -version = "5.0.0-alpha" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7cd6c946b411dc175da09825bc0b041503eabca8347cefbf40eb346e035efc" +checksum = "e77ad38bef6454f97ca33481e960e9b105cb97f3794656071cbf50229445a89d" dependencies = [ "anyhow", "blake2", @@ -1264,17 +2013,17 @@ dependencies = [ "hex", "ink_env", "ink_metadata", - "itertools 0.12.1", + "itertools 0.13.0", "pallet-contracts-uapi-next", "parity-scale-codec", "rust_decimal", "scale-info", "serde", "serde_json", - "sp-core", - "sp-runtime", - "sp-weights", - "subxt", + "sp-core 32.0.0", + "sp-runtime 35.0.0", + "sp-weights 31.0.0", + "subxt 0.38.0", "tokio", "tracing", "url", @@ -1282,13 +2031,13 @@ dependencies = [ [[package]] name = "contract-metadata" -version = "5.0.0-alpha" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fc0718fc46a399688c032f2141a3abafa0eb42041e3d6880390069ca4700fca" +checksum = "733a6624ea05dd71050641c3cd9baff7a1445032a0082f0e55c800c078716424" dependencies = [ "anyhow", - "impl-serde", - "semver", + "impl-serde 0.5.0", + "semver 1.0.23", "serde", "serde_json", "url", @@ -1296,9 +2045,9 @@ dependencies = [ [[package]] name = "contract-transcode" -version = "5.0.0-alpha" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671e0dff122f02fff592b7f32aa59b1886352939945cba487ee10f6367f13a75" +checksum = "cd9131028be7b8eefdd9151a0a682ed428c1418d5d1ec142c35d2dbe6b9653c6" dependencies = [ "anyhow", "base58", @@ -1306,19 +2055,19 @@ dependencies = [ "contract-metadata", "escape8259", "hex", - "indexmap 2.5.0", + "indexmap 2.7.0", "ink_env", "ink_metadata", - "itertools 0.12.1", + "itertools 0.13.0", "nom", "nom-supreme", "parity-scale-codec", - "primitive-types", + "primitive-types 0.13.1", "scale-info", "serde", "serde_json", "strsim 0.11.1", - "thiserror", + "thiserror 2.0.6", "tracing", ] @@ -1364,60 +2113,168 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] [[package]] -name = "cranelift-entity" +name = "cranelift-bforest" version = "0.95.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40099d38061b37e505e63f89bab52199037a72b931ad4868d9089ff7268660b0" +checksum = "1277fbfa94bc82c8ec4af2ded3e639d49ca5f7f3c7eeab2c66accd135ece4e70" dependencies = [ - "serde", + "cranelift-entity", ] [[package]] -name = "crc32fast" -version = "1.4.2" +name = "cranelift-codegen" +version = "0.95.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "c6e8c31ad3b2270e9aeec38723888fe1b0ace3bea2b06b3f749ccf46661d3220" dependencies = [ - "cfg-if", + "bumpalo", + "cranelift-bforest", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-entity", + "cranelift-isle", + "gimli 0.27.3", + "hashbrown 0.13.2", + "log", + "regalloc2 0.6.1", + "smallvec", + "target-lexicon", ] [[package]] -name = "crossbeam-queue" -version = "0.3.11" +name = "cranelift-codegen-meta" +version = "0.95.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +checksum = "c8ac5ac30d62b2d66f12651f6b606dbdfd9c2cfd0908de6b387560a277c5c9da" dependencies = [ - "crossbeam-utils", + "cranelift-codegen-shared", ] [[package]] -name = "crossbeam-utils" -version = "0.8.20" +name = "cranelift-codegen-shared" +version = "0.95.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "dd82b8b376247834b59ed9bdc0ddeb50f517452827d4a11bccf5937b213748b8" [[package]] -name = "crossterm" -version = "0.27.0" +name = "cranelift-entity" +version = "0.95.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +checksum = "40099d38061b37e505e63f89bab52199037a72b931ad4868d9089ff7268660b0" dependencies = [ - "bitflags 2.6.0", - "crossterm_winapi", - "libc", - "mio 0.8.11", - "parking_lot", - "signal-hook", - "signal-hook-mio", - "winapi", + "serde", +] + +[[package]] +name = "cranelift-frontend" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a25d9d0a0ae3079c463c34115ec59507b4707175454f0eee0891e83e30e82d" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-isle" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80de6a7d0486e4acbd5f9f87ec49912bf4c8fb6aea00087b989685460d4469ba" + +[[package]] +name = "cranelift-native" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb6b03e0e03801c4b3fd8ce0758a94750c07a44e7944cc0ffbf0d3f2e7c79b00" +dependencies = [ + "cranelift-codegen", + "libc", + "target-lexicon", +] + +[[package]] +name = "cranelift-wasm" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff3220489a3d928ad91e59dd7aeaa8b3de18afb554a6211213673a71c90737ac" +dependencies = [ + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", + "itertools 0.10.5", + "log", + "smallvec", + "wasmparser 0.102.0", + "wasmtime-types", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags 2.6.0", + "crossterm_winapi", + "mio", + "parking_lot", + "rustix 0.38.42", + "signal-hook", + "signal-hook-mio", + "winapi", ] [[package]] @@ -1468,6 +2325,320 @@ dependencies = [ "subtle", ] +[[package]] +name = "crypto_secretbox" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d6cf87adf719ddf43a805e92c6870a531aedda35ff640442cbaf8674e141e1" +dependencies = [ + "aead", + "cipher", + "generic-array", + "poly1305", + "salsa20", + "subtle", + "zeroize", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "cumulus-pallet-aura-ext" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cbe2735fc7cf2b6521eab00cb1a1ab025abc1575cc36887b36dc8c5cb1c9434" +dependencies = [ + "cumulus-pallet-parachain-system", + "frame-support", + "frame-system", + "pallet-aura", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-application-crypto 38.0.0", + "sp-consensus-aura", + "sp-runtime 39.0.2", +] + +[[package]] +name = "cumulus-pallet-dmp-queue" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97263a8e758d201ebe81db7cea7b278b4fb869c11442f77acef70138ac1a252f" +dependencies = [ + "cumulus-primitives-core", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "staging-xcm 14.2.0", +] + +[[package]] +name = "cumulus-pallet-parachain-system" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "546403ee1185f4051a74cc9c9d76e82c63cac3fb68e1bf29f61efb5604c96488" +dependencies = [ + "bytes", + "cumulus-pallet-parachain-system-proc-macro", + "cumulus-primitives-core", + "cumulus-primitives-parachain-inherent", + "cumulus-primitives-proof-size-hostfunction", + "environmental", + "frame-benchmarking", + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "log", + "pallet-message-queue", + "parity-scale-codec", + "polkadot-parachain-primitives", + "polkadot-runtime-common", + "polkadot-runtime-parachains", + "scale-info", + "sp-core 34.0.0", + "sp-externalities 0.29.0", + "sp-inherents", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-state-machine 0.43.0", + "sp-std", + "sp-trie 37.0.0", + "sp-version", + "staging-xcm 14.2.0", + "staging-xcm-builder", + "trie-db 0.29.1", +] + +[[package]] +name = "cumulus-pallet-parachain-system-proc-macro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "befbaf3a1ce23ac8476481484fef5f4d500cbd15b4dad6380ce1d28134b0c1f7" +dependencies = [ + "proc-macro-crate 3.2.0", + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "cumulus-pallet-session-benchmarking" +version = "19.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18168570689417abfb514ac8812fca7e6429764d01942750e395d7d8ce0716ef" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-session", + "parity-scale-codec", + "sp-runtime 39.0.2", +] + +[[package]] +name = "cumulus-pallet-solo-to-para" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42c74548c8cab75da6f2479a953f044b582cfce98479862344a24df7bbd215" +dependencies = [ + "cumulus-pallet-parachain-system", + "frame-support", + "frame-system", + "pallet-sudo", + "parity-scale-codec", + "polkadot-primitives 16.0.0", + "scale-info", + "sp-runtime 39.0.2", +] + +[[package]] +name = "cumulus-pallet-xcm" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e49231f6cd8274438b078305dc8ce44c54c0d3f4a28e902589bcbaa53d954608" +dependencies = [ + "cumulus-primitives-core", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "staging-xcm 14.2.0", +] + +[[package]] +name = "cumulus-pallet-xcmp-queue" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f788bdac9474795ea13ba791b55798fb664b2e3da8c3a7385b480c9af4e6539" +dependencies = [ + "bounded-collections", + "bp-xcm-bridge-hub-router", + "cumulus-primitives-core", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-message-queue", + "parity-scale-codec", + "polkadot-runtime-common", + "polkadot-runtime-parachains", + "scale-info", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "staging-xcm 14.2.0", + "staging-xcm-builder", + "staging-xcm-executor", +] + +[[package]] +name = "cumulus-ping" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f47128f797359951723e2d106a80e592d007bb7446c299958cdbafb1489ddbf0" +dependencies = [ + "cumulus-pallet-xcm", + "cumulus-primitives-core", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-runtime 39.0.2", + "staging-xcm 14.2.0", +] + +[[package]] +name = "cumulus-primitives-aura" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11e7825bcf3cc6c962a5b9b9f47e02dc381109e521d0bc00cad785c65da18471" +dependencies = [ + "parity-scale-codec", + "polkadot-core-primitives", + "polkadot-primitives 15.0.0", + "sp-api", + "sp-consensus-aura", + "sp-runtime 39.0.2", +] + +[[package]] +name = "cumulus-primitives-core" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c6b5221a4a3097f2ebef66c84c1e6d7a0b8ec7e63f2bd5ae04c1e6d3fc7514e" +dependencies = [ + "parity-scale-codec", + "polkadot-core-primitives", + "polkadot-parachain-primitives", + "polkadot-primitives 16.0.0", + "scale-info", + "sp-api", + "sp-runtime 39.0.2", + "sp-trie 37.0.0", + "staging-xcm 14.2.0", +] + +[[package]] +name = "cumulus-primitives-parachain-inherent" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "842a694901e04a62d88995418dec35c22f7dba2b34d32d2b8de37d6b92f973ff" +dependencies = [ + "async-trait", + "cumulus-primitives-core", + "parity-scale-codec", + "scale-info", + "sp-core 34.0.0", + "sp-inherents", + "sp-trie 37.0.0", +] + +[[package]] +name = "cumulus-primitives-proof-size-hostfunction" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "421f03af054aac7c89e87a49e47964886e53a8d7395990eab27b6f201d42524f" +dependencies = [ + "sp-externalities 0.29.0", + "sp-runtime-interface 28.0.0", + "sp-trie 37.0.0", +] + +[[package]] +name = "cumulus-primitives-storage-weight-reclaim" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fc49dfec0ba3438afad73787736cc0dba88d15b5855881f12a4d8b812a72927" +dependencies = [ + "cumulus-primitives-core", + "cumulus-primitives-proof-size-hostfunction", + "docify", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-runtime 39.0.2", +] + +[[package]] +name = "cumulus-primitives-timestamp" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33cffb8f010f39ac36b31d38994b8f9d9256d9b5e495d96b4ec59d3e30852d53" +dependencies = [ + "cumulus-primitives-core", + "sp-inherents", + "sp-timestamp", +] + +[[package]] +name = "cumulus-primitives-utility" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bdcf4d46dd93f1e6d5dd6d379133566a44042ba6476d04bdcbdb4981c622ae4" +dependencies = [ + "cumulus-primitives-core", + "frame-support", + "log", + "pallet-asset-conversion", + "parity-scale-codec", + "polkadot-runtime-common", + "sp-runtime 39.0.2", + "staging-xcm 14.2.0", + "staging-xcm-builder", + "staging-xcm-executor", +] + +[[package]] +name = "cumulus-test-relay-sproof-builder" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e570e41c3f05a8143ebff967bbb0c7dcaaa6f0bebd8639b9418b8005b13eda03" +dependencies = [ + "cumulus-primitives-core", + "parity-scale-codec", + "polkadot-primitives 16.0.0", + "sp-runtime 39.0.2", + "sp-state-machine 0.43.0", + "sp-trie 37.0.0", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -1492,7 +2663,7 @@ dependencies = [ "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", - "rustc_version", + "rustc_version 0.4.1", "subtle", "zeroize", ] @@ -1505,51 +2676,66 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] name = "cxx" -version = "1.0.128" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54ccead7d199d584d139148b04b4a368d1ec7556a1d9ea2548febb1b9d49f9a4" +checksum = "05e1ec88093d2abd9cf1b09ffd979136b8e922bf31cad966a8fe0d73233112ef" dependencies = [ "cc", + "cxxbridge-cmd", "cxxbridge-flags", "cxxbridge-macro", + "foldhash", "link-cplusplus", ] [[package]] name = "cxx-build" -version = "1.0.128" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77953e99f01508f89f55c494bfa867171ef3a6c8cea03d26975368f2121a5c1" +checksum = "9afa390d956ee7ccb41aeed7ed7856ab3ffb4fc587e7216be7e0f83e949b4e6c" dependencies = [ "cc", "codespan-reporting", - "once_cell", "proc-macro2", "quote", "scratch", - "syn 2.0.89", + "syn 2.0.90", +] + +[[package]] +name = "cxxbridge-cmd" +version = "1.0.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c23bfff654d6227cbc83de8e059d2f8678ede5fc3a6c5a35d5c379983cc61e6" +dependencies = [ + "clap", + "codespan-reporting", + "proc-macro2", + "quote", + "syn 2.0.90", ] [[package]] name = "cxxbridge-flags" -version = "1.0.128" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65777e06cc48f0cb0152024c77d6cf9e4bdb4408e7b48bea993d42fa0f5b02b6" +checksum = "f7c01b36e22051bc6928a78583f1621abaaf7621561c2ada1b00f7878fbe2caa" [[package]] name = "cxxbridge-macro" -version = "1.0.128" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98532a60dedaebc4848cb2cba5023337cc9ea3af16a5b062633fabfd9f18fb60" +checksum = "f6e14013136fac689345d17b9a6df55977251f11d333c0a571e8d963b55e1f95" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "rustversion", + "syn 2.0.90", ] [[package]] @@ -1597,7 +2783,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1619,7 +2805,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core 0.20.10", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1687,7 +2873,7 @@ checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1698,18 +2884,18 @@ checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] name = "derive_arbitrary" -version = "1.3.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1721,8 +2907,8 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "rustc_version", - "syn 2.0.89", + "rustc_version 0.4.1", + "syn 2.0.90", ] [[package]] @@ -1742,7 +2928,8 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", + "unicode-xid", ] [[package]] @@ -1784,6 +2971,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + [[package]] name = "dirs" version = "5.0.1" @@ -1806,14 +3003,25 @@ dependencies = [ ] [[package]] -name = "displaydoc" -version = "0.2.5" +name = "dirs-sys-next" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1824,18 +3032,18 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "docify" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a2f138ad521dc4a2ced1a4576148a6a610b4c5923933b062a263130a6802ce" +checksum = "a772b62b1837c8f060432ddcc10b17aae1453ef17617a99bc07789252d2a5896" dependencies = [ "docify_macros", ] [[package]] name = "docify_macros" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a081e51fb188742f5a7a1164ad752121abcb22874b21e2c3b0dd040c515fdad" +checksum = "60e6be249b0a462a14784a99b19bf35a667bb5e09de611738bb7362fa4c95ff7" dependencies = [ "common-path", "derive-syn-parse", @@ -1843,7 +3051,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.89", + "syn 2.0.90", "termcolor", "toml 0.8.19", "walkdir", @@ -1867,6 +3075,12 @@ dependencies = [ "shared_child", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "dyn-clonable" version = "0.9.0" @@ -1997,13 +3211,44 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.34" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] +[[package]] +name = "enumflags2" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" +dependencies = [ + "enumflags2_derive", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "enumn" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "env_filter" version = "0.1.2" @@ -2014,6 +3259,19 @@ dependencies = [ "regex", ] +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + [[package]] name = "env_logger" version = "0.11.5" @@ -2041,12 +3299,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2055,6 +3313,47 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5692dd7b5a1978a5aeb0ce83b7655c58ca8efdcb79d21036ea249da95afec2c6" +[[package]] +name = "ethabi-decode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d398648d65820a727d6a81e58b962f874473396a047e4c30bafe3240953417" +dependencies = [ + "ethereum-types", + "tiny-keccak", +] + +[[package]] +name = "ethbloom" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" +dependencies = [ + "crunchy", + "fixed-hash", + "impl-codec 0.6.0", + "impl-rlp", + "impl-serde 0.4.0", + "scale-info", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-codec 0.6.0", + "impl-rlp", + "impl-serde 0.4.0", + "primitive-types 0.12.2", + "scale-info", + "uint 0.9.5", +] + [[package]] name = "event-listener" version = "4.0.3" @@ -2078,9 +3377,9 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" dependencies = [ "event-listener 5.3.1", "pin-project-lite", @@ -2098,7 +3397,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2107,11 +3406,28 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + [[package]] name = "fastrand" -version = "2.1.1" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fastrlp" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec 0.7.6", + "auto_impl", + "bytes", +] [[package]] name = "ff" @@ -2139,6 +3455,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "file-per-thread-logger" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84f2e425d9790201ba4af4630191feac6dcc98765b118d4d18e91d23c2353866" +dependencies = [ + "env_logger 0.10.2", + "log", +] + [[package]] name = "filetime" version = "0.2.25" @@ -2151,6 +3477,22 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "finality-grandpa" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36530797b9bf31cd4ff126dcfee8170f86b00cfdcea3269d73133cc0415945c3" +dependencies = [ + "either", + "futures", + "futures-timer", + "log", + "num-traits", + "parity-scale-codec", + "parking_lot", + "scale-info", +] + [[package]] name = "finito" version = "0.1.0" @@ -2175,9 +3517,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", "miniz_oxide", @@ -2198,6 +3540,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" + [[package]] name = "foreign-types" version = "0.3.2" @@ -2223,142 +3571,417 @@ dependencies = [ ] [[package]] -name = "frame-metadata" -version = "15.1.0" +name = "frame-benchmarking" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "878babb0b136e731cc77ec2fd883ff02745ff21e6fb662729953d44923df009c" +checksum = "a01bdd47c2d541b38bd892da647d1e972c9d85b4ecd7094ad64f7600175da54d" dependencies = [ - "cfg-if", + "frame-support", + "frame-support-procedural", + "frame-system", + "linregress", + "log", "parity-scale-codec", + "paste", "scale-info", + "serde", + "sp-api", + "sp-application-crypto 38.0.0", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-runtime-interface 28.0.0", + "sp-storage 21.0.0", + "static_assertions", ] [[package]] -name = "frame-metadata" -version = "16.0.0" +name = "frame-benchmarking-pallet-pov" +version = "28.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cf1549fba25a6fcac22785b61698317d958e96cac72a59102ea45b9ae64692" +checksum = "6ffde6f573a63eeb1ccb7d2667c5741a11ce93bc30f33712e5326b9d8a811c29" dependencies = [ - "cfg-if", + "frame-benchmarking", + "frame-support", + "frame-system", "parity-scale-codec", "scale-info", - "serde", + "sp-io 38.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "fs-err" -version = "2.11.0" +name = "frame-decode" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" +checksum = "02d3379df61ff3dd871e2dde7d1bcdc0263e613c21c7579b149fd4f0ad9b1dc2" dependencies = [ - "autocfg", + "frame-metadata 17.0.0", + "parity-scale-codec", + "scale-decode 0.14.0", + "scale-info", + "scale-type-resolver 0.2.0", + "sp-crypto-hashing", ] [[package]] -name = "funty" -version = "2.0.0" +name = "frame-election-provider-solution-type" +version = "14.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +checksum = "8156f209055d352994ecd49e19658c6b469d7c6de923bd79868957d0dcfb6f71" +dependencies = [ + "proc-macro-crate 3.2.0", + "proc-macro2", + "quote", + "syn 2.0.90", +] [[package]] -name = "futures" -version = "0.3.30" +name = "frame-election-provider-support" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "c36f5116192c63d39f1b4556fa30ac7db5a6a52575fa241b045f7dfa82ecc2be" dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", + "frame-election-provider-solution-type", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-arithmetic 26.0.0", + "sp-core 34.0.0", + "sp-npos-elections", + "sp-runtime 39.0.2", ] [[package]] -name = "futures-channel" -version = "0.3.30" +name = "frame-executive" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "c365bf3879de25bbee28e9584096955a02fbe8d7e7624e10675800317f1cee5b" dependencies = [ - "futures-core", - "futures-sink", + "aquamarine", + "frame-support", + "frame-system", + "frame-try-runtime", + "log", + "parity-scale-codec", + "scale-info", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-tracing 17.0.1", ] [[package]] -name = "futures-core" -version = "0.3.30" +name = "frame-metadata" +version = "15.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "878babb0b136e731cc77ec2fd883ff02745ff21e6fb662729953d44923df009c" +dependencies = [ + "cfg-if", + "parity-scale-codec", + "scale-info", +] [[package]] -name = "futures-executor" -version = "0.3.30" +name = "frame-metadata" +version = "16.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "87cf1549fba25a6fcac22785b61698317d958e96cac72a59102ea45b9ae64692" dependencies = [ - "futures-core", - "futures-task", - "futures-util", - "num_cpus", + "cfg-if", + "parity-scale-codec", + "scale-info", + "serde", ] [[package]] -name = "futures-io" -version = "0.3.30" +name = "frame-metadata" +version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "701bac17e9b55e0f95067c428ebcb46496587f08e8cf4ccc0fe5903bea10dbb8" +dependencies = [ + "cfg-if", + "parity-scale-codec", + "scale-info", + "serde", +] [[package]] -name = "futures-lite" -version = "2.3.0" +name = "frame-metadata-hash-extension" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +checksum = "56ac71dbd97039c49fdd69f416a4dd5d8da3652fdcafc3738b45772ad79eb4ec" dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", + "array-bytes", + "docify", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-runtime 39.0.2", ] [[package]] -name = "futures-macro" -version = "0.3.30" +name = "frame-support" +version = "38.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e44af69fa61bc5005ffe0339e198957e77f0f255704a9bee720da18a733e3dc" +dependencies = [ + "aquamarine", + "array-bytes", + "bitflags 1.3.2", + "docify", + "environmental", + "frame-metadata 16.0.0", + "frame-support-procedural", + "impl-trait-for-tuples", + "k256", + "log", + "macro_magic", + "parity-scale-codec", + "paste", + "scale-info", + "serde", + "serde_json", + "smallvec", + "sp-api", + "sp-arithmetic 26.0.0", + "sp-core 34.0.0", + "sp-crypto-hashing-proc-macro", + "sp-debug-derive", + "sp-genesis-builder", + "sp-inherents", + "sp-io 38.0.0", + "sp-metadata-ir", + "sp-runtime 39.0.2", + "sp-staking 36.0.0", + "sp-state-machine 0.43.0", + "sp-std", + "sp-tracing 17.0.1", + "sp-weights 31.0.0", + "static_assertions", + "tt-call", +] + +[[package]] +name = "frame-support-procedural" +version = "30.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "5e8f9b6bc1517a6fcbf0b2377e5c8c6d39f5bb7862b191a59a9992081d63972d" dependencies = [ + "Inflector", + "cfg-expr", + "derive-syn-parse", + "expander", + "frame-support-procedural-tools", + "itertools 0.11.0", + "macro_magic", + "proc-macro-warning", "proc-macro2", "quote", - "syn 2.0.89", + "sp-crypto-hashing", + "syn 2.0.90", ] [[package]] -name = "futures-sink" -version = "0.3.30" +name = "frame-support-procedural-tools" +version = "13.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "bead15a320be1764cdd50458c4cfacb23e0cee65f64f500f8e34136a94c7eeca" +dependencies = [ + "frame-support-procedural-tools-derive", + "proc-macro-crate 3.2.0", + "proc-macro2", + "quote", + "syn 2.0.90", +] [[package]] -name = "futures-task" -version = "0.3.30" +name = "frame-support-procedural-tools-derive" +version = "12.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "ed971c6435503a099bdac99fe4c5bea08981709e5b5a0a8535a1856f48561191" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] [[package]] -name = "futures-timer" -version = "3.0.3" +name = "frame-system" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" +checksum = "e3c7fa02f8c305496d2ae52edaecdb9d165f11afa965e05686d7d7dd1ce93611" +dependencies = [ + "cfg-if", + "docify", + "frame-support", + "log", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-std", + "sp-version", + "sp-weights 31.0.0", +] [[package]] -name = "futures-util" -version = "0.3.30" +name = "frame-system-benchmarking" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9693b2a736beb076e673520e1e8dee4fc128b8d35b020ef3e8a4b1b5ad63d9f2" dependencies = [ - "futures-channel", - "futures-core", + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core 34.0.0", + "sp-runtime 39.0.2", +] + +[[package]] +name = "frame-system-rpc-runtime-api" +version = "34.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "475c4f8604ba7e4f05cd2c881ba71105093e638b9591ec71a8db14a64b3b4ec3" +dependencies = [ + "docify", + "parity-scale-codec", + "sp-api", +] + +[[package]] +name = "frame-try-runtime" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83c811a5a1f5429c7fb5ebbf6cf9502d8f9b673fd395c12cf46c44a30a7daf0e" +dependencies = [ + "frame-support", + "parity-scale-codec", + "sp-api", + "sp-runtime 39.0.2", +] + +[[package]] +name = "fs-err" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" +dependencies = [ + "autocfg", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", + "num_cpus", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", "futures-io", "futures-macro", "futures-sink", @@ -2369,6 +3992,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -2401,22 +4033,42 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gimli" version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" dependencies = [ - "fallible-iterator", + "fallible-iterator 0.2.0", "indexmap 1.9.3", "stable_deref_trait", ] [[package]] name = "gimli" -version = "0.31.0" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +dependencies = [ + "fallible-iterator 0.3.0", + "stable_deref_trait", +] + +[[package]] +name = "gimli" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "git2" @@ -2482,7 +4134,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.5.0", + "indexmap 2.7.0", "slab", "tokio", "tokio-util", @@ -2491,17 +4143,17 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http 1.1.0", - "indexmap 2.5.0", + "http 1.2.0", + "indexmap 2.7.0", "slab", "tokio", "tokio-util", @@ -2552,6 +4204,17 @@ dependencies = [ "serde", ] +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "heck" version = "0.4.1" @@ -2588,6 +4251,21 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" +[[package]] +name = "hex-conservative" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +dependencies = [ + "arrayvec 0.7.6", +] + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + [[package]] name = "hkdf" version = "0.12.4" @@ -2649,9 +4327,9 @@ dependencies = [ [[package]] name = "http" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" dependencies = [ "bytes", "fnv", @@ -2676,7 +4354,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.1.0", + "http 1.2.0", ] [[package]] @@ -2687,7 +4365,7 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "pin-project-lite", ] @@ -2700,9 +4378,9 @@ checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -2727,9 +4405,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.30" +version = "0.14.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" +checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" dependencies = [ "bytes", "futures-channel", @@ -2751,15 +4429,15 @@ dependencies = [ [[package]] name = "hyper" -version = "1.4.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.6", - "http 1.1.0", + "h2 0.4.7", + "http 1.2.0", "http-body 1.0.1", "httparse", "httpdate", @@ -2777,7 +4455,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" dependencies = [ "hex", - "hyper 1.4.1", + "hyper 1.5.1", "hyper-util", "pin-project-lite", "tokio", @@ -2793,7 +4471,7 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http 0.2.12", - "hyper 0.14.30", + "hyper 0.14.31", "log", "rustls 0.21.12", "rustls-native-certs 0.6.3", @@ -2808,13 +4486,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", - "http 1.1.0", - "hyper 1.4.1", + "http 1.2.0", + "hyper 1.5.1", "hyper-util", - "rustls 0.23.13", + "rustls 0.23.19", "rustls-pki-types", "tokio", - "tokio-rustls 0.26.0", + "tokio-rustls 0.26.1", "tower-service", ] @@ -2824,7 +4502,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper 0.14.30", + "hyper 0.14.31", "pin-project-lite", "tokio", "tokio-io-timeout", @@ -2837,7 +4515,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper 0.14.30", + "hyper 0.14.31", "native-tls", "tokio", "tokio-native-tls", @@ -2851,7 +4529,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.4.1", + "hyper 1.5.1", "hyper-util", "native-tls", "tokio", @@ -2861,33 +4539,32 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.8" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da62f120a8a37763efb0cf8fdf264b884c7b8b9ac8660b900c8661030c00e6ba" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", - "hyper 1.4.1", + "hyper 1.5.1", "pin-project-lite", "socket2", "tokio", - "tower", "tower-service", "tracing", ] [[package]] -name = "hyperlocal-next" -version = "0.9.0" +name = "hyperlocal" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf569d43fa9848e510358c07b80f4adf34084ddc28c6a4a651ee8474c070dcc" +checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7" dependencies = [ "hex", "http-body-util", - "hyper 1.4.1", + "hyper 1.5.1", "hyper-util", "pin-project-lite", "tokio", @@ -2896,9 +4573,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -3032,7 +4709,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -3071,6 +4748,24 @@ dependencies = [ "parity-scale-codec", ] +[[package]] +name = "impl-codec" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67aa010c1e3da95bf151bd8b4c059b2ed7e75387cdb969b4f8f2723a43f9941" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + [[package]] name = "impl-serde" version = "0.4.0" @@ -3080,15 +4775,43 @@ dependencies = [ "serde", ] +[[package]] +name = "impl-serde" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a143eada6a1ec4aefa5049037a26a6d597bfd64f8c026d07b77133e02b7dd0b" +dependencies = [ + "serde", +] + [[package]] name = "impl-trait-for-tuples" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "include_dir" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", ] [[package]] @@ -3110,12 +4833,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.5.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.15.2", "serde", ] @@ -3127,80 +4850,81 @@ checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" [[package]] name = "indicatif" -version = "0.17.8" +version = "0.17.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" +checksum = "cbf675b85ed934d3c67b5c5469701eec7db22689d0a2139d856e0925fa28b281" dependencies = [ "console", - "instant", "number_prefix", "portable-atomic", - "unicode-width", + "unicode-width 0.2.0", + "web-time", ] [[package]] name = "ink_allocator" -version = "5.0.0" +version = "5.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cee56055bac6d928d425e944c5f3b69baa33c9635822fd1c00cd4afc70fde3e" +checksum = "2ec348ce75d284bc2e698187dc01da416a52dfa2d685e2a57d04e9e580447df0" dependencies = [ "cfg-if", ] [[package]] name = "ink_engine" -version = "5.0.0" +version = "5.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f357e2e867f4e222ffc4015a6e61d1073548de89f70a4e36a8b0385562777fa" +checksum = "d273f2aa983d04a6476d3c5ac76ddbef07555664b88f923996e7465e261dda48" dependencies = [ "blake2", - "derive_more 0.99.18", + "derive_more 1.0.0", "ink_primitives", - "pallet-contracts-uapi-next", + "pallet-contracts-uapi 9.0.0", "parity-scale-codec", - "secp256k1", + "secp256k1 0.28.2", "sha2 0.10.8", "sha3", ] [[package]] name = "ink_env" -version = "5.0.0" +version = "5.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42cec50b7e4f8406aab25801b015d3802a52d76cfbe48ce11cfb4200fa88e296" +checksum = "f9ee6089a1836c2e92d00be97d42308b7fb2c2b51ff6150b1130166a58107092" dependencies = [ "blake2", "cfg-if", "const_env", - "derive_more 0.99.18", + "derive_more 1.0.0", "ink_allocator", "ink_engine", "ink_prelude", "ink_primitives", "ink_storage_traits", "num-traits", - "pallet-contracts-uapi-next", + "pallet-contracts-uapi 9.0.0", "parity-scale-codec", "paste", "rlibc", - "scale-decode 0.10.0", - "scale-encode 0.5.0", + "scale-decode 0.11.1", + "scale-encode 0.6.0", "scale-info", "schnorrkel", - "secp256k1", + "secp256k1 0.28.2", "sha2 0.10.8", "sha3", + "staging-xcm 11.0.0", "static_assertions", ] [[package]] name = "ink_metadata" -version = "5.0.0" +version = "5.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a98fcc0ff9292ff68c7ee7b84c93533c9ff13859ec3b148faa822e2da9954fe6" +checksum = "27135c651274087ba0578d2c07866c31d8dd481ae8de5bb7295fe3931491aa80" dependencies = [ - "derive_more 0.99.18", - "impl-serde", + "derive_more 1.0.0", + "impl-serde 0.4.0", "ink_prelude", "ink_primitives", "linkme", @@ -3212,33 +4936,33 @@ dependencies = [ [[package]] name = "ink_prelude" -version = "5.0.0" +version = "5.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea1734d058c80aa72e59c8ae75624fd8a51791efba21469f273156c0f4cad5c9" +checksum = "b29c9b7f686f4305f523bca5e2ae6f22a09531ec2bf0a9498cdc877959f70ad0" dependencies = [ "cfg-if", ] [[package]] name = "ink_primitives" -version = "5.0.0" +version = "5.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec35ef7f45e67a53b6142d7e7f18e6d9292d76c3a2a1da14cf8423e481813d" +checksum = "a530c1b352a53176ea718f3a65f15003e54e0474ec12353ea0e0e5bb60b25741" dependencies = [ - "derive_more 0.99.18", + "derive_more 1.0.0", "ink_prelude", "parity-scale-codec", - "scale-decode 0.10.0", - "scale-encode 0.5.0", + "scale-decode 0.11.1", + "scale-encode 0.6.0", "scale-info", "xxhash-rust", ] [[package]] name = "ink_storage_traits" -version = "5.0.0" +version = "5.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83ce49e3d2935fc1ec3e73117119712b187d3123339f6a31624e92f75fa2293d" +checksum = "fde9b3f4a1e355682e5d13fd5639e5da4d0a2029537292e05a4255ea1169663e" dependencies = [ "ink_metadata", "ink_prelude", @@ -3287,9 +5011,20 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.10.0" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" + +[[package]] +name = "is-terminal" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +dependencies = [ + "hermit-abi 0.4.0", + "libc", + "windows-sys 0.52.0", +] [[package]] name = "is_terminal_polyfill" @@ -3306,6 +5041,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.12.1" @@ -3315,11 +5059,20 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jni" @@ -3331,7 +5084,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror", + "thiserror 1.0.69", "walkdir", ] @@ -3358,10 +5111,11 @@ checksum = "72167d68f5fce3b8655487b8038691a3c9984ee769590f93f2a631f4ad64e4f5" [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -3373,7 +5127,7 @@ checksum = "ec9ad60d674508f3ca8f380a928cfe7b096bc729c4e2dbfe3852bc45da3ab30b" dependencies = [ "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -3386,7 +5140,7 @@ dependencies = [ "pest_derive", "regex", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -3409,12 +5163,24 @@ checksum = "62b089779ad7f80768693755a031cc14a7766aba707cbe886674e3f79e9b7e47" dependencies = [ "jsonrpsee-core 0.23.2", "jsonrpsee-types 0.23.2", - "jsonrpsee-ws-client", + "jsonrpsee-ws-client 0.23.2", ] [[package]] -name = "jsonrpsee-client-transport" -version = "0.22.5" +name = "jsonrpsee" +version = "0.24.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5c71d8c1a731cc4227c2f698d377e7848ca12c8a48866fc5e6951c43a4db843" +dependencies = [ + "jsonrpsee-client-transport 0.24.7", + "jsonrpsee-core 0.24.7", + "jsonrpsee-types 0.24.7", + "jsonrpsee-ws-client 0.24.7", +] + +[[package]] +name = "jsonrpsee-client-transport" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4978087a58c3ab02efc5b07c5e5e2803024536106fd5506f558db172c889b3aa" dependencies = [ @@ -3425,7 +5191,7 @@ dependencies = [ "rustls-native-certs 0.7.3", "rustls-pki-types", "soketto 0.7.1", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-rustls 0.25.0", "tokio-util", @@ -3441,16 +5207,39 @@ checksum = "08163edd8bcc466c33d79e10f695cdc98c00d1e6ddfb95cec41b6b0279dd5432" dependencies = [ "base64 0.22.1", "futures-util", - "http 1.1.0", + "http 1.2.0", "jsonrpsee-core 0.23.2", "pin-project", - "rustls 0.23.13", + "rustls 0.23.19", + "rustls-pki-types", + "rustls-platform-verifier", + "soketto 0.8.1", + "thiserror 1.0.69", + "tokio", + "tokio-rustls 0.26.1", + "tokio-util", + "tracing", + "url", +] + +[[package]] +name = "jsonrpsee-client-transport" +version = "0.24.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "548125b159ba1314104f5bb5f38519e03a41862786aa3925cf349aae9cdd546e" +dependencies = [ + "base64 0.22.1", + "futures-util", + "http 1.2.0", + "jsonrpsee-core 0.24.7", + "pin-project", + "rustls 0.23.19", "rustls-pki-types", "rustls-platform-verifier", - "soketto 0.8.0", - "thiserror", + "soketto 0.8.1", + "thiserror 1.0.69", "tokio", - "tokio-rustls 0.26.0", + "tokio-rustls 0.26.1", "tokio-util", "tracing", "url", @@ -3467,13 +5256,13 @@ dependencies = [ "beef", "futures-timer", "futures-util", - "hyper 0.14.30", + "hyper 0.14.31", "jsonrpsee-types 0.22.5", "pin-project", - "rustc-hash", + "rustc-hash 1.1.0", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-stream", "tracing", @@ -3492,10 +5281,30 @@ dependencies = [ "futures-util", "jsonrpsee-types 0.23.2", "pin-project", - "rustc-hash", + "rustc-hash 1.1.0", + "serde", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "jsonrpsee-core" +version = "0.24.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2882f6f8acb9fdaec7cefc4fd607119a9bd709831df7d7672a1d3b644628280" +dependencies = [ + "async-trait", + "futures-timer", + "futures-util", + "jsonrpsee-types 0.24.7", + "pin-project", + "rustc-hash 2.1.0", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-stream", "tracing", @@ -3508,13 +5317,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ccf93fc4a0bfe05d851d37d7c32b7f370fe94336b52a2f0efc5f1981895c2e5" dependencies = [ "async-trait", - "hyper 0.14.30", + "hyper 0.14.31", "hyper-rustls 0.24.2", "jsonrpsee-core 0.22.5", "jsonrpsee-types 0.22.5", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "tokio", "tower", "tracing", @@ -3531,7 +5340,7 @@ dependencies = [ "beef", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -3541,10 +5350,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c465fbe385238e861fdc4d1c85e04ada6c1fd246161d26385c1b311724d2af" dependencies = [ "beef", - "http 1.1.0", + "http 1.2.0", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "jsonrpsee-types" +version = "0.24.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a178c60086f24cc35bb82f57c651d0d25d99c4742b4d335de04e97fa1f08a8a1" +dependencies = [ + "http 1.2.0", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -3553,18 +5374,31 @@ version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c28759775f5cb2f1ea9667672d3fe2b0e701d1f4b7b67954e60afe7fd058b5e" dependencies = [ - "http 1.1.0", + "http 1.2.0", "jsonrpsee-client-transport 0.23.2", "jsonrpsee-core 0.23.2", "jsonrpsee-types 0.23.2", "url", ] +[[package]] +name = "jsonrpsee-ws-client" +version = "0.24.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fe322e0896d0955a3ebdd5bf813571c53fea29edd713bc315b76620b327e86d" +dependencies = [ + "http 1.2.0", + "jsonrpsee-client-transport 0.24.7", + "jsonrpsee-core 0.24.7", + "jsonrpsee-types 0.24.7", + "url", +] + [[package]] name = "k256" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" dependencies = [ "cfg-if", "ecdsa", @@ -3597,6 +5431,16 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "keccak-hash" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e1b8590eb6148af2ea2d75f38e7d29f5ca970d5a4df456b3ef19b8b415d0264" +dependencies = [ + "primitive-types 0.13.1", + "tiny-keccak", +] + [[package]] name = "kube" version = "0.87.2" @@ -3623,7 +5467,7 @@ dependencies = [ "home", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.30", + "hyper 0.14.31", "hyper-rustls 0.24.2", "hyper-timeout", "jsonpath-rust", @@ -3634,11 +5478,11 @@ dependencies = [ "rand", "rustls 0.21.12", "rustls-pemfile 1.0.4", - "secrecy", + "secrecy 0.8.0", "serde", "serde_json", "serde_yaml", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-tungstenite", "tokio-util", @@ -3661,7 +5505,7 @@ dependencies = [ "once_cell", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -3684,7 +5528,7 @@ dependencies = [ "serde", "serde_json", "smallvec", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-util", "tracing", @@ -3704,9 +5548,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "libgit2-sys" @@ -3724,9 +5568,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libp2p" @@ -3748,7 +5592,7 @@ dependencies = [ "multiaddr", "pin-project", "rw-stream-sink", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -3798,16 +5642,16 @@ dependencies = [ "rand", "rw-stream-sink", "smallvec", - "thiserror", - "unsigned-varint", + "thiserror 1.0.69", + "unsigned-varint 0.7.2", "void", ] [[package]] name = "libp2p-identity" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cca1eb2bc1fd29f099f3daaab7effd01e1a54b7c577d0ed082521034d912e8" +checksum = "257b5621d159b32282eac446bed6670c39c7dc68a200a992d8f056afa0066f6d" dependencies = [ "bs58", "ed25519-dalek", @@ -3816,7 +5660,7 @@ dependencies = [ "quick-protobuf", "rand", "sha2 0.10.8", - "thiserror", + "thiserror 1.0.69", "tracing", "zeroize", ] @@ -3938,22 +5782,31 @@ dependencies = [ [[package]] name = "linkme" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c943daedff228392b791b33bba32e75737756e80a613e32e246c6ce9cbab20a" +checksum = "566336154b9e58a4f055f6dd4cbab62c7dc0826ce3c0a04e63b2d2ecd784cdae" dependencies = [ "linkme-impl", ] [[package]] name = "linkme-impl" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb26336e6dc7cc76e7927d2c9e7e3bb376d7af65a6f56a0b16c47d18a9b1abc5" +checksum = "edbe595006d355eaf9ae11db92707d4338cd2384d16866131cc1afdbdd35d8d9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", +] + +[[package]] +name = "linregress" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9eda9dcf4f2a99787827661f312ac3219292549c2ee992bf9a6248ffb066bf7" +dependencies = [ + "nalgebra", ] [[package]] @@ -3992,11 +5845,20 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "lru" -version = "0.12.4" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" +checksum = "b6e8aaa3f231bb4bd57b84b2d5dc3ae7f350265df8aa96492e0bc394a1571909" dependencies = [ - "hashbrown 0.14.5", + "hashbrown 0.12.3", +] + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.2", ] [[package]] @@ -4008,6 +5870,54 @@ dependencies = [ "libc", ] +[[package]] +name = "macro_magic" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc33f9f0351468d26fbc53d9ce00a096c8522ecb42f19b50f34f2c422f76d21d" +dependencies = [ + "macro_magic_core", + "macro_magic_macros", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "macro_magic_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1687dc887e42f352865a393acae7cf79d98fab6351cde1f58e9e057da89bf150" +dependencies = [ + "const-random", + "derive-syn-parse", + "macro_magic_core_macros", + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "macro_magic_core_macros" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b02abfe41815b5bd98dbd4260173db2c116dda171dc0fe7838cb206333b83308" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "macro_magic_macros" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ea28ee64b88876bf45277ed9a5817c1817df061a74f2b988971a12570e5869" +dependencies = [ + "macro_magic_core", + "quote", + "syn 2.0.90", +] + [[package]] name = "matchers" version = "0.0.1" @@ -4017,6 +5927,25 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matrixmultiply" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" +dependencies = [ + "autocfg", + "rawpointer", +] + [[package]] name = "memchr" version = "2.7.4" @@ -4029,7 +5958,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" dependencies = [ - "rustix 0.38.36", + "rustix 0.38.42", ] [[package]] @@ -4095,42 +6024,30 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.11" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", "log", "wasi", - "windows-sys 0.48.0", -] - -[[package]] -name = "mio" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" -dependencies = [ - "hermit-abi 0.3.9", - "libc", - "wasi", "windows-sys 0.52.0", ] [[package]] name = "mockito" -version = "1.5.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b34bd91b9e5c5b06338d392463e1318d683cf82ec3d3af4014609be6e2108d" +checksum = "652cd6d169a36eaf9d1e6bce1a221130439a966d7f27858af66a33a66e9c4ee2" dependencies = [ "assert-json-diff", "bytes", "colored", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "http-body-util", - "hyper 1.4.1", + "hyper 1.5.1", "hyper-util", "log", "rand", @@ -4141,11 +6058,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "multi-stash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685a9ac4b61f4e728e1d2c6a7844609c16527aeb5e6c865915c08e619c16410f" + [[package]] name = "multiaddr" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b852bc02a2da5feed68cd14fa50d0774b92790a5bdbfa932a813926c8472070" +checksum = "fe6351f60b488e04c1d21bc69e56b89cb3f5e8f5d22557d6e8031bdfd79b6961" dependencies = [ "arrayref", "byteorder", @@ -4156,7 +6079,7 @@ dependencies = [ "percent-encoding", "serde", "static_assertions", - "unsigned-varint", + "unsigned-varint 0.8.0", "url", ] @@ -4173,12 +6096,12 @@ dependencies = [ [[package]] name = "multihash" -version = "0.19.1" +version = "0.19.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076d548d76a0e2a0d4ab471d0b1c36c577786dfc4471242035d97a12a735c492" +checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" dependencies = [ "core2", - "unsigned-varint", + "unsigned-varint 0.8.0", ] [[package]] @@ -4192,7 +6115,22 @@ dependencies = [ "log", "pin-project", "smallvec", - "unsigned-varint", + "unsigned-varint 0.7.2", +] + +[[package]] +name = "nalgebra" +version = "0.33.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26aecdf64b707efd1310e3544d709c5c0ac61c13756046aaaba41be5c4f66a3b" +dependencies = [ + "approx", + "matrixmultiply", + "num-complex", + "num-rational", + "num-traits", + "simba", + "typenum", ] [[package]] @@ -4270,6 +6208,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -4280,12 +6228,32 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "num-format" version = "0.4.4" @@ -4323,6 +6291,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -4355,18 +6324,27 @@ dependencies = [ [[package]] name = "object" -version = "0.36.4" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "object" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "opaque-debug" @@ -4376,9 +6354,9 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.66" +version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ "bitflags 2.6.0", "cfg-if", @@ -4397,7 +6375,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -4408,18 +6386,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.3.2+3.3.2" +version = "300.4.1+3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a211a18d945ef7e648cc6e0058f4c548ee46aab922ea203e0d30e966ea23647b" +checksum = "faa4eac4138c62414b5622d1b31c5c304f34b406b013c079c2bbc652fdd6678c" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.103" +version = "0.9.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" dependencies = [ "cc", "libc", @@ -4445,9 +6423,9 @@ dependencies = [ [[package]] name = "os_info" -version = "3.8.2" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092" +checksum = "e5ca711d8b83edbb00b44d504503cd247c9c0bd8b0fa2694f2a1a3d8165379ce" dependencies = [ "log", "windows-sys 0.52.0", @@ -4464,2421 +6442,7001 @@ dependencies = [ ] [[package]] -name = "pallet-contracts-uapi-next" -version = "6.0.3" +name = "overload" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd549c16296ea5b2eb7c65c56aba548b286c1be4d7675b424ff6ccb8319c97a9" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "pallet-alliance" +version = "37.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59378a648a0aa279a4b10650366c3389cd0a1239b1876f74bfecd268eecb086b" dependencies = [ - "bitflags 1.3.2", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-collective", + "pallet-identity", "parity-scale-codec", - "paste", - "polkavm-derive 0.5.0", "scale-info", + "sp-core 34.0.0", + "sp-crypto-hashing", + "sp-io 38.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "parity-bip39" -version = "2.0.1" +name = "pallet-asset-conversion" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e69bf016dc406eff7d53a7d3f7cf1c2e72c82b9088aac1118591e36dd2cd3e9" +checksum = "33f0078659ae95efe6a1bf138ab5250bc41ab98f22ff3651d0208684f08ae797" dependencies = [ - "bitcoin_hashes 0.13.0", - "rand", - "rand_core 0.6.4", - "serde", - "unicode-normalization", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-arithmetic 26.0.0", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "parity-scale-codec" -version = "3.6.12" +name = "pallet-asset-conversion-ops" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" +checksum = "3edbeda834bcd6660f311d4eead3dabdf6d385b7308ac75b0fae941a960e6c3a" dependencies = [ - "arrayvec 0.7.6", - "bitvec", - "byte-slice-cast", - "bytes", - "impl-trait-for-tuples", - "parity-scale-codec-derive", - "serde", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-asset-conversion", + "parity-scale-codec", + "scale-info", + "sp-arithmetic 26.0.0", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "parity-scale-codec-derive" -version = "3.6.12" +name = "pallet-asset-conversion-tx-payment" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" +checksum = "1ab66c4c22ac0f20e620a954ce7ba050118d6d8011e2d02df599309502064e98" dependencies = [ - "proc-macro-crate 3.2.0", - "proc-macro2", - "quote", - "syn 1.0.109", + "frame-support", + "frame-system", + "pallet-asset-conversion", + "pallet-transaction-payment", + "parity-scale-codec", + "scale-info", + "sp-runtime 39.0.2", ] [[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "parking_lot" -version = "0.12.3" +name = "pallet-asset-rate" +version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "71b2149aa741bc39466bbcc92d9d0ab6e9adcf39d2790443a735ad573b3191e7" dependencies = [ - "lock_api", - "parking_lot_core", + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core 34.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "parking_lot_core" -version = "0.9.10" +name = "pallet-asset-tx-payment" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "406a486466d15acc48c99420191f96f1af018f3381fde829c467aba489030f18" dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.52.6", + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-transaction-payment", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "password-hash" -version = "0.5.0" +name = "pallet-assets" +version = "40.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +checksum = "f45f4eb6027fc34c4650e0ed6a7e57ed3335cc364be74b4531f714237676bcee" dependencies = [ - "base64ct", - "rand_core 0.6.4", - "subtle", + "frame-benchmarking", + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "scale-info", + "sp-core 34.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "paste" -version = "1.0.15" +name = "pallet-assets-freezer" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +checksum = "127adc2250b89416b940850ce2175dab10a9297b503b1fcb05dc555bd9bd3207" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-assets", + "parity-scale-codec", + "scale-info", + "sp-runtime 39.0.2", +] [[package]] -name = "pbkdf2" -version = "0.12.2" +name = "pallet-atomic-swap" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +checksum = "15906a685adeabe6027e49c814a34066222dd6136187a8a79c213d0d739b6634" dependencies = [ - "digest 0.10.7", - "password-hash", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "pem" -version = "3.0.4" +name = "pallet-aura" +version = "37.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +checksum = "b31da6e794d655d1f9c4da6557a57399538d75905a7862a2ed3f7e5fb711d7e4" dependencies = [ - "base64 0.22.1", - "serde", + "frame-support", + "frame-system", + "log", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-application-crypto 38.0.0", + "sp-consensus-aura", + "sp-runtime 39.0.2", ] [[package]] -name = "percent-encoding" -version = "2.3.1" +name = "pallet-authority-discovery" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "ffb0208f0538d58dcb78ce1ff5e6e8641c5f37b23b20b05587e51da30ab13541" +dependencies = [ + "frame-support", + "frame-system", + "pallet-session", + "parity-scale-codec", + "scale-info", + "sp-application-crypto 38.0.0", + "sp-authority-discovery", + "sp-runtime 39.0.2", +] [[package]] -name = "pest" -version = "2.7.12" +name = "pallet-authorship" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c73c26c01b8c87956cea613c907c9d6ecffd8d18a2a5908e5de0adfaa185cea" +checksum = "625d47577cabbe1318ccec5d612e2379002d1b6af1ab6edcef3243c66ec246df" dependencies = [ - "memchr", - "thiserror", - "ucd-trie", + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "parity-scale-codec", + "scale-info", + "sp-runtime 39.0.2", ] [[package]] -name = "pest_derive" -version = "2.7.12" +name = "pallet-babe" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "664d22978e2815783adbdd2c588b455b1bd625299ce36b2a99881ac9627e6d8d" +checksum = "4ee096c0def13832475b340d00121025e0225de29604d44bc6dfcaa294c995b4" dependencies = [ - "pest", - "pest_generator", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-authorship", + "pallet-session", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-application-crypto 38.0.0", + "sp-consensus-babe", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-session", + "sp-staking 36.0.0", ] [[package]] -name = "pest_generator" -version = "2.7.12" +name = "pallet-bags-list" +version = "37.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d5487022d5d33f4c30d91c22afa240ce2a644e87fe08caad974d4eab6badbe" +checksum = "0fd23a6f94ba9c1e57c8a7f8a41327d132903a79c55c0c83f36cbae19946cf10" dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn 2.0.89", + "aquamarine", + "docify", + "frame-benchmarking", + "frame-election-provider-support", + "frame-support", + "frame-system", + "log", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-tracing 17.0.1", ] [[package]] -name = "pest_meta" -version = "2.7.12" +name = "pallet-balances" +version = "39.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0091754bbd0ea592c4deb3a122ce8ecbb0753b738aa82bc055fcc2eccc8d8174" +checksum = "5c6945b078919acb14d126490e4b0973a688568b30142476ca69c6df2bed27ad" dependencies = [ - "once_cell", - "pest", - "sha2 0.10.8", + "docify", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-runtime 39.0.2", ] [[package]] -name = "pin-project" -version = "1.1.5" +name = "pallet-beefy" +version = "39.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "014d177a3aba19ac144fc6b2b5eb94930b9874734b91fd014902b6706288bb5f" dependencies = [ - "pin-project-internal", + "frame-support", + "frame-system", + "log", + "pallet-authorship", + "pallet-session", + "parity-scale-codec", + "scale-info", + "serde", + "sp-consensus-beefy", + "sp-runtime 39.0.2", + "sp-session", + "sp-staking 36.0.0", ] [[package]] -name = "pin-project-internal" -version = "1.1.5" +name = "pallet-beefy-mmr" +version = "39.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "9c64f536e7f04cf3a0a17fdf20870ddb3d63a7690419c40f75cfd2f72b6e6d22" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.89", + "array-bytes", + "binary-merkle-tree", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-beefy", + "pallet-mmr", + "pallet-session", + "parity-scale-codec", + "scale-info", + "serde", + "sp-api", + "sp-consensus-beefy", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-state-machine 0.43.0", ] [[package]] -name = "pin-project-lite" -version = "0.2.14" +name = "pallet-bounties" +version = "37.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "a1163f9cd8bbc47ec0c6900a3ca67689d8d7b40bedfa6aa22b1b3c6027b1090e" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-treasury", + "parity-scale-codec", + "scale-info", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", +] [[package]] -name = "pin-utils" -version = "0.1.0" +name = "pallet-bridge-grandpa" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "1d825fbed9fb68bc5d344311653dc0f69caeabe647365abf79a539310b2245f6" +dependencies = [ + "bp-header-chain", + "bp-runtime", + "bp-test-utils", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-consensus-grandpa", + "sp-runtime 39.0.2", + "sp-std", +] [[package]] -name = "piper" -version = "0.2.4" +name = "pallet-bridge-messages" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +checksum = "a1decdc9fb885e46eb17f850aa14f8cf39e17f31574aa6a5fa1a9e603cc526a2" dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", + "bp-header-chain", + "bp-messages", + "bp-runtime", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-runtime 39.0.2", + "sp-std", + "sp-trie 37.0.0", ] [[package]] -name = "pkcs8" -version = "0.10.2" +name = "pallet-bridge-parachains" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +checksum = "41450a8d214f20eaff57aeca8e647b20c0df7d66871ee2262609b90824bd4cca" dependencies = [ - "der", - "spki", + "bp-header-chain", + "bp-parachains", + "bp-polkadot-core", + "bp-runtime", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-bridge-grandpa", + "parity-scale-codec", + "scale-info", + "sp-runtime 39.0.2", + "sp-std", ] [[package]] -name = "pkg-config" -version = "0.3.30" +name = "pallet-bridge-relayers" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "2faead05455a965a0a0ec69ffa779933479b599e40bda809c0aa1efa72a39281" +dependencies = [ + "bp-header-chain", + "bp-messages", + "bp-relayers", + "bp-runtime", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-bridge-grandpa", + "pallet-bridge-messages", + "pallet-bridge-parachains", + "pallet-transaction-payment", + "parity-scale-codec", + "scale-info", + "sp-arithmetic 26.0.0", + "sp-runtime 39.0.2", + "sp-std", +] [[package]] -name = "polkavm-common" -version = "0.5.0" +name = "pallet-broker" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b4e215c80fe876147f3d58158d5dfeae7dabdd6047e175af77095b78d0035c" +checksum = "3043c90106d88cb93fcf0d9b6d19418f11f44cc2b11873414aec3b46044a24ea" +dependencies = [ + "bitvec", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-arithmetic 26.0.0", + "sp-core 34.0.0", + "sp-runtime 39.0.2", +] [[package]] -name = "polkavm-common" -version = "0.8.0" +name = "pallet-child-bounties" +version = "37.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92c99f7eee94e7be43ba37eef65ad0ee8cbaf89b7c00001c3f6d2be985cb1817" +checksum = "c7f3bc38ae6584b5f57e4de3e49e5184bfc0f20692829530ae1465ffe04e09e7" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-bounties", + "pallet-treasury", + "parity-scale-codec", + "scale-info", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", +] [[package]] -name = "polkavm-common" -version = "0.9.0" +name = "pallet-collator-selection" +version = "19.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d9428a5cfcc85c5d7b9fc4b6a18c4b802d0173d768182a51cc7751640f08b92" +checksum = "658798d70c9054165169f6a6a96cfa9d6a5e7d24a524bc19825bf17fcbc5cc5a" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-authorship", + "pallet-balances", + "pallet-session", + "parity-scale-codec", + "rand", + "scale-info", + "sp-runtime 39.0.2", + "sp-staking 36.0.0", +] [[package]] -name = "polkavm-derive" -version = "0.5.0" +name = "pallet-collective" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6380dbe1fb03ecc74ad55d841cfc75480222d153ba69ddcb00977866cbdabdb8" +checksum = "8e149f1aefd444c9a1da6ec5a94bc8a7671d7a33078f85dd19ae5b06e3438e60" dependencies = [ - "polkavm-derive-impl 0.5.0", - "syn 2.0.89", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "polkavm-derive" -version = "0.8.0" +name = "pallet-collective-content" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79fa916f7962348bd1bb1a65a83401675e6fc86c51a0fdbcf92a3108e58e6125" +checksum = "38a6a5cbe781d9c711be74855ba32ef138f3779d6c54240c08e6d1b4bbba4d1d" dependencies = [ - "polkavm-derive-impl-macro 0.8.0", + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core 34.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "polkavm-derive" -version = "0.9.1" +name = "pallet-contracts" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8c4bea6f3e11cd89bb18bcdddac10bd9a24015399bd1c485ad68a985a19606" +checksum = "5df77077745d891c822b4275f273f336077a97e69e62a30134776aa721c96fee" dependencies = [ - "polkavm-derive-impl-macro 0.9.0", + "bitflags 1.3.2", + "environmental", + "frame-benchmarking", + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "log", + "pallet-balances", + "pallet-contracts-proc-macro", + "pallet-contracts-uapi 12.0.0", + "parity-scale-codec", + "paste", + "rand", + "scale-info", + "serde", + "smallvec", + "sp-api", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-std", + "staging-xcm 14.2.0", + "staging-xcm-builder", + "wasm-instrument", + "wasmi 0.32.3", ] [[package]] -name = "polkavm-derive-impl" -version = "0.5.0" +name = "pallet-contracts-mock-network" +version = "14.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc8211b3365bbafb2fb32057d68b0e1ca55d079f5cf6f9da9b98079b94b3987d" +checksum = "309666537ed001c61a99f59fa7b98680f4a6e4e361ed3bc64f7b0237da3e3e06" +dependencies = [ + "frame-support", + "frame-system", + "pallet-assets", + "pallet-balances", + "pallet-contracts", + "pallet-contracts-proc-macro", + "pallet-contracts-uapi 12.0.0", + "pallet-insecure-randomness-collective-flip", + "pallet-message-queue", + "pallet-proxy", + "pallet-timestamp", + "pallet-utility", + "pallet-xcm", + "parity-scale-codec", + "polkadot-parachain-primitives", + "polkadot-primitives 16.0.0", + "polkadot-runtime-parachains", + "scale-info", + "sp-api", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-keystore 0.40.0", + "sp-runtime 39.0.2", + "sp-tracing 17.0.1", + "staging-xcm 14.2.0", + "staging-xcm-builder", + "staging-xcm-executor", + "xcm-simulator", +] + +[[package]] +name = "pallet-contracts-proc-macro" +version = "23.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94226cbd48516b7c310eb5dae8d50798c1ce73a7421dc0977c55b7fc2237a283" dependencies = [ - "polkavm-common 0.5.0", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] -name = "polkavm-derive-impl" -version = "0.8.0" +name = "pallet-contracts-uapi" +version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c10b2654a8a10a83c260bfb93e97b262cf0017494ab94a65d389e0eda6de6c9c" +checksum = "2d7a51646d9ff1d91abd0186d2c074f0dfd3b1a2d55f08a229a2f2e4bc6d1e49" dependencies = [ - "polkavm-common 0.8.0", - "proc-macro2", - "quote", - "syn 2.0.89", + "bitflags 1.3.2", + "paste", + "polkavm-derive 0.9.1", ] [[package]] -name = "polkavm-derive-impl" -version = "0.9.0" +name = "pallet-contracts-uapi" +version = "12.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c4fdfc49717fb9a196e74a5d28e0bc764eb394a2c803eb11133a31ac996c60c" +checksum = "16f74b000590c33fadea48585d3ae3f4b7867e99f0a524c444d5779f36b9a1b6" dependencies = [ - "polkavm-common 0.9.0", - "proc-macro2", - "quote", - "syn 2.0.89", + "bitflags 1.3.2", + "parity-scale-codec", + "paste", + "polkavm-derive 0.9.1", + "scale-info", ] [[package]] -name = "polkavm-derive-impl-macro" -version = "0.8.0" +name = "pallet-contracts-uapi-next" +version = "6.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e85319a0d5129dc9f021c62607e0804f5fb777a05cdda44d750ac0732def66" +checksum = "fd549c16296ea5b2eb7c65c56aba548b286c1be4d7675b424ff6ccb8319c97a9" dependencies = [ - "polkavm-derive-impl 0.8.0", - "syn 2.0.89", + "bitflags 1.3.2", + "parity-scale-codec", + "paste", + "polkavm-derive 0.5.0", + "scale-info", ] [[package]] -name = "polkavm-derive-impl-macro" -version = "0.9.0" +name = "pallet-conviction-voting" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" +checksum = "999c242491b74395b8c5409ef644e782fe426d87ae36ad92240ffbf21ff0a76e" dependencies = [ - "polkavm-derive-impl 0.9.0", - "syn 2.0.89", + "assert_matches", + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "serde", + "sp-io 38.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "polling" -version = "3.7.3" +name = "pallet-core-fellowship" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" +checksum = "d063b41df454bd128d6fefd5800af8a71ac383c9dd6f20096832537efc110a8a" dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi 0.4.0", - "pin-project-lite", - "rustix 0.38.36", - "tracing", - "windows-sys 0.59.0", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-ranked-collective", + "parity-scale-codec", + "scale-info", + "sp-arithmetic 26.0.0", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "poly1305" -version = "0.8.0" +name = "pallet-delegated-staking" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +checksum = "117f003a97f980514c6db25a50c22aaec2a9ccb5664b3cb32f52fb990e0b0c12" dependencies = [ - "cpufeatures", - "opaque-debug", - "universal-hash", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-staking 36.0.0", ] [[package]] -name = "pop-cli" -version = "0.5.0" +name = "pallet-democracy" +version = "38.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d1dc655f50b7c65bb2fb14086608ba11af02ef2936546f7a67db980ec1f133" dependencies = [ - "anyhow", - "assert_cmd", - "clap", - "cliclack", - "console", - "dirs", - "duct", - "env_logger", - "git2", - "os_info", - "pop-common", - "pop-contracts", - "pop-parachains", - "pop-telemetry", - "predicates", - "reqwest 0.12.7", - "serde_json", - "sp-core", - "sp-weights", - "strum 0.26.3", - "strum_macros 0.26.4", - "tempfile", - "tokio", - "url", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "pop-common" -version = "0.5.0" +name = "pallet-dev-mode" +version = "20.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1d8050c09c5e003d502c1addc7fdfbde21a854bd57787e94447078032710c8" dependencies = [ - "anyhow", - "cargo_toml", - "contract-build", - "contract-extrinsics", - "duct", - "flate2", - "git2", - "git2_credentials", - "ink_env", - "mockito", - "regex", - "reqwest 0.12.7", + "frame-support", + "frame-system", + "log", + "pallet-balances", + "parity-scale-codec", "scale-info", - "serde", - "serde_json", - "strum 0.26.3", - "strum_macros 0.26.4", - "subxt", - "subxt-signer", - "tar", - "tempfile", - "thiserror", - "tokio", - "toml 0.5.11", - "toml_edit 0.22.20", - "url", + "sp-io 38.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "pop-contracts" -version = "0.5.0" +name = "pallet-election-provider-multi-phase" +version = "37.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f9ad5ae0c13ba3727183dadf1825b6b7b0b0598ed5c366f8697e13fd540f7d" dependencies = [ - "anyhow", - "contract-build", - "contract-extrinsics", - "contract-transcode", - "dirs", - "duct", - "flate2", - "heck 0.5.0", - "ink_env", - "mockito", - "pop-common", - "reqwest 0.12.7", + "frame-benchmarking", + "frame-election-provider-support", + "frame-support", + "frame-system", + "log", + "pallet-election-provider-support-benchmarking", + "parity-scale-codec", + "rand", "scale-info", - "sp-core", - "sp-weights", + "sp-arithmetic 26.0.0", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-npos-elections", + "sp-runtime 39.0.2", "strum 0.26.3", - "strum_macros 0.26.4", - "tar", - "tempfile", - "thiserror", - "tokio", - "tokio-test", - "url", ] [[package]] -name = "pop-parachains" -version = "0.5.0" +name = "pallet-election-provider-support-benchmarking" +version = "37.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4111d0d27545c260c9dd0d6fc504961db59c1ec4b42e1bcdc28ebd478895c22" dependencies = [ - "anyhow", - "askama", - "clap", - "duct", - "flate2", - "glob", - "hex", - "indexmap 2.5.0", - "mockito", - "pop-common", - "reqwest 0.12.7", - "scale-info", - "scale-value", - "serde_json", - "strum 0.26.3", - "strum_macros 0.26.4", - "subxt", - "symlink", - "tar", - "tempfile", - "thiserror", - "tokio", - "tokio-test", - "toml_edit 0.22.20", - "url", - "walkdir", - "zombienet-sdk", + "frame-benchmarking", + "frame-election-provider-support", + "frame-system", + "parity-scale-codec", + "sp-npos-elections", + "sp-runtime 39.0.2", ] [[package]] -name = "pop-telemetry" -version = "0.5.0" +name = "pallet-elections-phragmen" +version = "39.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "705c66d6c231340c6d085a0df0319a6ce42a150f248171e88e389ab1e3ce20f5" dependencies = [ - "dirs", - "env_logger", + "frame-benchmarking", + "frame-support", + "frame-system", "log", - "mockito", - "reqwest 0.12.7", - "serde", - "serde_json", - "tempfile", - "thiserror", - "tokio", + "parity-scale-codec", + "scale-info", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-npos-elections", + "sp-runtime 39.0.2", + "sp-staking 36.0.0", ] [[package]] -name = "portable-atomic" -version = "1.7.0" +name = "pallet-fast-unstake" +version = "37.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" +checksum = "e0ee60e8ef10b3936f2700bd61fa45dcc190c61124becc63bed787addcfa0d20" +dependencies = [ + "docify", + "frame-benchmarking", + "frame-election-provider-support", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-staking 36.0.0", +] [[package]] -name = "powerfmt" -version = "0.2.0" +name = "pallet-glutton" +version = "24.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +checksum = "a1c79ab340890f6ab088a638c350ac1173a1b2a79c18004787523032025582b4" +dependencies = [ + "blake2", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-core 34.0.0", + "sp-inherents", + "sp-io 38.0.0", + "sp-runtime 39.0.2", +] [[package]] -name = "ppv-lite86" -version = "0.2.20" +name = "pallet-grandpa" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "6d3a570a4aac3173ea46b600408183ca2bcfdaadc077f802f11e6055963e2449" dependencies = [ - "zerocopy", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-authorship", + "pallet-session", + "parity-scale-codec", + "scale-info", + "sp-application-crypto 38.0.0", + "sp-consensus-grandpa", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-session", + "sp-staking 36.0.0", ] [[package]] -name = "predicates" -version = "3.1.2" +name = "pallet-identity" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" +checksum = "e3a4288548de9a755e39fcb82ffb9024b6bb1ba0f582464a44423038dd7a892e" dependencies = [ - "anstyle", - "difflib", - "float-cmp", - "normalize-line-endings", - "predicates-core", - "regex", + "enumflags2", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-io 38.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "predicates-core" -version = "1.0.8" +name = "pallet-im-online" +version = "37.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" +checksum = "c6fd95270cf029d16cb40fe6bd9f8ab9c78cd966666dccbca4d8bfec35c5bba5" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-authorship", + "parity-scale-codec", + "scale-info", + "sp-application-crypto 38.0.0", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-staking 36.0.0", +] [[package]] -name = "predicates-tree" -version = "1.0.11" +name = "pallet-indices" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" +checksum = "c5e4b97de630427a39d50c01c9e81ab8f029a00e56321823958b39b438f7b940" dependencies = [ - "predicates-core", - "termtree", + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-keyring", + "sp-runtime 39.0.2", ] [[package]] -name = "prettyplease" -version = "0.2.22" +name = "pallet-insecure-randomness-collective-flip" +version = "26.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" +checksum = "dce7ad80675d78bd38a7a66ecbbf2d218dd32955e97f8e301d0afe6c87b0f251" dependencies = [ - "proc-macro2", - "syn 2.0.89", + "frame-support", + "frame-system", + "parity-scale-codec", + "safe-mix", + "scale-info", + "sp-runtime 39.0.2", ] [[package]] -name = "primitive-types" -version = "0.12.2" +name = "pallet-lottery" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +checksum = "ae0920ee53cf7b0665cfb6d275759ae0537dc3850ec78da5f118d814c99d3562" dependencies = [ - "fixed-hash", - "impl-codec", - "impl-serde", + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", "scale-info", - "uint", + "sp-runtime 39.0.2", ] [[package]] -name = "proc-macro-crate" -version = "1.3.1" +name = "pallet-membership" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +checksum = "1868b5dca4bbfd1f4a222cbb80735a5197020712a71577b496bbb7e19aaa5394" dependencies = [ - "once_cell", - "toml_edit 0.19.15", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "proc-macro-crate" -version = "3.2.0" +name = "pallet-message-queue" +version = "41.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +checksum = "983f7d1be18e9a089a3e23670918f5085705b4403acd3fdde31878d57b76a1a8" dependencies = [ - "toml_edit 0.22.20", + "environmental", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-arithmetic 26.0.0", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-weights 31.0.0", ] [[package]] -name = "proc-macro-error" -version = "1.0.4" +name = "pallet-migrations" +version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +checksum = "9b417fc975636bce94e7c6d707e42d0706d67dfa513e72f5946918e1044beef1" dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", + "docify", + "frame-benchmarking", + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "scale-info", + "sp-core 34.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +name = "pallet-mixnet" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +checksum = "cf3fa2b7f759a47f698a403ab40c54bc8935e2969387947224cbdb4e2bc8a28a" dependencies = [ - "proc-macro2", - "quote", - "version_check", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "serde", + "sp-application-crypto 38.0.0", + "sp-arithmetic 26.0.0", + "sp-io 38.0.0", + "sp-mixnet", + "sp-runtime 39.0.2", ] [[package]] -name = "proc-macro2" -version = "1.0.92" +name = "pallet-mmr" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "f6932dfb85f77a57c2d1fdc28a7b3a59ffe23efd8d5bb02dc3039d91347e4a3b" dependencies = [ - "unicode-ident", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-mmr-primitives", + "sp-runtime 39.0.2", ] [[package]] -name = "psm" -version = "0.1.23" +name = "pallet-multisig" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa37f80ca58604976033fae9515a8a2989fc13797d953f7c04fb8fa36a11f205" +checksum = "0e5099c9a4442efcc1568d88ca1d22d624e81ab96358f99f616c67fbd82532d2" dependencies = [ - "cc", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-io 38.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "ptr_meta" -version = "0.1.4" +name = "pallet-nft-fractionalization" +version = "21.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +checksum = "168792cf95a32fa3baf9b874efec82a45124da0a79cee1ae3c98a823e6841959" dependencies = [ - "ptr_meta_derive", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-assets", + "pallet-nfts", + "parity-scale-codec", + "scale-info", + "sp-runtime 39.0.2", ] [[package]] -name = "ptr_meta_derive" -version = "0.1.4" +name = "pallet-nfts" +version = "32.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +checksum = "59e2aad461a0849d7f0471576eeb1fe3151795bcf2ec9e15eca5cca5b9d743b2" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "enumflags2", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "quick-protobuf" -version = "0.8.1" +name = "pallet-nfts-runtime-api" +version = "24.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6da84cc204722a989e01ba2f6e1e276e190f22263d0cb6ce8526fcdb0d2e1f" +checksum = "a7a1f50c217e19dc50ff586a71eb5915df6a05bc0b25564ea20674c8cd182c1f" dependencies = [ - "byteorder", + "pallet-nfts", + "parity-scale-codec", + "sp-api", ] [[package]] -name = "quote" -version = "1.0.37" +name = "pallet-nis" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "8ac349e119880b7df1a7c4c36d919b33a498d0e9548af3c237365c654ae0c73d" dependencies = [ - "proc-macro2", + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-arithmetic 26.0.0", + "sp-core 34.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "radium" -version = "0.7.0" +name = "pallet-node-authorization" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +checksum = "39ec3133be9e767b8feafbb26edd805824faa59956da008d2dc7fcf4b4720e56" +dependencies = [ + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", +] [[package]] -name = "rand" -version = "0.8.5" +name = "pallet-nomination-pools" +version = "35.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "c42906923f9f2b65b22f1211136b57c6878296ba6f6228a075c4442cc1fc1659" dependencies = [ - "libc", - "rand_chacha", - "rand_core 0.6.4", + "frame-support", + "frame-system", + "log", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-staking 36.0.0", + "sp-tracing 17.0.1", ] [[package]] -name = "rand_chacha" -version = "0.3.1" +name = "pallet-nomination-pools-benchmarking" +version = "36.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "38d2eaca0349bcda923343226b8b64d25a80b67e0a1ebaaa5b0ab1e1b3b225bc" dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", + "frame-benchmarking", + "frame-election-provider-support", + "frame-support", + "frame-system", + "pallet-bags-list", + "pallet-delegated-staking", + "pallet-nomination-pools", + "pallet-staking", + "parity-scale-codec", + "scale-info", + "sp-runtime 39.0.2", + "sp-runtime-interface 28.0.0", + "sp-staking 36.0.0", ] [[package]] -name = "rand_core" -version = "0.5.1" +name = "pallet-nomination-pools-runtime-api" +version = "33.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +checksum = "7a9e1cb89cc2e6df06ce274a7fc814e5e688aad04c43902a10191fa3d2a56a96" +dependencies = [ + "pallet-nomination-pools", + "parity-scale-codec", + "sp-api", +] [[package]] -name = "rand_core" -version = "0.6.4" +name = "pallet-offences" +version = "37.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "6c4379cf853465696c1c5c03e7e8ce80aeaca0a6139d698abe9ecb3223fd732a" dependencies = [ - "getrandom", + "frame-support", + "frame-system", + "log", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "serde", + "sp-runtime 39.0.2", + "sp-staking 36.0.0", ] [[package]] -name = "reconnecting-jsonrpsee-ws-client" -version = "0.4.3" +name = "pallet-offences-benchmarking" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06fa4f17e09edfc3131636082faaec633c7baa269396b4004040bc6c52f49f65" +checksum = "69aa1b24cdffc3fa8c89cdea32c83f1bf9c1c82a87fa00e57ae4be8e85f5e24f" dependencies = [ - "cfg_aliases", - "finito", - "futures", - "jsonrpsee 0.23.2", - "serde_json", - "thiserror", - "tokio", - "tracing", + "frame-benchmarking", + "frame-election-provider-support", + "frame-support", + "frame-system", + "log", + "pallet-babe", + "pallet-balances", + "pallet-grandpa", + "pallet-im-online", + "pallet-offences", + "pallet-session", + "pallet-staking", + "parity-scale-codec", + "scale-info", + "sp-runtime 39.0.2", + "sp-staking 36.0.0", ] [[package]] -name = "redox_syscall" -version = "0.5.3" +name = "pallet-paged-list" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "c8e099fb116068836b17ca4232dc52f762b69dc8cd4e33f509372d958de278b0" dependencies = [ - "bitflags 2.6.0", + "docify", + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-metadata-ir", + "sp-runtime 39.0.2", ] [[package]] -name = "redox_users" -version = "0.4.6" +name = "pallet-parameters" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +checksum = "b9aba424d55e17b2a2bec766a41586eab878137704d4803c04bebd6a4743db7b" dependencies = [ - "getrandom", - "libredox", - "thiserror", + "docify", + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "paste", + "scale-info", + "serde", + "sp-core 34.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "ref-cast" -version = "1.0.23" +name = "pallet-preimage" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf0a6f84d5f1d581da8b41b47ec8600871962f2a528115b542b362d4b744931" +checksum = "407828bc48c6193ac076fdf909b2fadcaaecd65f42b0b0a04afe22fe8e563834" dependencies = [ - "ref-cast-impl", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "ref-cast-impl" -version = "1.0.23" +name = "pallet-proxy" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" +checksum = "d39df395f0dbcf07dafe842916adea3266a87ce36ed87b5132184b6bcd746393" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.89", + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-io 38.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "regex" -version = "1.10.6" +name = "pallet-ranked-collective" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "c2b38708feaed202debf1ac6beffaa5e20c99a9825c5ca0991753c2d4eaaf3ac" dependencies = [ - "aho-corasick", - "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "frame-benchmarking", + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "scale-info", + "sp-arithmetic 26.0.0", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "regex-automata" -version = "0.1.10" +name = "pallet-recovery" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +checksum = "406a116aa6d05f88f3c10d79ff89cf577323680a48abd8e5550efb47317e67fa" dependencies = [ - "regex-syntax 0.6.29", + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-io 38.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "regex-automata" -version = "0.4.7" +name = "pallet-referenda" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "c3008c20531d1730c9b457ae77ecf0e3c9b07aaf8c4f5d798d61ef6f0b9e2d4b" dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax 0.8.4", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "serde", + "sp-arithmetic 26.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "regex-syntax" -version = "0.6.29" +name = "pallet-remark" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +checksum = "a3e8cae0e20888065ec73dda417325c6ecabf797f4002329484b59c25ecc34d4" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", +] [[package]] -name = "regex-syntax" -version = "0.8.4" +name = "pallet-revive" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "be02c94dcbadd206a910a244ec19b493aac793eed95e23d37d6699547234569f" +dependencies = [ + "bitflags 1.3.2", + "environmental", + "frame-benchmarking", + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "log", + "pallet-balances", + "pallet-revive-fixtures", + "pallet-revive-proc-macro", + "pallet-revive-uapi", + "parity-scale-codec", + "paste", + "polkavm 0.10.0", + "scale-info", + "serde", + "sp-api", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-std", + "staging-xcm 14.2.0", + "staging-xcm-builder", +] [[package]] -name = "rend" -version = "0.4.2" +name = "pallet-revive-fixtures" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +checksum = "8a38c27f1531f36e5327f3084eb24cf1c9dd46b372e030c0169e843ce363105e" dependencies = [ - "bytecheck", + "anyhow", + "frame-system", + "parity-wasm", + "polkavm-linker 0.10.0", + "sp-runtime 39.0.2", + "tempfile", + "toml 0.8.19", ] [[package]] -name = "reqwest" -version = "0.11.27" +name = "pallet-revive-mock-network" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" -dependencies = [ - "base64 0.21.7", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.30", - "hyper-tls 0.5.0", - "ipnet", - "js-sys", - "log", - "mime", - "native-tls", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls-pemfile 1.0.4", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper 0.1.2", - "system-configuration 0.5.1", - "tokio", - "tokio-native-tls", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "winreg", +checksum = "60e74591d44dbd78db02c8593f5caa75bd61bcc4d63999302150223fb969ae37" +dependencies = [ + "frame-support", + "frame-system", + "pallet-assets", + "pallet-balances", + "pallet-message-queue", + "pallet-proxy", + "pallet-revive", + "pallet-revive-proc-macro", + "pallet-revive-uapi", + "pallet-timestamp", + "pallet-utility", + "pallet-xcm", + "parity-scale-codec", + "polkadot-parachain-primitives", + "polkadot-primitives 16.0.0", + "polkadot-runtime-parachains", + "scale-info", + "sp-api", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-keystore 0.40.0", + "sp-runtime 39.0.2", + "sp-tracing 17.0.1", + "staging-xcm 14.2.0", + "staging-xcm-builder", + "staging-xcm-executor", + "xcm-simulator", ] [[package]] -name = "reqwest" -version = "0.12.7" +name = "pallet-revive-proc-macro" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +checksum = "0cc16d1f7cee6a1ee6e8cd710e16230d59fb4935316c1704cf770e4d2335f8d4" dependencies = [ - "base64 0.22.1", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2 0.4.6", - "http 1.1.0", - "http-body 1.0.1", - "http-body-util", - "hyper 1.4.1", - "hyper-rustls 0.27.3", - "hyper-tls 0.6.0", - "hyper-util", - "ipnet", - "js-sys", - "log", - "mime", - "native-tls", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls-pemfile 2.1.3", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper 1.0.1", - "system-configuration 0.6.1", - "tokio", - "tokio-native-tls", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "windows-registry", + "proc-macro2", + "quote", + "syn 2.0.90", ] [[package]] -name = "rfc6979" -version = "0.4.0" +name = "pallet-revive-uapi" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +checksum = "ecb4686c8415619cc13e43fadef146ffff46424d9b4d037fe4c069de52708aac" dependencies = [ - "hmac 0.12.1", - "subtle", + "bitflags 1.3.2", + "parity-scale-codec", + "paste", + "polkavm-derive 0.10.0", + "scale-info", ] [[package]] -name = "ring" -version = "0.17.8" +name = "pallet-root-offences" +version = "35.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "b35774b830928daaeeca7196cead7c56eeed952a6616ad6dc5ec068d8c85c81a" dependencies = [ - "cc", - "cfg-if", - "getrandom", - "libc", - "spin", - "untrusted", - "windows-sys 0.52.0", + "frame-support", + "frame-system", + "pallet-session", + "pallet-staking", + "parity-scale-codec", + "scale-info", + "sp-runtime 39.0.2", + "sp-staking 36.0.0", ] [[package]] -name = "rkyv" -version = "0.7.45" +name = "pallet-root-testing" +version = "14.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" +checksum = "be95e7c320ac1d381715364cd721e67ab3152ab727f8e4defd3a92e41ebbc880" dependencies = [ - "bitvec", - "bytecheck", - "bytes", - "hashbrown 0.12.3", - "ptr_meta", - "rend", - "rkyv_derive", - "seahash", - "tinyvec", - "uuid", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "rkyv_derive" -version = "0.7.45" +name = "pallet-safe-mode" +version = "19.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" +checksum = "6d3e67dd4644c168cedbf257ac3dd2527aad81acf4a0d413112197094e549f76" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "docify", + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-balances", + "pallet-proxy", + "pallet-utility", + "parity-scale-codec", + "scale-info", + "sp-arithmetic 26.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "rlibc" -version = "1.0.0" +name = "pallet-salary" +version = "23.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc874b127765f014d792f16763a81245ab80500e2ad921ed4ee9e82481ee08fe" +checksum = "0544a71dba06a9a29da0778ba8cb37728c3b9a8377ac9737c4b1bc48c618bc2f" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-ranked-collective", + "parity-scale-codec", + "scale-info", + "sp-arithmetic 26.0.0", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", +] [[package]] -name = "rust_decimal" -version = "1.36.0" +name = "pallet-scheduler" +version = "39.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" +checksum = "26899a331e7ab5f7d5966cbf203e1cf5bd99cd110356d7ddcaa7597087cdc0b5" dependencies = [ - "arrayvec 0.7.6", - "borsh", - "bytes", - "num-traits", - "rand", - "rkyv", - "serde", - "serde_json", + "docify", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-weights 31.0.0", ] [[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustc-hex" -version = "2.1.0" +name = "pallet-scored-pool" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" +checksum = "9f84b48bb4702712c902f43931c4077d3a1cb6773c8d8c290d4a6251f6bc2a5c" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-io 38.0.0", + "sp-runtime 39.0.2", +] [[package]] -name = "rustc_version" -version = "0.4.1" +name = "pallet-session" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +checksum = "8474b62b6b7622f891e83d922a589e2ad5be5471f5ca47d45831a797dba0b3f4" dependencies = [ - "semver", + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "log", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-session", + "sp-staking 36.0.0", + "sp-state-machine 0.43.0", + "sp-trie 37.0.0", ] [[package]] -name = "rustix" -version = "0.36.17" +name = "pallet-session-benchmarking" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "305efbd14fde4139eb501df5f136994bb520b033fa9fbdce287507dc23b8c7ed" +checksum = "8aadce7df0fee981721983795919642648b846dab5ab9096f82c2cea781007d0" dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.1.4", - "windows-sys 0.45.0", + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-session", + "pallet-staking", + "parity-scale-codec", + "rand", + "sp-runtime 39.0.2", + "sp-session", ] [[package]] -name = "rustix" -version = "0.38.36" +name = "pallet-skip-feeless-payment" +version = "13.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36" +checksum = "f8c2cb0dae13d2c2d2e76373f337d408468f571459df1900cbd7458f21cf6c01" dependencies = [ - "bitflags 2.6.0", - "errno", - "libc", - "linux-raw-sys 0.4.14", - "windows-sys 0.52.0", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-runtime 39.0.2", ] [[package]] -name = "rustls" -version = "0.21.12" +name = "pallet-society" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +checksum = "d1dc69fea8a8de343e71691f009d5fece6ae302ed82b7bb357882b2ea6454143" dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", "log", - "ring", - "rustls-webpki 0.101.7", - "sct", + "parity-scale-codec", + "rand_chacha", + "scale-info", + "sp-arithmetic 26.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "rustls" -version = "0.22.4" +name = "pallet-staking" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +checksum = "c870d123f4f053b56af808a4beae1ffc4309a696e829796c26837936c926db3b" dependencies = [ + "frame-benchmarking", + "frame-election-provider-support", + "frame-support", + "frame-system", "log", - "ring", - "rustls-pki-types", - "rustls-webpki 0.102.8", - "subtle", - "zeroize", + "pallet-authorship", + "pallet-session", + "parity-scale-codec", + "scale-info", + "serde", + "sp-application-crypto 38.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-staking 36.0.0", ] [[package]] -name = "rustls" -version = "0.23.13" +name = "pallet-staking-reward-fn" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" +checksum = "988a7ebeacc84d4bdb0b12409681e956ffe35438447d8f8bc78db547cffb6ebc" dependencies = [ "log", - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki 0.102.8", - "subtle", - "zeroize", + "sp-arithmetic 26.0.0", ] [[package]] -name = "rustls-native-certs" -version = "0.6.3" +name = "pallet-staking-runtime-api" +version = "24.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +checksum = "e7298559ef3a6b2f5dfbe9a3b8f3d22f2ff9b073c97f4c4853d2b316d973e72d" dependencies = [ - "openssl-probe", - "rustls-pemfile 1.0.4", - "schannel", - "security-framework", + "parity-scale-codec", + "sp-api", + "sp-staking 36.0.0", ] [[package]] -name = "rustls-native-certs" -version = "0.7.3" +name = "pallet-state-trie-migration" +version = "40.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +checksum = "138c15b4200b9dc4c3e031def6a865a235cdc76ff91ee96fba19ca1787c9dda6" dependencies = [ - "openssl-probe", - "rustls-pemfile 2.1.3", - "rustls-pki-types", - "schannel", - "security-framework", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "rustls-pemfile" -version = "1.0.4" +name = "pallet-statement" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +checksum = "5e03e147efa900e75cd106337f36da3d7dcd185bd9e5f5c3df474c08c3c37d16" dependencies = [ - "base64 0.21.7", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-statement-store", ] [[package]] -name = "rustls-pemfile" -version = "2.1.3" +name = "pallet-sudo" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +checksum = "1574fe2aed3d52db4a389b77b53d8c9758257b121e3e7bbe24c4904e11681e0e" dependencies = [ - "base64 0.22.1", - "rustls-pki-types", + "docify", + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-io 38.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "rustls-pki-types" -version = "1.8.0" +name = "pallet-timestamp" +version = "37.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" +checksum = "a9ba9b71bbfd33ae672f23ba7efaeed2755fdac37b8f946cb7474fc37841b7e1" +dependencies = [ + "docify", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-inherents", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-storage 21.0.0", + "sp-timestamp", +] [[package]] -name = "rustls-platform-verifier" -version = "0.3.4" +name = "pallet-tips" +version = "37.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afbb878bdfdf63a336a5e63561b1835e7a8c91524f51621db870169eac84b490" +checksum = "aa1d4371a70c309ba11624933f8f5262fe4edad0149c556361d31f26190da936" dependencies = [ - "core-foundation", - "core-foundation-sys", - "jni", + "frame-benchmarking", + "frame-support", + "frame-system", "log", - "once_cell", - "rustls 0.23.13", - "rustls-native-certs 0.7.3", - "rustls-platform-verifier-android", - "rustls-webpki 0.102.8", - "security-framework", - "security-framework-sys", - "webpki-roots", - "winapi", + "pallet-treasury", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "rustls-platform-verifier-android" -version = "0.1.1" +name = "pallet-transaction-payment" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" +checksum = "47b1aa3498107a30237f941b0f02180db3b79012c3488878ff01a4ac3e8ee04e" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", +] [[package]] -name = "rustls-webpki" -version = "0.101.7" +name = "pallet-transaction-payment-rpc-runtime-api" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +checksum = "49fdf5ab71e9dbcadcf7139736b6ea6bac8ec4a83985d46cbd130e1eec770e41" dependencies = [ - "ring", - "untrusted", + "pallet-transaction-payment", + "parity-scale-codec", + "sp-api", + "sp-runtime 39.0.2", + "sp-weights 31.0.0", ] [[package]] -name = "rustls-webpki" -version = "0.102.8" +name = "pallet-transaction-storage" +version = "37.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "f8c337a972a6a796c0a0acc6c03b5e02901c43ad721ce79eb87b45717d75c93b" dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "serde", + "sp-inherents", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-transaction-storage-proof", ] [[package]] -name = "rustversion" -version = "1.0.17" +name = "pallet-treasury" +version = "37.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "98bfdd3bb9b58fb010bcd419ff5bf940817a8e404cdbf7886a53ac730f5dda2b" +dependencies = [ + "docify", + "frame-benchmarking", + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core 34.0.0", + "sp-runtime 39.0.2", +] [[package]] -name = "ruzstd" -version = "0.5.0" +name = "pallet-tx-pause" +version = "19.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58c4eb8a81997cf040a091d1f7e1938aeab6749d3a0dfa73af43cdc32393483d" +checksum = "cee153f5be5efc84ebd53aa581e5361cde17dc3669ef80d8ad327f4041d89ebe" dependencies = [ - "byteorder", - "derive_more 0.99.18", - "twox-hash", + "docify", + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-balances", + "pallet-proxy", + "pallet-utility", + "parity-scale-codec", + "scale-info", + "sp-runtime 39.0.2", ] [[package]] -name = "rw-stream-sink" -version = "0.4.0" +name = "pallet-uniques" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8c9026ff5d2f23da5e45bbc283f156383001bfb09c4e44256d02c1a685fe9a1" +checksum = "c2b13cdaedf2d5bd913a5f6e637cb52b5973d8ed4b8d45e56d921bc4d627006f" dependencies = [ - "futures", - "pin-project", - "static_assertions", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-runtime 39.0.2", ] [[package]] -name = "ryu" -version = "1.0.18" +name = "pallet-utility" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "2fdcade6efc0b66fc7fc4138964802c02d0ffb7380d894e26b9dd5073727d2b3" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", +] [[package]] -name = "same-file" -version = "1.0.6" +name = "pallet-vesting" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +checksum = "807df2ef13ab6bf940879352c3013bfa00b670458b4c125c2f60e5753f68e3d5" dependencies = [ - "winapi-util", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-runtime 39.0.2", ] [[package]] -name = "scale-bits" -version = "0.4.0" +name = "pallet-whitelist" +version = "37.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "036575c29af9b6e4866ffb7fa055dbf623fe7a9cc159b33786de6013a6969d89" +checksum = "1ef17df925290865cf37096dd0cb76f787df11805bba01b1d0ca3e106d06280b" dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", "parity-scale-codec", "scale-info", + "sp-api", + "sp-runtime 39.0.2", ] [[package]] -name = "scale-bits" -version = "0.6.0" +name = "pallet-xcm" +version = "17.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e57b1e7f6b65ed1f04e79a85a57d755ad56d76fdf1e9bddcc9ae14f71fcdcf54" +checksum = "989676964dbda5f5275650fbdcd3894fe7fac626d113abf89d572b4952adcc36" dependencies = [ + "bounded-collections", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-balances", "parity-scale-codec", "scale-info", - "scale-type-resolver", "serde", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "staging-xcm 14.2.0", + "staging-xcm-builder", + "staging-xcm-executor", + "tracing", + "xcm-runtime-apis", ] [[package]] -name = "scale-decode" -version = "0.10.0" +name = "pallet-xcm-benchmarks" +version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7caaf753f8ed1ab4752c6afb20174f03598c664724e0e32628e161c21000ff76" +checksum = "2da423463933b42f4a4c74175f9e9295a439de26719579b894ce533926665e4a" dependencies = [ - "derive_more 0.99.18", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", "parity-scale-codec", - "scale-bits 0.4.0", - "scale-decode-derive 0.10.0", "scale-info", - "smallvec", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "staging-xcm 14.2.0", + "staging-xcm-builder", + "staging-xcm-executor", ] [[package]] -name = "scale-decode" -version = "0.13.1" +name = "pallet-xcm-bridge-hub" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e98f3262c250d90e700bb802eb704e1f841e03331c2eb815e46516c4edbf5b27" +checksum = "d5f9670065b7cba92771060a4a3925b6650ff67611443ccfccd5aa356f7d5aac" dependencies = [ - "derive_more 0.99.18", + "bp-messages", + "bp-runtime", + "bp-xcm-bridge-hub", + "frame-support", + "frame-system", + "log", + "pallet-bridge-messages", "parity-scale-codec", - "primitive-types", - "scale-bits 0.6.0", - "scale-decode-derive 0.13.1", - "scale-type-resolver", - "smallvec", + "scale-info", + "sp-core 34.0.0", + "sp-runtime 39.0.2", + "sp-std", + "staging-xcm 14.2.0", + "staging-xcm-builder", + "staging-xcm-executor", ] [[package]] -name = "scale-decode-derive" -version = "0.10.0" +name = "pallet-xcm-bridge-hub-router" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3475108a1b62c7efd1b5c65974f30109a598b2f45f23c9ae030acb9686966db" +checksum = "f3b5347c826b721098ef39afb0d750e621c77538044fc1e865af1a8747824fdf" dependencies = [ - "darling 0.14.4", - "proc-macro-crate 1.3.1", - "proc-macro2", - "quote", - "syn 1.0.109", + "bp-xcm-bridge-hub-router", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-core 34.0.0", + "sp-runtime 39.0.2", + "sp-std", + "staging-xcm 14.2.0", + "staging-xcm-builder", ] [[package]] -name = "scale-decode-derive" -version = "0.13.1" +name = "parachains-common" +version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb22f574168103cdd3133b19281639ca65ad985e24612728f727339dcaf4021" +checksum = "c9460a69f409be27c62161d8b4d36ffc32735d09a4f9097f9c789db0cca7196c" dependencies = [ - "darling 0.14.4", - "proc-macro2", - "quote", - "syn 1.0.109", + "cumulus-primitives-core", + "cumulus-primitives-utility", + "frame-support", + "frame-system", + "log", + "pallet-asset-tx-payment", + "pallet-assets", + "pallet-authorship", + "pallet-balances", + "pallet-collator-selection", + "pallet-message-queue", + "pallet-xcm", + "parity-scale-codec", + "polkadot-primitives 16.0.0", + "scale-info", + "sp-consensus-aura", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "staging-parachain-info", + "staging-xcm 14.2.0", + "staging-xcm-executor", + "substrate-wasm-builder", +] + +[[package]] +name = "parachains-runtimes-test-utils" +version = "17.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287d2db0a2d19466caa579a69f021bfdc6fa352f382c8395dade58d1d0c6adfe" +dependencies = [ + "cumulus-pallet-parachain-system", + "cumulus-pallet-xcmp-queue", + "cumulus-primitives-core", + "cumulus-primitives-parachain-inherent", + "cumulus-test-relay-sproof-builder", + "frame-support", + "frame-system", + "pallet-balances", + "pallet-collator-selection", + "pallet-session", + "pallet-timestamp", + "pallet-xcm", + "parity-scale-codec", + "polkadot-parachain-primitives", + "sp-consensus-aura", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-tracing 17.0.1", + "staging-parachain-info", + "staging-xcm 14.2.0", + "staging-xcm-executor", + "substrate-wasm-builder", ] [[package]] -name = "scale-encode" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d70cb4b29360105483fac1ed567ff95d65224a14dd275b6303ed0a654c78de5" -dependencies = [ - "derive_more 0.99.18", - "parity-scale-codec", - "scale-encode-derive 0.5.0", - "scale-info", - "smallvec", -] - -[[package]] -name = "scale-encode" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ba0b9c48dc0eb20c60b083c29447c0c4617cb7c4a4c9fef72aa5c5bc539e15e" -dependencies = [ - "derive_more 0.99.18", - "parity-scale-codec", - "primitive-types", - "scale-bits 0.6.0", - "scale-encode-derive 0.7.1", - "scale-type-resolver", - "smallvec", -] - -[[package]] -name = "scale-encode-derive" -version = "0.5.0" +name = "parity-bip39" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "995491f110efdc6bea96d6a746140e32bfceb4ea47510750a5467295a4707a25" +checksum = "4e69bf016dc406eff7d53a7d3f7cf1c2e72c82b9088aac1118591e36dd2cd3e9" dependencies = [ - "darling 0.14.4", - "proc-macro-crate 1.3.1", - "proc-macro2", - "quote", - "syn 1.0.109", + "bitcoin_hashes 0.13.0", + "rand", + "rand_core 0.6.4", + "serde", + "unicode-normalization", ] [[package]] -name = "scale-encode-derive" -version = "0.7.1" +name = "parity-bytes" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82ab7e60e2d9c8d47105f44527b26f04418e5e624ffc034f6b4a86c0ba19c5bf" -dependencies = [ - "darling 0.14.4", - "proc-macro-crate 1.3.1", - "proc-macro2", - "quote", - "syn 1.0.109", -] +checksum = "16b56e3a2420138bdb970f84dfb9c774aea80fa0e7371549eedec0d80c209c67" [[package]] -name = "scale-info" -version = "2.11.6" +name = "parity-scale-codec" +version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346a3b32eba2640d17a9cb5927056b08f3de90f65b72fe09402c2ad07d684d0b" +checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" dependencies = [ + "arrayvec 0.7.6", "bitvec", - "cfg-if", - "derive_more 1.0.0", - "parity-scale-codec", - "scale-info-derive", - "schemars", + "byte-slice-cast", + "bytes", + "impl-trait-for-tuples", + "parity-scale-codec-derive", "serde", ] [[package]] -name = "scale-info-derive" -version = "2.11.6" +name = "parity-scale-codec-derive" +version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6630024bf739e2179b91fb424b28898baf819414262c5d376677dbff1fe7ebf" +checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.89", + "syn 1.0.109", ] [[package]] -name = "scale-type-resolver" -version = "0.2.0" +name = "parity-util-mem" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0cded6518aa0bd6c1be2b88ac81bf7044992f0f154bfbabd5ad34f43512abcb" +checksum = "0d32c34f4f5ca7f9196001c0aba5a1f9a5a12382c8944b8b0f90233282d1e8f8" dependencies = [ - "scale-info", + "cfg-if", + "ethereum-types", + "hashbrown 0.12.3", + "impl-trait-for-tuples", + "lru 0.8.1", + "parity-util-mem-derive", + "parking_lot", + "primitive-types 0.12.2", "smallvec", + "winapi", ] [[package]] -name = "scale-typegen" -version = "0.8.0" +name = "parity-util-mem-derive" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "498d1aecf2ea61325d4511787c115791639c0fd21ef4f8e11e49dd09eff2bbac" +checksum = "f557c32c6d268a07c921471619c0295f5efad3a0e76d4f97a05c091a51d110b2" dependencies = [ "proc-macro2", - "quote", - "scale-info", - "syn 2.0.89", - "thiserror", -] - -[[package]] -name = "scale-value" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4d772cfb7569e03868400344a1695d16560bf62b86b918604773607d39ec84" -dependencies = [ - "base58", - "blake2", - "derive_more 0.99.18", - "either", - "frame-metadata 15.1.0", - "parity-scale-codec", - "scale-bits 0.6.0", - "scale-decode 0.13.1", - "scale-encode 0.7.1", - "scale-info", - "scale-type-resolver", - "serde", - "yap", + "syn 1.0.109", + "synstructure 0.12.6", ] [[package]] -name = "schannel" -version = "0.1.24" +name = "parity-wasm" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" -dependencies = [ - "windows-sys 0.59.0", -] +checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" [[package]] -name = "schemars" -version = "0.8.21" +name = "parking" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" -dependencies = [ - "dyn-clone", - "schemars_derive", - "serde", - "serde_json", -] +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] -name = "schemars_derive" -version = "0.8.21" +name = "parking_lot" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 2.0.89", + "lock_api", + "parking_lot_core", ] [[package]] -name = "schnellru" -version = "0.2.3" +name = "parking_lot_core" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9a8ef13a93c54d20580de1e5c413e624e53121d42fc7e2c11d10ef7f8b02367" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ - "ahash 0.8.11", "cfg-if", - "hashbrown 0.13.2", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", ] [[package]] -name = "schnorrkel" -version = "0.11.4" +name = "password-hash" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de18f6d8ba0aad7045f5feae07ec29899c1112584a38509a84ad7b04451eaa0" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ - "aead", - "arrayref", - "arrayvec 0.7.6", - "curve25519-dalek 4.1.3", - "getrandom_or_panic", - "merlin", + "base64ct", "rand_core 0.6.4", - "serde_bytes", - "sha2 0.10.8", "subtle", - "zeroize", ] [[package]] -name = "scopeguard" -version = "1.2.0" +name = "paste" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] -name = "scratch" -version = "1.0.7" +name = "pbkdf2" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest 0.10.7", + "hmac 0.12.1", + "password-hash", +] [[package]] -name = "sct" -version = "0.7.1" +name = "pem" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" dependencies = [ - "ring", - "untrusted", + "base64 0.22.1", + "serde", ] [[package]] -name = "seahash" -version = "4.1.0" +name = "percent-encoding" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] -name = "sec1" -version = "0.7.3" +name = "pest" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" dependencies = [ - "base16ct", - "der", - "generic-array", - "pkcs8", - "serdect", - "subtle", - "zeroize", + "memchr", + "thiserror 2.0.6", + "ucd-trie", ] [[package]] -name = "secp256k1" -version = "0.28.2" +name = "pest_derive" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" +checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" dependencies = [ - "secp256k1-sys", + "pest", + "pest_generator", ] [[package]] -name = "secp256k1-sys" -version = "0.9.2" +name = "pest_generator" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d1746aae42c19d583c3c1a8c646bfad910498e2051c551a7f2e3c0c9fbb7eb" +checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" dependencies = [ - "cc", + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.90", ] [[package]] -name = "secrecy" -version = "0.8.0" +name = "pest_meta" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" dependencies = [ - "serde", - "zeroize", + "once_cell", + "pest", + "sha2 0.10.8", ] [[package]] -name = "security-framework" -version = "2.11.1" +name = "pin-project" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" dependencies = [ - "bitflags 2.6.0", - "core-foundation", - "core-foundation-sys", - "libc", - "num-bigint", - "security-framework-sys", + "pin-project-internal", ] [[package]] -name = "security-framework-sys" -version = "2.11.1" +name = "pin-project-internal" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ - "core-foundation-sys", - "libc", + "proc-macro2", + "quote", + "syn 2.0.90", ] [[package]] -name = "semver" -version = "1.0.23" +name = "pin-project-lite" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" -dependencies = [ - "serde", -] +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] -name = "serde" -version = "1.0.210" +name = "pin-utils" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" -dependencies = [ - "serde_derive", -] +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] -name = "serde-value" -version = "0.7.0" +name = "piper" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" dependencies = [ - "ordered-float", - "serde", + "atomic-waker", + "fastrand", + "futures-io", ] [[package]] -name = "serde_bytes" -version = "0.11.15" +name = "pkcs8" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "serde", + "der", + "spki", ] [[package]] -name = "serde_derive" -version = "1.0.210" +name = "pkg-config" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.89", -] +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] -name = "serde_derive_internals" -version = "0.29.1" +name = "polkadot-ckb-merkle-mountain-range" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +checksum = "a4b44320e5f7ce2c18227537a3032ae5b2c476a7e8eddba45333e1011fc31b92" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.89", + "cfg-if", + "itertools 0.10.5", ] [[package]] -name = "serde_json" -version = "1.0.128" +name = "polkadot-core-primitives" +version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "e2900d3b857e34c480101618a950c3a4fbcddc8c0d50573d48553376185908b8" dependencies = [ - "indexmap 2.5.0", - "itoa", - "memchr", - "ryu", - "serde", + "parity-scale-codec", + "scale-info", + "sp-core 34.0.0", + "sp-runtime 39.0.2", ] [[package]] -name = "serde_repr" -version = "0.1.19" +name = "polkadot-parachain-primitives" +version = "14.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +checksum = "52b5648a2e8ce1f9a0f8c41c38def670cefd91932cd793468e1a5b0b0b4e4af1" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.89", + "bounded-collections", + "derive_more 0.99.18", + "parity-scale-codec", + "polkadot-core-primitives", + "scale-info", + "serde", + "sp-core 34.0.0", + "sp-runtime 39.0.2", + "sp-weights 31.0.0", ] [[package]] -name = "serde_spanned" -version = "0.6.7" +name = "polkadot-primitives" +version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +checksum = "b57bc055fa389372ec5fc0001b99aeffd50f3fd379280ce572d935189bb58dd8" dependencies = [ + "bitvec", + "hex-literal", + "log", + "parity-scale-codec", + "polkadot-core-primitives", + "polkadot-parachain-primitives", + "scale-info", "serde", + "sp-api", + "sp-application-crypto 38.0.0", + "sp-arithmetic 26.0.0", + "sp-authority-discovery", + "sp-consensus-slots", + "sp-core 34.0.0", + "sp-inherents", + "sp-io 38.0.0", + "sp-keystore 0.40.0", + "sp-runtime 39.0.2", + "sp-staking 34.0.0", ] [[package]] -name = "serde_urlencoded" -version = "0.7.1" +name = "polkadot-primitives" +version = "16.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +checksum = "6bb20b75d33212150242d39890d7ededab55f1084160c337f15d0eb8ca8c3ad4" dependencies = [ - "form_urlencoded", - "itoa", - "ryu", + "bitvec", + "hex-literal", + "log", + "parity-scale-codec", + "polkadot-core-primitives", + "polkadot-parachain-primitives", + "scale-info", "serde", + "sp-api", + "sp-application-crypto 38.0.0", + "sp-arithmetic 26.0.0", + "sp-authority-discovery", + "sp-consensus-slots", + "sp-core 34.0.0", + "sp-inherents", + "sp-io 38.0.0", + "sp-keystore 0.40.0", + "sp-runtime 39.0.2", + "sp-staking 36.0.0", ] [[package]] -name = "serde_with" -version = "3.9.0" +name = "polkadot-runtime-common" +version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" +checksum = "dc15154ba5ca55d323fcf7af0f5dcd39d58dcb4dfac3d9b30404840a6d8bbde4" dependencies = [ - "base64 0.22.1", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.5.0", + "bitvec", + "frame-benchmarking", + "frame-election-provider-support", + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "libsecp256k1", + "log", + "pallet-asset-rate", + "pallet-authorship", + "pallet-balances", + "pallet-broker", + "pallet-election-provider-multi-phase", + "pallet-fast-unstake", + "pallet-identity", + "pallet-session", + "pallet-staking", + "pallet-staking-reward-fn", + "pallet-timestamp", + "pallet-transaction-payment", + "pallet-treasury", + "pallet-vesting", + "parity-scale-codec", + "polkadot-primitives 16.0.0", + "polkadot-runtime-parachains", + "rustc-hex", + "scale-info", "serde", "serde_derive", - "serde_json", - "time", + "slot-range-helper", + "sp-api", + "sp-core 34.0.0", + "sp-inherents", + "sp-io 38.0.0", + "sp-npos-elections", + "sp-runtime 39.0.2", + "sp-session", + "sp-staking 36.0.0", + "staging-xcm 14.2.0", + "staging-xcm-builder", + "staging-xcm-executor", + "static_assertions", ] [[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" +name = "polkadot-runtime-metrics" +version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +checksum = "2c306f1ace7644a24de860479f92cf8d6467393bb0c9b0777c57e2d42c9d452a" dependencies = [ - "indexmap 2.5.0", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", + "bs58", + "frame-benchmarking", + "parity-scale-codec", + "polkadot-primitives 16.0.0", + "sp-tracing 17.0.1", ] [[package]] -name = "serdect" -version = "0.2.0" +name = "polkadot-runtime-parachains" +version = "17.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +checksum = "bd58e3a17e5df678f5737b018cbfec603af2c93bec56bbb9f8fb8b2b017b54b1" dependencies = [ - "base16ct", + "bitflags 1.3.2", + "bitvec", + "derive_more 0.99.18", + "frame-benchmarking", + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "log", + "pallet-authority-discovery", + "pallet-authorship", + "pallet-babe", + "pallet-balances", + "pallet-broker", + "pallet-message-queue", + "pallet-mmr", + "pallet-session", + "pallet-staking", + "pallet-timestamp", + "pallet-vesting", + "parity-scale-codec", + "polkadot-core-primitives", + "polkadot-parachain-primitives", + "polkadot-primitives 16.0.0", + "polkadot-runtime-metrics", + "rand", + "rand_chacha", + "scale-info", "serde", + "sp-api", + "sp-application-crypto 38.0.0", + "sp-arithmetic 26.0.0", + "sp-core 34.0.0", + "sp-inherents", + "sp-io 38.0.0", + "sp-keystore 0.40.0", + "sp-runtime 39.0.2", + "sp-session", + "sp-staking 36.0.0", + "sp-std", + "staging-xcm 14.2.0", + "staging-xcm-executor", ] [[package]] -name = "sha-1" -version = "0.9.8" +name = "polkadot-sdk" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", -] - -[[package]] -name = "sha1" -version = "0.10.6" +checksum = "eb819108697967452fa6d8d96ab4c0d48cbaa423b3156499dcb24f1cf95d6775" +dependencies = [ + "asset-test-utils", + "assets-common", + "binary-merkle-tree", + "bp-header-chain", + "bp-messages", + "bp-parachains", + "bp-polkadot", + "bp-polkadot-core", + "bp-relayers", + "bp-runtime", + "bp-test-utils", + "bp-xcm-bridge-hub", + "bp-xcm-bridge-hub-router", + "bridge-hub-common", + "bridge-hub-test-utils", + "bridge-runtime-common", + "cumulus-pallet-aura-ext", + "cumulus-pallet-dmp-queue", + "cumulus-pallet-parachain-system", + "cumulus-pallet-parachain-system-proc-macro", + "cumulus-pallet-session-benchmarking", + "cumulus-pallet-solo-to-para", + "cumulus-pallet-xcm", + "cumulus-pallet-xcmp-queue", + "cumulus-ping", + "cumulus-primitives-aura", + "cumulus-primitives-core", + "cumulus-primitives-parachain-inherent", + "cumulus-primitives-proof-size-hostfunction", + "cumulus-primitives-storage-weight-reclaim", + "cumulus-primitives-timestamp", + "cumulus-primitives-utility", + "cumulus-test-relay-sproof-builder", + "frame-benchmarking", + "frame-benchmarking-pallet-pov", + "frame-election-provider-support", + "frame-executive", + "frame-metadata-hash-extension", + "frame-support", + "frame-support-procedural", + "frame-system", + "frame-system-benchmarking", + "frame-system-rpc-runtime-api", + "frame-try-runtime", + "pallet-alliance", + "pallet-asset-conversion", + "pallet-asset-conversion-ops", + "pallet-asset-conversion-tx-payment", + "pallet-asset-rate", + "pallet-asset-tx-payment", + "pallet-assets", + "pallet-assets-freezer", + "pallet-atomic-swap", + "pallet-aura", + "pallet-authority-discovery", + "pallet-authorship", + "pallet-babe", + "pallet-bags-list", + "pallet-balances", + "pallet-beefy", + "pallet-beefy-mmr", + "pallet-bounties", + "pallet-bridge-grandpa", + "pallet-bridge-messages", + "pallet-bridge-parachains", + "pallet-bridge-relayers", + "pallet-broker", + "pallet-child-bounties", + "pallet-collator-selection", + "pallet-collective", + "pallet-collective-content", + "pallet-contracts", + "pallet-contracts-mock-network", + "pallet-conviction-voting", + "pallet-core-fellowship", + "pallet-delegated-staking", + "pallet-democracy", + "pallet-dev-mode", + "pallet-election-provider-multi-phase", + "pallet-election-provider-support-benchmarking", + "pallet-elections-phragmen", + "pallet-fast-unstake", + "pallet-glutton", + "pallet-grandpa", + "pallet-identity", + "pallet-im-online", + "pallet-indices", + "pallet-insecure-randomness-collective-flip", + "pallet-lottery", + "pallet-membership", + "pallet-message-queue", + "pallet-migrations", + "pallet-mixnet", + "pallet-mmr", + "pallet-multisig", + "pallet-nft-fractionalization", + "pallet-nfts", + "pallet-nfts-runtime-api", + "pallet-nis", + "pallet-node-authorization", + "pallet-nomination-pools", + "pallet-nomination-pools-benchmarking", + "pallet-nomination-pools-runtime-api", + "pallet-offences", + "pallet-offences-benchmarking", + "pallet-paged-list", + "pallet-parameters", + "pallet-preimage", + "pallet-proxy", + "pallet-ranked-collective", + "pallet-recovery", + "pallet-referenda", + "pallet-remark", + "pallet-revive", + "pallet-revive-fixtures", + "pallet-revive-mock-network", + "pallet-root-offences", + "pallet-root-testing", + "pallet-safe-mode", + "pallet-salary", + "pallet-scheduler", + "pallet-scored-pool", + "pallet-session", + "pallet-session-benchmarking", + "pallet-skip-feeless-payment", + "pallet-society", + "pallet-staking", + "pallet-staking-reward-fn", + "pallet-staking-runtime-api", + "pallet-state-trie-migration", + "pallet-statement", + "pallet-sudo", + "pallet-timestamp", + "pallet-tips", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", + "pallet-transaction-storage", + "pallet-treasury", + "pallet-tx-pause", + "pallet-uniques", + "pallet-utility", + "pallet-vesting", + "pallet-whitelist", + "pallet-xcm", + "pallet-xcm-benchmarks", + "pallet-xcm-bridge-hub", + "pallet-xcm-bridge-hub-router", + "parachains-common", + "parachains-runtimes-test-utils", + "polkadot-core-primitives", + "polkadot-parachain-primitives", + "polkadot-primitives 16.0.0", + "polkadot-runtime-common", + "polkadot-runtime-metrics", + "polkadot-runtime-parachains", + "polkadot-sdk-frame", + "sc-executor", + "slot-range-helper", + "snowbridge-beacon-primitives", + "snowbridge-core", + "snowbridge-ethereum", + "snowbridge-outbound-queue-merkle-tree", + "snowbridge-outbound-queue-runtime-api", + "snowbridge-pallet-ethereum-client", + "snowbridge-pallet-ethereum-client-fixtures", + "snowbridge-pallet-inbound-queue", + "snowbridge-pallet-inbound-queue-fixtures", + "snowbridge-pallet-outbound-queue", + "snowbridge-pallet-system", + "snowbridge-router-primitives", + "snowbridge-runtime-common", + "snowbridge-runtime-test-common", + "snowbridge-system-runtime-api", + "sp-api", + "sp-api-proc-macro", + "sp-application-crypto 38.0.0", + "sp-arithmetic 26.0.0", + "sp-authority-discovery", + "sp-block-builder", + "sp-consensus-aura", + "sp-consensus-babe", + "sp-consensus-beefy", + "sp-consensus-grandpa", + "sp-consensus-pow", + "sp-consensus-slots", + "sp-core 34.0.0", + "sp-core-hashing", + "sp-crypto-ec-utils", + "sp-crypto-hashing", + "sp-debug-derive", + "sp-externalities 0.29.0", + "sp-genesis-builder", + "sp-inherents", + "sp-io 38.0.0", + "sp-keyring", + "sp-keystore 0.40.0", + "sp-metadata-ir", + "sp-mixnet", + "sp-mmr-primitives", + "sp-npos-elections", + "sp-offchain", + "sp-runtime 39.0.2", + "sp-runtime-interface 28.0.0", + "sp-session", + "sp-staking 36.0.0", + "sp-state-machine 0.43.0", + "sp-statement-store", + "sp-std", + "sp-storage 21.0.0", + "sp-timestamp", + "sp-tracing 17.0.1", + "sp-transaction-pool", + "sp-transaction-storage-proof", + "sp-trie 37.0.0", + "sp-version", + "sp-wasm-interface 21.0.1", + "sp-weights 31.0.0", + "staging-parachain-info", + "staging-xcm 14.2.0", + "staging-xcm-builder", + "staging-xcm-executor", + "substrate-bip39 0.6.0", + "testnet-parachains-constants", + "xcm-runtime-apis", +] + +[[package]] +name = "polkadot-sdk-frame" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +checksum = "cbdeb15ce08142082461afe1a62c15f7ce10a731d91b203ad6a8dc8d2e4a6a54" dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.7", + "docify", + "frame-benchmarking", + "frame-executive", + "frame-support", + "frame-system", + "frame-system-benchmarking", + "frame-system-rpc-runtime-api", + "frame-try-runtime", + "log", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-arithmetic 26.0.0", + "sp-block-builder", + "sp-consensus-aura", + "sp-consensus-grandpa", + "sp-core 34.0.0", + "sp-inherents", + "sp-io 38.0.0", + "sp-offchain", + "sp-runtime 39.0.2", + "sp-session", + "sp-storage 21.0.0", + "sp-transaction-pool", + "sp-version", ] [[package]] -name = "sha2" -version = "0.9.9" +name = "polkavm" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +checksum = "8a3693e5efdb2bf74e449cd25fd777a28bd7ed87e41f5d5da75eb31b4de48b94" dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", + "libc", + "log", + "polkavm-assembler 0.9.0", + "polkavm-common 0.9.0", + "polkavm-linux-raw 0.9.0", ] [[package]] -name = "sha2" -version = "0.10.8" +name = "polkavm" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "b7ec0c5935f2eff23cfc4653002f4f8d12b37f87a720e0631282d188c32089d6" dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.7", + "libc", + "log", + "polkavm-assembler 0.10.0", + "polkavm-common 0.10.0", + "polkavm-linux-raw 0.10.0", ] [[package]] -name = "sha3" -version = "0.10.8" +name = "polkavm-assembler" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +checksum = "1fa96d6d868243acc12de813dd48e756cbadcc8e13964c70d272753266deadc1" dependencies = [ - "digest 0.10.7", - "keccak", + "log", ] [[package]] -name = "sharded-slab" -version = "0.1.7" +name = "polkavm-assembler" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +checksum = "d8e4fd5a43100bf1afe9727b8130d01f966f5cfc9144d5604b21e795c2bcd80e" dependencies = [ - "lazy_static", + "log", ] [[package]] -name = "shared_child" -version = "1.0.1" +name = "polkavm-common" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09fa9338aed9a1df411814a5b2252f7cd206c55ae9bf2fa763f8de84603aa60c" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] +checksum = "88b4e215c80fe876147f3d58158d5dfeae7dabdd6047e175af77095b78d0035c" [[package]] -name = "shell-words" -version = "1.1.0" +name = "polkavm-common" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" +checksum = "92c99f7eee94e7be43ba37eef65ad0ee8cbaf89b7c00001c3f6d2be985cb1817" [[package]] -name = "shlex" -version = "1.3.0" +name = "polkavm-common" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "1d9428a5cfcc85c5d7b9fc4b6a18c4b802d0173d768182a51cc7751640f08b92" +dependencies = [ + "log", +] [[package]] -name = "signal-hook" -version = "0.3.17" +name = "polkavm-common" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +checksum = "0097b48bc0bedf9f3f537ce8f37e8f1202d8d83f9b621bdb21ff2c59b9097c50" dependencies = [ - "libc", - "signal-hook-registry", + "log", + "polkavm-assembler 0.10.0", ] [[package]] -name = "signal-hook-mio" -version = "0.2.4" +name = "polkavm-derive" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +checksum = "6380dbe1fb03ecc74ad55d841cfc75480222d153ba69ddcb00977866cbdabdb8" dependencies = [ - "libc", - "mio 0.8.11", - "signal-hook", + "polkavm-derive-impl 0.5.0", + "syn 2.0.90", ] [[package]] -name = "signal-hook-registry" -version = "1.4.2" +name = "polkavm-derive" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "79fa916f7962348bd1bb1a65a83401675e6fc86c51a0fdbcf92a3108e58e6125" dependencies = [ - "libc", + "polkavm-derive-impl-macro 0.8.0", ] [[package]] -name = "signature" -version = "2.2.0" +name = "polkavm-derive" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +checksum = "ae8c4bea6f3e11cd89bb18bcdddac10bd9a24015399bd1c485ad68a985a19606" dependencies = [ - "digest 0.10.7", - "rand_core 0.6.4", + "polkavm-derive-impl-macro 0.9.0", ] [[package]] -name = "simdutf8" -version = "0.1.4" +name = "polkavm-derive" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" +checksum = "0dcc701385c08c31bdb0569f0c51a290c580d892fa77f1dd88a7352a62679ecf" +dependencies = [ + "polkavm-derive-impl-macro 0.10.0", +] [[package]] -name = "similar" -version = "2.6.0" +name = "polkavm-derive-impl" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" +checksum = "dc8211b3365bbafb2fb32057d68b0e1ca55d079f5cf6f9da9b98079b94b3987d" +dependencies = [ + "polkavm-common 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.90", +] [[package]] -name = "simple-mermaid" -version = "0.1.1" +name = "polkavm-derive-impl" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "620a1d43d70e142b1d46a929af51d44f383db9c7a2ec122de2cd992ccfcf3c18" +checksum = "c10b2654a8a10a83c260bfb93e97b262cf0017494ab94a65d389e0eda6de6c9c" +dependencies = [ + "polkavm-common 0.8.0", + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "polkavm-derive-impl" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c4fdfc49717fb9a196e74a5d28e0bc764eb394a2c803eb11133a31ac996c60c" +dependencies = [ + "polkavm-common 0.9.0", + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "polkavm-derive-impl" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7855353a5a783dd5d09e3b915474bddf66575f5a3cf45dec8d1c5e051ba320dc" +dependencies = [ + "polkavm-common 0.10.0", + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "polkavm-derive-impl-macro" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e85319a0d5129dc9f021c62607e0804f5fb777a05cdda44d750ac0732def66" +dependencies = [ + "polkavm-derive-impl 0.8.0", + "syn 2.0.90", +] + +[[package]] +name = "polkavm-derive-impl-macro" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" +dependencies = [ + "polkavm-derive-impl 0.9.0", + "syn 2.0.90", +] + +[[package]] +name = "polkavm-derive-impl-macro" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9324fe036de37c17829af233b46ef6b5562d4a0c09bb7fdb9f8378856dee30cf" +dependencies = [ + "polkavm-derive-impl 0.10.0", + "syn 2.0.90", +] + +[[package]] +name = "polkavm-linker" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c7be503e60cf56c0eb785f90aaba4b583b36bff00e93997d93fef97f9553c39" +dependencies = [ + "gimli 0.28.1", + "hashbrown 0.14.5", + "log", + "object 0.32.2", + "polkavm-common 0.9.0", + "regalloc2 0.9.3", + "rustc-demangle", +] + +[[package]] +name = "polkavm-linker" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d704edfe7bdcc876784f19436d53d515b65eb07bc9a0fae77085d552c2dbbb5" +dependencies = [ + "gimli 0.28.1", + "hashbrown 0.14.5", + "log", + "object 0.36.5", + "polkavm-common 0.10.0", + "regalloc2 0.9.3", + "rustc-demangle", +] + +[[package]] +name = "polkavm-linux-raw" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26e85d3456948e650dff0cfc85603915847faf893ed1e66b020bb82ef4557120" + +[[package]] +name = "polkavm-linux-raw" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26e45fa59c7e1bb12ef5289080601e9ec9b31435f6e32800a5c90c132453d126" + +[[package]] +name = "polling" +version = "3.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.4.0", + "pin-project-lite", + "rustix 0.38.42", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "pop-cli" +version = "0.5.0" +dependencies = [ + "anyhow", + "assert_cmd", + "clap", + "cliclack", + "console", + "dirs", + "duct", + "env_logger 0.11.5", + "git2", + "os_info", + "pop-common", + "pop-contracts", + "pop-parachains", + "pop-telemetry", + "predicates", + "reqwest 0.12.9", + "serde_json", + "sp-core 31.0.0", + "sp-weights 30.0.0", + "strum 0.26.3", + "strum_macros 0.26.4", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "pop-common" +version = "0.5.0" +dependencies = [ + "anyhow", + "cargo_toml", + "contract-build", + "contract-extrinsics", + "duct", + "flate2", + "git2", + "git2_credentials", + "ink_env", + "mockito", + "regex", + "reqwest 0.12.9", + "scale-info", + "serde", + "serde_json", + "strum 0.26.3", + "strum_macros 0.26.4", + "subxt 0.37.0", + "subxt-signer 0.37.0", + "tar", + "tempfile", + "thiserror 1.0.69", + "tokio", + "toml 0.5.11", + "toml_edit 0.22.22", + "url", +] + +[[package]] +name = "pop-contracts" +version = "0.5.0" +dependencies = [ + "anyhow", + "contract-build", + "contract-extrinsics", + "contract-transcode", + "dirs", + "duct", + "flate2", + "heck 0.5.0", + "ink_env", + "mockito", + "pop-common", + "reqwest 0.12.9", + "scale-info", + "sp-core 31.0.0", + "sp-weights 30.0.0", + "strum 0.26.3", + "strum_macros 0.26.4", + "tar", + "tempfile", + "thiserror 1.0.69", + "tokio", + "tokio-test", + "url", +] + +[[package]] +name = "pop-parachains" +version = "0.5.0" +dependencies = [ + "anyhow", + "askama", + "clap", + "duct", + "flate2", + "glob", + "hex", + "indexmap 2.7.0", + "mockito", + "pop-common", + "reqwest 0.12.9", + "scale-info", + "scale-value 0.16.3", + "serde_json", + "strum 0.26.3", + "strum_macros 0.26.4", + "subxt 0.37.0", + "symlink", + "tar", + "tempfile", + "thiserror 1.0.69", + "tokio", + "tokio-test", + "toml_edit 0.22.22", + "url", + "walkdir", + "zombienet-sdk", +] + +[[package]] +name = "pop-telemetry" +version = "0.5.0" +dependencies = [ + "dirs", + "env_logger 0.11.5", + "log", + "mockito", + "reqwest 0.12.9", + "serde", + "serde_json", + "tempfile", + "thiserror 1.0.69", + "tokio", +] + +[[package]] +name = "portable-atomic" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "predicates" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" +dependencies = [ + "anstyle", + "difflib", + "float-cmp", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" + +[[package]] +name = "predicates-tree" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "prettyplease" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +dependencies = [ + "proc-macro2", + "syn 2.0.90", +] + +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec 0.6.0", + "impl-rlp", + "impl-serde 0.4.0", + "scale-info", + "uint 0.9.5", +] + +[[package]] +name = "primitive-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15600a7d856470b7d278b3fe0e311fe28c2526348549f8ef2ff7db3299c87f5" +dependencies = [ + "fixed-hash", + "impl-codec 0.7.0", + "impl-serde 0.5.0", + "scale-info", + "uint 0.10.0", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +dependencies = [ + "toml_edit 0.22.22", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "proc-macro-warning" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "834da187cfe638ae8abb0203f0b33e5ccdb02a28e7199f2f47b3e2754f50edca" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.6.0", + "lazy_static", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax 0.8.5", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "psm" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200b9ff220857e53e184257720a14553b2f4aa02577d2ed9842d45d4b9654810" +dependencies = [ + "cc", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quick-protobuf" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6da84cc204722a989e01ba2f6e1e276e190f22263d0cb6ce8526fcdb0d2e1f" +dependencies = [ + "byteorder", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "reconnecting-jsonrpsee-ws-client" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06fa4f17e09edfc3131636082faaec633c7baa269396b4004040bc6c52f49f65" +dependencies = [ + "cfg_aliases", + "finito", + "futures", + "jsonrpsee 0.23.2", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "ref-cast" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf0a6f84d5f1d581da8b41b47ec8600871962f2a528115b542b362d4b744931" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "regalloc2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80535183cae11b149d618fbd3c37e38d7cda589d82d7769e196ca9a9042d7621" +dependencies = [ + "fxhash", + "log", + "slice-group-by", + "smallvec", +] + +[[package]] +name = "regalloc2" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad156d539c879b7a24a363a2016d77961786e71f48f2e2fc8302a92abd2429a6" +dependencies = [ + "hashbrown 0.13.2", + "log", + "rustc-hash 1.1.0", + "slice-group-by", + "smallvec", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.31", + "hyper-tls 0.5.0", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile 1.0.4", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 0.1.2", + "system-configuration 0.5.1", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "reqwest" +version = "0.12.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.4.7", + "http 1.2.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.5.1", + "hyper-rustls 0.27.3", + "hyper-tls 0.6.0", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile 2.2.0", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.2", + "system-configuration 0.6.1", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-registry", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac 0.12.1", + "subtle", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rkyv" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rlibc" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc874b127765f014d792f16763a81245ab80500e2ad921ed4ee9e82481ee08fe" + +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + +[[package]] +name = "rococo-runtime-constants" +version = "17.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1ec6683a2e52fe3be2eaf942a80619abd99eb36e973c5ab4489a2f3b100db5c" +dependencies = [ + "frame-support", + "polkadot-primitives 16.0.0", + "polkadot-runtime-common", + "smallvec", + "sp-core 34.0.0", + "sp-runtime 39.0.2", + "sp-weights 31.0.0", + "staging-xcm 14.2.0", + "staging-xcm-builder", +] + +[[package]] +name = "ruint" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3cc4c2511671f327125da14133d0c5c5d137f006a1017a16f557bc85b16286" +dependencies = [ + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "bytes", + "fastrlp", + "num-bigint", + "num-traits", + "parity-scale-codec", + "primitive-types 0.12.2", + "proptest", + "rand", + "rlp", + "ruint-macro", + "serde", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" + +[[package]] +name = "rust_decimal" +version = "1.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" +dependencies = [ + "arrayvec 0.7.6", + "borsh", + "bytes", + "num-traits", + "rand", + "rkyv", + "serde", + "serde_json", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.23", +] + +[[package]] +name = "rustix" +version = "0.36.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "305efbd14fde4139eb501df5f136994bb520b033fa9fbdce287507dc23b8c7ed" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.1.4", + "windows-sys 0.45.0", +] + +[[package]] +name = "rustix" +version = "0.38.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys 0.4.14", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls" +version = "0.23.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile 1.0.4", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.2.0", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" + +[[package]] +name = "rustls-platform-verifier" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afbb878bdfdf63a336a5e63561b1835e7a8c91524f51621db870169eac84b490" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls 0.23.19", + "rustls-native-certs 0.7.3", + "rustls-platform-verifier-android", + "rustls-webpki 0.102.8", + "security-framework", + "security-framework-sys", + "webpki-roots", + "winapi", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "ruzstd" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c4eb8a81997cf040a091d1f7e1938aeab6749d3a0dfa73af43cdc32393483d" +dependencies = [ + "byteorder", + "derive_more 0.99.18", + "twox-hash", +] + +[[package]] +name = "ruzstd" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5174a470eeb535a721ae9fdd6e291c2411a906b96592182d05217591d5c5cf7b" +dependencies = [ + "byteorder", + "derive_more 0.99.18", +] + +[[package]] +name = "rw-stream-sink" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8c9026ff5d2f23da5e45bbc283f156383001bfb09c4e44256d02c1a685fe9a1" +dependencies = [ + "futures", + "pin-project", + "static_assertions", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "safe-mix" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d3d055a2582e6b00ed7a31c1524040aa391092bf636328350813f3a0605215c" +dependencies = [ + "rustc_version 0.2.3", +] + +[[package]] +name = "safe_arch" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3460605018fdc9612bce72735cba0d27efbcd9904780d44c7e3a9948f96148a" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "sc-allocator" +version = "29.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b975ee3a95eaacb611e7b415737a7fa2db4d8ad7b880cc1b97371b04e95c7903" +dependencies = [ + "log", + "sp-core 34.0.0", + "sp-wasm-interface 21.0.1", + "thiserror 1.0.69", +] + +[[package]] +name = "sc-executor" +version = "0.40.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f0cc0a3728fd033589183460c5a49b2e7545d09dc89a098216ef9e9aadcd9dc" +dependencies = [ + "parity-scale-codec", + "parking_lot", + "sc-executor-common", + "sc-executor-polkavm", + "sc-executor-wasmtime", + "schnellru", + "sp-api", + "sp-core 34.0.0", + "sp-externalities 0.29.0", + "sp-io 38.0.0", + "sp-panic-handler", + "sp-runtime-interface 28.0.0", + "sp-trie 37.0.0", + "sp-version", + "sp-wasm-interface 21.0.1", + "tracing", +] + +[[package]] +name = "sc-executor-common" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3b703a33dcb7cddf19176fdf12294b9a6408125836b0f4afee3e6969e7f190" +dependencies = [ + "polkavm 0.9.3", + "sc-allocator", + "sp-maybe-compressed-blob", + "sp-wasm-interface 21.0.1", + "thiserror 1.0.69", + "wasm-instrument", +] + +[[package]] +name = "sc-executor-polkavm" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26fe58d9cacfab73e5595fa84b80f7bd03efebe54a0574daaeb221a1d1f7ab80" +dependencies = [ + "log", + "polkavm 0.9.3", + "sc-executor-common", + "sp-wasm-interface 21.0.1", +] + +[[package]] +name = "sc-executor-wasmtime" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cd498f2f77ec1f861c30804f5bfd796d4afcc8ce44ea1f11bfbe2847551d161" +dependencies = [ + "anyhow", + "cfg-if", + "libc", + "log", + "parking_lot", + "rustix 0.36.17", + "sc-allocator", + "sc-executor-common", + "sp-runtime-interface 28.0.0", + "sp-wasm-interface 21.0.1", + "wasmtime", +] + +[[package]] +name = "scale-bits" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "662d10dcd57b1c2a3c41c9cf68f71fb09747ada1ea932ad961aca7e2ca28315f" +dependencies = [ + "parity-scale-codec", + "scale-type-resolver 0.1.1", +] + +[[package]] +name = "scale-bits" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57b1e7f6b65ed1f04e79a85a57d755ad56d76fdf1e9bddcc9ae14f71fcdcf54" +dependencies = [ + "parity-scale-codec", + "scale-info", + "scale-type-resolver 0.2.0", + "serde", +] + +[[package]] +name = "scale-decode" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc79ba56a1c742f5aeeed1f1801f3edf51f7e818f0a54582cac6f131364ea7b" +dependencies = [ + "derive_more 0.99.18", + "parity-scale-codec", + "scale-bits 0.5.0", + "scale-decode-derive 0.11.1", + "scale-type-resolver 0.1.1", + "smallvec", +] + +[[package]] +name = "scale-decode" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e98f3262c250d90e700bb802eb704e1f841e03331c2eb815e46516c4edbf5b27" +dependencies = [ + "derive_more 0.99.18", + "parity-scale-codec", + "primitive-types 0.12.2", + "scale-bits 0.6.0", + "scale-decode-derive 0.13.1", + "scale-type-resolver 0.2.0", + "smallvec", +] + +[[package]] +name = "scale-decode" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ae9cc099ae85ff28820210732b00f019546f36f33225f509fe25d5816864a0" +dependencies = [ + "derive_more 1.0.0", + "parity-scale-codec", + "primitive-types 0.13.1", + "scale-bits 0.6.0", + "scale-decode-derive 0.14.0", + "scale-type-resolver 0.2.0", + "smallvec", +] + +[[package]] +name = "scale-decode-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5398fdb3c7bea3cb419bac4983aadacae93fe1a7b5f693f4ebd98c3821aad7a5" +dependencies = [ + "darling 0.14.4", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "scale-decode-derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb22f574168103cdd3133b19281639ca65ad985e24612728f727339dcaf4021" +dependencies = [ + "darling 0.14.4", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "scale-decode-derive" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ed9401effa946b493f9f84dc03714cca98119b230497df6f3df6b84a2b03648" +dependencies = [ + "darling 0.20.10", + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "scale-encode" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628800925a33794fb5387781b883b5e14d130fece9af5a63613867b8de07c5c7" +dependencies = [ + "derive_more 0.99.18", + "parity-scale-codec", + "scale-encode-derive 0.6.0", + "scale-type-resolver 0.1.1", + "smallvec", +] + +[[package]] +name = "scale-encode" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528464e6ae6c8f98e2b79633bf79ef939552e795e316579dab09c61670d56602" +dependencies = [ + "derive_more 0.99.18", + "parity-scale-codec", + "primitive-types 0.12.2", + "scale-bits 0.6.0", + "scale-encode-derive 0.7.2", + "scale-type-resolver 0.2.0", + "smallvec", +] + +[[package]] +name = "scale-encode" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9271284d05d0749c40771c46180ce89905fd95aa72a2a2fddb4b7c0aa424db" +dependencies = [ + "derive_more 1.0.0", + "parity-scale-codec", + "primitive-types 0.13.1", + "scale-bits 0.6.0", + "scale-encode-derive 0.8.0", + "scale-type-resolver 0.2.0", + "smallvec", +] + +[[package]] +name = "scale-encode-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a304e1af7cdfbe7a24e08b012721456cc8cecdedadc14b3d10513eada63233c" +dependencies = [ + "darling 0.14.4", + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "scale-encode-derive" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef2618f123c88da9cd8853b69d766068f1eddc7692146d7dfe9b89e25ce2efd" +dependencies = [ + "darling 0.20.10", + "proc-macro-crate 3.2.0", + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "scale-encode-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "102fbc6236de6c53906c0b262f12c7aa69c2bdc604862c12728f5f4d370bc137" +dependencies = [ + "darling 0.20.10", + "proc-macro-crate 3.2.0", + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "scale-info" +version = "2.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346a3b32eba2640d17a9cb5927056b08f3de90f65b72fe09402c2ad07d684d0b" +dependencies = [ + "bitvec", + "cfg-if", + "derive_more 1.0.0", + "parity-scale-codec", + "scale-info-derive", + "schemars", + "serde", +] + +[[package]] +name = "scale-info-derive" +version = "2.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6630024bf739e2179b91fb424b28898baf819414262c5d376677dbff1fe7ebf" +dependencies = [ + "proc-macro-crate 3.2.0", + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "scale-type-resolver" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10b800069bfd43374e0f96f653e0d46882a2cb16d6d961ac43bea80f26c76843" +dependencies = [ + "smallvec", +] + +[[package]] +name = "scale-type-resolver" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0cded6518aa0bd6c1be2b88ac81bf7044992f0f154bfbabd5ad34f43512abcb" +dependencies = [ + "scale-info", + "smallvec", +] + +[[package]] +name = "scale-typegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498d1aecf2ea61325d4511787c115791639c0fd21ef4f8e11e49dd09eff2bbac" +dependencies = [ + "proc-macro2", + "quote", + "scale-info", + "syn 2.0.90", + "thiserror 1.0.69", +] + +[[package]] +name = "scale-typegen" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc4c70c7fea2eef1740f0081d3fe385d8bee1eef11e9272d3bec7dc8e5438e0" +dependencies = [ + "proc-macro2", + "quote", + "scale-info", + "syn 2.0.90", + "thiserror 1.0.69", +] + +[[package]] +name = "scale-value" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cd6ab090d823e75cfdb258aad5fe92e13f2af7d04b43a55d607d25fcc38c811" +dependencies = [ + "base58", + "blake2", + "derive_more 0.99.18", + "either", + "frame-metadata 15.1.0", + "parity-scale-codec", + "scale-bits 0.6.0", + "scale-decode 0.13.1", + "scale-encode 0.7.2", + "scale-info", + "scale-type-resolver 0.2.0", + "serde", + "yap", +] + +[[package]] +name = "scale-value" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e0ef2a0ee1e02a69ada37feb87ea1616ce9808aca072befe2d3131bf28576e" +dependencies = [ + "base58", + "blake2", + "derive_more 1.0.0", + "either", + "parity-scale-codec", + "scale-bits 0.6.0", + "scale-decode 0.14.0", + "scale-encode 0.8.0", + "scale-info", + "scale-type-resolver 0.2.0", + "serde", + "yap", +] + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "schemars" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.90", +] + +[[package]] +name = "schnellru" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9a8ef13a93c54d20580de1e5c413e624e53121d42fc7e2c11d10ef7f8b02367" +dependencies = [ + "ahash 0.8.11", + "cfg-if", + "hashbrown 0.13.2", +] + +[[package]] +name = "schnorrkel" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de18f6d8ba0aad7045f5feae07ec29899c1112584a38509a84ad7b04451eaa0" +dependencies = [ + "aead", + "arrayref", + "arrayvec 0.7.6", + "curve25519-dalek 4.1.3", + "getrandom_or_panic", + "merlin", + "rand_core 0.6.4", + "serde_bytes", + "sha2 0.10.8", + "subtle", + "zeroize", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scratch" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" + +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "password-hash", + "pbkdf2", + "salsa20", + "sha2 0.10.8", +] + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "serdect", + "subtle", + "zeroize", +] + +[[package]] +name = "secp256k1" +version = "0.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" +dependencies = [ + "secp256k1-sys 0.9.2", +] + +[[package]] +name = "secp256k1" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" +dependencies = [ + "bitcoin_hashes 0.14.0", + "rand", + "secp256k1-sys 0.10.1", +] + +[[package]] +name = "secp256k1-sys" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d1746aae42c19d583c3c1a8c646bfad910498e2051c551a7f2e3c0c9fbb7eb" +dependencies = [ + "cc", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "serde", + "zeroize", +] + +[[package]] +name = "secrecy" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +dependencies = [ + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "core-foundation-sys", + "libc", + "num-bigint", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a3186ec9e65071a2095434b1f5bb24838d4e8e130f584c790f6033c79943537" +dependencies = [ + "semver-parser 0.7.0", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser 0.7.0", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser 0.10.3", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +dependencies = [ + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "semver-parser" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" +dependencies = [ + "pest", +] + +[[package]] +name = "serde" +version = "1.0.216" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-big-array" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd31f59f6fe2b0c055371bb2f16d7f0aa7d8881676c04a55b1596d1a17cd10a4" +dependencies = [ + "serde", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde_bytes" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.216" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "serde_json" +version = "1.0.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +dependencies = [ + "indexmap 2.7.0", + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.7.0", + "serde", + "serde_derive", + "serde_json", + "time", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.7.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "sha-1" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shared_child" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09fa9338aed9a1df411814a5b2252f7cd206c55ae9bf2fa763f8de84603aa60c" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + +[[package]] +name = "simba" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a386a501cd104797982c15ae17aafe8b9261315b5d07e3ec803f2ea26be0fa" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "similar" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" + +[[package]] +name = "simple-mermaid" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "620a1d43d70e142b1d46a929af51d44f383db9c7a2ec122de2cd992ccfcf3c18" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "slice-group-by" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" + +[[package]] +name = "slot-range-helper" +version = "15.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e34f1146a457a5c554dedeae6c7273aa54c3b031f3e9eb0abd037b5511e2ce9" +dependencies = [ + "enumn", + "parity-scale-codec", + "paste", + "sp-runtime 39.0.2", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "smol" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f" +dependencies = [ + "async-channel", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-net", + "async-process", + "blocking", + "futures-lite", +] + +[[package]] +name = "smoldot" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d1eaa97d77be4d026a1e7ffad1bb3b78448763b357ea6f8188d3e6f736a9b9" +dependencies = [ + "arrayvec 0.7.6", + "async-lock", + "atomic-take", + "base64 0.21.7", + "bip39", + "blake2-rfc", + "bs58", + "chacha20", + "crossbeam-queue", + "derive_more 0.99.18", + "ed25519-zebra 4.0.3", + "either", + "event-listener 4.0.3", + "fnv", + "futures-lite", + "futures-util", + "hashbrown 0.14.5", + "hex", + "hmac 0.12.1", + "itertools 0.12.1", + "libm", + "libsecp256k1", + "merlin", + "no-std-net", + "nom", + "num-bigint", + "num-rational", + "num-traits", + "pbkdf2", + "pin-project", + "poly1305", + "rand", + "rand_chacha", + "ruzstd 0.5.0", + "schnorrkel", + "serde", + "serde_json", + "sha2 0.10.8", + "sha3", + "siphasher", + "slab", + "smallvec", + "soketto 0.7.1", + "twox-hash", + "wasmi 0.31.2", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "smoldot" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "966e72d77a3b2171bb7461d0cb91f43670c63558c62d7cf42809cae6c8b6b818" +dependencies = [ + "arrayvec 0.7.6", + "async-lock", + "atomic-take", + "base64 0.22.1", + "bip39", + "blake2-rfc", + "bs58", + "chacha20", + "crossbeam-queue", + "derive_more 0.99.18", + "ed25519-zebra 4.0.3", + "either", + "event-listener 5.3.1", + "fnv", + "futures-lite", + "futures-util", + "hashbrown 0.14.5", + "hex", + "hmac 0.12.1", + "itertools 0.13.0", + "libm", + "libsecp256k1", + "merlin", + "nom", + "num-bigint", + "num-rational", + "num-traits", + "pbkdf2", + "pin-project", + "poly1305", + "rand", + "rand_chacha", + "ruzstd 0.6.0", + "schnorrkel", + "serde", + "serde_json", + "sha2 0.10.8", + "sha3", + "siphasher", + "slab", + "smallvec", + "soketto 0.8.1", + "twox-hash", + "wasmi 0.32.3", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "smoldot-light" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5496f2d116b7019a526b1039ec2247dd172b8670633b1a64a614c9ea12c9d8c7" +dependencies = [ + "async-channel", + "async-lock", + "base64 0.21.7", + "blake2-rfc", + "derive_more 0.99.18", + "either", + "event-listener 4.0.3", + "fnv", + "futures-channel", + "futures-lite", + "futures-util", + "hashbrown 0.14.5", + "hex", + "itertools 0.12.1", + "log", + "lru 0.12.5", + "no-std-net", + "parking_lot", + "pin-project", + "rand", + "rand_chacha", + "serde", + "serde_json", + "siphasher", + "slab", + "smol", + "smoldot 0.16.0", + "zeroize", +] + +[[package]] +name = "smoldot-light" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a33b06891f687909632ce6a4e3fd7677b24df930365af3d0bcb078310129f3f" +dependencies = [ + "async-channel", + "async-lock", + "base64 0.22.1", + "blake2-rfc", + "bs58", + "derive_more 0.99.18", + "either", + "event-listener 5.3.1", + "fnv", + "futures-channel", + "futures-lite", + "futures-util", + "hashbrown 0.14.5", + "hex", + "itertools 0.13.0", + "log", + "lru 0.12.5", + "parking_lot", + "pin-project", + "rand", + "rand_chacha", + "serde", + "serde_json", + "siphasher", + "slab", + "smol", + "smoldot 0.18.0", + "zeroize", +] + +[[package]] +name = "snowbridge-amcl" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460a9ed63cdf03c1b9847e8a12a5f5ba19c4efd5869e4a737e05be25d7c427e5" +dependencies = [ + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "snowbridge-beacon-primitives" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10bd720997e558beb556d354238fa90781deb38241cf31c1b6368738ef21c279" +dependencies = [ + "byte-slice-cast", + "frame-support", + "hex", + "parity-scale-codec", + "rlp", + "scale-info", + "serde", + "snowbridge-ethereum", + "snowbridge-milagro-bls", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-std", + "ssz_rs", + "ssz_rs_derive", +] + +[[package]] +name = "snowbridge-core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6be61e4db95d1e253a1d5e722953b2d2f6605e5f9761f0a919e5d3fbdbff9da9" +dependencies = [ + "ethabi-decode", + "frame-support", + "frame-system", + "hex-literal", + "parity-scale-codec", + "polkadot-parachain-primitives", + "scale-info", + "serde", + "snowbridge-beacon-primitives", + "sp-arithmetic 26.0.0", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-std", + "staging-xcm 14.2.0", + "staging-xcm-builder", +] + +[[package]] +name = "snowbridge-ethereum" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc3d6d549c57df27cf89ec852f932fa4008eea877a6911a87e03e8002104eabd" +dependencies = [ + "ethabi-decode", + "ethbloom", + "ethereum-types", + "hex-literal", + "parity-bytes", + "parity-scale-codec", + "rlp", + "scale-info", + "serde", + "serde-big-array", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-std", +] + +[[package]] +name = "snowbridge-milagro-bls" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "026aa8638f690a53e3f7676024b9e913b1cab0111d1b7b92669d40a188f9d7e6" +dependencies = [ + "hex", + "lazy_static", + "parity-scale-codec", + "rand", + "scale-info", + "snowbridge-amcl", + "zeroize", +] + +[[package]] +name = "snowbridge-outbound-queue-merkle-tree" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74c6a9b65fa61711b704f0c6afb3663c6288288e8822ddae5cc1146fe3ad9ce8" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-core 34.0.0", + "sp-runtime 39.0.2", +] + +[[package]] +name = "snowbridge-outbound-queue-runtime-api" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d27b8d9cb8022637a5ce4f52692520fa75874f393e04ef5cd75bd8795087f6" +dependencies = [ + "frame-support", + "parity-scale-codec", + "snowbridge-core", + "snowbridge-outbound-queue-merkle-tree", + "sp-api", + "sp-std", +] + +[[package]] +name = "snowbridge-pallet-ethereum-client" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d53d32d8470c643f9f8c1f508e1e34263f76297e4c9150e10e8f2e0b63992e1" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "serde", + "snowbridge-beacon-primitives", + "snowbridge-core", + "snowbridge-ethereum", + "snowbridge-pallet-ethereum-client-fixtures", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-std", + "static_assertions", +] + +[[package]] +name = "snowbridge-pallet-ethereum-client-fixtures" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3984b98465af1d862d4e87ba783e1731f2a3f851b148d6cb98d526cebd351185" +dependencies = [ + "hex-literal", + "snowbridge-beacon-primitives", + "snowbridge-core", + "sp-core 34.0.0", + "sp-std", +] + +[[package]] +name = "snowbridge-pallet-inbound-queue" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2e6a9d00e60e3744e6b6f0c21fea6694b9c6401ac40e41340a96e561dcf1935" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "serde", + "snowbridge-beacon-primitives", + "snowbridge-core", + "snowbridge-pallet-inbound-queue-fixtures", + "snowbridge-router-primitives", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-std", + "staging-xcm 14.2.0", + "staging-xcm-executor", +] + +[[package]] +name = "snowbridge-pallet-inbound-queue-fixtures" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b099db83f4c10c0bf84e87deb1596019f91411ea1c8c9733ea9a7f2e7e967073" +dependencies = [ + "hex-literal", + "snowbridge-beacon-primitives", + "snowbridge-core", + "sp-core 34.0.0", + "sp-std", +] + +[[package]] +name = "snowbridge-pallet-outbound-queue" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d49478041b6512c710d0d4655675d146fe00a8e0c1624e5d8a1d6c161d490f" +dependencies = [ + "bridge-hub-common", + "ethabi-decode", + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "serde", + "snowbridge-core", + "snowbridge-outbound-queue-merkle-tree", + "sp-arithmetic 26.0.0", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-std", +] + +[[package]] +name = "snowbridge-pallet-system" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "674db59b3c8013382e5c07243ad9439b64d81d2e8b3c4f08d752b55aa5de697e" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "snowbridge-core", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-std", + "staging-xcm 14.2.0", + "staging-xcm-executor", +] + +[[package]] +name = "snowbridge-router-primitives" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "025f1e6805753821b1db539369f1fb183fd59fd5df7023f7633a4c0cfd3e62f9" +dependencies = [ + "frame-support", + "hex-literal", + "log", + "parity-scale-codec", + "scale-info", + "snowbridge-core", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-std", + "staging-xcm 14.2.0", + "staging-xcm-executor", +] + +[[package]] +name = "snowbridge-runtime-common" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6093f0e73d6cfdd2eea8712155d1d75b5063fc9b1d854d2665b097b4bb29570d" +dependencies = [ + "frame-support", + "log", + "parity-scale-codec", + "snowbridge-core", + "sp-arithmetic 26.0.0", + "sp-std", + "staging-xcm 14.2.0", + "staging-xcm-builder", + "staging-xcm-executor", +] + +[[package]] +name = "snowbridge-runtime-test-common" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "893480d6cde2489051c65efb5d27fa87efe047b3b61216d8e27bb2f0509b7faf" +dependencies = [ + "cumulus-pallet-parachain-system", + "frame-support", + "frame-system", + "pallet-balances", + "pallet-collator-selection", + "pallet-message-queue", + "pallet-session", + "pallet-timestamp", + "pallet-utility", + "pallet-xcm", + "parachains-runtimes-test-utils", + "parity-scale-codec", + "snowbridge-core", + "snowbridge-pallet-ethereum-client", + "snowbridge-pallet-ethereum-client-fixtures", + "snowbridge-pallet-outbound-queue", + "snowbridge-pallet-system", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-keyring", + "sp-runtime 39.0.2", + "staging-parachain-info", + "staging-xcm 14.2.0", + "staging-xcm-executor", +] + +[[package]] +name = "snowbridge-system-runtime-api" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b8b83b3db781c49844312a23965073e4d93341739a35eafe526c53b578d3b7" +dependencies = [ + "parity-scale-codec", + "snowbridge-core", + "sp-api", + "sp-std", + "staging-xcm 14.2.0", +] + +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "soketto" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d1c5305e39e09653383c2c7244f2f78b3bcae37cf50c64cb4789c9f5096ec2" +dependencies = [ + "base64 0.13.1", + "bytes", + "futures", + "httparse", + "log", + "rand", + "sha-1", +] + +[[package]] +name = "soketto" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e859df029d160cb88608f5d7df7fb4753fd20fdfb4de5644f3d8b8440841721" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures", + "httparse", + "log", + "rand", + "sha1", +] + +[[package]] +name = "sp-api" +version = "34.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbce492e0482134128b7729ea36f5ef1a9f9b4de2d48ff8dde7b5e464e28ce75" +dependencies = [ + "docify", + "hash-db", + "log", + "parity-scale-codec", + "scale-info", + "sp-api-proc-macro", + "sp-core 34.0.0", + "sp-externalities 0.29.0", + "sp-metadata-ir", + "sp-runtime 39.0.2", + "sp-runtime-interface 28.0.0", + "sp-state-machine 0.43.0", + "sp-trie 37.0.0", + "sp-version", + "thiserror 1.0.69", +] + +[[package]] +name = "sp-api-proc-macro" +version = "20.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9aadf9e97e694f0e343978aa632938c5de309cbcc8afed4136cb71596737278" +dependencies = [ + "Inflector", + "blake2", + "expander", + "proc-macro-crate 3.2.0", + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "sp-application-crypto" +version = "34.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505fad69251900048ddddc6387265e1545d1a366e3b4dcd57b76a03f0a65ae7" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "sp-core 32.0.0", + "sp-io 34.0.0", + "sp-std", +] + +[[package]] +name = "sp-application-crypto" +version = "38.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8133012faa5f75b2f0b1619d9f720c1424ac477152c143e5f7dbde2fe1a958" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "sp-core 34.0.0", + "sp-io 38.0.0", +] + +[[package]] +name = "sp-arithmetic" +version = "25.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "910c07fa263b20bf7271fdd4adcb5d3217dfdac14270592e0780223542e7e114" +dependencies = [ + "integer-sqrt", + "num-traits", + "parity-scale-codec", + "scale-info", + "serde", + "sp-std", + "static_assertions", +] + +[[package]] +name = "sp-arithmetic" +version = "26.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46d0d0a4c591c421d3231ddd5e27d828618c24456d51445d21a1f79fcee97c23" +dependencies = [ + "docify", + "integer-sqrt", + "num-traits", + "parity-scale-codec", + "scale-info", + "serde", + "sp-std", + "static_assertions", +] + +[[package]] +name = "sp-authority-discovery" +version = "34.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "519c33af0e25ba2dd2eb3790dc404d634b6e4ce0801bcc8fa3574e07c365e734" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-application-crypto 38.0.0", + "sp-runtime 39.0.2", +] + +[[package]] +name = "sp-block-builder" +version = "34.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74738809461e3d4bd707b5b94e0e0c064a623a74a6a8fe5c98514417a02858dd" +dependencies = [ + "sp-api", + "sp-inherents", + "sp-runtime 39.0.2", +] + +[[package]] +name = "sp-consensus-aura" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a8faaa05bbcb9c41f0cc535c4c1315abf6df472b53eae018678d1b4d811ac47" +dependencies = [ + "async-trait", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-application-crypto 38.0.0", + "sp-consensus-slots", + "sp-inherents", + "sp-runtime 39.0.2", + "sp-timestamp", +] + +[[package]] +name = "sp-consensus-babe" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36ee95e17ee8dcd14db7d584b899a426565ca9abe5a266ab82277977fc547f86" +dependencies = [ + "async-trait", + "parity-scale-codec", + "scale-info", + "serde", + "sp-api", + "sp-application-crypto 38.0.0", + "sp-consensus-slots", + "sp-core 34.0.0", + "sp-inherents", + "sp-runtime 39.0.2", + "sp-timestamp", +] + +[[package]] +name = "sp-consensus-beefy" +version = "22.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d97e8cd75d85d15cda6f1923cf3834e848f80d5a6de1cf4edbbc5f0ad607eb" +dependencies = [ + "lazy_static", + "parity-scale-codec", + "scale-info", + "serde", + "sp-api", + "sp-application-crypto 38.0.0", + "sp-core 34.0.0", + "sp-crypto-hashing", + "sp-io 38.0.0", + "sp-keystore 0.40.0", + "sp-mmr-primitives", + "sp-runtime 39.0.2", + "sp-weights 31.0.0", + "strum 0.26.3", +] + +[[package]] +name = "sp-consensus-grandpa" +version = "21.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "587b791efe6c5f18e09dbbaf1ece0ee7b5fe51602c233e7151a3676b0de0260b" +dependencies = [ + "finality-grandpa", + "log", + "parity-scale-codec", + "scale-info", + "serde", + "sp-api", + "sp-application-crypto 38.0.0", + "sp-core 34.0.0", + "sp-keystore 0.40.0", + "sp-runtime 39.0.2", +] + +[[package]] +name = "sp-consensus-pow" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa6b7d199a1c16cea1b74ee7cee174bf08f2120ab66a87bee7b12353100b47c" +dependencies = [ + "parity-scale-codec", + "sp-api", + "sp-core 34.0.0", + "sp-runtime 39.0.2", +] + +[[package]] +name = "sp-consensus-slots" +version = "0.40.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbafb7ed44f51c22fa277fb39b33dc601fa426133a8e2b53f3f46b10f07fba43" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "sp-timestamp", +] + +[[package]] +name = "sp-core" +version = "31.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d7a0fd8f16dcc3761198fc83be12872f823b37b749bc72a3a6a1f702509366" +dependencies = [ + "array-bytes", + "bitflags 1.3.2", + "blake2", + "bounded-collections", + "bs58", + "dyn-clonable", + "ed25519-zebra 3.1.0", + "futures", + "hash-db", + "hash256-std-hasher", + "impl-serde 0.4.0", + "itertools 0.10.5", + "k256", + "libsecp256k1", + "log", + "merlin", + "parity-bip39", + "parity-scale-codec", + "parking_lot", + "paste", + "primitive-types 0.12.2", + "rand", + "scale-info", + "schnorrkel", + "secp256k1 0.28.2", + "secrecy 0.8.0", + "serde", + "sp-crypto-hashing", + "sp-debug-derive", + "sp-externalities 0.27.0", + "sp-runtime-interface 26.0.0", + "sp-std", + "sp-storage 20.0.0", + "ss58-registry", + "substrate-bip39 0.5.0", + "thiserror 1.0.69", + "tracing", + "w3f-bls", + "zeroize", +] + +[[package]] +name = "sp-core" +version = "32.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2dac7e47c7ddbb61efe196d5cce99f6ea88926c961fa39909bfeae46fc5a7b" +dependencies = [ + "array-bytes", + "bitflags 1.3.2", + "blake2", + "bounded-collections", + "bs58", + "dyn-clonable", + "ed25519-zebra 3.1.0", + "futures", + "hash-db", + "hash256-std-hasher", + "impl-serde 0.4.0", + "itertools 0.10.5", + "k256", + "libsecp256k1", + "log", + "merlin", + "parity-bip39", + "parity-scale-codec", + "parking_lot", + "paste", + "primitive-types 0.12.2", + "rand", + "scale-info", + "schnorrkel", + "secp256k1 0.28.2", + "secrecy 0.8.0", + "serde", + "sp-crypto-hashing", + "sp-debug-derive", + "sp-externalities 0.28.0", + "sp-runtime-interface 27.0.0", + "sp-std", + "sp-storage 21.0.0", + "ss58-registry", + "substrate-bip39 0.6.0", + "thiserror 1.0.69", + "tracing", + "w3f-bls", + "zeroize", +] + +[[package]] +name = "sp-core" +version = "34.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c961a5e33fb2962fa775c044ceba43df9c6f917e2c35d63bfe23738468fa76a7" +dependencies = [ + "array-bytes", + "bitflags 1.3.2", + "blake2", + "bounded-collections", + "bs58", + "dyn-clonable", + "ed25519-zebra 4.0.3", + "futures", + "hash-db", + "hash256-std-hasher", + "impl-serde 0.4.0", + "itertools 0.11.0", + "k256", + "libsecp256k1", + "log", + "merlin", + "parity-bip39", + "parity-scale-codec", + "parking_lot", + "paste", + "primitive-types 0.12.2", + "rand", + "scale-info", + "schnorrkel", + "secp256k1 0.28.2", + "secrecy 0.8.0", + "serde", + "sp-crypto-hashing", + "sp-debug-derive", + "sp-externalities 0.29.0", + "sp-runtime-interface 28.0.0", + "sp-std", + "sp-storage 21.0.0", + "ss58-registry", + "substrate-bip39 0.6.0", + "thiserror 1.0.69", + "tracing", + "w3f-bls", + "zeroize", +] + +[[package]] +name = "sp-core-hashing" +version = "16.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f812cb2dff962eb378c507612a50f1c59f52d92eb97b710f35be3c2346a3cd7" +dependencies = [ + "sp-crypto-hashing", +] + +[[package]] +name = "sp-crypto-ec-utils" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acb24f8a607a48a87f0ee4c090fc5d577eee49ff39ced6a3c491e06eca03c37" +dependencies = [ + "ark-bls12-377", + "ark-bls12-377-ext", + "ark-bls12-381", + "ark-bls12-381-ext", + "ark-bw6-761", + "ark-bw6-761-ext", + "ark-ec", + "ark-ed-on-bls12-377", + "ark-ed-on-bls12-377-ext", + "ark-ed-on-bls12-381-bandersnatch", + "ark-ed-on-bls12-381-bandersnatch-ext", + "ark-scale", + "sp-runtime-interface 28.0.0", +] + +[[package]] +name = "sp-crypto-hashing" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9927a7f81334ed5b8a98a4a978c81324d12bd9713ec76b5c68fd410174c5eb" +dependencies = [ + "blake2b_simd", + "byteorder", + "digest 0.10.7", + "sha2 0.10.8", + "sha3", + "twox-hash", +] + +[[package]] +name = "sp-crypto-hashing-proc-macro" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b85d0f1f1e44bd8617eb2a48203ee854981229e3e79e6f468c7175d5fd37489b" +dependencies = [ + "quote", + "sp-crypto-hashing", + "syn 2.0.90", +] + +[[package]] +name = "sp-debug-derive" +version = "14.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d09fa0a5f7299fb81ee25ae3853d26200f7a348148aed6de76be905c007dbe" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "sp-externalities" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d6a4572eadd4a63cff92509a210bf425501a0c5e76574b30a366ac77653787" +dependencies = [ + "environmental", + "parity-scale-codec", + "sp-std", + "sp-storage 20.0.0", +] + +[[package]] +name = "sp-externalities" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33abaec4be69b1613796bbf430decbbcaaf978756379e2016e683a4d6379cd02" +dependencies = [ + "environmental", + "parity-scale-codec", + "sp-storage 21.0.0", +] + +[[package]] +name = "sp-externalities" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a904407d61cb94228c71b55a9d3708e9d6558991f9e83bd42bd91df37a159d30" +dependencies = [ + "environmental", + "parity-scale-codec", + "sp-storage 21.0.0", +] + +[[package]] +name = "sp-genesis-builder" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a646ed222fd86d5680faa4a8967980eb32f644cae6c8523e1c689a6deda3e8" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde_json", + "sp-api", + "sp-runtime 39.0.2", +] + +[[package]] +name = "sp-inherents" +version = "34.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afffbddc380d99a90c459ba1554bbbc01d62e892de9f1485af6940b89c4c0d57" +dependencies = [ + "async-trait", + "impl-trait-for-tuples", + "parity-scale-codec", + "scale-info", + "sp-runtime 39.0.2", + "thiserror 1.0.69", +] + +[[package]] +name = "sp-io" +version = "34.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c44ed47247b6eee76ff703f9fa9f04f99c4104ac1faf629e6d1128e09066b57b" +dependencies = [ + "bytes", + "ed25519-dalek", + "libsecp256k1", + "log", + "parity-scale-codec", + "polkavm-derive 0.9.1", + "rustversion", + "secp256k1 0.28.2", + "sp-core 32.0.0", + "sp-crypto-hashing", + "sp-externalities 0.28.0", + "sp-keystore 0.38.0", + "sp-runtime-interface 27.0.0", + "sp-state-machine 0.39.0", + "sp-std", + "sp-tracing 17.0.1", + "sp-trie 33.0.0", + "tracing", + "tracing-core", +] + +[[package]] +name = "sp-io" +version = "38.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ef7eb561bb4839cc8424ce58c5ea236cbcca83f26fcc0426d8decfe8aa97d4" +dependencies = [ + "bytes", + "docify", + "ed25519-dalek", + "libsecp256k1", + "log", + "parity-scale-codec", + "polkavm-derive 0.9.1", + "rustversion", + "secp256k1 0.28.2", + "sp-core 34.0.0", + "sp-crypto-hashing", + "sp-externalities 0.29.0", + "sp-keystore 0.40.0", + "sp-runtime-interface 28.0.0", + "sp-state-machine 0.43.0", + "sp-tracing 17.0.1", + "sp-trie 37.0.0", + "tracing", + "tracing-core", +] + +[[package]] +name = "sp-keyring" +version = "39.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c0e20624277f578b27f44ecfbe2ebc2e908488511ee2c900c5281599f700ab3" +dependencies = [ + "sp-core 34.0.0", + "sp-runtime 39.0.2", + "strum 0.26.3", +] + +[[package]] +name = "sp-keystore" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e6c7a7abd860a5211a356cf9d5fcabf0eb37d997985e5d722b6b33dcc815528" +dependencies = [ + "parity-scale-codec", + "parking_lot", + "sp-core 32.0.0", + "sp-externalities 0.28.0", +] + +[[package]] +name = "sp-keystore" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0248b4d784cb4a01472276928977121fa39d977a5bb24793b6b15e64b046df42" +dependencies = [ + "parity-scale-codec", + "parking_lot", + "sp-core 34.0.0", + "sp-externalities 0.29.0", +] + +[[package]] +name = "sp-maybe-compressed-blob" +version = "11.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c768c11afbe698a090386876911da4236af199cd38a5866748df4d8628aeff" +dependencies = [ + "thiserror 1.0.69", + "zstd 0.12.4", +] + +[[package]] +name = "sp-metadata-ir" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a616fa51350b35326682a472ee8e6ba742fdacb18babac38ecd46b3e05ead869" +dependencies = [ + "frame-metadata 16.0.0", + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "sp-mixnet" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0b017dd54823b6e62f9f7171a1df350972e5c6d0bf17e0c2f78680b5c31942" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-application-crypto 38.0.0", +] + +[[package]] +name = "sp-mmr-primitives" +version = "34.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a12dd76e368f1e48144a84b4735218b712f84b3f976970e2f25a29b30440e10" +dependencies = [ + "log", + "parity-scale-codec", + "polkadot-ckb-merkle-mountain-range", + "scale-info", + "serde", + "sp-api", + "sp-core 34.0.0", + "sp-debug-derive", + "sp-runtime 39.0.2", + "thiserror 1.0.69", +] + +[[package]] +name = "sp-npos-elections" +version = "34.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af922f112c7c1ed199eabe14f12a82ceb75e1adf0804870eccfbcf3399492847" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "sp-arithmetic 26.0.0", + "sp-core 34.0.0", + "sp-runtime 39.0.2", +] + +[[package]] +name = "sp-offchain" +version = "34.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d9de237d72ecffd07f90826eef18360208b16d8de939d54e61591fac0fcbf99" +dependencies = [ + "sp-api", + "sp-core 34.0.0", + "sp-runtime 39.0.2", +] + +[[package]] +name = "sp-panic-handler" +version = "13.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8f5a17a0a11de029a8b811cb6e8b32ce7e02183cc04a3e965c383246798c416" +dependencies = [ + "backtrace", + "lazy_static", + "regex", +] + +[[package]] +name = "sp-runtime" +version = "35.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ce931b7fbfdeeca1340801dbd4a1cae54ad4c97a1e3dcfcc79709bc800dd46" +dependencies = [ + "docify", + "either", + "hash256-std-hasher", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "paste", + "rand", + "scale-info", + "serde", + "simple-mermaid", + "sp-application-crypto 34.0.0", + "sp-arithmetic 26.0.0", + "sp-core 32.0.0", + "sp-io 34.0.0", + "sp-std", + "sp-weights 31.0.0", +] [[package]] -name = "siphasher" -version = "1.0.1" +name = "sp-runtime" +version = "39.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +checksum = "658f23be7c79a85581029676a73265c107c5469157e3444c8c640fdbaa8bfed0" +dependencies = [ + "docify", + "either", + "hash256-std-hasher", + "impl-trait-for-tuples", + "log", + "num-traits", + "parity-scale-codec", + "paste", + "rand", + "scale-info", + "serde", + "simple-mermaid", + "sp-application-crypto 38.0.0", + "sp-arithmetic 26.0.0", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-std", + "sp-weights 31.0.0", + "tracing", +] [[package]] -name = "slab" -version = "0.4.9" +name = "sp-runtime-interface" +version = "26.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +checksum = "e48a675ea4858333d4d755899ed5ed780174aa34fec15953428d516af5452295" dependencies = [ - "autocfg", + "bytes", + "impl-trait-for-tuples", + "parity-scale-codec", + "polkavm-derive 0.8.0", + "primitive-types 0.12.2", + "sp-externalities 0.27.0", + "sp-runtime-interface-proc-macro", + "sp-std", + "sp-storage 20.0.0", + "sp-tracing 16.0.0", + "sp-wasm-interface 20.0.0", + "static_assertions", ] [[package]] -name = "smallvec" -version = "1.13.2" +name = "sp-runtime-interface" +version = "27.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "647db5e1dc481686628b41554e832df6ab400c4b43a6a54e54d3b0a71ca404aa" +dependencies = [ + "bytes", + "impl-trait-for-tuples", + "parity-scale-codec", + "polkavm-derive 0.9.1", + "primitive-types 0.12.2", + "sp-externalities 0.28.0", + "sp-runtime-interface-proc-macro", + "sp-std", + "sp-storage 21.0.0", + "sp-tracing 17.0.1", + "sp-wasm-interface 21.0.1", + "static_assertions", +] [[package]] -name = "smawk" -version = "0.3.2" +name = "sp-runtime-interface" +version = "28.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" +checksum = "985eb981f40c689c6a0012c937b68ed58dabb4341d06f2dfe4dfd5ed72fa4017" +dependencies = [ + "bytes", + "impl-trait-for-tuples", + "parity-scale-codec", + "polkavm-derive 0.9.1", + "primitive-types 0.12.2", + "sp-externalities 0.29.0", + "sp-runtime-interface-proc-macro", + "sp-std", + "sp-storage 21.0.0", + "sp-tracing 17.0.1", + "sp-wasm-interface 21.0.1", + "static_assertions", +] [[package]] -name = "smol" -version = "2.0.2" +name = "sp-runtime-interface-proc-macro" +version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f" +checksum = "0195f32c628fee3ce1dfbbf2e7e52a30ea85f3589da9fe62a8b816d70fc06294" dependencies = [ - "async-channel", - "async-executor", - "async-fs", - "async-io", - "async-lock", - "async-net", - "async-process", - "blocking", - "futures-lite", + "Inflector", + "expander", + "proc-macro-crate 3.2.0", + "proc-macro2", + "quote", + "syn 2.0.90", ] [[package]] -name = "smoldot" -version = "0.16.0" +name = "sp-session" +version = "36.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d1eaa97d77be4d026a1e7ffad1bb3b78448763b357ea6f8188d3e6f736a9b9" +checksum = "00a3a307fedc423fb8cd2a7726a3bbb99014f1b4b52f26153993e2aae3338fe6" dependencies = [ - "arrayvec 0.7.6", - "async-lock", - "atomic-take", - "base64 0.21.7", - "bip39", - "blake2-rfc", - "bs58", - "chacha20", - "crossbeam-queue", - "derive_more 0.99.18", - "ed25519-zebra 4.0.3", - "either", - "event-listener 4.0.3", - "fnv", - "futures-lite", - "futures-util", - "hashbrown 0.14.5", - "hex", - "hmac 0.12.1", - "itertools 0.12.1", - "libm", - "libsecp256k1", - "merlin", - "no-std-net", - "nom", - "num-bigint", - "num-rational", - "num-traits", - "pbkdf2", - "pin-project", - "poly1305", - "rand", - "rand_chacha", - "ruzstd", - "schnorrkel", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-core 34.0.0", + "sp-keystore 0.40.0", + "sp-runtime 39.0.2", + "sp-staking 36.0.0", +] + +[[package]] +name = "sp-staking" +version = "34.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "143a764cacbab58347d8b2fd4c8909031fb0888d7b02a0ec9fa44f81f780d732" +dependencies = [ + "impl-trait-for-tuples", + "parity-scale-codec", + "scale-info", "serde", - "serde_json", - "sha2 0.10.8", - "sha3", - "siphasher", - "slab", + "sp-core 34.0.0", + "sp-runtime 39.0.2", +] + +[[package]] +name = "sp-staking" +version = "36.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a73eedb4b85f4cd420d31764827546aa22f82ce1646d0fd258993d051de7a90" +dependencies = [ + "impl-trait-for-tuples", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core 34.0.0", + "sp-runtime 39.0.2", +] + +[[package]] +name = "sp-state-machine" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21d9078306c3066f1824e41153e1ceec34231d39d9a7e7956b101eadf7b9fd3a" +dependencies = [ + "hash-db", + "log", + "parity-scale-codec", + "parking_lot", + "rand", "smallvec", - "soketto 0.7.1", - "twox-hash", - "wasmi", - "x25519-dalek", - "zeroize", + "sp-core 32.0.0", + "sp-externalities 0.28.0", + "sp-panic-handler", + "sp-trie 33.0.0", + "thiserror 1.0.69", + "tracing", + "trie-db 0.28.0", ] [[package]] -name = "smoldot-light" -version = "0.14.0" +name = "sp-state-machine" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5496f2d116b7019a526b1039ec2247dd172b8670633b1a64a614c9ea12c9d8c7" +checksum = "930104d6ae882626e8880d9b1578da9300655d337a3ffb45e130c608b6c89660" dependencies = [ - "async-channel", - "async-lock", - "base64 0.21.7", - "blake2-rfc", - "derive_more 0.99.18", - "either", - "event-listener 4.0.3", - "fnv", - "futures-channel", - "futures-lite", - "futures-util", - "hashbrown 0.14.5", - "hex", - "itertools 0.12.1", + "hash-db", "log", - "lru", - "no-std-net", + "parity-scale-codec", "parking_lot", - "pin-project", "rand", - "rand_chacha", + "smallvec", + "sp-core 34.0.0", + "sp-externalities 0.29.0", + "sp-panic-handler", + "sp-trie 37.0.0", + "thiserror 1.0.69", + "tracing", + "trie-db 0.29.1", +] + +[[package]] +name = "sp-statement-store" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c219bc34ef4d1f9835f3ed881f965643c32034fcc030eb33b759dadbc802c1c2" +dependencies = [ + "aes-gcm", + "curve25519-dalek 4.1.3", + "ed25519-dalek", + "hkdf", + "parity-scale-codec", + "rand", + "scale-info", + "sha2 0.10.8", + "sp-api", + "sp-application-crypto 38.0.0", + "sp-core 34.0.0", + "sp-crypto-hashing", + "sp-externalities 0.29.0", + "sp-runtime 39.0.2", + "sp-runtime-interface 28.0.0", + "thiserror 1.0.69", + "x25519-dalek", +] + +[[package]] +name = "sp-std" +version = "14.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f8ee986414b0a9ad741776762f4083cd3a5128449b982a3919c4df36874834" + +[[package]] +name = "sp-storage" +version = "20.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dba5791cb3978e95daf99dad919ecb3ec35565604e88cd38d805d9d4981e8bd" +dependencies = [ + "impl-serde 0.4.0", + "parity-scale-codec", + "ref-cast", "serde", - "serde_json", - "siphasher", - "slab", - "smol", - "smoldot", - "zeroize", + "sp-debug-derive", + "sp-std", ] [[package]] -name = "socket2" -version = "0.5.7" +name = "sp-storage" +version = "21.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99c82989b3a4979a7e1ad848aad9f5d0b4388f1f454cc131766526601ab9e8f8" +dependencies = [ + "impl-serde 0.4.0", + "parity-scale-codec", + "ref-cast", + "serde", + "sp-debug-derive", +] + +[[package]] +name = "sp-timestamp" +version = "34.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a1cb4df653d62ccc0dbce1db45d1c9443ec60247ee9576962d24da4c9c6f07" +dependencies = [ + "async-trait", + "parity-scale-codec", + "sp-inherents", + "sp-runtime 39.0.2", + "thiserror 1.0.69", +] + +[[package]] +name = "sp-tracing" +version = "16.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "0351810b9d074df71c4514c5228ed05c250607cba131c1c9d1526760ab69c05c" dependencies = [ - "libc", - "windows-sys 0.52.0", + "parity-scale-codec", + "sp-std", + "tracing", + "tracing-core", + "tracing-subscriber 0.2.25", ] [[package]] -name = "soketto" -version = "0.7.1" +name = "sp-tracing" +version = "17.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d1c5305e39e09653383c2c7244f2f78b3bcae37cf50c64cb4789c9f5096ec2" +checksum = "cf641a1d17268c8fcfdb8e0fa51a79c2d4222f4cfda5f3944dbdbc384dced8d5" dependencies = [ - "base64 0.13.1", - "bytes", - "futures", - "httparse", - "log", - "rand", - "sha-1", + "parity-scale-codec", + "tracing", + "tracing-core", + "tracing-subscriber 0.3.19", ] [[package]] -name = "soketto" -version = "0.8.0" +name = "sp-transaction-pool" +version = "34.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37468c595637c10857701c990f93a40ce0e357cedb0953d1c26c8d8027f9bb53" +checksum = "fc4bf251059485a7dd38fe4afeda8792983511cc47f342ff4695e2dcae6b5247" dependencies = [ - "base64 0.22.1", - "bytes", - "futures", - "httparse", - "log", - "rand", - "sha1", + "sp-api", + "sp-runtime 39.0.2", ] [[package]] -name = "sp-application-crypto" -version = "33.0.0" +name = "sp-transaction-storage-proof" +version = "34.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13ca6121c22c8bd3d1dce1f05c479101fd0d7b159bef2a3e8c834138d839c75c" +checksum = "c765c2e9817d95f13d42a9f2295c60723464669765c6e5acbacebd2f54932f67" dependencies = [ + "async-trait", "parity-scale-codec", "scale-info", - "serde", - "sp-core", - "sp-io", - "sp-std", + "sp-core 34.0.0", + "sp-inherents", + "sp-runtime 39.0.2", + "sp-trie 37.0.0", ] [[package]] -name = "sp-arithmetic" -version = "25.0.0" +name = "sp-trie" +version = "33.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "910c07fa263b20bf7271fdd4adcb5d3217dfdac14270592e0780223542e7e114" +checksum = "d1f5b3620a1c87c265a83d85d7519c6b60c47acf7f77593966afe313d086f00e" dependencies = [ - "integer-sqrt", - "num-traits", + "ahash 0.8.11", + "hash-db", + "lazy_static", + "memory-db", + "nohash-hasher", "parity-scale-codec", + "parking_lot", + "rand", "scale-info", - "serde", - "sp-std", - "static_assertions", + "schnellru", + "sp-core 32.0.0", + "sp-externalities 0.28.0", + "thiserror 1.0.69", + "tracing", + "trie-db 0.28.0", + "trie-root", ] [[package]] -name = "sp-core" -version = "31.0.0" +name = "sp-trie" +version = "37.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d7a0fd8f16dcc3761198fc83be12872f823b37b749bc72a3a6a1f702509366" +checksum = "6282aef9f4b6ecd95a67a45bcdb67a71f4a4155c09a53c10add4ffe823db18cd" dependencies = [ - "array-bytes", - "bitflags 1.3.2", - "blake2", - "bounded-collections", - "bs58", - "dyn-clonable", - "ed25519-zebra 3.1.0", - "futures", + "ahash 0.8.11", "hash-db", - "hash256-std-hasher", - "impl-serde", - "itertools 0.10.5", - "k256", - "libsecp256k1", - "log", - "merlin", - "parity-bip39", + "lazy_static", + "memory-db", + "nohash-hasher", "parity-scale-codec", "parking_lot", - "paste", - "primitive-types", "rand", "scale-info", - "schnorrkel", - "secp256k1", - "secrecy", - "serde", - "sp-crypto-hashing", - "sp-debug-derive", - "sp-externalities", - "sp-runtime-interface", - "sp-std", - "sp-storage", - "ss58-registry", - "substrate-bip39", - "thiserror", + "schnellru", + "sp-core 34.0.0", + "sp-externalities 0.29.0", + "thiserror 1.0.69", "tracing", - "w3f-bls", - "zeroize", + "trie-db 0.29.1", + "trie-root", ] [[package]] -name = "sp-crypto-hashing" -version = "0.1.0" +name = "sp-version" +version = "37.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc9927a7f81334ed5b8a98a4a978c81324d12bd9713ec76b5c68fd410174c5eb" +checksum = "d521a405707b5be561367cd3d442ff67588993de24062ce3adefcf8437ee9fe1" dependencies = [ - "blake2b_simd", - "byteorder", - "digest 0.10.7", - "sha2 0.10.8", - "sha3", - "twox-hash", + "impl-serde 0.4.0", + "parity-scale-codec", + "parity-wasm", + "scale-info", + "serde", + "sp-crypto-hashing-proc-macro", + "sp-runtime 39.0.2", + "sp-std", + "sp-version-proc-macro", + "thiserror 1.0.69", ] [[package]] -name = "sp-debug-derive" +name = "sp-version-proc-macro" version = "14.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d09fa0a5f7299fb81ee25ae3853d26200f7a348148aed6de76be905c007dbe" +checksum = "5aee8f6730641a65fcf0c8f9b1e448af4b3bb083d08058b47528188bccc7b7a7" dependencies = [ + "parity-scale-codec", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] -name = "sp-externalities" -version = "0.27.0" +name = "sp-wasm-interface" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d6a4572eadd4a63cff92509a210bf425501a0c5e76574b30a366ac77653787" +checksum = "9ef97172c42eb4c6c26506f325f48463e9bc29b2034a587f1b9e48c751229bee" dependencies = [ - "environmental", + "anyhow", + "impl-trait-for-tuples", + "log", "parity-scale-codec", "sp-std", - "sp-storage", + "wasmtime", ] [[package]] -name = "sp-io" -version = "33.0.0" +name = "sp-wasm-interface" +version = "21.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e09bba780b55bd9e67979cd8f654a31e4a6cf45426ff371394a65953d2177f2" +checksum = "b066baa6d57951600b14ffe1243f54c47f9c23dd89c262e17ca00ae8dca58be9" dependencies = [ - "bytes", - "ed25519-dalek", - "libsecp256k1", + "anyhow", + "impl-trait-for-tuples", "log", "parity-scale-codec", - "polkavm-derive 0.9.1", - "rustversion", - "secp256k1", - "sp-core", - "sp-crypto-hashing", - "sp-externalities", - "sp-keystore", - "sp-runtime-interface", - "sp-state-machine", - "sp-std", - "sp-tracing", - "sp-trie", - "tracing", - "tracing-core", + "wasmtime", ] [[package]] -name = "sp-keystore" -version = "0.37.0" +name = "sp-weights" +version = "30.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdbab8b61bd61d5f8625a0c75753b5d5a23be55d3445419acd42caf59cf6236b" +checksum = "9af6c661fe3066b29f9e1d258000f402ff5cc2529a9191972d214e5871d0ba87" dependencies = [ + "bounded-collections", "parity-scale-codec", - "parking_lot", - "sp-core", - "sp-externalities", -] - -[[package]] -name = "sp-panic-handler" -version = "13.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8f5a17a0a11de029a8b811cb6e8b32ce7e02183cc04a3e965c383246798c416" -dependencies = [ - "backtrace", - "lazy_static", - "regex", + "scale-info", + "serde", + "smallvec", + "sp-arithmetic 25.0.0", + "sp-debug-derive", + "sp-std", ] [[package]] -name = "sp-runtime" -version = "34.0.0" +name = "sp-weights" +version = "31.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3cb126971e7db2f0fcf8053dce740684c438c7180cfca1959598230f342c58" +checksum = "93cdaf72a1dad537bbb130ba4d47307ebe5170405280ed1aa31fa712718a400e" dependencies = [ - "docify", - "either", - "hash256-std-hasher", - "impl-trait-for-tuples", - "log", + "bounded-collections", "parity-scale-codec", - "paste", - "rand", "scale-info", "serde", - "simple-mermaid", - "sp-application-crypto", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-std", - "sp-weights", + "smallvec", + "sp-arithmetic 26.0.0", + "sp-debug-derive", ] [[package]] -name = "sp-runtime-interface" -version = "26.0.0" +name = "spin" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a675ea4858333d4d755899ed5ed780174aa34fec15953428d516af5452295" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ - "bytes", - "impl-trait-for-tuples", - "parity-scale-codec", - "polkavm-derive 0.8.0", - "primitive-types", - "sp-externalities", - "sp-runtime-interface-proc-macro", - "sp-std", - "sp-storage", - "sp-tracing", - "sp-wasm-interface", - "static_assertions", + "base64ct", + "der", ] [[package]] -name = "sp-runtime-interface-proc-macro" -version = "18.0.0" +name = "ss58-registry" +version = "1.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0195f32c628fee3ce1dfbbf2e7e52a30ea85f3589da9fe62a8b816d70fc06294" +checksum = "19409f13998e55816d1c728395af0b52ec066206341d939e22e7766df9b494b8" dependencies = [ "Inflector", - "expander", - "proc-macro-crate 3.2.0", + "num-format", "proc-macro2", "quote", - "syn 2.0.89", + "serde", + "serde_json", + "unicode-xid", ] [[package]] -name = "sp-state-machine" -version = "0.38.0" +name = "ssz_rs" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1eae0eac8034ba14437e772366336f579398a46d101de13dbb781ab1e35e67c5" +checksum = "057291e5631f280978fa9c8009390663ca4613359fc1318e36a8c24c392f6d1f" dependencies = [ - "hash-db", - "log", - "parity-scale-codec", - "parking_lot", - "rand", - "smallvec", - "sp-core", - "sp-externalities", - "sp-panic-handler", - "sp-std", - "sp-trie", - "thiserror", - "tracing", - "trie-db", + "bitvec", + "num-bigint", + "sha2 0.9.9", + "ssz_rs_derive", ] [[package]] -name = "sp-std" -version = "14.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f8ee986414b0a9ad741776762f4083cd3a5128449b982a3919c4df36874834" - -[[package]] -name = "sp-storage" -version = "20.0.0" +name = "ssz_rs_derive" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dba5791cb3978e95daf99dad919ecb3ec35565604e88cd38d805d9d4981e8bd" +checksum = "f07d54c4d01a1713eb363b55ba51595da15f6f1211435b71466460da022aa140" dependencies = [ - "impl-serde", - "parity-scale-codec", - "ref-cast", - "serde", - "sp-debug-derive", - "sp-std", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] -name = "sp-tracing" -version = "16.0.0" +name = "stable_deref_trait" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0351810b9d074df71c4514c5228ed05c250607cba131c1c9d1526760ab69c05c" -dependencies = [ - "parity-scale-codec", - "sp-std", - "tracing", - "tracing-core", - "tracing-subscriber", -] +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] -name = "sp-trie" -version = "32.0.0" +name = "staging-parachain-info" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1aa91ad26c62b93d73e65f9ce7ebd04459c4bad086599348846a81988d6faa4" +checksum = "d28266dfddbfff721d70ad2f873380845b569adfab32f257cf97d9cedd894b68" dependencies = [ - "ahash 0.8.11", - "hash-db", - "lazy_static", - "memory-db", - "nohash-hasher", + "cumulus-primitives-core", + "frame-support", + "frame-system", "parity-scale-codec", - "parking_lot", - "rand", "scale-info", - "schnellru", - "sp-core", - "sp-externalities", - "sp-std", - "thiserror", - "tracing", - "trie-db", - "trie-root", + "sp-runtime 39.0.2", ] [[package]] -name = "sp-wasm-interface" -version = "20.0.0" +name = "staging-xcm" +version = "11.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ef97172c42eb4c6c26506f325f48463e9bc29b2034a587f1b9e48c751229bee" +checksum = "aded0292274ad473250c22ed3deaf2d9ed47d15786d700e9e83ab7c1cad2ad44" dependencies = [ - "anyhow", + "array-bytes", + "bounded-collections", + "derivative", + "environmental", "impl-trait-for-tuples", "log", "parity-scale-codec", - "sp-std", - "wasmtime", + "scale-info", + "serde", + "sp-weights 31.0.0", + "xcm-procedural 8.0.0", ] [[package]] -name = "sp-weights" -version = "30.0.0" +name = "staging-xcm" +version = "14.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9af6c661fe3066b29f9e1d258000f402ff5cc2529a9191972d214e5871d0ba87" +checksum = "96bee7cd999e9cdf10f8db72342070d456e21e82a0f5962ff3b87edbd5f2b20e" dependencies = [ + "array-bytes", "bounded-collections", + "derivative", + "environmental", + "impl-trait-for-tuples", + "log", "parity-scale-codec", "scale-info", "serde", - "smallvec", - "sp-arithmetic", - "sp-debug-derive", - "sp-std", + "sp-runtime 39.0.2", + "sp-weights 31.0.0", + "xcm-procedural 10.1.0", ] [[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - -[[package]] -name = "spki" -version = "0.7.3" +name = "staging-xcm-builder" +version = "17.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +checksum = "a3746adbbae27b1e6763f0cca622e15482ebcb94835a9e078c212dd7be896e35" dependencies = [ - "base64ct", - "der", + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "log", + "pallet-asset-conversion", + "pallet-transaction-payment", + "parity-scale-codec", + "polkadot-parachain-primitives", + "scale-info", + "sp-arithmetic 26.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-weights 31.0.0", + "staging-xcm 14.2.0", + "staging-xcm-executor", ] [[package]] -name = "ss58-registry" -version = "1.50.0" +name = "staging-xcm-executor" +version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43fce22ed1df64d04b262351c8f9d5c6da4f76f79f25ad15529792f893fad25d" +checksum = "79dd0c5332a5318e58f0300b20768b71cf9427c906f94a743c9dc7c3ee9e7fa9" dependencies = [ - "Inflector", - "num-format", - "proc-macro2", - "quote", - "serde", - "serde_json", - "unicode-xid", + "environmental", + "frame-benchmarking", + "frame-support", + "impl-trait-for-tuples", + "parity-scale-codec", + "scale-info", + "sp-arithmetic 26.0.0", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-weights 31.0.0", + "staging-xcm 14.2.0", + "tracing", ] -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "string-interner" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c6a0d765f5807e98a091107bae0a56ea3799f66a5de47b2c84c94a39c09974e" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "serde", +] + [[package]] name = "strsim" version = "0.10.0" @@ -6929,7 +13487,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -6945,6 +13503,40 @@ dependencies = [ "zeroize", ] +[[package]] +name = "substrate-bip39" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca58ffd742f693dc13d69bdbb2e642ae239e0053f6aab3b104252892f856700a" +dependencies = [ + "hmac 0.12.1", + "pbkdf2", + "schnorrkel", + "sha2 0.10.8", + "zeroize", +] + +[[package]] +name = "substrate-wasm-builder" +version = "24.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf035ffe7335fb24053edfe4d0a5780250eda772082a1b80ae25835dd4c09265" +dependencies = [ + "build-helper", + "cargo_metadata 0.15.4", + "console", + "filetime", + "jobserver", + "parity-wasm", + "polkavm-linker 0.9.2", + "sp-maybe-compressed-blob", + "strum 0.26.3", + "tempfile", + "toml 0.8.19", + "walkdir", + "wasm-opt", +] + [[package]] name = "subtle" version = "2.6.1" @@ -6963,30 +13555,67 @@ dependencies = [ "frame-metadata 16.0.0", "futures", "hex", - "impl-serde", + "impl-serde 0.4.0", "instant", "jsonrpsee 0.22.5", "parity-scale-codec", - "primitive-types", + "primitive-types 0.12.2", "reconnecting-jsonrpsee-ws-client", "scale-bits 0.6.0", "scale-decode 0.13.1", - "scale-encode 0.7.1", + "scale-encode 0.7.2", "scale-info", - "scale-value", + "scale-value 0.16.3", "serde", "serde_json", "sp-crypto-hashing", - "subxt-core", - "subxt-lightclient", - "subxt-macro", - "subxt-metadata", - "thiserror", + "subxt-core 0.37.1", + "subxt-lightclient 0.37.0", + "subxt-macro 0.37.0", + "subxt-metadata 0.37.0", + "thiserror 1.0.69", "tokio-util", "tracing", "url", ] +[[package]] +name = "subxt" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c53029d133e4e0cb7933f1fe06f2c68804b956de9bb8fa930ffca44e9e5e4230" +dependencies = [ + "async-trait", + "derive-where", + "either", + "frame-metadata 17.0.0", + "futures", + "hex", + "impl-serde 0.5.0", + "jsonrpsee 0.24.7", + "parity-scale-codec", + "polkadot-sdk", + "primitive-types 0.13.1", + "scale-bits 0.6.0", + "scale-decode 0.14.0", + "scale-encode 0.8.0", + "scale-info", + "scale-value 0.17.0", + "serde", + "serde_json", + "subxt-core 0.38.0", + "subxt-lightclient 0.38.0", + "subxt-macro 0.38.0", + "subxt-metadata 0.38.0", + "thiserror 1.0.69", + "tokio", + "tokio-util", + "tracing", + "url", + "wasm-bindgen-futures", + "web-time", +] + [[package]] name = "subxt-codegen" version = "0.37.0" @@ -7001,18 +13630,35 @@ dependencies = [ "proc-macro2", "quote", "scale-info", - "scale-typegen", - "subxt-metadata", - "syn 2.0.89", - "thiserror", + "scale-typegen 0.8.0", + "subxt-metadata 0.37.0", + "syn 2.0.90", + "thiserror 1.0.69", "tokio", ] +[[package]] +name = "subxt-codegen" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cfcfb7d9589f3df0ac87c4988661cf3fb370761fcb19f2fd33104cc59daf22a" +dependencies = [ + "heck 0.5.0", + "parity-scale-codec", + "proc-macro2", + "quote", + "scale-info", + "scale-typegen 0.9.0", + "subxt-metadata 0.38.0", + "syn 2.0.90", + "thiserror 1.0.69", +] + [[package]] name = "subxt-core" -version = "0.37.0" +version = "0.37.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59f41eb2e2eea6ed45649508cc735f92c27f1fcfb15229e75f8270ea73177345" +checksum = "3af3b36405538a36b424d229dc908d1396ceb0994c90825ce928709eac1a159a" dependencies = [ "base58", "blake2", @@ -7020,20 +13666,47 @@ dependencies = [ "frame-metadata 16.0.0", "hashbrown 0.14.5", "hex", - "impl-serde", + "impl-serde 0.4.0", "parity-scale-codec", - "primitive-types", + "primitive-types 0.12.2", "scale-bits 0.6.0", "scale-decode 0.13.1", - "scale-encode 0.7.1", + "scale-encode 0.7.2", "scale-info", - "scale-value", + "scale-value 0.16.3", "serde", "serde_json", - "sp-core", "sp-crypto-hashing", - "sp-runtime", - "subxt-metadata", + "subxt-metadata 0.37.0", + "tracing", +] + +[[package]] +name = "subxt-core" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ea28114366780d23684bd55ab879cd04c9d4cbba3b727a3854a3eca6bf29a1a" +dependencies = [ + "base58", + "blake2", + "derive-where", + "frame-decode", + "frame-metadata 17.0.0", + "hashbrown 0.14.5", + "hex", + "impl-serde 0.5.0", + "keccak-hash", + "parity-scale-codec", + "polkadot-sdk", + "primitive-types 0.13.1", + "scale-bits 0.6.0", + "scale-decode 0.14.0", + "scale-encode 0.8.0", + "scale-info", + "scale-value 0.17.0", + "serde", + "serde_json", + "subxt-metadata 0.38.0", "tracing", ] @@ -7047,8 +13720,25 @@ dependencies = [ "futures-util", "serde", "serde_json", - "smoldot-light", - "thiserror", + "smoldot-light 0.14.0", + "thiserror 1.0.69", + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "subxt-lightclient" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534d4b725183a9fa09ce0e0f135674473297fdd97dee4d683f41117f365ae997" +dependencies = [ + "futures", + "futures-util", + "serde", + "serde_json", + "smoldot-light 0.16.2", + "thiserror 1.0.69", "tokio", "tokio-stream", "tracing", @@ -7064,9 +13754,25 @@ dependencies = [ "parity-scale-codec", "proc-macro-error", "quote", - "scale-typegen", - "subxt-codegen", - "syn 2.0.89", + "scale-typegen 0.8.0", + "subxt-codegen 0.37.0", + "syn 2.0.90", +] + +[[package]] +name = "subxt-macro" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "228db9a5c95a6d8dc6152b4d6cdcbabc4f60821dd3f482a4f8791e022b7caadb" +dependencies = [ + "darling 0.20.10", + "parity-scale-codec", + "proc-macro-error2", + "quote", + "scale-typegen 0.9.0", + "subxt-codegen 0.38.0", + "subxt-utils-fetchmetadata", + "syn 2.0.90", ] [[package]] @@ -7082,6 +13788,20 @@ dependencies = [ "sp-crypto-hashing", ] +[[package]] +name = "subxt-metadata" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee13e6862eda035557d9a2871955306aff540d2b89c06e0a62a1136a700aed28" +dependencies = [ + "frame-decode", + "frame-metadata 17.0.0", + "hashbrown 0.14.5", + "parity-scale-codec", + "polkadot-sdk", + "scale-info", +] + [[package]] name = "subxt-signer" version = "0.37.0" @@ -7096,14 +13816,52 @@ dependencies = [ "pbkdf2", "regex", "schnorrkel", - "secp256k1", - "secrecy", + "secp256k1 0.28.2", + "secrecy 0.8.0", "sha2 0.10.8", "sp-crypto-hashing", - "subxt-core", + "subxt-core 0.37.1", + "zeroize", +] + +[[package]] +name = "subxt-signer" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7a336d6a1f86f126100a4a717be58352de4c8214300c4f7807f974494efdb9" +dependencies = [ + "base64 0.22.1", + "bip39", + "cfg-if", + "crypto_secretbox", + "hex", + "hmac 0.12.1", + "parity-scale-codec", + "pbkdf2", + "polkadot-sdk", + "regex", + "schnorrkel", + "scrypt", + "secp256k1 0.30.0", + "secrecy 0.10.3", + "serde", + "serde_json", + "sha2 0.10.8", + "subxt-core 0.38.0", "zeroize", ] +[[package]] +name = "subxt-utils-fetchmetadata" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3082b17a86e3c3fe45d858d94d68f6b5247caace193dad6201688f24db8ba9bb" +dependencies = [ + "hex", + "parity-scale-codec", + "thiserror 1.0.69", +] + [[package]] name = "symlink" version = "0.1.0" @@ -7123,9 +13881,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.89" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -7133,15 +13891,15 @@ dependencies = [ ] [[package]] -name = "syn_derive" -version = "0.1.8" +name = "syn-solidity" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +checksum = "86b837ef12ab88835251726eb12237655e61ec8dc8a280085d1961cdc3dfd047" dependencies = [ - "proc-macro-error", + "paste", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -7152,13 +13910,25 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "sync_wrapper" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-xid", +] + [[package]] name = "synstructure" version = "0.13.1" @@ -7167,7 +13937,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -7220,9 +13990,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tar" -version = "0.4.41" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb797dad5fb5b76fcf519e702f4a589483b5ef06567f160c392832c1f5e44909" +checksum = "c65998313f8e17d0d553d28f91a0df93e4dbbbf770279c7bc21ca0f09ea1a1f6" dependencies = [ "filetime", "libc", @@ -7237,14 +14007,14 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", "fastrand", "once_cell", - "rustix 0.38.36", + "rustix 0.38.42", "windows-sys 0.59.0", ] @@ -7273,6 +14043,22 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" +[[package]] +name = "testnet-parachains-constants" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94bceae6f7c89d47daff6c7e05f712551a01379f61b07d494661941144878589" +dependencies = [ + "cumulus-primitives-core", + "frame-support", + "polkadot-core-primitives", + "rococo-runtime-constants", + "smallvec", + "sp-runtime 39.0.2", + "staging-xcm 14.2.0", + "westend-runtime-constants", +] + [[package]] name = "textwrap" version = "0.16.1" @@ -7281,27 +14067,47 @@ checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" dependencies = [ "smawk", "unicode-linebreak", - "unicode-width", + "unicode-width 0.1.14", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" -version = "1.0.63" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.6", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", ] [[package]] @@ -7316,9 +14122,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -7337,14 +14143,23 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinystr" version = "0.7.6" @@ -7372,14 +14187,14 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.40.0" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" dependencies = [ "backtrace", "bytes", "libc", - "mio 1.0.2", + "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", @@ -7406,7 +14221,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -7442,20 +14257,19 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" dependencies = [ - "rustls 0.23.13", - "rustls-pki-types", + "rustls 0.23.19", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ "futures-core", "pin-project-lite", @@ -7489,9 +14303,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", @@ -7532,7 +14346,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.20", + "toml_edit 0.22.22", ] [[package]] @@ -7550,7 +14364,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.5.0", + "indexmap 2.7.0", "serde", "serde_spanned", "toml_datetime", @@ -7559,15 +14373,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.20" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.5.0", + "indexmap 2.7.0", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.18", + "winnow 0.6.20", ] [[package]] @@ -7622,9 +14436,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", @@ -7634,20 +14448,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -7664,6 +14478,17 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + [[package]] name = "tracing-serde" version = "0.1.3" @@ -7683,7 +14508,7 @@ dependencies = [ "ansi_term", "chrono", "lazy_static", - "matchers", + "matchers 0.0.1", "regex", "serde", "serde_json", @@ -7692,10 +14517,29 @@ dependencies = [ "thread_local", "tracing", "tracing-core", - "tracing-log", + "tracing-log 0.1.4", "tracing-serde", ] +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers 0.1.0", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "time", + "tracing", + "tracing-core", + "tracing-log 0.2.0", +] + [[package]] name = "trie-db" version = "0.28.0" @@ -7709,6 +14553,18 @@ dependencies = [ "smallvec", ] +[[package]] +name = "trie-db" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c992b4f40c234a074d48a757efeabb1a6be88af84c0c23f7ca158950cb0ae7f" +dependencies = [ + "hash-db", + "log", + "rustc-hex", + "smallvec", +] + [[package]] name = "trie-root" version = "0.18.0" @@ -7724,6 +14580,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tt-call" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f195fd851901624eee5a58c4bb2b4f06399148fcd0ed336e6f1cb60a9881df" + [[package]] name = "tungstenite" version = "0.20.1" @@ -7738,11 +14600,17 @@ dependencies = [ "log", "rand", "sha1", - "thiserror", + "thiserror 1.0.69", "url", "utf-8", ] +[[package]] +name = "tuplex" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "676ac81d5454c4dcf37955d34fa8626ede3490f744b86ca14a7b90168d2a08aa" + [[package]] name = "twox-hash" version = "1.6.3" @@ -7763,9 +14631,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "uint" @@ -7780,19 +14648,34 @@ dependencies = [ ] [[package]] -name = "unicase" -version = "2.7.0" +name = "uint" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e" dependencies = [ - "version_check", + "byteorder", + "crunchy", + "hex", + "static_assertions", ] +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicase" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" + [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-linebreak" @@ -7811,15 +14694,21 @@ dependencies = [ [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "unicode-xid" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "universal-hash" @@ -7843,6 +14732,12 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6889a77d49f1f013504cec6bf97a2c730394adedaeb1deb5ea08949a50541105" +[[package]] +name = "unsigned-varint" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" + [[package]] name = "untrusted" version = "0.9.0" @@ -7887,9 +14782,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ "getrandom", ] @@ -7930,15 +14825,15 @@ checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] name = "w3f-bls" -version = "0.1.4" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c5da5fa2c6afa2c9158eaa7cd9aee249765eb32b5fb0c63ad8b9e79336a47ec" +checksum = "70a3028804c8bbae2a97a15b71ffc0e308c4b01a520994aafa77d56e94e19024" dependencies = [ "ark-bls12-377", "ark-bls12-381", "ark-ec", - "ark-ff", - "ark-serialize", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", "ark-serialize-derive", "arrayref", "constcat", @@ -7948,7 +14843,7 @@ dependencies = [ "rand_core 0.6.4", "sha2 0.10.8", "sha3", - "thiserror", + "thiserror 1.0.69", "zeroize", ] @@ -7988,9 +14883,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", "once_cell", @@ -7999,36 +14894,36 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.43" +version = "0.4.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -8036,31 +14931,40 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "wasm-encoder" -version = "0.207.0" +version = "0.220.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d996306fb3aeaee0d9157adbe2f670df0236caf19f6728b221e92d0f27b3fe17" +checksum = "ebf48234b389415b226a4daef6562933d38c7b28a8b8f64c5c4130dad1561ab7" dependencies = [ "leb128", - "wasmparser 0.207.0", + "wasmparser 0.220.0", +] + +[[package]] +name = "wasm-instrument" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a47ecb37b9734d1085eaa5ae1a81e60801fd8c28d4cabdd8aedb982021918bc" +dependencies = [ + "parity-wasm", ] [[package]] @@ -8074,7 +14978,7 @@ dependencies = [ "strum 0.24.1", "strum_macros 0.24.3", "tempfile", - "thiserror", + "thiserror 1.0.69", "wasm-opt-cxx-sys", "wasm-opt-sys", ] @@ -8112,7 +15016,24 @@ dependencies = [ "smallvec", "spin", "wasmi_arena", - "wasmi_core", + "wasmi_core 0.13.0", + "wasmparser-nostd", +] + +[[package]] +name = "wasmi" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50386c99b9c32bd2ed71a55b6dd4040af2580530fae8bdb9a6576571a80d0cca" +dependencies = [ + "arrayvec 0.7.6", + "multi-stash", + "num-derive", + "num-traits", + "smallvec", + "spin", + "wasmi_collections", + "wasmi_core 0.32.3", "wasmparser-nostd", ] @@ -8122,6 +15043,17 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "104a7f73be44570cac297b3035d76b169d6599637631cf37a1703326a0727073" +[[package]] +name = "wasmi_collections" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c128c039340ffd50d4195c3f8ce31aac357f06804cfc494c8b9508d4b30dca4" +dependencies = [ + "ahash 0.8.11", + "hashbrown 0.14.5", + "string-interner", +] + [[package]] name = "wasmi_core" version = "0.13.0" @@ -8134,6 +15066,18 @@ dependencies = [ "paste", ] +[[package]] +name = "wasmi_core" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23b3a7f6c8c3ceeec6b83531ee61f0013c56e51cbf2b14b0f213548b23a4b41" +dependencies = [ + "downcast-rs", + "libm", + "num-traits", + "paste", +] + [[package]] name = "wasmparser" version = "0.102.0" @@ -8146,15 +15090,16 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.207.0" +version = "0.220.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e19bb9f8ab07616da582ef8adb24c54f1424c7ec876720b7da9db8ec0626c92c" +checksum = "e246c2772ce3ebc83f89a2d4487ac5794cad6c309b2071818a88c7db7c36d87b" dependencies = [ "ahash 0.8.11", "bitflags 2.6.0", "hashbrown 0.14.5", - "indexmap 2.5.0", - "semver", + "indexmap 2.7.0", + "semver 1.0.23", + "serde", ] [[package]] @@ -8182,9 +15127,12 @@ dependencies = [ "once_cell", "paste", "psm", + "rayon", "serde", "target-lexicon", "wasmparser 0.102.0", + "wasmtime-cache", + "wasmtime-cranelift", "wasmtime-environ", "wasmtime-jit", "wasmtime-runtime", @@ -8200,6 +15148,63 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "wasmtime-cache" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c86437fa68626fe896e5afc69234bb2b5894949083586535f200385adfd71213" +dependencies = [ + "anyhow", + "base64 0.21.7", + "bincode", + "directories-next", + "file-per-thread-logger", + "log", + "rustix 0.36.17", + "serde", + "sha2 0.10.8", + "toml 0.5.11", + "windows-sys 0.45.0", + "zstd 0.11.2+zstd.1.5.2", +] + +[[package]] +name = "wasmtime-cranelift" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1cefde0cce8cb700b1b21b6298a3837dba46521affd7b8c38a9ee2c869eee04" +dependencies = [ + "anyhow", + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", + "cranelift-native", + "cranelift-wasm", + "gimli 0.27.3", + "log", + "object 0.30.4", + "target-lexicon", + "thiserror 1.0.69", + "wasmparser 0.102.0", + "wasmtime-cranelift-shared", + "wasmtime-environ", +] + +[[package]] +name = "wasmtime-cranelift-shared" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd041e382ef5aea1b9fc78442394f1a4f6d676ce457e7076ca4cb3f397882f8b" +dependencies = [ + "anyhow", + "cranelift-codegen", + "cranelift-native", + "gimli 0.27.3", + "object 0.30.4", + "target-lexicon", + "wasmtime-environ", +] + [[package]] name = "wasmtime-environ" version = "8.0.1" @@ -8214,7 +15219,7 @@ dependencies = [ "object 0.30.4", "serde", "target-lexicon", - "thiserror", + "thiserror 1.0.69", "wasmparser 0.102.0", "wasmtime-types", ] @@ -8237,6 +15242,7 @@ dependencies = [ "serde", "target-lexicon", "wasmtime-environ", + "wasmtime-jit-debug", "wasmtime-jit-icache-coherence", "wasmtime-runtime", "windows-sys 0.45.0", @@ -8248,7 +15254,9 @@ version = "8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e0554b84c15a27d76281d06838aed94e13a77d7bf604bbbaf548aa20eb93846" dependencies = [ + "object 0.30.4", "once_cell", + "rustix 0.36.17", ] [[package]] @@ -8294,15 +15302,25 @@ checksum = "a4f6fffd2a1011887d57f07654dd112791e872e3ff4a2e626aee8059ee17f06f" dependencies = [ "cranelift-entity", "serde", - "thiserror", + "thiserror 1.0.69", "wasmparser 0.102.0", ] [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", "wasm-bindgen", @@ -8310,25 +15328,52 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.5" +version = "0.26.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bd24728e5af82c6c4ec1b66ac4844bdf8156257fccda846ec58b42cd0cdbe6a" +checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" dependencies = [ "rustls-pki-types", ] +[[package]] +name = "westend-runtime-constants" +version = "17.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06861bf945aadac59f4be23b44c85573029520ea9bd3d6c9ab21c8b306e81cdc" +dependencies = [ + "frame-support", + "polkadot-primitives 16.0.0", + "polkadot-runtime-common", + "smallvec", + "sp-core 34.0.0", + "sp-runtime 39.0.2", + "sp-weights 31.0.0", + "staging-xcm 14.2.0", + "staging-xcm-builder", +] + [[package]] name = "which" -version = "6.0.3" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" +checksum = "c9cad3279ade7346b96e38731a641d7343dd6a53d55083dd54eadfa5a1b38c6b" dependencies = [ "either", "home", - "rustix 0.38.36", + "rustix 0.38.42", "winsafe", ] +[[package]] +name = "wide" +version = "0.7.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58e6db2670d2be78525979e9a5f9c69d296fd7d670549fe9ebf70f8708cb5019" +dependencies = [ + "bytemuck", + "safe_arch", +] + [[package]] name = "winapi" version = "0.3.9" @@ -8624,9 +15669,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] @@ -8688,7 +15733,69 @@ checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" dependencies = [ "libc", "linux-raw-sys 0.4.14", - "rustix 0.38.36", + "rustix 0.38.42", +] + +[[package]] +name = "xcm-procedural" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4717a97970a9cda70d7db53cf50d2615c2f6f6b7c857445325b4a39ea7aa2cd" +dependencies = [ + "Inflector", + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "xcm-procedural" +version = "10.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fb4f14094d65c500a59bcf540cf42b99ee82c706edd6226a92e769ad60563e" +dependencies = [ + "Inflector", + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "xcm-runtime-apis" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d4473a5d157e4d437d9ebcb1b99f9693a64983877ee57d97005f0167869935" +dependencies = [ + "frame-support", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-weights 31.0.0", + "staging-xcm 14.2.0", + "staging-xcm-executor", +] + +[[package]] +name = "xcm-simulator" +version = "17.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "058e21bfc3e1180bbd83cad3690d0e63f34f43ab309e338afe988160aa776fcf" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "paste", + "polkadot-core-primitives", + "polkadot-parachain-primitives", + "polkadot-primitives 16.0.0", + "polkadot-runtime-parachains", + "scale-info", + "sp-io 38.0.0", + "sp-runtime 39.0.2", + "sp-std", + "staging-xcm 14.2.0", + "staging-xcm-builder", + "staging-xcm-executor", ] [[package]] @@ -8723,8 +15830,8 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", - "synstructure", + "syn 2.0.90", + "synstructure 0.13.1", ] [[package]] @@ -8745,7 +15852,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -8765,8 +15872,8 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", - "synstructure", + "syn 2.0.90", + "synstructure 0.13.1", ] [[package]] @@ -8786,7 +15893,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -8808,29 +15915,29 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] name = "zip" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc5e4288ea4057ae23afc69a4472434a87a2495cafce6632fd1c4ec9f5cf3494" +checksum = "99d52293fc86ea7cf13971b3bb81eb21683636e7ae24c729cdaf1b7c4157a352" dependencies = [ "arbitrary", "crc32fast", "crossbeam-utils", "displaydoc", - "indexmap 2.5.0", + "indexmap 2.7.0", "memchr", - "thiserror", + "thiserror 2.0.6", ] [[package]] name = "zombienet-configuration" -version = "0.2.14" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ab4af5d47f03cb8ae0fa96ffbf3881ca050c51e62367685ebdd3e8ae52b220" +checksum = "22860eef7e651d6e0aa7e37fc3bcba3c2c8b7bd1c140d7ea929caacf2b7fc726" dependencies = [ "anyhow", "lazy_static", @@ -8839,7 +15946,7 @@ dependencies = [ "reqwest 0.11.27", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "tokio", "toml 0.7.8", "url", @@ -8848,9 +15955,9 @@ dependencies = [ [[package]] name = "zombienet-orchestrator" -version = "0.2.14" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "580e01019655a2bd296e42f48876c2e78c94a989147376e025c0e64daf717cf7" +checksum = "b19b1b2fd2db3153155f21cb84cdd8e5d6faefc3043353b8c90661c44f4660da" dependencies = [ "anyhow", "async-trait", @@ -8866,10 +15973,10 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.8", - "sp-core", - "subxt", - "subxt-signer", - "thiserror", + "sp-core 31.0.0", + "subxt 0.38.0", + "subxt-signer 0.38.0", + "thiserror 1.0.69", "tokio", "tracing", "uuid", @@ -8881,20 +15988,20 @@ dependencies = [ [[package]] name = "zombienet-prom-metrics-parser" -version = "0.2.14" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d026607c1a6e198b653c5979fb07078740861978ddecfd5460f2c32cdc182b1" +checksum = "ea61ce9c6b2d43be864ad34328d05794079381807f5d77c737a062486966347f" dependencies = [ "pest", "pest_derive", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "zombienet-provider" -version = "0.2.14" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55583d8be6b2ca2e679d85ee2e17eb0802fbf8d2ee05c1b326fff81fd6b3b5c3" +checksum = "c99cc7c143f1145bda2b2f5a945b8040a898a85fc029dba51f220395a7188d9d" dependencies = [ "anyhow", "async-trait", @@ -8911,7 +16018,7 @@ dependencies = [ "serde_yaml", "sha2 0.10.8", "tar", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-util", "tracing", @@ -8923,14 +16030,15 @@ dependencies = [ [[package]] name = "zombienet-sdk" -version = "0.2.14" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300f3f07aa72df21a52611a0df1033325a454252e36d964c4308b0a8b7356f77" +checksum = "09e5abdad4ad32c1c06cb8fdc4507db026f65987cb5c46ae4224118cc496097b" dependencies = [ "async-trait", "futures", "lazy_static", - "subxt", + "subxt 0.38.0", + "subxt-signer 0.38.0", "tokio", "zombienet-configuration", "zombienet-orchestrator", @@ -8940,9 +16048,9 @@ dependencies = [ [[package]] name = "zombienet-support" -version = "0.2.14" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e566ea145dab27c6fbb24b13d4c789efa7e675e1fdaf6d007dfe1154d59317c6" +checksum = "2e3310631948f8bb4d394c160c4b063889a4e0c6beadd6b95f9b62d1616ab93c" dependencies = [ "anyhow", "async-trait", @@ -8951,8 +16059,56 @@ dependencies = [ "rand", "regex", "reqwest 0.11.27", - "thiserror", + "thiserror 1.0.69", "tokio", "tracing", "uuid", ] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe 5.0.2+zstd.1.5.2", +] + +[[package]] +name = "zstd" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a27595e173641171fc74a1232b7b1c7a7cb6e18222c11e9dfb9888fa424c53c" +dependencies = [ + "zstd-safe 6.0.6", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-safe" +version = "6.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee98ffd0b48ee95e6c5168188e44a54550b1564d9d530ee21d5f0eaed1069581" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.13+zstd.1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index 30c181caa..c31416f12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,16 +44,16 @@ tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } url = "2.5.4" # contracts -subxt-signer = { version = "0.37.0", features = ["subxt", "sr25519"] } -subxt = "0.37.0" +subxt-signer = { version = "0.38.0", features = ["subxt", "sr25519"] } +subxt = "0.38.0" ink_env = "5.0.0" -sp-core = "31" -sp-weights = "30" -contract-build = "5.0.0-alpha" -contract-extrinsics = "5.0.0-alpha" -contract-transcode = "5.0.0-alpha" -scale-info = { version = "2.11.3", default-features = false, features = ["derive"] } -scale-value = { version = "0.16.2", default-features = false, features = ["from-string", "parser-ss58"] } +sp-core = "32.0.0" +sp-weights = "31.0.0" +contract-build = "5.0.0" +contract-extrinsics = "5.0.0" +contract-transcode = "5.0.0" +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"] } heck = "0.5.0" hex = { version = "0.4.3", default-features = false } @@ -66,7 +66,7 @@ toml_edit = { version = "0.22", features = ["serde"] } symlink = "0.1" serde_json = { version = "1.0", features = ["preserve_order"] } serde = { version = "1.0", features = ["derive"] } -zombienet-sdk = "0.2.14" +zombienet-sdk = "0.2.18" git2_credentials = "0.13.0" # pop-cli diff --git a/deny.toml b/deny.toml index 53c6d37e6..b1e67ce44 100644 --- a/deny.toml +++ b/deny.toml @@ -22,7 +22,8 @@ allow = [ "MPL-2.0", "Unicode-3.0", "Unicode-DFS-2016", - "Unlicense" + "Unlicense", + "Zlib" ] confidence-threshold = 0.93 @@ -35,4 +36,4 @@ name = "ring" expression = "ISC AND MIT AND OpenSSL" license-files = [ { path = "LICENSE", hash = 0xbd0eed23 }, -] \ No newline at end of file +] From 4ff112abec01c91fe47ac75c49cd364d441e9c83 Mon Sep 17 00:00:00 2001 From: Frank Bell <60948618+evilrobot-01@users.noreply.github.com> Date: Mon, 16 Dec 2024 16:24:40 +0000 Subject: [PATCH 202/211] chore: set msrv (#385) subxt 0.38 uses features only available from Rust 1.81 --- Cargo.lock | 903 ++++++----------------------------------------------- Cargo.toml | 1 + README.md | 8 +- 3 files changed, 107 insertions(+), 805 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 74d393d5d..bb211c82e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -817,7 +817,7 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ - "event-listener 5.3.1", + "event-listener", "event-listener-strategy", "pin-project-lite", ] @@ -846,7 +846,7 @@ dependencies = [ "async-task", "blocking", "cfg-if", - "event-listener 5.3.1", + "event-listener", "futures-lite", "rustix 0.38.42", "tracing", @@ -1015,15 +1015,6 @@ dependencies = [ "serde", ] -[[package]] -name = "beef" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" -dependencies = [ - "serde", -] - [[package]] name = "binary-merkle-tree" version = "15.0.1" @@ -2022,8 +2013,8 @@ dependencies = [ "serde_json", "sp-core 32.0.0", "sp-runtime 35.0.0", - "sp-weights 31.0.0", - "subxt 0.38.0", + "sp-weights", + "subxt", "tokio", "tracing", "url", @@ -3354,16 +3345,6 @@ dependencies = [ "uint 0.9.5", ] -[[package]] -name = "event-listener" -version = "4.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" -dependencies = [ - "concurrent-queue", - "pin-project-lite", -] - [[package]] name = "event-listener" version = "5.3.1" @@ -3381,7 +3362,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" dependencies = [ - "event-listener 5.3.1", + "event-listener", "pin-project-lite", ] @@ -3493,16 +3474,6 @@ dependencies = [ "scale-info", ] -[[package]] -name = "finito" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2384245d85162258a14b43567a9ee3598f5ae746a1581fb5d3d2cb780f0dbf95" -dependencies = [ - "futures-timer", - "pin-project", -] - [[package]] name = "fixed-hash" version = "0.8.0" @@ -3647,7 +3618,7 @@ dependencies = [ "frame-system", "parity-scale-codec", "scale-info", - "sp-arithmetic 26.0.0", + "sp-arithmetic", "sp-core 34.0.0", "sp-npos-elections", "sp-runtime 39.0.2", @@ -3672,17 +3643,6 @@ dependencies = [ "sp-tracing 17.0.1", ] -[[package]] -name = "frame-metadata" -version = "15.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "878babb0b136e731cc77ec2fd883ff02745ff21e6fb662729953d44923df009c" -dependencies = [ - "cfg-if", - "parity-scale-codec", - "scale-info", -] - [[package]] name = "frame-metadata" version = "16.0.0" @@ -3747,7 +3707,7 @@ dependencies = [ "serde_json", "smallvec", "sp-api", - "sp-arithmetic 26.0.0", + "sp-arithmetic", "sp-core 34.0.0", "sp-crypto-hashing-proc-macro", "sp-debug-derive", @@ -3760,7 +3720,7 @@ dependencies = [ "sp-state-machine 0.43.0", "sp-std", "sp-tracing 17.0.1", - "sp-weights 31.0.0", + "sp-weights", "static_assertions", "tt-call", ] @@ -3827,7 +3787,7 @@ dependencies = [ "sp-runtime 39.0.2", "sp-std", "sp-version", - "sp-weights 31.0.0", + "sp-weights", ] [[package]] @@ -5050,15 +5010,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.13.0" @@ -5143,83 +5094,16 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "jsonrpsee" -version = "0.22.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfdb12a2381ea5b2e68c3469ec604a007b367778cdb14d09612c8069ebd616ad" -dependencies = [ - "jsonrpsee-client-transport 0.22.5", - "jsonrpsee-core 0.22.5", - "jsonrpsee-http-client", - "jsonrpsee-types 0.22.5", -] - -[[package]] -name = "jsonrpsee" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b089779ad7f80768693755a031cc14a7766aba707cbe886674e3f79e9b7e47" -dependencies = [ - "jsonrpsee-core 0.23.2", - "jsonrpsee-types 0.23.2", - "jsonrpsee-ws-client 0.23.2", -] - [[package]] name = "jsonrpsee" version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5c71d8c1a731cc4227c2f698d377e7848ca12c8a48866fc5e6951c43a4db843" dependencies = [ - "jsonrpsee-client-transport 0.24.7", - "jsonrpsee-core 0.24.7", - "jsonrpsee-types 0.24.7", - "jsonrpsee-ws-client 0.24.7", -] - -[[package]] -name = "jsonrpsee-client-transport" -version = "0.22.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4978087a58c3ab02efc5b07c5e5e2803024536106fd5506f558db172c889b3aa" -dependencies = [ - "futures-util", - "http 0.2.12", - "jsonrpsee-core 0.22.5", - "pin-project", - "rustls-native-certs 0.7.3", - "rustls-pki-types", - "soketto 0.7.1", - "thiserror 1.0.69", - "tokio", - "tokio-rustls 0.25.0", - "tokio-util", - "tracing", - "url", -] - -[[package]] -name = "jsonrpsee-client-transport" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08163edd8bcc466c33d79e10f695cdc98c00d1e6ddfb95cec41b6b0279dd5432" -dependencies = [ - "base64 0.22.1", - "futures-util", - "http 1.2.0", - "jsonrpsee-core 0.23.2", - "pin-project", - "rustls 0.23.19", - "rustls-pki-types", - "rustls-platform-verifier", - "soketto 0.8.1", - "thiserror 1.0.69", - "tokio", - "tokio-rustls 0.26.1", - "tokio-util", - "tracing", - "url", + "jsonrpsee-client-transport", + "jsonrpsee-core", + "jsonrpsee-types", + "jsonrpsee-ws-client", ] [[package]] @@ -5231,12 +5115,12 @@ dependencies = [ "base64 0.22.1", "futures-util", "http 1.2.0", - "jsonrpsee-core 0.24.7", + "jsonrpsee-core", "pin-project", "rustls 0.23.19", "rustls-pki-types", "rustls-platform-verifier", - "soketto 0.8.1", + "soketto", "thiserror 1.0.69", "tokio", "tokio-rustls 0.26.1", @@ -5245,51 +5129,6 @@ dependencies = [ "url", ] -[[package]] -name = "jsonrpsee-core" -version = "0.22.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b257e1ec385e07b0255dde0b933f948b5c8b8c28d42afda9587c3a967b896d" -dependencies = [ - "anyhow", - "async-trait", - "beef", - "futures-timer", - "futures-util", - "hyper 0.14.31", - "jsonrpsee-types 0.22.5", - "pin-project", - "rustc-hash 1.1.0", - "serde", - "serde_json", - "thiserror 1.0.69", - "tokio", - "tokio-stream", - "tracing", -] - -[[package]] -name = "jsonrpsee-core" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79712302e737d23ca0daa178e752c9334846b08321d439fd89af9a384f8c830b" -dependencies = [ - "anyhow", - "async-trait", - "beef", - "futures-timer", - "futures-util", - "jsonrpsee-types 0.23.2", - "pin-project", - "rustc-hash 1.1.0", - "serde", - "serde_json", - "thiserror 1.0.69", - "tokio", - "tokio-stream", - "tracing", -] - [[package]] name = "jsonrpsee-core" version = "0.24.7" @@ -5299,7 +5138,7 @@ dependencies = [ "async-trait", "futures-timer", "futures-util", - "jsonrpsee-types 0.24.7", + "jsonrpsee-types", "pin-project", "rustc-hash 2.1.0", "serde", @@ -5310,52 +5149,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "jsonrpsee-http-client" -version = "0.22.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ccf93fc4a0bfe05d851d37d7c32b7f370fe94336b52a2f0efc5f1981895c2e5" -dependencies = [ - "async-trait", - "hyper 0.14.31", - "hyper-rustls 0.24.2", - "jsonrpsee-core 0.22.5", - "jsonrpsee-types 0.22.5", - "serde", - "serde_json", - "thiserror 1.0.69", - "tokio", - "tower", - "tracing", - "url", -] - -[[package]] -name = "jsonrpsee-types" -version = "0.22.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "150d6168405890a7a3231a3c74843f58b8959471f6df76078db2619ddee1d07d" -dependencies = [ - "anyhow", - "beef", - "serde", - "serde_json", - "thiserror 1.0.69", -] - -[[package]] -name = "jsonrpsee-types" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c465fbe385238e861fdc4d1c85e04ada6c1fd246161d26385c1b311724d2af" -dependencies = [ - "beef", - "http 1.2.0", - "serde", - "serde_json", - "thiserror 1.0.69", -] - [[package]] name = "jsonrpsee-types" version = "0.24.7" @@ -5368,19 +5161,6 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "jsonrpsee-ws-client" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c28759775f5cb2f1ea9667672d3fe2b0e701d1f4b7b67954e60afe7fd058b5e" -dependencies = [ - "http 1.2.0", - "jsonrpsee-client-transport 0.23.2", - "jsonrpsee-core 0.23.2", - "jsonrpsee-types 0.23.2", - "url", -] - [[package]] name = "jsonrpsee-ws-client" version = "0.24.7" @@ -5388,9 +5168,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fe322e0896d0955a3ebdd5bf813571c53fea29edd713bc315b76620b327e86d" dependencies = [ "http 1.2.0", - "jsonrpsee-client-transport 0.24.7", - "jsonrpsee-core 0.24.7", - "jsonrpsee-types 0.24.7", + "jsonrpsee-client-transport", + "jsonrpsee-core", + "jsonrpsee-types", "url", ] @@ -6161,12 +5941,6 @@ dependencies = [ "libc", ] -[[package]] -name = "no-std-net" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" - [[package]] name = "nodrop" version = "0.1.14" @@ -6480,7 +6254,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-api", - "sp-arithmetic 26.0.0", + "sp-arithmetic", "sp-core 34.0.0", "sp-io 38.0.0", "sp-runtime 39.0.2", @@ -6499,7 +6273,7 @@ dependencies = [ "pallet-asset-conversion", "parity-scale-codec", "scale-info", - "sp-arithmetic 26.0.0", + "sp-arithmetic", "sp-core 34.0.0", "sp-io 38.0.0", "sp-runtime 39.0.2", @@ -6855,7 +6629,7 @@ dependencies = [ "pallet-transaction-payment", "parity-scale-codec", "scale-info", - "sp-arithmetic 26.0.0", + "sp-arithmetic", "sp-runtime 39.0.2", "sp-std", ] @@ -6874,7 +6648,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-api", - "sp-arithmetic 26.0.0", + "sp-arithmetic", "sp-core 34.0.0", "sp-runtime 39.0.2", ] @@ -6980,7 +6754,7 @@ dependencies = [ "staging-xcm 14.2.0", "staging-xcm-builder", "wasm-instrument", - "wasmi 0.32.3", + "wasmi", ] [[package]] @@ -7097,7 +6871,7 @@ dependencies = [ "pallet-ranked-collective", "parity-scale-codec", "scale-info", - "sp-arithmetic 26.0.0", + "sp-arithmetic", "sp-core 34.0.0", "sp-io 38.0.0", "sp-runtime 39.0.2", @@ -7168,7 +6942,7 @@ dependencies = [ "parity-scale-codec", "rand", "scale-info", - "sp-arithmetic 26.0.0", + "sp-arithmetic", "sp-core 34.0.0", "sp-io 38.0.0", "sp-npos-elections", @@ -7382,11 +7156,11 @@ dependencies = [ "log", "parity-scale-codec", "scale-info", - "sp-arithmetic 26.0.0", + "sp-arithmetic", "sp-core 34.0.0", "sp-io 38.0.0", "sp-runtime 39.0.2", - "sp-weights 31.0.0", + "sp-weights", ] [[package]] @@ -7421,7 +7195,7 @@ dependencies = [ "scale-info", "serde", "sp-application-crypto 38.0.0", - "sp-arithmetic 26.0.0", + "sp-arithmetic", "sp-io 38.0.0", "sp-mixnet", "sp-runtime 39.0.2", @@ -7518,7 +7292,7 @@ dependencies = [ "frame-system", "parity-scale-codec", "scale-info", - "sp-arithmetic 26.0.0", + "sp-arithmetic", "sp-core 34.0.0", "sp-runtime 39.0.2", ] @@ -7712,7 +7486,7 @@ dependencies = [ "log", "parity-scale-codec", "scale-info", - "sp-arithmetic 26.0.0", + "sp-arithmetic", "sp-core 34.0.0", "sp-io 38.0.0", "sp-runtime 39.0.2", @@ -7746,7 +7520,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-arithmetic 26.0.0", + "sp-arithmetic", "sp-io 38.0.0", "sp-runtime 39.0.2", ] @@ -7919,7 +7693,7 @@ dependencies = [ "pallet-utility", "parity-scale-codec", "scale-info", - "sp-arithmetic 26.0.0", + "sp-arithmetic", "sp-runtime 39.0.2", ] @@ -7936,7 +7710,7 @@ dependencies = [ "pallet-ranked-collective", "parity-scale-codec", "scale-info", - "sp-arithmetic 26.0.0", + "sp-arithmetic", "sp-core 34.0.0", "sp-io 38.0.0", "sp-runtime 39.0.2", @@ -7957,7 +7731,7 @@ dependencies = [ "scale-info", "sp-io 38.0.0", "sp-runtime 39.0.2", - "sp-weights 31.0.0", + "sp-weights", ] [[package]] @@ -8039,7 +7813,7 @@ dependencies = [ "parity-scale-codec", "rand_chacha", "scale-info", - "sp-arithmetic 26.0.0", + "sp-arithmetic", "sp-io 38.0.0", "sp-runtime 39.0.2", ] @@ -8073,7 +7847,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "988a7ebeacc84d4bdb0b12409681e956ffe35438447d8f8bc78db547cffb6ebc" dependencies = [ "log", - "sp-arithmetic 26.0.0", + "sp-arithmetic", ] [[package]] @@ -8203,7 +7977,7 @@ dependencies = [ "parity-scale-codec", "sp-api", "sp-runtime 39.0.2", - "sp-weights 31.0.0", + "sp-weights", ] [[package]] @@ -8767,7 +8541,7 @@ dependencies = [ "serde", "sp-core 34.0.0", "sp-runtime 39.0.2", - "sp-weights 31.0.0", + "sp-weights", ] [[package]] @@ -8786,7 +8560,7 @@ dependencies = [ "serde", "sp-api", "sp-application-crypto 38.0.0", - "sp-arithmetic 26.0.0", + "sp-arithmetic", "sp-authority-discovery", "sp-consensus-slots", "sp-core 34.0.0", @@ -8813,7 +8587,7 @@ dependencies = [ "serde", "sp-api", "sp-application-crypto 38.0.0", - "sp-arithmetic 26.0.0", + "sp-arithmetic", "sp-authority-discovery", "sp-consensus-slots", "sp-core 34.0.0", @@ -8923,7 +8697,7 @@ dependencies = [ "serde", "sp-api", "sp-application-crypto 38.0.0", - "sp-arithmetic 26.0.0", + "sp-arithmetic", "sp-core 34.0.0", "sp-inherents", "sp-io 38.0.0", @@ -9118,7 +8892,7 @@ dependencies = [ "sp-api", "sp-api-proc-macro", "sp-application-crypto 38.0.0", - "sp-arithmetic 26.0.0", + "sp-arithmetic", "sp-authority-discovery", "sp-block-builder", "sp-consensus-aura", @@ -9158,7 +8932,7 @@ dependencies = [ "sp-trie 37.0.0", "sp-version", "sp-wasm-interface 21.0.1", - "sp-weights 31.0.0", + "sp-weights", "staging-parachain-info", "staging-xcm 14.2.0", "staging-xcm-builder", @@ -9186,7 +8960,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-api", - "sp-arithmetic 26.0.0", + "sp-arithmetic", "sp-block-builder", "sp-consensus-aura", "sp-consensus-grandpa", @@ -9492,8 +9266,8 @@ dependencies = [ "predicates", "reqwest 0.12.9", "serde_json", - "sp-core 31.0.0", - "sp-weights 30.0.0", + "sp-core 32.0.0", + "sp-weights", "strum 0.26.3", "strum_macros 0.26.4", "tempfile", @@ -9522,8 +9296,8 @@ dependencies = [ "serde_json", "strum 0.26.3", "strum_macros 0.26.4", - "subxt 0.37.0", - "subxt-signer 0.37.0", + "subxt", + "subxt-signer", "tar", "tempfile", "thiserror 1.0.69", @@ -9550,8 +9324,8 @@ dependencies = [ "pop-common", "reqwest 0.12.9", "scale-info", - "sp-core 31.0.0", - "sp-weights 30.0.0", + "sp-core 32.0.0", + "sp-weights", "strum 0.26.3", "strum_macros 0.26.4", "tar", @@ -9578,11 +9352,11 @@ dependencies = [ "pop-common", "reqwest 0.12.9", "scale-info", - "scale-value 0.16.3", + "scale-value", "serde_json", "strum 0.26.3", "strum_macros 0.26.4", - "subxt 0.37.0", + "subxt", "symlink", "tar", "tempfile", @@ -9934,22 +9708,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "reconnecting-jsonrpsee-ws-client" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06fa4f17e09edfc3131636082faaec633c7baa269396b4004040bc6c52f49f65" -dependencies = [ - "cfg_aliases", - "finito", - "futures", - "jsonrpsee 0.23.2", - "serde_json", - "thiserror 1.0.69", - "tokio", - "tracing", -] - [[package]] name = "redox_syscall" version = "0.5.7" @@ -10233,7 +9991,7 @@ dependencies = [ "smallvec", "sp-core 34.0.0", "sp-runtime 39.0.2", - "sp-weights 31.0.0", + "sp-weights", "staging-xcm 14.2.0", "staging-xcm-builder", ] @@ -10374,20 +10132,6 @@ dependencies = [ "sct", ] -[[package]] -name = "rustls" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" -dependencies = [ - "log", - "ring", - "rustls-pki-types", - "rustls-webpki 0.102.8", - "subtle", - "zeroize", -] - [[package]] name = "rustls" version = "0.23.19" @@ -10518,17 +10262,6 @@ dependencies = [ "wait-timeout", ] -[[package]] -name = "ruzstd" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58c4eb8a81997cf040a091d1f7e1938aeab6749d3a0dfa73af43cdc32393483d" -dependencies = [ - "byteorder", - "derive_more 0.99.18", - "twox-hash", -] - [[package]] name = "ruzstd" version = "0.6.0" @@ -10709,21 +10442,6 @@ dependencies = [ "smallvec", ] -[[package]] -name = "scale-decode" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e98f3262c250d90e700bb802eb704e1f841e03331c2eb815e46516c4edbf5b27" -dependencies = [ - "derive_more 0.99.18", - "parity-scale-codec", - "primitive-types 0.12.2", - "scale-bits 0.6.0", - "scale-decode-derive 0.13.1", - "scale-type-resolver 0.2.0", - "smallvec", -] - [[package]] name = "scale-decode" version = "0.14.0" @@ -10751,18 +10469,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "scale-decode-derive" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb22f574168103cdd3133b19281639ca65ad985e24612728f727339dcaf4021" -dependencies = [ - "darling 0.14.4", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "scale-decode-derive" version = "0.14.0" @@ -10788,21 +10494,6 @@ dependencies = [ "smallvec", ] -[[package]] -name = "scale-encode" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528464e6ae6c8f98e2b79633bf79ef939552e795e316579dab09c61670d56602" -dependencies = [ - "derive_more 0.99.18", - "parity-scale-codec", - "primitive-types 0.12.2", - "scale-bits 0.6.0", - "scale-encode-derive 0.7.2", - "scale-type-resolver 0.2.0", - "smallvec", -] - [[package]] name = "scale-encode" version = "0.8.0" @@ -10833,9 +10524,9 @@ dependencies = [ [[package]] name = "scale-encode-derive" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef2618f123c88da9cd8853b69d766068f1eddc7692146d7dfe9b89e25ce2efd" +checksum = "102fbc6236de6c53906c0b262f12c7aa69c2bdc604862c12728f5f4d370bc137" dependencies = [ "darling 0.20.10", "proc-macro-crate 3.2.0", @@ -10845,23 +10536,10 @@ dependencies = [ ] [[package]] -name = "scale-encode-derive" -version = "0.8.0" +name = "scale-info" +version = "2.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "102fbc6236de6c53906c0b262f12c7aa69c2bdc604862c12728f5f4d370bc137" -dependencies = [ - "darling 0.20.10", - "proc-macro-crate 3.2.0", - "proc-macro2", - "quote", - "syn 2.0.90", -] - -[[package]] -name = "scale-info" -version = "2.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346a3b32eba2640d17a9cb5927056b08f3de90f65b72fe09402c2ad07d684d0b" +checksum = "346a3b32eba2640d17a9cb5927056b08f3de90f65b72fe09402c2ad07d684d0b" dependencies = [ "bitvec", "cfg-if", @@ -10903,19 +10581,6 @@ dependencies = [ "smallvec", ] -[[package]] -name = "scale-typegen" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "498d1aecf2ea61325d4511787c115791639c0fd21ef4f8e11e49dd09eff2bbac" -dependencies = [ - "proc-macro2", - "quote", - "scale-info", - "syn 2.0.90", - "thiserror 1.0.69", -] - [[package]] name = "scale-typegen" version = "0.9.0" @@ -10929,27 +10594,6 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "scale-value" -version = "0.16.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd6ab090d823e75cfdb258aad5fe92e13f2af7d04b43a55d607d25fcc38c811" -dependencies = [ - "base58", - "blake2", - "derive_more 0.99.18", - "either", - "frame-metadata 15.1.0", - "parity-scale-codec", - "scale-bits 0.6.0", - "scale-decode 0.13.1", - "scale-encode 0.7.2", - "scale-info", - "scale-type-resolver 0.2.0", - "serde", - "yap", -] - [[package]] name = "scale-value" version = "0.17.0" @@ -11364,19 +11008,6 @@ dependencies = [ "serde", ] -[[package]] -name = "sha-1" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", -] - [[package]] name = "sha1" version = "0.10.6" @@ -11586,61 +11217,6 @@ dependencies = [ "futures-lite", ] -[[package]] -name = "smoldot" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d1eaa97d77be4d026a1e7ffad1bb3b78448763b357ea6f8188d3e6f736a9b9" -dependencies = [ - "arrayvec 0.7.6", - "async-lock", - "atomic-take", - "base64 0.21.7", - "bip39", - "blake2-rfc", - "bs58", - "chacha20", - "crossbeam-queue", - "derive_more 0.99.18", - "ed25519-zebra 4.0.3", - "either", - "event-listener 4.0.3", - "fnv", - "futures-lite", - "futures-util", - "hashbrown 0.14.5", - "hex", - "hmac 0.12.1", - "itertools 0.12.1", - "libm", - "libsecp256k1", - "merlin", - "no-std-net", - "nom", - "num-bigint", - "num-rational", - "num-traits", - "pbkdf2", - "pin-project", - "poly1305", - "rand", - "rand_chacha", - "ruzstd 0.5.0", - "schnorrkel", - "serde", - "serde_json", - "sha2 0.10.8", - "sha3", - "siphasher", - "slab", - "smallvec", - "soketto 0.7.1", - "twox-hash", - "wasmi 0.31.2", - "x25519-dalek", - "zeroize", -] - [[package]] name = "smoldot" version = "0.18.0" @@ -11659,7 +11235,7 @@ dependencies = [ "derive_more 0.99.18", "ed25519-zebra 4.0.3", "either", - "event-listener 5.3.1", + "event-listener", "fnv", "futures-lite", "futures-util", @@ -11679,7 +11255,7 @@ dependencies = [ "poly1305", "rand", "rand_chacha", - "ruzstd 0.6.0", + "ruzstd", "schnorrkel", "serde", "serde_json", @@ -11688,49 +11264,13 @@ dependencies = [ "siphasher", "slab", "smallvec", - "soketto 0.8.1", + "soketto", "twox-hash", - "wasmi 0.32.3", + "wasmi", "x25519-dalek", "zeroize", ] -[[package]] -name = "smoldot-light" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5496f2d116b7019a526b1039ec2247dd172b8670633b1a64a614c9ea12c9d8c7" -dependencies = [ - "async-channel", - "async-lock", - "base64 0.21.7", - "blake2-rfc", - "derive_more 0.99.18", - "either", - "event-listener 4.0.3", - "fnv", - "futures-channel", - "futures-lite", - "futures-util", - "hashbrown 0.14.5", - "hex", - "itertools 0.12.1", - "log", - "lru 0.12.5", - "no-std-net", - "parking_lot", - "pin-project", - "rand", - "rand_chacha", - "serde", - "serde_json", - "siphasher", - "slab", - "smol", - "smoldot 0.16.0", - "zeroize", -] - [[package]] name = "smoldot-light" version = "0.16.2" @@ -11744,7 +11284,7 @@ dependencies = [ "bs58", "derive_more 0.99.18", "either", - "event-listener 5.3.1", + "event-listener", "fnv", "futures-channel", "futures-lite", @@ -11763,7 +11303,7 @@ dependencies = [ "siphasher", "slab", "smol", - "smoldot 0.18.0", + "smoldot", "zeroize", ] @@ -11815,7 +11355,7 @@ dependencies = [ "scale-info", "serde", "snowbridge-beacon-primitives", - "sp-arithmetic 26.0.0", + "sp-arithmetic", "sp-core 34.0.0", "sp-io 38.0.0", "sp-runtime 39.0.2", @@ -11981,7 +11521,7 @@ dependencies = [ "serde", "snowbridge-core", "snowbridge-outbound-queue-merkle-tree", - "sp-arithmetic 26.0.0", + "sp-arithmetic", "sp-core 34.0.0", "sp-io 38.0.0", "sp-runtime 39.0.2", @@ -12039,7 +11579,7 @@ dependencies = [ "log", "parity-scale-codec", "snowbridge-core", - "sp-arithmetic 26.0.0", + "sp-arithmetic", "sp-std", "staging-xcm 14.2.0", "staging-xcm-builder", @@ -12101,21 +11641,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "soketto" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d1c5305e39e09653383c2c7244f2f78b3bcae37cf50c64cb4789c9f5096ec2" -dependencies = [ - "base64 0.13.1", - "bytes", - "futures", - "httparse", - "log", - "rand", - "sha-1", -] - [[package]] name = "soketto" version = "0.8.1" @@ -12196,21 +11721,6 @@ dependencies = [ "sp-io 38.0.0", ] -[[package]] -name = "sp-arithmetic" -version = "25.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "910c07fa263b20bf7271fdd4adcb5d3217dfdac14270592e0780223542e7e114" -dependencies = [ - "integer-sqrt", - "num-traits", - "parity-scale-codec", - "scale-info", - "serde", - "sp-std", - "static_assertions", -] - [[package]] name = "sp-arithmetic" version = "26.0.0" @@ -12305,7 +11815,7 @@ dependencies = [ "sp-keystore 0.40.0", "sp-mmr-primitives", "sp-runtime 39.0.2", - "sp-weights 31.0.0", + "sp-weights", "strum 0.26.3", ] @@ -12768,7 +12278,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-arithmetic 26.0.0", + "sp-arithmetic", "sp-core 34.0.0", "sp-runtime 39.0.2", ] @@ -12813,11 +12323,11 @@ dependencies = [ "serde", "simple-mermaid", "sp-application-crypto 34.0.0", - "sp-arithmetic 26.0.0", + "sp-arithmetic", "sp-core 32.0.0", "sp-io 34.0.0", "sp-std", - "sp-weights 31.0.0", + "sp-weights", ] [[package]] @@ -12839,11 +12349,11 @@ dependencies = [ "serde", "simple-mermaid", "sp-application-crypto 38.0.0", - "sp-arithmetic 26.0.0", + "sp-arithmetic", "sp-core 34.0.0", "sp-io 38.0.0", "sp-std", - "sp-weights 31.0.0", + "sp-weights", "tracing", ] @@ -13232,22 +12742,6 @@ dependencies = [ "wasmtime", ] -[[package]] -name = "sp-weights" -version = "30.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9af6c661fe3066b29f9e1d258000f402ff5cc2529a9191972d214e5871d0ba87" -dependencies = [ - "bounded-collections", - "parity-scale-codec", - "scale-info", - "serde", - "smallvec", - "sp-arithmetic 25.0.0", - "sp-debug-derive", - "sp-std", -] - [[package]] name = "sp-weights" version = "31.0.0" @@ -13259,7 +12753,7 @@ dependencies = [ "scale-info", "serde", "smallvec", - "sp-arithmetic 26.0.0", + "sp-arithmetic", "sp-debug-derive", ] @@ -13352,7 +12846,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-weights 31.0.0", + "sp-weights", "xcm-procedural 8.0.0", ] @@ -13372,7 +12866,7 @@ dependencies = [ "scale-info", "serde", "sp-runtime 39.0.2", - "sp-weights 31.0.0", + "sp-weights", "xcm-procedural 10.1.0", ] @@ -13391,10 +12885,10 @@ dependencies = [ "parity-scale-codec", "polkadot-parachain-primitives", "scale-info", - "sp-arithmetic 26.0.0", + "sp-arithmetic", "sp-io 38.0.0", "sp-runtime 39.0.2", - "sp-weights 31.0.0", + "sp-weights", "staging-xcm 14.2.0", "staging-xcm-executor", ] @@ -13411,11 +12905,11 @@ dependencies = [ "impl-trait-for-tuples", "parity-scale-codec", "scale-info", - "sp-arithmetic 26.0.0", + "sp-arithmetic", "sp-core 34.0.0", "sp-io 38.0.0", "sp-runtime 39.0.2", - "sp-weights 31.0.0", + "sp-weights", "staging-xcm 14.2.0", "tracing", ] @@ -13543,42 +13037,6 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" -[[package]] -name = "subxt" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a160cba1edbf3ec4fbbeaea3f1a185f70448116a6bccc8276bb39adb3b3053bd" -dependencies = [ - "async-trait", - "derive-where", - "either", - "frame-metadata 16.0.0", - "futures", - "hex", - "impl-serde 0.4.0", - "instant", - "jsonrpsee 0.22.5", - "parity-scale-codec", - "primitive-types 0.12.2", - "reconnecting-jsonrpsee-ws-client", - "scale-bits 0.6.0", - "scale-decode 0.13.1", - "scale-encode 0.7.2", - "scale-info", - "scale-value 0.16.3", - "serde", - "serde_json", - "sp-crypto-hashing", - "subxt-core 0.37.1", - "subxt-lightclient 0.37.0", - "subxt-macro 0.37.0", - "subxt-metadata 0.37.0", - "thiserror 1.0.69", - "tokio-util", - "tracing", - "url", -] - [[package]] name = "subxt" version = "0.38.0" @@ -13592,7 +13050,7 @@ dependencies = [ "futures", "hex", "impl-serde 0.5.0", - "jsonrpsee 0.24.7", + "jsonrpsee", "parity-scale-codec", "polkadot-sdk", "primitive-types 0.13.1", @@ -13600,13 +13058,13 @@ dependencies = [ "scale-decode 0.14.0", "scale-encode 0.8.0", "scale-info", - "scale-value 0.17.0", + "scale-value", "serde", "serde_json", - "subxt-core 0.38.0", - "subxt-lightclient 0.38.0", - "subxt-macro 0.38.0", - "subxt-metadata 0.38.0", + "subxt-core", + "subxt-lightclient", + "subxt-macro", + "subxt-metadata", "thiserror 1.0.69", "tokio", "tokio-util", @@ -13616,27 +13074,6 @@ dependencies = [ "web-time", ] -[[package]] -name = "subxt-codegen" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d703dca0905cc5272d7cc27a4ac5f37dcaae7671acc7fef0200057cc8c317786" -dependencies = [ - "frame-metadata 16.0.0", - "heck 0.5.0", - "hex", - "jsonrpsee 0.22.5", - "parity-scale-codec", - "proc-macro2", - "quote", - "scale-info", - "scale-typegen 0.8.0", - "subxt-metadata 0.37.0", - "syn 2.0.90", - "thiserror 1.0.69", - "tokio", -] - [[package]] name = "subxt-codegen" version = "0.38.0" @@ -13648,39 +13085,12 @@ dependencies = [ "proc-macro2", "quote", "scale-info", - "scale-typegen 0.9.0", - "subxt-metadata 0.38.0", + "scale-typegen", + "subxt-metadata", "syn 2.0.90", "thiserror 1.0.69", ] -[[package]] -name = "subxt-core" -version = "0.37.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3af3b36405538a36b424d229dc908d1396ceb0994c90825ce928709eac1a159a" -dependencies = [ - "base58", - "blake2", - "derive-where", - "frame-metadata 16.0.0", - "hashbrown 0.14.5", - "hex", - "impl-serde 0.4.0", - "parity-scale-codec", - "primitive-types 0.12.2", - "scale-bits 0.6.0", - "scale-decode 0.13.1", - "scale-encode 0.7.2", - "scale-info", - "scale-value 0.16.3", - "serde", - "serde_json", - "sp-crypto-hashing", - "subxt-metadata 0.37.0", - "tracing", -] - [[package]] name = "subxt-core" version = "0.38.0" @@ -13703,27 +13113,10 @@ dependencies = [ "scale-decode 0.14.0", "scale-encode 0.8.0", "scale-info", - "scale-value 0.17.0", + "scale-value", "serde", "serde_json", - "subxt-metadata 0.38.0", - "tracing", -] - -[[package]] -name = "subxt-lightclient" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d9406fbdb9548c110803cb8afa750f8b911d51eefdf95474b11319591d225d9" -dependencies = [ - "futures", - "futures-util", - "serde", - "serde_json", - "smoldot-light 0.14.0", - "thiserror 1.0.69", - "tokio", - "tokio-stream", + "subxt-metadata", "tracing", ] @@ -13737,28 +13130,13 @@ dependencies = [ "futures-util", "serde", "serde_json", - "smoldot-light 0.16.2", + "smoldot-light", "thiserror 1.0.69", "tokio", "tokio-stream", "tracing", ] -[[package]] -name = "subxt-macro" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c195f803d70687e409aba9be6c87115b5da8952cd83c4d13f2e043239818fcd" -dependencies = [ - "darling 0.20.10", - "parity-scale-codec", - "proc-macro-error", - "quote", - "scale-typegen 0.8.0", - "subxt-codegen 0.37.0", - "syn 2.0.90", -] - [[package]] name = "subxt-macro" version = "0.38.0" @@ -13769,25 +13147,12 @@ dependencies = [ "parity-scale-codec", "proc-macro-error2", "quote", - "scale-typegen 0.9.0", - "subxt-codegen 0.38.0", + "scale-typegen", + "subxt-codegen", "subxt-utils-fetchmetadata", "syn 2.0.90", ] -[[package]] -name = "subxt-metadata" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "738be5890fdeff899bbffff4d9c0f244fe2a952fb861301b937e3aa40ebb55da" -dependencies = [ - "frame-metadata 16.0.0", - "hashbrown 0.14.5", - "parity-scale-codec", - "scale-info", - "sp-crypto-hashing", -] - [[package]] name = "subxt-metadata" version = "0.38.0" @@ -13802,28 +13167,6 @@ dependencies = [ "scale-info", ] -[[package]] -name = "subxt-signer" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f49888ae6ae90fe01b471193528eea5bd4ed52d8eecd2d13f4a2333b87388850" -dependencies = [ - "bip39", - "cfg-if", - "hex", - "hmac 0.12.1", - "parity-scale-codec", - "pbkdf2", - "regex", - "schnorrkel", - "secp256k1 0.28.2", - "secrecy 0.8.0", - "sha2 0.10.8", - "sp-crypto-hashing", - "subxt-core 0.37.1", - "zeroize", -] - [[package]] name = "subxt-signer" version = "0.38.0" @@ -13847,7 +13190,7 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.8", - "subxt-core 0.38.0", + "subxt-core", "zeroize", ] @@ -14244,17 +13587,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-rustls" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" -dependencies = [ - "rustls 0.22.4", - "rustls-pki-types", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.1" @@ -15007,19 +14339,6 @@ dependencies = [ "cxx-build", ] -[[package]] -name = "wasmi" -version = "0.31.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8281d1d660cdf54c76a3efa9ddd0c270cada1383a995db3ccb43d166456c7" -dependencies = [ - "smallvec", - "spin", - "wasmi_arena", - "wasmi_core 0.13.0", - "wasmparser-nostd", -] - [[package]] name = "wasmi" version = "0.32.3" @@ -15033,16 +14352,10 @@ dependencies = [ "smallvec", "spin", "wasmi_collections", - "wasmi_core 0.32.3", + "wasmi_core", "wasmparser-nostd", ] -[[package]] -name = "wasmi_arena" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "104a7f73be44570cac297b3035d76b169d6599637631cf37a1703326a0727073" - [[package]] name = "wasmi_collections" version = "0.32.3" @@ -15054,18 +14367,6 @@ dependencies = [ "string-interner", ] -[[package]] -name = "wasmi_core" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf1a7db34bff95b85c261002720c00c3a6168256dcb93041d3fa2054d19856a" -dependencies = [ - "downcast-rs", - "libm", - "num-traits", - "paste", -] - [[package]] name = "wasmi_core" version = "0.32.3" @@ -15347,7 +14648,7 @@ dependencies = [ "smallvec", "sp-core 34.0.0", "sp-runtime 39.0.2", - "sp-weights 31.0.0", + "sp-weights", "staging-xcm 14.2.0", "staging-xcm-builder", ] @@ -15770,7 +15071,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-api", - "sp-weights 31.0.0", + "sp-weights", "staging-xcm 14.2.0", "staging-xcm-executor", ] @@ -15974,8 +15275,8 @@ dependencies = [ "serde_json", "sha2 0.10.8", "sp-core 31.0.0", - "subxt 0.38.0", - "subxt-signer 0.38.0", + "subxt", + "subxt-signer", "thiserror 1.0.69", "tokio", "tracing", @@ -16037,8 +15338,8 @@ dependencies = [ "async-trait", "futures", "lazy_static", - "subxt 0.38.0", - "subxt-signer 0.38.0", + "subxt", + "subxt-signer", "tokio", "zombienet-configuration", "zombienet-orchestrator", diff --git a/Cargo.toml b/Cargo.toml index c31416f12..86a7a928d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ edition = "2021" documentation = "https://learn.onpop.io/" license = "GPL-3.0" repository = "https://github.com/r0gue-io/pop-cli" +rust-version = "1.81.0" version = "0.5.0" [workspace.dependencies] diff --git a/README.md b/README.md index 7657482d2..bdd46d216 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@

- + [![Twitter URL](https://img.shields.io/twitter/follow/Pop?style=social)](https://x.com/onpopio/) [![Twitter URL](https://img.shields.io/twitter/follow/R0GUE?style=social)](https://twitter.com/gor0gue) [![Telegram](https://img.shields.io/badge/Telegram-gray?logo=telegram)](https://t.me/onpopio) @@ -14,9 +14,6 @@
- - - ## Installation You can install Pop CLI from [crates.io](https://crates.io/crates/pop-cli): @@ -25,6 +22,8 @@ You can install Pop CLI from [crates.io](https://crates.io/crates/pop-cli): cargo install --force --locked pop-cli ``` +> :information_source: Pop CLI requires Rust 1.81 or later. + You can also install Pop CLI using the [Pop CLI GitHub repo](https://github.com/r0gue-io/pop-cli): ```shell @@ -44,6 +43,7 @@ our [telemetry](crates/pop-telemetry/README.md) documentation. ## Documentation On the [Pop Docs website](https://learn.onpop.io) you will find: + * 👉 [Get Started with Pop CLI](https://learn.onpop.io/v/cli) ## Building Pop CLI locally From 093ac7469dbcc2b0da20610de060e0a53369ea28 Mon Sep 17 00:00:00 2001 From: Frank Bell <60948618+evilrobot-01@users.noreply.github.com> Date: Tue, 17 Dec 2024 07:27:33 +0000 Subject: [PATCH 203/211] refactor: ensure short args consistency (#386) --- .../pop-cli/src/commands/build/parachain.rs | 4 ++-- crates/pop-cli/src/commands/build/spec.rs | 18 +++++++------- crates/pop-cli/src/commands/call/chain.rs | 2 +- crates/pop-cli/src/commands/call/contract.rs | 14 +++++------ crates/pop-cli/src/commands/clean.rs | 2 +- crates/pop-cli/src/commands/install/mod.rs | 2 +- crates/pop-cli/src/commands/new/contract.rs | 8 ++----- crates/pop-cli/src/commands/test/contract.rs | 14 ++++------- crates/pop-cli/src/commands/up/contract.rs | 24 +++++++++---------- crates/pop-cli/src/commands/up/parachain.rs | 4 ++-- 10 files changed, 42 insertions(+), 50 deletions(-) diff --git a/crates/pop-cli/src/commands/build/parachain.rs b/crates/pop-cli/src/commands/build/parachain.rs index 67000e62f..13d8fe963 100644 --- a/crates/pop-cli/src/commands/build/parachain.rs +++ b/crates/pop-cli/src/commands/build/parachain.rs @@ -14,13 +14,13 @@ pub struct BuildParachainCommand { #[arg(long)] pub(crate) path: Option, /// The package to be built. - #[arg(short = 'p', long)] + #[arg(short, long)] pub(crate) package: Option, /// Build profile [default: debug]. #[clap(long, value_enum)] pub(crate) profile: Option, /// Parachain ID to be used when generating the chain spec files. - #[arg(short = 'i', long = "id")] + #[arg(short, long)] pub(crate) id: Option, // Deprecation flag, used to specify whether the deprecation warning is shown. #[clap(skip)] diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index 3cde4c61e..7e9ba052b 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -136,38 +136,38 @@ pub(crate) enum RelayChain { pub struct BuildSpecCommand { /// File name for the resulting spec. If a path is given, /// the necessary directories will be created - #[arg(short = 'o', long = "output")] + #[arg(short, long = "output")] pub(crate) output_file: Option, /// [DEPRECATED] and will be removed in v0.7.0, use `profile`. - #[arg(short = 'r', long, conflicts_with = "profile")] + #[arg(short = 'R', long, conflicts_with = "profile")] pub(crate) release: bool, /// Build profile for the binary to generate the chain specification. #[arg(long, value_enum)] pub(crate) profile: Option, /// Parachain ID to be used when generating the chain spec files. - #[arg(short = 'i', long)] + #[arg(short, long)] pub(crate) id: Option, /// Whether to keep localhost as a bootnode. - #[arg(long)] + #[arg(short = 'b', long)] pub(crate) default_bootnode: bool, /// Type of the chain. #[arg(short = 't', long = "type", value_enum)] pub(crate) chain_type: Option, /// Provide the chain specification to use (e.g. dev, local, custom or a path to an existing /// file). - #[arg(short = 'c', long = "chain")] + #[arg(short, long)] pub(crate) chain: Option, /// Relay chain this parachain will connect to. - #[arg(long, value_enum)] + #[arg(short = 'r', long, value_enum)] pub(crate) relay: Option, /// Protocol-id to use in the specification. - #[arg(long = "protocol-id")] + #[arg(short = 'P', long = "protocol-id")] pub(crate) protocol_id: Option, /// Whether the genesis state file should be generated. - #[arg(long = "genesis-state")] + #[arg(short = 'S', long = "genesis-state")] pub(crate) genesis_state: bool, /// Whether the genesis code file should be generated. - #[arg(long = "genesis-code")] + #[arg(short = 'C', long = "genesis-code")] pub(crate) genesis_code: bool, } diff --git a/crates/pop-cli/src/commands/call/chain.rs b/crates/pop-cli/src/commands/call/chain.rs index 9751b7dd9..36b4dbd1e 100644 --- a/crates/pop-cli/src/commands/call/chain.rs +++ b/crates/pop-cli/src/commands/call/chain.rs @@ -47,7 +47,7 @@ pub struct CallChainCommand { #[arg(short = 'S', long)] sudo: bool, /// Automatically signs and submits the extrinsic without prompting for confirmation. - #[arg(short('y'), long)] + #[arg(short = 'y', long)] skip_confirm: bool, } diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 0b2815a85..e83a871d2 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -24,16 +24,16 @@ pub struct CallContractCommand { #[arg(short, long)] path: Option, /// The address of the contract to call. - #[arg(name = "contract", short, long, env = "CONTRACT")] + #[arg(short, long, env = "CONTRACT")] contract: Option, /// The name of the contract message to call. - #[arg(long, short)] + #[arg(short, long)] message: Option, /// The message arguments, encoded as strings. #[arg(short, long, num_args = 0..,)] args: Vec, /// The value to be transferred as part of the call. - #[arg(name = "value", short, long, default_value = DEFAULT_PAYABLE_VALUE)] + #[arg(short, long, default_value = DEFAULT_PAYABLE_VALUE)] value: String, /// Maximum amount of gas to be used for this command. /// If not specified it will perform a dry-run to estimate the gas consumed for the @@ -45,24 +45,24 @@ pub struct CallContractCommand { #[arg(short = 'P', long)] proof_size: Option, /// Websocket endpoint of a node. - #[arg(name = "url", short, long, value_parser, default_value = DEFAULT_URL)] + #[arg(short, long, value_parser, default_value = DEFAULT_URL)] url: url::Url, /// Secret key URI for the account calling the contract. /// /// e.g. /// - for a dev account "//Alice" /// - with a password "//Alice///SECRET_PASSWORD" - #[arg(name = "suri", long, short, default_value = DEFAULT_URI)] + #[arg(short, long, default_value = DEFAULT_URI)] suri: String, /// Submit an extrinsic for on-chain execution. - #[arg(short('x'), long)] + #[arg(short = 'x', long)] execute: bool, /// Perform a dry-run via RPC to estimate the gas usage. This does not submit a transaction. #[arg(short = 'D', long, conflicts_with = "execute")] dry_run: bool, /// Enables developer mode, bypassing certain user prompts for faster testing. /// Recommended for testing and local development only. - #[arg(name = "dev", long, short, default_value = "false")] + #[arg(name = "dev", short, long, default_value = "false")] dev_mode: bool, } impl CallContractCommand { diff --git a/crates/pop-cli/src/commands/clean.rs b/crates/pop-cli/src/commands/clean.rs index a2e219c18..63981fedf 100644 --- a/crates/pop-cli/src/commands/clean.rs +++ b/crates/pop-cli/src/commands/clean.rs @@ -26,7 +26,7 @@ pub(crate) enum Command { #[derive(Args)] pub struct CleanCommandArgs { /// Pass flag to remove all artifacts - #[arg(short = 'a', long)] + #[arg(short, long)] pub(crate) all: bool, } diff --git a/crates/pop-cli/src/commands/install/mod.rs b/crates/pop-cli/src/commands/install/mod.rs index 19b9fd3a2..137a4b117 100644 --- a/crates/pop-cli/src/commands/install/mod.rs +++ b/crates/pop-cli/src/commands/install/mod.rs @@ -53,7 +53,7 @@ pub enum Dependencies { #[command(args_conflicts_with_subcommands = true)] pub(crate) struct InstallArgs { /// Automatically install all dependencies required without prompting for confirmation. - #[clap(short('y'), long)] + #[clap(short = 'y', long)] skip_confirm: bool, } diff --git a/crates/pop-cli/src/commands/new/contract.rs b/crates/pop-cli/src/commands/new/contract.rs index 4c3055966..35a9132c6 100644 --- a/crates/pop-cli/src/commands/new/contract.rs +++ b/crates/pop-cli/src/commands/new/contract.rs @@ -32,17 +32,13 @@ pub struct NewContractCommand { /// The type of contract. #[arg( default_value = ContractType::Examples.as_ref(), - short = 'c', + short, long, value_parser = enum_variants!(ContractType) )] pub(crate) contract_type: Option, /// The template to use. - #[arg( - short = 't', - long, - value_parser = enum_variants!(Contract) - )] + #[arg(short, long, value_parser = enum_variants!(Contract))] pub(crate) template: Option, } diff --git a/crates/pop-cli/src/commands/test/contract.rs b/crates/pop-cli/src/commands/test/contract.rs index 7c5edfa74..190d9ac64 100644 --- a/crates/pop-cli/src/commands/test/contract.rs +++ b/crates/pop-cli/src/commands/test/contract.rs @@ -13,22 +13,18 @@ use {std::time::Duration, tokio::time::sleep}; #[derive(Args)] pub(crate) struct TestContractCommand { - #[arg(short = 'p', long, help = "Path for the contract project [default: current directory]")] + #[arg(short, long, help = "Path for the contract project [default: current directory]")] path: Option, /// [DEPRECATED] Run e2e tests - #[arg(short = 'f', long = "features", value_parser=["e2e-tests"])] + #[arg(short, long, value_parser=["e2e-tests"])] features: Option, /// Run end-to-end tests - #[arg(short = 'e', long = "e2e")] + #[arg(short, long)] e2e: bool, - #[arg( - short = 'n', - long = "node", - help = "Path to the contracts node to run e2e tests [default: none]" - )] + #[arg(short, long, help = "Path to the contracts node to run e2e tests [default: none]")] node: Option, /// Automatically source the needed binary required without prompting for confirmation. - #[clap(short('y'), long)] + #[clap(short = 'y', long)] skip_confirm: bool, } diff --git a/crates/pop-cli/src/commands/up/contract.rs b/crates/pop-cli/src/commands/up/contract.rs index 964237642..40e158032 100644 --- a/crates/pop-cli/src/commands/up/contract.rs +++ b/crates/pop-cli/src/commands/up/contract.rs @@ -30,49 +30,49 @@ const FAILED: &str = "🚫 Deployment failed."; #[derive(Args, Clone)] pub struct UpContractCommand { /// Path to the contract build directory. - #[arg(short = 'p', long)] + #[arg(short, long)] path: Option, /// The name of the contract constructor to call. - #[clap(name = "constructor", long, default_value = "new")] + #[clap(short, long, default_value = "new")] constructor: String, /// The constructor arguments, encoded as strings. - #[clap(long, num_args = 0..,)] + #[clap(short, long, num_args = 0..,)] args: Vec, /// Transfers an initial balance to the instantiated contract. - #[clap(name = "value", long, default_value = "0")] + #[clap(short, long, default_value = "0")] value: String, /// Maximum amount of gas to be used for this command. /// If not specified it will perform a dry-run to estimate the gas consumed for the /// instantiation. - #[clap(name = "gas", long)] + #[clap(name = "gas", short, long)] gas_limit: Option, /// Maximum proof size for the instantiation. /// If not specified it will perform a dry-run to estimate the proof size required. - #[clap(long)] + #[clap(short = 'P', long)] proof_size: Option, /// A salt used in the address derivation of the new contract. Use to create multiple /// instances of the same contract code from the same account. - #[clap(long, value_parser = parse_hex_bytes)] + #[clap(short = 'S', long, value_parser = parse_hex_bytes)] salt: Option, /// Websocket endpoint of a chain. - #[clap(name = "url", long, value_parser, default_value = DEFAULT_URL)] + #[clap(short, long, value_parser, default_value = DEFAULT_URL)] url: Url, /// Secret key URI for the account deploying the contract. /// /// e.g. /// - for a dev account "//Alice" /// - with a password "//Alice///SECRET_PASSWORD" - #[clap(name = "suri", long, short, default_value = "//Alice")] + #[clap(short, long, default_value = "//Alice")] suri: String, /// Perform a dry-run via RPC to estimate the gas usage. This does not submit a transaction. - #[clap(long)] + #[clap(short = 'D', long)] dry_run: bool, /// Uploads the contract only, without instantiation. - #[clap(short('u'), long)] + #[clap(short = 'U', long)] upload_only: bool, /// Automatically source or update the needed binary required without prompting for /// confirmation. - #[clap(short('y'), long)] + #[clap(short = 'y', long)] skip_confirm: bool, } diff --git a/crates/pop-cli/src/commands/up/parachain.rs b/crates/pop-cli/src/commands/up/parachain.rs index 2a8da10af..4d59646d0 100644 --- a/crates/pop-cli/src/commands/up/parachain.rs +++ b/crates/pop-cli/src/commands/up/parachain.rs @@ -40,13 +40,13 @@ pub(crate) struct ZombienetCommand { #[arg(short, long)] parachain: Option>, /// The command to run after the network has been launched. - #[clap(name = "cmd", short = 'c', long)] + #[clap(name = "cmd", short, long)] command: Option, /// Whether the output should be verbose. #[arg(short, long, action)] verbose: bool, /// Automatically source all needed binaries required without prompting for confirmation. - #[clap(short('y'), long)] + #[clap(short = 'y', long)] skip_confirm: bool, } From e1bfbc85adf33cb99a6babbf9bceba8517bd72e5 Mon Sep 17 00:00:00 2001 From: Frank Bell <60948618+evilrobot-01@users.noreply.github.com> Date: Tue, 17 Dec 2024 07:28:15 +0000 Subject: [PATCH 204/211] feat(call-parachain): add remark action (#387) * feat(call-parachain): add remark action * test(call-parachain): simplify tests * refactor: improve language * refactor: improve language --- .../src/call/metadata/action.rs | 90 ++++++++++--------- 1 file changed, 47 insertions(+), 43 deletions(-) diff --git a/crates/pop-parachains/src/call/metadata/action.rs b/crates/pop-parachains/src/call/metadata/action.rs index cd47e1279..1e8298fc7 100644 --- a/crates/pop-parachains/src/call/metadata/action.rs +++ b/crates/pop-parachains/src/call/metadata/action.rs @@ -43,19 +43,19 @@ pub enum Action { props(Pallet = "Assets") )] MintAsset, - /// Create an NFT collection. + /// Create a NFT collection. #[strum( serialize = "create_nft", message = "create", - detailed_message = "Create an NFT collection", + detailed_message = "Create a NFT collection", props(Pallet = "Nfts") )] CreateCollection, - /// Mint an NFT. + /// Mint a NFT. #[strum( serialize = "mint_nft", message = "mint", - detailed_message = "Mint an NFT", + detailed_message = "Mint a NFT", props(Pallet = "Nfts") )] MintNFT, @@ -83,6 +83,14 @@ pub enum Action { props(Pallet = "Registrar") )] Register, + /// Make a remark. + #[strum( + serialize = "remark", + message = "remark_with_event", + detailed_message = "Make a remark", + props(Pallet = "System") + )] + Remark, } impl Action { @@ -119,7 +127,7 @@ pub fn supported_actions(pallets: &[Pallet]) -> Vec { #[cfg(test)] mod tests { - use super::*; + use super::{Action::*, *}; use crate::{call::tests::POP_NETWORK_TESTNET_URL, parse_chain_metadata, set_up_client}; use anyhow::Result; use std::collections::HashMap; @@ -129,14 +137,15 @@ mod tests { #[test] fn action_descriptions_are_correct() { let descriptions = HashMap::from([ - (Action::CreateAsset, "Create an asset"), - (Action::MintAsset, "Mint an asset"), - (Action::CreateCollection, "Create an NFT collection"), - (Action::MintNFT, "Mint an NFT"), - (Action::PurchaseOnDemandCoretime, "Purchase on-demand coretime"), - (Action::Transfer, "Transfer balance"), - (Action::Register, "Register a parachain ID with genesis state and code"), - (Action::Reserve, "Reserve a parachain ID"), + (CreateAsset, "Create an asset"), + (MintAsset, "Mint an asset"), + (CreateCollection, "Create a NFT collection"), + (MintNFT, "Mint a NFT"), + (PurchaseOnDemandCoretime, "Purchase on-demand coretime"), + (Transfer, "Transfer balance"), + (Register, "Register a parachain ID with genesis state and code"), + (Reserve, "Reserve a parachain ID"), + (Remark, "Make a remark"), ]); for action in Action::VARIANTS.iter() { @@ -147,14 +156,15 @@ mod tests { #[test] fn pallet_names_are_correct() { let pallets = HashMap::from([ - (Action::CreateAsset, "Assets"), - (Action::MintAsset, "Assets"), - (Action::CreateCollection, "Nfts"), - (Action::MintNFT, "Nfts"), - (Action::PurchaseOnDemandCoretime, "OnDemand"), - (Action::Transfer, "Balances"), - (Action::Register, "Registrar"), - (Action::Reserve, "Registrar"), + (CreateAsset, "Assets"), + (MintAsset, "Assets"), + (CreateCollection, "Nfts"), + (MintNFT, "Nfts"), + (PurchaseOnDemandCoretime, "OnDemand"), + (Transfer, "Balances"), + (Register, "Registrar"), + (Reserve, "Registrar"), + (Remark, "System"), ]); for action in Action::VARIANTS.iter() { @@ -165,14 +175,15 @@ mod tests { #[test] fn function_names_are_correct() { let pallets = HashMap::from([ - (Action::CreateAsset, "create"), - (Action::MintAsset, "mint"), - (Action::CreateCollection, "create"), - (Action::MintNFT, "mint"), - (Action::PurchaseOnDemandCoretime, "place_order_allow_death"), - (Action::Transfer, "transfer_allow_death"), - (Action::Register, "register"), - (Action::Reserve, "reserve"), + (CreateAsset, "create"), + (MintAsset, "mint"), + (CreateCollection, "create"), + (MintNFT, "mint"), + (PurchaseOnDemandCoretime, "place_order_allow_death"), + (Transfer, "transfer_allow_death"), + (Register, "register"), + (Reserve, "reserve"), + (Remark, "remark_with_event"), ]); for action in Action::VARIANTS.iter() { @@ -185,23 +196,16 @@ mod tests { // Test Pop Parachain. let mut client: subxt::OnlineClient = set_up_client(POP_NETWORK_TESTNET_URL).await?; - let mut actions = supported_actions(&parse_chain_metadata(&client)?); - assert_eq!(actions.len(), 5); - assert_eq!(actions[0], Action::Transfer); - assert_eq!(actions[1], Action::CreateAsset); - assert_eq!(actions[2], Action::MintAsset); - assert_eq!(actions[3], Action::CreateCollection); - assert_eq!(actions[4], Action::MintNFT); + let actions = supported_actions(&parse_chain_metadata(&client)?); + assert_eq!( + actions, + vec![Transfer, CreateAsset, MintAsset, CreateCollection, MintNFT, Remark] + ); // Test Polkadot Relay Chain. client = set_up_client(POLKADOT_NETWORK_URL).await?; - actions = supported_actions(&parse_chain_metadata(&client)?); - assert_eq!(actions.len(), 4); - assert_eq!(actions[0], Action::Transfer); - assert_eq!(actions[1], Action::PurchaseOnDemandCoretime); - assert_eq!(actions[2], Action::Reserve); - assert_eq!(actions[3], Action::Register); - + let actions = supported_actions(&parse_chain_metadata(&client)?); + assert_eq!(actions, vec![Transfer, PurchaseOnDemandCoretime, Reserve, Register, Remark]); Ok(()) } } From 22a74b3f790e3f73ef23eb4c7892ba062d746547 Mon Sep 17 00:00:00 2001 From: Peter White Date: Wed, 18 Dec 2024 19:42:38 -0700 Subject: [PATCH 205/211] feat: wallet integration (#371) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(wallet-integration): server and API (#362) * feat(wallet-integration): implement server and API * feat(wallet-integration): add a few tests to server. Needs more * refactor(wallet-integration): just use call_data, remove data types * feat(wallet-integration): add frontend type for flexible serving * feat(wallet-integration): StateHandler in WalletIntegrationManager. Add terminate method * refactor(wallet-integration): move to module with pop-cli crate * feat(wallet-integration): server-side error handling, update port to 9090 * refactor(wallet-integration): restructure server for easier use in thread * fix(wallet-integration): remove invalid fn `finish` * docs(wallet-integration): inline comments * test(wallet-integration): unit tests for wallet-integration server. Add new frontend type * feat(wallet-integration): two new routes: error and terminate * refactor(wallet-integration): consistent comments, remove unnecessary code in tests * feat: add TransactionData::new * refactor(wallet-integration: shutdown channel error handling, terminate helper, take_error and TranscationData::new tests * chore: clippy warning * feat(wallet integration): pop up contract (#365) * feat(wallet-integration): server and API (#362) * feat(wallet-integration): implement server and API * feat(wallet-integration): add a few tests to server. Needs more * refactor(wallet-integration): just use call_data, remove data types * feat(wallet-integration): add frontend type for flexible serving * feat(wallet-integration): StateHandler in WalletIntegrationManager. Add terminate method * refactor(wallet-integration): move to module with pop-cli crate * feat(wallet-integration): server-side error handling, update port to 9090 * refactor(wallet-integration): restructure server for easier use in thread * fix(wallet-integration): remove invalid fn `finish` * docs(wallet-integration): inline comments * test(wallet-integration): unit tests for wallet-integration server. Add new frontend type * feat(wallet-integration): two new routes: error and terminate * refactor(wallet-integration): consistent comments, remove unnecessary code in tests * feat: add TransactionData::new * refactor(wallet-integration: shutdown channel error handling, terminate helper, take_error and TranscationData::new tests * chore: clippy warning * feat(up-contract): get call data for upload-only and pass to wallet integration -- PoC * feat(up-contract): get call data for instantiate and pass to wallet integration -- PoC * feat(up-contract): run server and pass call_data for signing -- PoC * feat(up-contract): submit signed payload to node * fix(up-contract): update to FrontendFromDir type * refactor(pop-up): create functions for wallet integration, plus general cleanup * chore: use git branch for cargo contract * feat(pop-up-contract): better error handling and prompt displays * chore(up-contract): outdated field name and clippy allow * feat(up-contract): handle subxt events better, various improvements * feat(up-contract): custom errors in contracts crate * fix: minor fixes after rebase * fix(up-contract): test compilation after rebase * feat(up-contract): suri and use-wallet can't be used together * refactor: wait for finalization * feat(up-contract): serve HTML from string, fix deployment message on error, cors, and other misc. improvements (#380) * docs: add function comments * feat: wallet integration pop call chain (#379) * feat(wallet-integration): server and API (#362) * feat(wallet-integration): implement server and API * feat(wallet-integration): add a few tests to server. Needs more * refactor(wallet-integration): just use call_data, remove data types * feat(wallet-integration): add frontend type for flexible serving * feat(wallet-integration): StateHandler in WalletIntegrationManager. Add terminate method * refactor(wallet-integration): move to module with pop-cli crate * feat(wallet-integration): server-side error handling, update port to 9090 * refactor(wallet-integration): restructure server for easier use in thread * fix(wallet-integration): remove invalid fn `finish` * docs(wallet-integration): inline comments * test(wallet-integration): unit tests for wallet-integration server. Add new frontend type * feat(wallet-integration): two new routes: error and terminate * refactor(wallet-integration): consistent comments, remove unnecessary code in tests * feat: add TransactionData::new * refactor(wallet-integration: shutdown channel error handling, terminate helper, take_error and TranscationData::new tests * chore: clippy warning * feat(up-contract): get call data for upload-only and pass to wallet integration -- PoC * feat(up-contract): get call data for instantiate and pass to wallet integration -- PoC * feat(up-contract): run server and pass call_data for signing -- PoC * feat(up-contract): submit signed payload to node * fix(up-contract): update to FrontendFromDir type * refactor(pop-up): create functions for wallet integration, plus general cleanup * chore: use git branch for cargo contract * feat(pop-up-contract): better error handling and prompt displays * chore(up-contract): outdated field name and clippy allow * feat(up-contract): handle subxt events better, various improvements * feat(up-contract): custom errors in contracts crate * fix: minor fixes after rebase * fix(up-contract): test compilation after rebase * feat(up-contract): suri and use-wallet can't be used together * refactor: wait for finalization * feat(up-contract): serve HTML from string, fix deployment message on error, cors, and other misc. improvements (#380) * feat(wallet-integration): server and API (#362) * feat(wallet-integration): implement server and API * feat(wallet-integration): add a few tests to server. Needs more * refactor(wallet-integration): just use call_data, remove data types * feat(wallet-integration): add frontend type for flexible serving * feat(wallet-integration): StateHandler in WalletIntegrationManager. Add terminate method * refactor(wallet-integration): move to module with pop-cli crate * feat(wallet-integration): server-side error handling, update port to 9090 * refactor(wallet-integration): restructure server for easier use in thread * fix(wallet-integration): remove invalid fn `finish` * docs(wallet-integration): inline comments * test(wallet-integration): unit tests for wallet-integration server. Add new frontend type * feat(wallet-integration): two new routes: error and terminate * refactor(wallet-integration): consistent comments, remove unnecessary code in tests * feat: add TransactionData::new * refactor(wallet-integration: shutdown channel error handling, terminate helper, take_error and TranscationData::new tests * chore: clippy warning * feat(up-contract): get call data for upload-only and pass to wallet integration -- PoC * refactor(pop-up): create functions for wallet integration, plus general cleanup * chore: use git branch for cargo contract * feat(up-contract): handle subxt events better, various improvements * feat(wallet-integration): implement server and API * feat(wallet-integration): add a few tests to server. Needs more * refactor(wallet-integration): just use call_data, remove data types * feat(wallet-integration): add frontend type for flexible serving * feat(wallet-integration): StateHandler in WalletIntegrationManager. Add terminate method * refactor(wallet-integration): move to module with pop-cli crate * feat(wallet-integration): server-side error handling, update port to 9090 * refactor(wallet-integration): restructure server for easier use in thread * fix(wallet-integration): remove invalid fn `finish` * docs(wallet-integration): inline comments * test(wallet-integration): unit tests for wallet-integration server. Add new frontend type * feat(wallet-integration): two new routes: error and terminate * refactor(wallet-integration): consistent comments, remove unnecessary code in tests * feat: guide user to call a contract (#306) * feat: guide user for calling a contract * feat: get metadata contract from the contract path * refactor: refactor test and validate address input * fix: apply feedback * feat: prompt to have another call and skip questions for queries * refactor: use Cli module instead of cliclack * test: unit test pop-cli crate * test: unit contracts crate * chore: format * test: refactor and improve test cases * fix: fix todos and refactor * test: fix unit test * feat: parse types of parameters and display it to the user in the placeholder * refactor: error handling for pop call * refactor: display call to be executed after guide and reorder * refactor: when repeat call use same contract values and dont clean screen * test: add dry-run test * test: refactor and add more test coverage * test: more coverage * fix: unit test * feat: dev mode to skip certain user prompts * refactor: test functions, renaming and fix clippy * refactor: improve devex of pop call contract * test: adjust tests to refactor * chore: reset_for_new_call fields * fix: build contract if has not been built * refactor: use command state (#338) Merged set_up_call_config and guide_user_to_call_contract into a single function. Also adds short symbols for arguments. * fix: parse user inputs for Option arguments (#332) * fix: automatically add some or none to Option argument * test: refactor and tests * refactor: improve code and comments * fix: renaming and clean code * chore: option params not mandatory * fix: parse user inputs for Option arguments in constructor (#335) * fix: automatically add some or none to Option argument * fix: tests * refactor: process_function_args * test: update tests accordingly last changes * fix: issue with delimiter * test: fix unit test * refactor: renaming and fix comments * refactor: format types (#339) Shows the full type representation, making it easier to see the entry format of parameter values. * fix: logo doesn't show in README --------- Co-authored-by: Frank Bell <60948618+evilrobot-01@users.noreply.github.com> Co-authored-by: Alejandro Martinez Andres <11448715+al3mart@users.noreply.github.com> * test: fix unit test * feat: allow users to specify custom contract metadata files (#347) * chore: allow the user specify the metadata file to call a contract * test: unit test to parse metadata from a file * docs: fix docs * refactor: ensure_contract_built after user input path * fix: call contract when metadata file * fix: remove default_input in contract address * docs: rename metadata with artifact * fix: panic at has_contract_been_built * fix: clippy * refactor: keep ensure_contract_built as a CallContractCommand function * fix: ensure_contract_built * docs: improve comments * fix: feedback and include wasm file for testing * fix: clippy * chore: after build contract prompt the user if the contract is already deployed * refactor: ensure_contract_built * refactor: has_contract_been_built function * docs: fix comments and messages * refactor: get_messages and get_constructors * test: fix unit tests call ui --------- Co-authored-by: Frank Bell <60948618+evilrobot-01@users.noreply.github.com> Co-authored-by: Alejandro Martinez Andres <11448715+al3mart@users.noreply.github.com> * fix: support new substrate-contracts-node structure and stabilize integration tests (#360) * fix: parse new structure substrate-contracts-node * fix: paseo+coretime integration test * fix: sourcing latest version substrate-contracts-node * refactor: set_executable_permission function * fix: clippy * chore: CI configuration * test: specify port in run_contracts_node * fix: use random ports instead of hardcoded ones * feat: guide user to call a parachain (#316) * feat: guide user for calling a contract * feat: get metadata contract from the contract path * refactor: refactor test and validate address input * fix: apply feedback * feat: prompt to have another call and skip questions for queries * refactor: use Cli module instead of cliclack * test: unit test pop-cli crate * test: unit contracts crate * chore: format * test: refactor and improve test cases * fix: fix todos and refactor * test: fix unit test * feat: parse types of parameters and display it to the user in the placeholder * refactor: error handling for pop call * refactor: display call to be executed after guide and reorder * refactor: when repeat call use same contract values and dont clean screen * test: add dry-run test * test: refactor and add more test coverage * test: more coverage * fix: unit test * feat: dev mode to skip certain user prompts * refactor: test functions, renaming and fix clippy * refactor: improve devex of pop call contract * test: adjust tests to refactor * chore: reset_for_new_call fields * fix: build contract if has not been built * refactor: use command state (#338) Merged set_up_call_config and guide_user_to_call_contract into a single function. Also adds short symbols for arguments. * fix: automatically add some or none to Option argument * test: refactor and tests * refactor: improve code and comments * fix: renaming and clean code * chore: option params not mandatory * fix: parse user inputs for Option arguments in constructor (#335) * fix: automatically add some or none to Option argument * fix: tests * refactor: process_function_args * test: update tests accordingly last changes * fix: issue with delimiter * test: fix unit test * refactor: renaming and fix comments * refactor: format types (#339) Shows the full type representation, making it easier to see the entry format of parameter values. * fix: logo doesn't show in README * feat: pop call parachain prototype * feat: dispaly arguments of extrinsic * refactor: structure similar to pop call contract * feat: parse all values for extrinsic/storage * refactor: signer in common * refactor: improve messages * feat: call parachain ui * fix: calls working * refactor: remove unused code * refactor: remove unused code * refactor: various fixes * refactor: various fixes * feat: add option to include params from command line * refactor: clean docs and refactor code * fix: tests * refactor: parse all the metadata again * refactor: reorganize and clean metadata functions * feat: display specific use cases to the user * refactor: predefined actions * fix: various fixes * fix: error message not supported for complex types * refactor: parse all metadata, including parameters at once * refactor: clean docs and move code * fix: format_type * fix: parse user inputs for Option arguments (#332) * fix: automatically add some or none to Option argument * test: refactor and tests * refactor: improve code and comments * fix: renaming and clean code * chore: option params not mandatory * fix: parse user inputs for Option arguments in constructor (#335) * fix: automatically add some or none to Option argument * fix: tests * refactor: process_function_args * test: update tests accordingly last changes * fix: issue with delimiter * test: fix unit test * refactor: renaming and fix comments * refactor: format types (#339) Shows the full type representation, making it easier to see the entry format of parameter values. * fix: logo doesn't show in README --------- Co-authored-by: Frank Bell <60948618+evilrobot-01@users.noreply.github.com> Co-authored-by: Alejandro Martinez Andres <11448715+al3mart@users.noreply.github.com> * test: fix unit test * refactor: clean the way to parse and prompt parameters * feat: add Purchase on-demand coretime use cases * test: add skip_confirm, move when prompt for the signer and create the integration test * test: call parachain ui unit test * refactor: separate structs * fmt * test: pop-cli unit testing * test: pop-common unit tests * test: parse metadata unit tests * test: refactor and test processing parameters * test: comments and unit test in call functions * fix: clippy warnings * chore: fmt * fix: solve conflicts and unit tests (#359) * test: call parachain ui unit test * test: pop-cli unit testing * test: pop-common unit tests * test: parse metadata unit tests * test: refactor and test processing parameters * test: comments and unit test in call functions * fix: clippy warnings * chore: fmt * fix: conflicts and unit tests * test: remove test and improve test * feat: guide user for calling a contract * feat: get metadata contract from the contract path * refactor: refactor test and validate address input * fix: apply feedback * feat: prompt to have another call and skip questions for queries * refactor: use Cli module instead of cliclack * test: unit test pop-cli crate * test: unit contracts crate * chore: format * test: refactor and improve test cases * fix: fix todos and refactor * test: fix unit test * feat: parse types of parameters and display it to the user in the placeholder * refactor: error handling for pop call * refactor: display call to be executed after guide and reorder * refactor: when repeat call use same contract values and dont clean screen * test: add dry-run test * test: refactor and add more test coverage * test: more coverage * fix: unit test * feat: dev mode to skip certain user prompts * refactor: test functions, renaming and fix clippy * refactor: improve devex of pop call contract * test: adjust tests to refactor * chore: reset_for_new_call fields * fix: build contract if has not been built * refactor: use command state (#338) Merged set_up_call_config and guide_user_to_call_contract into a single function. Also adds short symbols for arguments. * fix: automatically add some or none to Option argument * test: refactor and tests * refactor: improve code and comments * fix: renaming and clean code * chore: option params not mandatory * fix: parse user inputs for Option arguments in constructor (#335) * fix: automatically add some or none to Option argument * fix: tests * refactor: process_function_args * test: update tests accordingly last changes * fix: issue with delimiter * test: fix unit test * refactor: renaming and fix comments * refactor: format types (#339) Shows the full type representation, making it easier to see the entry format of parameter values. * feat: pop call parachain prototype * feat: dispaly arguments of extrinsic * refactor: structure similar to pop call contract * feat: parse all values for extrinsic/storage * refactor: signer in common * refactor: improve messages * feat: call parachain ui * fix: calls working * refactor: remove unused code * refactor: remove unused code * refactor: various fixes * refactor: various fixes * feat: add option to include params from command line * refactor: clean docs and refactor code * fix: tests * refactor: parse all the metadata again * refactor: reorganize and clean metadata functions * feat: display specific use cases to the user * refactor: predefined actions * fix: various fixes * fix: error message not supported for complex types * refactor: parse all metadata, including parameters at once * refactor: clean docs and move code * fix: format_type * test: fix unit test * refactor: clean the way to parse and prompt parameters * feat: add Purchase on-demand coretime use cases * test: add skip_confirm, move when prompt for the signer and create the integration test * test: call parachain ui unit test * test: pop-cli unit testing * test: pop-common unit tests * test: parse metadata unit tests * test: refactor and test processing parameters * test: comments and unit test in call functions * fix: clippy warnings * chore: fmt * feat: repeat call only if using guide UI * fix: clippy * refactor: various improvements * chore: parser for pallet and extrinsic input names * refactor: only move to pop_common the needed functions * refactor: improve test, docs and errors * test: fix unit tests * fix: reset_for_new_call when extrinisc is not supported * fix: build with parachain features * test: wait before call parachain in integration test * docs: minor improvements * test: migrate find_free_port to pop_common * test: fix increase waiting time * test: remove unnecesary test case * refactor: rename api with client * refactor: naming and docs * docs: improve docs and missing comments * test: remove unnecesary verbose * test: find_free_port * docs: improve parameter documentation * test: add missing test to sign_and_submit_extrinsic * fix: apply feedback from auxiliar PRs, remove unnecesary clones * docs: public modules * refactor: clean unused params * fix: mark all extrinsics that uses calls as parameter as unsupported * test: fix expect_select * docs: improve documentation * feat: submit extrinsic from call_data (#348) * feat: submit extrinsic from call_data * test: unit test for initialize_api_client * test: unit test for send_extrinsic_from_call_data * fix: CallData struct * fix: skip_confirm for send_extrinsic_from_call_data * chore: clippy * chore: fmt * refactor: minor doc and naming changes * refactor: remove unnecesary clones and return early when submit_extrinsic_from_call_data * chore: fmt * refactor: split decode_call_data logic outside sign_and_submit_extrinsic_with_call_data * feat: parse files when the argument values are very big (#363) * feat: parse files when the argument values are very big * test: unit test * chore: fmt * feat: file logic using the command line * fix: sequence arguments * test: fix unit test * refactor: remove prompting the user if input is file or value * refactor: parse_extrinsic_arguments * fix: CI deny * refactor: reorder Param derive macros * test: fix decode_call_data_works unit test * refactor: use Default derive macro and define constants for test values (#366) * feat: parse files when the argument values are very big * chore: fmt * feat: file logic using the command line * fix: sequence arguments * refactor: parse_extrinsic_arguments * refactor: use Default in pop_parachain structs * refactor: use Default in CallParachainCommand struct * refactor: use constant in tests * chore: fmt and small refactor * feat: flag sudo to wrap extrinsic (#349) * feat: submit extrinsic from call_data * test: unit test for initialize_api_client * feat: wrap call into a sudo call * test: add unit test to the new logic * fix: skip_confirm for send_extrinsic_from_call_data * chore: clippy * docs: renaming and improve docs * test: use force_transfer for testing * fix: check if sudo exist before prompt the user * chore: fmt * chore: fmt * test: fix wrong assert * docs: improve comments and output messages * refactor: split decode_call_data logic outside sign_and_submit_extrinsic_with_call_data * fix: test construct_sudo_extrinsic_works and formatting * refactor: various fixes and improvements (#367) * refactor: sort pallets/dispatchables * refactor: remove unnecessary async * fix: resolve issue after rebase * fix: more async issues after rebase * refactor: use single constant * refactor: terminology (#368) * refactor: terminology * refactor: simply pallet/function relationship * fix: amend call_data conflicts after refactor * refactor: improvements (#370) * fix: add missing short arg option * refactor: note that extrinsic wait includes finalization * refactor: remove clones * style: formatting * refactor: make file prompt more generic * refactor: add missing license headers * style: formatting * docs: comments * docs: comments * docs: comments * refactor: reuse existing metadata * refactor: minimise clones * docs: comments * refactor: naming * docs: fix parameter doc comments * refactor: address clippy warnings * refactor: rename parachain with chain as the primary command and retain parachain as an alias (#373) * refactor: rename parachain with chain in visible messages * refactor: rename parachain with chain internal code * chore: solve fmt after rebase * refactor: small fix, use alias instead aliases * refactor: rename CallParachain struct into Call --------- Co-authored-by: Frank Bell <60948618+evilrobot-01@users.noreply.github.com> Co-authored-by: Alejandro Martinez Andres <11448715+al3mart@users.noreply.github.com> Co-authored-by: Daanvdplas * feat(wallet-integration): server and API (#362) * feat(wallet-integration): implement server and API * feat(wallet-integration): add a few tests to server. Needs more * refactor(wallet-integration): just use call_data, remove data types * feat(wallet-integration): add frontend type for flexible serving * feat(wallet-integration): StateHandler in WalletIntegrationManager. Add terminate method * refactor(wallet-integration): move to module with pop-cli crate * feat(wallet-integration): server-side error handling, update port to 9090 * refactor(wallet-integration): restructure server for easier use in thread * fix(wallet-integration): remove invalid fn `finish` * docs(wallet-integration): inline comments * test(wallet-integration): unit tests for wallet-integration server. Add new frontend type * feat(wallet-integration): two new routes: error and terminate * refactor(wallet-integration): consistent comments, remove unnecessary code in tests * feat: add TransactionData::new * refactor(wallet-integration: shutdown channel error handling, terminate helper, take_error and TranscationData::new tests * chore: clippy warning * feat(up-contract): get call data for upload-only and pass to wallet integration -- PoC * chore: use git branch for cargo contract * feat(up-contract): handle subxt events better, various improvements * refactor: reuse pop up logic * chore: rebase fixes * refactor: clean after rebase * fix: cargo.lock after rebase * refactor: move terminate node into common::contracts * fix: sp-core not only for contracts * feat(up-contract): serve HTML from string, fix deployment message on error, cors, and other misc. improvements * fix: rebase * feat: secure signing logic for pop call chain * test: unit tests in pop-cli * feat: secure signing for call from call_data * refactor: docs and remove unneded functions * refactor: replace hex library * fix: rebase issues * fix: rebase * refactor: small changes * refactor: renaming * feat: wallet integration call contract (#378) * feat(wallet-integration): server and API (#362) * feat(wallet-integration): implement server and API * feat(wallet-integration): add a few tests to server. Needs more * refactor(wallet-integration): just use call_data, remove data types * feat(wallet-integration): add frontend type for flexible serving * feat(wallet-integration): StateHandler in WalletIntegrationManager. Add terminate method * refactor(wallet-integration): move to module with pop-cli crate * feat(wallet-integration): server-side error handling, update port to 9090 * refactor(wallet-integration): restructure server for easier use in thread * fix(wallet-integration): remove invalid fn `finish` * docs(wallet-integration): inline comments * test(wallet-integration): unit tests for wallet-integration server. Add new frontend type * feat(wallet-integration): two new routes: error and terminate * refactor(wallet-integration): consistent comments, remove unnecessary code in tests * feat: add TransactionData::new * refactor(wallet-integration: shutdown channel error handling, terminate helper, take_error and TranscationData::new tests * chore: clippy warning * feat(up-contract): get call data for upload-only and pass to wallet integration -- PoC * feat(up-contract): get call data for instantiate and pass to wallet integration -- PoC * feat(up-contract): run server and pass call_data for signing -- PoC * feat(up-contract): submit signed payload to node * fix(up-contract): update to FrontendFromDir type * refactor(pop-up): create functions for wallet integration, plus general cleanup * chore: use git branch for cargo contract * feat(pop-up-contract): better error handling and prompt displays * chore(up-contract): outdated field name and clippy allow * feat(up-contract): handle subxt events better, various improvements * feat(up-contract): custom errors in contracts crate * feat(wallet-integration): implement server and API * feat(wallet-integration): add a few tests to server. Needs more * refactor(wallet-integration): just use call_data, remove data types * feat(wallet-integration): add frontend type for flexible serving * feat(wallet-integration): StateHandler in WalletIntegrationManager. Add terminate method * refactor(wallet-integration): move to module with pop-cli crate * feat(wallet-integration): server-side error handling, update port to 9090 * refactor(wallet-integration): restructure server for easier use in thread * fix(wallet-integration): remove invalid fn `finish` * docs(wallet-integration): inline comments * test(wallet-integration): unit tests for wallet-integration server. Add new frontend type * feat(wallet-integration): two new routes: error and terminate * refactor(wallet-integration): consistent comments, remove unnecessary code in tests * feat: add TransactionData::new * feat: guide user to call a contract (#306) * feat: guide user for calling a contract * feat: get metadata contract from the contract path * refactor: refactor test and validate address input * fix: apply feedback * feat: prompt to have another call and skip questions for queries * refactor: use Cli module instead of cliclack * test: unit test pop-cli crate * test: unit contracts crate * chore: format * test: refactor and improve test cases * fix: fix todos and refactor * test: fix unit test * feat: parse types of parameters and display it to the user in the placeholder * refactor: error handling for pop call * refactor: display call to be executed after guide and reorder * refactor: when repeat call use same contract values and dont clean screen * test: add dry-run test * test: refactor and add more test coverage * test: more coverage * fix: unit test * feat: dev mode to skip certain user prompts * refactor: test functions, renaming and fix clippy * refactor: improve devex of pop call contract * test: adjust tests to refactor * chore: reset_for_new_call fields * fix: build contract if has not been built * refactor: use command state (#338) Merged set_up_call_config and guide_user_to_call_contract into a single function. Also adds short symbols for arguments. * fix: parse user inputs for Option arguments (#332) * fix: automatically add some or none to Option argument * test: refactor and tests * refactor: improve code and comments * fix: renaming and clean code * chore: option params not mandatory * fix: parse user inputs for Option arguments in constructor (#335) * fix: automatically add some or none to Option argument * fix: tests * refactor: process_function_args * test: update tests accordingly last changes * fix: issue with delimiter * test: fix unit test * refactor: renaming and fix comments * refactor: format types (#339) Shows the full type representation, making it easier to see the entry format of parameter values. * fix: logo doesn't show in README --------- Co-authored-by: Frank Bell <60948618+evilrobot-01@users.noreply.github.com> Co-authored-by: Alejandro Martinez Andres <11448715+al3mart@users.noreply.github.com> * test: fix unit test * feat: allow users to specify custom contract metadata files (#347) * chore: allow the user specify the metadata file to call a contract * test: unit test to parse metadata from a file * docs: fix docs * refactor: ensure_contract_built after user input path * fix: call contract when metadata file * fix: remove default_input in contract address * docs: rename metadata with artifact * fix: panic at has_contract_been_built * fix: clippy * refactor: keep ensure_contract_built as a CallContractCommand function * fix: ensure_contract_built * docs: improve comments * fix: feedback and include wasm file for testing * fix: clippy * chore: after build contract prompt the user if the contract is already deployed * refactor: ensure_contract_built * refactor: has_contract_been_built function * docs: fix comments and messages * refactor: get_messages and get_constructors * test: fix unit tests call ui --------- Co-authored-by: Frank Bell <60948618+evilrobot-01@users.noreply.github.com> Co-authored-by: Alejandro Martinez Andres <11448715+al3mart@users.noreply.github.com> * fix: support new substrate-contracts-node structure and stabilize integration tests (#360) * fix: parse new structure substrate-contracts-node * fix: paseo+coretime integration test * fix: sourcing latest version substrate-contracts-node * refactor: set_executable_permission function * fix: clippy * chore: CI configuration * test: specify port in run_contracts_node * fix: use random ports instead of hardcoded ones * fix: build spec experience (#331) * fix: add chain to specify the chain specification * fix: default_bootnode by default to true * chore: fmt * chore: deprecate flag --release in build specs * fix: clean output (#334) * fix: undo deprecation of --release flag * refactor: small fix * style: remove extra space * fix(spec): better handling of spinner * style: use spinner instead of multispinner * docs: help message to include build * feat: reuse existing chain spec * refactor: remove clone * refactor: opt in to edit provided chain spec * docs: improve * refactor: flow flag input * fix: prepare_output_path * refactor: resolve small improvements * fix: protocol id prompt * fix: spinner * fix: docs * test: test cli * chore: refactor * chore: amend test * feat: production profile * refactor: improve profile experience * chore: feedback and rebase * chore: add profile tests * fix(test): parachain_lifecycle * style: fmt * fix: clippy * fix: cli required changes introduced by PR * fix: test * fix: clippy * docs: deprecation message --------- Co-authored-by: Alejandro Martinez Andres <11448715+al3mart@users.noreply.github.com> Co-authored-by: Daanvdplas * feat: guide user to call a parachain (#316) * feat: guide user for calling a contract * feat: get metadata contract from the contract path * refactor: refactor test and validate address input * fix: apply feedback * feat: prompt to have another call and skip questions for queries * refactor: use Cli module instead of cliclack * test: unit test pop-cli crate * test: unit contracts crate * chore: format * test: refactor and improve test cases * fix: fix todos and refactor * test: fix unit test * feat: parse types of parameters and display it to the user in the placeholder * refactor: error handling for pop call * refactor: display call to be executed after guide and reorder * refactor: when repeat call use same contract values and dont clean screen * test: add dry-run test * test: refactor and add more test coverage * test: more coverage * fix: unit test * feat: dev mode to skip certain user prompts * refactor: test functions, renaming and fix clippy * refactor: improve devex of pop call contract * test: adjust tests to refactor * chore: reset_for_new_call fields * fix: build contract if has not been built * refactor: use command state (#338) Merged set_up_call_config and guide_user_to_call_contract into a single function. Also adds short symbols for arguments. * fix: automatically add some or none to Option argument * test: refactor and tests * refactor: improve code and comments * fix: renaming and clean code * chore: option params not mandatory * fix: parse user inputs for Option arguments in constructor (#335) * fix: automatically add some or none to Option argument * fix: tests * refactor: process_function_args * test: update tests accordingly last changes * fix: issue with delimiter * test: fix unit test * refactor: renaming and fix comments * refactor: format types (#339) Shows the full type representation, making it easier to see the entry format of parameter values. * fix: logo doesn't show in README * feat: pop call parachain prototype * feat: dispaly arguments of extrinsic * refactor: structure similar to pop call contract * feat: parse all values for extrinsic/storage * refactor: signer in common * refactor: improve messages * feat: call parachain ui * fix: calls working * refactor: remove unused code * refactor: remove unused code * refactor: various fixes * refactor: various fixes * feat: add option to include params from command line * refactor: clean docs and refactor code * fix: tests * refactor: parse all the metadata again * refactor: reorganize and clean metadata functions * feat: display specific use cases to the user * refactor: predefined actions * fix: various fixes * fix: error message not supported for complex types * refactor: parse all metadata, including parameters at once * refactor: clean docs and move code * fix: format_type * fix: parse user inputs for Option arguments (#332) * fix: automatically add some or none to Option argument * test: refactor and tests * refactor: improve code and comments * fix: renaming and clean code * chore: option params not mandatory * fix: parse user inputs for Option arguments in constructor (#335) * fix: automatically add some or none to Option argument * fix: tests * refactor: process_function_args * test: update tests accordingly last changes * fix: issue with delimiter * test: fix unit test * refactor: renaming and fix comments * refactor: format types (#339) Shows the full type representation, making it easier to see the entry format of parameter values. * fix: logo doesn't show in README --------- Co-authored-by: Frank Bell <60948618+evilrobot-01@users.noreply.github.com> Co-authored-by: Alejandro Martinez Andres <11448715+al3mart@users.noreply.github.com> * test: fix unit test * refactor: clean the way to parse and prompt parameters * feat: add Purchase on-demand coretime use cases * test: add skip_confirm, move when prompt for the signer and create the integration test * test: call parachain ui unit test * refactor: separate structs * fmt * test: pop-cli unit testing * test: pop-common unit tests * test: parse metadata unit tests * test: refactor and test processing parameters * test: comments and unit test in call functions * fix: clippy warnings * chore: fmt * fix: solve conflicts and unit tests (#359) * test: call parachain ui unit test * test: pop-cli unit testing * test: pop-common unit tests * test: parse metadata unit tests * test: refactor and test processing parameters * test: comments and unit test in call functions * fix: clippy warnings * chore: fmt * fix: conflicts and unit tests * test: remove test and improve test * feat: guide user for calling a contract * feat: get metadata contract from the contract path * refactor: refactor test and validate address input * fix: apply feedback * feat: prompt to have another call and skip questions for queries * refactor: use Cli module instead of cliclack * test: unit test pop-cli crate * test: unit contracts crate * chore: format * test: refactor and improve test cases * fix: fix todos and refactor * test: fix unit test * feat: parse types of parameters and display it to the user in the placeholder * refactor: error handling for pop call * refactor: display call to be executed after guide and reorder * refactor: when repeat call use same contract values and dont clean screen * test: add dry-run test * test: refactor and add more test coverage * test: more coverage * fix: unit test * feat: dev mode to skip certain user prompts * refactor: test functions, renaming and fix clippy * refactor: improve devex of pop call contract * test: adjust tests to refactor * chore: reset_for_new_call fields * fix: build contract if has not been built * refactor: use command state (#338) Merged set_up_call_config and guide_user_to_call_contract into a single function. Also adds short symbols for arguments. * fix: automatically add some or none to Option argument * test: refactor and tests * refactor: improve code and comments * fix: renaming and clean code * chore: option params not mandatory * fix: parse user inputs for Option arguments in constructor (#335) * fix: automatically add some or none to Option argument * fix: tests * refactor: process_function_args * test: update tests accordingly last changes * fix: issue with delimiter * test: fix unit test * refactor: renaming and fix comments * refactor: format types (#339) Shows the full type representation, making it easier to see the entry format of parameter values. * feat: pop call parachain prototype * feat: dispaly arguments of extrinsic * refactor: structure similar to pop call contract * feat: parse all values for extrinsic/storage * refactor: signer in common * refactor: improve messages * feat: call parachain ui * fix: calls working * refactor: remove unused code * refactor: remove unused code * refactor: various fixes * refactor: various fixes * feat: add option to include params from command line * refactor: clean docs and refactor code * fix: tests * refactor: parse all the metadata again * refactor: reorganize and clean metadata functions * feat: display specific use cases to the user * refactor: predefined actions * fix: various fixes * fix: error message not supported for complex types * refactor: parse all metadata, including parameters at once * refactor: clean docs and move code * fix: format_type * test: fix unit test * refactor: clean the way to parse and prompt parameters * feat: add Purchase on-demand coretime use cases * test: add skip_confirm, move when prompt for the signer and create the integration test * test: call parachain ui unit test * test: pop-cli unit testing * test: pop-common unit tests * test: parse metadata unit tests * test: refactor and test processing parameters * test: comments and unit test in call functions * fix: clippy warnings * chore: fmt * feat: repeat call only if using guide UI * fix: clippy * refactor: various improvements * chore: parser for pallet and extrinsic input names * refactor: only move to pop_common the needed functions * refactor: improve test, docs and errors * test: fix unit tests * fix: reset_for_new_call when extrinisc is not supported * fix: build with parachain features * test: wait before call parachain in integration test * docs: minor improvements * test: migrate find_free_port to pop_common * test: fix increase waiting time * test: remove unnecesary test case * refactor: rename api with client * refactor: naming and docs * docs: improve docs and missing comments * test: remove unnecesary verbose * test: find_free_port * docs: improve parameter documentation * test: add missing test to sign_and_submit_extrinsic * fix: apply feedback from auxiliar PRs, remove unnecesary clones * docs: public modules * refactor: clean unused params * fix: mark all extrinsics that uses calls as parameter as unsupported * test: fix expect_select * docs: improve documentation * feat: submit extrinsic from call_data (#348) * feat: submit extrinsic from call_data * test: unit test for initialize_api_client * test: unit test for send_extrinsic_from_call_data * fix: CallData struct * fix: skip_confirm for send_extrinsic_from_call_data * chore: clippy * chore: fmt * refactor: minor doc and naming changes * refactor: remove unnecesary clones and return early when submit_extrinsic_from_call_data * chore: fmt * refactor: split decode_call_data logic outside sign_and_submit_extrinsic_with_call_data * feat: parse files when the argument values are very big (#363) * feat: parse files when the argument values are very big * test: unit test * chore: fmt * feat: file logic using the command line * fix: sequence arguments * test: fix unit test * refactor: remove prompting the user if input is file or value * refactor: parse_extrinsic_arguments * fix: CI deny * refactor: reorder Param derive macros * test: fix decode_call_data_works unit test * refactor: use Default derive macro and define constants for test values (#366) * feat: parse files when the argument values are very big * chore: fmt * feat: file logic using the command line * fix: sequence arguments * refactor: parse_extrinsic_arguments * refactor: use Default in pop_parachain structs * refactor: use Default in CallParachainCommand struct * refactor: use constant in tests * chore: fmt and small refactor * feat: flag sudo to wrap extrinsic (#349) * feat: submit extrinsic from call_data * test: unit test for initialize_api_client * feat: wrap call into a sudo call * test: add unit test to the new logic * fix: skip_confirm for send_extrinsic_from_call_data * chore: clippy * docs: renaming and improve docs * test: use force_transfer for testing * fix: check if sudo exist before prompt the user * chore: fmt * chore: fmt * test: fix wrong assert * docs: improve comments and output messages * refactor: split decode_call_data logic outside sign_and_submit_extrinsic_with_call_data * fix: test construct_sudo_extrinsic_works and formatting * refactor: various fixes and improvements (#367) * refactor: sort pallets/dispatchables * refactor: remove unnecessary async * fix: resolve issue after rebase * fix: more async issues after rebase * refactor: use single constant * refactor: terminology (#368) * refactor: terminology * refactor: simply pallet/function relationship * fix: amend call_data conflicts after refactor * refactor: improvements (#370) * fix: add missing short arg option * refactor: note that extrinsic wait includes finalization * refactor: remove clones * style: formatting * refactor: make file prompt more generic * refactor: add missing license headers * style: formatting * docs: comments * docs: comments * docs: comments * refactor: reuse existing metadata * refactor: minimise clones * docs: comments * refactor: naming * docs: fix parameter doc comments * refactor: address clippy warnings * refactor: rename parachain with chain as the primary command and retain parachain as an alias (#373) * refactor: rename parachain with chain in visible messages * refactor: rename parachain with chain internal code * chore: solve fmt after rebase * refactor: small fix, use alias instead aliases * refactor: rename CallParachain struct into Call --------- Co-authored-by: Frank Bell <60948618+evilrobot-01@users.noreply.github.com> Co-authored-by: Alejandro Martinez Andres <11448715+al3mart@users.noreply.github.com> Co-authored-by: Daanvdplas * feat(wallet-integration): server and API (#362) * feat(wallet-integration): implement server and API * feat(wallet-integration): add a few tests to server. Needs more * refactor(wallet-integration): just use call_data, remove data types * feat(wallet-integration): add frontend type for flexible serving * feat(wallet-integration): StateHandler in WalletIntegrationManager. Add terminate method * refactor(wallet-integration): move to module with pop-cli crate * feat(wallet-integration): server-side error handling, update port to 9090 * refactor(wallet-integration): restructure server for easier use in thread * fix(wallet-integration): remove invalid fn `finish` * docs(wallet-integration): inline comments * test(wallet-integration): unit tests for wallet-integration server. Add new frontend type * feat(wallet-integration): two new routes: error and terminate * refactor(wallet-integration): consistent comments, remove unnecessary code in tests * feat: add TransactionData::new * refactor(wallet-integration: shutdown channel error handling, terminate helper, take_error and TranscationData::new tests * chore: clippy warning * feat(up-contract): get call data for upload-only and pass to wallet integration -- PoC * chore: use git branch for cargo contract * feat(up-contract): handle subxt events better, various improvements * refactor: reuse pop up logic * chore: rebase fixes * refactor: clean after rebase * fix: cargo.lock after rebase * refactor: move terminate node into common::contracts * fix: sp-core not only for contracts * feat(up-contract): serve HTML from string, fix deployment message on error, cors, and other misc. improvements * fix: rebase * chore: deny.toml license * feat: secure signing logic for pop call contract * feat: integrate secure signing with pop call contract * test: adapt pop-cli ui tests * refactor: clean code * docs: improve docs functions * test: ignore failing test (fixed in another PR), and remove println * fix: import removed by mistake * feat(up-contract): serve HTML from string, fix deployment message on error, cors, and other misc. improvements * feat(up-contract): serve HTML from string, fix deployment message on error, cors, and other misc. improvements * feat(wallet-integration): server and API (#362) * feat(wallet-integration): implement server and API * feat(wallet-integration): add a few tests to server. Needs more * refactor(wallet-integration): just use call_data, remove data types * feat(wallet-integration): add frontend type for flexible serving * feat(wallet-integration): StateHandler in WalletIntegrationManager. Add terminate method * refactor(wallet-integration): move to module with pop-cli crate * feat(wallet-integration): server-side error handling, update port to 9090 * refactor(wallet-integration): restructure server for easier use in thread * fix(wallet-integration): remove invalid fn `finish` * docs(wallet-integration): inline comments * test(wallet-integration): unit tests for wallet-integration server. Add new frontend type * feat(wallet-integration): two new routes: error and terminate * refactor(wallet-integration): consistent comments, remove unnecessary code in tests * feat: add TransactionData::new * refactor(wallet-integration: shutdown channel error handling, terminate helper, take_error and TranscationData::new tests * chore: clippy warning * feat(up-contract): get call data for upload-only and pass to wallet integration -- PoC * feat(up-contract): get call data for instantiate and pass to wallet integration -- PoC * refactor(pop-up): create functions for wallet integration, plus general cleanup * chore: use git branch for cargo contract * feat(up-contract): handle subxt events better, various improvements * feat(wallet-integration): implement server and API * feat(wallet-integration): add a few tests to server. Needs more * refactor(wallet-integration): just use call_data, remove data types * feat(wallet-integration): add frontend type for flexible serving * feat(wallet-integration): StateHandler in WalletIntegrationManager. Add terminate method * refactor(wallet-integration): move to module with pop-cli crate * feat(wallet-integration): server-side error handling, update port to 9090 * refactor(wallet-integration): restructure server for easier use in thread * fix(wallet-integration): remove invalid fn `finish` * docs(wallet-integration): inline comments * test(wallet-integration): unit tests for wallet-integration server. Add new frontend type * feat(wallet-integration): two new routes: error and terminate * refactor(wallet-integration): consistent comments, remove unnecessary code in tests * feat: guide user to call a contract (#306) * feat: guide user for calling a contract * feat: get metadata contract from the contract path * refactor: refactor test and validate address input * fix: apply feedback * feat: prompt to have another call and skip questions for queries * refactor: use Cli module instead of cliclack * test: unit test pop-cli crate * test: unit contracts crate * chore: format * test: refactor and improve test cases * fix: fix todos and refactor * test: fix unit test * feat: parse types of parameters and display it to the user in the placeholder * refactor: error handling for pop call * refactor: display call to be executed after guide and reorder * refactor: when repeat call use same contract values and dont clean screen * test: add dry-run test * test: refactor and add more test coverage * test: more coverage * fix: unit test * feat: dev mode to skip certain user prompts * refactor: test functions, renaming and fix clippy * refactor: improve devex of pop call contract * test: adjust tests to refactor * chore: reset_for_new_call fields * fix: build contract if has not been built * refactor: use command state (#338) Merged set_up_call_config and guide_user_to_call_contract into a single function. Also adds short symbols for arguments. * fix: parse user inputs for Option arguments (#332) * fix: automatically add some or none to Option argument * test: refactor and tests * refactor: improve code and comments * fix: renaming and clean code * chore: option params not mandatory * fix: parse user inputs for Option arguments in constructor (#335) * fix: automatically add some or none to Option argument * fix: tests * refactor: process_function_args * test: update tests accordingly last changes * fix: issue with delimiter * test: fix unit test * refactor: renaming and fix comments * refactor: format types (#339) Shows the full type representation, making it easier to see the entry format of parameter values. * fix: logo doesn't show in README --------- Co-authored-by: Frank Bell <60948618+evilrobot-01@users.noreply.github.com> Co-authored-by: Alejandro Martinez Andres <11448715+al3mart@users.noreply.github.com> * test: fix unit test * feat: allow users to specify custom contract metadata files (#347) * chore: allow the user specify the metadata file to call a contract * test: unit test to parse metadata from a file * docs: fix docs * refactor: ensure_contract_built after user input path * fix: call contract when metadata file * fix: remove default_input in contract address * docs: rename metadata with artifact * fix: panic at has_contract_been_built * fix: clippy * refactor: keep ensure_contract_built as a CallContractCommand function * fix: ensure_contract_built * docs: improve comments * fix: feedback and include wasm file for testing * fix: clippy * chore: after build contract prompt the user if the contract is already deployed * refactor: ensure_contract_built * refactor: has_contract_been_built function * docs: fix comments and messages * refactor: get_messages and get_constructors * test: fix unit tests call ui --------- Co-authored-by: Frank Bell <60948618+evilrobot-01@users.noreply.github.com> Co-authored-by: Alejandro Martinez Andres <11448715+al3mart@users.noreply.github.com> * fix: support new substrate-contracts-node structure and stabilize integration tests (#360) * fix: parse new structure substrate-contracts-node * fix: paseo+coretime integration test * fix: sourcing latest version substrate-contracts-node * refactor: set_executable_permission function * fix: clippy * chore: CI configuration * test: specify port in run_contracts_node * fix: use random ports instead of hardcoded ones * feat: guide user to call a parachain (#316) * feat: guide user for calling a contract * feat: get metadata contract from the contract path * refactor: refactor test and validate address input * fix: apply feedback * feat: prompt to have another call and skip questions for queries * refactor: use Cli module instead of cliclack * test: unit test pop-cli crate * test: unit contracts crate * chore: format * test: refactor and improve test cases * fix: fix todos and refactor * test: fix unit test * feat: parse types of parameters and display it to the user in the placeholder * refactor: error handling for pop call * refactor: display call to be executed after guide and reorder * refactor: when repeat call use same contract values and dont clean screen * test: add dry-run test * test: refactor and add more test coverage * test: more coverage * fix: unit test * feat: dev mode to skip certain user prompts * refactor: test functions, renaming and fix clippy * refactor: improve devex of pop call contract * test: adjust tests to refactor * chore: reset_for_new_call fields * fix: build contract if has not been built * refactor: use command state (#338) Merged set_up_call_config and guide_user_to_call_contract into a single function. Also adds short symbols for arguments. * fix: automatically add some or none to Option argument * test: refactor and tests * refactor: improve code and comments * fix: renaming and clean code * chore: option params not mandatory * fix: parse user inputs for Option arguments in constructor (#335) * fix: automatically add some or none to Option argument * fix: tests * refactor: process_function_args * test: update tests accordingly last changes * fix: issue with delimiter * test: fix unit test * refactor: renaming and fix comments * refactor: format types (#339) Shows the full type representation, making it easier to see the entry format of parameter values. * fix: logo doesn't show in README * feat: pop call parachain prototype * feat: dispaly arguments of extrinsic * refactor: structure similar to pop call contract * feat: parse all values for extrinsic/storage * refactor: signer in common * refactor: improve messages * feat: call parachain ui * fix: calls working * refactor: remove unused code * refactor: remove unused code * refactor: various fixes * refactor: various fixes * feat: add option to include params from command line * refactor: clean docs and refactor code * fix: tests * refactor: parse all the metadata again * refactor: reorganize and clean metadata functions * feat: display specific use cases to the user * refactor: predefined actions * fix: various fixes * fix: error message not supported for complex types * refactor: parse all metadata, including parameters at once * refactor: clean docs and move code * fix: format_type * fix: parse user inputs for Option arguments (#332) * fix: automatically add some or none to Option argument * test: refactor and tests * refactor: improve code and comments * fix: renaming and clean code * chore: option params not mandatory * fix: parse user inputs for Option arguments in constructor (#335) * fix: automatically add some or none to Option argument * fix: tests * refactor: process_function_args * test: update tests accordingly last changes * fix: issue with delimiter * test: fix unit test * refactor: renaming and fix comments * refactor: format types (#339) Shows the full type representation, making it easier to see the entry format of parameter values. * fix: logo doesn't show in README --------- Co-authored-by: Frank Bell <60948618+evilrobot-01@users.noreply.github.com> Co-authored-by: Alejandro Martinez Andres <11448715+al3mart@users.noreply.github.com> * test: fix unit test * refactor: clean the way to parse and prompt parameters * feat: add Purchase on-demand coretime use cases * test: add skip_confirm, move when prompt for the signer and create the integration test * test: call parachain ui unit test * refactor: separate structs * fmt * test: pop-cli unit testing * test: pop-common unit tests * test: parse metadata unit tests * test: refactor and test processing parameters * test: comments and unit test in call functions * fix: clippy warnings * chore: fmt * fix: solve conflicts and unit tests (#359) * test: call parachain ui unit test * test: pop-cli unit testing * test: pop-common unit tests * test: parse metadata unit tests * test: refactor and test processing parameters * test: comments and unit test in call functions * fix: clippy warnings * chore: fmt * fix: conflicts and unit tests * test: remove test and improve test * feat: guide user for calling a contract * feat: get metadata contract from the contract path * refactor: refactor test and validate address input * fix: apply feedback * feat: prompt to have another call and skip questions for queries * refactor: use Cli module instead of cliclack * test: unit test pop-cli crate * test: unit contracts crate * chore: format * test: refactor and improve test cases * fix: fix todos and refactor * test: fix unit test * feat: parse types of parameters and display it to the user in the placeholder * refactor: error handling for pop call * refactor: display call to be executed after guide and reorder * refactor: when repeat call use same contract values and dont clean screen * test: add dry-run test * test: refactor and add more test coverage * test: more coverage * fix: unit test * feat: dev mode to skip certain user prompts * refactor: test functions, renaming and fix clippy * refactor: improve devex of pop call contract * test: adjust tests to refactor * chore: reset_for_new_call fields * fix: build contract if has not been built * refactor: use command state (#338) Merged set_up_call_config and guide_user_to_call_contract into a single function. Also adds short symbols for arguments. * fix: automatically add some or none to Option argument * test: refactor and tests * refactor: improve code and comments * fix: renaming and clean code * chore: option params not mandatory * fix: parse user inputs for Option arguments in constructor (#335) * fix: automatically add some or none to Option argument * fix: tests * refactor: process_function_args * test: update tests accordingly last changes * fix: issue with delimiter * test: fix unit test * refactor: renaming and fix comments * refactor: format types (#339) Shows the full type representation, making it easier to see the entry format of parameter values. * feat: pop call parachain prototype * feat: dispaly arguments of extrinsic * refactor: structure similar to pop call contract * feat: parse all values for extrinsic/storage * refactor: signer in common * refactor: improve messages * feat: call parachain ui * fix: calls working * refactor: remove unused code * refactor: remove unused code * refactor: various fixes * refactor: various fixes * feat: add option to include params from command line * refactor: clean docs and refactor code * fix: tests * refactor: parse all the metadata again * refactor: reorganize and clean metadata functions * feat: display specific use cases to the user * refactor: predefined actions * fix: various fixes * fix: error message not supported for complex types * refactor: parse all metadata, including parameters at once * refactor: clean docs and move code * fix: format_type * test: fix unit test * refactor: clean the way to parse and prompt parameters * feat: add Purchase on-demand coretime use cases * test: add skip_confirm, move when prompt for the signer and create the integration test * test: call parachain ui unit test * test: pop-cli unit testing * test: pop-common unit tests * test: parse metadata unit tests * test: refactor and test processing parameters * test: comments and unit test in call functions * fix: clippy warnings * chore: fmt * feat: repeat call only if using guide UI * fix: clippy * refactor: various improvements * chore: parser for pallet and extrinsic input names * refactor: only move to pop_common the needed functions * refactor: improve test, docs and errors * test: fix unit tests * fix: reset_for_new_call when extrinisc is not supported * fix: build with parachain features * test: wait before call parachain in integration test * docs: minor improvements * test: migrate find_free_port to pop_common * test: fix increase waiting time * test: remove unnecesary test case * refactor: rename api with client * refactor: naming and docs * docs: improve docs and missing comments * test: remove unnecesary verbose * test: find_free_port * docs: improve parameter documentation * test: add missing test to sign_and_submit_extrinsic * fix: apply feedback from auxiliar PRs, remove unnecesary clones * docs: public modules * refactor: clean unused params * fix: mark all extrinsics that uses calls as parameter as unsupported * test: fix expect_select * docs: improve documentation * feat: submit extrinsic from call_data (#348) * feat: submit extrinsic from call_data * test: unit test for initialize_api_client * test: unit test for send_extrinsic_from_call_data * fix: CallData struct * fix: skip_confirm for send_extrinsic_from_call_data * chore: clippy * chore: fmt * refactor: minor doc and naming changes * refactor: remove unnecesary clones and return early when submit_extrinsic_from_call_data * chore: fmt * refactor: split decode_call_data logic outside sign_and_submit_extrinsic_with_call_data * feat: parse files when the argument values are very big (#363) * feat: parse files when the argument values are very big * test: unit test * chore: fmt * feat: file logic using the command line * fix: sequence arguments * test: fix unit test * refactor: remove prompting the user if input is file or value * refactor: parse_extrinsic_arguments * fix: CI deny * refactor: reorder Param derive macros * test: fix decode_call_data_works unit test * refactor: use Default derive macro and define constants for test values (#366) * feat: parse files when the argument values are very big * chore: fmt * feat: file logic using the command line * fix: sequence arguments * refactor: parse_extrinsic_arguments * refactor: use Default in pop_parachain structs * refactor: use Default in CallParachainCommand struct * refactor: use constant in tests * chore: fmt and small refactor * feat: flag sudo to wrap extrinsic (#349) * feat: submit extrinsic from call_data * test: unit test for initialize_api_client * feat: wrap call into a sudo call * test: add unit test to the new logic * fix: skip_confirm for send_extrinsic_from_call_data * chore: clippy * docs: rena… * Test wallet integration (#383) * chore: get_payload_works * test(wallet-integration): up contract get payload works * get paylaod from server works * test(wallet-integration): retrieve upload call data works * test(wallet-integration): retrieve instantiate call data works * style: better comments * test: try higher wait times in CI * test(wallet-integration): bump sleep time * test(wallet-integration): even more sleep time * test(wallet-integration): maybe a port problem ? * revert 0075e94 * test(wallet-integration): better unit tests * test(wallet-integration): wait for wallet signature * test(wallet-integration): assert on received payload * test(wallet-integration): use tokio spawn * test(wallet-integration): add some waiting time * test(wallet-integration): use cargo run * style: nightly fmt * test(wallet-integration): 500s sleep time * test(wallet-integration): ignore some tests * test(wallet-integration): get instantiate call data * test(wallet-integration): integration tests improvements * test(wallet-integration): bump sleep time * test(wallet-integration): merge integration tests * test(wallet-integration): remove sign_call_data test * test(wallet-integration): use random free port * test(wallet-integration): define gas_limit & proof_size * fix: merge issues --------- Co-authored-by: Alejandro Martinez Andres <11448715+al3mart@users.noreply.github.com> * test(wallet-integration): remove sign_call_data test (#384) * chore: remove hex crate and use alternate means with test * chore: minor changes to comments, spinners, and deps * refactor: shared prompt, determine_signing_method * refactor: use extrinsic instead of transaction * chore: latest frontend * feat: send remark on contracts-node launch * chore: use cargo-contract crate release * feat(wallet): choose random port for server * feat(wallet): auto-open browser * refactor: find_free_port has preferred port * fix: conflicting tests merged to one * fix(wallet): port selection, update frontend * feat: update frontend with warning * [CI test] test: include instantiate_call_data_works (#389) * test: include instantiate_call_data_works * test: increase sleep time in run_contracts_node * fix: fmt --------- Co-authored-by: AlexD10S * refactor: review fixes and improvements (#390) * docs: align description across commands for consistency * refactor: use short = '' arg style recently adopted in main for consistency * test: remove unused variable * refactor: align function naming * refactor: remove unnecessary async * refactor: improve result handling Eliminates the expects and unwraps. * refactor: remove unnecessary clones * refactor: simplify code * style: formatting * docs: adds missing doc comments * refactor: remove unused function * refactor: use .expect() as server_url is parameter on public function * docs: add missing public function doc comments * refactor: use constant * refactor: module directory to file * refactor: use imports * refactor: eliminate duplication * docs: add missing doc comments * style: formatting * chore: use latest frontend * chore: latest frontend * chore: update frontend to remove trailing slash * chore: improve uploading / calling contract message to wait for finalization * test: improve `crates/pop-cli/src/common/contracts.rs` coverage (#391) * test: improve common/contracts coverage * style: fmt * refactor: include Cli in methods * style: typo * fix: improve tests * style: better comment * refactor: include cache path as param in check_contracts_node_and_prompt * docs: improve function docs * remove typo in comment --------- Co-authored-by: Alex Bean Co-authored-by: Frank Bell <60948618+evilrobot-01@users.noreply.github.com> Co-authored-by: Alejandro Martinez Andres <11448715+al3mart@users.noreply.github.com> Co-authored-by: Daanvdplas --- Cargo.lock | 649 +++++++---- Cargo.toml | 12 +- crates/pop-cli/Cargo.toml | 14 +- crates/pop-cli/src/assets/index.html | 1024 +++++++++++++++++ crates/pop-cli/src/cli.rs | 1 - crates/pop-cli/src/commands/call/chain.rs | 134 ++- crates/pop-cli/src/commands/call/contract.rs | 181 ++- crates/pop-cli/src/commands/test/contract.rs | 8 +- crates/pop-cli/src/commands/up/contract.rs | 373 ++++-- crates/pop-cli/src/common/contracts.rs | 97 +- crates/pop-cli/src/common/mod.rs | 1 + crates/pop-cli/src/common/wallet.rs | 67 ++ crates/pop-cli/src/main.rs | 1 + crates/pop-cli/src/wallet_integration.rs | 579 ++++++++++ crates/pop-cli/tests/contract.rs | 115 +- crates/pop-cli/tests/parachain.rs | 2 +- crates/pop-common/src/lib.rs | 16 +- crates/pop-contracts/Cargo.toml | 6 +- crates/pop-contracts/src/call.rs | 56 +- crates/pop-contracts/src/lib.rs | 14 +- .../src/{node/mod.rs => node.rs} | 24 +- crates/pop-contracts/src/up.rs | 260 ++++- crates/pop-contracts/src/utils/metadata.rs | 2 +- crates/pop-contracts/tests/files/testing.wasm | Bin 3710 -> 0 bytes crates/pop-parachains/Cargo.toml | 2 +- .../pop-parachains/src/call/metadata/mod.rs | 7 +- crates/pop-parachains/src/call/mod.rs | 31 +- crates/pop-parachains/src/lib.rs | 7 +- 28 files changed, 3237 insertions(+), 446 deletions(-) create mode 100644 crates/pop-cli/src/assets/index.html create mode 100644 crates/pop-cli/src/common/wallet.rs create mode 100644 crates/pop-cli/src/wallet_integration.rs rename crates/pop-contracts/src/{node/mod.rs => node.rs} (91%) diff --git a/Cargo.lock b/Cargo.lock index bb211c82e..f581a91d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "Inflector" @@ -724,7 +724,7 @@ dependencies = [ "parachains-runtimes-test-utils", "parity-scale-codec", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "staging-parachain-info", "staging-xcm 14.2.0", "staging-xcm-builder", @@ -749,7 +749,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-api", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "staging-xcm 14.2.0", "staging-xcm-builder", "staging-xcm-executor", @@ -938,6 +938,61 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.5.1", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper 1.0.2", + "tokio", + "tower 0.5.2", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper 1.0.2", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "backoff" version = "0.4.0" @@ -1271,7 +1326,7 @@ dependencies = [ "serde", "sp-consensus-grandpa", "sp-core 34.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-std", ] @@ -1306,7 +1361,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-core 34.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-std", ] @@ -1339,7 +1394,7 @@ dependencies = [ "scale-info", "serde", "sp-core 34.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-std", ] @@ -1358,7 +1413,7 @@ dependencies = [ "pallet-utility", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-std", ] @@ -1379,7 +1434,7 @@ dependencies = [ "serde", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-state-machine 0.43.0", "sp-std", "sp-trie 37.0.0", @@ -1402,7 +1457,7 @@ dependencies = [ "sp-application-crypto 38.0.0", "sp-consensus-grandpa", "sp-core 34.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-std", "sp-trie 37.0.0", ] @@ -1434,7 +1489,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-core 34.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "staging-xcm 14.2.0", ] @@ -1451,7 +1506,7 @@ dependencies = [ "scale-info", "snowbridge-core", "sp-core 34.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-std", "staging-xcm 14.2.0", ] @@ -1493,7 +1548,7 @@ dependencies = [ "sp-core 34.0.0", "sp-io 38.0.0", "sp-keyring", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-tracing 17.0.1", "staging-xcm 14.2.0", "staging-xcm-builder", @@ -1525,7 +1580,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-std", "sp-trie 37.0.0", "staging-xcm 14.2.0", @@ -1552,9 +1607,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" +checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8" dependencies = [ "memchr", "regex-automata 0.4.9", @@ -1648,7 +1703,7 @@ checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" dependencies = [ "camino", "cargo-platform", - "semver 1.0.23", + "semver 1.0.24", "serde", "serde_json", "thiserror 1.0.69", @@ -1662,7 +1717,7 @@ checksum = "8769706aad5d996120af43197bf46ef6ad0fda35216b4505f926a365a232d924" dependencies = [ "camino", "cargo-platform", - "semver 1.0.23", + "semver 1.0.24", "serde", "serde_json", "thiserror 2.0.6", @@ -1680,9 +1735,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.3" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d" +checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf" dependencies = [ "jobserver", "libc", @@ -1948,9 +2003,9 @@ checksum = "cd7e35aee659887cbfb97aaf227ac12cad1a9d7c71e55ff3376839ed4e282d08" [[package]] name = "contract-build" -version = "5.0.1" +version = "5.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "857769855bf40d230e41baf6575cc44bd5e6869f69f88f45f6791da793f49a0c" +checksum = "b014fa89030235ecd8bdeb061ec97df326281b484f89ad3e17a79f08759c2f52" dependencies = [ "anyhow", "blake2", @@ -1967,7 +2022,7 @@ dependencies = [ "parity-scale-codec", "regex", "rustc_version 0.4.1", - "semver 1.0.23", + "semver 1.0.24", "serde", "serde_json", "strum 0.26.3", @@ -1989,9 +2044,9 @@ dependencies = [ [[package]] name = "contract-extrinsics" -version = "5.0.1" +version = "5.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e77ad38bef6454f97ca33481e960e9b105cb97f3794656071cbf50229445a89d" +checksum = "67d0c91349c31caec4d5e3c544b4bc4fcc7c0468dc49ee84a96a24f915464401" dependencies = [ "anyhow", "blake2", @@ -2022,13 +2077,13 @@ dependencies = [ [[package]] name = "contract-metadata" -version = "5.0.1" +version = "5.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "733a6624ea05dd71050641c3cd9baff7a1445032a0082f0e55c800c078716424" +checksum = "83ae8bcb5f7c5ea033d05fa0bbffa4e762a5b69c0ce96e4188fb15385a01998b" dependencies = [ "anyhow", "impl-serde 0.5.0", - "semver 1.0.23", + "semver 1.0.24", "serde", "serde_json", "url", @@ -2036,9 +2091,9 @@ dependencies = [ [[package]] name = "contract-transcode" -version = "5.0.1" +version = "5.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd9131028be7b8eefdd9151a0a682ed428c1418d5d1ec142c35d2dbe6b9653c6" +checksum = "baca96dc859fd180eba5f15e468f59dc8c932a6b72f6b76f91b571b6743a9e7d" dependencies = [ "anyhow", "base58", @@ -2355,7 +2410,7 @@ dependencies = [ "scale-info", "sp-application-crypto 38.0.0", "sp-consensus-aura", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -2372,7 +2427,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "staging-xcm 14.2.0", ] @@ -2403,7 +2458,7 @@ dependencies = [ "sp-externalities 0.29.0", "sp-inherents", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-state-machine 0.43.0", "sp-std", "sp-trie 37.0.0", @@ -2436,7 +2491,7 @@ dependencies = [ "frame-system", "pallet-session", "parity-scale-codec", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -2452,7 +2507,7 @@ dependencies = [ "parity-scale-codec", "polkadot-primitives 16.0.0", "scale-info", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -2467,7 +2522,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "staging-xcm 14.2.0", ] @@ -2491,7 +2546,7 @@ dependencies = [ "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "staging-xcm 14.2.0", "staging-xcm-builder", "staging-xcm-executor", @@ -2509,7 +2564,7 @@ dependencies = [ "frame-system", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "staging-xcm 14.2.0", ] @@ -2524,7 +2579,7 @@ dependencies = [ "polkadot-primitives 15.0.0", "sp-api", "sp-consensus-aura", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -2539,7 +2594,7 @@ dependencies = [ "polkadot-primitives 16.0.0", "scale-info", "sp-api", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-trie 37.0.0", "staging-xcm 14.2.0", ] @@ -2584,7 +2639,7 @@ dependencies = [ "log", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -2610,7 +2665,7 @@ dependencies = [ "pallet-asset-conversion", "parity-scale-codec", "polkadot-runtime-common", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "staging-xcm 14.2.0", "staging-xcm-builder", "staging-xcm-executor", @@ -2625,7 +2680,7 @@ dependencies = [ "cumulus-primitives-core", "parity-scale-codec", "polkadot-primitives 16.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-state-machine 0.43.0", "sp-trie 37.0.0", ] @@ -2672,9 +2727,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.133" +version = "1.0.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05e1ec88093d2abd9cf1b09ffd979136b8e922bf31cad966a8fe0d73233112ef" +checksum = "a5a32d755fe20281b46118ee4b507233311fb7a48a0cfd42f554b93640521a2f" dependencies = [ "cc", "cxxbridge-cmd", @@ -2686,9 +2741,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.133" +version = "1.0.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afa390d956ee7ccb41aeed7ed7856ab3ffb4fc587e7216be7e0f83e949b4e6c" +checksum = "11645536ada5d1c8804312cbffc9ab950f2216154de431de930da47ca6955199" dependencies = [ "cc", "codespan-reporting", @@ -2700,9 +2755,9 @@ dependencies = [ [[package]] name = "cxxbridge-cmd" -version = "1.0.133" +version = "1.0.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c23bfff654d6227cbc83de8e059d2f8678ede5fc3a6c5a35d5c379983cc61e6" +checksum = "ebcc9c78e3c7289665aab921a2b394eaffe8bdb369aa18d81ffc0f534fd49385" dependencies = [ "clap", "codespan-reporting", @@ -2713,15 +2768,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.133" +version = "1.0.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c01b36e22051bc6928a78583f1621abaaf7621561c2ada1b00f7878fbe2caa" +checksum = "3a22a87bd9e78d7204d793261470a4c9d585154fddd251828d8aefbb5f74c3bf" [[package]] name = "cxxbridge-macro" -version = "1.0.133" +version = "1.0.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6e14013136fac689345d17b9a6df55977251f11d333c0a571e8d963b55e1f95" +checksum = "1dfdb020ff8787c5daf6e0dca743005cc8782868faeadfbabb8824ede5cb1c72" dependencies = [ "proc-macro2", "quote", @@ -3560,7 +3615,7 @@ dependencies = [ "sp-application-crypto 38.0.0", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-runtime-interface 28.0.0", "sp-storage 21.0.0", "static_assertions", @@ -3578,7 +3633,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -3621,7 +3676,7 @@ dependencies = [ "sp-arithmetic", "sp-core 34.0.0", "sp-npos-elections", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -3639,7 +3694,7 @@ dependencies = [ "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-tracing 17.0.1", ] @@ -3680,7 +3735,7 @@ dependencies = [ "log", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -3715,7 +3770,7 @@ dependencies = [ "sp-inherents", "sp-io 38.0.0", "sp-metadata-ir", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-staking 36.0.0", "sp-state-machine 0.43.0", "sp-std", @@ -3784,7 +3839,7 @@ dependencies = [ "serde", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-std", "sp-version", "sp-weights", @@ -3802,7 +3857,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-core 34.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -3825,7 +3880,7 @@ dependencies = [ "frame-support", "parity-scale-codec", "sp-api", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -4336,6 +4391,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" +[[package]] +name = "http-range-header" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" + [[package]] name = "httparse" version = "1.9.5" @@ -4449,7 +4510,7 @@ dependencies = [ "http 1.2.0", "hyper 1.5.1", "hyper-util", - "rustls 0.23.19", + "rustls 0.23.20", "rustls-pki-types", "tokio", "tokio-rustls 0.26.1", @@ -4975,6 +5036,15 @@ version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + [[package]] name = "is-terminal" version = "0.4.13" @@ -4986,6 +5056,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -5117,7 +5197,7 @@ dependencies = [ "http 1.2.0", "jsonrpsee-core", "pin-project", - "rustls 0.23.19", + "rustls 0.23.20", "rustls-pki-types", "rustls-platform-verifier", "soketto", @@ -5266,8 +5346,8 @@ dependencies = [ "tokio", "tokio-tungstenite", "tokio-util", - "tower", - "tower-http", + "tower 0.4.13", + "tower-http 0.4.4", "tracing", ] @@ -5716,6 +5796,12 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "matrixmultiply" version = "0.3.9" @@ -6126,6 +6212,17 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "open" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ecd52f0b8d15c40ce4820aa251ed5de032e5d91fab27f7db2f40d42a8bdf69c" +dependencies = [ + "is-wsl", + "libc", + "pathdiff", +] + [[package]] name = "openssl" version = "0.10.68" @@ -6238,7 +6335,7 @@ dependencies = [ "sp-core 34.0.0", "sp-crypto-hashing", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -6257,7 +6354,7 @@ dependencies = [ "sp-arithmetic", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -6276,7 +6373,7 @@ dependencies = [ "sp-arithmetic", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -6291,7 +6388,7 @@ dependencies = [ "pallet-transaction-payment", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -6306,7 +6403,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-core 34.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -6324,7 +6421,7 @@ dependencies = [ "serde", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -6341,7 +6438,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-core 34.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -6357,7 +6454,7 @@ dependencies = [ "pallet-assets", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -6372,7 +6469,7 @@ dependencies = [ "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -6389,7 +6486,7 @@ dependencies = [ "scale-info", "sp-application-crypto 38.0.0", "sp-consensus-aura", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -6405,7 +6502,7 @@ dependencies = [ "scale-info", "sp-application-crypto 38.0.0", "sp-authority-discovery", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -6419,7 +6516,7 @@ dependencies = [ "impl-trait-for-tuples", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -6441,7 +6538,7 @@ dependencies = [ "sp-consensus-babe", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-session", "sp-staking 36.0.0", ] @@ -6464,7 +6561,7 @@ dependencies = [ "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-tracing 17.0.1", ] @@ -6481,7 +6578,7 @@ dependencies = [ "log", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -6499,7 +6596,7 @@ dependencies = [ "scale-info", "serde", "sp-consensus-beefy", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-session", "sp-staking 36.0.0", ] @@ -6526,7 +6623,7 @@ dependencies = [ "sp-consensus-beefy", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-state-machine 0.43.0", ] @@ -6545,7 +6642,7 @@ dependencies = [ "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -6564,7 +6661,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-consensus-grandpa", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-std", ] @@ -6583,7 +6680,7 @@ dependencies = [ "log", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-std", "sp-trie 37.0.0", ] @@ -6605,7 +6702,7 @@ dependencies = [ "pallet-bridge-grandpa", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-std", ] @@ -6630,7 +6727,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-arithmetic", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-std", ] @@ -6650,7 +6747,7 @@ dependencies = [ "sp-api", "sp-arithmetic", "sp-core 34.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -6669,7 +6766,7 @@ dependencies = [ "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -6688,7 +6785,7 @@ dependencies = [ "parity-scale-codec", "rand", "scale-info", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-staking 36.0.0", ] @@ -6706,7 +6803,7 @@ dependencies = [ "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -6721,7 +6818,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-core 34.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -6749,7 +6846,7 @@ dependencies = [ "sp-api", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-std", "staging-xcm 14.2.0", "staging-xcm-builder", @@ -6785,7 +6882,7 @@ dependencies = [ "sp-core 34.0.0", "sp-io 38.0.0", "sp-keystore 0.40.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-tracing 17.0.1", "staging-xcm 14.2.0", "staging-xcm-builder", @@ -6855,7 +6952,7 @@ dependencies = [ "scale-info", "serde", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -6874,7 +6971,7 @@ dependencies = [ "sp-arithmetic", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -6889,7 +6986,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-staking 36.0.0", ] @@ -6908,7 +7005,7 @@ dependencies = [ "serde", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -6924,7 +7021,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -6946,7 +7043,7 @@ dependencies = [ "sp-core 34.0.0", "sp-io 38.0.0", "sp-npos-elections", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "strum 0.26.3", ] @@ -6961,7 +7058,7 @@ dependencies = [ "frame-system", "parity-scale-codec", "sp-npos-elections", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -6979,7 +7076,7 @@ dependencies = [ "sp-core 34.0.0", "sp-io 38.0.0", "sp-npos-elections", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-staking 36.0.0", ] @@ -6998,7 +7095,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-staking 36.0.0", ] @@ -7018,7 +7115,7 @@ dependencies = [ "sp-core 34.0.0", "sp-inherents", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -7039,7 +7136,7 @@ dependencies = [ "sp-consensus-grandpa", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-session", "sp-staking 36.0.0", ] @@ -7058,7 +7155,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -7077,7 +7174,7 @@ dependencies = [ "sp-application-crypto 38.0.0", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-staking 36.0.0", ] @@ -7095,7 +7192,7 @@ dependencies = [ "sp-core 34.0.0", "sp-io 38.0.0", "sp-keyring", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -7109,7 +7206,7 @@ dependencies = [ "parity-scale-codec", "safe-mix", "scale-info", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -7123,7 +7220,7 @@ dependencies = [ "frame-system", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -7140,7 +7237,7 @@ dependencies = [ "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -7159,7 +7256,7 @@ dependencies = [ "sp-arithmetic", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-weights", ] @@ -7178,7 +7275,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-core 34.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -7198,7 +7295,7 @@ dependencies = [ "sp-arithmetic", "sp-io 38.0.0", "sp-mixnet", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -7216,7 +7313,7 @@ dependencies = [ "sp-core 34.0.0", "sp-io 38.0.0", "sp-mmr-primitives", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -7232,7 +7329,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -7249,7 +7346,7 @@ dependencies = [ "pallet-nfts", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -7267,7 +7364,7 @@ dependencies = [ "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -7294,7 +7391,7 @@ dependencies = [ "scale-info", "sp-arithmetic", "sp-core 34.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -7310,7 +7407,7 @@ dependencies = [ "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -7327,7 +7424,7 @@ dependencies = [ "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-staking 36.0.0", "sp-tracing 17.0.1", ] @@ -7348,7 +7445,7 @@ dependencies = [ "pallet-staking", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-runtime-interface 28.0.0", "sp-staking 36.0.0", ] @@ -7377,7 +7474,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-staking 36.0.0", ] @@ -7401,7 +7498,7 @@ dependencies = [ "pallet-staking", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-staking 36.0.0", ] @@ -7420,7 +7517,7 @@ dependencies = [ "sp-core 34.0.0", "sp-io 38.0.0", "sp-metadata-ir", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -7438,7 +7535,7 @@ dependencies = [ "scale-info", "serde", "sp-core 34.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -7455,7 +7552,7 @@ dependencies = [ "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -7470,7 +7567,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -7489,7 +7586,7 @@ dependencies = [ "sp-arithmetic", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -7504,7 +7601,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -7522,7 +7619,7 @@ dependencies = [ "serde", "sp-arithmetic", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -7539,7 +7636,7 @@ dependencies = [ "serde", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -7567,7 +7664,7 @@ dependencies = [ "sp-api", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-std", "staging-xcm 14.2.0", "staging-xcm-builder", @@ -7583,7 +7680,7 @@ dependencies = [ "frame-system", "parity-wasm", "polkavm-linker 0.10.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "tempfile", "toml 0.8.19", ] @@ -7615,7 +7712,7 @@ dependencies = [ "sp-core 34.0.0", "sp-io 38.0.0", "sp-keystore 0.40.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-tracing 17.0.1", "staging-xcm 14.2.0", "staging-xcm-builder", @@ -7659,7 +7756,7 @@ dependencies = [ "pallet-staking", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-staking 36.0.0", ] @@ -7675,7 +7772,7 @@ dependencies = [ "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -7694,7 +7791,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-arithmetic", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -7713,7 +7810,7 @@ dependencies = [ "sp-arithmetic", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -7730,7 +7827,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-weights", ] @@ -7745,7 +7842,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -7763,7 +7860,7 @@ dependencies = [ "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-session", "sp-staking 36.0.0", "sp-state-machine 0.43.0", @@ -7783,7 +7880,7 @@ dependencies = [ "pallet-staking", "parity-scale-codec", "rand", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-session", ] @@ -7797,7 +7894,7 @@ dependencies = [ "frame-system", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -7815,7 +7912,7 @@ dependencies = [ "scale-info", "sp-arithmetic", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -7836,7 +7933,7 @@ dependencies = [ "serde", "sp-application-crypto 38.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-staking 36.0.0", ] @@ -7875,7 +7972,7 @@ dependencies = [ "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -7892,7 +7989,7 @@ dependencies = [ "sp-api", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-statement-store", ] @@ -7909,7 +8006,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -7927,7 +8024,7 @@ dependencies = [ "scale-info", "sp-inherents", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-storage 21.0.0", "sp-timestamp", ] @@ -7948,7 +8045,7 @@ dependencies = [ "serde", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -7964,7 +8061,7 @@ dependencies = [ "serde", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -7976,7 +8073,7 @@ dependencies = [ "pallet-transaction-payment", "parity-scale-codec", "sp-api", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-weights", ] @@ -7996,7 +8093,7 @@ dependencies = [ "serde", "sp-inherents", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-transaction-storage-proof", ] @@ -8016,7 +8113,7 @@ dependencies = [ "scale-info", "serde", "sp-core 34.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -8034,7 +8131,7 @@ dependencies = [ "pallet-utility", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -8049,7 +8146,7 @@ dependencies = [ "log", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -8065,7 +8162,7 @@ dependencies = [ "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -8080,7 +8177,7 @@ dependencies = [ "log", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -8095,7 +8192,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-api", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -8115,7 +8212,7 @@ dependencies = [ "serde", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "staging-xcm 14.2.0", "staging-xcm-builder", "staging-xcm-executor", @@ -8136,7 +8233,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "staging-xcm 14.2.0", "staging-xcm-builder", "staging-xcm-executor", @@ -8158,7 +8255,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-core 34.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-std", "staging-xcm 14.2.0", "staging-xcm-builder", @@ -8179,7 +8276,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-core 34.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-std", "staging-xcm 14.2.0", "staging-xcm-builder", @@ -8209,7 +8306,7 @@ dependencies = [ "sp-consensus-aura", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "staging-parachain-info", "staging-xcm 14.2.0", "staging-xcm-executor", @@ -8239,7 +8336,7 @@ dependencies = [ "sp-consensus-aura", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-tracing 17.0.1", "staging-parachain-info", "staging-xcm 14.2.0", @@ -8374,6 +8471,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + [[package]] name = "pbkdf2" version = "0.12.2" @@ -8524,7 +8627,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-core 34.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -8540,7 +8643,7 @@ dependencies = [ "scale-info", "serde", "sp-core 34.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-weights", ] @@ -8567,7 +8670,7 @@ dependencies = [ "sp-inherents", "sp-io 38.0.0", "sp-keystore 0.40.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-staking 34.0.0", ] @@ -8594,7 +8697,7 @@ dependencies = [ "sp-inherents", "sp-io 38.0.0", "sp-keystore 0.40.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-staking 36.0.0", ] @@ -8639,7 +8742,7 @@ dependencies = [ "sp-inherents", "sp-io 38.0.0", "sp-npos-elections", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-session", "sp-staking 36.0.0", "staging-xcm 14.2.0", @@ -8702,7 +8805,7 @@ dependencies = [ "sp-inherents", "sp-io 38.0.0", "sp-keystore 0.40.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-session", "sp-staking 36.0.0", "sp-std", @@ -8917,7 +9020,7 @@ dependencies = [ "sp-mmr-primitives", "sp-npos-elections", "sp-offchain", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-runtime-interface 28.0.0", "sp-session", "sp-staking 36.0.0", @@ -8968,7 +9071,7 @@ dependencies = [ "sp-inherents", "sp-io 38.0.0", "sp-offchain", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-session", "sp-storage 21.0.0", "sp-transaction-pool", @@ -9251,13 +9354,16 @@ version = "0.5.0" dependencies = [ "anyhow", "assert_cmd", + "axum", "clap", "cliclack", "console", + "contract-extrinsics", "dirs", "duct", "env_logger 0.11.5", "git2", + "open", "os_info", "pop-common", "pop-contracts", @@ -9265,13 +9371,17 @@ dependencies = [ "pop-telemetry", "predicates", "reqwest 0.12.9", + "serde", "serde_json", "sp-core 32.0.0", "sp-weights", "strum 0.26.3", "strum_macros 0.26.4", + "subxt", + "subxt-signer", "tempfile", "tokio", + "tower-http 0.6.2", "url", ] @@ -9328,6 +9438,8 @@ dependencies = [ "sp-weights", "strum 0.26.3", "strum_macros 0.26.4", + "subxt", + "subxt-signer", "tar", "tempfile", "thiserror 1.0.69", @@ -9346,7 +9458,6 @@ dependencies = [ "duct", "flate2", "glob", - "hex", "indexmap 2.7.0", "mockito", "pop-common", @@ -9354,6 +9465,7 @@ dependencies = [ "scale-info", "scale-value", "serde_json", + "sp-core 32.0.0", "strum 0.26.3", "strum_macros 0.26.4", "subxt", @@ -9710,9 +9822,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ "bitflags 2.6.0", ] @@ -9990,7 +10102,7 @@ dependencies = [ "polkadot-runtime-common", "smallvec", "sp-core 34.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-weights", "staging-xcm 14.2.0", "staging-xcm-builder", @@ -10090,7 +10202,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.23", + "semver 1.0.24", ] [[package]] @@ -10134,9 +10246,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.19" +version = "0.23.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" +checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" dependencies = [ "log", "once_cell", @@ -10192,9 +10304,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" [[package]] name = "rustls-platform-verifier" @@ -10207,7 +10319,7 @@ dependencies = [ "jni", "log", "once_cell", - "rustls 0.23.19", + "rustls 0.23.20", "rustls-native-certs 0.7.3", "rustls-platform-verifier-android", "rustls-webpki 0.102.8", @@ -10842,9 +10954,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" dependencies = [ "serde", ] @@ -10936,6 +11048,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + [[package]] name = "serde_repr" version = "0.1.19" @@ -11185,7 +11307,7 @@ dependencies = [ "enumn", "parity-scale-codec", "paste", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -11334,7 +11456,7 @@ dependencies = [ "snowbridge-milagro-bls", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-std", "ssz_rs", "ssz_rs_derive", @@ -11358,7 +11480,7 @@ dependencies = [ "sp-arithmetic", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-std", "staging-xcm 14.2.0", "staging-xcm-builder", @@ -11381,7 +11503,7 @@ dependencies = [ "serde", "serde-big-array", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-std", ] @@ -11409,7 +11531,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-core 34.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -11446,7 +11568,7 @@ dependencies = [ "snowbridge-pallet-ethereum-client-fixtures", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-std", "static_assertions", ] @@ -11486,7 +11608,7 @@ dependencies = [ "snowbridge-router-primitives", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-std", "staging-xcm 14.2.0", "staging-xcm-executor", @@ -11524,7 +11646,7 @@ dependencies = [ "sp-arithmetic", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-std", ] @@ -11543,7 +11665,7 @@ dependencies = [ "snowbridge-core", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-std", "staging-xcm 14.2.0", "staging-xcm-executor", @@ -11563,7 +11685,7 @@ dependencies = [ "snowbridge-core", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-std", "staging-xcm 14.2.0", "staging-xcm-executor", @@ -11612,7 +11734,7 @@ dependencies = [ "sp-core 34.0.0", "sp-io 38.0.0", "sp-keyring", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "staging-parachain-info", "staging-xcm 14.2.0", "staging-xcm-executor", @@ -11671,7 +11793,7 @@ dependencies = [ "sp-core 34.0.0", "sp-externalities 0.29.0", "sp-metadata-ir", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-runtime-interface 28.0.0", "sp-state-machine 0.43.0", "sp-trie 37.0.0", @@ -11747,7 +11869,7 @@ dependencies = [ "scale-info", "sp-api", "sp-application-crypto 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -11758,7 +11880,7 @@ checksum = "74738809461e3d4bd707b5b94e0e0c064a623a74a6a8fe5c98514417a02858dd" dependencies = [ "sp-api", "sp-inherents", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -11774,7 +11896,7 @@ dependencies = [ "sp-application-crypto 38.0.0", "sp-consensus-slots", "sp-inherents", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-timestamp", ] @@ -11793,7 +11915,7 @@ dependencies = [ "sp-consensus-slots", "sp-core 34.0.0", "sp-inherents", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-timestamp", ] @@ -11814,7 +11936,7 @@ dependencies = [ "sp-io 38.0.0", "sp-keystore 0.40.0", "sp-mmr-primitives", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-weights", "strum 0.26.3", ] @@ -11834,7 +11956,7 @@ dependencies = [ "sp-application-crypto 38.0.0", "sp-core 34.0.0", "sp-keystore 0.40.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -11846,7 +11968,7 @@ dependencies = [ "parity-scale-codec", "sp-api", "sp-core 34.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -12112,7 +12234,7 @@ dependencies = [ "scale-info", "serde_json", "sp-api", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -12125,7 +12247,7 @@ dependencies = [ "impl-trait-for-tuples", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "thiserror 1.0.69", ] @@ -12190,7 +12312,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c0e20624277f578b27f44ecfbe2ebc2e908488511ee2c900c5281599f700ab3" dependencies = [ "sp-core 34.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "strum 0.26.3", ] @@ -12265,7 +12387,7 @@ dependencies = [ "sp-api", "sp-core 34.0.0", "sp-debug-derive", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "thiserror 1.0.69", ] @@ -12280,7 +12402,7 @@ dependencies = [ "serde", "sp-arithmetic", "sp-core 34.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -12291,7 +12413,7 @@ checksum = "2d9de237d72ecffd07f90826eef18360208b16d8de939d54e61591fac0fcbf99" dependencies = [ "sp-api", "sp-core 34.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -12332,9 +12454,9 @@ dependencies = [ [[package]] name = "sp-runtime" -version = "39.0.2" +version = "39.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658f23be7c79a85581029676a73265c107c5469157e3444c8c640fdbaa8bfed0" +checksum = "ef567865c042b9002dfa44b8fc850fe611038acdf1e382e539495015f60f692f" dependencies = [ "docify", "either", @@ -12442,7 +12564,7 @@ dependencies = [ "sp-api", "sp-core 34.0.0", "sp-keystore 0.40.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-staking 36.0.0", ] @@ -12457,7 +12579,7 @@ dependencies = [ "scale-info", "serde", "sp-core 34.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -12471,7 +12593,7 @@ dependencies = [ "scale-info", "serde", "sp-core 34.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -12535,7 +12657,7 @@ dependencies = [ "sp-core 34.0.0", "sp-crypto-hashing", "sp-externalities 0.29.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-runtime-interface 28.0.0", "thiserror 1.0.69", "x25519-dalek", @@ -12583,7 +12705,7 @@ dependencies = [ "async-trait", "parity-scale-codec", "sp-inherents", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "thiserror 1.0.69", ] @@ -12619,7 +12741,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc4bf251059485a7dd38fe4afeda8792983511cc47f342ff4695e2dcae6b5247" dependencies = [ "sp-api", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -12633,7 +12755,7 @@ dependencies = [ "scale-info", "sp-core 34.0.0", "sp-inherents", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-trie 37.0.0", ] @@ -12697,7 +12819,7 @@ dependencies = [ "scale-info", "serde", "sp-crypto-hashing-proc-macro", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-std", "sp-version-proc-macro", "thiserror 1.0.69", @@ -12828,7 +12950,7 @@ dependencies = [ "frame-system", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", ] [[package]] @@ -12865,7 +12987,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-weights", "xcm-procedural 10.1.0", ] @@ -12887,7 +13009,7 @@ dependencies = [ "scale-info", "sp-arithmetic", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-weights", "staging-xcm 14.2.0", "staging-xcm-executor", @@ -12908,7 +13030,7 @@ dependencies = [ "sp-arithmetic", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-weights", "staging-xcm 14.2.0", "tracing", @@ -13397,7 +13519,7 @@ dependencies = [ "polkadot-core-primitives", "rococo-runtime-constants", "smallvec", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "staging-xcm 14.2.0", "westend-runtime-constants", ] @@ -13593,7 +13715,7 @@ version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" dependencies = [ - "rustls 0.23.19", + "rustls 0.23.20", "tokio", ] @@ -13733,6 +13855,22 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper 1.0.2", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower-http" version = "0.4.4" @@ -13746,9 +13884,34 @@ dependencies = [ "futures-util", "http 0.2.12", "http-body 0.4.6", - "http-range-header", + "http-range-header 0.3.1", + "mime", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697" +dependencies = [ + "bitflags 2.6.0", + "bytes", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", + "http-body-util", + "http-range-header 0.4.2", + "httpdate", "mime", + "mime_guess", + "percent-encoding", "pin-project-lite", + "tokio", + "tokio-util", "tower-layer", "tower-service", "tracing", @@ -14399,7 +14562,7 @@ dependencies = [ "bitflags 2.6.0", "hashbrown 0.14.5", "indexmap 2.7.0", - "semver 1.0.23", + "semver 1.0.24", "serde", ] @@ -14647,7 +14810,7 @@ dependencies = [ "polkadot-runtime-common", "smallvec", "sp-core 34.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-weights", "staging-xcm 14.2.0", "staging-xcm-builder", @@ -15092,7 +15255,7 @@ dependencies = [ "polkadot-runtime-parachains", "scale-info", "sp-io 38.0.0", - "sp-runtime 39.0.2", + "sp-runtime 39.0.3", "sp-std", "staging-xcm 14.2.0", "staging-xcm-builder", @@ -15236,9 +15399,9 @@ dependencies = [ [[package]] name = "zombienet-configuration" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22860eef7e651d6e0aa7e37fc3bcba3c2c8b7bd1c140d7ea929caacf2b7fc726" +checksum = "d716b3ff8112d98ced15f53b0c72454f8cde533fe2b68bb04379228961efbd80" dependencies = [ "anyhow", "lazy_static", @@ -15256,9 +15419,9 @@ dependencies = [ [[package]] name = "zombienet-orchestrator" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b19b1b2fd2db3153155f21cb84cdd8e5d6faefc3043353b8c90661c44f4660da" +checksum = "4098a7d33b729b59e32c41a87aa4d484bd1b8771a059bbd4edfb4d430b3b2d74" dependencies = [ "anyhow", "async-trait", @@ -15289,9 +15452,9 @@ dependencies = [ [[package]] name = "zombienet-prom-metrics-parser" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61ce9c6b2d43be864ad34328d05794079381807f5d77c737a062486966347f" +checksum = "961e30be45b34f6ebeabf29ee2f47b0cd191ea62e40c064752572207509a6f5c" dependencies = [ "pest", "pest_derive", @@ -15300,9 +15463,9 @@ dependencies = [ [[package]] name = "zombienet-provider" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c99cc7c143f1145bda2b2f5a945b8040a898a85fc029dba51f220395a7188d9d" +checksum = "ab0f7f01780b7c99a6c40539d195d979f234305f32808d547438b50829d44262" dependencies = [ "anyhow", "async-trait", @@ -15331,9 +15494,9 @@ dependencies = [ [[package]] name = "zombienet-sdk" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09e5abdad4ad32c1c06cb8fdc4507db026f65987cb5c46ae4224118cc496097b" +checksum = "99a3c5f2d657235b3ab7dc384677e63cde21983029e99106766ecd49e9f8d7f3" dependencies = [ "async-trait", "futures", @@ -15349,9 +15512,9 @@ dependencies = [ [[package]] name = "zombienet-support" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e3310631948f8bb4d394c160c4b063889a4e0c6beadd6b95f9b62d1616ab93c" +checksum = "296f887ea88e07edd771f8e1d0dec5297a58b422f4b884a6292a21ebe03277cb" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 86a7a928d..44dd1fbd3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,13 +50,12 @@ subxt = "0.38.0" ink_env = "5.0.0" sp-core = "32.0.0" sp-weights = "31.0.0" -contract-build = "5.0.0" -contract-extrinsics = "5.0.0" -contract-transcode = "5.0.0" 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" +contract-extrinsics = "5.0.2" +contract-transcode = "5.0.2" heck = "0.5.0" -hex = { version = "0.4.3", default-features = false } # parachains askama = "0.12" @@ -77,3 +76,8 @@ console = "0.15" os_info = { version = "3", default-features = false } strum = "0.26" strum_macros = "0.26" + +# wallet-integration +axum = "0.7.9" +open = "5.3.1" +tower-http = "0.6.2" diff --git a/crates/pop-cli/Cargo.toml b/crates/pop-cli/Cargo.toml index c9761b642..34e51111d 100644 --- a/crates/pop-cli/Cargo.toml +++ b/crates/pop-cli/Cargo.toml @@ -18,6 +18,7 @@ duct.workspace = true env_logger.workspace = true os_info.workspace = true reqwest.workspace = true +serde = { workspace = true, features = ["derive"] } serde_json.workspace = true tempfile.workspace = true tokio.workspace = true @@ -27,12 +28,12 @@ url.workspace = true clap.workspace = true cliclack.workspace = true console.workspace = true +sp-core.workspace = true strum.workspace = true strum_macros.workspace = true # contracts pop-contracts = { path = "../pop-contracts", version = "0.5.0", optional = true } -sp-core = { workspace = true, optional = true } sp-weights = { workspace = true, optional = true } # parachains @@ -46,12 +47,21 @@ pop-telemetry = { path = "../pop-telemetry", version = "0.5.0", optional = true # common pop-common = { path = "../pop-common", version = "0.5.0" } +# wallet-integration +axum.workspace = true +open.workspace = true +tower-http = { workspace = true, features = ["fs", "cors"] } + [dev-dependencies] assert_cmd.workspace = true +contract-extrinsics.workspace = true predicates.workspace = true +subxt.workspace = true +subxt-signer.workspace = true +sp-weights.workspace = true [features] default = ["contract", "parachain", "telemetry"] -contract = ["dep:pop-contracts", "dep:sp-core", "dep:sp-weights", "dep:dirs"] +contract = ["dep:pop-contracts", "dep:sp-weights", "dep:dirs"] parachain = ["dep:pop-parachains", "dep:dirs"] telemetry = ["dep:pop-telemetry"] diff --git a/crates/pop-cli/src/assets/index.html b/crates/pop-cli/src/assets/index.html new file mode 100644 index 000000000..d1d5a8ef7 --- /dev/null +++ b/crates/pop-cli/src/assets/index.html @@ -0,0 +1,1024 @@ + + + + + + + + Pop CLI Signing Portal + + + + + +
+ + + diff --git a/crates/pop-cli/src/cli.rs b/crates/pop-cli/src/cli.rs index 6c6bb3bce..f98c934e9 100644 --- a/crates/pop-cli/src/cli.rs +++ b/crates/pop-cli/src/cli.rs @@ -150,7 +150,6 @@ impl traits::Confirm for Confirm { /// A input prompt using cliclack. struct Input(cliclack::Input); - impl traits::Input for Input { /// Sets the default value for the input. fn default_input(mut self, value: &str) -> Self { diff --git a/crates/pop-cli/src/commands/call/chain.rs b/crates/pop-cli/src/commands/call/chain.rs index 36b4dbd1e..41348826f 100644 --- a/crates/pop-cli/src/commands/call/chain.rs +++ b/crates/pop-cli/src/commands/call/chain.rs @@ -2,14 +2,17 @@ use std::path::Path; -use crate::cli::{self, traits::*}; +use crate::{ + cli::{self, traits::*}, + common::wallet::{prompt_to_use_wallet, request_signature}, +}; 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, supported_actions, Action, CallData, DynamicPayload, Function, - OnlineClient, Pallet, Param, SubstrateConfig, + sign_and_submit_extrinsic, submit_signed_extrinsic, supported_actions, Action, CallData, + DynamicPayload, Function, OnlineClient, Pallet, Param, Payload, SubstrateConfig, }; use url::Url; @@ -40,6 +43,15 @@ pub struct CallChainCommand { /// - with a password "//Alice///SECRET_PASSWORD" #[arg(short, long)] suri: Option, + /// Use a browser extension wallet to sign the extrinsic. + #[arg( + name = "use-wallet", + short = 'w', + long, + default_value = "false", + conflicts_with = "suri" + )] + use_wallet: bool, /// SCALE encoded bytes representing the call data of the extrinsic. #[arg(name = "call", short, long, conflicts_with_all = ["pallet", "function", "args"])] call_data: Option, @@ -95,7 +107,14 @@ impl CallChainCommand { }; // Sign and submit the extrinsic. - if let Err(e) = call.submit_extrinsic(&chain.client, &chain.url, xt, &mut cli).await { + 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 + } else { + call.submit_extrinsic(&chain.client, &chain.url, xt, &mut cli).await + }; + + if let Err(e) = result { display_message(&e.to_string(), false, &mut cli)?; break; } @@ -197,12 +216,8 @@ impl CallChainCommand { // sudo. self.configure_sudo(chain, cli)?; - // Resolve who is signing the extrinsic. - let suri = match self.suri.as_ref() { - Some(suri) => suri.clone(), - None => - cli.input("Signer of the extrinsic:").default_input(DEFAULT_URI).interact()?, - }; + let (use_wallet, suri) = self.determine_signing_method(cli)?; + self.use_wallet = use_wallet; return Ok(Call { function: function.clone(), @@ -210,6 +225,7 @@ impl CallChainCommand { suri, skip_confirm: self.skip_confirm, sudo: self.sudo, + use_wallet: self.use_wallet, }); } } @@ -222,11 +238,18 @@ impl CallChainCommand { call_data: &str, cli: &mut impl Cli, ) -> Result<()> { - // Resolve who is signing the extrinsic. - let suri = match self.suri.as_ref() { - Some(suri) => suri, - None => &cli.input("Signer of the extrinsic:").default_input(DEFAULT_URI).interact()?, - }; + let (use_wallet, suri) = self.determine_signing_method(cli)?; + + // Perform signing steps with wallet integration and return early. + if use_wallet { + let call_data_bytes = + decode_call_data(call_data).map_err(|err| anyhow!("{}", format!("{err:?}")))?; + submit_extrinsic_with_wallet(client, url, call_data_bytes, cli) + .await + .map_err(|err| anyhow!("{}", format!("{err:?}")))?; + display_message("Call complete.", true, cli)?; + return Ok(()); + } cli.info(format!("Encoded call data: {}", call_data))?; if !self.skip_confirm && !cli.confirm("Do you want to submit the extrinsic?") @@ -244,7 +267,7 @@ impl CallChainCommand { spinner.start("Signing and submitting the extrinsic and then waiting for finalization, please be patient..."); let call_data_bytes = decode_call_data(call_data).map_err(|err| anyhow!("{}", format!("{err:?}")))?; - let result = sign_and_submit_extrinsic(client, url, CallData::new(call_data_bytes), suri) + let result = sign_and_submit_extrinsic(client, url, CallData::new(call_data_bytes), &suri) .await .map_err(|err| anyhow!("{}", format!("{err:?}")))?; @@ -253,6 +276,29 @@ impl CallChainCommand { Ok(()) } + // Resolve who is signing the extrinsic. If a `suri` was provided via the command line, + // skip the prompt. + fn determine_signing_method(&self, cli: &mut impl Cli) -> Result<(bool, String)> { + let mut use_wallet = self.use_wallet; + let suri = match self.suri.as_ref() { + Some(suri) => suri.clone(), + None => + if !self.use_wallet { + if prompt_to_use_wallet(cli)? { + use_wallet = true; + DEFAULT_URI.to_string() + } else { + cli.input("Signer of the extrinsic:") + .default_input(DEFAULT_URI) + .interact()? + } + } else { + DEFAULT_URI.to_string() + }, + }; + Ok((use_wallet, suri)) + } + // Checks if the chain has the Sudo pallet and prompts the user to confirm if they want to // execute the call via `sudo`. fn configure_sudo(&mut self, chain: &Chain, cli: &mut impl Cli) -> Result<()> { @@ -283,6 +329,7 @@ impl CallChainCommand { self.function = None; self.args.clear(); self.sudo = false; + self.use_wallet = false; } // Function to check if all required fields are specified. @@ -334,6 +381,8 @@ struct Call { /// - for a dev account "//Alice" /// - with a password "//Alice///SECRET_PASSWORD" suri: String, + /// Whether to use your browser wallet to sign the extrinsic. + use_wallet: bool, /// Whether to automatically sign and submit the extrinsic without prompting for confirmation. skip_confirm: bool, /// Whether to dispatch the function call with `Root` origin. @@ -411,7 +460,12 @@ impl Call { .collect(); full_message.push_str(&format!(" --args {}", args.join(" "))); } - full_message.push_str(&format!(" --url {} --suri {}", chain.url, self.suri)); + full_message.push_str(&format!(" --url {}", chain.url)); + if self.use_wallet { + full_message.push_str(" --use-wallet"); + } else { + full_message.push_str(&format!(" --suri {}", self.suri)); + } if self.sudo { full_message.push_str(" --sudo"); } @@ -419,6 +473,32 @@ impl Call { } } +// Sign and submit an extrinsic using wallet integration. +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 { + display_message("No signed payload received.", false, cli)?; + } + Ok(()) +} + // 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 { @@ -589,7 +669,7 @@ fn parse_function_name(name: &str) -> Result { #[cfg(test)] mod tests { use super::*; - use crate::cli::MockCli; + use crate::{cli::MockCli, common::wallet::USE_WALLET_PROMPT}; use tempfile::tempdir; use url::Url; @@ -642,7 +722,7 @@ mod tests { ) .expect_input("The value for `remark` might be too large to enter. You may enter the path to a file instead.", "0x11".into()) .expect_confirm("Would you like to dispatch this function call with `Root` origin?", true) - .expect_input("Signer of the extrinsic:", "//Bob".into()); + .expect_confirm(USE_WALLET_PROMPT, true); let chain = call_config.configure_chain(&mut cli).await?; assert_eq!(chain.url, Url::parse(POP_NETWORK_TESTNET_URL)?); @@ -651,9 +731,10 @@ mod tests { assert_eq!(call_chain.function.pallet, "System"); assert_eq!(call_chain.function.name, "remark"); assert_eq!(call_chain.args, ["0x11".to_string()].to_vec()); - assert_eq!(call_chain.suri, "//Bob"); + assert_eq!(call_chain.suri, "//Alice"); // Default value + assert!(call_chain.use_wallet); assert!(call_chain.sudo); - assert_eq!(call_chain.display(&chain), "pop call chain --pallet System --function remark --args \"0x11\" --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Bob --sudo"); + assert_eq!(call_chain.display(&chain), "pop call chain --pallet System --function remark --args \"0x11\" --url wss://rpc1.paseo.popnetwork.xyz/ --use-wallet --sudo"); cli.verify() } @@ -714,6 +795,7 @@ mod tests { }, args: vec!["0x11".to_string()].to_vec(), suri: DEFAULT_URI.to_string(), + use_wallet: false, skip_confirm: false, sudo: false, }; @@ -753,6 +835,7 @@ mod tests { function: find_dispatchable_by_name(&pallets, "System", "remark")?.clone(), args: vec!["0x11".to_string()].to_vec(), suri: DEFAULT_URI.to_string(), + use_wallet: false, skip_confirm: false, sudo: false, }; @@ -776,11 +859,13 @@ mod tests { args: vec![].to_vec(), url: Some(Url::parse(POP_NETWORK_TESTNET_URL)?), suri: None, + use_wallet: false, skip_confirm: false, call_data: Some("0x00000411".to_string()), sudo: false, }; let mut cli = MockCli::new() + .expect_confirm(USE_WALLET_PROMPT, false) .expect_input("Signer of the extrinsic:", "//Bob".into()) .expect_confirm("Do you want to submit the extrinsic?", false) .expect_outro_cancel("Extrinsic with call data 0x00000411 was not submitted."); @@ -803,8 +888,9 @@ mod tests { pallet: None, function: None, args: vec![].to_vec(), - url: Some(Url::parse("wss://polkadot-rpc.publicnode.com")?), + url: Some(Url::parse(POLKADOT_NETWORK_URL)?), suri: Some("//Alice".to_string()), + use_wallet: false, skip_confirm: false, call_data: Some("0x00000411".to_string()), sudo: true, @@ -836,6 +922,7 @@ mod tests { function: Some("remark".to_string()), args: vec!["0x11".to_string()].to_vec(), url: Some(Url::parse(POP_NETWORK_TESTNET_URL)?), + use_wallet: true, suri: Some(DEFAULT_URI.to_string()), skip_confirm: false, call_data: None, @@ -846,6 +933,7 @@ mod tests { assert_eq!(call_config.function, None); assert_eq!(call_config.args.len(), 0); assert!(!call_config.sudo); + assert!(!call_config.use_wallet); Ok(()) } @@ -857,6 +945,7 @@ mod tests { args: vec!["0x11".to_string()].to_vec(), url: Some(Url::parse(POP_NETWORK_TESTNET_URL)?), suri: Some(DEFAULT_URI.to_string()), + use_wallet: false, skip_confirm: false, call_data: None, sudo: false, @@ -875,6 +964,7 @@ mod tests { args: vec!["2000".to_string(), "0x1".to_string(), "0x12".to_string()].to_vec(), url: Some(Url::parse(POP_NETWORK_TESTNET_URL)?), suri: Some(DEFAULT_URI.to_string()), + use_wallet: false, call_data: None, skip_confirm: false, sudo: false, diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index e83a871d2..ab7a826fd 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -2,17 +2,22 @@ use crate::{ cli::{self, traits::*}, - common::contracts::has_contract_been_built, + common::{ + contracts::has_contract_been_built, + wallet::{prompt_to_use_wallet, request_signature}, + }, }; use anyhow::{anyhow, Result}; use clap::Args; use cliclack::spinner; +use pop_common::{DefaultConfig, Keypair}; use pop_contracts::{ - build_smart_contract, call_smart_contract, dry_run_call, dry_run_gas_estimate_call, - get_messages, parse_account, set_up_call, CallOpts, Verbosity, + build_smart_contract, call_smart_contract, call_smart_contract_from_signed_payload, + dry_run_call, dry_run_gas_estimate_call, get_call_payload, get_message, get_messages, + parse_account, set_up_call, CallExec, CallOpts, DefaultEnvironment, Verbosity, }; use sp_weights::Weight; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; const DEFAULT_URL: &str = "ws://localhost:9944/"; const DEFAULT_URI: &str = "//Alice"; @@ -54,6 +59,15 @@ pub struct CallContractCommand { /// - with a password "//Alice///SECRET_PASSWORD" #[arg(short, long, default_value = DEFAULT_URI)] suri: String, + /// Use a browser extension wallet to sign the extrinsic. + #[arg( + name = "use-wallet", + long, + short = 'w', + default_value = "false", + conflicts_with = "suri" + )] + use_wallet: bool, /// Submit an extrinsic for on-chain execution. #[arg(short = 'x', long)] execute: bool, @@ -115,14 +129,19 @@ impl CallContractCommand { full_message.push_str(&format!(" --gas {}", gas_limit)); } if let Some(proof_size) = self.proof_size { - full_message.push_str(&format!(" --proof_size {}", proof_size)); + full_message.push_str(&format!(" --proof-size {}", proof_size)); + } + full_message.push_str(&format!(" --url {}", self.url)); + if self.use_wallet { + full_message.push_str(" --use-wallet"); + } else { + full_message.push_str(&format!(" --suri {}", self.suri)); } - full_message.push_str(&format!(" --url {} --suri {}", self.url, self.suri)); if self.execute { full_message.push_str(" --execute"); } if self.dry_run { - full_message.push_str(" --dry_run"); + full_message.push_str(" --dry-run"); } full_message } @@ -165,7 +184,7 @@ impl CallContractCommand { fn is_contract_build_required(&self) -> bool { self.path .as_ref() - .map(|p| p.is_dir() && !has_contract_been_built(Some(&p))) + .map(|p| p.is_dir() && !has_contract_been_built(Some(p))) .unwrap_or_default() } @@ -303,18 +322,22 @@ impl CallContractCommand { self.proof_size = proof_size_input.parse::().ok(); // If blank or bad input, estimate it. } - // Resolve who is calling the contract. - if self.suri == DEFAULT_URI { - // Prompt for uri. - self.suri = cli - .input("Signer calling the contract:") - .placeholder("//Alice") - .default_input("//Alice") - .interact()?; - }; + // Resolve who is calling the contract. If a `suri` was provided via the command line, skip + // the prompt. + if self.suri == DEFAULT_URI && !self.use_wallet && message.mutates { + if prompt_to_use_wallet(cli)? { + self.use_wallet = true; + } else { + self.suri = cli + .input("Signer calling the contract:") + .placeholder("//Alice") + .default_input("//Alice") + .interact()?; + }; + } // Finally prompt for confirmation. - let is_call_confirmed = if message.mutates && !self.dev_mode { + let is_call_confirmed = if message.mutates && !self.dev_mode && !self.use_wallet { cli.confirm("Do you want to execute the call? (Selecting 'No' will perform a dry run)") .initial_value(true) .interact()? @@ -340,6 +363,14 @@ impl CallContractCommand { return Err(anyhow!("Please specify the message to call.")); }, }; + // Disable wallet signing and display warning if the call is read-only. + let message_metadata = + get_message(self.path.as_deref().unwrap_or_else(|| Path::new("./")), &message)?; + if !message_metadata.mutates && self.use_wallet { + cli.warning("NOTE: Signing is not required for this read-only call. The '--use-wallet' flag will be ignored.")?; + self.use_wallet = false; + } + let contract = match &self.contract { Some(contract) => contract.to_string(), None => { @@ -366,6 +397,12 @@ impl CallContractCommand { }, }; + // Perform signing steps with wallet integration, skipping secure signing for query-only + // operations. + if self.use_wallet { + self.execute_with_wallet(call_exec, cli).await?; + return self.finalize_execute_call(cli, prompt_to_repeat_call).await; + } if self.dry_run { let spinner = spinner(); spinner.start("Doing a dry run to estimate the gas..."); @@ -386,6 +423,7 @@ impl CallContractCommand { let spinner = spinner(); spinner.start("Calling the contract..."); let call_dry_run_result = dry_run_call(&call_exec).await?; + spinner.stop(""); cli.info(format!("Result: {}", call_dry_run_result))?; cli.warning("Your call has not been executed.")?; } else { @@ -414,7 +452,15 @@ impl CallContractCommand { cli.info(call_result)?; } + self.finalize_execute_call(cli, prompt_to_repeat_call).await + } + /// Finalize the current call, prompting the user to repeat or conclude the process. + async fn finalize_execute_call( + &mut self, + cli: &mut impl Cli, + prompt_to_repeat_call: bool, + ) -> Result<()> { // Prompt for any additional calls. if !prompt_to_repeat_call { display_message("Call completed successfully!", true, cli)?; @@ -435,12 +481,56 @@ impl CallContractCommand { } } + /// Execute the smart contract call using wallet integration. + async fn execute_with_wallet( + &self, + call_exec: CallExec, + cli: &mut impl Cli, + ) -> Result<()> { + let call_data = self.get_contract_data(&call_exec).map_err(|err| { + anyhow!("An error occurred getting the call data: {}", err.to_string()) + })?; + + let maybe_payload = request_signature(call_data, self.url.to_string()).await?; + if let Some(payload) = maybe_payload { + cli.success("Signed payload received.")?; + let spinner = spinner(); + spinner + .start("Calling the contract and waiting for finalization, please be patient..."); + + let call_result = + call_smart_contract_from_signed_payload(call_exec, payload, &self.url) + .await + .map_err(|err| anyhow!("{} {}", "ERROR:", format!("{err:?}")))?; + + cli.info(call_result)?; + } else { + display_message("No signed payload received.", false, cli)?; + } + Ok(()) + } + + // Get the call data. + fn get_contract_data( + &self, + call_exec: &CallExec, + ) -> anyhow::Result> { + let weight_limit = if self.gas_limit.is_some() && self.proof_size.is_some() { + Weight::from_parts(self.gas_limit.unwrap(), self.proof_size.unwrap()) + } else { + Weight::zero() + }; + let call_data = get_call_payload(call_exec, weight_limit)?; + Ok(call_data) + } + /// Resets message specific fields to default values for a new call. fn reset_for_new_call(&mut self) { self.message = None; self.value = DEFAULT_PAYABLE_VALUE.to_string(); self.gas_limit = None; self.proof_size = None; + self.use_wallet = false; } } @@ -456,7 +546,7 @@ fn display_message(message: &str, success: bool, cli: &mut impl Cli) -> Result<( #[cfg(test)] mod tests { use super::*; - use crate::cli::MockCli; + use crate::{cli::MockCli, common::wallet::USE_WALLET_PROMPT}; use pop_contracts::{mock_build_process, new_environment}; use std::{env, fs::write}; use url::Url; @@ -482,6 +572,7 @@ mod tests { proof_size: None, url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, suri: "//Alice".to_string(), + use_wallet: false, dry_run: false, execute: false, dev_mode: false, @@ -517,13 +608,14 @@ mod tests { proof_size: Some(10), url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, suri: "//Alice".to_string(), + use_wallet: false, dry_run: true, execute: false, dev_mode: false, }; call_config.configure(&mut cli, false).await?; assert_eq!(call_config.display(), format!( - "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message flip --gas 100 --proof_size 10 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --dry_run", + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message flip --gas 100 --proof-size 10 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --dry-run", temp_dir.path().join("testing").display().to_string(), )); // Contract deployed on Pop Network testnet, test dry-run @@ -553,13 +645,14 @@ mod tests { proof_size: Some(10), url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, suri: "//Alice".to_string(), + use_wallet: false, dry_run: true, execute: false, dev_mode: false, }; call_config.configure(&mut cli, false).await?; assert_eq!(call_config.display(), format!( - "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message flip --gas 100 --proof_size 10 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --dry_run", + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message flip --gas 100 --proof-size 10 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --dry-run", current_dir.join("pop-contracts/tests/files/testing.contract").display().to_string(), )); // Contract deployed on Pop Network testnet, test dry-run @@ -569,7 +662,7 @@ mod tests { call_config.path = Some(current_dir.join("pop-contracts/tests/files/testing.json")); call_config.configure(&mut cli, false).await?; assert_eq!(call_config.display(), format!( - "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message flip --gas 100 --proof_size 10 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --dry_run", + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message flip --gas 100 --proof-size 10 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --dry-run", current_dir.join("pop-contracts/tests/files/testing.json").display().to_string(), )); @@ -577,7 +670,7 @@ mod tests { call_config.path = Some(current_dir.join("pop-contracts/tests/files/testing.wasm")); call_config.configure(&mut cli, false).await?; assert_eq!(call_config.display(), format!( - "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message flip --gas 100 --proof_size 10 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --dry_run", + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message flip --gas 100 --proof-size 10 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --dry-run", current_dir.join("pop-contracts/tests/files/testing.wasm").display().to_string(), )); // Contract deployed on Pop Network testnet, test dry-run @@ -608,10 +701,6 @@ mod tests { "Do you want to perform another call using the existing smart contract?", true, ) - .expect_confirm( - "Do you want to perform another call using the existing smart contract?", - false, - ) .expect_select( "Select the message to call:", Some(false), @@ -619,12 +708,16 @@ mod tests { Some(items), 1, // "get" message ) - .expect_input("Signer calling the contract:", "//Alice".into()) .expect_info(format!( "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message get --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice", temp_dir.path().join("testing").display().to_string(), )) + .expect_warning("NOTE: Signing is not required for this read-only call. The '--use-wallet' flag will be ignored.") .expect_warning("Your call has not been executed.") + .expect_confirm( + "Do you want to perform another call using the existing smart contract?", + false, + ) .expect_outro("Contract calling complete."); // Contract deployed on Pop Network testnet, test get @@ -638,6 +731,7 @@ mod tests { proof_size: None, url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, suri: "//Alice".to_string(), + use_wallet: true, dry_run: false, execute: false, dev_mode: false, @@ -688,7 +782,6 @@ mod tests { "Provide the on-chain contract address:", "15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".into(), ) - .expect_input("Signer calling the contract:", "//Alice".into()) .expect_info(format!( "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message get --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice", temp_dir.path().join("testing").display().to_string(), @@ -704,6 +797,7 @@ mod tests { proof_size: None, url: Url::parse(DEFAULT_URL)?, suri: DEFAULT_URI.to_string(), + use_wallet: false, dry_run: false, execute: false, dev_mode: false, @@ -750,14 +844,6 @@ mod tests { ]; // The inputs are processed in reverse order. let mut cli = MockCli::new() - .expect_confirm("Do you want to execute the call? (Selecting 'No' will perform a dry run)", true) - .expect_select( - "Select the message to call:", - Some(false), - true, - Some(items), - 2, // "specific_flip" message - ) .expect_input( "Where is your project or contract artifact located?", temp_dir.path().join("testing").display().to_string(), @@ -770,14 +856,21 @@ mod tests { "Provide the on-chain contract address:", "15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".into(), ) + .expect_select( + "Select the message to call:", + Some(false), + true, + Some(items), + 2, // "specific_flip" message + ) .expect_input("Enter the value for the parameter: new_value", "true".into()) // Args for specific_flip .expect_input("Enter the value for the parameter: number", "2".into()) // Args for specific_flip .expect_input("Value to transfer to the call:", "50".into()) // Only if payable .expect_input("Enter the gas limit:", "".into()) // Only if call .expect_input("Enter the proof size limit:", "".into()) // Only if call - .expect_input("Signer calling the contract:", "//Alice".into()) + .expect_confirm(USE_WALLET_PROMPT, true) .expect_info(format!( - "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args \"true\", \"2\" --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args \"true\", \"2\" --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --use-wallet --execute", temp_dir.path().join("testing").display().to_string(), )); @@ -791,6 +884,7 @@ mod tests { proof_size: None, url: Url::parse(DEFAULT_URL)?, suri: DEFAULT_URI.to_string(), + use_wallet: false, dry_run: false, execute: false, dev_mode: false, @@ -809,10 +903,11 @@ mod tests { assert_eq!(call_config.proof_size, None); assert_eq!(call_config.url.to_string(), "wss://rpc1.paseo.popnetwork.xyz/"); assert_eq!(call_config.suri, "//Alice"); + assert!(call_config.use_wallet); assert!(call_config.execute); assert!(!call_config.dry_run); assert_eq!(call_config.display(), format!( - "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args \"true\", \"2\" --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", + "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args \"true\", \"2\" --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --use-wallet --execute", temp_dir.path().join("testing").display().to_string(), )); @@ -877,6 +972,7 @@ mod tests { proof_size: None, url: Url::parse(DEFAULT_URL)?, suri: DEFAULT_URI.to_string(), + use_wallet: false, dry_run: false, execute: false, dev_mode: true, @@ -935,6 +1031,7 @@ mod tests { proof_size: None, url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, suri: "//Alice".to_string(), + use_wallet: false, dry_run: false, execute: false, dev_mode: false, @@ -984,6 +1081,7 @@ mod tests { proof_size: None, url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, suri: "//Alice".to_string(), + use_wallet: false, dry_run: false, execute: false, dev_mode: false, @@ -1002,6 +1100,7 @@ mod tests { proof_size: None, url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, suri: "//Alice".to_string(), + use_wallet: false, dry_run: false, execute: false, dev_mode: false, @@ -1025,6 +1124,7 @@ mod tests { proof_size: None, url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, suri: "//Alice".to_string(), + use_wallet: false, dry_run: false, execute: false, dev_mode: false, @@ -1055,6 +1155,7 @@ mod tests { proof_size: None, url: Url::parse("wss://rpc1.paseo.popnetwork.xyz")?, suri: "//Alice".to_string(), + use_wallet: false, dry_run: false, execute: false, dev_mode: false, diff --git a/crates/pop-cli/src/commands/test/contract.rs b/crates/pop-cli/src/commands/test/contract.rs index 190d9ac64..05e842eca 100644 --- a/crates/pop-cli/src/commands/test/contract.rs +++ b/crates/pop-cli/src/commands/test/contract.rs @@ -48,7 +48,13 @@ impl TestContractCommand { sleep(Duration::from_secs(3)).await; } - self.node = match check_contracts_node_and_prompt(self.skip_confirm).await { + self.node = match check_contracts_node_and_prompt( + &mut Cli, + &crate::cache()?, + self.skip_confirm, + ) + .await + { Ok(binary_path) => Some(binary_path), Err(_) => { warning("🚫 substrate-contracts-node is necessary to run e2e tests. Will try to run tests anyway...")?; diff --git a/crates/pop-cli/src/commands/up/contract.rs b/crates/pop-cli/src/commands/up/contract.rs index 40e158032..f46c0776f 100644 --- a/crates/pop-cli/src/commands/up/contract.rs +++ b/crates/pop-cli/src/commands/up/contract.rs @@ -2,23 +2,25 @@ use crate::{ cli::{traits::Cli as _, Cli}, - common::contracts::{check_contracts_node_and_prompt, has_contract_been_built}, + common::{ + contracts::{check_contracts_node_and_prompt, has_contract_been_built, terminate_node}, + wallet::request_signature, + }, style::style, }; use clap::Args; -use cliclack::{confirm, log, log::error, spinner}; +use cliclack::{confirm, log, log::error, spinner, ProgressBar}; use console::{Emoji, Style}; use pop_contracts::{ build_smart_contract, dry_run_gas_estimate_instantiate, dry_run_upload, - instantiate_smart_contract, is_chain_alive, parse_hex_bytes, run_contracts_node, - set_up_deployment, set_up_upload, upload_smart_contract, UpOpts, Verbosity, + get_code_hash_from_event, get_contract_code, get_instantiate_payload, get_upload_payload, + instantiate_contract_signed, instantiate_smart_contract, is_chain_alive, parse_hex_bytes, + run_contracts_node, set_up_deployment, set_up_upload, upload_contract_signed, + upload_smart_contract, UpOpts, Verbosity, }; use sp_core::Bytes; use sp_weights::Weight; -use std::{ - path::PathBuf, - process::{Child, Command}, -}; +use std::path::PathBuf; use tempfile::NamedTempFile; use url::Url; @@ -64,6 +66,15 @@ pub struct UpContractCommand { /// - with a password "//Alice///SECRET_PASSWORD" #[clap(short, long, default_value = "//Alice")] suri: String, + /// Use a browser extension wallet to sign the extrinsic. + #[clap( + name = "use-wallet", + long, + default_value = "false", + short('w'), + conflicts_with = "suri" + )] + use_wallet: bool, /// Perform a dry-run via RPC to estimate the gas usage. This does not submit a transaction. #[clap(short = 'D', long)] dry_run: bool, @@ -128,7 +139,13 @@ impl UpContractCommand { let log = NamedTempFile::new()?; // uses the cache location - let binary_path = match check_contracts_node_and_prompt(self.skip_confirm).await { + let binary_path = match check_contracts_node_and_prompt( + &mut Cli, + &crate::cache()?, + self.skip_confirm, + ) + .await + { Ok(binary_path) => binary_path, Err(_) => { Cli.outro_cancel( @@ -164,10 +181,89 @@ impl UpContractCommand { None }; + // Run steps for signing with wallet integration. Returns early. + if self.use_wallet { + let (call_data, hash) = match self.get_contract_data().await { + Ok(data) => data, + Err(e) => { + error(format!("An error occurred getting the call data: {e}"))?; + terminate_node(&mut Cli, process)?; + Cli.outro_cancel(FAILED)?; + return Ok(()); + }, + }; + + let maybe_payload = request_signature(call_data, self.url.to_string()).await?; + if let Some(payload) = maybe_payload { + log::success("Signed payload received.")?; + let spinner = spinner(); + spinner.start( + "Uploading the contract and waiting for finalization, please be patient...", + ); + + if self.upload_only { + let upload_result = match upload_contract_signed(self.url.as_str(), payload) + .await + { + Err(e) => { + spinner + .error(format!("An error occurred uploading your contract: {e}")); + terminate_node(&mut Cli, process)?; + Cli.outro_cancel(FAILED)?; + return Ok(()); + }, + Ok(result) => result, + }; + + match get_code_hash_from_event(&upload_result, hash) { + Ok(r) => { + spinner.stop(format!("Contract uploaded: The code hash is {:?}", r)); + }, + Err(e) => { + spinner + .error(format!("An error occurred uploading your contract: {e}")); + }, + }; + } else { + let contract_info = + match instantiate_contract_signed(self.url.as_str(), payload).await { + Err(e) => { + spinner.error(format!( + "An error occurred uploading your contract: {e}" + )); + terminate_node(&mut Cli, process)?; + Cli.outro_cancel(FAILED)?; + return Ok(()); + }, + Ok(result) => result, + }; + + let hash = contract_info.code_hash.map(|code_hash| format!("{:?}", code_hash)); + display_contract_info( + &spinner, + contract_info.contract_address.to_string(), + hash, + ); + }; + + if self.upload_only { + log::warning("NOTE: The contract has not been instantiated.")?; + } + } else { + Cli.outro_cancel("Signed payload doesn't exist.")?; + terminate_node(&mut Cli, process)?; + return Ok(()); + } + + terminate_node(&mut Cli, process)?; + Cli.outro(COMPLETE)?; + return Ok(()); + } + // Check for upload only. if self.upload_only { let result = self.upload_contract().await; - Self::terminate_node(process)?; + terminate_node(&mut Cli, process)?; match result { Ok(_) => { Cli.outro(COMPLETE)?; @@ -180,23 +276,11 @@ impl UpContractCommand { } // Otherwise instantiate. - let instantiate_exec = match set_up_deployment(UpOpts { - path: self.path.clone(), - constructor: self.constructor.clone(), - args: self.args.clone(), - value: self.value.clone(), - gas_limit: self.gas_limit, - proof_size: self.proof_size, - salt: self.salt.clone(), - url: self.url.clone(), - suri: self.suri.clone(), - }) - .await - { + let instantiate_exec = match set_up_deployment(self.clone().into()).await { Ok(i) => i, Err(e) => { error(format!("An error occurred instantiating the contract: {e}"))?; - Self::terminate_node(process)?; + terminate_node(&mut Cli, process)?; Cli.outro_cancel(FAILED)?; return Ok(()); }, @@ -214,7 +298,7 @@ impl UpContractCommand { }, Err(e) => { spinner.error(format!("{e}")); - Self::terminate_node(process)?; + terminate_node(&mut Cli, process)?; Cli.outro_cancel(FAILED)?; return Ok(()); }, @@ -226,30 +310,13 @@ impl UpContractCommand { let spinner = spinner(); spinner.start("Uploading and instantiating the contract..."); let contract_info = instantiate_smart_contract(instantiate_exec, weight_limit).await?; - spinner.stop(format!( - "Contract deployed and instantiated:\n{}", - style(format!( - "{}\n{}", - style(format!( - "{} The contract address is {:?}", - console::Emoji("●", ">"), - contract_info.address - )) - .dim(), - contract_info - .code_hash - .map(|hash| style(format!( - "{} The contract code hash is {:?}", - console::Emoji("●", ">"), - hash - )) - .dim() - .to_string()) - .unwrap_or_default(), - )) - .dim() - )); - Self::terminate_node(process)?; + display_contract_info( + &spinner, + contract_info.address.to_string(), + contract_info.code_hash, + ); + + terminate_node(&mut Cli, process)?; Cli.outro(COMPLETE)?; } @@ -291,27 +358,25 @@ impl UpContractCommand { Ok(()) } - /// Handles the optional termination of a local running node. - fn terminate_node(process: Option<(Child, NamedTempFile)>) -> anyhow::Result<()> { - // Prompt to close any launched node - let Some((process, log)) = process else { - return Ok(()); - }; - if confirm("Would you like to terminate the local node?") - .initial_value(true) - .interact()? - { - // Stop the process contracts-node - Command::new("kill") - .args(["-s", "TERM", &process.id().to_string()]) - .spawn()? - .wait()?; + // get the call data and contract code hash + async fn get_contract_data(&self) -> anyhow::Result<(Vec, [u8; 32])> { + let contract_code = get_contract_code(self.path.as_ref())?; + let hash = contract_code.code_hash(); + if self.upload_only { + let call_data = get_upload_payload(contract_code, self.url.as_str()).await?; + Ok((call_data, hash)) } else { - log.keep()?; - log::warning(format!("NOTE: The node is running in the background with process ID {}. Please terminate it manually when done.", process.id()))?; - } + let instantiate_exec = set_up_deployment(self.clone().into()).await?; - Ok(()) + let weight_limit = if self.gas_limit.is_some() && self.proof_size.is_some() { + Weight::from_parts(self.gas_limit.unwrap(), self.proof_size.unwrap()) + } else { + // Frontend will do dry run and update call data. + Weight::zero() + }; + let call_data = get_instantiate_payload(instantiate_exec, weight_limit)?; + Ok((call_data, hash)) + } } } @@ -331,14 +396,44 @@ impl From for UpOpts { } } +fn display_contract_info(spinner: &ProgressBar, address: String, code_hash: Option) { + spinner.stop(format!( + "Contract deployed and instantiated:\n{}", + style(format!( + "{}\n{}", + style(format!("{} The contract address is {:?}", console::Emoji("●", ">"), address)) + .dim(), + code_hash + .map(|hash| style(format!( + "{} The contract code hash is {:?}", + console::Emoji("●", ">"), + hash + )) + .dim() + .to_string()) + .unwrap_or_default(), + )) + .dim() + )); +} + #[cfg(test)] mod tests { use super::*; + use pop_common::{find_free_port, set_executable_permission}; + use pop_contracts::{contracts_node_generator, mock_build_process, new_environment}; + use std::{ + env, + process::{Child, Command}, + time::Duration, + }; + use subxt::{tx::Payload, SubstrateConfig}; + use tempfile::TempDir; + use tokio::time::sleep; use url::Url; - #[test] - fn conversion_up_contract_command_to_up_opts_works() -> anyhow::Result<()> { - let command = UpContractCommand { + fn default_up_contract_command() -> UpContractCommand { + UpContractCommand { path: None, constructor: "new".to_string(), args: vec![], @@ -346,12 +441,40 @@ mod tests { gas_limit: None, proof_size: None, salt: None, - url: Url::parse("ws://localhost:9944")?, + url: Url::parse("ws://localhost:9944").expect("default url is valid"), suri: "//Alice".to_string(), dry_run: false, upload_only: false, skip_confirm: false, - }; + use_wallet: false, + } + } + + async fn start_test_environment() -> anyhow::Result<(Child, u16, TempDir)> { + let random_port = find_free_port(None); + let temp_dir = new_environment("testing")?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("../pop-contracts/tests/files/testing.contract"), + current_dir.join("../pop-contracts/tests/files/testing.json"), + )?; + let cache = temp_dir.path().join(""); + let binary = contracts_node_generator(cache.clone(), None).await?; + binary.source(false, &(), true).await?; + set_executable_permission(binary.path())?; + let process = run_contracts_node(binary.path(), None, random_port).await?; + Ok((process, random_port, temp_dir)) + } + + fn stop_test_environment(id: &str) -> anyhow::Result<()> { + Command::new("kill").args(["-s", "TERM", id]).spawn()?.wait()?; + Ok(()) + } + + #[test] + fn conversion_up_contract_command_to_up_opts_works() -> anyhow::Result<()> { + let command = default_up_contract_command(); let opts: UpOpts = command.into(); assert_eq!( opts, @@ -369,4 +492,108 @@ mod tests { ); Ok(()) } + + #[tokio::test] + async fn get_upload_and_instantiate_call_data_works() -> anyhow::Result<()> { + let (contracts_node_process, port, temp_dir) = start_test_environment().await?; + sleep(Duration::from_secs(5)).await; + + get_upload_call_data_works(port, temp_dir.path().join("testing")).await?; + get_instantiate_call_data_works(port, temp_dir.path().join("testing")).await?; + + // Stop running contracts-node + stop_test_environment(&contracts_node_process.id().to_string())?; + Ok(()) + } + + async fn get_upload_call_data_works(port: u16, temp_dir: PathBuf) -> anyhow::Result<()> { + let localhost_url = format!("ws://127.0.0.1:{}", port); + + let up_contract_opts = UpContractCommand { + path: Some(temp_dir), + constructor: "new".to_string(), + args: vec![], + value: "0".to_string(), + gas_limit: None, + proof_size: None, + salt: None, + url: Url::parse(&localhost_url).expect("given url is valid"), + suri: "//Alice".to_string(), + dry_run: false, + upload_only: true, + skip_confirm: true, + use_wallet: true, + }; + + let rpc_client = subxt::backend::rpc::RpcClient::from_url(&up_contract_opts.url).await?; + let client = subxt::OnlineClient::::from_rpc_client(rpc_client).await?; + + // Retrieve call data based on the above command options. + let (retrieved_call_data, _) = match up_contract_opts.get_contract_data().await { + Ok(data) => data, + Err(e) => { + error(format!("An error occurred getting the call data: {e}"))?; + return Err(e); + }, + }; + // We have retrieved some payload. + assert!(!retrieved_call_data.is_empty()); + + // Craft encoded call data for an upload code call. + let contract_code = get_contract_code(up_contract_opts.path.as_ref())?; + let storage_deposit_limit: Option = None; + let upload_code = contract_extrinsics::extrinsic_calls::UploadCode::new( + contract_code, + storage_deposit_limit, + contract_extrinsics::upload::Determinism::Enforced, + ); + let expected_call_data = upload_code.build(); + let mut encoded_expected_call_data = Vec::::new(); + expected_call_data + .encode_call_data_to(&client.metadata(), &mut encoded_expected_call_data)?; + + // Retrieved call data and calculated match. + assert_eq!(retrieved_call_data, encoded_expected_call_data); + Ok(()) + } + + async fn get_instantiate_call_data_works(port: u16, temp_dir: PathBuf) -> anyhow::Result<()> { + let localhost_url = format!("ws://127.0.0.1:{}", port); + + let up_contract_opts = UpContractCommand { + path: Some(temp_dir), + constructor: "new".to_string(), + args: vec!["false".to_string()], + value: "0".to_string(), + gas_limit: Some(200_000_000), + proof_size: Some(30_000), + salt: None, + url: Url::parse(&localhost_url).expect("given url is valid"), + suri: "//Alice".to_string(), + dry_run: false, + upload_only: false, + skip_confirm: true, + use_wallet: true, + }; + + // Retrieve call data based on the above command options. + let (retrieved_call_data, _) = match up_contract_opts.get_contract_data().await { + Ok(data) => data, + Err(e) => { + error(format!("An error occurred getting the call data: {e}"))?; + return Err(e); + }, + }; + // We have retrieved some payload. + assert!(!retrieved_call_data.is_empty()); + + // Craft instantiate call data. + let weight = Weight::from_parts(200_000_000, 30_000); + let expected_call_data = + get_instantiate_payload(set_up_deployment(up_contract_opts.into()).await?, weight)?; + // Retrieved call data matches the one crafted above. + assert_eq!(retrieved_call_data, expected_call_data); + + Ok(()) + } } diff --git a/crates/pop-cli/src/common/contracts.rs b/crates/pop-cli/src/common/contracts.rs index 3d0be11f6..a3b8f878c 100644 --- a/crates/pop-cli/src/common/contracts.rs +++ b/crates/pop-cli/src/common/contracts.rs @@ -1,22 +1,33 @@ // SPDX-License-Identifier: GPL-3.0 -use cliclack::{confirm, log::warning, spinner}; +use crate::cli::traits::*; +use cliclack::spinner; use pop_common::{manifest::from_path, sourcing::set_executable_permission}; use pop_contracts::contracts_node_generator; -use std::path::{Path, PathBuf}; +use std::{ + path::{Path, PathBuf}, + process::{Child, Command}, +}; +use tempfile::NamedTempFile; /// Checks the status of the `substrate-contracts-node` binary, sources it if necessary, and /// prompts the user to update it if the existing binary is not the latest version. /// /// # Arguments +/// * `cli`: Command line interface. +/// * `cache_path`: The cache directory path. /// * `skip_confirm`: A boolean indicating whether to skip confirmation prompts. -pub async fn check_contracts_node_and_prompt(skip_confirm: bool) -> anyhow::Result { - let cache_path: PathBuf = crate::cache()?; - let mut binary = contracts_node_generator(cache_path, None).await?; +pub async fn check_contracts_node_and_prompt( + cli: &mut impl Cli, + cache_path: &Path, + skip_confirm: bool, +) -> anyhow::Result { + let mut binary = contracts_node_generator(PathBuf::from(cache_path), None).await?; let mut node_path = binary.path(); if !binary.exists() { - warning("⚠️ The substrate-contracts-node binary is not found.")?; - if confirm("📦 Would you like to source it automatically now?") + cli.warning("⚠️ The substrate-contracts-node binary is not found.")?; + if cli + .confirm("📦 Would you like to source it automatically now?") .initial_value(true) .interact()? { @@ -33,14 +44,14 @@ pub async fn check_contracts_node_and_prompt(skip_confirm: bool) -> anyhow::Resu } } if binary.stale() { - warning(format!( + cli.warning(format!( "ℹ️ There is a newer version of {} available:\n {} -> {}", binary.name(), binary.version().unwrap_or("None"), binary.latest().unwrap_or("None") ))?; let latest = if !skip_confirm { - confirm( + cli.confirm( "📦 Would you like to source it automatically now? It may take some time..." .to_string(), ) @@ -68,6 +79,36 @@ pub async fn check_contracts_node_and_prompt(skip_confirm: bool) -> anyhow::Resu Ok(node_path) } +/// Handles the optional termination of a local running node. +/// # Arguments +/// * `cli`: Command line interface. +/// * `process`: Tuple identifying the child process to terminate and its log file. +pub fn terminate_node( + cli: &mut impl Cli, + process: Option<(Child, NamedTempFile)>, +) -> anyhow::Result<()> { + // Prompt to close any launched node + let Some((process, log)) = process else { + return Ok(()); + }; + if cli + .confirm("Would you like to terminate the local node?") + .initial_value(true) + .interact()? + { + // Stop the process contracts-node + Command::new("kill") + .args(["-s", "TERM", &process.id().to_string()]) + .spawn()? + .wait()?; + } else { + log.keep()?; + cli.warning(format!("NOTE: The node is running in the background with process ID {}. Please terminate it manually when done.", process.id()))?; + } + + Ok(()) +} + /// Checks if a contract has been built by verifying the existence of the build directory and the /// .contract file. /// @@ -88,8 +129,12 @@ pub fn has_contract_been_built(path: Option<&Path>) -> bool { #[cfg(test)] mod tests { use super::*; + use crate::cli::MockCli; use duct::cmd; + use pop_common::find_free_port; + use pop_contracts::{is_chain_alive, run_contracts_node}; use std::fs::{self, File}; + use url::Url; #[test] fn has_contract_been_built_works() -> anyhow::Result<()> { @@ -111,4 +156,38 @@ mod tests { assert!(has_contract_been_built(Some(&path.join(name)))); Ok(()) } + + #[tokio::test] + async fn check_contracts_node_and_prompt_works() -> anyhow::Result<()> { + let cache_path = tempfile::tempdir().expect("Could create temp dir"); + let mut cli = MockCli::new() + .expect_warning("⚠️ The substrate-contracts-node binary is not found.") + .expect_confirm("📦 Would you like to source it automatically now?", true) + .expect_warning("⚠️ The substrate-contracts-node binary is not found."); + + let node_path = check_contracts_node_and_prompt(&mut cli, cache_path.path(), false).await?; + // Binary path is at least equal to the cache path + "substrate-contracts-node". + assert!(node_path + .to_str() + .unwrap() + .starts_with(&cache_path.path().join("substrate-contracts-node").to_str().unwrap())); + cli.verify() + } + + #[tokio::test] + async fn node_is_terminated() -> anyhow::Result<()> { + let cache = tempfile::tempdir().expect("Could not create temp dir"); + let binary = contracts_node_generator(PathBuf::from(cache.path()), None).await?; + binary.source(false, &(), true).await?; + set_executable_permission(binary.path())?; + let port = find_free_port(None); + let process = run_contracts_node(binary.path(), None, port).await?; + let log = NamedTempFile::new()?; + // Terminate the process. + let mut cli = + MockCli::new().expect_confirm("Would you like to terminate the local node?", true); + assert!(terminate_node(&mut cli, Some((process, log))).is_ok()); + assert_eq!(is_chain_alive(Url::parse(&format!("ws://localhost:{}", port))?).await?, false); + cli.verify() + } } diff --git a/crates/pop-cli/src/common/mod.rs b/crates/pop-cli/src/common/mod.rs index 1cb3ee579..4a89036e5 100644 --- a/crates/pop-cli/src/common/mod.rs +++ b/crates/pop-cli/src/common/mod.rs @@ -3,3 +3,4 @@ #[cfg(feature = "contract")] pub mod contracts; pub mod helpers; +pub mod wallet; diff --git a/crates/pop-cli/src/common/wallet.rs b/crates/pop-cli/src/common/wallet.rs new file mode 100644 index 000000000..1aedd0294 --- /dev/null +++ b/crates/pop-cli/src/common/wallet.rs @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-3.0 + +use crate::{ + cli::traits::Cli, + wallet_integration::{FrontendFromString, TransactionData, WalletIntegrationManager}, +}; +use cliclack::{log, spinner}; + +/// 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')"; + +/// Launches the wallet integration for in-browser signing. Blocks until the signature is received. +/// +/// # Arguments +/// * `call_data` - The call data to be signed. +/// * `url` - Chain rpc. +/// # Returns +/// * The signed payload, if it exists. +pub async fn request_signature(call_data: Vec, rpc: String) -> anyhow::Result> { + let ui = FrontendFromString::new(include_str!("../assets/index.html").to_string()); + + let transaction_data = TransactionData::new(rpc, call_data); + // Starts server with port 9090. + let mut wallet = WalletIntegrationManager::new(ui, transaction_data, Some(9090)); + let url = format!("http://{}", &wallet.server_url); + log::step(format!("Wallet signing portal started at {url}."))?; + + let spinner = spinner(); + spinner.start(format!("Opening browser to {url}")); + if let Err(e) = open::that(url) { + spinner.error(format!("Failed to launch browser. Please open link manually. {e}")); + } + + spinner.start("Waiting for signature... Press Ctrl+C to terminate early."); + loop { + // Display error, if any. + if let Some(error) = wallet.take_error().await { + log::error(format!("Signing portal error: {error}"))?; + } + + let state = wallet.state.lock().await; + // If the payload is submitted we terminate the frontend. + if !wallet.is_running() || state.signed_payload.is_some() { + wallet.task_handle.await??; + break; + } + } + spinner.stop(""); + + let signed_payload = wallet.state.lock().await.signed_payload.take(); + Ok(signed_payload) +} + +/// Prompts the user to use the wallet for signing. +/// # Arguments +/// * `cli` - The CLI instance. +/// # Returns +/// * `true` if the user wants to use the wallet, `false` otherwise. +pub fn prompt_to_use_wallet(cli: &mut impl Cli) -> anyhow::Result { + use crate::cli::traits::Confirm; + + if cli.confirm(USE_WALLET_PROMPT).initial_value(true).interact()? { + Ok(true) + } else { + Ok(false) + } +} diff --git a/crates/pop-cli/src/main.rs b/crates/pop-cli/src/main.rs index 9a41fd7c1..f8e3504e5 100644 --- a/crates/pop-cli/src/main.rs +++ b/crates/pop-cli/src/main.rs @@ -19,6 +19,7 @@ mod cli; mod commands; mod common; mod style; +mod wallet_integration; #[tokio::main] async fn main() -> Result<()> { diff --git a/crates/pop-cli/src/wallet_integration.rs b/crates/pop-cli/src/wallet_integration.rs new file mode 100644 index 000000000..92e3395ec --- /dev/null +++ b/crates/pop-cli/src/wallet_integration.rs @@ -0,0 +1,579 @@ +use axum::{ + http::HeaderValue, + response::Html, + routing::{get, post}, + Router, +}; +use pop_common::find_free_port; +use serde::Serialize; +use std::{path::PathBuf, sync::Arc}; +use tokio::{ + sync::{oneshot, Mutex}, + task::JoinHandle, +}; +use tower_http::{cors::Any, services::ServeDir}; + +/// Make frontend sourcing more flexible by allowing a custom route to be defined. +pub trait Frontend { + /// Serves the content via a [Router]. + fn serve_content(&self) -> Router; +} + +/// Transaction payload to be sent to frontend for signing. +#[derive(Serialize, Debug)] +#[cfg_attr(test, derive(serde::Deserialize, Clone))] +pub struct TransactionData { + chain_rpc: String, + call_data: Vec, +} + +impl TransactionData { + /// Create a new transaction payload. + /// # Arguments + /// * `chain_rpc`: The RPC of the chain. + /// * `call_data`: the call data. + /// # Returns + /// The transaction payload to be sent to frontend for signing. + pub fn new(chain_rpc: String, call_data: Vec) -> Self { + Self { chain_rpc, call_data } + } +} + +/// Shared state between routes. Serves two purposes: +/// - Maintains a channel to signal shutdown to the main app. +/// - Stores the signed payload received from the wallet. +#[derive(Default)] +pub struct StateHandler { + /// Channel to signal shutdown to the main app. + shutdown_tx: Option>, + /// Received from UI. + pub signed_payload: Option, + /// Holds a single error message. + /// Only method for consuming error removes (takes) it from state. + error: Option, +} + +/// Manages the wallet integration for secure signing of transactions. +pub struct WalletIntegrationManager { + pub server_url: String, + /// Shared state between routes. + pub state: Arc>, + /// Web server task handle. + pub task_handle: JoinHandle>, +} + +impl WalletIntegrationManager { + /// Launches a server for hosting the wallet integration. Server launched in separate task. + /// # Arguments + /// * `frontend`: A frontend with custom route to serve content. + /// * `payload`: Payload to be sent to the frontend for signing. + /// * `maybe_port`: Optional port for server to bind to. `None` will result in a random port. + /// + /// # Returns + /// A `WalletIntegrationManager` instance, with access to the state and task handle for the + /// server. + pub fn new( + frontend: F, + payload: TransactionData, + maybe_port: Option, + ) -> Self { + let port = find_free_port(maybe_port); + Self::new_with_address(frontend, payload, format!("127.0.0.1:{}", port)) + } + + /// Same as `new`, but allows specifying the address to bind to. + /// # Arguments + /// * `frontend`: A frontend with custom route to serve content. + /// * `payload`: Payload to be sent to the frontend for signing. + /// * `server_url`: The address to bind to. + /// + /// # Returns + /// A `WalletIntegrationManager` instance, with access to the state and task handle for the + pub fn new_with_address( + frontend: F, + payload: TransactionData, + server_url: String, + ) -> Self { + // Channel to signal shutdown. + let (tx, rx) = oneshot::channel(); + + let state = Arc::new(Mutex::new(StateHandler { + shutdown_tx: Some(tx), + signed_payload: None, + error: None, + })); + + let payload = Arc::new(payload); + + let cors = tower_http::cors::CorsLayer::new() + .allow_origin(server_url.parse::().expect("invalid server url")) + .allow_methods(Any) // Allow any HTTP method + .allow_headers(Any); // Allow any headers (like 'Content-Type') + + let app = Router::new() + .route("/payload", get(routes::get_payload_handler).with_state(payload)) + .route("/submit", post(routes::submit_handler).with_state(state.clone())) + .route("/error", post(routes::error_handler).with_state(state.clone())) + .route("/terminate", post(routes::terminate_handler).with_state(state.clone())) + .merge(frontend.serve_content()) // Custom route for serving frontend. + .layer(cors); + + let url_owned = server_url.to_string(); + + // Will shut down when the signed payload is received. + let task_handle = tokio::spawn(async move { + let listener = tokio::net::TcpListener::bind(&url_owned) + .await + .map_err(|e| anyhow::anyhow!("Failed to bind to {}: {}", url_owned, e))?; + + axum::serve(listener, app) + .with_graceful_shutdown(async move { + let _ = rx.await.ok(); + }) + .await + .map_err(|e| anyhow::anyhow!("Server encountered an error: {}", e))?; + Ok(()) + }); + + Self { state, server_url, task_handle } + } + + /// Signals the wallet integration server to shut down. + #[allow(dead_code)] + pub async fn terminate(&mut self) -> anyhow::Result<()> { + terminate_helper(&self.state).await + } + + /// Checks if the server task is still running. + pub fn is_running(&self) -> bool { + !self.task_handle.is_finished() + } + + /// Takes the error from the state if it exists. + pub async fn take_error(&mut self) -> Option { + self.state.lock().await.error.take() + } +} + +mod routes { + use super::{terminate_helper, Arc, Mutex, StateHandler, TransactionData}; + use anyhow::Error; + use axum::{ + extract::State, + http::StatusCode, + response::{IntoResponse, Response}, + Json, + }; + use serde_json::json; + + pub(super) struct ApiError(Error); + + impl From for ApiError { + fn from(err: Error) -> Self { + ApiError(err) + } + } + + // Implementing IntoResponse for ApiError allows us to return it directly from a route handler. + impl IntoResponse for ApiError { + fn into_response(self) -> Response { + let body = json!({ + "error": self.0.to_string(), + }); + (StatusCode::INTERNAL_SERVER_ERROR, Json(body)).into_response() + } + } + + /// Responds with the serialized JSON data for signing. + pub(super) async fn get_payload_handler( + State(payload): State>, + ) -> Result, ApiError> { + // Error should never occur. + let json_payload = serde_json::to_value(&*payload) + .map_err(|e| anyhow::anyhow!("Failed to serialize payload: {}", e))?; + Ok(Json(json_payload)) + } + + /// Receives the signed payload from the wallet. + /// Will signal for shutdown on success. + pub(super) async fn submit_handler( + State(state): State>>, + Json(payload): Json, + ) -> Result, ApiError> { + // Signal shutdown. + let res = terminate_helper(&state).await; + + let mut state_locked = state.lock().await; + state_locked.signed_payload = Some(payload); + + res?; + + // Graceful shutdown ensures response is sent before shutdown. + Ok(Json(json!({"status": "success"}))) + } + + /// Receives an error message from the wallet. + pub(super) async fn error_handler( + State(state): State>>, + Json(error): Json, + ) { + let mut state = state.lock().await; + state.error = Some(error); + } + + /// Allows the server to be terminated from the frontend. + pub(super) async fn terminate_handler( + State(state): State>>, + ) -> Result<(), ApiError> { + Ok(terminate_helper(&state).await?) + } +} + +async fn terminate_helper(handle: &Arc>) -> anyhow::Result<()> { + if let Some(shutdown_tx) = handle.lock().await.shutdown_tx.take() { + shutdown_tx + .send(()) + .map_err(|_| anyhow::anyhow!("Failed to send shutdown signal"))?; + } + Ok(()) +} + +/// Serves static files from a directory. +pub struct FrontendFromDir { + content: PathBuf, +} +#[allow(dead_code)] +impl FrontendFromDir { + /// A new static server. + /// # Arguments + /// * `content`: A directory path. + pub fn new(content: PathBuf) -> Self { + Self { content } + } +} + +impl Frontend for FrontendFromDir { + fn serve_content(&self) -> Router { + Router::new().nest_service("/", ServeDir::new(self.content.clone())) + } +} + +/// Serves a hard-coded HTML string as the frontend. +pub struct FrontendFromString { + content: String, +} + +#[allow(dead_code)] +impl FrontendFromString { + /// A new static server. + /// # Arguments + /// * `content`: A hard-coded HTML string + pub fn new(content: String) -> Self { + Self { content } + } +} + +impl Frontend for FrontendFromString { + fn serve_content(&self) -> Router { + let content = self.content.clone(); + Router::new().route("/", get(move || async { Html(content) })) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + const TEST_HTML: &str = "Hello, world!"; + + // Wait for server to launch. + async fn wait() { + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + } + + fn default_payload() -> TransactionData { + TransactionData { chain_rpc: "localhost:9944".to_string(), call_data: vec![1, 2, 3] } + } + + #[tokio::test] + async fn new_works() { + let frontend = FrontendFromString::new(TEST_HTML.to_string()); + let mut wim = WalletIntegrationManager::new(frontend, default_payload(), Some(9190)); + + assert_eq!(wim.server_url, "127.0.0.1:9190"); + assert_eq!(wim.is_running(), true); + assert!(wim.state.lock().await.shutdown_tx.is_some()); + assert!(wim.state.lock().await.signed_payload.is_none()); + + // Terminate the server and make sure result is ok. + wim.terminate().await.expect("Termination should not fail."); + assert!(wim.task_handle.await.is_ok()); + } + + #[tokio::test] + async fn new_with_random_port_works() { + let servers = (0..3) + .map(|_| { + let frontend = FrontendFromString::new(TEST_HTML.to_string()); + WalletIntegrationManager::new(frontend, default_payload(), None) + }) + .collect::>(); + + // Ensure all server URLs are unique + for i in 0..servers.len() { + for j in (i + 1)..servers.len() { + assert_ne!(servers[i].server_url, servers[j].server_url); + } + } + + assert!(servers.iter().all(|server| server.is_running())); + for mut server in servers.into_iter() { + assert!(server.state.lock().await.shutdown_tx.is_some()); + assert!(server.state.lock().await.signed_payload.is_none()); + server.terminate().await.expect("Server termination should not fail"); + + let task_result = server.task_handle.await; + assert!(task_result.is_ok()); + } + } + + #[test] + fn new_transaction_data_works() { + let chain_rpc = "localhost:9944".to_string(); + let call_data = vec![1, 2, 3]; + let transaction_data = TransactionData::new(chain_rpc.clone(), call_data.clone()); + + assert_eq!(transaction_data.chain_rpc, chain_rpc); + assert_eq!(transaction_data.call_data, call_data); + } + + #[tokio::test] + async fn take_error_works() { + let frontend = FrontendFromString::new(TEST_HTML.to_string()); + let mut wim = WalletIntegrationManager::new(frontend, default_payload(), None); + + assert_eq!(wim.take_error().await, None); + + let error = "An error occurred".to_string(); + wim.state.lock().await.error = Some(error.clone()); + + let taken_error = wim.take_error().await; + assert_eq!(taken_error, Some(error)); + } + + #[tokio::test] + async fn payload_handler_works() { + // offset port per test to avoid conflicts + let frontend = FrontendFromString::new(TEST_HTML.to_string()); + let expected_payload = + TransactionData { chain_rpc: "localhost:9944".to_string(), call_data: vec![1, 2, 3] }; + let mut wim = WalletIntegrationManager::new(frontend, expected_payload.clone(), None); + wait().await; + + let addr = format!("http://{}", wim.server_url); + let actual_payload = reqwest::get(&format!("{}/payload", addr)) + .await + .expect("Failed to get payload") + .json::() + .await + .expect("Failed to parse payload"); + + assert_eq!(actual_payload.chain_rpc, expected_payload.chain_rpc); + assert_eq!(actual_payload.call_data, expected_payload.call_data); + + wim.terminate().await.expect("Termination should not fail"); + assert!(wim.task_handle.await.is_ok()); + } + + #[tokio::test] + async fn submit_handler_works() { + let frontend = FrontendFromString::new(TEST_HTML.to_string()); + let mut wim = WalletIntegrationManager::new(frontend, default_payload(), None); + wait().await; + + let addr = format!("http://{}", wim.server_url); + let response = reqwest::Client::new() + .post(&format!("{}/submit", addr)) + .json(&"0xDEADBEEF") + .send() + .await + .expect("Failed to submit payload") + .text() + .await + .expect("Failed to parse response"); + + assert_eq!(response, json!({"status": "success"}).to_string()); + assert_eq!(wim.state.lock().await.signed_payload, Some("0xDEADBEEF".to_string())); + assert_eq!(wim.is_running(), false); + + wim.terminate().await.expect("Termination should not fail"); + assert!(wim.task_handle.await.is_ok()); + } + + #[tokio::test] + async fn error_handler_works() { + let frontend = FrontendFromString::new(TEST_HTML.to_string()); + let mut wim = WalletIntegrationManager::new(frontend, default_payload(), None); + wait().await; + + let addr = format!("http://{}", wim.server_url); + let response = reqwest::Client::new() + .post(&format!("{}/error", addr)) + .json(&"an error occurred") + .send() + .await + .expect("Failed to submit error") + .text() + .await + .expect("Failed to parse response"); + + // no response expected + assert_eq!(response.len(), 0); + assert_eq!(wim.state.lock().await.error, Some("an error occurred".to_string())); + assert_eq!(wim.is_running(), true); + + wim.terminate().await.expect("Termination should not fail"); + assert!(wim.task_handle.await.is_ok()); + } + + #[tokio::test] + async fn terminate_handler_works() { + let frontend = FrontendFromString::new(TEST_HTML.to_string()); + + let wim = WalletIntegrationManager::new(frontend, default_payload(), None); + wait().await; + + let addr = format!("http://{}", wim.server_url); + let response = reqwest::Client::new() + .post(&format!("{}/terminate", addr)) + .send() + .await + .expect("Failed to terminate") + .text() + .await + .expect("Failed to parse response"); + + // No response expected. + assert_eq!(response.len(), 0); + assert_eq!(wim.is_running(), false); + + assert!(wim.task_handle.await.is_ok()); + } + + #[tokio::test] + async fn wallet_terminate_works() { + let frontend = FrontendFromString::new(TEST_HTML.to_string()); + + let mut wim = WalletIntegrationManager::new(frontend, default_payload(), None); + assert_eq!(wim.is_running(), true); + wim.terminate().await.expect("Termination should not fail"); + wait().await; + assert_eq!(wim.is_running(), false); + + wim.terminate().await.expect("Termination should not fail"); + assert!(wim.task_handle.await.is_ok()); + } + + #[tokio::test] + async fn frontend_from_string_works() { + let frontend = FrontendFromString::new(TEST_HTML.to_string()); + let mut wim = WalletIntegrationManager::new(frontend, default_payload(), None); + wait().await; + + let actual_content = reqwest::get(&format!("http://{}", wim.server_url)) + .await + .expect("Failed to get web page") + .text() + .await + .expect("Failed to parse page"); + + assert_eq!(actual_content, TEST_HTML); + + wim.terminate().await.expect("Termination should not fail"); + assert!(wim.task_handle.await.is_ok()); + } + + #[tokio::test] + async fn frontend_from_dir_works() { + use std::fs; + use tempfile::tempdir; + + let temp_dir = tempdir().expect("Failed to create temp directory"); + let index_file_path = temp_dir.path().join("index.html"); + + let test_html = "Hello, world from Directory!"; + fs::write(&index_file_path, test_html).expect("Failed to write index.html"); + + let frontend = FrontendFromDir::new(temp_dir.path().to_path_buf()); + let mut wim = WalletIntegrationManager::new(frontend, default_payload(), None); + wait().await; + + let actual_content = reqwest::get(&format!("http://{}", wim.server_url)) + .await + .expect("Failed to get web page") + .text() + .await + .expect("Failed to parse page"); + + assert_eq!(actual_content, test_html); + + wim.terminate().await.expect("Termination should not fail"); + assert!(wim.task_handle.await.is_ok()); + } + + #[tokio::test] + async fn large_payload_works() { + let frontend = FrontendFromString::new(TEST_HTML.to_string()); + + let call_data_5mb = vec![99u8; 5 * 1024 * 1024]; + + let expected_payload = TransactionData { + chain_rpc: "localhost:9944".to_string(), + call_data: call_data_5mb.clone(), + }; + let mut wim = WalletIntegrationManager::new(frontend, expected_payload.clone(), None); + wait().await; + + let addr = format!("http://{}", wim.server_url); + let actual_payload = reqwest::get(&format!("{}/payload", addr)) + .await + .expect("Failed to get payload") + .json::() + .await + .expect("Failed to parse payload"); + + assert_eq!(actual_payload.chain_rpc, expected_payload.chain_rpc); + assert_eq!(actual_payload.call_data, call_data_5mb); + + wim.terminate().await.expect("Termination should not fail."); + assert!(wim.task_handle.await.is_ok()); + } + + #[tokio::test] + async fn new_with_conflicting_address_fails() { + // offset port per test to avoid conflicts + let addr = "127.0.0.1:9099".to_string(); + + let frontend = FrontendFromString::new(TEST_HTML.to_string()); + let wim = + WalletIntegrationManager::new_with_address(frontend, default_payload(), addr.clone()); + wait().await; + + assert_eq!(wim.is_running(), true); + + let frontend = FrontendFromString::new(TEST_HTML.to_string()); + let wim_conflict = + WalletIntegrationManager::new_with_address(frontend, default_payload(), addr.clone()); + wait().await; + + assert_eq!(wim_conflict.is_running(), false); + let task_result = wim_conflict.task_handle.await.unwrap(); + match task_result { + Err(e) => assert!(e + .to_string() + .starts_with(&format!("Failed to bind to {}: Address already in use", addr))), + Ok(_) => panic!("Expected error, but task succeeded"), + } + } +} diff --git a/crates/pop-cli/tests/contract.rs b/crates/pop-cli/tests/contract.rs index 77c8dba5d..1af21b385 100644 --- a/crates/pop-cli/tests/contract.rs +++ b/crates/pop-cli/tests/contract.rs @@ -2,19 +2,57 @@ use anyhow::Result; use assert_cmd::Command; -use pop_common::{set_executable_permission, templates::Template}; +use pop_common::{find_free_port, set_executable_permission, templates::Template}; use pop_contracts::{ contracts_node_generator, dry_run_gas_estimate_instantiate, instantiate_smart_contract, run_contracts_node, set_up_deployment, Contract, UpOpts, }; -use std::{path::Path, process::Command as Cmd}; +use serde::{Deserialize, Serialize}; +use std::{path::Path, process::Command as Cmd, time::Duration}; use strum::VariantArray; +use subxt::{config::DefaultExtrinsicParamsBuilder as Params, tx::Payload, utils::to_hex}; +use subxt_signer::sr25519::dev; +use tokio::time::sleep; use url::Url; +// This struct implements the [`Payload`] trait and is used to submit +// pre-encoded SCALE call data directly, without the dynamic construction of transactions. +struct CallData(Vec); +impl Payload for CallData { + fn encode_call_data_to( + &self, + _: &subxt::Metadata, + out: &mut Vec, + ) -> Result<(), subxt::ext::subxt_core::Error> { + out.extend_from_slice(&self.0); + Ok(()) + } +} + +// TransactionData has been copied from wallet_integration.rs +/// Transaction payload to be sent to frontend for signing. +#[derive(Serialize, Debug)] +#[cfg_attr(test, derive(Deserialize, Clone))] +pub struct TransactionData { + chain_rpc: String, + call_data: Vec, +} +impl TransactionData { + pub fn new(chain_rpc: String, call_data: Vec) -> Self { + Self { chain_rpc, call_data } + } + pub fn call_data(&self) -> Vec { + self.call_data.clone() + } +} + /// Test the contract lifecycle: new, build, up, call #[tokio::test] async fn contract_lifecycle() -> Result<()> { - const DEFAULT_PORT: u16 = 9944; + const WALLET_INT_URI: &str = "http://127.0.0.1:9090"; + const WAIT_SECS: u64 = 240; + let endpoint_port = find_free_port(None); + let default_endpoint: &str = &format!("ws://127.0.0.1:{}", endpoint_port); let temp = tempfile::tempdir().unwrap(); let temp_dir = temp.path(); //let temp_dir = Path::new("./"); //For testing locally @@ -46,14 +84,15 @@ async fn contract_lifecycle() -> Result<()> { let binary = contracts_node_generator(temp_dir.to_path_buf().clone(), None).await?; binary.source(false, &(), true).await?; set_executable_permission(binary.path())?; - let process = run_contracts_node(binary.path(), None, DEFAULT_PORT).await?; + let process = run_contracts_node(binary.path(), None, endpoint_port).await?; + sleep(Duration::from_secs(5)).await; // Only upload the contract // pop up contract --upload-only Command::cargo_bin("pop") .unwrap() .current_dir(&temp_dir.join("test_contract")) - .args(&["up", "contract", "--upload-only"]) + .args(&["up", "contract", "--upload-only", "--url", default_endpoint]) .assert() .success(); // Instantiate contract, only dry-run @@ -70,6 +109,8 @@ async fn contract_lifecycle() -> Result<()> { "--suri", "//Alice", "--dry-run", + "--url", + default_endpoint, ]) .assert() .success(); @@ -83,7 +124,7 @@ async fn contract_lifecycle() -> Result<()> { gas_limit: None, proof_size: None, salt: None, - url: Url::parse("ws://127.0.0.1:9944")?, + url: Url::parse(default_endpoint)?, suri: "//Alice".to_string(), }) .await?; @@ -103,6 +144,8 @@ async fn contract_lifecycle() -> Result<()> { "get", "--suri", "//Alice", + "--url", + default_endpoint, ]) .assert() .success(); @@ -122,10 +165,70 @@ async fn contract_lifecycle() -> Result<()> { "--suri", "//Alice", "-x", + "--url", + default_endpoint, ]) .assert() .success(); + // pop up contract --upload-only --use-wallet + // Will run http server for wallet integration. + // Using `cargo run --` as means for the CI to pass. + // Possibly there's room for improvement here. + let _ = tokio::process::Command::new("cargo") + .args(&[ + "run", + "--", + "up", + "contract", + "--upload-only", + "--use-wallet", + "--skip-confirm", + "--dry-run", + "-p", + temp_dir.join("test_contract").to_str().expect("to_str"), + "--url", + default_endpoint, + ]) + .spawn()?; + // Wait a moment for node and server to be up. + sleep(Duration::from_secs(WAIT_SECS)).await; + + // Request payload from server. + let response = reqwest::get(&format!("{}/payload", WALLET_INT_URI)) + .await + .expect("Failed to get payload") + .json::() + .await + .expect("Failed to parse payload"); + // We have received some payload. + assert!(!response.call_data().is_empty()); + + let rpc_client = subxt::backend::rpc::RpcClient::from_url(default_endpoint).await?; + let client = subxt::OnlineClient::::from_rpc_client(rpc_client).await?; + + // Sign payload. + let signer = dev::alice(); + let payload = CallData(response.call_data()); + let ext_params = Params::new().build(); + let signed = client.tx().create_signed(&payload, &signer, ext_params).await?; + + // Submit signed payload. This kills the wallet integration server. + let _ = reqwest::Client::new() + .post(&format!("{}/submit", WALLET_INT_URI)) + .json(&to_hex(signed.encoded())) + .send() + .await + .expect("Failed to submit payload") + .text() + .await + .expect("Failed to parse JSON response"); + + // Request payload from server after signed payload has been sent. + // Server should not be running! + let response = reqwest::get(&format!("{}/payload", WALLET_INT_URI)).await; + assert!(response.is_err()); + // Stop the process contracts-node Cmd::new("kill") .args(["-s", "TERM", &process.id().to_string()]) diff --git a/crates/pop-cli/tests/parachain.rs b/crates/pop-cli/tests/parachain.rs index 67132e6d4..31ad6bded 100644 --- a/crates/pop-cli/tests/parachain.rs +++ b/crates/pop-cli/tests/parachain.rs @@ -95,7 +95,7 @@ async fn parachain_lifecycle() -> Result<()> { // Overwrite the config file to manually set the port to test pop call parachain. let network_toml_path = temp_parachain_dir.join("network.toml"); fs::create_dir_all(&temp_parachain_dir)?; - let random_port = find_free_port(); + let random_port = find_free_port(None); let localhost_url = format!("ws://127.0.0.1:{}", random_port); fs::write( &network_toml_path, diff --git a/crates/pop-common/src/lib.rs b/crates/pop-common/src/lib.rs index 70dbcc8f8..b4c0c1973 100644 --- a/crates/pop-common/src/lib.rs +++ b/crates/pop-common/src/lib.rs @@ -64,12 +64,20 @@ pub fn target() -> Result<&'static str, Error> { Err(Error::UnsupportedPlatform { arch: ARCH, os: OS }) } -/// Finds an available port by binding to port 0 and retrieving the assigned port. -pub fn find_free_port() -> u16 { +/// Checks if preferred port is available, otherwise returns a random available port. +pub fn find_free_port(preferred_port: Option) -> u16 { + // Try to bind to preferred port if provided. + if let Some(port) = preferred_port { + if TcpListener::bind(format!("127.0.0.1:{}", port)).is_ok() { + return port; + } + } + + // Else, fallback to a random available port TcpListener::bind("127.0.0.1:0") .expect("Failed to bind to an available port") .local_addr() - .expect("Failed to retrieve local address") + .expect("Failed to retrieve local address. This should never occur.") .port() } @@ -105,7 +113,7 @@ mod test { #[test] fn find_free_port_works() -> Result<()> { - let port = find_free_port(); + let port = find_free_port(None); let addr = format!("127.0.0.1:{}", port); // Constructs the TcpListener from the above port let listener = TcpListener::bind(&addr); diff --git a/crates/pop-contracts/Cargo.toml b/crates/pop-contracts/Cargo.toml index b3ab82cb8..f44332299 100644 --- a/crates/pop-contracts/Cargo.toml +++ b/crates/pop-contracts/Cargo.toml @@ -27,11 +27,13 @@ sp-core.workspace = true sp-weights.workspace = true strum.workspace = true strum_macros.workspace = true +subxt-signer.workspace = true +subxt.workspace = true # cargo-contracts contract-build.workspace = true contract-extrinsics.workspace = true -contract-transcode.workspace = true +contract-transcode.workspace = true scale-info.workspace = true # pop pop-common = { path = "../pop-common", version = "0.5.0" } @@ -39,4 +41,4 @@ pop-common = { path = "../pop-common", version = "0.5.0" } [dev-dependencies] dirs.workspace = true mockito.workspace = true -tokio-test.workspace = true +tokio-test.workspace = true \ No newline at end of file diff --git a/crates/pop-contracts/src/call.rs b/crates/pop-contracts/src/call.rs index 03effcb0a..2ffbd3258 100644 --- a/crates/pop-contracts/src/call.rs +++ b/crates/pop-contracts/src/call.rs @@ -2,6 +2,7 @@ use crate::{ errors::Error, + submit_signed_payload, utils::{ get_manifest_path, metadata::{process_function_args, FunctionType}, @@ -11,16 +12,18 @@ use crate::{ use anyhow::Context; use contract_build::Verbosity; use contract_extrinsics::{ - BalanceVariant, CallCommandBuilder, CallExec, ContractArtifacts, DisplayEvents, ErrorVariant, - ExtrinsicOptsBuilder, TokenMetadata, + extrinsic_calls::Call, BalanceVariant, CallCommandBuilder, CallExec, ContractArtifacts, + DisplayEvents, ErrorVariant, ExtrinsicOptsBuilder, TokenMetadata, }; use ink_env::{DefaultEnvironment, Environment}; use pop_common::{create_signer, Config, DefaultConfig, Keypair}; use sp_weights::Weight; use std::path::PathBuf; +use subxt::{tx::Payload, SubstrateConfig}; use url::Url; /// Attributes for the `call` command. +#[derive(Clone, Debug, PartialEq)] pub struct CallOpts { /// Path to the contract build directory. pub path: Option, @@ -173,6 +176,53 @@ pub async fn call_smart_contract( Ok(output) } +/// Executes a smart contract call using a signed payload. +/// +/// # Arguments +/// +/// * `call_exec` - A struct containing the details of the contract call. +/// * `payload` - The signed payload string to be submitted for executing the call. +/// * `url` - The endpoint of the node where the call is executed. +pub async fn call_smart_contract_from_signed_payload( + call_exec: CallExec, + payload: String, + url: &Url, +) -> anyhow::Result { + let token_metadata = TokenMetadata::query::(url).await?; + let metadata = call_exec.client().metadata(); + let events = submit_signed_payload(url.as_str(), payload).await?; + let display_events = DisplayEvents::from_events::( + &events, None, &metadata, + )?; + + let output = + display_events.display_events::(Verbosity::Default, &token_metadata)?; + Ok(output) +} + +/// Generates the payload for executing a smart contract call. +/// +/// # Arguments +/// * `call_exec` - A struct containing the details of the contract call. +/// * `gas_limit` - The maximum amount of gas allocated for executing the contract call. +pub fn get_call_payload( + call_exec: &CallExec, + gas_limit: Weight, +) -> anyhow::Result> { + let storage_deposit_limit: Option = call_exec.opts().storage_deposit_limit(); + let mut encoded_data = Vec::::new(); + Call::new( + call_exec.contract().into(), + call_exec.value(), + gas_limit, + storage_deposit_limit.as_ref(), + call_exec.call_data().clone(), + ) + .build() + .encode_call_data_to(&call_exec.client().metadata(), &mut encoded_data)?; + Ok(encoded_data) +} + #[cfg(test)] mod tests { use super::*; @@ -335,7 +385,7 @@ mod tests { #[tokio::test] async fn call_works() -> Result<()> { - let random_port = find_free_port(); + let random_port = find_free_port(None); let localhost_url = format!("ws://127.0.0.1:{}", random_port); let temp_dir = new_environment("testing")?; let current_dir = env::current_dir().expect("Failed to get current directory"); diff --git a/crates/pop-contracts/src/lib.rs b/crates/pop-contracts/src/lib.rs index c5e76e2f1..6dded2fe0 100644 --- a/crates/pop-contracts/src/lib.rs +++ b/crates/pop-contracts/src/lib.rs @@ -14,7 +14,8 @@ mod utils; pub use build::{build_smart_contract, is_supported, Verbosity}; pub use call::{ - call_smart_contract, dry_run_call, dry_run_gas_estimate_call, set_up_call, CallOpts, + call_smart_contract, call_smart_contract_from_signed_payload, dry_run_call, + dry_run_gas_estimate_call, get_call_payload, set_up_call, CallOpts, }; pub use new::{create_smart_contract, is_valid_contract_name}; pub use node::{contracts_node_generator, is_chain_alive, run_contracts_node}; @@ -22,10 +23,15 @@ pub use templates::{Contract, ContractType}; pub use test::{test_e2e_smart_contract, test_smart_contract}; pub use testing::{mock_build_process, new_environment}; pub use up::{ - dry_run_gas_estimate_instantiate, dry_run_upload, instantiate_smart_contract, - set_up_deployment, set_up_upload, upload_smart_contract, UpOpts, + dry_run_gas_estimate_instantiate, dry_run_upload, get_code_hash_from_event, get_contract_code, + get_instantiate_payload, get_upload_payload, instantiate_contract_signed, + instantiate_smart_contract, set_up_deployment, set_up_upload, submit_signed_payload, + upload_contract_signed, upload_smart_contract, ContractInfo, UpOpts, }; pub use utils::{ - metadata::{get_messages, ContractFunction}, + metadata::{get_message, get_messages, ContractFunction}, parse_account, parse_hex_bytes, }; +// External exports +pub use contract_extrinsics::CallExec; +pub use ink_env::DefaultEnvironment; diff --git a/crates/pop-contracts/src/node/mod.rs b/crates/pop-contracts/src/node.rs similarity index 91% rename from crates/pop-contracts/src/node/mod.rs rename to crates/pop-contracts/src/node.rs index be5680a04..158865d40 100644 --- a/crates/pop-contracts/src/node/mod.rs +++ b/crates/pop-contracts/src/node.rs @@ -19,9 +19,11 @@ use std::{ process::{Child, Command, Stdio}, time::Duration, }; +use subxt::{dynamic::Value, SubstrateConfig}; use tokio::time::sleep; const BIN_NAME: &str = "substrate-contracts-node"; +const STARTUP: Duration = Duration::from_millis(20_000); /// Checks if the specified node is alive and responsive. /// @@ -131,8 +133,24 @@ pub async fn run_contracts_node( let process = command.spawn()?; - // Wait 5 secs until the node is ready - sleep(Duration::from_millis(5000)).await; + // Wait until the node is ready + sleep(STARTUP).await; + + let data = Value::from_bytes(subxt::utils::to_hex("initialize contracts node")); + let payload = subxt::dynamic::tx("System", "remark", [data].to_vec()); + + let client = subxt::client::OnlineClient::::from_url(format!( + "ws://127.0.0.1:{}", + port + )) + .await + .map_err(|e| Error::AnyhowError(e.into()))?; + client + .tx() + .sign_and_submit_default(&payload, &subxt_signer::sr25519::dev::alice()) + .await + .map_err(|e| Error::AnyhowError(e.into()))?; + Ok(process) } @@ -222,7 +240,7 @@ mod tests { #[ignore = "Works fine locally but is causing issues when running tests in parallel in the CI environment."] #[tokio::test] async fn run_contracts_node_works() -> Result<(), Error> { - let random_port = find_free_port(); + let random_port = find_free_port(None); let localhost_url = format!("ws://127.0.0.1:{}", random_port); let local_url = url::Url::parse(&localhost_url)?; diff --git a/crates/pop-contracts/src/up.rs b/crates/pop-contracts/src/up.rs index bed7dfa45..3ed614c7e 100644 --- a/crates/pop-contracts/src/up.rs +++ b/crates/pop-contracts/src/up.rs @@ -1,4 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 + use crate::{ errors::Error, utils::{ @@ -8,17 +9,29 @@ use crate::{ }, }; use contract_extrinsics::{ - BalanceVariant, ErrorVariant, ExtrinsicOptsBuilder, InstantiateCommandBuilder, InstantiateExec, - TokenMetadata, UploadCommandBuilder, UploadExec, + events::{CodeStored, ContractInstantiated}, + extrinsic_calls::{Instantiate, InstantiateWithCode, UploadCode}, + upload::Determinism, + BalanceVariant, Code, ErrorVariant, ExtrinsicOptsBuilder, InstantiateCommandBuilder, + InstantiateExec, InstantiateExecResult, TokenMetadata, UploadCommandBuilder, UploadExec, + UploadResult, WasmCode, }; use ink_env::{DefaultEnvironment, Environment}; use pop_common::{create_signer, DefaultConfig, Keypair}; -use sp_core::Bytes; +use sp_core::{bytes::from_hex, Bytes}; use sp_weights::Weight; -use std::{fmt::Write, path::PathBuf}; +use std::{ + fmt::Write, + path::{Path, PathBuf}, +}; +use subxt::{ + blocks::ExtrinsicEvents, + tx::{Payload, SubmittableExtrinsic}, + Config, SubstrateConfig, +}; /// Attributes for the `up` command -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct UpOpts { /// Path to the contract build directory. pub path: Option, @@ -100,14 +113,177 @@ pub async fn set_up_upload( let upload_exec: UploadExec = UploadCommandBuilder::new(extrinsic_opts).done().await?; + Ok(upload_exec) } +/// Gets the encoded payload call data for contract upload (not instantiate). +/// +/// # Arguments +/// * `code` - contract code to upload. +/// * `url` - the rpc of the chain node. +pub async fn get_upload_payload(code: WasmCode, url: &str) -> anyhow::Result> { + let storage_deposit_limit: Option = None; + let upload_code = UploadCode::new(code, storage_deposit_limit, Determinism::Enforced); + + let rpc_client = subxt::backend::rpc::RpcClient::from_url(url).await?; + let client = subxt::OnlineClient::::from_rpc_client(rpc_client).await?; + + let call_data = upload_code.build(); + let mut encoded_data = Vec::::new(); + call_data.encode_call_data_to(&client.metadata(), &mut encoded_data)?; + Ok(encoded_data) +} + +/// Gets the encoded payload call data for a contract instantiation. +/// +/// # Arguments +/// * `instantiate_exec` - arguments for contract instantiate. +/// * `gas_limit` - max amount of gas to be used for instantiation. +pub fn get_instantiate_payload( + instantiate_exec: InstantiateExec, + gas_limit: Weight, +) -> anyhow::Result> { + let storage_deposit_limit: Option = None; + let mut encoded_data = Vec::::new(); + let args = instantiate_exec.args(); + match args.code() { + Code::Upload(code) => InstantiateWithCode::new( + args.value(), + gas_limit, + storage_deposit_limit, + code.clone(), + args.data().into(), + args.salt().into(), + ) + .build() + .encode_call_data_to(&instantiate_exec.client().metadata(), &mut encoded_data), + Code::Existing(hash) => Instantiate::new( + args.value(), + gas_limit, + storage_deposit_limit, + hash, + args.data().into(), + args.salt().into(), + ) + .build() + .encode_call_data_to(&instantiate_exec.client().metadata(), &mut encoded_data), + }?; + + Ok(encoded_data) +} + +/// Reads the contract code from contract file. +/// +/// # Arguments +/// * `path` - path to the contract file. +pub fn get_contract_code(path: Option<&PathBuf>) -> anyhow::Result { + let manifest_path = get_manifest_path(path.map(|p| p as &Path))?; + + // signer does not matter for this + let signer = create_signer("//Alice")?; + let extrinsic_opts = + ExtrinsicOptsBuilder::::new(signer) + .manifest_path(Some(manifest_path)) + .done(); + let artifacts = extrinsic_opts.contract_artifacts()?; + + let artifacts_path = artifacts.artifact_path().to_path_buf(); + let code = artifacts.code.ok_or_else(|| { + Error::UploadContractError(format!( + "Contract code not found from artifact file {}", + artifacts_path.display() + )) + })?; + Ok(code) +} + +/// Submit a pre-signed payload for uploading a contract. +/// +/// # Arguments +/// * `url` - rpc for chain. +/// * `payload` - the signed payload to submit (encoded call data). +pub async fn upload_contract_signed( + url: &str, + payload: String, +) -> anyhow::Result> { + let events = submit_signed_payload(url, payload).await?; + + let code_stored = events.find_first::>()?; + + Ok(UploadResult { code_stored, events }) +} + +/// Submit a pre-signed payload for instantiating a contract. +/// +/// # Arguments +/// * `url` - rpc for chain. +/// * `payload` - the signed payload to submit (encoded call data). +pub async fn instantiate_contract_signed( + url: &str, + payload: String, +) -> anyhow::Result> { + let events = submit_signed_payload(url, payload).await?; + + // The CodeStored event is only raised if the contract has not already been + // uploaded. + let code_hash = events + .find_first::>()? + .map(|code_stored| code_stored.code_hash); + + let instantiated = events + .find_first::>()? + .ok_or_else(|| { + Error::InstantiateContractError("Failed to find Instantiated event".to_string()) + })?; + + Ok(InstantiateExecResult { events, code_hash, contract_address: instantiated.contract }) +} + +/// Submit a pre-signed payload. +/// +/// # Arguments +/// * `url` - rpc for chain. +/// * `payload` - the signed payload to submit (encoded call data). +pub async fn submit_signed_payload( + url: &str, + payload: String, +) -> anyhow::Result> { + let rpc_client = subxt::backend::rpc::RpcClient::from_url(url).await?; + let client = subxt::OnlineClient::::from_rpc_client(rpc_client).await?; + + let hex_encoded = from_hex(&payload)?; + + let extrinsic = SubmittableExtrinsic::from_bytes(client, hex_encoded); + + // src: https://github.com/use-ink/cargo-contract/blob/68691b9b6cdb7c6ec52ea441b3dc31fcb1ce08e0/crates/extrinsics/src/lib.rs#L143 + + use subxt::{ + error::{RpcError, TransactionError}, + tx::TxStatus, + }; + + let mut tx = extrinsic.submit_and_watch().await?; + + while let Some(status) = tx.next().await { + match status? { + TxStatus::InFinalizedBlock(tx_in_block) => { + let events = tx_in_block.wait_for_success().await?; + return Ok(events) + }, + TxStatus::Error { message } => return Err(TransactionError::Error(message).into()), + TxStatus::Invalid { message } => return Err(TransactionError::Invalid(message).into()), + TxStatus::Dropped { message } => return Err(TransactionError::Dropped(message).into()), + _ => continue, + } + } + Err(RpcError::SubscriptionDropped.into()) +} + /// Estimate the gas required for instantiating a contract without modifying the state of the /// blockchain. /// /// # Arguments -/// /// * `instantiate_exec` - the preprocessed data to instantiate a contract. pub async fn dry_run_gas_estimate_instantiate( instantiate_exec: &InstantiateExec, @@ -136,14 +312,15 @@ pub async fn dry_run_gas_estimate_instantiate( /// Result of a dry-run upload of a smart contract. pub struct UploadDryRunResult { + /// The key under which the new code is stored. pub code_hash: String, + /// The deposit that was reserved at the caller. Is zero when the code already existed. pub deposit: String, } /// Performs a dry-run for uploading a contract without modifying the state of the blockchain. /// /// # Arguments -/// /// * `upload_exec` - the preprocessed data to upload a contract. pub async fn dry_run_upload( upload_exec: &UploadExec, @@ -175,7 +352,6 @@ pub struct ContractInfo { /// Instantiate a contract. /// /// # Arguments -/// /// * `instantiate_exec` - the preprocessed data to instantiate a contract. /// * `gas_limit` - maximum amount of gas to be used for this call. pub async fn instantiate_smart_contract( @@ -195,7 +371,6 @@ pub async fn instantiate_smart_contract( /// Upload a contract. /// /// # Arguments -/// /// * `upload_exec` - the preprocessed data to upload a contract. pub async fn upload_smart_contract( upload_exec: &UploadExec, @@ -204,14 +379,26 @@ pub async fn upload_smart_contract( .upload_code() .await .map_err(|error_variant| Error::UploadContractError(format!("{:?}", error_variant)))?; - if let Some(code_stored) = upload_result.code_stored { + get_code_hash_from_event(&upload_result, upload_exec.code().code_hash()) +} + +/// Get the code hash of a contract from the upload event. +/// +/// # Arguments +/// * `upload_result` - the result of uploading the contract. +/// * `metadata_code_hash` - the code hash from the metadata Used only for error reporting. +pub fn get_code_hash_from_event( + upload_result: &UploadResult, + // used for error reporting + metadata_code_hash: [u8; 32], +) -> Result { + if let Some(code_stored) = upload_result.code_stored.as_ref() { Ok(format!("{:?}", code_stored.code_hash)) } else { - let code_hash: String = - upload_exec.code().code_hash().iter().fold(String::new(), |mut output, b| { - write!(output, "{:02x}", b).expect("expected to write to string"); - output - }); + let code_hash: String = metadata_code_hash.iter().fold(String::new(), |mut output, b| { + write!(output, "{:02x}", b).expect("expected to write to string"); + output + }); Err(Error::UploadContractError(format!( "This contract has already been uploaded with code hash: 0x{code_hash}" ))) @@ -228,6 +415,10 @@ mod tests { use anyhow::Result; use pop_common::{find_free_port, set_executable_permission}; use std::{env, process::Command, time::Duration}; + use subxt::{ + config::{substrate::BlakeTwo256, Hasher}, + utils::H256, + }; use tokio::time::sleep; use url::Url; @@ -281,6 +472,43 @@ mod tests { Ok(()) } + #[tokio::test] + async fn get_payload_works() -> Result<()> { + let temp_dir = new_environment("testing")?; + let current_dir = env::current_dir().expect("Failed to get current directory"); + mock_build_process( + temp_dir.path().join("testing"), + current_dir.join("./tests/files/testing.contract"), + current_dir.join("./tests/files/testing.json"), + )?; + let up_opts = UpOpts { + path: Some(temp_dir.path().join("testing")), + constructor: "new".to_string(), + args: ["false".to_string()].to_vec(), + value: "1000".to_string(), + gas_limit: None, + proof_size: None, + salt: None, + url: Url::parse(CONTRACTS_NETWORK_URL)?, + suri: "//Alice".to_string(), + }; + let contract_code = get_contract_code(up_opts.path.as_ref())?; + let call_data = get_upload_payload(contract_code, CONTRACTS_NETWORK_URL).await?; + let payload_hash = BlakeTwo256::hash(&call_data); + // We know that for the above opts the payload hash should be: + // 0x98c24584107b3a01d12e8e02c0bb634d15dc86123c44d186206813ede42f478d + let hex_bytes = + from_hex("98c24584107b3a01d12e8e02c0bb634d15dc86123c44d186206813ede42f478d") + .expect("Invalid hex string"); + + let hex_array: [u8; 32] = hex_bytes.try_into().expect("Expected 32-byte array"); + + // Create `H256` from the `[u8; 32]` array + let expected_hash = H256::from(hex_array); + assert_eq!(expected_hash, payload_hash); + Ok(()) + } + #[tokio::test] async fn dry_run_gas_estimate_instantiate_works() -> Result<()> { let temp_dir = new_environment("testing")?; @@ -365,7 +593,7 @@ mod tests { #[tokio::test] async fn instantiate_and_upload() -> Result<()> { - let random_port = find_free_port(); + let random_port = find_free_port(None); let localhost_url = format!("ws://127.0.0.1:{}", random_port); let temp_dir = new_environment("testing")?; let current_dir = env::current_dir().expect("Failed to get current directory"); diff --git a/crates/pop-contracts/src/utils/metadata.rs b/crates/pop-contracts/src/utils/metadata.rs index 111468f0e..0c2d26d6d 100644 --- a/crates/pop-contracts/src/utils/metadata.rs +++ b/crates/pop-contracts/src/utils/metadata.rs @@ -117,7 +117,7 @@ fn get_contract_functions( /// # Arguments /// * `path` - Location path of the project or contract artifact. /// * `message` - The label of the contract message. -fn get_message

(path: P, message: &str) -> Result +pub fn get_message

(path: P, message: &str) -> Result where P: AsRef, { diff --git a/crates/pop-contracts/tests/files/testing.wasm b/crates/pop-contracts/tests/files/testing.wasm index 430888669464e770d310139f29e8af1ebf865ba4..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 GIT binary patch literal 0 HcmV?d00001 literal 3710 zcmb_fO>ZPu6}|7Hx~pC8aXC1S84uE{iiI30?T;bxhzSyYkzz9%A+uqL>>j(uxZBen zSNAv>DN1{y2#GCQ#0H5i8-4|1#S$U0VGA2J2nk5ed9T|aN+LFl<*Daa@1A?_x#!;3 zE#2WWB_isb)`1?6<-m-`V>LbyIj~r9$(_kxa-hOX?BcK*>ro>aR^8Lh?6?~34@YNp z_qY-h_BN7}!Sfd*k?`03Z2099*z2C|*VX7nJrEnM<}bRZFRJ}f-5m^{R&`w+iI{3| z(RfxpJF8!6rE~MfVxsU*8i-r0G*gWXMC-<)87qHzX|u8Tf4@rjxcoH9n)BKoc27^` zn~kID`RUn9xjXaUrN!69DQ9-iF>FHZX2 zoR>+jFisVgZwwAP!(-anj>tn1XT9p-t>%NKnKQ28l0E=GJ}*;j0$#bKS0;e_0Jce? zBG3=pKyzwU%a%rT-u4QF$cLE~1m_Y$*tietQQ7oM=S2&#HTzsOorQ5>r*N=#*0nAI zW@%k>RDTU}1F;kc(;+~lt6c+01Q;9BQP zJgAPeA85lN+6ms3h;YK42}eV!npF4877Hr2Db zo1sVqd%(S+6dZ{Xi7JaWKpB-nVI%>cE9+891aw{lswp5C9Yw7emt0J>a*ge>9z@cj zRuWVRe?gRi06;+FfnDStV+^_9(9IMFNhU!hR7ew%LA;<2+=j*qX$FO$nQk`~_=L+q zAOduHLf{NG0m>?slmMGb2c>}f|JhX3Rhh-6g7pXu2A)#HWkXXXE}J%0fQwC)UNu$n zwM}(J6awT7LDNqe+G~@s8|1L5Ch3J%Z)RTUu^yXk1foA^tGm!0l!j3SPS{I)U*7pqR3A@Pcalxt>?~of6Z>3xev%T=q~D zUvjcc{Quy}|HOVv&+6@9Ka1wd?*zH5>e(nGAUK)9ct_o%b`_XbX5B9xmfqyPQZ}u9mW9#KT5f2sSZ%Fn% zW{TMk3i+;ps?gQoUWcPBpZ`zT*BPgR(<~@br|~at$=d1nFE7;toq|O`fqOO#+PSp( zyxyi%)3U+GY#G=L+J#yWBq+iFN3VcH#*YSN?Ub`QB?f?K2SA{(00|3>VnD*09Z7|g ze%6NW(I`f;G$fxdX20fj0=74%Y$Mbyy%J7}U%_X9TE;Wxrm8s z#1kk+nYtwZ29+1i8^3f>pzVErftnbfUK9=XI1HD1_&8ufn;XBr3k&1_D)IR@*+p&s zsdj5jZwvxIw~Ji3_||S+w=L34^BKBWMM#(tK>}|Rw<9a~G=kPw{*OS3<_`fB?SBKz zw!x@Tv%GQrpVais&*&{4`m^8=Mr~}k>u`A=gv6H+{{rI2^$_oPM6!fwh2f?c)dEtJ zSoYN^M=Kl{q*S45A#$|is(!oFY$2>x7(Oz?t&yMx=qimL=!DZPo!Gse;VlgqpFk1T<3Se(<8FPCwND(&g%kyOvL!IYK=m*JP zmn?rRu%GO+T7t~3Wx7;Cb352U zVE2gGk%gcx0Q?1J5#YDif+qqbh$}I~I=K&n)}dPr8cqZo|G0B3lr{xdIV0 z)*uXc#b5}-hXn@X=lNp3FeRT=fVksU_Ni86)@J`w>pvb=^>Dp=T7C4S>JHY|4!ia7 z*?L_apA1L!%k^P>xPCG?syR+XyDr;P_-vt)C6{4}aP{8LU5f@f6>v*6N{%lIJ4w6a3!6@BhXB^KN}Idf6G` hi&oV+JUglmJKtH`THCxIVhI~`eF@)v`23K*zW{;(I0gU! diff --git a/crates/pop-parachains/Cargo.toml b/crates/pop-parachains/Cargo.toml index 64bef2d67..6380fbd10 100644 --- a/crates/pop-parachains/Cargo.toml +++ b/crates/pop-parachains/Cargo.toml @@ -24,11 +24,11 @@ tokio.workspace = true url.workspace = true askama.workspace = true -hex.workspace = true indexmap.workspace = true reqwest.workspace = true scale-info.workspace = true scale-value.workspace = true +sp-core.workspace = true subxt.workspace = true symlink.workspace = true toml_edit.workspace = true diff --git a/crates/pop-parachains/src/call/metadata/mod.rs b/crates/pop-parachains/src/call/metadata/mod.rs index 1f53f523c..5a9a9a2cb 100644 --- a/crates/pop-parachains/src/call/metadata/mod.rs +++ b/crates/pop-parachains/src/call/metadata/mod.rs @@ -4,7 +4,7 @@ use crate::errors::Error; use params::Param; use scale_value::stringify::custom_parsers; use std::fmt::{Display, Formatter}; -use subxt::{dynamic::Value, Metadata, OnlineClient, SubstrateConfig}; +use subxt::{dynamic::Value, utils::to_hex, Metadata, OnlineClient, SubstrateConfig}; pub mod action; pub mod params; @@ -179,7 +179,7 @@ pub fn parse_dispatchable_arguments( .map(|(param, raw_param)| { // Convert sequence parameters to hex if is_sequence let processed_param = if param.is_sequence && !raw_param.starts_with("0x") { - format!("0x{}", hex::encode(raw_param)) + to_hex(&raw_param) } else { raw_param }; @@ -199,6 +199,7 @@ mod tests { use crate::{call::tests::POP_NETWORK_TESTNET_URL, set_up_client}; use anyhow::Result; + use sp_core::bytes::from_hex; use subxt::ext::scale_bits; #[tokio::test] @@ -283,7 +284,7 @@ mod tests { ] .to_vec(); let addr: Vec<_> = - hex::decode("8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48") + from_hex("8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48") .unwrap() .into_iter() .map(|b| Value::u128(b as u128)) diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs index 1f7e61b22..f5bb05b71 100644 --- a/crates/pop-parachains/src/call/mod.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -5,12 +5,12 @@ use pop_common::{ call::{DefaultEnvironment, DisplayEvents, TokenMetadata, Verbosity}, create_signer, }; +use sp_core::bytes::{from_hex, to_hex}; use subxt::{ dynamic::Value, - tx::{DynamicPayload, Payload}, + tx::{DynamicPayload, Payload, SubmittableExtrinsic}, OnlineClient, SubstrateConfig, }; - pub mod metadata; /// Sets up an [OnlineClient] instance for connecting to a blockchain. @@ -82,6 +82,28 @@ pub async fn sign_and_submit_extrinsic( Ok(format!("Extrinsic Submitted with hash: {:?}\n\n{}", result.extrinsic_hash(), events)) } +/// Submits a signed extrinsic. +/// +/// # Arguments +/// * `client` - The client used to interact with the chain. +/// * `payload` - The signed payload string to be submitted. +pub async fn submit_signed_extrinsic( + client: OnlineClient, + payload: String, +) -> Result { + 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 + .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())) +} + /// Encodes the call data for a given extrinsic into a hexadecimal string. /// /// # Arguments @@ -94,7 +116,7 @@ pub fn encode_call_data( let call_data = xt .encode_call_data(&client.metadata()) .map_err(|e| Error::CallDataEncodingError(e.to_string()))?; - Ok(format!("0x{}", hex::encode(call_data))) + Ok(to_hex(&call_data, false)) } /// Decodes a hex-encoded string into a vector of bytes representing the call data. @@ -102,8 +124,7 @@ pub fn encode_call_data( /// # Arguments /// * `call_data` - The hex-encoded string representing call data. pub fn decode_call_data(call_data: &str) -> Result, Error> { - hex::decode(call_data.trim_start_matches("0x")) - .map_err(|e| Error::CallDataDecodingError(e.to_string())) + from_hex(call_data).map_err(|e| Error::CallDataDecodingError(e.to_string())) } /// This struct implements the [`Payload`] trait and is used to submit diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index 5bba81542..7e70c2149 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -24,14 +24,17 @@ pub use call::{ params::Param, parse_chain_metadata, Function, Pallet, }, - set_up_client, sign_and_submit_extrinsic, CallData, + set_up_client, sign_and_submit_extrinsic, submit_signed_extrinsic, CallData, }; pub use errors::Error; pub use indexmap::IndexSet; pub use new_pallet::{create_pallet_template, new_pallet_options::*, TemplatePalletConfig}; pub use new_parachain::instantiate_template_dir; // External export from subxt. -pub use subxt::{tx::DynamicPayload, OnlineClient, SubstrateConfig}; +pub use subxt::{ + tx::{DynamicPayload, Payload}, + OnlineClient, SubstrateConfig, +}; pub use templates::{Config, Parachain, Provider}; pub use up::Zombienet; pub use utils::helpers::is_initial_endowment_valid; From c1ed1ceaf28a7f176a9097680aca32d26dc1865c Mon Sep 17 00:00:00 2001 From: Frank Bell <60948618+evilrobot-01@users.noreply.github.com> Date: Thu, 19 Dec 2024 09:28:46 +0000 Subject: [PATCH 206/211] fix: hrmp (#278) * chore: switch pop network configuration to paseo with hrmp channels * test: add relay+asset hub network config files * feat: clean dmpq state at genesis (#279) * dispatch kill tx * paseo-local metadata * chore: address hrmp channels at creation * clean unnecessary deps * fix: don't wait for finalisation * Provide documentation * refactor: improve ux * feat: add rococo-local metadata * fix: resolve merge conflict * chore: update zombienet-sdk * chore: update cargo.lock * refactor: use dynamic tx * docs: add variant doc comment * refactor: address clippy warning * chore: remove duplicate dependency after rebase * refactor: identify hrmp_channels earlier * test: add supported relay chains test * refactor: use construct_sudo_extrinsic * refactor: split into smaller functions for better testing * chore: add westend network configs * refactor: replace rococo with westend * refactor: remove duplicate import Co-authored-by: Peter White * refactor: remove unusued parameter Co-authored-by: Peter White * refactor: use global import Co-authored-by: Peter White * fix: update error message * refactor: improve function name * refactor: use non-clashing variable name to support global import * refactor: reuse spinner --------- Co-authored-by: Alejandro Martinez Andres <11448715+al3mart@users.noreply.github.com> Co-authored-by: Peter White --- Cargo.lock | 1 + crates/pop-cli/src/commands/call/chain.rs | 2 +- crates/pop-cli/src/commands/up/parachain.rs | 43 +++++- crates/pop-parachains/Cargo.toml | 3 +- crates/pop-parachains/src/call/mod.rs | 6 +- crates/pop-parachains/src/errors.rs | 3 + crates/pop-parachains/src/lib.rs | 2 + crates/pop-parachains/src/relay.rs | 146 ++++++++++++++++++++ crates/pop-parachains/src/up/mod.rs | 69 ++++++++- crates/pop-parachains/src/utils/helpers.rs | 4 +- tests/networks/kusama+asset-hub.toml | 19 +++ tests/networks/paseo+asset-hub.toml | 19 +++ tests/networks/polkadot+asset-hub.toml | 19 +++ tests/networks/pop.toml | 14 +- tests/networks/westend+asset-hub.toml | 19 +++ tests/networks/westend.toml | 12 ++ 16 files changed, 364 insertions(+), 17 deletions(-) create mode 100644 crates/pop-parachains/src/relay.rs create mode 100644 tests/networks/kusama+asset-hub.toml create mode 100644 tests/networks/paseo+asset-hub.toml create mode 100644 tests/networks/polkadot+asset-hub.toml create mode 100644 tests/networks/westend+asset-hub.toml create mode 100644 tests/networks/westend.toml diff --git a/Cargo.lock b/Cargo.lock index f581a91d5..ac67e52da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9469,6 +9469,7 @@ dependencies = [ "strum 0.26.3", "strum_macros 0.26.4", "subxt", + "subxt-signer", "symlink", "tar", "tempfile", diff --git a/crates/pop-cli/src/commands/call/chain.rs b/crates/pop-cli/src/commands/call/chain.rs index 41348826f..18f61fe77 100644 --- a/crates/pop-cli/src/commands/call/chain.rs +++ b/crates/pop-cli/src/commands/call/chain.rs @@ -403,7 +403,7 @@ impl Call { }, }; // If sudo is required, wrap the call in a sudo call. - let xt = if self.sudo { construct_sudo_extrinsic(xt)? } else { xt }; + let xt = if self.sudo { construct_sudo_extrinsic(xt) } else { xt }; let encoded_data = encode_call_data(client, &xt)?; // If the encoded call data is too long, don't display it all. if encoded_data.len() < ENCODED_CALL_DATA_MAX_LEN { diff --git a/crates/pop-cli/src/commands/up/parachain.rs b/crates/pop-cli/src/commands/up/parachain.rs index 4d59646d0..30fc6fce0 100644 --- a/crates/pop-cli/src/commands/up/parachain.rs +++ b/crates/pop-cli/src/commands/up/parachain.rs @@ -3,13 +3,13 @@ use crate::style::{style, Theme}; use clap::Args; use cliclack::{ - clear_screen, confirm, intro, log, multi_progress, outro, outro_cancel, set_theme, ProgressBar, - Theme as _, ThemeState, + clear_screen, confirm, intro, log, multi_progress, outro, outro_cancel, set_theme, spinner, + ProgressBar, Theme as _, ThemeState, }; use console::{Emoji, Style, Term}; use duct::cmd; use pop_common::Status; -use pop_parachains::{Error, IndexSet, NetworkNode, Zombienet}; +use pop_parachains::{clear_dmpq, Error, IndexSet, NetworkNode, RelayChain, Zombienet}; use std::{path::Path, time::Duration}; use tokio::time::sleep; @@ -91,8 +91,8 @@ impl ZombienetCommand { } // Finally spawn network and wait for signal to terminate - let spinner = cliclack::spinner(); - spinner.start("🚀 Launching local network..."); + let progress = spinner(); + progress.start("🚀 Launching local network..."); match zombienet.spawn().await { Ok(network) => { let mut result = @@ -143,10 +143,39 @@ impl ZombienetCommand { } if let Some(command) = &self.command { - run_custom_command(&spinner, command).await?; + run_custom_command(&progress, command).await?; + } + + progress.stop(result); + + // Check for any specified channels + if zombienet.hrmp_channels() { + let relay_chain = zombienet.relay_chain(); + match RelayChain::from(relay_chain) { + None => { + log::error(format!("🚫 Using `{relay_chain}` with HRMP channels is currently unsupported. Please use `paseo-local` or `westend-local`."))?; + }, + Some(_) => { + let progress = spinner(); + progress.start("Connecting to relay chain to prepare channels..."); + // Allow relay node time to start + sleep(Duration::from_secs(10)).await; + progress.set_message("Preparing channels..."); + let relay_endpoint = network.relaychain().nodes()[0].client().await?; + let para_ids: Vec<_> = + network.parachains().iter().map(|p| p.para_id()).collect(); + tokio::spawn(async move { + if let Err(e) = clear_dmpq(relay_endpoint, ¶_ids).await { + progress.stop(format!("🚫 Could not prepare channels: {e}")); + return Ok::<(), Error>(()); + } + progress.stop("Channels successfully prepared for initialization."); + Ok::<(), Error>(()) + }); + }, + } } - spinner.stop(result); tokio::signal::ctrl_c().await?; outro("Done")?; }, diff --git a/crates/pop-parachains/Cargo.toml b/crates/pop-parachains/Cargo.toml index 6380fbd10..89c1df6d9 100644 --- a/crates/pop-parachains/Cargo.toml +++ b/crates/pop-parachains/Cargo.toml @@ -17,6 +17,8 @@ glob.workspace = true serde_json.workspace = true strum.workspace = true strum_macros.workspace = true +subxt-signer.workspace = true +subxt.workspace = true tar.workspace = true tempfile.workspace = true thiserror.workspace = true @@ -29,7 +31,6 @@ reqwest.workspace = true scale-info.workspace = true scale-value.workspace = true sp-core.workspace = true -subxt.workspace = true symlink.workspace = true toml_edit.workspace = true walkdir.workspace = true diff --git a/crates/pop-parachains/src/call/mod.rs b/crates/pop-parachains/src/call/mod.rs index f5bb05b71..4569687ec 100644 --- a/crates/pop-parachains/src/call/mod.rs +++ b/crates/pop-parachains/src/call/mod.rs @@ -41,8 +41,8 @@ pub fn construct_extrinsic( /// # Arguments /// * `xt`: The extrinsic representing the dispatchable function call to be dispatched with `Root` /// privileges. -pub fn construct_sudo_extrinsic(xt: DynamicPayload) -> Result { - Ok(subxt::dynamic::tx("Sudo", "sudo", [xt.into_value()].to_vec())) +pub fn construct_sudo_extrinsic(xt: DynamicPayload) -> DynamicPayload { + subxt::dynamic::tx("Sudo", "sudo", [xt.into_value()].to_vec()) } /// Signs and submits a given extrinsic. @@ -255,7 +255,7 @@ mod tests { "100".to_string(), ], )?; - let xt = construct_sudo_extrinsic(xt)?; + let xt = construct_sudo_extrinsic(xt); assert_eq!(xt.call_name(), "sudo"); assert_eq!(xt.pallet_name(), "Sudo"); Ok(()) diff --git a/crates/pop-parachains/src/errors.rs b/crates/pop-parachains/src/errors.rs index 8bd97434a..2dd8805ac 100644 --- a/crates/pop-parachains/src/errors.rs +++ b/crates/pop-parachains/src/errors.rs @@ -62,6 +62,9 @@ pub enum Error { RustfmtError(std::io::Error), #[error("Template error: {0}")] SourcingError(#[from] pop_common::sourcing::Error), + /// An error occurred whilst interacting with a chain using `subxt`. + #[error("Subxt error: {0}")] + SubXtError(#[from] subxt::Error), #[error("Toml error: {0}")] TomlError(#[from] toml_edit::de::Error), #[error("Unsupported command: {0}")] diff --git a/crates/pop-parachains/src/lib.rs b/crates/pop-parachains/src/lib.rs index 7e70c2149..50bb6af5b 100644 --- a/crates/pop-parachains/src/lib.rs +++ b/crates/pop-parachains/src/lib.rs @@ -8,6 +8,7 @@ mod errors; mod generator; mod new_pallet; mod new_parachain; +mod relay; mod templates; mod up; mod utils; @@ -30,6 +31,7 @@ pub use errors::Error; 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}; // External export from subxt. pub use subxt::{ tx::{DynamicPayload, Payload}, diff --git a/crates/pop-parachains/src/relay.rs b/crates/pop-parachains/src/relay.rs new file mode 100644 index 000000000..6ded8c497 --- /dev/null +++ b/crates/pop-parachains/src/relay.rs @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-3.0 + +use crate::{call, DynamicPayload, Error}; +use sp_core::twox_128; +use subxt::{ + config::BlockHash, + dynamic::{self, Value}, + ext::sp_core, + OnlineClient, PolkadotConfig, +}; + +/// Clears the DMPQ state for the given parachain IDs. +/// +/// # Arguments +/// * `client` - Client for the network which state is to be modified. +/// * `para_ids` - List of ids to build the keys that will be mutated. +pub async fn clear_dmpq( + client: OnlineClient, + para_ids: &[u32], +) -> Result { + // Wait for blocks to be produced. + let mut sub = client.blocks().subscribe_finalized().await?; + for _ in 0..2 { + sub.next().await; + } + + // Generate storage keys to be removed + let clear_dmq_keys = generate_storage_keys(para_ids); + + // Submit calls to remove specified keys + let kill_storage = construct_kill_storage_call(clear_dmq_keys); + let sudo = subxt_signer::sr25519::dev::alice(); + let sudo_call = call::construct_sudo_extrinsic(kill_storage); + Ok(client.tx().sign_and_submit_default(&sudo_call, &sudo).await?) +} + +fn construct_kill_storage_call(keys: Vec>) -> DynamicPayload { + dynamic::tx( + "System", + "kill_storage", + vec![Value::unnamed_composite(keys.into_iter().map(Value::from_bytes))], + ) +} + +fn generate_storage_keys(para_ids: &[u32]) -> Vec> { + let dmp = twox_128("Dmp".as_bytes()); + let dmp_queue_heads = twox_128("DownwardMessageQueueHeads".as_bytes()); + let dmp_queues = twox_128("DownwardMessageQueues".as_bytes()); + let mut clear_dmq_keys = Vec::>::new(); + for id in para_ids { + let id = id.to_le_bytes(); + // DMP Queue Head + let mut key = dmp.to_vec(); + key.extend(&dmp_queue_heads); + key.extend(sp_core::twox_64(&id)); + key.extend(id); + clear_dmq_keys.push(key); + // DMP Queue + let mut key = dmp.to_vec(); + key.extend(&dmp_queues); + key.extend(sp_core::twox_64(&id)); + key.extend(id); + clear_dmq_keys.push(key); + } + clear_dmq_keys +} + +/// A supported relay chain. +#[derive(Debug, PartialEq)] +pub enum RelayChain { + /// Paseo. + PaseoLocal, + /// Westend. + WestendLocal, +} + +impl RelayChain { + /// Attempts to convert a chain identifier into a supported `RelayChain` variant. + /// + /// # Arguments + /// * `id` - The relay chain identifier. + pub fn from(id: &str) -> Option { + match id { + "paseo-local" => Some(RelayChain::PaseoLocal), + "westend-local" => Some(RelayChain::WestendLocal), + _ => None, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use subxt::ext::sp_core::twox_64; + use RelayChain::*; + + #[test] + fn construct_kill_storage_call_works() { + let keys = vec!["key".as_bytes().to_vec()]; + assert_eq!( + construct_kill_storage_call(keys.clone()), + dynamic::tx( + "System", + "kill_storage", + vec![Value::unnamed_composite(keys.into_iter().map(Value::from_bytes))], + ) + ) + } + + #[test] + fn generate_storage_keys_works() { + let para_ids = vec![1_000, 4_385]; + let dmp = twox_128("Dmp".as_bytes()); + let dmp_queue_heads = [dmp, twox_128("DownwardMessageQueueHeads".as_bytes())].concat(); + let dmp_queues = [dmp, twox_128("DownwardMessageQueues".as_bytes())].concat(); + + assert_eq!( + generate_storage_keys(¶_ids), + para_ids + .iter() + .flat_map(|id| { + let id = id.to_le_bytes().to_vec(); + [ + // DMP Queue Head + [dmp_queue_heads.clone(), twox_64(&id).to_vec(), id.clone()].concat(), + // DMP Queue + [dmp_queues.clone(), twox_64(&id).to_vec(), id].concat(), + ] + }) + .collect::>() + ) + } + + #[test] + fn supported_relay_chains() { + for (s, e) in [ + // Only chains with sudo supported + ("paseo-local", Some(PaseoLocal)), + ("westend-local", Some(WestendLocal)), + ("kusama-local", None), + ("polkadot-local", None), + ] { + assert_eq!(RelayChain::from(s), e) + } + } +} diff --git a/crates/pop-parachains/src/up/mod.rs b/crates/pop-parachains/src/up/mod.rs index 036ae889d..91c1678af 100644 --- a/crates/pop-parachains/src/up/mod.rs +++ b/crates/pop-parachains/src/up/mod.rs @@ -30,6 +30,8 @@ pub struct Zombienet { relay_chain: RelayChain, /// The configuration required to launch parachains. parachains: IndexMap, + /// Whether any HRMP channels are to be pre-opened. + hrmp_channels: bool, } impl Zombienet { @@ -59,7 +61,7 @@ impl Zombienet { // Parse network config let network_config = NetworkConfiguration::from(network_config)?; // Determine relay and parachain requirements based on arguments and config - let relay_chain = Self::relay_chain( + let relay_chain = Self::init_relay_chain( relay_chain_version, relay_chain_runtime_version, &network_config, @@ -84,7 +86,9 @@ impl Zombienet { cache, ) .await?; - Ok(Self { network_config, relay_chain, parachains }) + let hrmp_channels = + network_config.hrmp_channels().map(|c| !c.is_empty()).unwrap_or_default(); + Ok(Self { network_config, relay_chain, parachains, hrmp_channels }) } /// The binaries required to launch the network. @@ -216,7 +220,7 @@ impl Zombienet { /// will use the latest available version). /// * `network_config` - The network configuration to be used to launch a network. /// * `cache` - The location used for caching binaries. - async fn relay_chain( + async fn init_relay_chain( version: Option<&str>, runtime_version: Option<&str>, network_config: &NetworkConfiguration, @@ -276,6 +280,16 @@ impl Zombienet { Ok(relay::default(version, runtime_version, chain, cache).await?) } + /// The name of the relay chain. + pub fn relay_chain(&self) -> &str { + &self.relay_chain.chain + } + + /// Whether any HRMP channels are to be pre-opened. + pub fn hrmp_channels(&self) -> bool { + self.hrmp_channels + } + /// Launches the local network. pub async fn spawn(&mut self) -> Result, Error> { // Symlink polkadot workers @@ -354,6 +368,11 @@ impl NetworkConfiguration { self.0.get_mut("parachains").and_then(|p| p.as_array_of_tables_mut()) } + /// Returns the `hrmp_channels` configuration. + fn hrmp_channels(&self) -> Option<&ArrayOfTables> { + self.0.get("hrmp_channels").and_then(|p| p.as_array_of_tables()) + } + /// Returns the `command` configuration. fn command(config: &Table) -> Option<&Item> { config.get("command") @@ -706,6 +725,8 @@ chain = "paseo-local" if *tag == Some(version.to_string()) )); assert!(zombienet.parachains.is_empty()); + assert_eq!(zombienet.relay_chain(), "paseo-local"); + assert!(!zombienet.hrmp_channels()); Ok(()) } @@ -1186,6 +1207,48 @@ default_command = "moonbeam" Ok(()) } + #[tokio::test] + async fn new_with_hrmp_channels_works() -> Result<()> { + let temp_dir = tempdir()?; + let cache = PathBuf::from(temp_dir.path()); + let config = Builder::new().suffix(".toml").tempfile()?; + writeln!( + config.as_file(), + r#" +[relaychain] +chain = "paseo-local" + +[[parachains]] +id = 1000 +chain = "asset-hub-paseo-local" + +[[parachains]] +id = 4385 +default_command = "pop-node" + +[[hrmp_channels]] +sender = 4385 +recipient = 1000 +max_capacity = 1000 +max_message_size = 8000 +"# + )?; + + let zombienet = Zombienet::new( + &cache, + config.path().to_str().unwrap(), + None, + None, + None, + None, + None, + ) + .await?; + + assert!(zombienet.hrmp_channels()); + Ok(()) + } + #[tokio::test] async fn new_ensures_parachain_id_exists() -> Result<()> { let temp_dir = tempdir()?; diff --git a/crates/pop-parachains/src/utils/helpers.rs b/crates/pop-parachains/src/utils/helpers.rs index bd39eec9e..fd8e91d68 100644 --- a/crates/pop-parachains/src/utils/helpers.rs +++ b/crates/pop-parachains/src/utils/helpers.rs @@ -33,7 +33,8 @@ pub fn is_initial_endowment_valid(initial_endowment: &str) -> bool { initial_endowment.parse::().is_ok() || is_valid_bitwise_left_shift(initial_endowment).is_ok() } -// Auxiliar method to check if the endowment input with a shift left (1u64 << 60) format is valid. + +// Auxiliary method to check if the endowment input with a shift left (1u64 << 60) format is valid. // Parse the self << rhs format and check the shift left operation is valid. fn is_valid_bitwise_left_shift(initial_endowment: &str) -> Result { let v: Vec<&str> = initial_endowment.split(" << ").collect(); @@ -87,6 +88,7 @@ mod tests { use super::*; use crate::generator::parachain::ChainSpec; use askama::Template; + use std::env::var; use tempfile::tempdir; #[test] diff --git a/tests/networks/kusama+asset-hub.toml b/tests/networks/kusama+asset-hub.toml new file mode 100644 index 000000000..c652bd841 --- /dev/null +++ b/tests/networks/kusama+asset-hub.toml @@ -0,0 +1,19 @@ +# pop up parachain -f ./tests/networks/kusama+asset-hub.toml + +[relaychain] +chain = "kusama-local" + +[[relaychain.nodes]] +name = "alice" +validator = true + +[[relaychain.nodes]] +name = "bob" +validator = true + +[[parachains]] +id = 1000 +chain = "asset-hub-kusama-local" + +[[parachains.collators]] +name = "asset-hub" \ No newline at end of file diff --git a/tests/networks/paseo+asset-hub.toml b/tests/networks/paseo+asset-hub.toml new file mode 100644 index 000000000..a75719376 --- /dev/null +++ b/tests/networks/paseo+asset-hub.toml @@ -0,0 +1,19 @@ +# pop up parachain -f ./tests/networks/paseo+asset-hub.toml + +[relaychain] +chain = "paseo-local" + +[[relaychain.nodes]] +name = "alice" +validator = true + +[[relaychain.nodes]] +name = "bob" +validator = true + +[[parachains]] +id = 1000 +chain = "asset-hub-paseo-local" + +[[parachains.collators]] +name = "asset-hub" \ No newline at end of file diff --git a/tests/networks/polkadot+asset-hub.toml b/tests/networks/polkadot+asset-hub.toml new file mode 100644 index 000000000..9cf6735b2 --- /dev/null +++ b/tests/networks/polkadot+asset-hub.toml @@ -0,0 +1,19 @@ +# pop up parachain -f ./tests/networks/polkadot+asset-hub.toml + +[relaychain] +chain = "polkadot-local" + +[[relaychain.nodes]] +name = "alice" +validator = true + +[[relaychain.nodes]] +name = "bob" +validator = true + +[[parachains]] +id = 1000 +chain = "asset-hub-polkadot-local" + +[[parachains.collators]] +name = "asset-hub" \ No newline at end of file diff --git a/tests/networks/pop.toml b/tests/networks/pop.toml index 9ad7c871d..643f1a0b5 100644 --- a/tests/networks/pop.toml +++ b/tests/networks/pop.toml @@ -24,4 +24,16 @@ default_command = "pop-node" [[parachains.collators]] name = "pop" -args = ["-lruntime::contracts=debug"] \ No newline at end of file +args = ["-lruntime::contracts=debug"] + +[[hrmp_channels]] +sender = 1000 +recipient = 4385 +max_capacity = 1000 +max_message_size = 5000 + +[[hrmp_channels]] +sender = 4385 +recipient = 1000 +max_capacity = 1000 +max_message_size = 8000 \ No newline at end of file diff --git a/tests/networks/westend+asset-hub.toml b/tests/networks/westend+asset-hub.toml new file mode 100644 index 000000000..3404173f8 --- /dev/null +++ b/tests/networks/westend+asset-hub.toml @@ -0,0 +1,19 @@ +# pop up parachain -f ./tests/networks/westend+asset-hub.toml + +[relaychain] +chain = "westend-local" + +[[relaychain.nodes]] +name = "alice" +validator = true + +[[relaychain.nodes]] +name = "bob" +validator = true + +[[parachains]] +id = 1000 +chain = "asset-hub-westend-local" + +[[parachains.collators]] +name = "asset-hub" \ No newline at end of file diff --git a/tests/networks/westend.toml b/tests/networks/westend.toml new file mode 100644 index 000000000..7bdb66bdd --- /dev/null +++ b/tests/networks/westend.toml @@ -0,0 +1,12 @@ +# pop up parachain -f ./tests/networks/westend.toml + +[relaychain] +chain = "westend-local" + +[[relaychain.nodes]] +name = "alice" +validator = true + +[[relaychain.nodes]] +name = "bob" +validator = true From 21d02dbfd836067f6cce12716cf8e818b982c51f Mon Sep 17 00:00:00 2001 From: Frank Bell <60948618+evilrobot-01@users.noreply.github.com> Date: Thu, 19 Dec 2024 09:29:13 +0000 Subject: [PATCH 207/211] refactor(up): bump fallback versions (#393) * refactor: bump fallback versions * test: bump versions --- crates/pop-parachains/src/up/chain_specs.rs | 12 ++-- crates/pop-parachains/src/up/mod.rs | 61 ++++++++++----------- crates/pop-parachains/src/up/parachains.rs | 20 +++---- crates/pop-parachains/src/up/relay.rs | 16 +++--- 4 files changed, 52 insertions(+), 57 deletions(-) diff --git a/crates/pop-parachains/src/up/chain_specs.rs b/crates/pop-parachains/src/up/chain_specs.rs index 3513a587e..e81db7ee9 100644 --- a/crates/pop-parachains/src/up/chain_specs.rs +++ b/crates/pop-parachains/src/up/chain_specs.rs @@ -22,7 +22,7 @@ pub(super) enum Runtime { Repository = "https://github.com/r0gue-io/polkadot-runtimes", Binary = "chain-spec-generator", Chain = "kusama-local", - Fallback = "v1.2.7" + Fallback = "v1.3.3" ))] Kusama, /// Paseo. @@ -30,7 +30,7 @@ pub(super) enum Runtime { Repository = "https://github.com/r0gue-io/paseo-runtimes", Binary = "chain-spec-generator", Chain = "paseo-local", - Fallback = "v1.2.6" + Fallback = "v1.3.4" ))] Paseo, /// Polkadot. @@ -38,7 +38,7 @@ pub(super) enum Runtime { Repository = "https://github.com/r0gue-io/polkadot-runtimes", Binary = "chain-spec-generator", Chain = "polkadot-local", - Fallback = "v1.2.7" + Fallback = "v1.3.3" ))] Polkadot, } @@ -111,7 +111,7 @@ mod tests { #[tokio::test] async fn kusama_works() -> anyhow::Result<()> { let expected = Runtime::Kusama; - let version = "v1.2.7"; + let version = "v1.3.3"; let temp_dir = tempdir()?; let binary = chain_spec_generator("kusama-local", Some(version), temp_dir.path()) .await? @@ -135,7 +135,7 @@ mod tests { #[tokio::test] async fn paseo_works() -> anyhow::Result<()> { let expected = Runtime::Paseo; - let version = "v1.2.4"; + let version = "v1.3.4"; let temp_dir = tempdir()?; let binary = chain_spec_generator("paseo-local", Some(version), temp_dir.path()) .await? @@ -159,7 +159,7 @@ mod tests { #[tokio::test] async fn polkadot_works() -> anyhow::Result<()> { let expected = Runtime::Polkadot; - let version = "v1.2.7"; + let version = "v1.3.3"; let temp_dir = tempdir()?; let binary = chain_spec_generator("polkadot-local", Some(version), temp_dir.path()) .await? diff --git a/crates/pop-parachains/src/up/mod.rs b/crates/pop-parachains/src/up/mod.rs index 91c1678af..abefa8051 100644 --- a/crates/pop-parachains/src/up/mod.rs +++ b/crates/pop-parachains/src/up/mod.rs @@ -679,6 +679,8 @@ mod tests { use std::{env::current_dir, fs::File, io::Write}; use tempfile::tempdir; + pub(crate) const VERSION: &str = "stable2409"; + mod zombienet { use super::*; use pop_common::Status; @@ -702,12 +704,11 @@ mod tests { chain = "paseo-local" "# )?; - let version = "v1.12.0"; let zombienet = Zombienet::new( &cache, config.path().to_str().unwrap(), - Some(version), + Some(VERSION), None, None, None, @@ -717,12 +718,12 @@ chain = "paseo-local" let relay_chain = &zombienet.relay_chain.binary; assert_eq!(relay_chain.name(), "polkadot"); - assert_eq!(relay_chain.path(), temp_dir.path().join(format!("polkadot-{version}"))); - assert_eq!(relay_chain.version().unwrap(), version); + assert_eq!(relay_chain.path(), temp_dir.path().join(format!("polkadot-{VERSION}"))); + assert_eq!(relay_chain.version().unwrap(), VERSION); assert!(matches!( relay_chain, Binary::Source { source: Source::GitHub(ReleaseArchive { tag, .. }), .. } - if *tag == Some(version.to_string()) + if *tag == Some(VERSION.to_string()) )); assert!(zombienet.parachains.is_empty()); assert_eq!(zombienet.relay_chain(), "paseo-local"); @@ -742,7 +743,7 @@ chain = "paseo-local" chain = "paseo-local" "# )?; - let version = "v1.2.7"; + let version = "v1.3.3"; let zombienet = Zombienet::new( &cache, @@ -782,15 +783,14 @@ chain = "paseo-local" r#" [relaychain] chain = "paseo-local" -default_command = "./bin-v1.6.0/polkadot" +default_command = "./bin-stable2409/polkadot" "# )?; - let version = "v1.12.0"; let zombienet = Zombienet::new( &cache, config.path().to_str().unwrap(), - Some(version), + Some(VERSION), None, None, None, @@ -800,12 +800,12 @@ default_command = "./bin-v1.6.0/polkadot" let relay_chain = &zombienet.relay_chain.binary; assert_eq!(relay_chain.name(), "polkadot"); - assert_eq!(relay_chain.path(), temp_dir.path().join(format!("polkadot-{version}"))); - assert_eq!(relay_chain.version().unwrap(), version); + assert_eq!(relay_chain.path(), temp_dir.path().join(format!("polkadot-{VERSION}"))); + assert_eq!(relay_chain.version().unwrap(), VERSION); assert!(matches!( relay_chain, Binary::Source { source: Source::GitHub(ReleaseArchive { tag, .. }), .. } - if *tag == Some(version.to_string()) + if *tag == Some(VERSION.to_string()) )); assert!(zombienet.parachains.is_empty()); Ok(()) @@ -828,12 +828,11 @@ validator = true command = "polkadot" "# )?; - let version = "v1.12.0"; let zombienet = Zombienet::new( &cache, config.path().to_str().unwrap(), - Some(version), + Some(VERSION), None, None, None, @@ -843,12 +842,12 @@ command = "polkadot" let relay_chain = &zombienet.relay_chain.binary; assert_eq!(relay_chain.name(), "polkadot"); - assert_eq!(relay_chain.path(), temp_dir.path().join(format!("polkadot-{version}"))); - assert_eq!(relay_chain.version().unwrap(), version); + assert_eq!(relay_chain.path(), temp_dir.path().join(format!("polkadot-{VERSION}"))); + assert_eq!(relay_chain.version().unwrap(), VERSION); assert!(matches!( relay_chain, Binary::Source { source: Source::GitHub(ReleaseArchive { tag, .. }), .. } - if *tag == Some(version.to_string()) + if *tag == Some(VERSION.to_string()) )); assert!(zombienet.parachains.is_empty()); Ok(()) @@ -873,14 +872,14 @@ command = "polkadot" [[relaychain.nodes]] name = "bob" validator = true -command = "polkadot-v1.12.0" +command = "polkadot-stable2409" "# )?; assert!(matches!( Zombienet::new(&cache, config.path().to_str().unwrap(), None, None, None, None, None).await, Err(Error::UnsupportedCommand(error)) - if error == "the relay chain command is unsupported: polkadot-v1.12.0" + if error == "the relay chain command is unsupported: polkadot-stable2409" )); Ok(()) } @@ -900,14 +899,14 @@ default_command = "polkadot" [[relaychain.nodes]] name = "alice" validator = true -command = "polkadot-v1.12.0" +command = "polkadot-stable2409" "# )?; assert!(matches!( Zombienet::new(&cache, config.path().to_str().unwrap(), None, None, None, None, None).await, Err(Error::UnsupportedCommand(error)) - if error == "the relay chain command is unsupported: polkadot-v1.12.0" + if error == "the relay chain command is unsupported: polkadot-stable2409" )); Ok(()) } @@ -928,12 +927,12 @@ id = 1000 chain = "asset-hub-paseo-local" "# )?; - let system_parachain_version = "v1.12.0"; + let system_parachain_version = "stable2407"; let zombienet = Zombienet::new( &cache, config.path().to_str().unwrap(), - Some("v1.11.0"), + Some(VERSION), None, Some(system_parachain_version), None, @@ -973,7 +972,6 @@ id = 1000 chain = "asset-hub-paseo-local" "# )?; - let version = "v1.12.0"; let zombienet = Zombienet::new( &cache, @@ -981,7 +979,7 @@ chain = "asset-hub-paseo-local" None, None, None, - Some(version), + Some(VERSION), None, ) .await?; @@ -993,13 +991,13 @@ chain = "asset-hub-paseo-local" assert_eq!(chain_spec_generator.name(), "paseo-chain-spec-generator"); assert_eq!( chain_spec_generator.path(), - temp_dir.path().join(format!("paseo-chain-spec-generator-{version}")) + temp_dir.path().join(format!("paseo-chain-spec-generator-{VERSION}")) ); - assert_eq!(chain_spec_generator.version().unwrap(), version); + assert_eq!(chain_spec_generator.version().unwrap(), VERSION); assert!(matches!( chain_spec_generator, Binary::Source { source: Source::GitHub(ReleaseArchive { tag, .. }), .. } - if *tag == Some(version.to_string()) + if *tag == Some(VERSION.to_string()) )); Ok(()) } @@ -1447,10 +1445,9 @@ chain = "paseo-local" chain = "paseo-local" "# )?; - let version = "v1.12.0"; - File::create(cache.join(format!("polkadot-{version}")))?; - File::create(cache.join(format!("polkadot-execute-worker-{version}")))?; - File::create(cache.join(format!("polkadot-prepare-worker-{version}")))?; + File::create(cache.join(format!("polkadot-{VERSION}")))?; + File::create(cache.join(format!("polkadot-execute-worker-{VERSION}")))?; + File::create(cache.join(format!("polkadot-prepare-worker-{VERSION}")))?; let mut zombienet = Zombienet::new( &cache, diff --git a/crates/pop-parachains/src/up/parachains.rs b/crates/pop-parachains/src/up/parachains.rs index 06eda16e6..ad6075454 100644 --- a/crates/pop-parachains/src/up/parachains.rs +++ b/crates/pop-parachains/src/up/parachains.rs @@ -22,7 +22,7 @@ pub(super) enum Parachain { Repository = "https://github.com/r0gue-io/polkadot", Binary = "polkadot-parachain", TagFormat = "polkadot-{tag}", - Fallback = "v1.12.0" + Fallback = "stable2409" ))] System, /// Pop Network makes it easy for smart contract developers to use the power of Polkadot. @@ -30,7 +30,7 @@ pub(super) enum Parachain { Repository = "https://github.com/r0gue-io/pop-node", Binary = "pop-node", Prerelease = "false", - Fallback = "testnet-v0.4.1" + Fallback = "testnet-v0.4.2" ))] Pop, } @@ -148,7 +148,7 @@ pub(super) async fn from( #[cfg(test)] mod tests { - use super::*; + use super::{super::tests::VERSION, *}; use std::path::PathBuf; use tempfile::tempdir; @@ -159,7 +159,7 @@ mod tests { "polkadot", None, None, - "v1.12.0", + VERSION, Some("asset-hub-paseo-local"), tempdir()?.path() ) @@ -170,13 +170,12 @@ mod tests { #[tokio::test] async fn system_using_relay_version() -> anyhow::Result<()> { - let version = "v1.12.0"; let expected = Parachain::System; let para_id = 1000; let temp_dir = tempdir()?; let parachain = - system(para_id, expected.binary(), None, None, version, None, temp_dir.path()) + system(para_id, expected.binary(), None, None, VERSION, None, temp_dir.path()) .await? .unwrap(); assert_eq!(para_id, parachain.id); @@ -184,7 +183,7 @@ mod tests { if name == expected.binary() && source == Source::GitHub(ReleaseArchive { owner: "r0gue-io".to_string(), repository: "polkadot".to_string(), - tag: Some(version.to_string()), + tag: Some(VERSION.to_string()), tag_format: Some("polkadot-{tag}".to_string()), archive: format!("{name}-{}.tar.gz", target()?), contents: vec![(expected.binary(), None)], @@ -196,13 +195,12 @@ mod tests { #[tokio::test] async fn system_works() -> anyhow::Result<()> { - let version = "v1.12.0"; let expected = Parachain::System; let para_id = 1000; let temp_dir = tempdir()?; let parachain = - system(para_id, expected.binary(), Some(version), None, version, None, temp_dir.path()) + system(para_id, expected.binary(), Some(VERSION), None, VERSION, None, temp_dir.path()) .await? .unwrap(); assert_eq!(para_id, parachain.id); @@ -210,7 +208,7 @@ mod tests { if name == expected.binary() && source == Source::GitHub(ReleaseArchive { owner: "r0gue-io".to_string(), repository: "polkadot".to_string(), - tag: Some(version.to_string()), + tag: Some(VERSION.to_string()), tag_format: Some("polkadot-{tag}".to_string()), archive: format!("{name}-{}.tar.gz", target()?), contents: vec![(expected.binary(), None)], @@ -223,7 +221,7 @@ mod tests { #[tokio::test] async fn system_with_chain_spec_generator_works() -> anyhow::Result<()> { let expected = Parachain::System; - let runtime_version = "v1.2.7"; + let runtime_version = "v1.3.3"; let para_id = 1000; let temp_dir = tempdir()?; diff --git a/crates/pop-parachains/src/up/relay.rs b/crates/pop-parachains/src/up/relay.rs index 662e639fd..2390e446a 100644 --- a/crates/pop-parachains/src/up/relay.rs +++ b/crates/pop-parachains/src/up/relay.rs @@ -24,7 +24,7 @@ pub(super) enum RelayChain { Repository = "https://github.com/r0gue-io/polkadot", Binary = "polkadot", TagFormat = "polkadot-{tag}", - Fallback = "v1.12.0" + Fallback = "stable2409" ))] Polkadot, } @@ -131,17 +131,18 @@ mod tests { use super::*; use tempfile::tempdir; + const VERSION: &str = "stable2409"; + #[tokio::test] async fn default_works() -> anyhow::Result<()> { let expected = RelayChain::Polkadot; - let version = "v1.12.0"; let temp_dir = tempdir()?; - let relay = default(Some(version), None, None, temp_dir.path()).await?; + let relay = default(Some(VERSION), None, None, temp_dir.path()).await?; assert!(matches!(relay.binary, Binary::Source { name, source, cache } if name == expected.binary() && source == Source::GitHub(ReleaseArchive { owner: "r0gue-io".to_string(), repository: "polkadot".to_string(), - tag: Some(version.to_string()), + tag: Some(VERSION.to_string()), tag_format: Some("polkadot-{tag}".to_string()), archive: format!("{name}-{}.tar.gz", target()?), contents: ["polkadot", "polkadot-execute-worker", "polkadot-prepare-worker"].map(|b| (b, None)).to_vec(), @@ -154,7 +155,7 @@ mod tests { #[tokio::test] async fn default_with_chain_spec_generator_works() -> anyhow::Result<()> { - let runtime_version = "v1.2.7"; + let runtime_version = "v1.3.3"; let temp_dir = tempdir()?; let relay = default(None, Some(runtime_version), Some("paseo-local"), temp_dir.path()).await?; @@ -186,15 +187,14 @@ mod tests { #[tokio::test] async fn from_handles_local_command() -> anyhow::Result<()> { let expected = RelayChain::Polkadot; - let version = "v1.12.0"; let temp_dir = tempdir()?; let relay = - from("./bin-v1.6.0/polkadot", Some(version), None, None, temp_dir.path()).await?; + from("./bin-stable2409/polkadot", Some(VERSION), None, None, temp_dir.path()).await?; assert!(matches!(relay.binary, Binary::Source { name, source, cache } if name == expected.binary() && source == Source::GitHub(ReleaseArchive { owner: "r0gue-io".to_string(), repository: "polkadot".to_string(), - tag: Some(version.to_string()), + tag: Some(VERSION.to_string()), tag_format: Some("polkadot-{tag}".to_string()), archive: format!("{name}-{}.tar.gz", target()?), contents: ["polkadot", "polkadot-execute-worker", "polkadot-prepare-worker"].map(|b| (b, None)).to_vec(), From 2a58bf4c66008368530d29fbeee679b966422d02 Mon Sep 17 00:00:00 2001 From: Alex Bean Date: Thu, 19 Dec 2024 10:29:29 +0100 Subject: [PATCH 208/211] chore: replace codeowners (#388) --- .github/CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e3fe3ffae..a2715e0fc 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,3 @@ @AlexD10S -@brunopgalvao -@weezy20 \ No newline at end of file +@al3mart +@evilrobot-01 \ No newline at end of file From 661c6257c8c00f6cda5d1be236141573d7f0d737 Mon Sep 17 00:00:00 2001 From: Frank Bell <60948618+evilrobot-01@users.noreply.github.com> Date: Thu, 19 Dec 2024 13:18:19 +0000 Subject: [PATCH 209/211] build(deps): remove unused dependencies (#369) * build(deps): remove unused dependencies * refactor: remove unused import * refactor: silence warning as function will be used in the future * revert: revert removal of tokio-test as used by doc tests --- Cargo.lock | 27 ---------------------- Cargo.toml | 1 - crates/pop-cli/Cargo.toml | 1 - crates/pop-cli/src/cli.rs | 1 + crates/pop-contracts/Cargo.toml | 7 ++---- crates/pop-parachains/Cargo.toml | 5 +--- crates/pop-parachains/src/utils/helpers.rs | 1 - 7 files changed, 4 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ac67e52da..48e49894a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3551,15 +3551,6 @@ dependencies = [ "miniz_oxide", ] -[[package]] -name = "float-cmp" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" -dependencies = [ - "num-traits", -] - [[package]] name = "fnv" version = "1.0.7" @@ -6062,12 +6053,6 @@ dependencies = [ "nom", ] -[[package]] -name = "normalize-line-endings" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" - [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -9369,7 +9354,6 @@ dependencies = [ "pop-contracts", "pop-parachains", "pop-telemetry", - "predicates", "reqwest 0.12.9", "serde", "serde_json", @@ -9425,12 +9409,9 @@ dependencies = [ "contract-build", "contract-extrinsics", "contract-transcode", - "dirs", "duct", - "flate2", "heck 0.5.0", "ink_env", - "mockito", "pop-common", "reqwest 0.12.9", "scale-info", @@ -9440,7 +9421,6 @@ dependencies = [ "strum_macros 0.26.4", "subxt", "subxt-signer", - "tar", "tempfile", "thiserror 1.0.69", "tokio", @@ -9456,12 +9436,9 @@ dependencies = [ "askama", "clap", "duct", - "flate2", "glob", "indexmap 2.7.0", - "mockito", "pop-common", - "reqwest 0.12.9", "scale-info", "scale-value", "serde_json", @@ -9471,7 +9448,6 @@ dependencies = [ "subxt", "subxt-signer", "symlink", - "tar", "tempfile", "thiserror 1.0.69", "tokio", @@ -9527,10 +9503,7 @@ checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" dependencies = [ "anstyle", "difflib", - "float-cmp", - "normalize-line-endings", "predicates-core", - "regex", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 44dd1fbd3..8b284b10a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,6 @@ git2 = { version = "0.18", features = ["vendored-openssl"] } glob = "0.3.1" log = "0.4.20" mockito = "1.4.0" -predicates = "3.1.0" tar = "0.4.40" tempfile = "3.10" thiserror = "1.0.58" diff --git a/crates/pop-cli/Cargo.toml b/crates/pop-cli/Cargo.toml index 34e51111d..6093569dc 100644 --- a/crates/pop-cli/Cargo.toml +++ b/crates/pop-cli/Cargo.toml @@ -55,7 +55,6 @@ tower-http = { workspace = true, features = ["fs", "cors"] } [dev-dependencies] assert_cmd.workspace = true contract-extrinsics.workspace = true -predicates.workspace = true subxt.workspace = true subxt-signer.workspace = true sp-weights.workspace = true diff --git a/crates/pop-cli/src/cli.rs b/crates/pop-cli/src/cli.rs index f98c934e9..9d2b86ec7 100644 --- a/crates/pop-cli/src/cli.rs +++ b/crates/pop-cli/src/cli.rs @@ -304,6 +304,7 @@ pub(crate) mod tests { self } + #[allow(dead_code)] pub(crate) fn expect_success(mut self, message: impl Display) -> Self { self.success_expectations.push(message.to_string()); self diff --git a/crates/pop-contracts/Cargo.toml b/crates/pop-contracts/Cargo.toml index f44332299..cd880213c 100644 --- a/crates/pop-contracts/Cargo.toml +++ b/crates/pop-contracts/Cargo.toml @@ -13,9 +13,7 @@ version.workspace = true [dependencies] anyhow.workspace = true duct.workspace = true -flate2.workspace = true reqwest.workspace = true -tar.workspace = true tempfile.workspace = true thiserror.workspace = true tokio.workspace = true @@ -35,10 +33,9 @@ contract-build.workspace = true contract-extrinsics.workspace = true contract-transcode.workspace = true scale-info.workspace = true -# pop +# pop pop-common = { path = "../pop-common", version = "0.5.0" } [dev-dependencies] -dirs.workspace = true -mockito.workspace = true +# Used in doc tests. tokio-test.workspace = true \ No newline at end of file diff --git a/crates/pop-parachains/Cargo.toml b/crates/pop-parachains/Cargo.toml index 89c1df6d9..4b2ee4111 100644 --- a/crates/pop-parachains/Cargo.toml +++ b/crates/pop-parachains/Cargo.toml @@ -12,14 +12,12 @@ version.workspace = true anyhow.workspace = true clap.workspace = true duct.workspace = true -flate2.workspace = true glob.workspace = true serde_json.workspace = true strum.workspace = true strum_macros.workspace = true subxt-signer.workspace = true subxt.workspace = true -tar.workspace = true tempfile.workspace = true thiserror.workspace = true tokio.workspace = true @@ -27,7 +25,6 @@ url.workspace = true askama.workspace = true indexmap.workspace = true -reqwest.workspace = true scale-info.workspace = true scale-value.workspace = true sp-core.workspace = true @@ -41,5 +38,5 @@ zombienet-sdk.workspace = true pop-common = { path = "../pop-common", version = "0.5.0" } [dev-dependencies] -mockito.workspace = true +# Used in doc tests. tokio-test.workspace = true diff --git a/crates/pop-parachains/src/utils/helpers.rs b/crates/pop-parachains/src/utils/helpers.rs index fd8e91d68..f38d7bce5 100644 --- a/crates/pop-parachains/src/utils/helpers.rs +++ b/crates/pop-parachains/src/utils/helpers.rs @@ -88,7 +88,6 @@ mod tests { use super::*; use crate::generator::parachain::ChainSpec; use askama::Template; - use std::env::var; use tempfile::tempdir; #[test] From e96d8c7fd03d03c2a0935e7db51585a7242fd953 Mon Sep 17 00:00:00 2001 From: Peter White Date: Thu, 19 Dec 2024 10:35:15 -0700 Subject: [PATCH 210/211] chore(wallet): update frontend with papi support (#395) --- crates/pop-cli/src/assets/index.html | 30 ++++++++++++++-------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/crates/pop-cli/src/assets/index.html b/crates/pop-cli/src/assets/index.html index d1d5a8ef7..2b4430f8b 100644 --- a/crates/pop-cli/src/assets/index.html +++ b/crates/pop-cli/src/assets/index.html @@ -7,7 +7,7 @@ href="data:image/svg+xml,%3Csvg width='512' height='512' viewBox='0 0 512 512' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg clip-path='url(%23clip0_873_174)'%3E%3Crect width='512' height='512' rx='256' fill='%231C0533'/%3E%3Crect width='512' height='512' rx='256' fill='url(%23paint0_radial_873_174)' fill-opacity='0.8'/%3E%3Crect width='512' height='512' rx='256' fill='url(%23paint1_radial_873_174)' fill-opacity='0.6'/%3E%3Cmask id='mask0_873_174' style='mask-type:alpha' maskUnits='userSpaceOnUse' x='-429' y='-502' width='1428' height='1351'%3E%3Cpath d='M127.637 -501.289L998.911 322.903L984.661 336.383L113.388 -487.809L127.637 -501.289Z' fill='%23D9D9D9'/%3E%3Cpath d='M88.9594 -464.701L960.232 359.491L945.983 372.971L74.7096 -451.221L88.9594 -464.701Z' fill='%23D9D9D9'/%3E%3Cpath d='M50.2814 -428.113L921.554 396.079L907.305 409.559L36.0316 -414.633L50.2814 -428.113Z' fill='%23D9D9D9'/%3E%3Cpath d='M11.6034 -391.525L882.876 432.667L868.627 446.147L-2.64642 -378.045L11.6034 -391.525Z' fill='%23D9D9D9'/%3E%3Cpath d='M-27.0746 -354.937L844.198 469.255L829.949 482.735L-41.3244 -341.457L-27.0746 -354.937Z' fill='%23D9D9D9'/%3E%3Cpath d='M-65.7526 -318.349L805.52 505.843L791.271 519.323L-80.0024 -304.869L-65.7526 -318.349Z' fill='%23D9D9D9'/%3E%3Cpath d='M-104.431 -281.761L766.842 542.431L752.593 555.911L-118.68 -268.281L-104.431 -281.761Z' fill='%23D9D9D9'/%3E%3Cpath d='M-143.109 -245.173L728.164 579.019L713.915 592.499L-157.358 -231.693L-143.109 -245.173Z' fill='%23D9D9D9'/%3E%3Cpath d='M-181.787 -208.585L689.486 615.607L675.237 629.087L-196.036 -195.105L-181.787 -208.585Z' fill='%23D9D9D9'/%3E%3Cpath d='M-220.465 -171.997L650.808 652.195L636.559 665.675L-234.714 -158.517L-220.465 -171.997Z' fill='%23D9D9D9'/%3E%3Cpath d='M-259.143 -135.409L612.13 688.783L597.881 702.263L-273.392 -121.929L-259.143 -135.409Z' fill='%23D9D9D9'/%3E%3Cpath d='M-297.821 -98.8211L573.452 725.371L559.203 738.851L-312.07 -85.3413L-297.821 -98.8211Z' fill='%23D9D9D9'/%3E%3Cpath d='M-336.499 -62.2331L534.774 761.959L520.525 775.439L-350.748 -48.7533L-336.499 -62.2331Z' fill='%23D9D9D9'/%3E%3Cpath d='M-375.177 -25.6452L496.096 798.547L481.847 812.026L-389.426 -12.1654L-375.177 -25.6452Z' fill='%23D9D9D9'/%3E%3Cpath d='M-413.855 10.9428L457.418 835.135L443.169 848.615L-428.104 24.4225L-413.855 10.9428Z' fill='%23D9D9D9'/%3E%3C/mask%3E%3Cg mask='url(%23mask0_873_174)'%3E%3Cpath d='M511.169 254.929C511.169 396.905 396.739 512 255.584 512C114.428 512 -0.000976562 396.905 -0.000976562 254.929C-0.000976562 112.953 114.428 -2.14209 255.584 -2.14209C396.739 -2.14209 511.169 112.953 511.169 254.929Z' fill='%23E6007A'/%3E%3C/g%3E%3Cpath d='M183.804 160.44C171.052 151.144 156.458 147.168 141.864 147.168H52.2334L5.63086 367.567H122.684L132.083 322.765C141.879 321.622 151.384 318.84 160.403 314.394C161.8 313.705 163.178 312.986 164.536 312.239C168.249 321.014 173.177 329.173 179.315 336.581C187.573 346.548 197.657 354.366 209.117 360.016L209.12 360.018L209.517 360.213L209.917 360.399C222.605 366.289 236.098 368.784 249.63 368.784C263.218 368.784 276.507 366.489 289.292 361.868C295.36 359.675 301.212 357.046 306.832 353.988L303.961 367.567H421.013L430.413 322.765C440.209 321.622 449.714 318.84 458.733 314.394C469.61 309.03 479.317 301.877 487.672 293.046L487.682 293.035L487.841 292.867L487.995 292.7L487.998 292.697C496.09 283.931 502.611 273.995 507.338 262.975C512.203 251.631 514.88 239.536 514.88 226.953C514.88 214.447 512.231 202.327 507.001 190.977C501.508 178.753 493.141 168.335 482.134 160.44C469.382 151.144 454.788 147.168 440.194 147.168H350.563L345.323 171.949C338.297 165.041 330.227 159.445 321.314 155.191C308.55 148.958 294.91 146.357 281.28 146.357C267.691 146.357 254.402 148.652 241.618 153.273C229.473 157.663 218.174 163.78 207.814 171.541C205.377 173.358 203.011 175.252 200.715 177.221C196.062 170.823 190.408 165.177 183.804 160.44Z' fill='%232B0532'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M229.569 331.163L229.632 331.194L229.696 331.224C237.592 334.889 246.407 336.65 256.019 336.65C265.979 336.65 275.609 334.978 284.877 331.628C294.055 328.31 302.528 323.689 310.277 317.771C317.967 311.898 324.68 305.15 330.397 297.531L330.413 297.509L330.429 297.487C336.137 289.737 340.585 281.451 343.748 272.638C347.074 263.611 348.753 254.388 348.753 244.999C348.753 236.541 347.45 228.398 344.825 220.6C342.311 212.598 338.452 205.423 333.24 199.132C327.963 192.763 321.484 187.769 313.875 184.16C306.078 180.325 297.295 178.491 287.67 178.491C277.709 178.491 268.079 180.164 258.812 183.513C249.647 186.826 241.125 191.437 233.261 197.332C225.542 203.084 218.751 209.844 212.899 217.597L212.889 217.611C207.16 225.246 202.642 233.54 199.345 242.478L199.33 242.518L199.316 242.559C196.135 251.575 194.532 260.778 194.532 270.142C194.532 278.624 195.843 286.844 198.472 294.776L198.477 294.792L198.482 294.808C201.135 302.663 205.057 309.744 210.248 316.009C215.517 322.37 221.981 327.422 229.569 331.163ZM287.732 276.015L287.742 276C290.74 271.657 292.939 267.045 294.364 262.148C295.831 257.108 296.54 252.276 296.54 247.635C296.54 243.3 295.843 239.458 294.528 236.043L294.487 235.934L294.449 235.824C293.328 232.545 291.583 230.131 289.252 228.349C287.226 226.801 284.199 225.736 279.606 225.736C274.271 225.736 269.81 226.979 266.027 229.305L265.974 229.337L265.921 229.368C261.869 231.746 258.495 234.905 255.765 238.909C252.893 243.123 250.673 247.698 249.104 252.654C247.65 257.67 246.947 262.48 246.947 267.101C246.947 271.459 247.585 275.422 248.809 279.033C250.055 282.339 251.89 284.885 254.293 286.836C256.34 288.379 259.301 289.405 263.68 289.405C269.175 289.405 273.697 288.15 277.46 285.837C281.567 283.313 284.975 280.058 287.722 276.029L287.732 276.015Z' fill='%23E6007A'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M171.542 186.53C164.879 181.64 157.017 179.302 148.254 179.302H84.4909L51.4775 335.434H103.163L112.436 291.23H128.094C136.792 291.23 145.029 289.337 152.73 285.54C160.225 281.844 166.856 276.954 172.594 270.888L172.619 270.861L172.644 270.834C178.339 264.665 182.824 257.798 186.062 250.247C189.323 242.645 190.992 234.866 190.992 226.953C190.992 219.052 189.327 211.496 186.009 204.348C182.762 197.062 177.927 191.083 171.542 186.53ZM135.575 242.131L135.598 242.101C136.363 241.1 137.06 239.71 137.567 237.773L137.604 237.631L137.648 237.49C138.222 235.663 138.577 233.269 138.577 230.197C138.577 227.864 138.266 226.771 138.111 226.428C138.024 226.235 137.948 226.093 137.889 225.994L137.862 225.949L137.849 225.939H126.349L122.444 244.594H132.693C132.756 244.581 132.999 244.516 133.45 244.244C133.961 243.935 134.685 243.325 135.553 242.16L135.575 242.131Z' fill='%23E6007A'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M469.872 186.53C463.209 181.64 455.347 179.302 446.583 179.302H382.821L349.807 335.434H401.493L410.766 291.23H426.424C435.122 291.23 443.359 289.337 451.06 285.54C458.555 281.844 465.186 276.954 470.923 270.888L470.949 270.861L470.973 270.834C476.669 264.665 481.153 257.798 484.392 250.247C487.652 242.645 489.322 234.866 489.322 226.953C489.322 219.052 487.657 211.496 484.339 204.348C481.092 197.062 476.257 191.083 469.872 186.53ZM433.905 242.131L433.928 242.101C434.693 241.1 435.39 239.71 435.896 237.773L435.934 237.631L435.978 237.49C436.552 235.663 436.907 233.269 436.907 230.197C436.907 227.864 436.596 226.771 436.441 226.428C436.354 226.235 436.278 226.093 436.219 225.994L436.192 225.949L436.178 225.939H424.678L420.774 244.594H431.023C431.086 244.581 431.329 244.516 431.779 244.244C432.29 243.935 433.014 243.325 433.883 242.16L433.905 242.131Z' fill='%23E6007A'/%3E%3Cmask id='path-6-outside-1_873_174' maskUnits='userSpaceOnUse' x='35.8232' y='166.788' width='435' height='156' fill='black'%3E%3Crect fill='white' x='35.8232' y='166.788' width='435' height='156'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M68.9791 168.599H131.11C139.505 168.599 146.942 170.833 153.22 175.445C159.314 179.787 163.927 185.486 167.032 192.461C170.229 199.344 171.832 206.612 171.832 214.222C171.832 221.841 170.226 229.347 167.067 236.713C163.922 244.045 159.566 250.719 154.022 256.724L154.006 256.742L153.989 256.759C148.417 262.651 141.98 267.398 134.699 270.989C127.28 274.646 119.347 276.472 110.95 276.472H93.6573L84.3839 320.675H36.8232L68.9791 168.599ZM90.3873 272.416H110.95C118.745 272.416 126.07 270.727 132.924 267.347C139.778 263.968 145.826 259.507 151.068 253.964C156.309 248.287 160.408 242.001 163.365 235.107C166.322 228.213 167.8 221.251 167.8 214.222C167.8 207.193 166.322 200.501 163.365 194.148C160.543 187.795 156.376 182.658 150.866 178.738C145.356 174.682 138.77 172.655 131.11 172.655H72.2437L41.8028 316.62H81.1139L90.3873 272.416ZM120.037 230.626L120.052 230.607C121.01 229.354 121.81 227.706 122.372 225.559L122.397 225.463L122.427 225.37C123.078 223.296 123.449 220.685 123.449 217.466C123.449 215.01 123.13 213.583 122.802 212.859C122.362 211.884 122.041 211.655 122.042 211.653L121.876 211.558L121.71 211.439C121.399 211.215 121.2 211.18 121.03 211.18H107.569L102.815 233.89H115.587C115.879 233.89 116.444 233.794 117.343 233.252C118.131 232.776 119.04 231.963 120.022 230.646L120.037 230.626ZM97.8464 237.946L104.297 207.125H121.03C122.105 207.125 123.113 207.463 124.054 208.139C124.995 208.68 125.801 209.693 126.473 211.18C127.145 212.667 127.481 214.763 127.481 217.466C127.481 220.981 127.078 224.022 126.271 226.591C125.599 229.159 124.591 231.322 123.247 233.079C122.038 234.701 120.761 235.918 119.417 236.729C118.073 237.54 116.796 237.946 115.587 237.946H97.8464ZM213.354 316.632L213.312 316.611C205.969 312.991 199.733 308.113 194.652 301.981C189.624 295.911 185.822 289.05 183.247 281.426L183.24 281.404C180.681 273.683 179.404 265.679 179.404 257.411C179.404 248.285 180.966 239.311 184.072 230.507L184.081 230.479L184.091 230.452C187.319 221.7 191.743 213.579 197.354 206.101L197.361 206.092C203.099 198.491 209.753 191.867 217.318 186.229C225.025 180.452 233.372 175.936 242.35 172.691C251.395 169.421 260.794 167.788 270.526 167.788C279.899 167.788 288.376 169.574 295.858 173.257C303.215 176.744 309.461 181.561 314.547 187.699C319.59 193.786 323.327 200.732 325.764 208.5C328.321 216.086 329.593 224.016 329.593 232.268C329.593 241.412 327.958 250.4 324.71 259.212C321.616 267.835 317.262 275.95 311.665 283.549L311.655 283.564L311.644 283.578C306.04 291.046 299.46 297.663 291.914 303.425C284.329 309.218 276.037 313.741 267.051 316.989C258.006 320.259 248.606 321.892 238.875 321.892C229.51 321.892 220.99 320.177 213.397 316.652L213.354 316.632ZM320.924 257.817C324.016 249.436 325.561 240.92 325.561 232.268C325.561 224.428 324.352 216.926 321.932 209.761C319.648 202.461 316.153 195.973 311.449 190.295C306.746 184.618 300.966 180.157 294.112 176.913C287.258 173.533 279.396 171.843 270.526 171.843C261.252 171.843 252.315 173.398 243.713 176.507C235.112 179.616 227.115 183.942 219.723 189.484C212.466 194.891 206.082 201.245 200.572 208.544C195.196 215.709 190.962 223.482 187.871 231.863C184.915 240.244 183.436 248.76 183.436 257.411C183.436 265.252 184.646 272.822 187.065 280.121C189.484 287.286 193.046 293.707 197.75 299.384C202.453 305.062 208.233 309.59 215.087 312.97C222.075 316.214 230.005 317.836 238.875 317.836C248.148 317.836 257.086 316.282 265.687 313.173C274.289 310.064 282.218 305.738 289.475 300.196C296.733 294.653 303.05 288.3 308.425 281.135C313.801 273.836 317.968 266.063 320.924 257.817ZM272.24 264.445L272.254 264.426C275.373 259.908 277.667 255.099 279.155 249.987C280.67 244.779 281.412 239.756 281.412 234.904C281.412 230.347 280.678 226.254 279.264 222.58L279.237 222.508L279.212 222.434C277.972 218.806 276.002 216.048 273.327 214.004C270.857 212.115 267.361 210.978 262.462 210.978C256.8 210.978 251.967 212.302 247.832 214.844L247.796 214.865L247.761 214.886C243.447 217.417 239.854 220.785 236.959 225.032C233.968 229.418 231.66 234.18 230.031 239.332C228.525 244.523 227.787 249.532 227.787 254.37C227.787 258.944 228.459 263.14 229.768 266.989C231.136 270.636 233.189 273.504 235.911 275.706C238.394 277.588 241.822 278.702 246.536 278.702C252.349 278.702 257.246 277.37 261.367 274.836C265.718 272.162 269.334 268.708 272.24 264.445ZM233.432 278.905C230.072 276.201 227.586 272.687 225.973 268.361C224.495 264.035 223.755 259.372 223.755 254.37C223.755 249.098 224.562 243.691 226.175 238.149C227.922 232.606 230.408 227.469 233.634 222.738C236.859 218.007 240.891 214.222 245.729 211.383C250.568 208.409 256.145 206.922 262.462 206.922C267.972 206.922 272.407 208.206 275.767 210.775C279.127 213.343 281.546 216.79 283.024 221.116C284.637 225.307 285.444 229.903 285.444 234.904C285.444 240.176 284.637 245.583 283.024 251.126C281.412 256.668 278.925 261.872 275.565 266.739C272.34 271.47 268.308 275.323 263.47 278.297C258.631 281.271 252.987 282.757 246.536 282.757C241.16 282.757 236.792 281.473 233.432 278.905ZM367.309 168.599H429.439C437.835 168.599 445.272 170.833 451.55 175.445C457.644 179.787 462.256 185.485 465.361 192.46C468.559 199.344 470.162 206.611 470.162 214.222C470.162 221.841 468.556 229.347 465.397 236.713C462.252 244.045 457.896 250.719 452.352 256.724L452.335 256.742L452.319 256.759C446.747 262.651 440.31 267.398 433.028 270.989C425.61 274.646 417.677 276.472 409.28 276.472H391.987L382.714 320.675H335.153L367.309 168.599ZM388.717 272.416H409.28C417.075 272.416 424.4 270.727 431.254 267.347C438.108 263.968 444.156 259.507 449.397 253.964C454.639 248.287 458.738 242.001 461.695 235.107C464.651 228.213 466.13 221.251 466.13 214.222C466.13 207.193 464.651 200.501 461.695 194.148C458.872 187.795 454.706 182.658 449.196 178.738C443.686 174.682 437.1 172.655 429.439 172.655H370.574L340.133 316.62H379.444L388.717 272.416ZM418.366 230.626L418.381 230.607C419.34 229.354 420.14 227.706 420.702 225.559L420.727 225.463L420.756 225.37C421.408 223.296 421.779 220.685 421.779 217.466C421.779 215.01 421.46 213.583 421.132 212.859C420.692 211.884 420.371 211.655 420.372 211.653L420.206 211.558L420.04 211.439C419.729 211.215 419.53 211.18 419.36 211.18H405.899L401.145 233.89H413.917C414.209 233.89 414.774 233.794 415.673 233.252C416.461 232.776 417.369 231.963 418.352 230.646L418.366 230.626ZM396.176 237.946L402.627 207.125H419.36C420.435 207.125 421.443 207.463 422.384 208.139C423.324 208.68 424.131 209.693 424.803 211.18C425.475 212.667 425.811 214.763 425.811 217.466C425.811 220.981 425.408 224.022 424.601 226.591C423.929 229.159 422.921 231.322 421.577 233.079C420.368 234.701 419.091 235.918 417.747 236.729C416.403 237.54 415.126 237.946 413.917 237.946H396.176Z'/%3E%3C/mask%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M68.9791 168.599H131.11C139.505 168.599 146.942 170.833 153.22 175.445C159.314 179.787 163.927 185.486 167.032 192.461C170.229 199.344 171.832 206.612 171.832 214.222C171.832 221.841 170.226 229.347 167.067 236.713C163.922 244.045 159.566 250.719 154.022 256.724L154.006 256.742L153.989 256.759C148.417 262.651 141.98 267.398 134.699 270.989C127.28 274.646 119.347 276.472 110.95 276.472H93.6573L84.3839 320.675H36.8232L68.9791 168.599ZM90.3873 272.416H110.95C118.745 272.416 126.07 270.727 132.924 267.347C139.778 263.968 145.826 259.507 151.068 253.964C156.309 248.287 160.408 242.001 163.365 235.107C166.322 228.213 167.8 221.251 167.8 214.222C167.8 207.193 166.322 200.501 163.365 194.148C160.543 187.795 156.376 182.658 150.866 178.738C145.356 174.682 138.77 172.655 131.11 172.655H72.2437L41.8028 316.62H81.1139L90.3873 272.416ZM120.037 230.626L120.052 230.607C121.01 229.354 121.81 227.706 122.372 225.559L122.397 225.463L122.427 225.37C123.078 223.296 123.449 220.685 123.449 217.466C123.449 215.01 123.13 213.583 122.802 212.859C122.362 211.884 122.041 211.655 122.042 211.653L121.876 211.558L121.71 211.439C121.399 211.215 121.2 211.18 121.03 211.18H107.569L102.815 233.89H115.587C115.879 233.89 116.444 233.794 117.343 233.252C118.131 232.776 119.04 231.963 120.022 230.646L120.037 230.626ZM97.8464 237.946L104.297 207.125H121.03C122.105 207.125 123.113 207.463 124.054 208.139C124.995 208.68 125.801 209.693 126.473 211.18C127.145 212.667 127.481 214.763 127.481 217.466C127.481 220.981 127.078 224.022 126.271 226.591C125.599 229.159 124.591 231.322 123.247 233.079C122.038 234.701 120.761 235.918 119.417 236.729C118.073 237.54 116.796 237.946 115.587 237.946H97.8464ZM213.354 316.632L213.312 316.611C205.969 312.991 199.733 308.113 194.652 301.981C189.624 295.911 185.822 289.05 183.247 281.426L183.24 281.404C180.681 273.683 179.404 265.679 179.404 257.411C179.404 248.285 180.966 239.311 184.072 230.507L184.081 230.479L184.091 230.452C187.319 221.7 191.743 213.579 197.354 206.101L197.361 206.092C203.099 198.491 209.753 191.867 217.318 186.229C225.025 180.452 233.372 175.936 242.35 172.691C251.395 169.421 260.794 167.788 270.526 167.788C279.899 167.788 288.376 169.574 295.858 173.257C303.215 176.744 309.461 181.561 314.547 187.699C319.59 193.786 323.327 200.732 325.764 208.5C328.321 216.086 329.593 224.016 329.593 232.268C329.593 241.412 327.958 250.4 324.71 259.212C321.616 267.835 317.262 275.95 311.665 283.549L311.655 283.564L311.644 283.578C306.04 291.046 299.46 297.663 291.914 303.425C284.329 309.218 276.037 313.741 267.051 316.989C258.006 320.259 248.606 321.892 238.875 321.892C229.51 321.892 220.99 320.177 213.397 316.652L213.354 316.632ZM320.924 257.817C324.016 249.436 325.561 240.92 325.561 232.268C325.561 224.428 324.352 216.926 321.932 209.761C319.648 202.461 316.153 195.973 311.449 190.295C306.746 184.618 300.966 180.157 294.112 176.913C287.258 173.533 279.396 171.843 270.526 171.843C261.252 171.843 252.315 173.398 243.713 176.507C235.112 179.616 227.115 183.942 219.723 189.484C212.466 194.891 206.082 201.245 200.572 208.544C195.196 215.709 190.962 223.482 187.871 231.863C184.915 240.244 183.436 248.76 183.436 257.411C183.436 265.252 184.646 272.822 187.065 280.121C189.484 287.286 193.046 293.707 197.75 299.384C202.453 305.062 208.233 309.59 215.087 312.97C222.075 316.214 230.005 317.836 238.875 317.836C248.148 317.836 257.086 316.282 265.687 313.173C274.289 310.064 282.218 305.738 289.475 300.196C296.733 294.653 303.05 288.3 308.425 281.135C313.801 273.836 317.968 266.063 320.924 257.817ZM272.24 264.445L272.254 264.426C275.373 259.908 277.667 255.099 279.155 249.987C280.67 244.779 281.412 239.756 281.412 234.904C281.412 230.347 280.678 226.254 279.264 222.58L279.237 222.508L279.212 222.434C277.972 218.806 276.002 216.048 273.327 214.004C270.857 212.115 267.361 210.978 262.462 210.978C256.8 210.978 251.967 212.302 247.832 214.844L247.796 214.865L247.761 214.886C243.447 217.417 239.854 220.785 236.959 225.032C233.968 229.418 231.66 234.18 230.031 239.332C228.525 244.523 227.787 249.532 227.787 254.37C227.787 258.944 228.459 263.14 229.768 266.989C231.136 270.636 233.189 273.504 235.911 275.706C238.394 277.588 241.822 278.702 246.536 278.702C252.349 278.702 257.246 277.37 261.367 274.836C265.718 272.162 269.334 268.708 272.24 264.445ZM233.432 278.905C230.072 276.201 227.586 272.687 225.973 268.361C224.495 264.035 223.755 259.372 223.755 254.37C223.755 249.098 224.562 243.691 226.175 238.149C227.922 232.606 230.408 227.469 233.634 222.738C236.859 218.007 240.891 214.222 245.729 211.383C250.568 208.409 256.145 206.922 262.462 206.922C267.972 206.922 272.407 208.206 275.767 210.775C279.127 213.343 281.546 216.79 283.024 221.116C284.637 225.307 285.444 229.903 285.444 234.904C285.444 240.176 284.637 245.583 283.024 251.126C281.412 256.668 278.925 261.872 275.565 266.739C272.34 271.47 268.308 275.323 263.47 278.297C258.631 281.271 252.987 282.757 246.536 282.757C241.16 282.757 236.792 281.473 233.432 278.905ZM367.309 168.599H429.439C437.835 168.599 445.272 170.833 451.55 175.445C457.644 179.787 462.256 185.485 465.361 192.46C468.559 199.344 470.162 206.611 470.162 214.222C470.162 221.841 468.556 229.347 465.397 236.713C462.252 244.045 457.896 250.719 452.352 256.724L452.335 256.742L452.319 256.759C446.747 262.651 440.31 267.398 433.028 270.989C425.61 274.646 417.677 276.472 409.28 276.472H391.987L382.714 320.675H335.153L367.309 168.599ZM388.717 272.416H409.28C417.075 272.416 424.4 270.727 431.254 267.347C438.108 263.968 444.156 259.507 449.397 253.964C454.639 248.287 458.738 242.001 461.695 235.107C464.651 228.213 466.13 221.251 466.13 214.222C466.13 207.193 464.651 200.501 461.695 194.148C458.872 187.795 454.706 182.658 449.196 178.738C443.686 174.682 437.1 172.655 429.439 172.655H370.574L340.133 316.62H379.444L388.717 272.416ZM418.366 230.626L418.381 230.607C419.34 229.354 420.14 227.706 420.702 225.559L420.727 225.463L420.756 225.37C421.408 223.296 421.779 220.685 421.779 217.466C421.779 215.01 421.46 213.583 421.132 212.859C420.692 211.884 420.371 211.655 420.372 211.653L420.206 211.558L420.04 211.439C419.729 211.215 419.53 211.18 419.36 211.18H405.899L401.145 233.89H413.917C414.209 233.89 414.774 233.794 415.673 233.252C416.461 232.776 417.369 231.963 418.352 230.646L418.366 230.626ZM396.176 237.946L402.627 207.125H419.36C420.435 207.125 421.443 207.463 422.384 208.139C423.324 208.68 424.131 209.693 424.803 211.18C425.475 212.667 425.811 214.763 425.811 217.466C425.811 220.981 425.408 224.022 424.601 226.591C423.929 229.159 422.921 231.322 421.577 233.079C420.368 234.701 419.091 235.918 417.747 236.729C416.403 237.54 415.126 237.946 413.917 237.946H396.176Z' fill='white'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M68.9791 168.599H131.11C139.505 168.599 146.942 170.833 153.22 175.445C159.314 179.787 163.927 185.486 167.032 192.461C170.229 199.344 171.832 206.612 171.832 214.222C171.832 221.841 170.226 229.347 167.067 236.713C163.922 244.045 159.566 250.719 154.022 256.724L154.006 256.742L153.989 256.759C148.417 262.651 141.98 267.398 134.699 270.989C127.28 274.646 119.347 276.472 110.95 276.472H93.6573L84.3839 320.675H36.8232L68.9791 168.599ZM90.3873 272.416H110.95C118.745 272.416 126.07 270.727 132.924 267.347C139.778 263.968 145.826 259.507 151.068 253.964C156.309 248.287 160.408 242.001 163.365 235.107C166.322 228.213 167.8 221.251 167.8 214.222C167.8 207.193 166.322 200.501 163.365 194.148C160.543 187.795 156.376 182.658 150.866 178.738C145.356 174.682 138.77 172.655 131.11 172.655H72.2437L41.8028 316.62H81.1139L90.3873 272.416ZM120.037 230.626L120.052 230.607C121.01 229.354 121.81 227.706 122.372 225.559L122.397 225.463L122.427 225.37C123.078 223.296 123.449 220.685 123.449 217.466C123.449 215.01 123.13 213.583 122.802 212.859C122.362 211.884 122.041 211.655 122.042 211.653L121.876 211.558L121.71 211.439C121.399 211.215 121.2 211.18 121.03 211.18H107.569L102.815 233.89H115.587C115.879 233.89 116.444 233.794 117.343 233.252C118.131 232.776 119.04 231.963 120.022 230.646L120.037 230.626ZM97.8464 237.946L104.297 207.125H121.03C122.105 207.125 123.113 207.463 124.054 208.139C124.995 208.68 125.801 209.693 126.473 211.18C127.145 212.667 127.481 214.763 127.481 217.466C127.481 220.981 127.078 224.022 126.271 226.591C125.599 229.159 124.591 231.322 123.247 233.079C122.038 234.701 120.761 235.918 119.417 236.729C118.073 237.54 116.796 237.946 115.587 237.946H97.8464ZM213.354 316.632L213.312 316.611C205.969 312.991 199.733 308.113 194.652 301.981C189.624 295.911 185.822 289.05 183.247 281.426L183.24 281.404C180.681 273.683 179.404 265.679 179.404 257.411C179.404 248.285 180.966 239.311 184.072 230.507L184.081 230.479L184.091 230.452C187.319 221.7 191.743 213.579 197.354 206.101L197.361 206.092C203.099 198.491 209.753 191.867 217.318 186.229C225.025 180.452 233.372 175.936 242.35 172.691C251.395 169.421 260.794 167.788 270.526 167.788C279.899 167.788 288.376 169.574 295.858 173.257C303.215 176.744 309.461 181.561 314.547 187.699C319.59 193.786 323.327 200.732 325.764 208.5C328.321 216.086 329.593 224.016 329.593 232.268C329.593 241.412 327.958 250.4 324.71 259.212C321.616 267.835 317.262 275.95 311.665 283.549L311.655 283.564L311.644 283.578C306.04 291.046 299.46 297.663 291.914 303.425C284.329 309.218 276.037 313.741 267.051 316.989C258.006 320.259 248.606 321.892 238.875 321.892C229.51 321.892 220.99 320.177 213.397 316.652L213.354 316.632ZM320.924 257.817C324.016 249.436 325.561 240.92 325.561 232.268C325.561 224.428 324.352 216.926 321.932 209.761C319.648 202.461 316.153 195.973 311.449 190.295C306.746 184.618 300.966 180.157 294.112 176.913C287.258 173.533 279.396 171.843 270.526 171.843C261.252 171.843 252.315 173.398 243.713 176.507C235.112 179.616 227.115 183.942 219.723 189.484C212.466 194.891 206.082 201.245 200.572 208.544C195.196 215.709 190.962 223.482 187.871 231.863C184.915 240.244 183.436 248.76 183.436 257.411C183.436 265.252 184.646 272.822 187.065 280.121C189.484 287.286 193.046 293.707 197.75 299.384C202.453 305.062 208.233 309.59 215.087 312.97C222.075 316.214 230.005 317.836 238.875 317.836C248.148 317.836 257.086 316.282 265.687 313.173C274.289 310.064 282.218 305.738 289.475 300.196C296.733 294.653 303.05 288.3 308.425 281.135C313.801 273.836 317.968 266.063 320.924 257.817ZM272.24 264.445L272.254 264.426C275.373 259.908 277.667 255.099 279.155 249.987C280.67 244.779 281.412 239.756 281.412 234.904C281.412 230.347 280.678 226.254 279.264 222.58L279.237 222.508L279.212 222.434C277.972 218.806 276.002 216.048 273.327 214.004C270.857 212.115 267.361 210.978 262.462 210.978C256.8 210.978 251.967 212.302 247.832 214.844L247.796 214.865L247.761 214.886C243.447 217.417 239.854 220.785 236.959 225.032C233.968 229.418 231.66 234.18 230.031 239.332C228.525 244.523 227.787 249.532 227.787 254.37C227.787 258.944 228.459 263.14 229.768 266.989C231.136 270.636 233.189 273.504 235.911 275.706C238.394 277.588 241.822 278.702 246.536 278.702C252.349 278.702 257.246 277.37 261.367 274.836C265.718 272.162 269.334 268.708 272.24 264.445ZM233.432 278.905C230.072 276.201 227.586 272.687 225.973 268.361C224.495 264.035 223.755 259.372 223.755 254.37C223.755 249.098 224.562 243.691 226.175 238.149C227.922 232.606 230.408 227.469 233.634 222.738C236.859 218.007 240.891 214.222 245.729 211.383C250.568 208.409 256.145 206.922 262.462 206.922C267.972 206.922 272.407 208.206 275.767 210.775C279.127 213.343 281.546 216.79 283.024 221.116C284.637 225.307 285.444 229.903 285.444 234.904C285.444 240.176 284.637 245.583 283.024 251.126C281.412 256.668 278.925 261.872 275.565 266.739C272.34 271.47 268.308 275.323 263.47 278.297C258.631 281.271 252.987 282.757 246.536 282.757C241.16 282.757 236.792 281.473 233.432 278.905ZM367.309 168.599H429.439C437.835 168.599 445.272 170.833 451.55 175.445C457.644 179.787 462.256 185.485 465.361 192.46C468.559 199.344 470.162 206.611 470.162 214.222C470.162 221.841 468.556 229.347 465.397 236.713C462.252 244.045 457.896 250.719 452.352 256.724L452.335 256.742L452.319 256.759C446.747 262.651 440.31 267.398 433.028 270.989C425.61 274.646 417.677 276.472 409.28 276.472H391.987L382.714 320.675H335.153L367.309 168.599ZM388.717 272.416H409.28C417.075 272.416 424.4 270.727 431.254 267.347C438.108 263.968 444.156 259.507 449.397 253.964C454.639 248.287 458.738 242.001 461.695 235.107C464.651 228.213 466.13 221.251 466.13 214.222C466.13 207.193 464.651 200.501 461.695 194.148C458.872 187.795 454.706 182.658 449.196 178.738C443.686 174.682 437.1 172.655 429.439 172.655H370.574L340.133 316.62H379.444L388.717 272.416ZM418.366 230.626L418.381 230.607C419.34 229.354 420.14 227.706 420.702 225.559L420.727 225.463L420.756 225.37C421.408 223.296 421.779 220.685 421.779 217.466C421.779 215.01 421.46 213.583 421.132 212.859C420.692 211.884 420.371 211.655 420.372 211.653L420.206 211.558L420.04 211.439C419.729 211.215 419.53 211.18 419.36 211.18H405.899L401.145 233.89H413.917C414.209 233.89 414.774 233.794 415.673 233.252C416.461 232.776 417.369 231.963 418.352 230.646L418.366 230.626ZM396.176 237.946L402.627 207.125H419.36C420.435 207.125 421.443 207.463 422.384 208.139C423.324 208.68 424.131 209.693 424.803 211.18C425.475 212.667 425.811 214.763 425.811 217.466C425.811 220.981 425.408 224.022 424.601 226.591C423.929 229.159 422.921 231.322 421.577 233.079C420.368 234.701 419.091 235.918 417.747 236.729C416.403 237.54 415.126 237.946 413.917 237.946H396.176Z' stroke='white' stroke-width='0.3' mask='url(%23path-6-outside-1_873_174)'/%3E%3C/g%3E%3Cdefs%3E%3CradialGradient id='paint0_radial_873_174' cx='0' cy='0' r='1' gradientUnits='userSpaceOnUse' gradientTransform='translate(-62.8 -86.4) rotate(70.436) scale(686.854 394.059)'%3E%3Cstop stop-color='%236335EC'/%3E%3Cstop offset='1' stop-opacity='0'/%3E%3C/radialGradient%3E%3CradialGradient id='paint1_radial_873_174' cx='0' cy='0' r='1' gradientUnits='userSpaceOnUse' gradientTransform='translate(640.4 677.6) rotate(-116.448) scale(1055.25 662.094)'%3E%3Cstop stop-color='%23E6007A'/%3E%3Cstop offset='1' stop-opacity='0'/%3E%3C/radialGradient%3E%3CclipPath id='clip0_873_174'%3E%3Crect width='512' height='512' rx='256' fill='white'/%3E%3C/clipPath%3E%3C/defs%3E%3C/svg%3E%0A" /> Pop CLI Signing Portal - + `],K);d$=function(e,t,n,r){var o,i=arguments.length,s=i<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,n):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)s=Reflect.decorate(e,t,n,r);else for(var a=e.length-1;a>=0;a--)(o=e[a])&&(s=(i<3?o(s):i>3?o(t,n,s):o(t,n))||s);return i>3&&s&&Object.defineProperty(t,n,s),s}([RA("dc-connection-button")],d$);const f$={chains:{},wallets:[new class extends QE{constructor(e){super(),X(this,"options"),this.options=e}async getWallets(){return await lS,(()=>{const{injectedWeb3:e}=window;return e?Object.keys(e):[]})().map((e=>new aS(e,this.options)))}}]};function p$(e,t,{checkForDefaultPrevented:n=!0}={}){return function(r){if(null==e||e(r),!1===n||!r.defaultPrevented)return null==t?void 0:t(r)}}function h$(e,t){if("function"==typeof e)return e(t);null!=e&&(e.current=t)}function v$(...e){return t=>{let n=!1;const r=e.map((e=>{const r=h$(e,t);return n||"function"!=typeof r||(n=!0),r}));if(n)return()=>{for(let t=0;t{const t=n.map((e=>qe.createContext(e)));return function(n){const r=(null==n?void 0:n[e])||t;return qe.useMemo((()=>({[`__scope${e}`]:{...n,[e]:r}})),[n,r])}};return r.scopeName=e,[function(t,r){const o=qe.createContext(r),i=n.length;n=[...n,r];const s=t=>{var n;const{scope:r,children:s,...a}=t,l=(null==(n=null==r?void 0:r[e])?void 0:n[i])||o,c=qe.useMemo((()=>a),Object.values(a));return nt.jsx(l.Provider,{value:c,children:s})};return s.displayName=t+"Provider",[s,function(n,s){var a;const l=(null==(a=null==s?void 0:s[e])?void 0:a[i])||o,c=qe.useContext(l);if(c)return c;if(void 0!==r)return r;throw new Error(`\`${n}\` must be used within \`${t}\``)}]},y$(r,...t)]}function y$(...e){const t=e[0];if(1===e.length)return t;const n=()=>{const n=e.map((e=>({useScope:e(),scopeName:e.scopeName})));return function(e){const r=n.reduce(((t,{useScope:n,scopeName:r})=>({...t,...n(e)[`__scope${r}`]})),{});return qe.useMemo((()=>({[`__scope${t.scopeName}`]:r})),[r])}};return n.scopeName=t.scopeName,n}function b$(e){const t=qe.useRef(e);return qe.useEffect((()=>{t.current=e})),qe.useMemo((()=>(...e)=>{var n;return null==(n=t.current)?void 0:n.call(t,...e)}),[])}function w$({prop:e,defaultProp:t,onChange:n=()=>{}}){const[r,o]=function({defaultProp:e,onChange:t}){const n=qe.useState(e),[r]=n,o=qe.useRef(r),i=b$(t);return qe.useEffect((()=>{o.current!==r&&(i(r),o.current=r)}),[r,o,i]),n}({defaultProp:t,onChange:n}),i=void 0!==e,s=i?e:r,a=b$(n);return[s,qe.useCallback((t=>{if(i){const n="function"==typeof t?t(e):t;n!==e&&a(n)}else o(t)}),[i,e,o,a])]}!function(e){_A.next(e.wallets)}({wallets:f$.wallets});var x$=qe.forwardRef(((e,t)=>{const{children:n,...r}=e,o=qe.Children.toArray(n),i=o.find(S$);if(i){const e=i.props.children,n=o.map((t=>t===i?qe.Children.count(e)>1?qe.Children.only(null):qe.isValidElement(e)?e.props.children:null:t));return nt.jsx(k$,{...r,ref:t,children:qe.isValidElement(e)?qe.cloneElement(e,void 0,n):null})}return nt.jsx(k$,{...r,ref:t,children:n})}));x$.displayName="Slot";var k$=qe.forwardRef(((e,t)=>{const{children:n,...r}=e;if(qe.isValidElement(n)){const e=function(e){var t,n;let r=null==(t=Object.getOwnPropertyDescriptor(e.props,"ref"))?void 0:t.get,o=r&&"isReactWarning"in r&&r.isReactWarning;if(o)return e.ref;if(r=null==(n=Object.getOwnPropertyDescriptor(e,"ref"))?void 0:n.get,o=r&&"isReactWarning"in r&&r.isReactWarning,o)return e.props.ref;return e.props.ref||e.ref}(n);return qe.cloneElement(n,{...C$(r,n.props),ref:t?v$(t,e):e})}return qe.Children.count(n)>1?qe.Children.only(null):null}));k$.displayName="SlotClone";var E$=({children:e})=>nt.jsx(nt.Fragment,{children:e});function S$(e){return qe.isValidElement(e)&&e.type===E$}function C$(e,t){const n={...t};for(const r in t){const o=e[r],i=t[r];/^on[A-Z]/.test(r)?o&&i?n[r]=(...e)=>{i(...e),o(...e)}:o&&(n[r]=o):"style"===r?n[r]={...o,...i}:"className"===r&&(n[r]=[o,i].filter(Boolean).join(" "))}return{...e,...n}}var _$=["a","button","div","form","h2","h3","img","input","label","li","nav","ol","p","span","svg","ul"].reduce(((e,t)=>{const n=qe.forwardRef(((e,n)=>{const{asChild:r,...o}=e,i=r?x$:t;return"undefined"!=typeof window&&(window[Symbol.for("radix-ui")]=!0),nt.jsx(i,{...o,ref:n})}));return n.displayName=`Primitive.${t}`,{...e,[t]:n}}),{});function A$(e,t){e&&df.flushSync((()=>e.dispatchEvent(t)))}function N$(e){const t=e+"CollectionProvider",[n,r]=g$(t),[o,i]=n(t,{collectionRef:{current:null},itemMap:new Map}),s=e=>{const{scope:t,children:n}=e,r=Ke.useRef(null),i=Ke.useRef(new Map).current;return nt.jsx(o,{scope:t,itemMap:i,collectionRef:r,children:n})};s.displayName=t;const a=e+"CollectionSlot",l=Ke.forwardRef(((e,t)=>{const{scope:n,children:r}=e,o=m$(t,i(a,n).collectionRef);return nt.jsx(x$,{ref:o,children:r})}));l.displayName=a;const c=e+"CollectionItemSlot",u="data-radix-collection-item",d=Ke.forwardRef(((e,t)=>{const{scope:n,children:r,...o}=e,s=Ke.useRef(null),a=m$(t,s),l=i(c,n);return Ke.useEffect((()=>(l.itemMap.set(s,{ref:s,...o}),()=>{l.itemMap.delete(s)}))),nt.jsx(x$,{[u]:"",ref:a,children:r})}));return d.displayName=c,[{Provider:s,Slot:l,ItemSlot:d},function(t){const n=i(e+"CollectionConsumer",t),r=Ke.useCallback((()=>{const e=n.collectionRef.current;if(!e)return[];const t=Array.from(e.querySelectorAll(`[${u}]`)),r=Array.from(n.itemMap.values()).sort(((e,n)=>t.indexOf(e.ref.current)-t.indexOf(n.ref.current)));return r}),[n.collectionRef,n.itemMap]);return r},r]}var P$=qe.createContext(void 0);function $$(e){const t=qe.useContext(P$);return e||t||"ltr"}var j$,O$="dismissableLayer.update",R$="dismissableLayer.pointerDownOutside",M$="dismissableLayer.focusOutside",T$=qe.createContext({layers:new Set,layersWithOutsidePointerEventsDisabled:new Set,branches:new Set}),L$=qe.forwardRef(((e,t)=>{const{disableOutsidePointerEvents:n=!1,onEscapeKeyDown:r,onPointerDownOutside:o,onFocusOutside:i,onInteractOutside:s,onDismiss:a,...l}=e,c=qe.useContext(T$),[u,d]=qe.useState(null),f=(null==u?void 0:u.ownerDocument)??(null==globalThis?void 0:globalThis.document),[,p]=qe.useState({}),h=m$(t,(e=>d(e))),v=Array.from(c.layers),[m]=[...c.layersWithOutsidePointerEventsDisabled].slice(-1),g=v.indexOf(m),y=u?v.indexOf(u):-1,b=c.layersWithOutsidePointerEventsDisabled.size>0,w=y>=g,x=function(e,t=(null==globalThis?void 0:globalThis.document)){const n=b$(e),r=qe.useRef(!1),o=qe.useRef((()=>{}));return qe.useEffect((()=>{const e=e=>{if(e.target&&!r.current){let r=function(){z$(R$,n,i,{discrete:!0})};const i={originalEvent:e};"touch"===e.pointerType?(t.removeEventListener("click",o.current),o.current=r,t.addEventListener("click",o.current,{once:!0})):r()}else t.removeEventListener("click",o.current);r.current=!1},i=window.setTimeout((()=>{t.addEventListener("pointerdown",e)}),0);return()=>{window.clearTimeout(i),t.removeEventListener("pointerdown",e),t.removeEventListener("click",o.current)}}),[t,n]),{onPointerDownCapture:()=>r.current=!0}}((e=>{const t=e.target,n=[...c.branches].some((e=>e.contains(t)));w&&!n&&(null==o||o(e),null==s||s(e),e.defaultPrevented||null==a||a())}),f),k=function(e,t=(null==globalThis?void 0:globalThis.document)){const n=b$(e),r=qe.useRef(!1);return qe.useEffect((()=>{const e=e=>{if(e.target&&!r.current){z$(M$,n,{originalEvent:e},{discrete:!1})}};return t.addEventListener("focusin",e),()=>t.removeEventListener("focusin",e)}),[t,n]),{onFocusCapture:()=>r.current=!0,onBlurCapture:()=>r.current=!1}}((e=>{const t=e.target;[...c.branches].some((e=>e.contains(t)))||(null==i||i(e),null==s||s(e),e.defaultPrevented||null==a||a())}),f);return function(e,t=(null==globalThis?void 0:globalThis.document)){const n=b$(e);qe.useEffect((()=>{const e=e=>{"Escape"===e.key&&n(e)};return t.addEventListener("keydown",e,{capture:!0}),()=>t.removeEventListener("keydown",e,{capture:!0})}),[n,t])}((e=>{y===c.layers.size-1&&(null==r||r(e),!e.defaultPrevented&&a&&(e.preventDefault(),a()))}),f),qe.useEffect((()=>{if(u)return n&&(0===c.layersWithOutsidePointerEventsDisabled.size&&(j$=f.body.style.pointerEvents,f.body.style.pointerEvents="none"),c.layersWithOutsidePointerEventsDisabled.add(u)),c.layers.add(u),I$(),()=>{n&&1===c.layersWithOutsidePointerEventsDisabled.size&&(f.body.style.pointerEvents=j$)}}),[u,f,n,c]),qe.useEffect((()=>()=>{u&&(c.layers.delete(u),c.layersWithOutsidePointerEventsDisabled.delete(u),I$())}),[u,c]),qe.useEffect((()=>{const e=()=>p({});return document.addEventListener(O$,e),()=>document.removeEventListener(O$,e)}),[]),nt.jsx(_$.div,{...l,ref:h,style:{pointerEvents:b?w?"auto":"none":void 0,...e.style},onFocusCapture:p$(e.onFocusCapture,k.onFocusCapture),onBlurCapture:p$(e.onBlurCapture,k.onBlurCapture),onPointerDownCapture:p$(e.onPointerDownCapture,x.onPointerDownCapture)})}));L$.displayName="DismissableLayer";function I$(){const e=new CustomEvent(O$);document.dispatchEvent(e)}function z$(e,t,n,{discrete:r}){const o=n.originalEvent.target,i=new CustomEvent(e,{bubbles:!1,cancelable:!0,detail:n});t&&o.addEventListener(e,t,{once:!0}),r?A$(o,i):o.dispatchEvent(i)}qe.forwardRef(((e,t)=>{const n=qe.useContext(T$),r=qe.useRef(null),o=m$(t,r);return qe.useEffect((()=>{const e=r.current;if(e)return n.branches.add(e),()=>{n.branches.delete(e)}}),[n.branches]),nt.jsx(_$.div,{...e,ref:o})})).displayName="DismissableLayerBranch";var D$=0;function B$(){const e=document.createElement("span");return e.setAttribute("data-radix-focus-guard",""),e.tabIndex=0,e.style.outline="none",e.style.opacity="0",e.style.position="fixed",e.style.pointerEvents="none",e}var U$="focusScope.autoFocusOnMount",F$="focusScope.autoFocusOnUnmount",H$={bubbles:!1,cancelable:!0},V$=qe.forwardRef(((e,t)=>{const{loop:n=!1,trapped:r=!1,onMountAutoFocus:o,onUnmountAutoFocus:i,...s}=e,[a,l]=qe.useState(null),c=b$(o),u=b$(i),d=qe.useRef(null),f=m$(t,(e=>l(e))),p=qe.useRef({paused:!1,pause(){this.paused=!0},resume(){this.paused=!1}}).current;qe.useEffect((()=>{if(r){let e=function(e){if(p.paused||!a)return;const t=e.target;a.contains(t)?d.current=t:Q$(d.current,{select:!0})},t=function(e){if(p.paused||!a)return;const t=e.relatedTarget;null!==t&&(a.contains(t)||Q$(d.current,{select:!0}))},n=function(e){if(document.activeElement===document.body)for(const t of e)t.removedNodes.length>0&&Q$(a)};document.addEventListener("focusin",e),document.addEventListener("focusout",t);const r=new MutationObserver(n);return a&&r.observe(a,{childList:!0,subtree:!0}),()=>{document.removeEventListener("focusin",e),document.removeEventListener("focusout",t),r.disconnect()}}}),[r,a,p.paused]),qe.useEffect((()=>{if(a){Y$.add(p);const t=document.activeElement;if(!a.contains(t)){const n=new CustomEvent(U$,H$);a.addEventListener(U$,c),a.dispatchEvent(n),n.defaultPrevented||(!function(e,{select:t=!1}={}){const n=document.activeElement;for(const r of e)if(Q$(r,{select:t}),document.activeElement!==n)return}((e=W$(a),e.filter((e=>"A"!==e.tagName))),{select:!0}),document.activeElement===t&&Q$(a))}return()=>{a.removeEventListener(U$,c),setTimeout((()=>{const e=new CustomEvent(F$,H$);a.addEventListener(F$,u),a.dispatchEvent(e),e.defaultPrevented||Q$(t??document.body,{select:!0}),a.removeEventListener(F$,u),Y$.remove(p)}),0)}}var e}),[a,c,u,p]);const h=qe.useCallback((e=>{if(!n&&!r)return;if(p.paused)return;const t="Tab"===e.key&&!e.altKey&&!e.ctrlKey&&!e.metaKey,o=document.activeElement;if(t&&o){const t=e.currentTarget,[r,i]=function(e){const t=W$(e),n=q$(t,e),r=q$(t.reverse(),e);return[n,r]}(t);r&&i?e.shiftKey||o!==i?e.shiftKey&&o===r&&(e.preventDefault(),n&&Q$(i,{select:!0})):(e.preventDefault(),n&&Q$(r,{select:!0})):o===t&&e.preventDefault()}}),[n,r,p.paused]);return nt.jsx(_$.div,{tabIndex:-1,...s,ref:f,onKeyDown:h})}));function W$(e){const t=[],n=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT,{acceptNode:e=>{const t="INPUT"===e.tagName&&"hidden"===e.type;return e.disabled||e.hidden||t?NodeFilter.FILTER_SKIP:e.tabIndex>=0?NodeFilter.FILTER_ACCEPT:NodeFilter.FILTER_SKIP}});for(;n.nextNode();)t.push(n.currentNode);return t}function q$(e,t){for(const n of e)if(!K$(n,{upTo:t}))return n}function K$(e,{upTo:t}){if("hidden"===getComputedStyle(e).visibility)return!0;for(;e;){if(void 0!==t&&e===t)return!1;if("none"===getComputedStyle(e).display)return!0;e=e.parentElement}return!1}function Q$(e,{select:t=!1}={}){if(e&&e.focus){const n=document.activeElement;e.focus({preventScroll:!0}),e!==n&&function(e){return e instanceof HTMLInputElement&&"select"in e}(e)&&t&&e.select()}}V$.displayName="FocusScope";var Y$=function(){let e=[];return{add(t){const n=e[0];t!==n&&(null==n||n.pause()),e=G$(e,t),e.unshift(t)},remove(t){var n;e=G$(e,t),null==(n=e[0])||n.resume()}}}();function G$(e,t){const n=[...e],r=n.indexOf(t);return-1!==r&&n.splice(r,1),n}var J$=Boolean(null==globalThis?void 0:globalThis.document)?qe.useLayoutEffect:()=>{},X$=Qe["useId".toString()]||(()=>{}),Z$=0;function ej(e){const[t,n]=qe.useState(X$());return J$((()=>{n((e=>e??String(Z$++)))}),[e]),t?`radix-${t}`:""}const tj=["top","right","bottom","left"],nj=Math.min,rj=Math.max,oj=Math.round,ij=Math.floor,sj=e=>({x:e,y:e}),aj={left:"right",right:"left",bottom:"top",top:"bottom"},lj={start:"end",end:"start"};function cj(e,t,n){return rj(e,nj(t,n))}function uj(e,t){return"function"==typeof e?e(t):e}function dj(e){return e.split("-")[0]}function fj(e){return e.split("-")[1]}function pj(e){return"x"===e?"y":"x"}function hj(e){return"y"===e?"height":"width"}function vj(e){return["top","bottom"].includes(dj(e))?"y":"x"}function mj(e){return pj(vj(e))}function gj(e){return e.replace(/start|end/g,(e=>lj[e]))}function yj(e){return e.replace(/left|right|bottom|top/g,(e=>aj[e]))}function bj(e){return"number"!=typeof e?function(e){return{top:0,right:0,bottom:0,left:0,...e}}(e):{top:e,right:e,bottom:e,left:e}}function wj(e){const{x:t,y:n,width:r,height:o}=e;return{width:r,height:o,top:n,left:t,right:t+r,bottom:n+o,x:t,y:n}}function xj(e,t,n){let{reference:r,floating:o}=e;const i=vj(t),s=mj(t),a=hj(s),l=dj(t),c="y"===i,u=r.x+r.width/2-o.width/2,d=r.y+r.height/2-o.height/2,f=r[a]/2-o[a]/2;let p;switch(l){case"top":p={x:u,y:r.y-o.height};break;case"bottom":p={x:u,y:r.y+r.height};break;case"right":p={x:r.x+r.width,y:d};break;case"left":p={x:r.x-o.width,y:d};break;default:p={x:r.x,y:r.y}}switch(fj(t)){case"start":p[s]-=f*(n&&c?-1:1);break;case"end":p[s]+=f*(n&&c?-1:1)}return p}async function kj(e,t){var n;void 0===t&&(t={});const{x:r,y:o,platform:i,rects:s,elements:a,strategy:l}=e,{boundary:c="clippingAncestors",rootBoundary:u="viewport",elementContext:d="floating",altBoundary:f=!1,padding:p=0}=uj(t,e),h=bj(p),v=a[f?"floating"===d?"reference":"floating":d],m=wj(await i.getClippingRect({element:null==(n=await(null==i.isElement?void 0:i.isElement(v)))||n?v:v.contextElement||await(null==i.getDocumentElement?void 0:i.getDocumentElement(a.floating)),boundary:c,rootBoundary:u,strategy:l})),g="floating"===d?{x:r,y:o,width:s.floating.width,height:s.floating.height}:s.reference,y=await(null==i.getOffsetParent?void 0:i.getOffsetParent(a.floating)),b=await(null==i.isElement?void 0:i.isElement(y))&&await(null==i.getScale?void 0:i.getScale(y))||{x:1,y:1},w=wj(i.convertOffsetParentRelativeRectToViewportRelativeRect?await i.convertOffsetParentRelativeRectToViewportRelativeRect({elements:a,rect:g,offsetParent:y,strategy:l}):g);return{top:(m.top-w.top+h.top)/b.y,bottom:(w.bottom-m.bottom+h.bottom)/b.y,left:(m.left-w.left+h.left)/b.x,right:(w.right-m.right+h.right)/b.x}}function Ej(e,t){return{top:e.top-t.height,right:e.right-t.width,bottom:e.bottom-t.height,left:e.left-t.width}}function Sj(e){return tj.some((t=>e[t]>=0))}function Cj(){return"undefined"!=typeof window}function _j(e){return Pj(e)?(e.nodeName||"").toLowerCase():"#document"}function Aj(e){var t;return(null==e||null==(t=e.ownerDocument)?void 0:t.defaultView)||window}function Nj(e){var t;return null==(t=(Pj(e)?e.ownerDocument:e.document)||window.document)?void 0:t.documentElement}function Pj(e){return!!Cj()&&(e instanceof Node||e instanceof Aj(e).Node)}function $j(e){return!!Cj()&&(e instanceof Element||e instanceof Aj(e).Element)}function jj(e){return!!Cj()&&(e instanceof HTMLElement||e instanceof Aj(e).HTMLElement)}function Oj(e){return!(!Cj()||"undefined"==typeof ShadowRoot)&&(e instanceof ShadowRoot||e instanceof Aj(e).ShadowRoot)}function Rj(e){const{overflow:t,overflowX:n,overflowY:r,display:o}=Dj(e);return/auto|scroll|overlay|hidden|clip/.test(t+r+n)&&!["inline","contents"].includes(o)}function Mj(e){return["table","td","th"].includes(_j(e))}function Tj(e){return[":popover-open",":modal"].some((t=>{try{return e.matches(t)}catch(n){return!1}}))}function Lj(e){const t=Ij(),n=$j(e)?Dj(e):e;return"none"!==n.transform||"none"!==n.perspective||!!n.containerType&&"normal"!==n.containerType||!t&&!!n.backdropFilter&&"none"!==n.backdropFilter||!t&&!!n.filter&&"none"!==n.filter||["transform","perspective","filter"].some((e=>(n.willChange||"").includes(e)))||["paint","layout","strict","content"].some((e=>(n.contain||"").includes(e)))}function Ij(){return!("undefined"==typeof CSS||!CSS.supports)&&CSS.supports("-webkit-backdrop-filter","none")}function zj(e){return["html","body","#document"].includes(_j(e))}function Dj(e){return Aj(e).getComputedStyle(e)}function Bj(e){return $j(e)?{scrollLeft:e.scrollLeft,scrollTop:e.scrollTop}:{scrollLeft:e.scrollX,scrollTop:e.scrollY}}function Uj(e){if("html"===_j(e))return e;const t=e.assignedSlot||e.parentNode||Oj(e)&&e.host||Nj(e);return Oj(t)?t.host:t}function Fj(e){const t=Uj(e);return zj(t)?e.ownerDocument?e.ownerDocument.body:e.body:jj(t)&&Rj(t)?t:Fj(t)}function Hj(e,t,n){var r;void 0===t&&(t=[]),void 0===n&&(n=!0);const o=Fj(e),i=o===(null==(r=e.ownerDocument)?void 0:r.body),s=Aj(o);if(i){const e=Vj(s);return t.concat(s,s.visualViewport||[],Rj(o)?o:[],e&&n?Hj(e):[])}return t.concat(o,Hj(o,[],n))}function Vj(e){return e.parent&&Object.getPrototypeOf(e.parent)?e.frameElement:null}function Wj(e){const t=Dj(e);let n=parseFloat(t.width)||0,r=parseFloat(t.height)||0;const o=jj(e),i=o?e.offsetWidth:n,s=o?e.offsetHeight:r,a=oj(n)!==i||oj(r)!==s;return a&&(n=i,r=s),{width:n,height:r,$:a}}function qj(e){return $j(e)?e:e.contextElement}function Kj(e){const t=qj(e);if(!jj(t))return sj(1);const n=t.getBoundingClientRect(),{width:r,height:o,$:i}=Wj(t);let s=(i?oj(n.width):n.width)/r,a=(i?oj(n.height):n.height)/o;return s&&Number.isFinite(s)||(s=1),a&&Number.isFinite(a)||(a=1),{x:s,y:a}}const Qj=sj(0);function Yj(e){const t=Aj(e);return Ij()&&t.visualViewport?{x:t.visualViewport.offsetLeft,y:t.visualViewport.offsetTop}:Qj}function Gj(e,t,n,r){void 0===t&&(t=!1),void 0===n&&(n=!1);const o=e.getBoundingClientRect(),i=qj(e);let s=sj(1);t&&(r?$j(r)&&(s=Kj(r)):s=Kj(e));const a=function(e,t,n){return void 0===t&&(t=!1),!(!n||t&&n!==Aj(e))&&t}(i,n,r)?Yj(i):sj(0);let l=(o.left+a.x)/s.x,c=(o.top+a.y)/s.y,u=o.width/s.x,d=o.height/s.y;if(i){const e=Aj(i),t=r&&$j(r)?Aj(r):r;let n=e,o=Vj(n);for(;o&&r&&t!==n;){const e=Kj(o),t=o.getBoundingClientRect(),r=Dj(o),i=t.left+(o.clientLeft+parseFloat(r.paddingLeft))*e.x,s=t.top+(o.clientTop+parseFloat(r.paddingTop))*e.y;l*=e.x,c*=e.y,u*=e.x,d*=e.y,l+=i,c+=s,n=Aj(o),o=Vj(n)}}return wj({width:u,height:d,x:l,y:c})}function Jj(e,t){const n=Bj(e).scrollLeft;return t?t.left+n:Gj(Nj(e)).left+n}function Xj(e,t,n){void 0===n&&(n=!1);const r=e.getBoundingClientRect();return{x:r.left+t.scrollLeft-(n?0:Jj(e,r)),y:r.top+t.scrollTop}}function Zj(e,t,n){let r;if("viewport"===t)r=function(e,t){const n=Aj(e),r=Nj(e),o=n.visualViewport;let i=r.clientWidth,s=r.clientHeight,a=0,l=0;if(o){i=o.width,s=o.height;const e=Ij();(!e||e&&"fixed"===t)&&(a=o.offsetLeft,l=o.offsetTop)}return{width:i,height:s,x:a,y:l}}(e,n);else if("document"===t)r=function(e){const t=Nj(e),n=Bj(e),r=e.ownerDocument.body,o=rj(t.scrollWidth,t.clientWidth,r.scrollWidth,r.clientWidth),i=rj(t.scrollHeight,t.clientHeight,r.scrollHeight,r.clientHeight);let s=-n.scrollLeft+Jj(e);const a=-n.scrollTop;return"rtl"===Dj(r).direction&&(s+=rj(t.clientWidth,r.clientWidth)-o),{width:o,height:i,x:s,y:a}}(Nj(e));else if($j(t))r=function(e,t){const n=Gj(e,!0,"fixed"===t),r=n.top+e.clientTop,o=n.left+e.clientLeft,i=jj(e)?Kj(e):sj(1);return{width:e.clientWidth*i.x,height:e.clientHeight*i.y,x:o*i.x,y:r*i.y}}(t,n);else{const n=Yj(e);r={x:t.x-n.x,y:t.y-n.y,width:t.width,height:t.height}}return wj(r)}function eO(e,t){const n=Uj(e);return!(n===t||!$j(n)||zj(n))&&("fixed"===Dj(n).position||eO(n,t))}function tO(e,t,n){const r=jj(t),o=Nj(t),i="fixed"===n,s=Gj(e,!0,i,t);let a={scrollLeft:0,scrollTop:0};const l=sj(0);if(r||!r&&!i)if(("body"!==_j(t)||Rj(o))&&(a=Bj(t)),r){const e=Gj(t,!0,i,t);l.x=e.x+t.clientLeft,l.y=e.y+t.clientTop}else o&&(l.x=Jj(o));const c=!o||r||i?sj(0):Xj(o,a);return{x:s.left+a.scrollLeft-l.x-c.x,y:s.top+a.scrollTop-l.y-c.y,width:s.width,height:s.height}}function nO(e){return"static"===Dj(e).position}function rO(e,t){if(!jj(e)||"fixed"===Dj(e).position)return null;if(t)return t(e);let n=e.offsetParent;return Nj(e)===n&&(n=n.ownerDocument.body),n}function oO(e,t){const n=Aj(e);if(Tj(e))return n;if(!jj(e)){let t=Uj(e);for(;t&&!zj(t);){if($j(t)&&!nO(t))return t;t=Uj(t)}return n}let r=rO(e,t);for(;r&&Mj(r)&&nO(r);)r=rO(r,t);return r&&zj(r)&&nO(r)&&!Lj(r)?n:r||function(e){let t=Uj(e);for(;jj(t)&&!zj(t);){if(Lj(t))return t;if(Tj(t))return null;t=Uj(t)}return null}(e)||n}const iO={convertOffsetParentRelativeRectToViewportRelativeRect:function(e){let{elements:t,rect:n,offsetParent:r,strategy:o}=e;const i="fixed"===o,s=Nj(r),a=!!t&&Tj(t.floating);if(r===s||a&&i)return n;let l={scrollLeft:0,scrollTop:0},c=sj(1);const u=sj(0),d=jj(r);if((d||!d&&!i)&&(("body"!==_j(r)||Rj(s))&&(l=Bj(r)),jj(r))){const e=Gj(r);c=Kj(r),u.x=e.x+r.clientLeft,u.y=e.y+r.clientTop}const f=!s||d||i?sj(0):Xj(s,l,!0);return{width:n.width*c.x,height:n.height*c.y,x:n.x*c.x-l.scrollLeft*c.x+u.x+f.x,y:n.y*c.y-l.scrollTop*c.y+u.y+f.y}},getDocumentElement:Nj,getClippingRect:function(e){let{element:t,boundary:n,rootBoundary:r,strategy:o}=e;const i=[..."clippingAncestors"===n?Tj(t)?[]:function(e,t){const n=t.get(e);if(n)return n;let r=Hj(e,[],!1).filter((e=>$j(e)&&"body"!==_j(e))),o=null;const i="fixed"===Dj(e).position;let s=i?Uj(e):e;for(;$j(s)&&!zj(s);){const t=Dj(s),n=Lj(s);n||"fixed"!==t.position||(o=null),(i?!n&&!o:!n&&"static"===t.position&&o&&["absolute","fixed"].includes(o.position)||Rj(s)&&!n&&eO(e,s))?r=r.filter((e=>e!==s)):o=t,s=Uj(s)}return t.set(e,r),r}(t,this._c):[].concat(n),r],s=i[0],a=i.reduce(((e,n)=>{const r=Zj(t,n,o);return e.top=rj(r.top,e.top),e.right=nj(r.right,e.right),e.bottom=nj(r.bottom,e.bottom),e.left=rj(r.left,e.left),e}),Zj(t,s,o));return{width:a.right-a.left,height:a.bottom-a.top,x:a.left,y:a.top}},getOffsetParent:oO,getElementRects:async function(e){const t=this.getOffsetParent||oO,n=this.getDimensions,r=await n(e.floating);return{reference:tO(e.reference,await t(e.floating),e.strategy),floating:{x:0,y:0,width:r.width,height:r.height}}},getClientRects:function(e){return Array.from(e.getClientRects())},getDimensions:function(e){const{width:t,height:n}=Wj(e);return{width:t,height:n}},getScale:Kj,isElement:$j,isRTL:function(e){return"rtl"===Dj(e).direction}};function sO(e,t,n,r){void 0===r&&(r={});const{ancestorScroll:o=!0,ancestorResize:i=!0,elementResize:s="function"==typeof ResizeObserver,layoutShift:a="function"==typeof IntersectionObserver,animationFrame:l=!1}=r,c=qj(e),u=o||i?[...c?Hj(c):[],...Hj(t)]:[];u.forEach((e=>{o&&e.addEventListener("scroll",n,{passive:!0}),i&&e.addEventListener("resize",n)}));const d=c&&a?function(e,t){let n,r=null;const o=Nj(e);function i(){var e;clearTimeout(n),null==(e=r)||e.disconnect(),r=null}return function s(a,l){void 0===a&&(a=!1),void 0===l&&(l=1),i();const{left:c,top:u,width:d,height:f}=e.getBoundingClientRect();if(a||t(),!d||!f)return;const p={rootMargin:-ij(u)+"px "+-ij(o.clientWidth-(c+d))+"px "+-ij(o.clientHeight-(u+f))+"px "+-ij(c)+"px",threshold:rj(0,nj(1,l))||1};let h=!0;function v(e){const t=e[0].intersectionRatio;if(t!==l){if(!h)return s();t?s(!1,t):n=setTimeout((()=>{s(!1,1e-7)}),1e3)}h=!1}try{r=new IntersectionObserver(v,{...p,root:o.ownerDocument})}catch(m){r=new IntersectionObserver(v,p)}r.observe(e)}(!0),i}(c,n):null;let f,p=-1,h=null;s&&(h=new ResizeObserver((e=>{let[r]=e;r&&r.target===c&&h&&(h.unobserve(t),cancelAnimationFrame(p),p=requestAnimationFrame((()=>{var e;null==(e=h)||e.observe(t)}))),n()})),c&&!l&&h.observe(c),h.observe(t));let v=l?Gj(e):null;return l&&function t(){const r=Gj(e);!v||r.x===v.x&&r.y===v.y&&r.width===v.width&&r.height===v.height||n();v=r,f=requestAnimationFrame(t)}(),n(),()=>{var e;u.forEach((e=>{o&&e.removeEventListener("scroll",n),i&&e.removeEventListener("resize",n)})),null==d||d(),null==(e=h)||e.disconnect(),h=null,l&&cancelAnimationFrame(f)}}const aO=function(e){return void 0===e&&(e=0),{name:"offset",options:e,async fn(t){var n,r;const{x:o,y:i,placement:s,middlewareData:a}=t,l=await async function(e,t){const{placement:n,platform:r,elements:o}=e,i=await(null==r.isRTL?void 0:r.isRTL(o.floating)),s=dj(n),a=fj(n),l="y"===vj(n),c=["left","top"].includes(s)?-1:1,u=i&&l?-1:1,d=uj(t,e);let{mainAxis:f,crossAxis:p,alignmentAxis:h}="number"==typeof d?{mainAxis:d,crossAxis:0,alignmentAxis:null}:{mainAxis:d.mainAxis||0,crossAxis:d.crossAxis||0,alignmentAxis:d.alignmentAxis};return a&&"number"==typeof h&&(p="end"===a?-1*h:h),l?{x:p*u,y:f*c}:{x:f*c,y:p*u}}(t,e);return s===(null==(n=a.offset)?void 0:n.placement)&&null!=(r=a.arrow)&&r.alignmentOffset?{}:{x:o+l.x,y:i+l.y,data:{...l,placement:s}}}}},lO=function(e){return void 0===e&&(e={}),{name:"shift",options:e,async fn(t){const{x:n,y:r,placement:o}=t,{mainAxis:i=!0,crossAxis:s=!1,limiter:a={fn:e=>{let{x:t,y:n}=e;return{x:t,y:n}}},...l}=uj(e,t),c={x:n,y:r},u=await kj(t,l),d=vj(dj(o)),f=pj(d);let p=c[f],h=c[d];if(i){const e="y"===f?"bottom":"right";p=cj(p+u["y"===f?"top":"left"],p,p-u[e])}if(s){const e="y"===d?"bottom":"right";h=cj(h+u["y"===d?"top":"left"],h,h-u[e])}const v=a.fn({...t,[f]:p,[d]:h});return{...v,data:{x:v.x-n,y:v.y-r,enabled:{[f]:i,[d]:s}}}}}},cO=function(e){return void 0===e&&(e={}),{name:"flip",options:e,async fn(t){var n,r;const{placement:o,middlewareData:i,rects:s,initialPlacement:a,platform:l,elements:c}=t,{mainAxis:u=!0,crossAxis:d=!0,fallbackPlacements:f,fallbackStrategy:p="bestFit",fallbackAxisSideDirection:h="none",flipAlignment:v=!0,...m}=uj(e,t);if(null!=(n=i.arrow)&&n.alignmentOffset)return{};const g=dj(o),y=vj(a),b=dj(a)===a,w=await(null==l.isRTL?void 0:l.isRTL(c.floating)),x=f||(b||!v?[yj(a)]:function(e){const t=yj(e);return[gj(e),t,gj(t)]}(a)),k="none"!==h;!f&&k&&x.push(...function(e,t,n,r){const o=fj(e);let i=function(e,t,n){const r=["left","right"],o=["right","left"],i=["top","bottom"],s=["bottom","top"];switch(e){case"top":case"bottom":return n?t?o:r:t?r:o;case"left":case"right":return t?i:s;default:return[]}}(dj(e),"start"===n,r);return o&&(i=i.map((e=>e+"-"+o)),t&&(i=i.concat(i.map(gj)))),i}(a,v,h,w));const E=[a,...x],S=await kj(t,m),C=[];let _=(null==(r=i.flip)?void 0:r.overflows)||[];if(u&&C.push(S[g]),d){const e=function(e,t,n){void 0===n&&(n=!1);const r=fj(e),o=mj(e),i=hj(o);let s="x"===o?r===(n?"end":"start")?"right":"left":"start"===r?"bottom":"top";return t.reference[i]>t.floating[i]&&(s=yj(s)),[s,yj(s)]}(o,s,w);C.push(S[e[0]],S[e[1]])}if(_=[..._,{placement:o,overflows:C}],!C.every((e=>e<=0))){var A,N;const e=((null==(A=i.flip)?void 0:A.index)||0)+1,t=E[e];if(t)return{data:{index:e,overflows:_},reset:{placement:t}};let n=null==(N=_.filter((e=>e.overflows[0]<=0)).sort(((e,t)=>e.overflows[1]-t.overflows[1]))[0])?void 0:N.placement;if(!n)switch(p){case"bestFit":{var P;const e=null==(P=_.filter((e=>{if(k){const t=vj(e.placement);return t===y||"y"===t}return!0})).map((e=>[e.placement,e.overflows.filter((e=>e>0)).reduce(((e,t)=>e+t),0)])).sort(((e,t)=>e[1]-t[1]))[0])?void 0:P[0];e&&(n=e);break}case"initialPlacement":n=a}if(o!==n)return{reset:{placement:n}}}return{}}}},uO=function(e){return void 0===e&&(e={}),{name:"size",options:e,async fn(t){var n,r;const{placement:o,rects:i,platform:s,elements:a}=t,{apply:l=()=>{},...c}=uj(e,t),u=await kj(t,c),d=dj(o),f=fj(o),p="y"===vj(o),{width:h,height:v}=i.floating;let m,g;"top"===d||"bottom"===d?(m=d,g=f===(await(null==s.isRTL?void 0:s.isRTL(a.floating))?"start":"end")?"left":"right"):(g=d,m="end"===f?"top":"bottom");const y=v-u.top-u.bottom,b=h-u.left-u.right,w=nj(v-u[m],y),x=nj(h-u[g],b),k=!t.middlewareData.shift;let E=w,S=x;if(null!=(n=t.middlewareData.shift)&&n.enabled.x&&(S=b),null!=(r=t.middlewareData.shift)&&r.enabled.y&&(E=y),k&&!f){const e=rj(u.left,0),t=rj(u.right,0),n=rj(u.top,0),r=rj(u.bottom,0);p?S=h-2*(0!==e||0!==t?e+t:rj(u.left,u.right)):E=v-2*(0!==n||0!==r?n+r:rj(u.top,u.bottom))}await l({...t,availableWidth:S,availableHeight:E});const C=await s.getDimensions(a.floating);return h!==C.width||v!==C.height?{reset:{rects:!0}}:{}}}},dO=function(e){return void 0===e&&(e={}),{name:"hide",options:e,async fn(t){const{rects:n}=t,{strategy:r="referenceHidden",...o}=uj(e,t);switch(r){case"referenceHidden":{const e=Ej(await kj(t,{...o,elementContext:"reference"}),n.reference);return{data:{referenceHiddenOffsets:e,referenceHidden:Sj(e)}}}case"escaped":{const e=Ej(await kj(t,{...o,altBoundary:!0}),n.floating);return{data:{escapedOffsets:e,escaped:Sj(e)}}}default:return{}}}}},fO=e=>({name:"arrow",options:e,async fn(t){const{x:n,y:r,placement:o,rects:i,platform:s,elements:a,middlewareData:l}=t,{element:c,padding:u=0}=uj(e,t)||{};if(null==c)return{};const d=bj(u),f={x:n,y:r},p=mj(o),h=hj(p),v=await s.getDimensions(c),m="y"===p,g=m?"top":"left",y=m?"bottom":"right",b=m?"clientHeight":"clientWidth",w=i.reference[h]+i.reference[p]-f[p]-i.floating[h],x=f[p]-i.reference[p],k=await(null==s.getOffsetParent?void 0:s.getOffsetParent(c));let E=k?k[b]:0;E&&await(null==s.isElement?void 0:s.isElement(k))||(E=a.floating[b]||i.floating[h]);const S=w/2-x/2,C=E/2-v[h]/2-1,_=nj(d[g],C),A=nj(d[y],C),N=_,P=E-v[h]-A,$=E/2-v[h]/2+S,j=cj(N,$,P),O=!l.arrow&&null!=fj(o)&&$!==j&&i.reference[h]/2-($n&&(p=n)}if(c){var g,y;const e="y"===f?"width":"height",t=["top","left"].includes(dj(o)),n=i.reference[d]-i.floating[e]+(t&&(null==(g=s.offset)?void 0:g[d])||0)+(t?0:m.crossAxis),r=i.reference[d]+i.reference[e]+(t?0:(null==(y=s.offset)?void 0:y[d])||0)-(t?m.crossAxis:0);hr&&(h=r)}return{[f]:p,[d]:h}}}},hO=(e,t,n)=>{const r=new Map,o={platform:iO,...n},i={...o.platform,_c:r};return(async(e,t,n)=>{const{placement:r="bottom",strategy:o="absolute",middleware:i=[],platform:s}=n,a=i.filter(Boolean),l=await(null==s.isRTL?void 0:s.isRTL(t));let c=await s.getElementRects({reference:e,floating:t,strategy:o}),{x:u,y:d}=xj(c,r,l),f=r,p={},h=0;for(let v=0;v{t.current=e})),t}const wO=e=>({name:"arrow",options:e,fn(t){const{element:n,padding:r}="function"==typeof e?e(t):e;return n&&(o=n,{}.hasOwnProperty.call(o,"current"))?null!=n.current?fO({element:n.current,padding:r}).fn(t):{}:n?fO({element:n,padding:r}).fn(t):{};var o}}),xO=(e,t)=>({...aO(e),options:[e,t]}),kO=(e,t)=>({...lO(e),options:[e,t]}),EO=(e,t)=>({...pO(e),options:[e,t]}),SO=(e,t)=>({...cO(e),options:[e,t]}),CO=(e,t)=>({...uO(e),options:[e,t]}),_O=(e,t)=>({...dO(e),options:[e,t]}),AO=(e,t)=>({...wO(e),options:[e,t]});var NO=qe.forwardRef(((e,t)=>{const{children:n,width:r=10,height:o=5,...i}=e;return nt.jsx(_$.svg,{...i,ref:t,width:r,height:o,viewBox:"0 0 30 10",preserveAspectRatio:"none",children:e.asChild?n:nt.jsx("polygon",{points:"0,0 30,0 15,10"})})}));NO.displayName="Arrow";var PO=NO;var $O="Popper",[jO,OO]=g$($O),[RO,MO]=jO($O),TO=e=>{const{__scopePopper:t,children:n}=e,[r,o]=qe.useState(null);return nt.jsx(RO,{scope:t,anchor:r,onAnchorChange:o,children:n})};TO.displayName=$O;var LO="PopperAnchor",IO=qe.forwardRef(((e,t)=>{const{__scopePopper:n,virtualRef:r,...o}=e,i=MO(LO,n),s=qe.useRef(null),a=m$(t,s);return qe.useEffect((()=>{i.onAnchorChange((null==r?void 0:r.current)||s.current)})),r?null:nt.jsx(_$.div,{...o,ref:a})}));IO.displayName=LO;var zO="PopperContent",[DO,BO]=jO(zO),UO=qe.forwardRef(((e,t)=>{var n,r,o,i,s,a;const{__scopePopper:l,side:c="bottom",sideOffset:u=0,align:d="center",alignOffset:f=0,arrowPadding:p=0,avoidCollisions:h=!0,collisionBoundary:v=[],collisionPadding:m=0,sticky:g="partial",hideWhenDetached:y=!1,updatePositionStrategy:b="optimized",onPlaced:w,...x}=e,k=MO(zO,l),[E,S]=qe.useState(null),C=m$(t,(e=>S(e))),[_,A]=qe.useState(null),N=function(e){const[t,n]=qe.useState(void 0);return J$((()=>{if(e){n({width:e.offsetWidth,height:e.offsetHeight});const t=new ResizeObserver((t=>{if(!Array.isArray(t))return;if(!t.length)return;const r=t[0];let o,i;if("borderBoxSize"in r){const e=r.borderBoxSize,t=Array.isArray(e)?e[0]:e;o=t.inlineSize,i=t.blockSize}else o=e.offsetWidth,i=e.offsetHeight;n({width:o,height:i})}));return t.observe(e,{box:"border-box"}),()=>t.unobserve(e)}n(void 0)}),[e]),t}(_),P=(null==N?void 0:N.width)??0,$=(null==N?void 0:N.height)??0,j=c+("center"!==d?"-"+d:""),O="number"==typeof m?m:{top:0,right:0,bottom:0,left:0,...m},R=Array.isArray(v)?v:[v],M=R.length>0,T={padding:O,boundary:R.filter(WO),altBoundary:M},{refs:L,floatingStyles:I,placement:z,isPositioned:D,middlewareData:B}=function(e){void 0===e&&(e={});const{placement:t="bottom",strategy:n="absolute",middleware:r=[],platform:o,elements:{reference:i,floating:s}={},transform:a=!0,whileElementsMounted:l,open:c}=e,[u,d]=qe.useState({x:0,y:0,strategy:n,placement:t,middlewareData:{},isPositioned:!1}),[f,p]=qe.useState(r);mO(f,r)||p(r);const[h,v]=qe.useState(null),[m,g]=qe.useState(null),y=qe.useCallback((e=>{e!==k.current&&(k.current=e,v(e))}),[]),b=qe.useCallback((e=>{e!==E.current&&(E.current=e,g(e))}),[]),w=i||h,x=s||m,k=qe.useRef(null),E=qe.useRef(null),S=qe.useRef(u),C=null!=l,_=bO(l),A=bO(o),N=bO(c),P=qe.useCallback((()=>{if(!k.current||!E.current)return;const e={placement:t,strategy:n,middleware:f};A.current&&(e.platform=A.current),hO(k.current,E.current,e).then((e=>{const t={...e,isPositioned:!1!==N.current};$.current&&!mO(S.current,t)&&(S.current=t,df.flushSync((()=>{d(t)})))}))}),[f,t,n,A,N]);vO((()=>{!1===c&&S.current.isPositioned&&(S.current.isPositioned=!1,d((e=>({...e,isPositioned:!1}))))}),[c]);const $=qe.useRef(!1);vO((()=>($.current=!0,()=>{$.current=!1})),[]),vO((()=>{if(w&&(k.current=w),x&&(E.current=x),w&&x){if(_.current)return _.current(w,x,P);P()}}),[w,x,P,_,C]);const j=qe.useMemo((()=>({reference:k,floating:E,setReference:y,setFloating:b})),[y,b]),O=qe.useMemo((()=>({reference:w,floating:x})),[w,x]),R=qe.useMemo((()=>{const e={position:n,left:0,top:0};if(!O.floating)return e;const t=yO(O.floating,u.x),r=yO(O.floating,u.y);return a?{...e,transform:"translate("+t+"px, "+r+"px)",...gO(O.floating)>=1.5&&{willChange:"transform"}}:{position:n,left:t,top:r}}),[n,a,O.floating,u.x,u.y]);return qe.useMemo((()=>({...u,update:P,refs:j,elements:O,floatingStyles:R})),[u,P,j,O,R])}({strategy:"fixed",placement:j,whileElementsMounted:(...e)=>sO(...e,{animationFrame:"always"===b}),elements:{reference:k.anchor},middleware:[xO({mainAxis:u+$,alignmentAxis:f}),h&&kO({mainAxis:!0,crossAxis:!1,limiter:"partial"===g?EO():void 0,...T}),h&&SO({...T}),CO({...T,apply:({elements:e,rects:t,availableWidth:n,availableHeight:r})=>{const{width:o,height:i}=t.reference,s=e.floating.style;s.setProperty("--radix-popper-available-width",`${n}px`),s.setProperty("--radix-popper-available-height",`${r}px`),s.setProperty("--radix-popper-anchor-width",`${o}px`),s.setProperty("--radix-popper-anchor-height",`${i}px`)}}),_&&AO({element:_,padding:p}),qO({arrowWidth:P,arrowHeight:$}),y&&_O({strategy:"referenceHidden",...T})]}),[U,F]=KO(z),H=b$(w);J$((()=>{D&&(null==H||H())}),[D,H]);const V=null==(n=B.arrow)?void 0:n.x,W=null==(r=B.arrow)?void 0:r.y,q=0!==(null==(o=B.arrow)?void 0:o.centerOffset),[K,Q]=qe.useState();return J$((()=>{E&&Q(window.getComputedStyle(E).zIndex)}),[E]),nt.jsx("div",{ref:L.setFloating,"data-radix-popper-content-wrapper":"",style:{...I,transform:D?I.transform:"translate(0, -200%)",minWidth:"max-content",zIndex:K,"--radix-popper-transform-origin":[null==(i=B.transformOrigin)?void 0:i.x,null==(s=B.transformOrigin)?void 0:s.y].join(" "),...(null==(a=B.hide)?void 0:a.referenceHidden)&&{visibility:"hidden",pointerEvents:"none"}},dir:e.dir,children:nt.jsx(DO,{scope:l,placedSide:U,onArrowChange:A,arrowX:V,arrowY:W,shouldHideArrow:q,children:nt.jsx(_$.div,{"data-side":U,"data-align":F,...x,ref:C,style:{...x.style,animation:D?void 0:"none"}})})})}));UO.displayName=zO;var FO="PopperArrow",HO={top:"bottom",right:"left",bottom:"top",left:"right"},VO=qe.forwardRef((function(e,t){const{__scopePopper:n,...r}=e,o=BO(FO,n),i=HO[o.placedSide];return nt.jsx("span",{ref:o.onArrowChange,style:{position:"absolute",left:o.arrowX,top:o.arrowY,[i]:0,transformOrigin:{top:"",right:"0 0",bottom:"center 0",left:"100% 0"}[o.placedSide],transform:{top:"translateY(100%)",right:"translateY(50%) rotate(90deg) translateX(-50%)",bottom:"rotate(180deg)",left:"translateY(50%) rotate(-90deg) translateX(50%)"}[o.placedSide],visibility:o.shouldHideArrow?"hidden":void 0},children:nt.jsx(PO,{...r,ref:t,style:{...r.style,display:"block"}})})}));function WO(e){return null!==e}VO.displayName=FO;var qO=e=>({name:"transformOrigin",options:e,fn(t){var n,r,o;const{placement:i,rects:s,middlewareData:a}=t,l=0!==(null==(n=a.arrow)?void 0:n.centerOffset),c=l?0:e.arrowWidth,u=l?0:e.arrowHeight,[d,f]=KO(i),p={start:"0%",center:"50%",end:"100%"}[f],h=((null==(r=a.arrow)?void 0:r.x)??0)+c/2,v=((null==(o=a.arrow)?void 0:o.y)??0)+u/2;let m="",g="";return"bottom"===d?(m=l?p:`${h}px`,g=-u+"px"):"top"===d?(m=l?p:`${h}px`,g=`${s.floating.height+u}px`):"right"===d?(m=-u+"px",g=l?p:`${v}px`):"left"===d&&(m=`${s.floating.width+u}px`,g=l?p:`${v}px`),{data:{x:m,y:g}}}});function KO(e){const[t,n="center"]=e.split("-");return[t,n]}var QO=TO,YO=IO,GO=UO,JO=VO,XO=qe.forwardRef(((e,t)=>{var n;const{container:r,...o}=e,[i,s]=qe.useState(!1);J$((()=>s(!0)),[]);const a=r||i&&(null==(n=null==globalThis?void 0:globalThis.document)?void 0:n.body);return a?ff.createPortal(nt.jsx(_$.div,{...o,ref:t}),a):null}));XO.displayName="Portal";var ZO=e=>{const{present:t,children:n}=e,r=function(e){const[t,n]=qe.useState(),r=qe.useRef({}),o=qe.useRef(e),i=qe.useRef("none"),s=e?"mounted":"unmounted",[a,l]=function(e,t){return qe.useReducer(((e,n)=>t[e][n]??e),e)}(s,{mounted:{UNMOUNT:"unmounted",ANIMATION_OUT:"unmountSuspended"},unmountSuspended:{MOUNT:"mounted",ANIMATION_END:"unmounted"},unmounted:{MOUNT:"mounted"}});return qe.useEffect((()=>{const e=eR(r.current);i.current="mounted"===a?e:"none"}),[a]),J$((()=>{const t=r.current,n=o.current;if(n!==e){const r=i.current,s=eR(t);if(e)l("MOUNT");else if("none"===s||"none"===(null==t?void 0:t.display))l("UNMOUNT");else{l(n&&r!==s?"ANIMATION_OUT":"UNMOUNT")}o.current=e}}),[e,l]),J$((()=>{if(t){let e;const n=t.ownerDocument.defaultView??window,s=i=>{const s=eR(r.current).includes(i.animationName);if(i.target===t&&s&&(l("ANIMATION_END"),!o.current)){const r=t.style.animationFillMode;t.style.animationFillMode="forwards",e=n.setTimeout((()=>{"forwards"===t.style.animationFillMode&&(t.style.animationFillMode=r)}))}},a=e=>{e.target===t&&(i.current=eR(r.current))};return t.addEventListener("animationstart",a),t.addEventListener("animationcancel",s),t.addEventListener("animationend",s),()=>{n.clearTimeout(e),t.removeEventListener("animationstart",a),t.removeEventListener("animationcancel",s),t.removeEventListener("animationend",s)}}l("ANIMATION_END")}),[t,l]),{isPresent:["mounted","unmountSuspended"].includes(a),ref:qe.useCallback((e=>{e&&(r.current=getComputedStyle(e)),n(e)}),[])}}(t),o="function"==typeof n?n({present:r.isPresent}):qe.Children.only(n),i=m$(r.ref,function(e){var t,n;let r=null==(t=Object.getOwnPropertyDescriptor(e.props,"ref"))?void 0:t.get,o=r&&"isReactWarning"in r&&r.isReactWarning;if(o)return e.ref;if(r=null==(n=Object.getOwnPropertyDescriptor(e,"ref"))?void 0:n.get,o=r&&"isReactWarning"in r&&r.isReactWarning,o)return e.props.ref;return e.props.ref||e.ref}(o));return"function"==typeof n||r.isPresent?qe.cloneElement(o,{ref:i}):null};function eR(e){return(null==e?void 0:e.animationName)||"none"}ZO.displayName="Presence";var tR="rovingFocusGroup.onEntryFocus",nR={bubbles:!1,cancelable:!0},rR="RovingFocusGroup",[oR,iR,sR]=N$(rR),[aR,lR]=g$(rR,[sR]),[cR,uR]=aR(rR),dR=qe.forwardRef(((e,t)=>nt.jsx(oR.Provider,{scope:e.__scopeRovingFocusGroup,children:nt.jsx(oR.Slot,{scope:e.__scopeRovingFocusGroup,children:nt.jsx(fR,{...e,ref:t})})})));dR.displayName=rR;var fR=qe.forwardRef(((e,t)=>{const{__scopeRovingFocusGroup:n,orientation:r,loop:o=!1,dir:i,currentTabStopId:s,defaultCurrentTabStopId:a,onCurrentTabStopIdChange:l,onEntryFocus:c,preventScrollOnEntryFocus:u=!1,...d}=e,f=qe.useRef(null),p=m$(t,f),h=$$(i),[v=null,m]=w$({prop:s,defaultProp:a,onChange:l}),[g,y]=qe.useState(!1),b=b$(c),w=iR(n),x=qe.useRef(!1),[k,E]=qe.useState(0);return qe.useEffect((()=>{const e=f.current;if(e)return e.addEventListener(tR,b),()=>e.removeEventListener(tR,b)}),[b]),nt.jsx(cR,{scope:n,orientation:r,dir:h,loop:o,currentTabStopId:v,onItemFocus:qe.useCallback((e=>m(e)),[m]),onItemShiftTab:qe.useCallback((()=>y(!0)),[]),onFocusableItemAdd:qe.useCallback((()=>E((e=>e+1))),[]),onFocusableItemRemove:qe.useCallback((()=>E((e=>e-1))),[]),children:nt.jsx(_$.div,{tabIndex:g||0===k?-1:0,"data-orientation":r,...d,ref:p,style:{outline:"none",...e.style},onMouseDown:p$(e.onMouseDown,(()=>{x.current=!0})),onFocus:p$(e.onFocus,(e=>{const t=!x.current;if(e.target===e.currentTarget&&t&&!g){const t=new CustomEvent(tR,nR);if(e.currentTarget.dispatchEvent(t),!t.defaultPrevented){const e=w().filter((e=>e.focusable));mR([e.find((e=>e.active)),e.find((e=>e.id===v)),...e].filter(Boolean).map((e=>e.ref.current)),u)}}x.current=!1})),onBlur:p$(e.onBlur,(()=>y(!1)))})})})),pR="RovingFocusGroupItem",hR=qe.forwardRef(((e,t)=>{const{__scopeRovingFocusGroup:n,focusable:r=!0,active:o=!1,tabStopId:i,...s}=e,a=ej(),l=i||a,c=uR(pR,n),u=c.currentTabStopId===l,d=iR(n),{onFocusableItemAdd:f,onFocusableItemRemove:p}=c;return qe.useEffect((()=>{if(r)return f(),()=>p()}),[r,f,p]),nt.jsx(oR.ItemSlot,{scope:n,id:l,focusable:r,active:o,children:nt.jsx(_$.span,{tabIndex:u?0:-1,"data-orientation":c.orientation,...s,ref:t,onMouseDown:p$(e.onMouseDown,(e=>{r?c.onItemFocus(l):e.preventDefault()})),onFocus:p$(e.onFocus,(()=>c.onItemFocus(l))),onKeyDown:p$(e.onKeyDown,(e=>{if("Tab"===e.key&&e.shiftKey)return void c.onItemShiftTab();if(e.target!==e.currentTarget)return;const t=function(e,t,n){const r=function(e,t){return"rtl"!==t?e:"ArrowLeft"===e?"ArrowRight":"ArrowRight"===e?"ArrowLeft":e}(e.key,n);return"vertical"===t&&["ArrowLeft","ArrowRight"].includes(r)||"horizontal"===t&&["ArrowUp","ArrowDown"].includes(r)?void 0:vR[r]}(e,c.orientation,c.dir);if(void 0!==t){if(e.metaKey||e.ctrlKey||e.altKey||e.shiftKey)return;e.preventDefault();let o=d().filter((e=>e.focusable)).map((e=>e.ref.current));if("last"===t)o.reverse();else if("prev"===t||"next"===t){"prev"===t&&o.reverse();const i=o.indexOf(e.currentTarget);o=c.loop?(r=i+1,(n=o).map(((e,t)=>n[(r+t)%n.length]))):o.slice(i+1)}setTimeout((()=>mR(o)))}var n,r}))})})}));hR.displayName=pR;var vR={ArrowLeft:"prev",ArrowUp:"prev",ArrowRight:"next",ArrowDown:"next",PageUp:"first",Home:"first",PageDown:"last",End:"last"};function mR(e,t=!1){const n=document.activeElement;for(const r of e){if(r===n)return;if(r.focus({preventScroll:t}),document.activeElement!==n)return}}var gR=dR,yR=hR,bR=new WeakMap,wR=new WeakMap,xR={},kR=0,ER=function(e){return e&&(e.host||ER(e.parentNode))},SR=function(e,t,n,r){var o=function(e,t){return t.map((function(t){if(e.contains(t))return t;var n=ER(t);return n&&e.contains(n)?n:(console.error("aria-hidden",t,"in not contained inside",e,". Doing nothing"),null)})).filter((function(e){return Boolean(e)}))}(t,Array.isArray(e)?e:[e]);xR[n]||(xR[n]=new WeakMap);var i=xR[n],s=[],a=new Set,l=new Set(o),c=function(e){e&&!a.has(e)&&(a.add(e),c(e.parentNode))};o.forEach(c);var u=function(e){e&&!l.has(e)&&Array.prototype.forEach.call(e.children,(function(e){if(a.has(e))u(e);else try{var t=e.getAttribute(r),o=null!==t&&"false"!==t,l=(bR.get(e)||0)+1,c=(i.get(e)||0)+1;bR.set(e,l),i.set(e,c),s.push(e),1===l&&o&&wR.set(e,!0),1===c&&e.setAttribute(n,"true"),o||e.setAttribute(r,"true")}catch(d){console.error("aria-hidden: cannot operate on ",e,d)}}))};return u(t),a.clear(),kR++,function(){s.forEach((function(e){var t=bR.get(e)-1,o=i.get(e)-1;bR.set(e,t),i.set(e,o),t||(wR.has(e)||e.removeAttribute(r),wR.delete(e)),o||e.removeAttribute(n)})),--kR||(bR=new WeakMap,bR=new WeakMap,wR=new WeakMap,xR={})}},CR=function(e,t,n){void 0===n&&(n="data-aria-hidden");var r=Array.from(Array.isArray(e)?e:[e]),o=function(e){return"undefined"==typeof document?null:(Array.isArray(e)?e[0]:e).ownerDocument.body}(e);return o?(r.push.apply(r,Array.from(o.querySelectorAll("[aria-live]"))),SR(r,o,n,"aria-hidden")):function(){return null}},_R="right-scroll-bar-position",AR="width-before-scroll-bar";function NR(e,t){return"function"==typeof e?e(t):e&&(e.current=t),e}var PR="undefined"!=typeof window?qe.useLayoutEffect:qe.useEffect,$R=new WeakMap;function jR(e,t){var n,r,o,i=(n=null,r=function(t){return e.forEach((function(e){return NR(e,t)}))},(o=qe.useState((function(){return{value:n,callback:r,facade:{get current(){return o.value},set current(e){var t=o.value;t!==e&&(o.value=e,o.callback(e,t))}}}}))[0]).callback=r,o.facade);return PR((function(){var t=$R.get(i);if(t){var n=new Set(t),r=new Set(e),o=i.current;n.forEach((function(e){r.has(e)||NR(e,null)})),r.forEach((function(e){n.has(e)||NR(e,o)}))}$R.set(i,e)}),[e]),i}function OR(e){return e}var RR=function(e){var t=e.sideCar,n=Qy(e,["sideCar"]);if(!t)throw new Error("Sidecar: please provide `sideCar` property to import the right car");var r=t.read();if(!r)throw new Error("Sidecar medium not found");return qe.createElement(r,Ky({},n))};RR.isSideCarExport=!0;var MR=function(e){void 0===e&&(e={});var t=function(e,t){void 0===t&&(t=OR);var n=[],r=!1;return{read:function(){if(r)throw new Error("Sidecar: could not `read` from an `assigned` medium. `read` could be used only with `useMedium`.");return n.length?n[n.length-1]:e},useMedium:function(e){var o=t(e,r);return n.push(o),function(){n=n.filter((function(e){return e!==o}))}},assignSyncMedium:function(e){for(r=!0;n.length;){var t=n;n=[],t.forEach(e)}n={push:function(t){return e(t)},filter:function(){return n}}},assignMedium:function(e){r=!0;var t=[];if(n.length){var o=n;n=[],o.forEach(e),t=n}var i=function(){var n=t;t=[],n.forEach(e)},s=function(){return Promise.resolve().then(i)};s(),n={push:function(e){t.push(e),s()},filter:function(e){return t=t.filter(e),n}}}}}(null);return t.options=Ky({async:!0,ssr:!1},e),t}(),TR=function(){},LR=qe.forwardRef((function(e,t){var n=qe.useRef(null),r=qe.useState({onScrollCapture:TR,onWheelCapture:TR,onTouchMoveCapture:TR}),o=r[0],i=r[1],s=e.forwardProps,a=e.children,l=e.className,c=e.removeScrollBar,u=e.enabled,d=e.shards,f=e.sideCar,p=e.noIsolation,h=e.inert,v=e.allowPinchZoom,m=e.as,g=void 0===m?"div":m,y=e.gapMode,b=Qy(e,["forwardProps","children","className","removeScrollBar","enabled","shards","sideCar","noIsolation","inert","allowPinchZoom","as","gapMode"]),w=f,x=jR([n,t]),k=Ky(Ky({},b),o);return qe.createElement(qe.Fragment,null,u&&qe.createElement(w,{sideCar:MR,removeScrollBar:c,shards:d,noIsolation:p,inert:h,setCallbacks:i,allowPinchZoom:!!v,lockRef:n,gapMode:y}),s?qe.cloneElement(qe.Children.only(a),Ky(Ky({},k),{ref:x})):qe.createElement(g,Ky({},k,{className:l,ref:x}),a))}));LR.defaultProps={enabled:!0,removeScrollBar:!0,inert:!1},LR.classNames={fullWidth:AR,zeroRight:_R};function IR(){if(!document)return null;var e=document.createElement("style");e.type="text/css";var t=function(){if("undefined"!=typeof __webpack_nonce__)return __webpack_nonce__}();return t&&e.setAttribute("nonce",t),e}var zR=function(){var e=0,t=null;return{add:function(n){var r,o;0==e&&(t=IR())&&(o=n,(r=t).styleSheet?r.styleSheet.cssText=o:r.appendChild(document.createTextNode(o)),function(e){(document.head||document.getElementsByTagName("head")[0]).appendChild(e)}(t)),e++},remove:function(){! --e&&t&&(t.parentNode&&t.parentNode.removeChild(t),t=null)}}},DR=function(){var e,t=(e=zR(),function(t,n){qe.useEffect((function(){return e.add(t),function(){e.remove()}}),[t&&n])});return function(e){var n=e.styles,r=e.dynamic;return t(n,r),null}},BR={left:0,top:0,right:0,gap:0},UR=function(e){return parseInt(e||"",10)||0},FR=function(e){if(void 0===e&&(e="margin"),"undefined"==typeof window)return BR;var t=function(e){var t=window.getComputedStyle(document.body),n=t["padding"===e?"paddingLeft":"marginLeft"],r=t["padding"===e?"paddingTop":"marginTop"],o=t["padding"===e?"paddingRight":"marginRight"];return[UR(n),UR(r),UR(o)]}(e),n=document.documentElement.clientWidth,r=window.innerWidth;return{left:t[0],top:t[1],right:t[2],gap:Math.max(0,r-n+t[2]-t[0])}},HR=DR(),VR="data-scroll-locked",WR=function(e,t,n,r){var o=e.left,i=e.top,s=e.right,a=e.gap;return void 0===n&&(n="margin"),"\n .".concat("with-scroll-bars-hidden"," {\n overflow: hidden ").concat(r,";\n padding-right: ").concat(a,"px ").concat(r,";\n }\n body[").concat(VR,"] {\n overflow: hidden ").concat(r,";\n overscroll-behavior: contain;\n ").concat([t&&"position: relative ".concat(r,";"),"margin"===n&&"\n padding-left: ".concat(o,"px;\n padding-top: ").concat(i,"px;\n padding-right: ").concat(s,"px;\n margin-left:0;\n margin-top:0;\n margin-right: ").concat(a,"px ").concat(r,";\n "),"padding"===n&&"padding-right: ".concat(a,"px ").concat(r,";")].filter(Boolean).join(""),"\n }\n \n .").concat(_R," {\n right: ").concat(a,"px ").concat(r,";\n }\n \n .").concat(AR," {\n margin-right: ").concat(a,"px ").concat(r,";\n }\n \n .").concat(_R," .").concat(_R," {\n right: 0 ").concat(r,";\n }\n \n .").concat(AR," .").concat(AR," {\n margin-right: 0 ").concat(r,";\n }\n \n body[").concat(VR,"] {\n ").concat("--removed-body-scroll-bar-size",": ").concat(a,"px;\n }\n")},qR=function(){var e=parseInt(document.body.getAttribute(VR)||"0",10);return isFinite(e)?e:0},KR=function(e){var t=e.noRelative,n=e.noImportant,r=e.gapMode,o=void 0===r?"margin":r;qe.useEffect((function(){return document.body.setAttribute(VR,(qR()+1).toString()),function(){var e=qR()-1;e<=0?document.body.removeAttribute(VR):document.body.setAttribute(VR,e.toString())}}),[]);var i=qe.useMemo((function(){return FR(o)}),[o]);return qe.createElement(HR,{styles:WR(i,!t,o,n?"":"!important")})},QR=!1;if("undefined"!=typeof window)try{var YR=Object.defineProperty({},"passive",{get:function(){return QR=!0,!0}});window.addEventListener("test",YR,YR),window.removeEventListener("test",YR,YR)}catch(aI){QR=!1}var GR=!!QR&&{passive:!1},JR=function(e,t){if(!(e instanceof Element))return!1;var n=window.getComputedStyle(e);return"hidden"!==n[t]&&!(n.overflowY===n.overflowX&&!function(e){return"TEXTAREA"===e.tagName}(e)&&"visible"===n[t])},XR=function(e,t){var n=t.ownerDocument,r=t;do{if("undefined"!=typeof ShadowRoot&&r instanceof ShadowRoot&&(r=r.host),ZR(e,r)){var o=eM(e,r);if(o[1]>o[2])return!0}r=r.parentNode}while(r&&r!==n.body);return!1},ZR=function(e,t){return"v"===e?function(e){return JR(e,"overflowY")}(t):function(e){return JR(e,"overflowX")}(t)},eM=function(e,t){return"v"===e?[(n=t).scrollTop,n.scrollHeight,n.clientHeight]:function(e){return[e.scrollLeft,e.scrollWidth,e.clientWidth]}(t);var n},tM=function(e){return"changedTouches"in e?[e.changedTouches[0].clientX,e.changedTouches[0].clientY]:[0,0]},nM=function(e){return[e.deltaX,e.deltaY]},rM=function(e){return e&&"current"in e?e.current:e},oM=function(e){return"\n .block-interactivity-".concat(e," {pointer-events: none;}\n .allow-interactivity-").concat(e," {pointer-events: all;}\n")},iM=0,sM=[];function aM(e){for(var t=null;null!==e;)e instanceof ShadowRoot&&(t=e.host,e=e.host),e=e.parentNode;return t}const lM=(cM=function(e){var t=qe.useRef([]),n=qe.useRef([0,0]),r=qe.useRef(),o=qe.useState(iM++)[0],i=qe.useState(DR)[0],s=qe.useRef(e);qe.useEffect((function(){s.current=e}),[e]),qe.useEffect((function(){if(e.inert){document.body.classList.add("block-interactivity-".concat(o));var t=Zy([e.lockRef.current],(e.shards||[]).map(rM),!0).filter(Boolean);return t.forEach((function(e){return e.classList.add("allow-interactivity-".concat(o))})),function(){document.body.classList.remove("block-interactivity-".concat(o)),t.forEach((function(e){return e.classList.remove("allow-interactivity-".concat(o))}))}}}),[e.inert,e.lockRef.current,e.shards]);var a=qe.useCallback((function(e,t){if("touches"in e&&2===e.touches.length||"wheel"===e.type&&e.ctrlKey)return!s.current.allowPinchZoom;var o,i=tM(e),a=n.current,l="deltaX"in e?e.deltaX:a[0]-i[0],c="deltaY"in e?e.deltaY:a[1]-i[1],u=e.target,d=Math.abs(l)>Math.abs(c)?"h":"v";if("touches"in e&&"h"===d&&"range"===u.type)return!1;var f=XR(d,u);if(!f)return!0;if(f?o=d:(o="v"===d?"h":"v",f=XR(d,u)),!f)return!1;if(!r.current&&"changedTouches"in e&&(l||c)&&(r.current=o),!o)return!0;var p=r.current||o;return function(e,t,n,r,o){var i=function(e,t){return"h"===e&&"rtl"===t?-1:1}(e,window.getComputedStyle(t).direction),s=i*r,a=n.target,l=t.contains(a),c=!1,u=s>0,d=0,f=0;do{var p=eM(e,a),h=p[0],v=p[1]-p[2]-i*h;(h||v)&&ZR(e,a)&&(d+=v,f+=h),a=a instanceof ShadowRoot?a.host:a.parentNode}while(!l&&a!==document.body||l&&(t.contains(a)||t===a));return u&&(Math.abs(d)<1||!o)?c=!0:u||!(Math.abs(f)<1)&&o||(c=!0),c}(p,t,e,"h"===p?l:c,!0)}),[]),l=qe.useCallback((function(e){var n=e;if(sM.length&&sM[sM.length-1]===i){var r="deltaY"in n?nM(n):tM(n),o=t.current.filter((function(e){return e.name===n.type&&(e.target===n.target||n.target===e.shadowParent)&&(t=e.delta,o=r,t[0]===o[0]&&t[1]===o[1]);var t,o}))[0];if(o&&o.should)n.cancelable&&n.preventDefault();else if(!o){var l=(s.current.shards||[]).map(rM).filter(Boolean).filter((function(e){return e.contains(n.target)}));(l.length>0?a(n,l[0]):!s.current.noIsolation)&&n.cancelable&&n.preventDefault()}}}),[]),c=qe.useCallback((function(e,n,r,o){var i={name:e,delta:n,target:r,should:o,shadowParent:aM(r)};t.current.push(i),setTimeout((function(){t.current=t.current.filter((function(e){return e!==i}))}),1)}),[]),u=qe.useCallback((function(e){n.current=tM(e),r.current=void 0}),[]),d=qe.useCallback((function(t){c(t.type,nM(t),t.target,a(t,e.lockRef.current))}),[]),f=qe.useCallback((function(t){c(t.type,tM(t),t.target,a(t,e.lockRef.current))}),[]);qe.useEffect((function(){return sM.push(i),e.setCallbacks({onScrollCapture:d,onWheelCapture:d,onTouchMoveCapture:f}),document.addEventListener("wheel",l,GR),document.addEventListener("touchmove",l,GR),document.addEventListener("touchstart",u,GR),function(){sM=sM.filter((function(e){return e!==i})),document.removeEventListener("wheel",l,GR),document.removeEventListener("touchmove",l,GR),document.removeEventListener("touchstart",u,GR)}}),[]);var p=e.removeScrollBar,h=e.inert;return qe.createElement(qe.Fragment,null,h?qe.createElement(i,{styles:oM(o)}):null,p?qe.createElement(KR,{gapMode:e.gapMode}):null)},MR.useMedium(cM),RR);var cM,uM=qe.forwardRef((function(e,t){return qe.createElement(LR,Ky({},e,{ref:t,sideCar:lM}))}));uM.classNames=LR.classNames;var dM=["Enter"," "],fM=["ArrowUp","PageDown","End"],pM=["ArrowDown","PageUp","Home",...fM],hM={ltr:[...dM,"ArrowRight"],rtl:[...dM,"ArrowLeft"]},vM={ltr:["ArrowLeft"],rtl:["ArrowRight"]},mM="Menu",[gM,yM,bM]=N$(mM),[wM,xM]=g$(mM,[bM,OO,lR]),kM=OO(),EM=lR(),[SM,CM]=wM(mM),[_M,AM]=wM(mM),NM=e=>{const{__scopeMenu:t,open:n=!1,children:r,dir:o,onOpenChange:i,modal:s=!0}=e,a=kM(t),[l,c]=qe.useState(null),u=qe.useRef(!1),d=b$(i),f=$$(o);return qe.useEffect((()=>{const e=()=>{u.current=!0,document.addEventListener("pointerdown",t,{capture:!0,once:!0}),document.addEventListener("pointermove",t,{capture:!0,once:!0})},t=()=>u.current=!1;return document.addEventListener("keydown",e,{capture:!0}),()=>{document.removeEventListener("keydown",e,{capture:!0}),document.removeEventListener("pointerdown",t,{capture:!0}),document.removeEventListener("pointermove",t,{capture:!0})}}),[]),nt.jsx(QO,{...a,children:nt.jsx(SM,{scope:t,open:n,onOpenChange:d,content:l,onContentChange:c,children:nt.jsx(_M,{scope:t,onClose:qe.useCallback((()=>d(!1)),[d]),isUsingKeyboardRef:u,dir:f,modal:s,children:r})})})};NM.displayName=mM;var PM=qe.forwardRef(((e,t)=>{const{__scopeMenu:n,...r}=e,o=kM(n);return nt.jsx(YO,{...o,...r,ref:t})}));PM.displayName="MenuAnchor";var $M="MenuPortal",[jM,OM]=wM($M,{forceMount:void 0}),RM=e=>{const{__scopeMenu:t,forceMount:n,children:r,container:o}=e,i=CM($M,t);return nt.jsx(jM,{scope:t,forceMount:n,children:nt.jsx(ZO,{present:n||i.open,children:nt.jsx(XO,{asChild:!0,container:o,children:r})})})};RM.displayName=$M;var MM="MenuContent",[TM,LM]=wM(MM),IM=qe.forwardRef(((e,t)=>{const n=OM(MM,e.__scopeMenu),{forceMount:r=n.forceMount,...o}=e,i=CM(MM,e.__scopeMenu),s=AM(MM,e.__scopeMenu);return nt.jsx(gM.Provider,{scope:e.__scopeMenu,children:nt.jsx(ZO,{present:r||i.open,children:nt.jsx(gM.Slot,{scope:e.__scopeMenu,children:s.modal?nt.jsx(zM,{...o,ref:t}):nt.jsx(DM,{...o,ref:t})})})})})),zM=qe.forwardRef(((e,t)=>{const n=CM(MM,e.__scopeMenu),r=qe.useRef(null),o=m$(t,r);return qe.useEffect((()=>{const e=r.current;if(e)return CR(e)}),[]),nt.jsx(BM,{...e,ref:o,trapFocus:n.open,disableOutsidePointerEvents:n.open,disableOutsideScroll:!0,onFocusOutside:p$(e.onFocusOutside,(e=>e.preventDefault()),{checkForDefaultPrevented:!1}),onDismiss:()=>n.onOpenChange(!1)})})),DM=qe.forwardRef(((e,t)=>{const n=CM(MM,e.__scopeMenu);return nt.jsx(BM,{...e,ref:t,trapFocus:!1,disableOutsidePointerEvents:!1,disableOutsideScroll:!1,onDismiss:()=>n.onOpenChange(!1)})})),BM=qe.forwardRef(((e,t)=>{const{__scopeMenu:n,loop:r=!1,trapFocus:o,onOpenAutoFocus:i,onCloseAutoFocus:s,disableOutsidePointerEvents:a,onEntryFocus:l,onEscapeKeyDown:c,onPointerDownOutside:u,onFocusOutside:d,onInteractOutside:f,onDismiss:p,disableOutsideScroll:h,...v}=e,m=CM(MM,n),g=AM(MM,n),y=kM(n),b=EM(n),w=yM(n),[x,k]=qe.useState(null),E=qe.useRef(null),S=m$(t,E,m.onContentChange),C=qe.useRef(0),_=qe.useRef(""),A=qe.useRef(0),N=qe.useRef(null),P=qe.useRef("right"),$=qe.useRef(0),j=h?uM:qe.Fragment,O=h?{as:x$,allowPinchZoom:!0}:void 0,R=e=>{var t,n;const r=_.current+e,o=w().filter((e=>!e.disabled)),i=document.activeElement,s=null==(t=o.find((e=>e.ref.current===i)))?void 0:t.textValue,a=function(e,t,n){const r=t.length>1&&Array.from(t).every((e=>e===t[0])),o=r?t[0]:t,i=n?e.indexOf(n):-1;let s=(a=e,l=Math.max(i,0),a.map(((e,t)=>a[(l+t)%a.length])));var a,l;1===o.length&&(s=s.filter((e=>e!==n)));const c=s.find((e=>e.toLowerCase().startsWith(o.toLowerCase())));return c!==n?c:void 0}(o.map((e=>e.textValue)),r,s),l=null==(n=o.find((e=>e.textValue===a)))?void 0:n.ref.current;!function e(t){_.current=t,window.clearTimeout(C.current),""!==t&&(C.current=window.setTimeout((()=>e("")),1e3))}(r),l&&setTimeout((()=>l.focus()))};qe.useEffect((()=>()=>window.clearTimeout(C.current)),[]),qe.useEffect((()=>{const e=document.querySelectorAll("[data-radix-focus-guard]");return document.body.insertAdjacentElement("afterbegin",e[0]??B$()),document.body.insertAdjacentElement("beforeend",e[1]??B$()),D$++,()=>{1===D$&&document.querySelectorAll("[data-radix-focus-guard]").forEach((e=>e.remove())),D$--}}),[]);const M=qe.useCallback((e=>{var t,n;return P.current===(null==(t=N.current)?void 0:t.side)&&function(e,t){if(!t)return!1;const n={x:e.clientX,y:e.clientY};return function(e,t){const{x:n,y:r}=e;let o=!1;for(let i=0,s=t.length-1;ir!=c>r&&n<(l-e)*(r-a)/(c-a)+e&&(o=!o)}return o}(n,t)}(e,null==(n=N.current)?void 0:n.area)}),[]);return nt.jsx(TM,{scope:n,searchRef:_,onItemEnter:qe.useCallback((e=>{M(e)&&e.preventDefault()}),[M]),onItemLeave:qe.useCallback((e=>{var t;M(e)||(null==(t=E.current)||t.focus(),k(null))}),[M]),onTriggerLeave:qe.useCallback((e=>{M(e)&&e.preventDefault()}),[M]),pointerGraceTimerRef:A,onPointerGraceIntentChange:qe.useCallback((e=>{N.current=e}),[]),children:nt.jsx(j,{...O,children:nt.jsx(V$,{asChild:!0,trapped:o,onMountAutoFocus:p$(i,(e=>{var t;e.preventDefault(),null==(t=E.current)||t.focus({preventScroll:!0})})),onUnmountAutoFocus:s,children:nt.jsx(L$,{asChild:!0,disableOutsidePointerEvents:a,onEscapeKeyDown:c,onPointerDownOutside:u,onFocusOutside:d,onInteractOutside:f,onDismiss:p,children:nt.jsx(gR,{asChild:!0,...b,dir:g.dir,orientation:"vertical",loop:r,currentTabStopId:x,onCurrentTabStopIdChange:k,onEntryFocus:p$(l,(e=>{g.isUsingKeyboardRef.current||e.preventDefault()})),preventScrollOnEntryFocus:!0,children:nt.jsx(GO,{role:"menu","aria-orientation":"vertical","data-state":fT(m.open),"data-radix-menu-content":"",dir:g.dir,...y,...v,ref:S,style:{outline:"none",...v.style},onKeyDown:p$(v.onKeyDown,(e=>{const t=e.target.closest("[data-radix-menu-content]")===e.currentTarget,n=e.ctrlKey||e.altKey||e.metaKey,r=1===e.key.length;t&&("Tab"===e.key&&e.preventDefault(),!n&&r&&R(e.key));const o=E.current;if(e.target!==o)return;if(!pM.includes(e.key))return;e.preventDefault();const i=w().filter((e=>!e.disabled)).map((e=>e.ref.current));fM.includes(e.key)&&i.reverse(),function(e){const t=document.activeElement;for(const n of e){if(n===t)return;if(n.focus(),document.activeElement!==t)return}}(i)})),onBlur:p$(e.onBlur,(e=>{e.currentTarget.contains(e.target)||(window.clearTimeout(C.current),_.current="")})),onPointerMove:p$(e.onPointerMove,vT((e=>{const t=e.target,n=$.current!==e.clientX;if(e.currentTarget.contains(t)&&n){const t=e.clientX>$.current?"right":"left";P.current=t,$.current=e.clientX}})))})})})})})})}));IM.displayName=MM;var UM=qe.forwardRef(((e,t)=>{const{__scopeMenu:n,...r}=e;return nt.jsx(_$.div,{role:"group",...r,ref:t})}));UM.displayName="MenuGroup";var FM=qe.forwardRef(((e,t)=>{const{__scopeMenu:n,...r}=e;return nt.jsx(_$.div,{...r,ref:t})}));FM.displayName="MenuLabel";var HM="MenuItem",VM="menu.itemSelect",WM=qe.forwardRef(((e,t)=>{const{disabled:n=!1,onSelect:r,...o}=e,i=qe.useRef(null),s=AM(HM,e.__scopeMenu),a=LM(HM,e.__scopeMenu),l=m$(t,i),c=qe.useRef(!1);return nt.jsx(qM,{...o,ref:l,disabled:n,onClick:p$(e.onClick,(()=>{const e=i.current;if(!n&&e){const t=new CustomEvent(VM,{bubbles:!0,cancelable:!0});e.addEventListener(VM,(e=>null==r?void 0:r(e)),{once:!0}),A$(e,t),t.defaultPrevented?c.current=!1:s.onClose()}})),onPointerDown:t=>{var n;null==(n=e.onPointerDown)||n.call(e,t),c.current=!0},onPointerUp:p$(e.onPointerUp,(e=>{var t;c.current||null==(t=e.currentTarget)||t.click()})),onKeyDown:p$(e.onKeyDown,(e=>{const t=""!==a.searchRef.current;n||t&&" "===e.key||dM.includes(e.key)&&(e.currentTarget.click(),e.preventDefault())}))})}));WM.displayName=HM;var qM=qe.forwardRef(((e,t)=>{const{__scopeMenu:n,disabled:r=!1,textValue:o,...i}=e,s=LM(HM,n),a=EM(n),l=qe.useRef(null),c=m$(t,l),[u,d]=qe.useState(!1),[f,p]=qe.useState("");return qe.useEffect((()=>{const e=l.current;e&&p((e.textContent??"").trim())}),[i.children]),nt.jsx(gM.ItemSlot,{scope:n,disabled:r,textValue:o??f,children:nt.jsx(yR,{asChild:!0,...a,focusable:!r,children:nt.jsx(_$.div,{role:"menuitem","data-highlighted":u?"":void 0,"aria-disabled":r||void 0,"data-disabled":r?"":void 0,...i,ref:c,onPointerMove:p$(e.onPointerMove,vT((e=>{if(r)s.onItemLeave(e);else if(s.onItemEnter(e),!e.defaultPrevented){e.currentTarget.focus({preventScroll:!0})}}))),onPointerLeave:p$(e.onPointerLeave,vT((e=>s.onItemLeave(e)))),onFocus:p$(e.onFocus,(()=>d(!0))),onBlur:p$(e.onBlur,(()=>d(!1)))})})})})),KM=qe.forwardRef(((e,t)=>{const{checked:n=!1,onCheckedChange:r,...o}=e;return nt.jsx(tT,{scope:e.__scopeMenu,checked:n,children:nt.jsx(WM,{role:"menuitemcheckbox","aria-checked":pT(n)?"mixed":n,...o,ref:t,"data-state":hT(n),onSelect:p$(o.onSelect,(()=>null==r?void 0:r(!!pT(n)||!n)),{checkForDefaultPrevented:!1})})})}));KM.displayName="MenuCheckboxItem";var QM="MenuRadioGroup",[YM,GM]=wM(QM,{value:void 0,onValueChange:()=>{}}),JM=qe.forwardRef(((e,t)=>{const{value:n,onValueChange:r,...o}=e,i=b$(r);return nt.jsx(YM,{scope:e.__scopeMenu,value:n,onValueChange:i,children:nt.jsx(UM,{...o,ref:t})})}));JM.displayName=QM;var XM="MenuRadioItem",ZM=qe.forwardRef(((e,t)=>{const{value:n,...r}=e,o=GM(XM,e.__scopeMenu),i=n===o.value;return nt.jsx(tT,{scope:e.__scopeMenu,checked:i,children:nt.jsx(WM,{role:"menuitemradio","aria-checked":i,...r,ref:t,"data-state":hT(i),onSelect:p$(r.onSelect,(()=>{var e;return null==(e=o.onValueChange)?void 0:e.call(o,n)}),{checkForDefaultPrevented:!1})})})}));ZM.displayName=XM;var eT="MenuItemIndicator",[tT,nT]=wM(eT,{checked:!1}),rT=qe.forwardRef(((e,t)=>{const{__scopeMenu:n,forceMount:r,...o}=e,i=nT(eT,n);return nt.jsx(ZO,{present:r||pT(i.checked)||!0===i.checked,children:nt.jsx(_$.span,{...o,ref:t,"data-state":hT(i.checked)})})}));rT.displayName=eT;var oT=qe.forwardRef(((e,t)=>{const{__scopeMenu:n,...r}=e;return nt.jsx(_$.div,{role:"separator","aria-orientation":"horizontal",...r,ref:t})}));oT.displayName="MenuSeparator";var iT=qe.forwardRef(((e,t)=>{const{__scopeMenu:n,...r}=e,o=kM(n);return nt.jsx(JO,{...o,...r,ref:t})}));iT.displayName="MenuArrow";var[sT,aT]=wM("MenuSub"),lT="MenuSubTrigger",cT=qe.forwardRef(((e,t)=>{const n=CM(lT,e.__scopeMenu),r=AM(lT,e.__scopeMenu),o=aT(lT,e.__scopeMenu),i=LM(lT,e.__scopeMenu),s=qe.useRef(null),{pointerGraceTimerRef:a,onPointerGraceIntentChange:l}=i,c={__scopeMenu:e.__scopeMenu},u=qe.useCallback((()=>{s.current&&window.clearTimeout(s.current),s.current=null}),[]);return qe.useEffect((()=>u),[u]),qe.useEffect((()=>{const e=a.current;return()=>{window.clearTimeout(e),l(null)}}),[a,l]),nt.jsx(PM,{asChild:!0,...c,children:nt.jsx(qM,{id:o.triggerId,"aria-haspopup":"menu","aria-expanded":n.open,"aria-controls":o.contentId,"data-state":fT(n.open),...e,ref:v$(t,o.onTriggerChange),onClick:t=>{var r;null==(r=e.onClick)||r.call(e,t),e.disabled||t.defaultPrevented||(t.currentTarget.focus(),n.open||n.onOpenChange(!0))},onPointerMove:p$(e.onPointerMove,vT((t=>{i.onItemEnter(t),t.defaultPrevented||e.disabled||n.open||s.current||(i.onPointerGraceIntentChange(null),s.current=window.setTimeout((()=>{n.onOpenChange(!0),u()}),100))}))),onPointerLeave:p$(e.onPointerLeave,vT((e=>{var t,r;u();const o=null==(t=n.content)?void 0:t.getBoundingClientRect();if(o){const t=null==(r=n.content)?void 0:r.dataset.side,s="right"===t,l=s?-5:5,c=o[s?"left":"right"],u=o[s?"right":"left"];i.onPointerGraceIntentChange({area:[{x:e.clientX+l,y:e.clientY},{x:c,y:o.top},{x:u,y:o.top},{x:u,y:o.bottom},{x:c,y:o.bottom}],side:t}),window.clearTimeout(a.current),a.current=window.setTimeout((()=>i.onPointerGraceIntentChange(null)),300)}else{if(i.onTriggerLeave(e),e.defaultPrevented)return;i.onPointerGraceIntentChange(null)}}))),onKeyDown:p$(e.onKeyDown,(t=>{var o;const s=""!==i.searchRef.current;e.disabled||s&&" "===t.key||hM[r.dir].includes(t.key)&&(n.onOpenChange(!0),null==(o=n.content)||o.focus(),t.preventDefault())}))})})}));cT.displayName=lT;var uT="MenuSubContent",dT=qe.forwardRef(((e,t)=>{const n=OM(MM,e.__scopeMenu),{forceMount:r=n.forceMount,...o}=e,i=CM(MM,e.__scopeMenu),s=AM(MM,e.__scopeMenu),a=aT(uT,e.__scopeMenu),l=qe.useRef(null),c=m$(t,l);return nt.jsx(gM.Provider,{scope:e.__scopeMenu,children:nt.jsx(ZO,{present:r||i.open,children:nt.jsx(gM.Slot,{scope:e.__scopeMenu,children:nt.jsx(BM,{id:a.contentId,"aria-labelledby":a.triggerId,...o,ref:c,align:"start",side:"rtl"===s.dir?"left":"right",disableOutsidePointerEvents:!1,disableOutsideScroll:!1,trapFocus:!1,onOpenAutoFocus:e=>{var t;s.isUsingKeyboardRef.current&&(null==(t=l.current)||t.focus()),e.preventDefault()},onCloseAutoFocus:e=>e.preventDefault(),onFocusOutside:p$(e.onFocusOutside,(e=>{e.target!==a.trigger&&i.onOpenChange(!1)})),onEscapeKeyDown:p$(e.onEscapeKeyDown,(e=>{s.onClose(),e.preventDefault()})),onKeyDown:p$(e.onKeyDown,(e=>{var t;const n=e.currentTarget.contains(e.target),r=vM[s.dir].includes(e.key);n&&r&&(i.onOpenChange(!1),null==(t=a.trigger)||t.focus(),e.preventDefault())}))})})})})}));function fT(e){return e?"open":"closed"}function pT(e){return"indeterminate"===e}function hT(e){return pT(e)?"indeterminate":e?"checked":"unchecked"}function vT(e){return t=>"mouse"===t.pointerType?e(t):void 0}dT.displayName=uT;var mT=NM,gT=PM,yT=RM,bT=IM,wT=UM,xT=FM,kT=WM,ET=KM,ST=JM,CT=ZM,_T=rT,AT=oT,NT=iT,PT=cT,$T=dT,jT="DropdownMenu",[OT,RT]=g$(jT,[xM]),MT=xM(),[TT,LT]=OT(jT),IT=e=>{const{__scopeDropdownMenu:t,children:n,dir:r,open:o,defaultOpen:i,onOpenChange:s,modal:a=!0}=e,l=MT(t),c=qe.useRef(null),[u=!1,d]=w$({prop:o,defaultProp:i,onChange:s});return nt.jsx(TT,{scope:t,triggerId:ej(),triggerRef:c,contentId:ej(),open:u,onOpenChange:d,onOpenToggle:qe.useCallback((()=>d((e=>!e))),[d]),modal:a,children:nt.jsx(mT,{...l,open:u,onOpenChange:d,dir:r,modal:a,children:n})})};IT.displayName=jT;var zT="DropdownMenuTrigger",DT=qe.forwardRef(((e,t)=>{const{__scopeDropdownMenu:n,disabled:r=!1,...o}=e,i=LT(zT,n),s=MT(n);return nt.jsx(gT,{asChild:!0,...s,children:nt.jsx(_$.button,{type:"button",id:i.triggerId,"aria-haspopup":"menu","aria-expanded":i.open,"aria-controls":i.open?i.contentId:void 0,"data-state":i.open?"open":"closed","data-disabled":r?"":void 0,disabled:r,...o,ref:v$(t,i.triggerRef),onPointerDown:p$(e.onPointerDown,(e=>{r||0!==e.button||!1!==e.ctrlKey||(i.onOpenToggle(),i.open||e.preventDefault())})),onKeyDown:p$(e.onKeyDown,(e=>{r||(["Enter"," "].includes(e.key)&&i.onOpenToggle(),"ArrowDown"===e.key&&i.onOpenChange(!0),["Enter"," ","ArrowDown"].includes(e.key)&&e.preventDefault())}))})})}));DT.displayName=zT;var BT=e=>{const{__scopeDropdownMenu:t,...n}=e,r=MT(t);return nt.jsx(yT,{...r,...n})};BT.displayName="DropdownMenuPortal";var UT="DropdownMenuContent",FT=qe.forwardRef(((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,o=LT(UT,n),i=MT(n),s=qe.useRef(!1);return nt.jsx(bT,{id:o.contentId,"aria-labelledby":o.triggerId,...i,...r,ref:t,onCloseAutoFocus:p$(e.onCloseAutoFocus,(e=>{var t;s.current||null==(t=o.triggerRef.current)||t.focus(),s.current=!1,e.preventDefault()})),onInteractOutside:p$(e.onInteractOutside,(e=>{const t=e.detail.originalEvent,n=0===t.button&&!0===t.ctrlKey,r=2===t.button||n;o.modal&&!r||(s.current=!0)})),style:{...e.style,"--radix-dropdown-menu-content-transform-origin":"var(--radix-popper-transform-origin)","--radix-dropdown-menu-content-available-width":"var(--radix-popper-available-width)","--radix-dropdown-menu-content-available-height":"var(--radix-popper-available-height)","--radix-dropdown-menu-trigger-width":"var(--radix-popper-anchor-width)","--radix-dropdown-menu-trigger-height":"var(--radix-popper-anchor-height)"}})}));FT.displayName=UT;qe.forwardRef(((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,o=MT(n);return nt.jsx(wT,{...o,...r,ref:t})})).displayName="DropdownMenuGroup";var HT=qe.forwardRef(((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,o=MT(n);return nt.jsx(xT,{...o,...r,ref:t})}));HT.displayName="DropdownMenuLabel";var VT=qe.forwardRef(((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,o=MT(n);return nt.jsx(kT,{...o,...r,ref:t})}));VT.displayName="DropdownMenuItem";var WT=qe.forwardRef(((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,o=MT(n);return nt.jsx(ET,{...o,...r,ref:t})}));WT.displayName="DropdownMenuCheckboxItem";qe.forwardRef(((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,o=MT(n);return nt.jsx(ST,{...o,...r,ref:t})})).displayName="DropdownMenuRadioGroup";qe.forwardRef(((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,o=MT(n);return nt.jsx(CT,{...o,...r,ref:t})})).displayName="DropdownMenuRadioItem";var qT=qe.forwardRef(((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,o=MT(n);return nt.jsx(_T,{...o,...r,ref:t})}));qT.displayName="DropdownMenuItemIndicator";var KT=qe.forwardRef(((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,o=MT(n);return nt.jsx(AT,{...o,...r,ref:t})}));KT.displayName="DropdownMenuSeparator";qe.forwardRef(((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,o=MT(n);return nt.jsx(NT,{...o,...r,ref:t})})).displayName="DropdownMenuArrow";var QT=qe.forwardRef(((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,o=MT(n);return nt.jsx(PT,{...o,...r,ref:t})}));QT.displayName="DropdownMenuSubTrigger";var YT=qe.forwardRef(((e,t)=>{const{__scopeDropdownMenu:n,...r}=e,o=MT(n);return nt.jsx($T,{...o,...r,ref:t,style:{...e.style,"--radix-dropdown-menu-content-transform-origin":"var(--radix-popper-transform-origin)","--radix-dropdown-menu-content-available-width":"var(--radix-popper-available-width)","--radix-dropdown-menu-content-available-height":"var(--radix-popper-available-height)","--radix-dropdown-menu-trigger-width":"var(--radix-popper-anchor-width)","--radix-dropdown-menu-trigger-height":"var(--radix-popper-anchor-height)"}})}));YT.displayName="DropdownMenuSubContent";var GT=IT,JT=DT,XT=BT,ZT=FT,eL=HT,tL=VT,nL=WT,rL=qT,oL=KT,iL=QT,sL=YT;const aL=(...e)=>e.filter(((e,t,n)=>Boolean(e)&&""!==e.trim()&&n.indexOf(e)===t)).join(" ").trim();var lL={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};const cL=qe.forwardRef((({color:e="currentColor",size:t=24,strokeWidth:n=2,absoluteStrokeWidth:r,className:o="",children:i,iconNode:s,...a},l)=>qe.createElement("svg",{ref:l,...lL,width:t,height:t,stroke:e,strokeWidth:r?24*Number(n)/Number(t):n,className:aL("lucide",o),...a},[...s.map((([e,t])=>qe.createElement(e,t))),...Array.isArray(i)?i:[i]]))),uL=(e,t)=>{const n=qe.forwardRef((({className:n,...r},o)=>{return qe.createElement(cL,{ref:o,iconNode:t,className:aL(`lucide-${i=e,i.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase()}`,n),...r});var i}));return n.displayName=`${e}`,n},dL=uL("Check",[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]]),fL=uL("ChevronRight",[["path",{d:"m9 18 6-6-6-6",key:"mthhwq"}]]),pL=GT,hL=JT;qe.forwardRef((({className:e,inset:t,children:n,...r},o)=>nt.jsxs(iL,{ref:o,className:ap("flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",t&&"pl-8",e),...r,children:[n,nt.jsx(fL,{className:"ml-auto h-4 w-4"})]}))).displayName=iL.displayName;qe.forwardRef((({className:e,...t},n)=>nt.jsx(sL,{ref:n,className:ap("z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",e),...t}))).displayName=sL.displayName;const vL=qe.forwardRef((({className:e,sideOffset:t=4,...n},r)=>nt.jsx(XT,{children:nt.jsx(ZT,{ref:r,sideOffset:t,className:ap("z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",e),...n})})));vL.displayName=ZT.displayName;const mL=qe.forwardRef((({className:e,inset:t,...n},r)=>nt.jsx(tL,{ref:r,className:ap("relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",t&&"pl-8",e),...n})));mL.displayName=tL.displayName;qe.forwardRef((({className:e,children:t,checked:n,...r},o)=>nt.jsxs(nL,{ref:o,className:ap("relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",e),checked:n,...r,children:[nt.jsx("span",{className:"absolute left-2 flex h-3.5 w-3.5 items-center justify-center",children:nt.jsx(rL,{children:nt.jsx(dL,{className:"h-4 w-4"})})}),t]}))).displayName=nL.displayName;qe.forwardRef((({className:e,inset:t,...n},r)=>nt.jsx(eL,{ref:r,className:ap("px-2 py-1.5 text-sm font-semibold",t&&"pl-8",e),...n}))).displayName=eL.displayName;const gL=qe.forwardRef((({className:e,...t},n)=>nt.jsx(oL,{ref:n,className:ap("-mx-1 my-1 h-px bg-muted",e),...t})));gL.displayName=oL.displayName;const yL=new Set(["children","localName","ref","style","className"]),bL=new WeakMap,wL=(e,t,n,r,o)=>{const i=null==o?void 0:o[t];void 0===i?(e[t]=n,null==n&&t in HTMLElement.prototype&&e.removeAttribute(t)):n!==r&&((e,t,n)=>{let r=bL.get(e);void 0===r&&bL.set(e,r=new Map);let o=r.get(t);void 0!==n?void 0===o?(r.set(t,o={handleEvent:n}),e.addEventListener(t,o)):o.handleEvent=n:void 0!==o&&(r.delete(t),e.removeEventListener(t,o))})(e,i,n)},xL=({react:e,tagName:t,elementClass:n,events:r,displayName:o})=>{const i=new Set(Object.keys(r??{})),s=e.forwardRef(((o,s)=>{const a=e.useRef(new Map),l=e.useRef(null),c={},u={};for(const[e,t]of Object.entries(o))yL.has(e)?c["className"===e?"class":e]=t:i.has(e)||e in n.prototype?u[e]=t:c[e]=t;return e.useLayoutEffect((()=>{if(null===l.current)return;const e=new Map;for(const t in u)wL(l.current,t,o[t],a.current.get(t),r),a.current.delete(t),e.set(t,o[t]);for(const[t,n]of a.current)wL(l.current,t,void 0,n,r);a.current=e})),e.useLayoutEffect((()=>{var e;null==(e=l.current)||e.removeAttribute("defer-hydration")}),[]),c.suppressHydrationWarning=!0,e.createElement(t,{...c,ref:e.useCallback((e=>{l.current=e,"function"==typeof s?s(e):null!==s&&(s.current=e)}),[s])})}));return s.displayName=o??n.name,s};xL({tagName:"dc-connection-button",elementClass:d$,react:Ke});const kL=xL({tagName:"dc-connection-dialog",elementClass:o$,react:Ke,events:{onClose:"close"}});function EL(...e){const t=e=>e,n=(e,t)=>n=>e(t(n));return{encode:e.map((e=>e.encode)).reduceRight(n,t),decode:e.map((e=>e.decode)).reduce(n,t)}}function SL(e){return{encode:t=>{if(!Array.isArray(t)||t.length&&"number"!=typeof t[0])throw new Error("alphabet.encode input should be an array of numbers");return t.map((t=>{if(t<0||t>=e.length)throw new Error(`Digit index outside alphabet: ${t} (alphabet: ${e.length})`);return e[t]}))},decode:t=>{if(!Array.isArray(t)||t.length&&"string"!=typeof t[0])throw new Error("alphabet.decode input should be array of strings");return t.map((t=>{if("string"!=typeof t)throw new Error(`alphabet.decode: not string element=${t}`);const n=e.indexOf(t);if(-1===n)throw new Error(`Unknown letter: "${t}". Allowed: ${e}`);return n}))}}}function CL(e=""){if("string"!=typeof e)throw new Error("join separator should be string");return{encode:t=>{if(!Array.isArray(t)||t.length&&"string"!=typeof t[0])throw new Error("join.encode input should be array of strings");for(let e of t)if("string"!=typeof e)throw new Error(`join.encode: non-string input=${e}`);return t.join(e)},decode:t=>{if("string"!=typeof t)throw new Error("join.decode input should be string");return t.split(e)}}}function _L(e,t,n){if(t<2)throw new Error(`convertRadix: wrong from=${t}, base cannot be less than 2`);if(n<2)throw new Error(`convertRadix: wrong to=${n}, base cannot be less than 2`);if(!Array.isArray(e))throw new Error("convertRadix: data should be array");if(!e.length)return[];let r=0;const o=[],i=Array.from(e);for(i.forEach((e=>{if(e<0||e>=t)throw new Error(`Wrong integer: ${e}`)}));;){let e=0,s=!0;for(let o=r;o{if(!function(e){return e instanceof Uint8Array||null!=e&&"object"==typeof e&&"Uint8Array"===e.constructor.name}(t))throw new Error("radix.encode input should be Uint8Array");return _L(Array.from(t),256,e)},decode:t=>{if(!Array.isArray(t)||t.length&&"number"!=typeof t[0])throw new Error("radix.decode input should be array of numbers");return Uint8Array.from(_L(t,e,256))}}}const NL=(e=>EL(AL(58),SL(e),CL("")))("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz");function PL(e){if(!Number.isSafeInteger(e)||e<0)throw new Error(`positive integer expected, not ${e}`)}function $L(e,...t){if(!function(e){return e instanceof Uint8Array||null!=e&&"object"==typeof e&&"Uint8Array"===e.constructor.name}(e))throw new Error("Uint8Array expected");if(t.length>0&&!t.includes(e.length))throw new Error(`Uint8Array expected of length ${t}, not of length=${e.length}`)}function jL(e,t=!0){if(e.destroyed)throw new Error("Hash instance has been destroyed");if(t&&e.finished)throw new Error("Hash#digest() has already been called")}const OL=e=>new Uint32Array(e.buffer,e.byteOffset,Math.floor(e.byteLength/4)),RL=68===new Uint8Array(new Uint32Array([287454020]).buffer)[0],ML=e=>e<<24&4278190080|e<<8&16711680|e>>>8&65280|e>>>24&255,TL=RL?e=>e:e=>ML(e);function LL(e){for(let t=0;te(n).update(IL(t)).digest(),n=e({});return t.outputLen=n.outputLen,t.blockLen=n.blockLen,t.create=t=>e(t),t}const BL=new Uint8Array([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,14,10,4,8,9,15,13,6,1,12,0,2,11,7,5,3,11,8,12,0,5,2,15,13,10,14,3,6,7,1,9,4,7,9,3,1,13,12,11,14,2,6,5,10,4,0,15,8,9,0,5,7,2,4,10,15,14,1,11,12,6,8,3,13,2,12,6,10,0,11,8,3,4,13,7,5,15,14,1,9,12,5,1,15,14,13,4,10,0,7,6,3,9,2,8,11,13,11,7,14,12,1,3,9,5,0,15,4,8,6,2,10,6,15,14,9,11,3,0,8,12,2,13,7,1,4,10,5,10,2,8,4,7,6,1,5,15,11,9,14,3,12,13,0,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,14,10,4,8,9,15,13,6,1,12,0,2,11,7,5,3]);class UL extends zL{constructor(e,t,n={},r,o,i){if(super(),this.blockLen=e,this.outputLen=t,this.length=0,this.pos=0,this.finished=!1,this.destroyed=!1,PL(e),PL(t),PL(r),t<0||t>r)throw new Error("outputLen bigger than keyLen");if(void 0!==n.key&&(n.key.length<1||n.key.length>r))throw new Error(`key must be up 1..${r} byte long or undefined`);if(void 0!==n.salt&&n.salt.length!==o)throw new Error(`salt must be ${o} byte long or undefined`);if(void 0!==n.personalization&&n.personalization.length!==i)throw new Error(`personalization must be ${i} byte long or undefined`);this.buffer32=OL(this.buffer=new Uint8Array(e))}update(e){jL(this);const{blockLen:t,buffer:n,buffer32:r}=this,o=(e=IL(e)).length,i=e.byteOffset,s=e.buffer;for(let a=0;ar[t]=TL(e)))}digest(){const{buffer:e,outputLen:t}=this;this.digestInto(e);const n=e.slice(0,t);return this.destroy(),n}_cloneInto(e){const{buffer:t,length:n,finished:r,destroyed:o,outputLen:i,pos:s}=this;return e||(e=new this.constructor({dkLen:i})),e.set(...this.get()),e.length=n,e.finished=r,e.destroyed=o,e.outputLen=i,e.buffer.set(t),e.pos=s,e}}const FL=BigInt(2**32-1),HL=BigInt(32);function VL(e,t=!1){return t?{h:Number(e&FL),l:Number(e>>HL&FL)}:{h:0|Number(e>>HL&FL),l:0|Number(e&FL)}}const WL={fromBig:VL,split:function(e,t=!1){let n=new Uint32Array(e.length),r=new Uint32Array(e.length);for(let o=0;oBigInt(e>>>0)<>>0),shrSH:(e,t,n)=>e>>>n,shrSL:(e,t,n)=>e<<32-n|t>>>n,rotrSH:(e,t,n)=>e>>>n|t<<32-n,rotrSL:(e,t,n)=>e<<32-n|t>>>n,rotrBH:(e,t,n)=>e<<64-n|t>>>n-32,rotrBL:(e,t,n)=>e>>>n-32|t<<64-n,rotr32H:(e,t)=>t,rotr32L:(e,t)=>e,rotlSH:(e,t,n)=>e<>>32-n,rotlSL:(e,t,n)=>t<>>32-n,rotlBH:(e,t,n)=>t<>>64-n,rotlBL:(e,t,n)=>e<>>64-n,add:function(e,t,n,r){const o=(t>>>0)+(r>>>0);return{h:e+n+(o/2**32|0)|0,l:0|o}},add3L:(e,t,n)=>(e>>>0)+(t>>>0)+(n>>>0),add3H:(e,t,n,r)=>t+n+r+(e/2**32|0)|0,add4L:(e,t,n,r)=>(e>>>0)+(t>>>0)+(n>>>0)+(r>>>0),add4H:(e,t,n,r,o)=>t+n+r+o+(e/2**32|0)|0,add5H:(e,t,n,r,o,i)=>t+n+r+o+i+(e/2**32|0)|0,add5L:(e,t,n,r,o)=>(e>>>0)+(t>>>0)+(n>>>0)+(r>>>0)+(o>>>0)},qL=new Uint32Array([4089235720,1779033703,2227873595,3144134277,4271175723,1013904242,1595750129,2773480762,2917565137,1359893119,725511199,2600822924,4215389547,528734635,327033209,1541459225]),KL=new Uint32Array(32);function QL(e,t,n,r,o,i){const s=o[i],a=o[i+1];let l=KL[2*e],c=KL[2*e+1],u=KL[2*t],d=KL[2*t+1],f=KL[2*n],p=KL[2*n+1],h=KL[2*r],v=KL[2*r+1],m=WL.add3L(l,u,s);c=WL.add3H(m,c,d,a),l=0|m,({Dh:v,Dl:h}={Dh:v^c,Dl:h^l}),({Dh:v,Dl:h}={Dh:WL.rotr32H(v,h),Dl:WL.rotr32L(v,h)}),({h:p,l:f}=WL.add(p,f,v,h)),({Bh:d,Bl:u}={Bh:d^p,Bl:u^f}),({Bh:d,Bl:u}={Bh:WL.rotrSH(d,u,24),Bl:WL.rotrSL(d,u,24)}),KL[2*e]=l,KL[2*e+1]=c,KL[2*t]=u,KL[2*t+1]=d,KL[2*n]=f,KL[2*n+1]=p,KL[2*r]=h,KL[2*r+1]=v}function YL(e,t,n,r,o,i){const s=o[i],a=o[i+1];let l=KL[2*e],c=KL[2*e+1],u=KL[2*t],d=KL[2*t+1],f=KL[2*n],p=KL[2*n+1],h=KL[2*r],v=KL[2*r+1],m=WL.add3L(l,u,s);c=WL.add3H(m,c,d,a),l=0|m,({Dh:v,Dl:h}={Dh:v^c,Dl:h^l}),({Dh:v,Dl:h}={Dh:WL.rotrSH(v,h,16),Dl:WL.rotrSL(v,h,16)}),({h:p,l:f}=WL.add(p,f,v,h)),({Bh:d,Bl:u}={Bh:d^p,Bl:u^f}),({Bh:d,Bl:u}={Bh:WL.rotrBH(d,u,63),Bl:WL.rotrBL(d,u,63)}),KL[2*e]=l,KL[2*e+1]=c,KL[2*t]=u,KL[2*t+1]=d,KL[2*n]=f,KL[2*n+1]=p,KL[2*r]=h,KL[2*r+1]=v}class GL extends UL{constructor(e={}){super(128,void 0===e.dkLen?64:e.dkLen,e,64,16,16),this.v0l=0|qL[0],this.v0h=0|qL[1],this.v1l=0|qL[2],this.v1h=0|qL[3],this.v2l=0|qL[4],this.v2h=0|qL[5],this.v3l=0|qL[6],this.v3h=0|qL[7],this.v4l=0|qL[8],this.v4h=0|qL[9],this.v5l=0|qL[10],this.v5h=0|qL[11],this.v6l=0|qL[12],this.v6h=0|qL[13],this.v7l=0|qL[14],this.v7h=0|qL[15];const t=e.key?e.key.length:0;if(this.v0l^=this.outputLen|t<<8|65536|1<<24,e.salt){const t=OL(IL(e.salt));this.v4l^=TL(t[0]),this.v4h^=TL(t[1]),this.v5l^=TL(t[2]),this.v5h^=TL(t[3])}if(e.personalization){const t=OL(IL(e.personalization));this.v6l^=TL(t[0]),this.v6h^=TL(t[1]),this.v7l^=TL(t[2]),this.v7h^=TL(t[3])}if(e.key){const t=new Uint8Array(this.blockLen);t.set(IL(e.key)),this.update(t)}}get(){let{v0l:e,v0h:t,v1l:n,v1h:r,v2l:o,v2h:i,v3l:s,v3h:a,v4l:l,v4h:c,v5l:u,v5h:d,v6l:f,v6h:p,v7l:h,v7h:v}=this;return[e,t,n,r,o,i,s,a,l,c,u,d,f,p,h,v]}set(e,t,n,r,o,i,s,a,l,c,u,d,f,p,h,v){this.v0l=0|e,this.v0h=0|t,this.v1l=0|n,this.v1h=0|r,this.v2l=0|o,this.v2h=0|i,this.v3l=0|s,this.v3h=0|a,this.v4l=0|l,this.v4h=0|c,this.v5l=0|u,this.v5h=0|d,this.v6l=0|f,this.v6h=0|p,this.v7l=0|h,this.v7h=0|v}compress(e,t,n){this.get().forEach(((e,t)=>KL[t]=e)),KL.set(qL,16);let{h:r,l:o}=WL.fromBig(BigInt(this.length));KL[24]=qL[8]^o,KL[25]=qL[9]^r,n&&(KL[28]=~KL[28],KL[29]=~KL[29]);let i=0;const s=BL;for(let a=0;a<12;a++)QL(0,4,8,12,e,t+2*s[i++]),YL(0,4,8,12,e,t+2*s[i++]),QL(1,5,9,13,e,t+2*s[i++]),YL(1,5,9,13,e,t+2*s[i++]),QL(2,6,10,14,e,t+2*s[i++]),YL(2,6,10,14,e,t+2*s[i++]),QL(3,7,11,15,e,t+2*s[i++]),YL(3,7,11,15,e,t+2*s[i++]),QL(0,5,10,15,e,t+2*s[i++]),YL(0,5,10,15,e,t+2*s[i++]),QL(1,6,11,12,e,t+2*s[i++]),YL(1,6,11,12,e,t+2*s[i++]),QL(2,7,8,13,e,t+2*s[i++]),YL(2,7,8,13,e,t+2*s[i++]),QL(3,4,9,14,e,t+2*s[i++]),YL(3,4,9,14,e,t+2*s[i++]);this.v0l^=KL[0]^KL[16],this.v0h^=KL[1]^KL[17],this.v1l^=KL[2]^KL[18],this.v1h^=KL[3]^KL[19],this.v2l^=KL[4]^KL[20],this.v2h^=KL[5]^KL[21],this.v3l^=KL[6]^KL[22],this.v3h^=KL[7]^KL[23],this.v4l^=KL[8]^KL[24],this.v4h^=KL[9]^KL[25],this.v5l^=KL[10]^KL[26],this.v5h^=KL[11]^KL[27],this.v6l^=KL[12]^KL[28],this.v6h^=KL[13]^KL[29],this.v7l^=KL[14]^KL[30],this.v7h^=KL[15]^KL[31],KL.fill(0)}destroy(){this.destroyed=!0,this.buffer32.fill(0),this.set(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)}}const JL=DL((e=>new GL(e))),XL=(new TextEncoder).encode("SS58PRE"),ZL=e=>{try{const t=NL.decode(e),n=t.subarray(0,64&t[0]?2:1),r=t.subarray(n.length,t.length-2),o=t.subarray(n.length+r.length),i=JL(Uint8Array.of(...XL,...n,...r),{dkLen:64}).subarray(0,2);return o[0]===i[0]&&o[1]===i[1]?{isValid:!0,ss58Format:eI(n),publicKey:r.slice()}:{isValid:!1}}catch{return{isValid:!1}}},eI=e=>{const t=new DataView(e.buffer,e.byteOffset,e.byteLength);return 1===t.byteLength?t.getUint8(0):t.getUint16(0)},tI={target:{colors:[0,28,0,0,28,0,0,28,0,0,28,0,0,28,0,0,28,0,1],freq:1},cube:{colors:[0,1,3,2,4,3,0,1,3,2,4,3,0,1,3,2,4,3,5],freq:20},quazar:{colors:[1,2,3,1,2,4,5,5,4,1,2,3,1,2,4,5,5,4,0],freq:16},flower:{colors:[0,1,2,0,1,2,0,1,2,0,1,2,0,1,2,0,1,2,3],freq:32},cyclic:{colors:[0,1,2,3,4,5,0,1,2,3,4,5,0,1,2,3,4,5,6],freq:32},vmirror:{colors:[0,1,2,3,4,5,3,4,2,0,1,6,7,8,9,7,8,6,10],freq:128},hmirror:{colors:[0,1,2,3,4,5,6,7,8,9,10,8,6,7,5,3,4,2,11],freq:128}},nI=e=>{let t=0;const n=Object.values(tI).find((n=>(t+=n.freq,e{var l;const[c,u]=qe.useState(!1),[d,f]=qe.useState(i),[p,h]=qe.useState(),[v,m]=qe.useState(),[g,y]=qe.useState();qe.useEffect((()=>{if("string"==typeof e&&!e.includes("px")&&!e.includes("rem"))throw new Error("Providing a string for 'size' in Polkicon should be expressed either in 'px', 'rem' or 'em'");let t,n;if("string"==typeof e)switch(n=e.replace(/[0-9.]/g,""),n){case"px":t=parseFloat(e);break;case"rem":t=10*parseFloat(e)}else"number"==typeof e&&(t=e);var r,o;h(n?""+("px"===n?t+"px":t/10+"rem"):t),t<12&&(r=n||"number",o="px"===n?"12px":"rem"===n?"1.2rem":12,console.warn(`Polkicon: 'Size' expressed in '${r}' cannot be less than ${o}. Will be resized to minimum size.`)),t<32?(y("0rem 0.5rem"),m("0.5rem")):t>=32&&t<64?(y("1rem 0.5rem"),m("1rem")):t>=64&&t<100?(y("2rem 1rem"),m("1.5rem")):t>=100&&(y("3rem 1rem"),m("2rem"))}),[e]);const b=qe.useCallback((()=>{n&&(async e=>{try{await navigator.clipboard.writeText(e),u(!0),f(i)}catch{u(!0),f("Failed!")}})(t)}),[n,t,i]);qe.useEffect((()=>{n&&c&&setTimeout((()=>{u(!1)}),o)}),[n,c,o]);const{c:w,r:x,rroot3o2:k,ro2:E,rroot3o4:S,ro4:C,r3o4:_,z:A,rot:N,scheme:P,palette:$}=(e=>{const t=JL(new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0])),n=ZL(e);let r=n.isValid?n.publicKey:e;if(!("object"==typeof r&&r&&r instanceof Uint8Array&&32==r.length))return{};r=Uint8Array.from(JL(r)).map(((e,n)=>(e+256-t[n])%256));const o=22.4,i=o*Math.sqrt(3)/2,s=o*Math.sqrt(3)/4,a=3*o/4,l=Object.keys(tI).map((e=>tI[e].freq)).reduce(((e,t)=>e+t)),c=Math.floor(70*r[29]/256+26)%80+30,u=Math.floor((r[30]+256*r[31])%l),d=nI(u),f=Array.from(r).map(((e,t)=>{const n=(e+t%28*58)%256;if(0==n)return"#444";if(255==n)return"transparent";const r=Math.floor(n%64*360/64),o=[53,15,35,75][Math.floor(n/64)];return`hsl(${r}, ${c}%, ${o}%)`}));return{c:32,r:o,rroot3o2:i,ro2:11.2,rroot3o4:s,ro4:5.6,r3o4:a,z:5,rot:r[28]%6*3,scheme:d,palette:f}})(t),j=ZL(t).isValid?null==(l=null==P?void 0:P.colors)?void 0:l.map(((e,t)=>$[null==P?void 0:P.colors[t<18?(t+N)%18:18]])):[];let O=0;return j?nt.jsxs("div",{onClick:n?b:void 0,style:n?{cursor:c?"none":"copy",position:"relative",display:"flex",justifyContent:"center",alignItems:"center"}:{display:"flex",justifyContent:"center",alignItems:"center"},children:[nt.jsxs("svg",{id:Math.random().toString(36).substring(2,9),className:a,style:s,width:p,height:p,viewBox:"0 0 64 64",children:[nt.jsx("circle",{cx:32,cy:32,r:32,fill:r}),nt.jsx("circle",{cx:w,cy:w-x,r:A,fill:j[O++]}),nt.jsx("circle",{cx:w,cy:w-E,r:A,fill:j[O++]}),nt.jsx("circle",{cx:w-S,cy:w-_,r:A,fill:j[O++]}),nt.jsx("circle",{cx:w-k,cy:w-E,r:A,fill:j[O++]}),nt.jsx("circle",{cx:w-S,cy:w-C,r:A,fill:j[O++]}),nt.jsx("circle",{cx:w-k,cy:w,r:A,fill:j[O++]}),nt.jsx("circle",{cx:w-k,cy:w+E,r:A,fill:j[O++]}),nt.jsx("circle",{cx:w-S,cy:w+C,r:A,fill:j[O++]}),nt.jsx("circle",{cx:w-S,cy:w+_,r:A,fill:j[O++]}),nt.jsx("circle",{cx:w,cy:w+x,r:A,fill:j[O++]}),nt.jsx("circle",{cx:w,cy:w+E,r:A,fill:j[O++]}),nt.jsx("circle",{cx:w+S,cy:w+_,r:A,fill:j[O++]}),nt.jsx("circle",{cx:w+k,cy:w+E,r:A,fill:j[O++]}),nt.jsx("circle",{cx:w+S,cy:w+C,r:A,fill:j[O++]}),nt.jsx("circle",{cx:w+k,cy:w,r:A,fill:j[O++]}),nt.jsx("circle",{cx:w+k,cy:w-E,r:A,fill:j[O++]}),nt.jsx("circle",{cx:w+S,cy:w-C,r:A,fill:j[O++]}),nt.jsx("circle",{cx:w+S,cy:w-_,r:A,fill:j[O++]}),nt.jsx("circle",{cx:w,cy:w,r:A,fill:j[O++]})]}),n&&c&&nt.jsx("p",{style:{fontSize:v,fontWeight:"bold",padding:g,width:p,height:p,position:"absolute",borderRadius:"55rem",color:"white",background:"green",opacity:"80%",alignItems:"center",justifyContent:"center",display:"flex"},children:d})]}):null},oI=()=>{const{accounts:e,selectAccount:t,selectedAccount:n}=QS(),[,r]=DS(),[o,i]=qe.useState(!1);return qe.useEffect((()=>{!(null==n?void 0:n.address)&&e.length>0&&(t(e[0]),i(!1))}),[e,t,null==n?void 0:n.address]),nt.jsxs(nt.Fragment,{children:[nt.jsx("div",{className:"flex w-full justify-center",children:nt.jsxs("div",{className:"flex w-full",children:[!e.length&&nt.jsx(aC,{onClick:()=>i(!0),className:"w-full text-lg font-bold bg-pink-700 hover:bg-blue-600",children:"Connect Wallet"}),!!e.length&&nt.jsxs(pL,{children:[nt.jsx(hL,{asChild:!0,children:nt.jsxs(aC,{variant:"outline",size:"default",className:"cursor-pointer overflow-hidden w-full flex items-center justify-center gap-2",children:[nt.jsx(rI,{size:36,address:(null==n?void 0:n.address)||"",className:"mr-2",outerColor:"transparent"}),null==n?void 0:n.name,nt.jsx(cC,{className:"ml-2 h-4 w-4",isOpen:!1})]})}),nt.jsxs(vL,{className:"max-h-[calc(100vh-5rem)] overflow-auto",children:[e.map(((n,r)=>nt.jsxs(qe.Fragment,{children:[nt.jsxs(mL,{className:"cursor-pointer",onClick:()=>t(n),children:[nt.jsx(rI,{size:28,address:n.address||"",className:"mr-2",outerColor:"transparent"}),n.name]},n.address),r!==e.length-1&&nt.jsx(gL,{})]},n.address))),nt.jsx(mL,{className:"cursor-pointer",onClick:()=>{i(!0)},children:"Show wallets"},"show"),nt.jsx(mL,{className:"cursor-pointer",onClick:()=>{r(),t(void 0)},children:"Disconnect"},"logout")]})]})]})}),nt.jsx(kL,{open:o,onClose:()=>i(!1)})]})};function iI(){return nt.jsx("div",{className:"flex items-center justify-center mt-6",children:nt.jsxs(up,{className:"w-full max-w-xl",children:[nt.jsx(dp,{children:nt.jsxs(fp,{className:"text-4xl text-center flex items-center justify-center gap-2",children:[nt.jsx(sI,{}),nt.jsxs("span",{children:["CLI: ",nt.jsx("span",{className:"font-light",children:"Signing Portal"})]})]})}),nt.jsx(pp,{className:"grid gap-4",children:nt.jsx(PS,{config:f$,children:nt.jsx(KS,{children:nt.jsxs("div",{className:"overflow-auto break-words whitespace-pre-wrap",children:[nt.jsx(oI,{}),nt.jsx(yC,{})]})})})})]})})}const sI=()=>nt.jsxs("svg",{xmlns:"http://www.w3.org/2000/svg",width:"100",height:"100",fill:"none",viewBox:"0 0 512 512",children:[nt.jsxs("g",{clipPath:"url(#clip0_873_174)",children:[nt.jsx("rect",{width:"512",height:"512",fill:"#1C0533",rx:"256"}),nt.jsx("rect",{width:"512",height:"512",fill:"url(#paint0_radial_873_174)",fillOpacity:"0.8",rx:"256"}),nt.jsx("rect",{width:"512",height:"512",fill:"url(#paint1_radial_873_174)",fillOpacity:"0.6",rx:"256"}),nt.jsx("mask",{id:"mask0_873_174",width:"1428",height:"1351",x:"-429",y:"-502",maskUnits:"userSpaceOnUse",style:{maskType:"alpha"},children:nt.jsx("path",{fill:"#D9D9D9",d:"m127.637-501.289 871.274 824.192-14.25 13.48-871.273-824.192zM88.96-464.701l871.272 824.192-14.249 13.48L74.71-451.221zM50.281-428.113l871.273 824.192-14.249 13.48L36.032-414.633zM11.603-391.525l871.273 824.192-14.249 13.48L-2.646-378.045zM-27.075-354.937l871.273 824.192-14.249 13.48-871.273-824.192zM-65.753-318.349 805.52 505.843l-14.249 13.48-871.273-824.192zM-104.431-281.761l871.273 824.192-14.249 13.48-871.273-824.192zM-143.109-245.173l871.273 824.192-14.249 13.48-871.273-824.192zM-181.787-208.585l871.273 824.192-14.249 13.48-871.273-824.192zM-220.465-171.997l871.273 824.192-14.249 13.48-871.273-824.192zM-259.143-135.409 612.13 688.783l-14.249 13.48-871.273-824.192zM-297.821-98.821 573.452 725.37l-14.249 13.48L-312.07-85.341zM-336.499-62.233l871.273 824.192-14.249 13.48-871.273-824.192zM-375.177-25.645l871.273 824.192-14.249 13.479-871.273-824.191zM-413.855 10.943l871.273 824.192-14.249 13.48-871.273-824.192z"})}),nt.jsx("g",{mask:"url(#mask0_873_174)",children:nt.jsx("path",{fill:"#E6007A",d:"M511.169 254.929C511.169 396.905 396.739 512 255.584 512 114.428 512-.001 396.905-.001 254.929S114.428-2.142 255.584-2.142c141.155 0 255.585 115.095 255.585 257.071"})}),nt.jsx("path",{fill:"#2B0532",d:"M183.804 160.44c-12.752-9.296-27.346-13.272-41.94-13.272h-89.63L5.63 367.567h117.053l9.399-44.802a86.4 86.4 0 0 0 28.32-8.371 104 104 0 0 0 4.133-2.155 97.7 97.7 0 0 0 14.779 24.342c8.258 9.967 18.342 17.785 29.802 23.435l.003.002.397.195.4.186c12.688 5.89 26.181 8.385 39.713 8.385 13.588 0 26.877-2.295 39.662-6.916a127 127 0 0 0 17.54-7.88l-2.871 13.579h117.052l9.4-44.802a86.4 86.4 0 0 0 28.32-8.371 101.4 101.4 0 0 0 28.939-21.348l.01-.011.159-.168.154-.167.003-.003c8.092-8.766 14.613-18.702 19.34-29.722 4.865-11.344 7.542-23.439 7.542-36.022 0-12.506-2.649-24.626-7.879-35.976-5.493-12.224-13.86-22.642-24.867-30.537-12.752-9.296-27.346-13.272-41.94-13.272h-89.631l-5.24 24.781c-7.026-6.908-15.096-12.504-24.009-16.758-12.764-6.233-26.404-8.834-40.034-8.834-13.589 0-26.878 2.295-39.662 6.916-12.145 4.39-23.444 10.507-33.804 18.268a133 133 0 0 0-7.099 5.68 74.6 74.6 0 0 0-16.911-16.781"}),nt.jsx("path",{fill:"#E6007A",fillRule:"evenodd",d:"m229.569 331.163.063.031.064.03c7.896 3.665 16.711 5.426 26.323 5.426 9.96 0 19.59-1.672 28.858-5.022a94.6 94.6 0 0 0 25.4-13.857c7.69-5.873 14.403-12.621 20.12-20.24l.016-.022.016-.022c5.708-7.75 10.156-16.036 13.319-24.849 3.326-9.027 5.005-18.25 5.005-27.639 0-8.458-1.303-16.601-3.928-24.399-2.514-8.002-6.373-15.177-11.585-21.468-5.277-6.369-11.756-11.363-19.365-14.972-7.797-3.835-16.58-5.669-26.205-5.669-9.961 0-19.591 1.673-28.858 5.022-9.165 3.313-17.687 7.924-25.551 13.819-7.719 5.752-14.51 12.512-20.362 20.265l-.01.014a97.4 97.4 0 0 0-13.544 24.867l-.015.04-.014.041c-3.181 9.016-4.784 18.219-4.784 27.583 0 8.482 1.311 16.702 3.94 24.634l.005.016.005.016c2.653 7.855 6.575 14.936 11.766 21.201 5.269 6.361 11.733 11.413 19.321 15.154m58.163-55.148.01-.015c2.998-4.343 5.197-8.955 6.622-13.852 1.467-5.04 2.176-9.872 2.176-14.513 0-4.335-.697-8.177-2.012-11.592l-.041-.109-.038-.11c-1.121-3.279-2.866-5.693-5.197-7.475-2.026-1.548-5.053-2.613-9.646-2.613-5.335 0-9.796 1.243-13.579 3.569l-.053.032-.053.031c-4.052 2.378-7.426 5.537-10.156 9.541a52.3 52.3 0 0 0-6.661 13.745c-1.454 5.016-2.157 9.826-2.157 14.447 0 4.358.638 8.321 1.862 11.932 1.246 3.306 3.081 5.852 5.484 7.803 2.047 1.543 5.008 2.569 9.387 2.569 5.495 0 10.017-1.255 13.78-3.568 4.107-2.524 7.515-5.779 10.262-9.808zM171.542 186.53c-6.663-4.89-14.525-7.228-23.288-7.228H84.491L51.478 335.434h51.685l9.273-44.204h15.658c8.698 0 16.935-1.893 24.636-5.69a69.4 69.4 0 0 0 19.864-14.652l.025-.027.025-.027c5.695-6.169 10.18-13.036 13.418-20.587 3.261-7.602 4.93-15.381 4.93-23.294 0-7.901-1.665-15.457-4.983-22.605-3.247-7.286-8.082-13.265-14.467-17.818m-35.967 55.601.023-.03c.765-1.001 1.462-2.391 1.969-4.328l.037-.142.044-.141c.574-1.827.929-4.221.929-7.293 0-2.333-.311-3.426-.466-3.769a4 4 0 0 0-.222-.434l-.027-.045-.013-.01h-11.5l-3.905 18.655h10.249c.063-.013.306-.078.757-.35.511-.309 1.235-.919 2.103-2.084zM469.872 186.53c-6.663-4.89-14.525-7.228-23.289-7.228h-63.762l-33.014 156.132h51.686l9.273-44.204h15.658c8.698 0 16.935-1.893 24.636-5.69a69.4 69.4 0 0 0 19.863-14.652l.026-.027.024-.027c5.696-6.169 10.18-13.036 13.419-20.587 3.26-7.602 4.93-15.381 4.93-23.294 0-7.901-1.665-15.457-4.983-22.605-3.247-7.286-8.082-13.265-14.467-17.818m-35.967 55.601.023-.03c.765-1.001 1.462-2.391 1.968-4.328l.038-.142.044-.141c.574-1.827.929-4.221.929-7.293 0-2.333-.311-3.426-.466-3.769a4 4 0 0 0-.222-.434l-.027-.045-.014-.01h-11.5l-3.904 18.655h10.249c.063-.013.306-.078.756-.35.511-.309 1.235-.919 2.104-2.084z",clipRule:"evenodd"}),nt.jsxs("mask",{id:"path-6-outside-1_873_174",width:"435",height:"156",x:"35.823",y:"166.788",fill:"#000",maskUnits:"userSpaceOnUse",children:[nt.jsx("path",{fill:"#fff",d:"M35.823 166.788h435v156h-435z"}),nt.jsx("path",{fillRule:"evenodd",d:"M68.98 168.599h62.13c8.395 0 15.832 2.234 22.11 6.846 6.094 4.342 10.707 10.041 13.812 17.016 3.197 6.883 4.8 14.151 4.8 21.761 0 7.619-1.606 15.125-4.765 22.491-3.145 7.332-7.501 14.006-13.045 20.011l-.016.018-.017.017a67.4 67.4 0 0 1-19.29 14.23c-7.419 3.657-15.352 5.483-23.749 5.483H93.657l-9.273 44.203h-47.56zm21.407 103.817h20.563q11.693 0 21.974-5.069a63.3 63.3 0 0 0 18.144-13.383q7.862-8.516 12.297-18.857t4.435-20.885-4.435-20.074q-4.233-9.53-12.499-15.41-8.265-6.084-19.756-6.083H72.244L41.803 316.62h39.31zm29.65-41.79.015-.019c.958-1.253 1.758-2.901 2.32-5.048l.025-.096.03-.093c.651-2.074 1.022-4.685 1.022-7.904 0-2.456-.319-3.883-.647-4.607-.44-.975-.761-1.204-.76-1.206l-.166-.095-.166-.119c-.311-.224-.51-.259-.68-.259h-13.461l-4.754 22.71h12.772c.292 0 .857-.096 1.756-.638.788-.476 1.697-1.289 2.679-2.606zm-22.19 7.32 6.45-30.821h16.733q1.612 0 3.024 1.014 1.411.811 2.419 3.041t1.008 6.286q0 5.272-1.21 9.125-1.008 3.852-3.024 6.488-1.814 2.433-3.83 3.65t-3.83 1.217zm115.507 78.686-.042-.021c-7.343-3.62-13.579-8.498-18.66-14.63-5.028-6.07-8.83-12.931-11.405-20.555l-.007-.022c-2.559-7.721-3.836-15.725-3.836-23.993 0-9.126 1.562-18.1 4.668-26.904l.009-.028.01-.027a95.4 95.4 0 0 1 13.263-24.351l.007-.009c5.738-7.601 12.392-14.225 19.957-19.863 7.707-5.777 16.054-10.293 25.032-13.538 9.045-3.27 18.444-4.903 28.176-4.903 9.373 0 17.85 1.786 25.332 5.469 7.357 3.487 13.603 8.304 18.689 14.442 5.043 6.087 8.78 13.033 11.217 20.801 2.557 7.586 3.829 15.516 3.829 23.768 0 9.144-1.635 18.132-4.883 26.944-3.094 8.623-7.448 16.738-13.045 24.337l-.01.015-.011.014c-5.604 7.468-12.184 14.085-19.73 19.847a92.6 92.6 0 0 1-24.863 13.564c-9.045 3.27-18.445 4.903-28.176 4.903-9.365 0-17.885-1.715-25.478-5.24zm107.57-58.815q4.638-12.571 4.637-25.549 0-11.76-3.629-22.507-3.427-10.95-10.483-19.466-7.055-8.516-17.337-13.382-10.281-5.07-23.586-5.07-13.91 0-26.813 4.664t-23.99 12.977q-10.886 8.111-19.151 19.06a91.3 91.3 0 0 0-12.701 23.319q-4.434 12.571-4.435 25.548 0 11.762 3.629 22.71 3.629 10.748 10.685 19.263 7.055 8.517 17.337 13.586 10.482 4.866 23.788 4.866 13.91 0 26.812-4.663a88.6 88.6 0 0 0 23.788-12.977q10.887-8.314 18.95-19.061 8.065-10.949 12.499-23.318m-48.684 6.628.014-.019c3.119-4.518 5.413-9.327 6.901-14.439 1.515-5.208 2.257-10.231 2.257-15.083 0-4.557-.734-8.65-2.148-12.324l-.027-.072-.025-.074c-1.24-3.628-3.21-6.386-5.885-8.43-2.47-1.889-5.966-3.026-10.865-3.026-5.662 0-10.495 1.324-14.63 3.866l-.036.021-.035.021c-4.314 2.531-7.907 5.899-10.802 10.146a54.3 54.3 0 0 0-6.928 14.3c-1.506 5.191-2.244 10.2-2.244 15.038 0 4.574.672 8.77 1.981 12.619 1.368 3.647 3.421 6.515 6.143 8.717 2.483 1.882 5.911 2.996 10.625 2.996 5.813 0 10.71-1.332 14.831-3.866 4.351-2.674 7.967-6.128 10.873-10.391m-38.808 14.46q-5.04-4.056-7.459-10.544-2.217-6.489-2.218-13.991 0-7.908 2.42-16.221a58.3 58.3 0 0 1 7.459-15.411q4.838-7.096 12.095-11.355 7.258-4.461 16.733-4.461 8.265 0 13.305 3.853 5.04 3.852 7.257 10.341 2.42 6.287 2.42 13.788 0 7.908-2.42 16.222-2.418 8.313-7.459 15.613-4.838 7.097-12.095 11.558t-16.934 4.46q-8.064 0-13.104-3.852m133.877-110.306h62.13c8.396 0 15.833 2.234 22.111 6.846 6.094 4.342 10.706 10.04 13.811 17.015 3.198 6.884 4.801 14.151 4.801 21.762 0 7.619-1.606 15.125-4.765 22.491-3.145 7.332-7.501 14.006-13.045 20.011l-.017.018-.016.017a67.4 67.4 0 0 1-19.291 14.23c-7.418 3.657-15.351 5.483-23.748 5.483h-17.293l-9.273 44.203h-47.561zm21.408 103.817h20.563q11.693 0 21.974-5.069a63.3 63.3 0 0 0 18.143-13.383q7.863-8.516 12.298-18.857t4.435-20.885-4.435-20.074q-4.234-9.53-12.499-15.41-8.265-6.084-19.757-6.083h-58.865L340.133 316.62h39.311zm29.649-41.79.015-.019c.959-1.253 1.759-2.901 2.321-5.048l.025-.096.029-.093c.652-2.074 1.023-4.685 1.023-7.904 0-2.456-.319-3.883-.647-4.607-.44-.975-.761-1.204-.76-1.206l-.166-.095-.166-.119c-.311-.224-.51-.259-.68-.259h-13.461l-4.754 22.71h12.772c.292 0 .857-.096 1.756-.638.788-.476 1.696-1.289 2.679-2.606zm-22.19 7.32 6.451-30.821h16.733q1.613 0 3.024 1.014 1.41.811 2.419 3.041 1.008 2.231 1.008 6.286 0 5.272-1.21 9.125-1.008 3.852-3.024 6.488-1.814 2.433-3.83 3.65t-3.83 1.217z",clipRule:"evenodd"})]}),nt.jsx("path",{fill:"#fff",fillRule:"evenodd",d:"M68.98 168.599h62.13c8.395 0 15.832 2.234 22.11 6.846 6.094 4.342 10.707 10.041 13.812 17.016 3.197 6.883 4.8 14.151 4.8 21.761 0 7.619-1.606 15.125-4.765 22.491-3.145 7.332-7.501 14.006-13.045 20.011l-.016.018-.017.017a67.4 67.4 0 0 1-19.29 14.23c-7.419 3.657-15.352 5.483-23.749 5.483H93.657l-9.273 44.203h-47.56zm21.407 103.817h20.563q11.693 0 21.974-5.069a63.3 63.3 0 0 0 18.144-13.383q7.862-8.516 12.297-18.857t4.435-20.885-4.435-20.074q-4.233-9.53-12.499-15.41-8.265-6.084-19.756-6.083H72.244L41.803 316.62h39.31zm29.65-41.79.015-.019c.958-1.253 1.758-2.901 2.32-5.048l.025-.096.03-.093c.651-2.074 1.022-4.685 1.022-7.904 0-2.456-.319-3.883-.647-4.607-.44-.975-.761-1.204-.76-1.206l-.166-.095-.166-.119c-.311-.224-.51-.259-.68-.259h-13.461l-4.754 22.71h12.772c.292 0 .857-.096 1.756-.638.788-.476 1.697-1.289 2.679-2.606zm-22.19 7.32 6.45-30.821h16.733q1.612 0 3.024 1.014 1.411.811 2.419 3.041t1.008 6.286q0 5.272-1.21 9.125-1.008 3.852-3.024 6.488-1.814 2.433-3.83 3.65t-3.83 1.217zm115.507 78.686-.042-.021c-7.343-3.62-13.579-8.498-18.66-14.63-5.028-6.07-8.83-12.931-11.405-20.555l-.007-.022c-2.559-7.721-3.836-15.725-3.836-23.993 0-9.126 1.562-18.1 4.668-26.904l.009-.028.01-.027a95.4 95.4 0 0 1 13.263-24.351l.007-.009c5.738-7.601 12.392-14.225 19.957-19.863 7.707-5.777 16.054-10.293 25.032-13.538 9.045-3.27 18.444-4.903 28.176-4.903 9.373 0 17.85 1.786 25.332 5.469 7.357 3.487 13.603 8.304 18.689 14.442 5.043 6.087 8.78 13.033 11.217 20.801 2.557 7.586 3.829 15.516 3.829 23.768 0 9.144-1.635 18.132-4.883 26.944-3.094 8.623-7.448 16.738-13.045 24.337l-.01.015-.011.014c-5.604 7.468-12.184 14.085-19.73 19.847a92.6 92.6 0 0 1-24.863 13.564c-9.045 3.27-18.445 4.903-28.176 4.903-9.365 0-17.885-1.715-25.478-5.24zm107.57-58.815q4.638-12.571 4.637-25.549 0-11.76-3.629-22.507-3.427-10.95-10.483-19.466-7.055-8.516-17.337-13.382-10.281-5.07-23.586-5.07-13.91 0-26.813 4.664t-23.99 12.977q-10.886 8.111-19.151 19.06a91.3 91.3 0 0 0-12.701 23.319q-4.434 12.571-4.435 25.548 0 11.762 3.629 22.71 3.629 10.748 10.685 19.263 7.055 8.517 17.337 13.586 10.482 4.866 23.788 4.866 13.91 0 26.812-4.663a88.6 88.6 0 0 0 23.788-12.977q10.887-8.314 18.95-19.061 8.065-10.949 12.499-23.318m-48.684 6.628.014-.019c3.119-4.518 5.413-9.327 6.901-14.439 1.515-5.208 2.257-10.231 2.257-15.083 0-4.557-.734-8.65-2.148-12.324l-.027-.072-.025-.074c-1.24-3.628-3.21-6.386-5.885-8.43-2.47-1.889-5.966-3.026-10.865-3.026-5.662 0-10.495 1.324-14.63 3.866l-.036.021-.035.021c-4.314 2.531-7.907 5.899-10.802 10.146a54.3 54.3 0 0 0-6.928 14.3c-1.506 5.191-2.244 10.2-2.244 15.038 0 4.574.672 8.77 1.981 12.619 1.368 3.647 3.421 6.515 6.143 8.717 2.483 1.882 5.911 2.996 10.625 2.996 5.813 0 10.71-1.332 14.831-3.866 4.351-2.674 7.967-6.128 10.873-10.391m-38.808 14.46q-5.04-4.056-7.459-10.544-2.217-6.489-2.218-13.991 0-7.908 2.42-16.221a58.3 58.3 0 0 1 7.459-15.411q4.838-7.096 12.095-11.355 7.258-4.461 16.733-4.461 8.265 0 13.305 3.853 5.04 3.852 7.257 10.341 2.42 6.287 2.42 13.788 0 7.908-2.42 16.222-2.418 8.313-7.459 15.613-4.838 7.097-12.095 11.558t-16.934 4.46q-8.064 0-13.104-3.852m133.877-110.306h62.13c8.396 0 15.833 2.234 22.111 6.846 6.094 4.342 10.706 10.04 13.811 17.015 3.198 6.884 4.801 14.151 4.801 21.762 0 7.619-1.606 15.125-4.765 22.491-3.145 7.332-7.501 14.006-13.045 20.011l-.017.018-.016.017a67.4 67.4 0 0 1-19.291 14.23c-7.418 3.657-15.351 5.483-23.748 5.483h-17.293l-9.273 44.203h-47.561zm21.408 103.817h20.563q11.693 0 21.974-5.069a63.3 63.3 0 0 0 18.143-13.383q7.863-8.516 12.298-18.857t4.435-20.885-4.435-20.074q-4.234-9.53-12.499-15.41-8.265-6.084-19.757-6.083h-58.865L340.133 316.62h39.311zm29.649-41.79.015-.019c.959-1.253 1.759-2.901 2.321-5.048l.025-.096.029-.093c.652-2.074 1.023-4.685 1.023-7.904 0-2.456-.319-3.883-.647-4.607-.44-.975-.761-1.204-.76-1.206l-.166-.095-.166-.119c-.311-.224-.51-.259-.68-.259h-13.461l-4.754 22.71h12.772c.292 0 .857-.096 1.756-.638.788-.476 1.696-1.289 2.679-2.606zm-22.19 7.32 6.451-30.821h16.733q1.613 0 3.024 1.014 1.41.811 2.419 3.041 1.008 2.231 1.008 6.286 0 5.272-1.21 9.125-1.008 3.852-3.024 6.488-1.814 2.433-3.83 3.65t-3.83 1.217z",clipRule:"evenodd"}),nt.jsx("path",{stroke:"#fff",strokeWidth:"0.3",d:"M68.98 168.599h62.13c8.395 0 15.832 2.234 22.11 6.846 6.094 4.342 10.707 10.041 13.812 17.016 3.197 6.883 4.8 14.151 4.8 21.761 0 7.619-1.606 15.125-4.765 22.491-3.145 7.332-7.501 14.006-13.045 20.011l-.016.018-.017.017a67.4 67.4 0 0 1-19.29 14.23c-7.419 3.657-15.352 5.483-23.749 5.483H93.657l-9.273 44.203h-47.56zm21.407 103.817h20.563q11.693 0 21.974-5.069a63.3 63.3 0 0 0 18.144-13.383q7.862-8.516 12.297-18.857t4.435-20.885-4.435-20.074q-4.233-9.53-12.499-15.41-8.265-6.084-19.756-6.083H72.244L41.803 316.62h39.31zm29.65-41.79.015-.019c.958-1.253 1.758-2.901 2.32-5.048l.025-.096.03-.093c.651-2.074 1.022-4.685 1.022-7.904 0-2.456-.319-3.883-.647-4.607-.44-.975-.761-1.204-.76-1.206l-.166-.095-.166-.119c-.311-.224-.51-.259-.68-.259h-13.461l-4.754 22.71h12.772c.292 0 .857-.096 1.756-.638.788-.476 1.697-1.289 2.679-2.606zm-22.19 7.32 6.45-30.821h16.733q1.612 0 3.024 1.014 1.411.811 2.419 3.041t1.008 6.286q0 5.272-1.21 9.125-1.008 3.852-3.024 6.488-1.814 2.433-3.83 3.65t-3.83 1.217zm115.507 78.686-.042-.021c-7.343-3.62-13.579-8.498-18.66-14.63-5.028-6.07-8.83-12.931-11.405-20.555l-.007-.022c-2.559-7.721-3.836-15.725-3.836-23.993 0-9.126 1.562-18.1 4.668-26.904l.009-.028.01-.027a95.4 95.4 0 0 1 13.263-24.351l.007-.009c5.738-7.601 12.392-14.225 19.957-19.863 7.707-5.777 16.054-10.293 25.032-13.538 9.045-3.27 18.444-4.903 28.176-4.903 9.373 0 17.85 1.786 25.332 5.469 7.357 3.487 13.603 8.304 18.689 14.442 5.043 6.087 8.78 13.033 11.217 20.801 2.557 7.586 3.829 15.516 3.829 23.768 0 9.144-1.635 18.132-4.883 26.944-3.094 8.623-7.448 16.738-13.045 24.337l-.01.015-.011.014c-5.604 7.468-12.184 14.085-19.73 19.847a92.6 92.6 0 0 1-24.863 13.564c-9.045 3.27-18.445 4.903-28.176 4.903-9.365 0-17.885-1.715-25.478-5.24zm107.57-58.815q4.638-12.571 4.637-25.549 0-11.76-3.629-22.507-3.427-10.95-10.483-19.466-7.055-8.516-17.337-13.382-10.281-5.07-23.586-5.07-13.91 0-26.813 4.664t-23.99 12.977q-10.886 8.111-19.151 19.06a91.3 91.3 0 0 0-12.701 23.319q-4.434 12.571-4.435 25.548 0 11.762 3.629 22.71 3.629 10.748 10.685 19.263 7.055 8.517 17.337 13.586 10.482 4.866 23.788 4.866 13.91 0 26.812-4.663a88.6 88.6 0 0 0 23.788-12.977q10.887-8.314 18.95-19.061 8.065-10.949 12.499-23.318Zm-48.684 6.628.014-.019c3.119-4.518 5.413-9.327 6.901-14.439 1.515-5.208 2.257-10.231 2.257-15.083 0-4.557-.734-8.65-2.148-12.324l-.027-.072-.025-.074c-1.24-3.628-3.21-6.386-5.885-8.43-2.47-1.889-5.966-3.026-10.865-3.026-5.662 0-10.495 1.324-14.63 3.866l-.036.021-.035.021c-4.314 2.531-7.907 5.899-10.802 10.146a54.3 54.3 0 0 0-6.928 14.3c-1.506 5.191-2.244 10.2-2.244 15.038 0 4.574.672 8.77 1.981 12.619 1.368 3.647 3.421 6.515 6.143 8.717 2.483 1.882 5.911 2.996 10.625 2.996 5.813 0 10.71-1.332 14.831-3.866 4.351-2.674 7.967-6.128 10.873-10.391Zm-38.808 14.46q-5.04-4.056-7.459-10.544-2.217-6.489-2.218-13.991 0-7.908 2.42-16.221a58.3 58.3 0 0 1 7.459-15.411q4.838-7.096 12.095-11.355 7.258-4.461 16.733-4.461 8.265 0 13.305 3.853 5.04 3.852 7.257 10.341 2.42 6.287 2.42 13.788 0 7.908-2.42 16.222-2.418 8.313-7.459 15.613-4.838 7.097-12.095 11.558t-16.934 4.46q-8.064 0-13.104-3.852Zm133.877-110.306h62.13c8.396 0 15.833 2.234 22.111 6.846 6.094 4.342 10.706 10.04 13.811 17.015 3.198 6.884 4.801 14.151 4.801 21.762 0 7.619-1.606 15.125-4.765 22.491-3.145 7.332-7.501 14.006-13.045 20.011l-.017.018-.016.017a67.4 67.4 0 0 1-19.291 14.23c-7.418 3.657-15.351 5.483-23.748 5.483h-17.293l-9.273 44.203h-47.561zm21.408 103.817h20.563q11.693 0 21.974-5.069a63.3 63.3 0 0 0 18.143-13.383q7.863-8.516 12.298-18.857t4.435-20.885-4.435-20.074q-4.234-9.53-12.499-15.41-8.265-6.084-19.757-6.083h-58.865L340.133 316.62h39.311zm29.649-41.79.015-.019c.959-1.253 1.759-2.901 2.321-5.048l.025-.096.029-.093c.652-2.074 1.023-4.685 1.023-7.904 0-2.456-.319-3.883-.647-4.607-.44-.975-.761-1.204-.76-1.206l-.166-.095-.166-.119c-.311-.224-.51-.259-.68-.259h-13.461l-4.754 22.71h12.772c.292 0 .857-.096 1.756-.638.788-.476 1.696-1.289 2.679-2.606zm-22.19 7.32 6.451-30.821h16.733q1.613 0 3.024 1.014 1.41.811 2.419 3.041 1.008 2.231 1.008 6.286 0 5.272-1.21 9.125-1.008 3.852-3.024 6.488-1.814 2.433-3.83 3.65t-3.83 1.217z",clipRule:"evenodd",mask:"url(#path-6-outside-1_873_174)"})]}),nt.jsxs("defs",{children:[nt.jsxs("radialGradient",{id:"paint0_radial_873_174",cx:"0",cy:"0",r:"1",gradientTransform:"matrix(229.99965 647.20057 -371.3092 131.95444 -62.8 -86.4)",gradientUnits:"userSpaceOnUse",children:[nt.jsx("stop",{stopColor:"#6335EC"}),nt.jsx("stop",{offset:"1",stopOpacity:"0"})]}),nt.jsxs("radialGradient",{id:"paint1_radial_873_174",cx:"0",cy:"0",r:"1",gradientTransform:"rotate(-116.448 530.068 140.453)scale(1055.25 662.094)",gradientUnits:"userSpaceOnUse",children:[nt.jsx("stop",{stopColor:"#E6007A"}),nt.jsx("stop",{offset:"1",stopOpacity:"0"})]}),nt.jsx("clipPath",{id:"clip0_873_174",children:nt.jsx("rect",{width:"512",height:"512",fill:"#fff",rx:"256"})})]})]});rt.createRoot(document.getElementById("root")).render(nt.jsx(Ke.StrictMode,{children:nt.jsx(iI,{})})); From cbf722a7d3248a5a32cc7ca7abb4fa50cd57f559 Mon Sep 17 00:00:00 2001 From: Alex Bean Date: Thu, 19 Dec 2024 18:39:21 +0100 Subject: [PATCH 211/211] chore: release v0.6.0 (#394) * chore: bump version * chore: update changelog * chore: remove build deprecated options * chore: remove e2e test deprecated * chore: remove deprecated templates * fix: include latest PR in CHANGELOG * refactor: remove unnecesary macros and Options * chore: update CHANGELOG * refactor: rename BuildContractCommand and BuildParachainCommand * refactor: subcommand comment * refactor: nitpicks comment fixes --- CHANGELOG.md | 31 ++++ Cargo.lock | 165 ++++++++++-------- Cargo.toml | 2 +- crates/pop-cli/Cargo.toml | 8 +- crates/pop-cli/src/commands/build/contract.rs | 50 ++---- crates/pop-cli/src/commands/build/mod.rs | 32 +--- .../pop-cli/src/commands/build/parachain.rs | 87 ++++----- crates/pop-cli/src/commands/mod.rs | 4 - crates/pop-cli/src/commands/test/contract.rs | 17 -- crates/pop-contracts/Cargo.toml | 2 +- crates/pop-parachains/Cargo.toml | 2 +- crates/pop-parachains/src/new_parachain.rs | 7 +- crates/pop-parachains/src/templates.rs | 21 --- 13 files changed, 179 insertions(+), 249 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f3099bc8..099453ad5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,37 @@ All notable changes to this project will be documented in this file. +## [0.6.0] - 2024-12-19 + +### 🚀 Features + +- Wallet integration (#371) +- Guide user to call a chain (#316) +- Guide user to call a contract (#306) +- Output events after calling a chain (#372) +- Add remark action (#387) + +### 🐛 Fixes + +- Build spec experience (#331) +- HRMP channels (#278) + +### 🚜 Refactor + +- Ensure short args consistency (#386) +- Bump fallback versions (#393) + +### ⚙️ Miscellaneous Tasks + +- Bump zombienet version to `v0.2.18` (#352) +- Set msrv (#385) +- Replace rococo to paseo name (#333) +- Replace codeowners (#388) + +### Build + +- *(deps)* Remove unused dependencies (#369) + ## [0.5.0] - 2024-11-08 ### 🚀 Features diff --git a/Cargo.lock b/Cargo.lock index 48e49894a..3e3981ecf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -951,7 +951,7 @@ dependencies = [ "http 1.2.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.1", + "hyper 1.5.2", "hyper-util", "itoa", "matchit", @@ -1247,7 +1247,7 @@ dependencies = [ "hex", "http 1.2.0", "http-body-util", - "hyper 1.5.1", + "hyper 1.5.2", "hyper-named-pipe", "hyper-util", "hyperlocal", @@ -1258,7 +1258,7 @@ dependencies = [ "serde_json", "serde_repr", "serde_urlencoded", - "thiserror 2.0.6", + "thiserror 2.0.8", "tokio", "tokio-util", "tower-service", @@ -1720,7 +1720,7 @@ dependencies = [ "semver 1.0.24", "serde", "serde_json", - "thiserror 2.0.6", + "thiserror 2.0.8", ] [[package]] @@ -1878,12 +1878,12 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "colored" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -1913,15 +1913,15 @@ dependencies = [ [[package]] name = "console" -version = "0.15.8" +version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" dependencies = [ "encode_unicode", - "lazy_static", "libc", - "unicode-width 0.1.14", - "windows-sys 0.52.0", + "once_cell", + "unicode-width 0.2.0", + "windows-sys 0.59.0", ] [[package]] @@ -2113,7 +2113,7 @@ dependencies = [ "serde", "serde_json", "strsim 0.11.1", - "thiserror 2.0.6", + "thiserror 2.0.8", "tracing", ] @@ -2275,9 +2275,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -2294,18 +2294,18 @@ dependencies = [ [[package]] name = "crossbeam-queue" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crossterm" @@ -2727,9 +2727,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.134" +version = "1.0.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5a32d755fe20281b46118ee4b507233311fb7a48a0cfd42f554b93640521a2f" +checksum = "4d44ff199ff93242c3afe480ab588d544dd08d72e92885e152ffebc670f076ad" dependencies = [ "cc", "cxxbridge-cmd", @@ -2741,9 +2741,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.134" +version = "1.0.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11645536ada5d1c8804312cbffc9ab950f2216154de431de930da47ca6955199" +checksum = "66fd8f17ad454fc1e4f4ab83abffcc88a532e90350d3ffddcb73030220fcbd52" dependencies = [ "cc", "codespan-reporting", @@ -2755,9 +2755,9 @@ dependencies = [ [[package]] name = "cxxbridge-cmd" -version = "1.0.134" +version = "1.0.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebcc9c78e3c7289665aab921a2b394eaffe8bdb369aa18d81ffc0f534fd49385" +checksum = "4717c9c806a9e07fdcb34c84965a414ea40fafe57667187052cf1eb7f5e8a8a9" dependencies = [ "clap", "codespan-reporting", @@ -2768,15 +2768,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.134" +version = "1.0.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a22a87bd9e78d7204d793261470a4c9d585154fddd251828d8aefbb5f74c3bf" +checksum = "2f6515329bf3d98f4073101c7866ff2bec4e635a13acb82e3f3753fff0bf43cb" [[package]] name = "cxxbridge-macro" -version = "1.0.134" +version = "1.0.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dfdb020ff8787c5daf6e0dca743005cc8782868faeadfbabb8824ede5cb1c72" +checksum = "fb93e6a7ce8ec985c02bbb758237a31598b340acbbc3c19c5a4fa6adaaac92ab" dependencies = [ "proc-macro2", "quote", @@ -3251,9 +3251,9 @@ dependencies = [ [[package]] name = "encode_unicode" -version = "0.3.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "encoding_rs" @@ -3465,6 +3465,17 @@ dependencies = [ "bytes", ] +[[package]] +name = "fastrlp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" +dependencies = [ + "arrayvec 0.7.6", + "auto_impl", + "bytes", +] + [[package]] name = "ff" version = "0.13.0" @@ -4313,11 +4324,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4417,9 +4428,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.31" +version = "0.14.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" dependencies = [ "bytes", "futures-channel", @@ -4441,9 +4452,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" +checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" dependencies = [ "bytes", "futures-channel", @@ -4467,7 +4478,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" dependencies = [ "hex", - "hyper 1.5.1", + "hyper 1.5.2", "hyper-util", "pin-project-lite", "tokio", @@ -4483,7 +4494,7 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http 0.2.12", - "hyper 0.14.31", + "hyper 0.14.32", "log", "rustls 0.21.12", "rustls-native-certs 0.6.3", @@ -4493,13 +4504,13 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.3" +version = "0.27.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +checksum = "f6884a48c6826ec44f524c7456b163cebe9e55a18d7b5e307cb4f100371cc767" dependencies = [ "futures-util", "http 1.2.0", - "hyper 1.5.1", + "hyper 1.5.2", "hyper-util", "rustls 0.23.20", "rustls-pki-types", @@ -4514,7 +4525,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper 0.14.31", + "hyper 0.14.32", "pin-project-lite", "tokio", "tokio-io-timeout", @@ -4527,7 +4538,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper 0.14.31", + "hyper 0.14.32", "native-tls", "tokio", "tokio-native-tls", @@ -4541,7 +4552,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.5.1", + "hyper 1.5.2", "hyper-util", "native-tls", "tokio", @@ -4560,7 +4571,7 @@ dependencies = [ "futures-util", "http 1.2.0", "http-body 1.0.1", - "hyper 1.5.1", + "hyper 1.5.2", "pin-project-lite", "socket2", "tokio", @@ -4576,7 +4587,7 @@ checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7" dependencies = [ "hex", "http-body-util", - "hyper 1.5.1", + "hyper 1.5.2", "hyper-util", "pin-project-lite", "tokio", @@ -5318,7 +5329,7 @@ dependencies = [ "home", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.31", + "hyper 0.14.32", "hyper-rustls 0.24.2", "hyper-timeout", "jsonpath-rust", @@ -5399,9 +5410,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.168" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libgit2-sys" @@ -5872,9 +5883,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" dependencies = [ "adler2", ] @@ -5904,7 +5915,7 @@ dependencies = [ "http 1.2.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.1", + "hyper 1.5.2", "hyper-util", "log", "rand", @@ -8496,7 +8507,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" dependencies = [ "memchr", - "thiserror 2.0.6", + "thiserror 2.0.8", "ucd-trie", ] @@ -9335,7 +9346,7 @@ dependencies = [ [[package]] name = "pop-cli" -version = "0.5.0" +version = "0.6.0" dependencies = [ "anyhow", "assert_cmd", @@ -9371,7 +9382,7 @@ dependencies = [ [[package]] name = "pop-common" -version = "0.5.0" +version = "0.6.0" dependencies = [ "anyhow", "cargo_toml", @@ -9403,7 +9414,7 @@ dependencies = [ [[package]] name = "pop-contracts" -version = "0.5.0" +version = "0.6.0" dependencies = [ "anyhow", "contract-build", @@ -9430,7 +9441,7 @@ dependencies = [ [[package]] name = "pop-parachains" -version = "0.5.0" +version = "0.6.0" dependencies = [ "anyhow", "askama", @@ -9460,7 +9471,7 @@ dependencies = [ [[package]] name = "pop-telemetry" -version = "0.5.0" +version = "0.6.0" dependencies = [ "dirs", "env_logger 0.11.5", @@ -9926,7 +9937,7 @@ dependencies = [ "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.31", + "hyper 0.14.32", "hyper-tls 0.5.0", "ipnet", "js-sys", @@ -9967,8 +9978,8 @@ dependencies = [ "http 1.2.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.1", - "hyper-rustls 0.27.3", + "hyper 1.5.2", + "hyper-rustls 0.27.4", "hyper-tls 0.6.0", "hyper-util", "ipnet", @@ -10084,16 +10095,18 @@ dependencies = [ [[package]] name = "ruint" -version = "1.12.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c3cc4c2511671f327125da14133d0c5c5d137f006a1017a16f557bc85b16286" +checksum = "f5ef8fb1dd8de3870cb8400d51b4c2023854bbafd5431a3ac7e7317243e22d2f" dependencies = [ "alloy-rlp", "ark-ff 0.3.0", "ark-ff 0.4.2", "bytes", - "fastrlp", + "fastrlp 0.3.1", + "fastrlp 0.4.0", "num-bigint", + "num-integer", "num-traits", "parity-scale-codec", "primitive-types 0.12.2", @@ -10891,9 +10904,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" +checksum = "1863fd3768cd83c56a7f60faa4dc0d403f1b6df0a38c3c25f44b7894e45370d5" dependencies = [ "core-foundation-sys", "libc", @@ -13520,11 +13533,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.6" +version = "2.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" +checksum = "08f5383f3e0071702bf93ab5ee99b52d26936be9dedd9413067cbdcddcb6141a" dependencies = [ - "thiserror-impl 2.0.6", + "thiserror-impl 2.0.8", ] [[package]] @@ -13540,9 +13553,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.6" +version = "2.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" +checksum = "f2f357fcec90b3caef6623a099691be676d033b40a058ac95d2a6ade6fa0c943" dependencies = [ "proc-macro2", "quote", @@ -15358,9 +15371,9 @@ dependencies = [ [[package]] name = "zip" -version = "2.2.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d52293fc86ea7cf13971b3bb81eb21683636e7ae24c729cdaf1b7c4157a352" +checksum = "ae9c1ea7b3a5e1f4b922ff856a129881167511563dc219869afe3787fc0c1a45" dependencies = [ "arbitrary", "crc32fast", @@ -15368,7 +15381,7 @@ dependencies = [ "displaydoc", "indexmap 2.7.0", "memchr", - "thiserror 2.0.6", + "thiserror 2.0.8", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8b284b10a..07be639c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ documentation = "https://learn.onpop.io/" license = "GPL-3.0" repository = "https://github.com/r0gue-io/pop-cli" rust-version = "1.81.0" -version = "0.5.0" +version = "0.6.0" [workspace.dependencies] anyhow = "1.0" diff --git a/crates/pop-cli/Cargo.toml b/crates/pop-cli/Cargo.toml index 6093569dc..538ef0d51 100644 --- a/crates/pop-cli/Cargo.toml +++ b/crates/pop-cli/Cargo.toml @@ -33,19 +33,19 @@ strum.workspace = true strum_macros.workspace = true # contracts -pop-contracts = { path = "../pop-contracts", version = "0.5.0", optional = true } +pop-contracts = { path = "../pop-contracts", version = "0.6.0", optional = true } sp-weights = { workspace = true, optional = true } # parachains -pop-parachains = { path = "../pop-parachains", version = "0.5.0", optional = true } +pop-parachains = { path = "../pop-parachains", version = "0.6.0", optional = true } dirs = { workspace = true, optional = true } git2.workspace = true # telemetry -pop-telemetry = { path = "../pop-telemetry", version = "0.5.0", optional = true } +pop-telemetry = { path = "../pop-telemetry", version = "0.6.0", optional = true } # common -pop-common = { path = "../pop-common", version = "0.5.0" } +pop-common = { path = "../pop-common", version = "0.6.0" } # wallet-integration axum.workspace = true diff --git a/crates/pop-cli/src/commands/build/contract.rs b/crates/pop-cli/src/commands/build/contract.rs index ca0cdd739..d8687aa0a 100644 --- a/crates/pop-cli/src/commands/build/contract.rs +++ b/crates/pop-cli/src/commands/build/contract.rs @@ -1,27 +1,18 @@ // SPDX-License-Identifier: GPL-3.0 use crate::cli; -use clap::Args; use pop_contracts::{build_smart_contract, Verbosity}; use std::path::PathBuf; -#[cfg(not(test))] -use std::{thread::sleep, time::Duration}; -#[derive(Args)] -pub struct BuildContractCommand { - /// Path for the contract project [default: current directory] - #[arg(long)] +/// Configuration for building a smart contract. +pub struct BuildContract { + /// Path of the contract project. pub(crate) path: Option, - /// The default compilation includes debug functionality, increasing contract size and gas - /// usage. For production, always build in release mode to exclude debug features. - #[clap(short, long)] + /// Build profile: `true` for release mode, `false` for debug mode. pub(crate) release: bool, - // Deprecation flag, used to specify whether the deprecation warning is shown. - #[clap(skip)] - pub(crate) valid: bool, } -impl BuildContractCommand { +impl BuildContract { /// Executes the command. pub(crate) fn execute(self) -> anyhow::Result<&'static str> { self.build(&mut cli::Cli) @@ -33,14 +24,6 @@ impl BuildContractCommand { /// * `cli` - The CLI implementation to be used. fn build(self, cli: &mut impl cli::traits::Cli) -> anyhow::Result<&'static str> { cli.intro("Building your contract")?; - - // Show warning if specified as deprecated. - if !self.valid { - cli.warning("NOTE: this command is deprecated. Please use `pop build` (or simply `pop b`) in future...")?; - #[cfg(not(test))] - sleep(Duration::from_secs(3)); - } - // Build contract. let build_result = build_smart_contract(self.path.as_deref(), self.release, Verbosity::Default)?; @@ -66,23 +49,16 @@ mod tests { create_smart_contract(name, &path.join(name), &Standard)?; for release in [false, true] { - for valid in [false, true] { - let mut cli = MockCli::new() - .expect_intro("Building your contract") - .expect_outro("Build completed successfully!"); - - if !valid { - cli = cli.expect_warning("NOTE: this command is deprecated. Please use `pop build` (or simply `pop b`) in future..."); - } + let mut cli = MockCli::new() + .expect_intro("Building your contract") + .expect_outro("Build completed successfully!"); - assert_eq!( - BuildContractCommand { path: Some(path.join(name)), release, valid } - .build(&mut cli)?, - "contract" - ); + assert_eq!( + BuildContract { path: Some(path.join(name)), release }.build(&mut cli)?, + "contract" + ); - cli.verify()?; - } + cli.verify()?; } Ok(()) diff --git a/crates/pop-cli/src/commands/build/mod.rs b/crates/pop-cli/src/commands/build/mod.rs index f1a69c9c2..d9ab9ba4d 100644 --- a/crates/pop-cli/src/commands/build/mod.rs +++ b/crates/pop-cli/src/commands/build/mod.rs @@ -3,12 +3,12 @@ use crate::cli::{self, Cli}; use clap::{Args, Subcommand}; #[cfg(feature = "contract")] -use contract::BuildContractCommand; +use contract::BuildContract; use duct::cmd; use pop_common::Profile; use std::path::PathBuf; #[cfg(feature = "parachain")] -use {parachain::BuildParachainCommand, spec::BuildSpecCommand}; +use {parachain::BuildParachain, spec::BuildSpecCommand}; #[cfg(feature = "contract")] pub(crate) mod contract; @@ -35,24 +35,11 @@ pub(crate) struct BuildArgs { /// Build profile [default: debug]. #[clap(long, value_enum)] pub(crate) profile: Option, - /// Parachain ID to be used when generating the chain spec files. - #[arg(short = 'i', long = "id")] - #[cfg(feature = "parachain")] - pub(crate) id: Option, } -/// Build a parachain, smart contract or Rust package. +/// Subcommand for building chain artifacts. #[derive(Subcommand)] pub(crate) enum Command { - /// [DEPRECATED] Build a parachain - #[cfg(feature = "parachain")] - #[clap(alias = "p")] - Parachain(BuildParachainCommand), - /// [DEPRECATED] Build a contract, generate metadata, bundle together in a `.contract` - /// file - #[cfg(feature = "contract")] - #[clap(alias = "c")] - Contract(BuildContractCommand), /// Build a chain specification and its genesis artifacts. #[cfg(feature = "parachain")] #[clap(alias = "s")] @@ -65,12 +52,11 @@ impl Command { // If only contract feature enabled, build as contract #[cfg(feature = "contract")] if pop_contracts::is_supported(args.path.as_deref())? { - // All commands originating from root command are valid let release = match args.profile { Some(profile) => profile.into(), None => args.release, }; - BuildContractCommand { path: args.path, release, valid: true }.execute()?; + BuildContract { path: args.path, release }.execute()?; return Ok("contract"); } @@ -81,13 +67,10 @@ impl Command { Some(profile) => profile, None => args.release.into(), }; - // All commands originating from root command are valid - BuildParachainCommand { - path: args.path, + BuildParachain { + path: args.path.unwrap_or_else(|| PathBuf::from("./")), package: args.package, - profile: Some(profile), - id: args.id, - valid: true, + profile, } .execute()?; return Ok("parachain"); @@ -160,7 +143,6 @@ mod tests { package: package.clone(), release, profile: Some(profile.clone()), - id: None, }, &mut cli, )?, diff --git a/crates/pop-cli/src/commands/build/parachain.rs b/crates/pop-cli/src/commands/build/parachain.rs index 13d8fe963..7091d426b 100644 --- a/crates/pop-cli/src/commands/build/parachain.rs +++ b/crates/pop-cli/src/commands/build/parachain.rs @@ -1,34 +1,24 @@ // SPDX-License-Identifier: GPL-3.0 use crate::{cli, style::style}; -use clap::Args; use pop_common::Profile; use pop_parachains::build_parachain; use std::path::PathBuf; #[cfg(not(test))] use std::{thread::sleep, time::Duration}; -#[derive(Args)] -pub struct BuildParachainCommand { - /// Directory path for your project [default: current directory]. - #[arg(long)] - pub(crate) path: Option, +// Configuration for building a parachain. +pub struct BuildParachain { + /// Directory path for your project. + pub(crate) path: PathBuf, /// The package to be built. - #[arg(short, long)] pub(crate) package: Option, - /// Build profile [default: debug]. - #[clap(long, value_enum)] - pub(crate) profile: Option, - /// Parachain ID to be used when generating the chain spec files. - #[arg(short, long)] - pub(crate) id: Option, - // Deprecation flag, used to specify whether the deprecation warning is shown. - #[clap(skip)] - pub(crate) valid: bool, + /// Build profile. + pub(crate) profile: Profile, } -impl BuildParachainCommand { - /// Executes the command. +impl BuildParachain { + /// Executes the build process. pub(crate) fn execute(self) -> anyhow::Result<&'static str> { self.build(&mut cli::Cli) } @@ -41,13 +31,7 @@ impl BuildParachainCommand { let project = if self.package.is_some() { "package" } else { "parachain" }; cli.intro(format!("Building your {project}"))?; - let profile = self.profile.unwrap_or(Profile::Debug); - // Show warning if specified as deprecated. - if !self.valid { - cli.warning("NOTE: this command is deprecated. Please use `pop build` (or simply `pop b`) in future...")?; - #[cfg(not(test))] - sleep(Duration::from_secs(3)) - } else if profile == Profile::Debug { + if self.profile == Profile::Debug { cli.warning("NOTE: this command now defaults to DEBUG builds. Please use `--release` (or simply `-r`) for a release build...")?; #[cfg(not(test))] sleep(Duration::from_secs(3)) @@ -55,9 +39,8 @@ impl BuildParachainCommand { // Build parachain. cli.warning("NOTE: this may take some time...")?; - let project_path = self.path.unwrap_or_else(|| PathBuf::from("./")); - let binary = build_parachain(&project_path, self.package, &profile, None)?; - cli.info(format!("The {project} was built in {} mode.", profile))?; + let binary = build_parachain(&self.path, self.package, &self.profile, None)?; + cli.info(format!("The {project} was built in {} mode.", self.profile))?; cli.outro("Build completed successfully!")?; let generated_files = [format!("Binary generated at: {}", binary.display())]; let generated_files: Vec<_> = generated_files @@ -115,36 +98,28 @@ mod tests { for package in [None, Some(name.to_string())] { for profile in Profile::VARIANTS { - for valid in [false, true] { - let project = if package.is_some() { "package" } else { "parachain" }; - let mut cli = MockCli::new() - .expect_intro(format!("Building your {project}")) - .expect_warning("NOTE: this may take some time...") - .expect_info(format!("The {project} was built in {profile} mode.")) - .expect_outro("Build completed successfully!"); - - if !valid { - cli = cli.expect_warning("NOTE: this command is deprecated. Please use `pop build` (or simply `pop b`) in future..."); - } else { - if profile == &Profile::Debug { - cli = cli.expect_warning("NOTE: this command now defaults to DEBUG builds. Please use `--release` (or simply `-r`) for a release build..."); - } + let project = if package.is_some() { "package" } else { "parachain" }; + let mut cli = MockCli::new() + .expect_intro(format!("Building your {project}")) + .expect_warning("NOTE: this may take some time...") + .expect_info(format!("The {project} was built in {profile} mode.")) + .expect_outro("Build completed successfully!"); + + if profile == &Profile::Debug { + cli = cli.expect_warning("NOTE: this command now defaults to DEBUG builds. Please use `--release` (or simply `-r`) for a release build..."); + } + + assert_eq!( + BuildParachain { + path: project_path.clone(), + package: package.clone(), + profile: profile.clone(), } + .build(&mut cli)?, + project + ); - assert_eq!( - BuildParachainCommand { - path: Some(project_path.clone()), - package: package.clone(), - profile: Some(profile.clone()), - id: None, - valid, - } - .build(&mut cli)?, - project - ); - - cli.verify()?; - } + cli.verify()?; } } diff --git a/crates/pop-cli/src/commands/mod.rs b/crates/pop-cli/src/commands/mod.rs index 9af86e3f4..064a34d0b 100644 --- a/crates/pop-cli/src/commands/mod.rs +++ b/crates/pop-cli/src/commands/mod.rs @@ -88,10 +88,6 @@ impl Command { Self::Build(args) => match args.command { None => build::Command::execute(args).map(|t| json!(t)), Some(cmd) => match cmd { - #[cfg(feature = "parachain")] - build::Command::Parachain(cmd) => cmd.execute().map(|_| Value::Null), - #[cfg(feature = "contract")] - build::Command::Contract(cmd) => cmd.execute().map(|_| Value::Null), #[cfg(feature = "parachain")] build::Command::Spec(cmd) => cmd.execute().await.map(|_| Value::Null), }, diff --git a/crates/pop-cli/src/commands/test/contract.rs b/crates/pop-cli/src/commands/test/contract.rs index 05e842eca..36d496163 100644 --- a/crates/pop-cli/src/commands/test/contract.rs +++ b/crates/pop-cli/src/commands/test/contract.rs @@ -8,16 +8,11 @@ use clap::Args; use cliclack::{clear_screen, log::warning, outro}; use pop_contracts::{test_e2e_smart_contract, test_smart_contract}; use std::path::PathBuf; -#[cfg(not(test))] -use {std::time::Duration, tokio::time::sleep}; #[derive(Args)] pub(crate) struct TestContractCommand { #[arg(short, long, help = "Path for the contract project [default: current directory]")] path: Option, - /// [DEPRECATED] Run e2e tests - #[arg(short, long, value_parser=["e2e-tests"])] - features: Option, /// Run end-to-end tests #[arg(short, long)] e2e: bool, @@ -33,21 +28,9 @@ impl TestContractCommand { pub(crate) async fn execute(mut self) -> anyhow::Result<&'static str> { clear_screen()?; - let mut show_deprecated = false; - if self.features.is_some() && self.features.clone().unwrap().contains("e2e-tests") { - show_deprecated = true; - self.e2e = true; - } - if self.e2e { Cli.intro("Starting end-to-end tests")?; - if show_deprecated { - warning("NOTE: --features e2e-tests is deprecated. Use --e2e instead.")?; - #[cfg(not(test))] - sleep(Duration::from_secs(3)).await; - } - self.node = match check_contracts_node_and_prompt( &mut Cli, &crate::cache()?, diff --git a/crates/pop-contracts/Cargo.toml b/crates/pop-contracts/Cargo.toml index cd880213c..581075f95 100644 --- a/crates/pop-contracts/Cargo.toml +++ b/crates/pop-contracts/Cargo.toml @@ -34,7 +34,7 @@ contract-extrinsics.workspace = true contract-transcode.workspace = true scale-info.workspace = true # pop -pop-common = { path = "../pop-common", version = "0.5.0" } +pop-common = { path = "../pop-common", version = "0.6.0" } [dev-dependencies] # Used in doc tests. diff --git a/crates/pop-parachains/Cargo.toml b/crates/pop-parachains/Cargo.toml index 4b2ee4111..619b3336e 100644 --- a/crates/pop-parachains/Cargo.toml +++ b/crates/pop-parachains/Cargo.toml @@ -35,7 +35,7 @@ walkdir.workspace = true zombienet-sdk.workspace = true # Pop -pop-common = { path = "../pop-common", version = "0.5.0" } +pop-common = { path = "../pop-common", version = "0.6.0" } [dev-dependencies] # Used in doc tests. diff --git a/crates/pop-parachains/src/new_parachain.rs b/crates/pop-parachains/src/new_parachain.rs index 8974cfede..f9e28e527 100644 --- a/crates/pop-parachains/src/new_parachain.rs +++ b/crates/pop-parachains/src/new_parachain.rs @@ -87,12 +87,7 @@ pub fn instantiate_openzeppelin_template( let source = temp_dir.path(); let tag = Git::clone_and_degit(template.repository_url()?, source, tag_version)?; - let mut template_name = template.template_name_without_provider(); - // Handle deprecated OpenZeppelin template - if matches!(template, Parachain::DeprecatedOpenZeppelinGeneric) { - template_name = Parachain::OpenZeppelinGeneric.template_name_without_provider(); - } - + let template_name = template.template_name_without_provider(); extract_template_files(template_name, temp_dir.path(), target, None)?; Ok(tag) } diff --git a/crates/pop-parachains/src/templates.rs b/crates/pop-parachains/src/templates.rs index 0f9ba5794..324f6f23f 100644 --- a/crates/pop-parachains/src/templates.rs +++ b/crates/pop-parachains/src/templates.rs @@ -177,23 +177,6 @@ pub enum Parachain { ) )] ParityFPT, - // OpenZeppelin - #[strum( - serialize = "polkadot-generic-runtime-template", - message = "Generic Runtime Template", - detailed_message = "A generic template for Substrate Runtime.", - props( - Provider = "OpenZeppelin", - Repository = "https://github.com/OpenZeppelin/polkadot-runtime-templates", - Network = "./zombienet-config/devnet.toml", - SupportedVersions = "v1.0.0,v2.0.1", - IsAudited = "true", - License = "GPL-3.0", - IsDeprecated = "true", - DeprecatedMessage = "This template is deprecated. Please use openzeppelin/generic-template in the future.", - ) - )] - DeprecatedOpenZeppelinGeneric, // templates for unit tests below #[cfg(test)] #[strum( @@ -273,7 +256,6 @@ mod tests { // openzeppelin ("openzeppelin/generic-template".to_string(), OpenZeppelinGeneric), ("openzeppelin/evm-template".to_string(), OpenZeppelinEVM), - ("polkadot-generic-runtime-template".to_string(), DeprecatedOpenZeppelinGeneric), ("cpt".to_string(), ParityContracts), ("fpt".to_string(), ParityFPT), ("test_01".to_string(), TestTemplate01), @@ -289,7 +271,6 @@ mod tests { (EVM, "evm".to_string()), (OpenZeppelinGeneric, "generic-template".to_string()), (OpenZeppelinEVM, "evm-template".to_string()), - (DeprecatedOpenZeppelinGeneric, "polkadot-generic-runtime-template".to_string()), (ParityContracts, "cpt".to_string()), (ParityFPT, "fpt".to_string()), (TestTemplate01, "test_01".to_string()), @@ -331,7 +312,6 @@ mod tests { (EVM, Some("./network.toml")), (OpenZeppelinGeneric, Some("./zombienet-config/devnet.toml")), (OpenZeppelinEVM, Some("./zombienet-config/devnet.toml")), - (DeprecatedOpenZeppelinGeneric, Some("./zombienet-config/devnet.toml")), (ParityContracts, Some("./zombienet.toml")), (ParityFPT, Some("./zombienet-config.toml")), (TestTemplate01, Some("")), @@ -348,7 +328,6 @@ mod tests { (EVM, Some("Unlicense")), (OpenZeppelinGeneric, Some("GPL-3.0")), (OpenZeppelinEVM, Some("GPL-3.0")), - (DeprecatedOpenZeppelinGeneric, Some("GPL-3.0")), (ParityContracts, Some("Unlicense")), (ParityFPT, Some("Unlicense")), (TestTemplate01, Some("Unlicense")),