Skip to content

Commit

Permalink
feat: submit extrinsic from call_data
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexD10S committed Nov 25, 2024
1 parent b6dba52 commit 44d2fd8
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 31 deletions.
122 changes: 92 additions & 30 deletions crates/pop-cli/src/commands/call/parachain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ 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,
DynamicPayload, OnlineClient, Pallet, Param, SubstrateConfig,
parse_chain_metadata, set_up_api, sign_and_submit_extrinsic,
sign_and_submit_extrinsic_with_call_data, supported_actions, Action, DynamicPayload,
OnlineClient, Pallet, Param, SubstrateConfig,
};

const DEFAULT_URL: &str = "ws://localhost:9944/";
Expand All @@ -33,33 +34,49 @@ pub struct CallParachainCommand {
/// - with a password "//Alice///SECRET_PASSWORD"
#[clap(name = "suri", long, short, default_value = DEFAULT_URI)]
suri: String,
/// SCALE encoded bytes representing the call data of the transaction.
#[arg(name = "call", short = 'c', long, conflicts_with_all = ["pallet", "extrinsic", "args"])]
call_data: Option<String>,
}

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 {
let api = match self.initialize_api_client(&mut cli::Cli).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) => {
if let Some(call_data) = self.call_data.as_ref().cloned() {
// Execute the call.
if let Err(e) =
self.send_extrinsic_from_call_data(&api, &call_data, &mut cli::Cli).await
{
display_message(&e.to_string(), false, &mut cli::Cli)?;
}
} else {
// 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.
if let Err(e) = self.configure(&api, &mut cli::Cli).await {
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)?;
}
// 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(());
},
};
// 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(())
}
Expand All @@ -80,19 +97,16 @@ impl CallParachainCommand {
full_message
}

/// Configure the call based on command line arguments/call UI.
async fn configure(
/// Initializes the API client by configuring the chain URL.
async fn initialize_api_client(
&mut self,
cli: &mut impl cli::traits::Cli,
repeat: bool,
) -> Result<OnlineClient<SubstrateConfig>> {
// Show intro on first run.
if !repeat {
cli.intro("Call a parachain")?;
}
cli.intro("Call a parachain")?;

// Resolve url.
if !repeat && self.url.as_str() == DEFAULT_URL {
if self.url.as_str() == DEFAULT_URL {
// Prompt for url.
let url: String = cli
.input("Which chain would you like to interact with?")
Expand All @@ -101,10 +115,17 @@ 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 {
Ok(api)
}

/// Configure the call based on command line arguments/call UI.
async fn configure(
&mut self,
api: &OnlineClient<SubstrateConfig>,
cli: &mut impl cli::traits::Cli,
) -> Result<()> {
let pallets = match parse_chain_metadata(api).await {
Ok(pallets) => pallets,
Err(e) => {
return Err(anyhow!(format!(
Expand Down Expand Up @@ -154,21 +175,21 @@ impl CallParachainCommand {
)?;
// Reset specific items from the last call and repeat.
self.reset_for_new_call();
Box::pin(self.configure(cli, true)).await?;
Box::pin(self.configure(api, cli)).await?;
}

// 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, &param)?;
let input = prompt_for_param(api, cli, &param)?;
contract_args.push(input);
}
self.args = contract_args;
}

cli.info(self.display())?;
Ok(api)
Ok(())
}

/// Prepares the extrinsic or query.
Expand Down Expand Up @@ -231,6 +252,7 @@ impl CallParachainCommand {
}
let spinner = cliclack::spinner();
spinner.start("Signing and submitting the extrinsic, please wait...");
// TODO: IF HERE
let result = sign_and_submit_extrinsic(api.clone(), tx, &self.suri)
.await
.map_err(|err| anyhow!("{}", format!("{err:?}")))?;
Expand All @@ -249,14 +271,54 @@ impl CallParachainCommand {
{
// Reset specific items from the last call and repeat.
self.reset_for_new_call();
self.configure(cli, true).await?;
self.configure(&api, cli).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(())
}
}
// Sends an extrinsic to the chain using the call data.
async fn send_extrinsic_from_call_data(
&mut self,
api: &OnlineClient<SubstrateConfig>,
call_data: &str,
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(format!("Encoded call data: {}", call_data))?;
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.",
self.extrinsic
),
false,
cli,
)?;
return Ok(());
}
let spinner = cliclack::spinner();
spinner.start("Signing and submitting the extrinsic, please wait...");
let result = sign_and_submit_extrinsic_with_call_data(api.clone(), call_data, &self.suri)
.await
.map_err(|err| anyhow!("{}", format!("{err:?}")))?;

spinner.stop(&format!("Extrinsic submitted successfully with hash: {:?}", result));
display_message("Parachain calling complete.", true, cli)?;
Ok(())
}
/// Resets specific fields to default values for a new call.
fn reset_for_new_call(&mut self) {
self.pallet = None;
Expand Down
47 changes: 47 additions & 0 deletions crates/pop-parachains/src/call/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,50 @@ pub fn encode_call_data(
let call_data = tx.encode_call_data(&api.metadata())?;
Ok(format!("0x{}", hex::encode(call_data)))
}

struct RawCall(Vec<u8>);

impl Payload for RawCall {
fn encode_call_data_to(
&self,
_: &subxt::Metadata,
out: &mut Vec<u8>,
) -> Result<(), subxt::ext::subxt_core::Error> {
out.extend_from_slice(&self.0);
Ok(())
}
}

pub async fn sign_and_submit_extrinsic_with_call_data(
api: OnlineClient<SubstrateConfig>,
call_data: &str,
suri: &str,
) -> Result<String, Error> {
let signer = create_signer(suri)?;
let call_data_bytes = hex::decode(call_data.trim_start_matches("0x"))?;
let payload = RawCall(call_data_bytes);
let result = api
.tx()
.sign_and_submit_then_watch_default(&payload, &signer)
.await?
.wait_for_finalized()
.await?
.wait_for_success()
.await?;
Ok(format!("{:?}", result.extrinsic_hash()))
}

#[cfg(test)]
mod tests {
use super::*;

#[ignore]
#[tokio::test]
async fn test_call_data() -> Result<(), Error> {
let api = set_up_api("ws://127.0.0.1:9944").await?;
let call_data = "0x00000411";
let result = sign_and_submit_extrinsic_with_call_data(api, call_data, "//Alice").await?;
println!("{:?}", result);
Ok(())
}
}
2 changes: 1 addition & 1 deletion crates/pop-parachains/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ pub use call::{
params::Param,
parse_chain_metadata, Pallet,
},
set_up_api, sign_and_submit_extrinsic,
set_up_api, sign_and_submit_extrinsic, sign_and_submit_extrinsic_with_call_data,
};
pub use errors::Error;
pub use indexmap::IndexSet;
Expand Down

0 comments on commit 44d2fd8

Please sign in to comment.