use crate::{Function, Pallet, Param, errors::Error, find_callable_by_name};
use pop_common::{
call::{DefaultEnvironment, DisplayEvents, TokenMetadata, Verbosity},
create_signer,
};
use sp_core::bytes::{from_hex, to_hex};
use subxt::{
OnlineClient, SubstrateConfig,
blocks::ExtrinsicEvents,
dynamic::Value,
tx::{DynamicPayload, Payload, SubmittableTransaction, TxStatus},
};
pub mod metadata;
pub async fn set_up_client(url: &str) -> Result<OnlineClient<SubstrateConfig>, Error> {
OnlineClient::<SubstrateConfig>::from_url(url)
.await
.map_err(|e| Error::ConnectionFailure(e.to_string()))
}
pub fn construct_extrinsic(
function: &Function,
args: Vec<String>,
) -> Result<DynamicPayload, Error> {
let parsed_args: Vec<Value> = metadata::parse_dispatchable_arguments(&function.params, args)?;
Ok(subxt::dynamic::tx(function.pallet.clone(), function.name.clone(), parsed_args))
}
pub fn construct_sudo_extrinsic(xt: DynamicPayload) -> DynamicPayload {
subxt::dynamic::tx("Sudo", "sudo", [xt.into_value()].to_vec())
}
pub fn construct_proxy_extrinsic(
pallets: &[Pallet],
proxied_account: String,
xt: DynamicPayload,
) -> Result<DynamicPayload, Error> {
let proxy_function = find_callable_by_name(pallets, "Proxy", "proxy")?;
let required_params: Vec<Param> = match proxy_function {
metadata::CallItem::Function(ref function) =>
function.params.iter().take(2).cloned().collect(),
_ => return Err(Error::CallableNotSupported),
};
let mut parsed_args: Vec<Value> = metadata::parse_dispatchable_arguments(
&required_params,
vec![proxied_account, "None()".to_string()],
)?;
let real = parsed_args.remove(0);
let proxy_type = parsed_args.remove(0);
Ok(subxt::dynamic::tx("Proxy", "proxy", [real, proxy_type, xt.into_value()].to_vec()))
}
pub async fn sign_and_submit_extrinsic<Xt: Payload>(
client: &OnlineClient<SubstrateConfig>,
url: &url::Url,
xt: Xt,
suri: &str,
) -> Result<String, Error> {
let signer = create_signer(suri)?;
let mut tx = client
.tx()
.sign_and_submit_then_watch_default(&xt, &signer)
.await
.map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))?;
let tx_hash = tx.extrinsic_hash();
while let Some(status) = tx.next().await {
match status.map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))? {
TxStatus::InFinalizedBlock(tx_in_block) => {
let events = tx_in_block
.wait_for_success()
.await
.map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))?;
let parsed_events = parse_and_format_events(client, url, &events).await?;
return Ok(format!(
"Extrinsic Submitted with hash: {:?}\n\n{}",
tx_hash, parsed_events
));
},
TxStatus::InBestBlock(tx_in_block) => {
let events = tx_in_block
.wait_for_success()
.await
.map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))?;
let parsed_events = parse_and_format_events(client, url, &events).await?;
return Ok(format!(
"Extrinsic Submitted with hash: {:?}\n\n{}",
tx_hash, parsed_events
));
},
TxStatus::Error { message } => {
return Err(Error::ExtrinsicSubmissionError(format!("{:?}", message)));
},
TxStatus::Invalid { message } => {
return Err(Error::ExtrinsicSubmissionError(format!("{:?}", message)));
},
TxStatus::Dropped { message } => {
return Err(Error::ExtrinsicSubmissionError(format!("{:?}", message)));
},
_ => continue,
}
}
Err(Error::ExtrinsicSubmissionError(
"Transaction stream ended without finalization".to_string(),
))
}
pub async fn parse_and_format_events(
client: &OnlineClient<SubstrateConfig>,
url: &url::Url,
result: &ExtrinsicEvents<SubstrateConfig>,
) -> Result<String, Error> {
let metadata = client.metadata();
let token_metadata = TokenMetadata::query::<SubstrateConfig>(url).await?;
let events =
DisplayEvents::from_events::<SubstrateConfig, DefaultEnvironment>(result, None, &metadata)?;
let events =
events.display_events::<DefaultEnvironment>(Verbosity::Default, &token_metadata)?;
Ok(events)
}
pub async fn submit_signed_extrinsic(
client: OnlineClient<SubstrateConfig>,
payload: String,
) -> Result<ExtrinsicEvents<SubstrateConfig>, Error> {
let hex_encoded =
from_hex(&payload).map_err(|e| Error::CallDataDecodingError(e.to_string()))?;
let extrinsic = SubmittableTransaction::from_bytes(client, hex_encoded);
extrinsic
.submit_and_watch()
.await
.map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))?
.wait_for_finalized_success()
.await
.map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))
}
pub fn encode_call_data(
client: &OnlineClient<SubstrateConfig>,
xt: &DynamicPayload,
) -> Result<String, Error> {
let call_data = xt
.encode_call_data(&client.metadata())
.map_err(|e| Error::CallDataEncodingError(e.to_string()))?;
Ok(to_hex(&call_data, false))
}
pub fn decode_call_data(call_data: &str) -> Result<Vec<u8>, Error> {
from_hex(call_data).map_err(|e| Error::CallDataDecodingError(e.to_string()))
}
pub struct CallData(Vec<u8>);
impl CallData {
pub fn new(data: Vec<u8>) -> CallData {
CallData(data)
}
}
impl Payload for CallData {
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(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::set_up_client;
use anyhow::Result;
const ALICE_SURI: &str = "//Alice";
#[tokio::test]
async fn set_up_client_fails_wrong_url() -> Result<()> {
assert!(matches!(
set_up_client("wss://wronguri.xyz").await,
Err(Error::ConnectionFailure(_))
));
Ok(())
}
#[tokio::test]
async fn construct_extrinsic_works() -> Result<()> {
let transfer_allow_death = Function {
pallet: "Balances".into(),
name: "transfer_allow_death".into(),
index: 0,
docs: ".".into(),
params: vec![
Param {
name: "dest".into(),
type_name: "MultiAddress<AccountId32 ([u8;32]),()>: Id(AccountId32 ([u8;32])), Index(Compact<()>), Raw([u8]), Address32([u8;32]), Address20([u8;20])".into(),
sub_params: vec![
Param {
name: "Id".into(),
type_name: "".into(),
sub_params: vec![
Param {
name: "Id".into(),
type_name: "AccountId32 ([u8;32])".into(),
sub_params: vec![
Param {
name: "Id".into(),
type_name: "[u8;32]".into(),
sub_params: vec![],
..Default::default()
}
],
..Default::default()
}
],
..Default::default()
}],
..Default::default()
},
Param {
name: "value".into(),
type_name: "Compact<u128>".into(),
sub_params: vec![],
..Default::default()
}
],
is_supported: true,
};
assert!(matches!(
construct_extrinsic(
&transfer_allow_death,
vec![ALICE_SURI.to_string(), "100".to_string()],
),
Err(Error::ParamProcessingError)
));
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 construct_sudo_extrinsic_works() -> Result<()> {
let xt = construct_extrinsic(
&Function::default(),
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(())
}
}