Skip to content

Commit

Permalink
feat: secure signing for call from call_data
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexD10S committed Dec 12, 2024
1 parent f51086e commit 023c5f8
Showing 1 changed file with 89 additions and 64 deletions.
153 changes: 89 additions & 64 deletions crates/pop-cli/src/commands/call/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,8 @@ impl CallChainCommand {
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
if let Err(e) =
self.submit_extrinsic_from_call_data(&chain, call_data, &mut cli::Cli).await
{
display_message(&e.to_string(), false, &mut cli::Cli)?;
}
Expand Down Expand Up @@ -98,7 +97,8 @@ impl CallChainCommand {

if self.use_wallet {
// Sign and submit the extrinsic.
if let Err(e) = call.submit_extrinsic_secure_signing(&chain, &xt, &mut cli).await {
let call_data = call_data(&chain.client, &xt)?;
if let Err(e) = submit_extrinsic_secure_signing(&chain, call_data, &mut cli).await {
display_message(&e.to_string(), false, &mut cli)?;
break;
}
Expand All @@ -110,9 +110,8 @@ impl CallChainCommand {
}
}

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()?
{
Expand Down Expand Up @@ -212,7 +211,7 @@ impl CallChainCommand {
// skip the prompt.
let suri = match self.suri.as_ref() {
Some(suri) => suri.clone(),
None => {
None =>
if !self.use_wallet {
if cli.confirm("Do you want to use your browser wallet to sign the transaction? (Selecting 'No' will prompt you to manually enter the secret key URI for signing, e.g., '//Alice')")
.initial_value(true)
Expand All @@ -225,8 +224,7 @@ impl CallChainCommand {
}
} else {
DEFAULT_URI.to_string()
}
},
},
};

return Ok(Call {
Expand All @@ -243,20 +241,43 @@ impl CallChainCommand {
// Submits an extrinsic to the chain using the provided encoded call data.
async fn submit_extrinsic_from_call_data(
&self,
client: &OnlineClient<SubstrateConfig>,
chain: &Chain,
call_data: &str,
cli: &mut impl Cli,
) -> Result<()> {
// Resolve who is signing the extrinsic.
// TODO: HERE TOO
// Resolve who is signing the extrinsic. If a `suri` was provided via the command line,
// skip the prompt.
let mut use_wallet = self.use_wallet;
let suri = match self.suri.as_ref() {
Some(suri) => suri,
None => &cli.input("Signer of the extrinsic:").default_input(DEFAULT_URI).interact()?,
Some(suri) => suri.clone(),
None =>
if !self.use_wallet {
if cli.confirm("Do you want to use your browser wallet to sign the transaction? (Selecting 'No' will prompt you to manually enter the secret key URI for signing, e.g., '//Alice')")
.initial_value(true)
.interact()? {
use_wallet = true;
DEFAULT_URI.to_string()
}
else {
cli.input("Signer of the extrinsic:").default_input(DEFAULT_URI).interact()?
}
} else {
DEFAULT_URI.to_string()
},
};
// Return early
if use_wallet {
let call_data_bytes =
decode_call_data(call_data).map_err(|err| anyhow!("{}", format!("{err:?}")))?;
submit_extrinsic_secure_signing(chain, 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?")
if !self.skip_confirm &&
!cli.confirm("Do you want to submit the extrinsic?")
.initial_value(true)
.interact()?
{
Expand All @@ -271,9 +292,10 @@ 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)
.await
.map_err(|err| anyhow!("{}", format!("{err:?}")))?;
let result =
sign_and_submit_extrinsic_with_call_data(&chain.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)?;
Expand All @@ -284,24 +306,22 @@ impl CallChainCommand {
// 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(_) => {
Ok(_) =>
if !self.sudo {
self.sudo = cli
.confirm(
"Would you like to dispatch this function call with `Root` origin?",
)
.initial_value(false)
.interact()?;
}
},
Err(_) => {
},
Err(_) =>
if self.sudo {
cli.warning(
"NOTE: sudo is not supported by the chain. Ignoring `--sudo` flag.",
)?;
self.sudo = false;
}
},
},
}
Ok(())
}
Expand All @@ -317,11 +337,11 @@ impl CallChainCommand {

// 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()
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.
Expand Down Expand Up @@ -402,9 +422,8 @@ impl Call {
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()?
{
Expand All @@ -425,31 +444,6 @@ impl Call {
Ok(())
}

// Sign and submit an extrinsic.
async fn submit_extrinsic_secure_signing(
&mut self,
chain: &Chain,
xt: &DynamicPayload,
cli: &mut impl Cli,
) -> Result<()> {
let call_data = call_data(&chain.client, xt)?;
let maybe_payload = wait_for_signature(call_data, chain.url.to_string()).await?;
if let Some(payload) = maybe_payload {
cli.success("Signed payload received.")?;
let spinner = cliclack::spinner();
spinner.start("Signing and submitting the extrinsic and then waiting for finalization, please be patient...");

let result = submit_signed_extrinsic(chain.client.clone(), payload)
.await
.map_err(|err| anyhow!("{}", format!("{err:?}")))?;

spinner.stop(format!("Extrinsic submitted with hash: {:?}", result));
} else {
display_message("Signed payload doesn't exist.", false, cli)?;
}
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));
Expand Down Expand Up @@ -482,6 +476,31 @@ impl Call {
}
}

// Sign and submit an extrinsic.
async fn submit_extrinsic_secure_signing(
chain: &Chain,
call_data: Vec<u8>,
cli: &mut impl Cli,
) -> Result<()> {
let maybe_payload = wait_for_signature(call_data, chain.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(chain.client.clone(), payload)
.await
.map_err(|err| anyhow!("{}", format!("{err:?}")))?;

spinner.stop(format!("Extrinsic submitted with hash: {:?}", result));
} else {
display_message("Signed payload doesn't exist.", 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 {
Expand Down Expand Up @@ -833,24 +852,30 @@ 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 client = set_up_client(POP_NETWORK_TESTNET_URL).await?;
let chain = Chain {
url: Url::parse(POP_NETWORK_TESTNET_URL)?,
client: client.clone(),
pallets: [].to_vec(),
};
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,
use_wallet: false,
skip_confirm: false,
call_data: Some("0x00000411".to_string()),
sudo: false,
};
let mut cli = MockCli::new()
.expect_confirm("Do you want to use your browser wallet to sign the transaction? (Selecting 'No' will prompt you to manually enter the secret key URI for signing, e.g., '//Alice')", 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.");
call_config
.submit_extrinsic_from_call_data(&client, "0x00000411", &mut cli)
.submit_extrinsic_from_call_data(&chain, "0x00000411", &mut cli)
.await?;

cli.verify()
Expand All @@ -863,7 +888,7 @@ 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,
Expand Down

0 comments on commit 023c5f8

Please sign in to comment.