1use crate::{Function, Pallet, Param, errors::Error, find_callable_by_name};
4use pop_common::{
5 call::{DefaultEnvironment, DisplayEvents, TokenMetadata, Verbosity},
6 create_signer,
7};
8use sp_core::bytes::{from_hex, to_hex};
9use subxt::{
10 OnlineClient, SubstrateConfig,
11 blocks::ExtrinsicEvents,
12 dynamic::Value,
13 tx::{DynamicPayload, Payload, SubmittableTransaction},
14};
15pub mod metadata;
16
17pub async fn set_up_client(url: &str) -> Result<OnlineClient<SubstrateConfig>, Error> {
22 OnlineClient::<SubstrateConfig>::from_url(url)
23 .await
24 .map_err(|e| Error::ConnectionFailure(e.to_string()))
25}
26
27pub fn construct_extrinsic(
33 function: &Function,
34 args: Vec<String>,
35) -> Result<DynamicPayload, Error> {
36 let parsed_args: Vec<Value> = metadata::parse_dispatchable_arguments(&function.params, args)?;
37 Ok(subxt::dynamic::tx(function.pallet.clone(), function.name.clone(), parsed_args))
38}
39
40pub fn construct_sudo_extrinsic(xt: DynamicPayload) -> DynamicPayload {
46 subxt::dynamic::tx("Sudo", "sudo", [xt.into_value()].to_vec())
47}
48
49pub fn construct_proxy_extrinsic(
57 pallets: &[Pallet],
58 proxied_account: String,
59 xt: DynamicPayload,
60) -> Result<DynamicPayload, Error> {
61 let proxy_function = find_callable_by_name(pallets, "Proxy", "proxy")?;
62 let required_params: Vec<Param> = match proxy_function {
67 metadata::CallItem::Function(ref function) =>
68 function.params.iter().take(2).cloned().collect(),
69 _ => return Err(Error::CallableNotSupported),
70 };
71 let mut parsed_args: Vec<Value> = metadata::parse_dispatchable_arguments(
72 &required_params,
73 vec![proxied_account, "None()".to_string()],
74 )?;
75 let real = parsed_args.remove(0);
76 let proxy_type = parsed_args.remove(0);
77 Ok(subxt::dynamic::tx("Proxy", "proxy", [real, proxy_type, xt.into_value()].to_vec()))
78}
79
80pub async fn sign_and_submit_extrinsic<Xt: Payload>(
88 client: &OnlineClient<SubstrateConfig>,
89 url: &url::Url,
90 xt: Xt,
91 suri: &str,
92) -> Result<String, Error> {
93 let signer = create_signer(suri)?;
94 let result = client
95 .tx()
96 .sign_and_submit_then_watch_default(&xt, &signer)
97 .await
98 .map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))?
99 .wait_for_finalized_success()
100 .await
101 .map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))?;
102
103 let events = parse_and_format_events(client, url, &result).await?;
104
105 Ok(format!("Extrinsic Submitted with hash: {:?}\n\n{}", result.extrinsic_hash(), events))
106}
107
108pub async fn parse_and_format_events(
115 client: &OnlineClient<SubstrateConfig>,
116 url: &url::Url,
117 result: &ExtrinsicEvents<SubstrateConfig>,
118) -> Result<String, Error> {
119 let metadata = client.metadata();
123 let token_metadata = TokenMetadata::query::<SubstrateConfig>(url).await?;
124 let events =
125 DisplayEvents::from_events::<SubstrateConfig, DefaultEnvironment>(result, None, &metadata)?;
126 let events =
127 events.display_events::<DefaultEnvironment>(Verbosity::Default, &token_metadata)?;
128
129 Ok(events)
130}
131
132pub async fn submit_signed_extrinsic(
138 client: OnlineClient<SubstrateConfig>,
139 payload: String,
140) -> Result<ExtrinsicEvents<SubstrateConfig>, Error> {
141 let hex_encoded =
142 from_hex(&payload).map_err(|e| Error::CallDataDecodingError(e.to_string()))?;
143 let extrinsic = SubmittableTransaction::from_bytes(client, hex_encoded);
144 extrinsic
145 .submit_and_watch()
146 .await
147 .map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))?
148 .wait_for_finalized_success()
149 .await
150 .map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))
151}
152
153pub fn encode_call_data(
159 client: &OnlineClient<SubstrateConfig>,
160 xt: &DynamicPayload,
161) -> Result<String, Error> {
162 let call_data = xt
163 .encode_call_data(&client.metadata())
164 .map_err(|e| Error::CallDataEncodingError(e.to_string()))?;
165 Ok(to_hex(&call_data, false))
166}
167
168pub fn decode_call_data(call_data: &str) -> Result<Vec<u8>, Error> {
173 from_hex(call_data).map_err(|e| Error::CallDataDecodingError(e.to_string()))
174}
175
176pub struct CallData(Vec<u8>);
179
180impl CallData {
181 pub fn new(data: Vec<u8>) -> CallData {
183 CallData(data)
184 }
185}
186
187impl Payload for CallData {
188 fn encode_call_data_to(
189 &self,
190 _: &subxt::Metadata,
191 out: &mut Vec<u8>,
192 ) -> Result<(), subxt::ext::subxt_core::Error> {
193 out.extend_from_slice(&self.0);
194 Ok(())
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201 use crate::set_up_client;
202 use anyhow::Result;
203
204 const ALICE_SURI: &str = "//Alice";
205
206 #[tokio::test]
207 async fn set_up_client_fails_wrong_url() -> Result<()> {
208 assert!(matches!(
209 set_up_client("wss://wronguri.xyz").await,
210 Err(Error::ConnectionFailure(_))
211 ));
212 Ok(())
213 }
214
215 #[tokio::test]
216 async fn construct_extrinsic_works() -> Result<()> {
217 let transfer_allow_death = Function {
218 pallet: "Balances".into(),
219 name: "transfer_allow_death".into(),
220 index: 0,
221 docs: ".".into(),
222 params: vec![
223 Param {
224 name: "dest".into(),
225 type_name: "MultiAddress<AccountId32 ([u8;32]),()>: Id(AccountId32 ([u8;32])), Index(Compact<()>), Raw([u8]), Address32([u8;32]), Address20([u8;20])".into(),
226 sub_params: vec![
227 Param {
228 name: "Id".into(),
229 type_name: "".into(),
230 sub_params: vec![
231 Param {
232 name: "Id".into(),
233 type_name: "AccountId32 ([u8;32])".into(),
234 sub_params: vec![
235 Param {
236 name: "Id".into(),
237 type_name: "[u8;32]".into(),
238 sub_params: vec![],
239 ..Default::default()
240 }
241 ],
242 ..Default::default()
243 }
244 ],
245 ..Default::default()
246 }],
247 ..Default::default()
248 },
249 Param {
250 name: "value".into(),
251 type_name: "Compact<u128>".into(),
252 sub_params: vec![],
253 ..Default::default()
254 }
255 ],
256 is_supported: true,
257 };
258 assert!(matches!(
260 construct_extrinsic(
261 &transfer_allow_death,
262 vec![ALICE_SURI.to_string(), "100".to_string()],
263 ),
264 Err(Error::ParamProcessingError)
265 ));
266 let xt = construct_extrinsic(
268 &transfer_allow_death,
269 vec![
270 "Id(5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty)".to_string(),
271 "100".to_string(),
272 ],
273 )?;
274 assert_eq!(xt.call_name(), "transfer_allow_death");
275 assert_eq!(xt.pallet_name(), "Balances");
276 Ok(())
277 }
278
279 #[tokio::test]
280 async fn construct_sudo_extrinsic_works() -> Result<()> {
281 let xt = construct_extrinsic(
282 &Function::default(),
283 vec![
284 "Id(5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty)".to_string(),
285 "Id(5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy)".to_string(),
286 "100".to_string(),
287 ],
288 )?;
289 let xt = construct_sudo_extrinsic(xt);
290 assert_eq!(xt.call_name(), "sudo");
291 assert_eq!(xt.pallet_name(), "Sudo");
292 Ok(())
293 }
294}