ac_compose_macros/
lib.rs

1/*
2   Copyright 2019 Supercomputing Systems AG
3
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7
8	   http://www.apache.org/licenses/LICENSE-2.0
9
10   Unless required by applicable law or agreed to in writing, software
11   distributed under the License is distributed on an "AS IS" BASIS,
12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   See the License for the specific language governing permissions and
14   limitations under the License.
15
16*/
17
18//! Offers macros that build extrinsics for custom runtime modules based on the metadata.
19
20#![cfg_attr(not(feature = "std"), no_std)]
21
22// re-export for macro resolution
23pub use ac_primitives as primitives;
24pub use log;
25
26mod rpc;
27
28/// Generates the extrinsic's call field for a given module and call passed as &str, if found in the metadata.
29/// Otherwise None is returned.
30/// # Arguments
31///
32/// * 'node_metadata' - This crate's parsed node metadata as field of the API.
33/// * 'pallet_name' - Pallet name as &str for which the call is composed.
34/// * 'call_name' - Call name as &str
35/// * 'args' - Optional sequence of arguments of the call. They are not checked against the metadata.
36#[macro_export]
37macro_rules! compose_call {
38($node_metadata: expr, $pallet_name: expr, $call_name: expr $(, $args: expr) *) => {
39        {
40			let maybe_pallet = $node_metadata.pallet_by_name($pallet_name);
41			let maybe_call = match  maybe_pallet {
42				Some(pallet) => {
43					$crate::compose_call_for_pallet_metadata!(pallet, $call_name $(, ($args)) *)
44				},
45				None => None,
46			};
47			maybe_call
48		}
49
50    };
51}
52
53/// Generates the extrinsic's call field for the given PalletMetadata
54/// # Arguments
55///
56/// * 'pallet_metadata' - This crate's parsed pallet metadata as field of the API.
57/// * 'call_name' - Call name as &str
58/// * 'args' - Optional sequence of arguments of the call. They are not checked against the metadata.
59///   As of now the user needs to check himself that the correct arguments are supplied.
60#[macro_export]
61macro_rules! compose_call_for_pallet_metadata {
62($pallet_metadata: expr, $call_name: expr $(, $args: expr) *) => {
63        {
64
65			let maybe_call_variant = $pallet_metadata.call_variant_by_name($call_name);
66			match maybe_call_variant {
67				Some(call_variant) => Some(([$pallet_metadata.index(), call_variant.index as u8] $(, ($args)) *)),
68				None => None,
69			}
70
71        }
72
73    };
74}
75
76/// Generates an UncheckedExtrinsic for a given call.
77/// # Arguments
78///
79/// * 'signer' - AccountKey that is used to sign the extrinsic.
80/// * 'call' - call as returned by the compose_call! macro or via substrate's call enums.
81/// * 'params' - Instance of `ExtrinsicParams` that can be used to fetch signed extra and additional signed
82#[macro_export]
83macro_rules! compose_extrinsic_offline {
84	($signer: expr,
85    $call: expr,
86    $params: expr) => {{
87		use $crate::primitives::extrinsics::{
88			ExtrinsicParams, SignExtrinsic, SignedPayload, UncheckedExtrinsic,
89		};
90
91		let extra = $params.transaction_extension();
92		let raw_payload = SignedPayload::from_raw($call.clone(), extra, $params.implicit());
93
94		let signature = raw_payload.using_encoded(|payload| $signer.sign(payload));
95
96		UncheckedExtrinsic::new_signed($call, $signer.extrinsic_address(), signature, extra)
97	}};
98}
99
100/// Generates an UncheckedExtrinsic for the given pallet and call, if they are found within the metadata.
101/// Otherwise None is returned.
102/// # Arguments
103///
104/// * 'api' - This instance of API. If the *signer* field is not set, an unsigned extrinsic will be generated.
105/// * 'nonce' - signer's account nonce: Index
106/// * 'pallet_name' - Pallet name as &str for which the call is composed.
107/// * 'call_name' - Call name as &str
108/// * 'args' - Optional sequence of arguments of the call. They are not checked against the metadata.
109#[macro_export]
110macro_rules! compose_extrinsic_with_nonce {
111	($api: expr,
112	$nonce: expr,
113	$pallet_name: expr,
114	$call_name: expr
115	$(, $args: expr) *) => {
116		{
117            use $crate::log::debug;
118            use $crate::primitives::UncheckedExtrinsic;
119
120            debug!("Composing generic extrinsic for module {:?} and call {:?}", $pallet_name, $call_name);
121
122			let metadata = $api.metadata();
123            let maybe_call = $crate::compose_call!(metadata, $pallet_name, $call_name $(, ($args)) *);
124
125			let maybe_extrinsic = match maybe_call {
126				Some(call) => {
127					let extrinsic = if let Some(signer) = $api.signer() {
128						$crate::compose_extrinsic_offline!(
129							signer,
130							call.clone(),
131							$api.extrinsic_params($nonce)
132						)
133					} else {
134						UncheckedExtrinsic::new_bare(call.clone())
135					};
136					Some(extrinsic)
137				},
138				None => None,
139			};
140			maybe_extrinsic
141
142
143		}
144	};
145}
146
147/// Generates an UncheckedExtrinsic for the given pallet and call from the metadata.
148///
149/// Returns None if call is not found within metadata.
150/// Fetches the nonce from the given `api` instance. If this fails, zero is taken as default nonce.
151/// See also compose_extrinsic_with_nonce
152#[macro_export]
153#[cfg(feature = "sync-api")]
154macro_rules! compose_extrinsic {
155	($api: expr,
156	$pallet_name: expr,
157	$call_name: expr
158	$(, $args: expr) *) => {
159		{
160			let nonce = $api.get_nonce().unwrap_or_default();
161			let maybe_extrinisc = $crate::compose_extrinsic_with_nonce!($api, nonce, $pallet_name, $call_name $(, ($args)) *);
162			maybe_extrinisc
163		}
164    };
165}
166
167/// Generates an UncheckedExtrinsic for the given pallet and call from the metadata.
168///
169/// Returns None if call is not found within metadata.
170/// Fetches the nonce from the given `api` instance. If this fails, zero is taken as default nonce.
171/// See also compose_extrinsic_with_nonce
172#[macro_export]
173#[cfg(not(feature = "sync-api"))]
174macro_rules! compose_extrinsic {
175	($api: expr,
176	$pallet_name: expr,
177	$call_name: expr
178	$(, $args: expr) *) => {
179		{
180			let nonce = $api.get_nonce().await.unwrap_or_default();
181			let maybe_extrinisc = $crate::compose_extrinsic_with_nonce!($api, nonce, $pallet_name, $call_name $(, ($args)) *);
182			maybe_extrinisc
183		}
184	};
185}
186
187#[cfg(test)]
188mod tests {
189	use super::*;
190	use ac_node_api::Metadata;
191	use codec::Decode;
192	use frame_metadata::RuntimeMetadataPrefixed;
193	use std::fs;
194
195	#[test]
196	fn macro_compose_call_for_pallet_metadata_works() {
197		let encoded_metadata = fs::read("../ksm_metadata_v14.bin").unwrap();
198		let runtime_metadata_prefixed =
199			RuntimeMetadataPrefixed::decode(&mut encoded_metadata.as_slice()).unwrap();
200		let metadata = Metadata::try_from(runtime_metadata_prefixed).unwrap();
201
202		let pallet_metadata = metadata.pallet_by_name("Balances").unwrap();
203
204		let extra_parameter = 10000;
205		let expected_call_one = ([4, 0], extra_parameter);
206		let call_one = compose_call_for_pallet_metadata!(
207			&pallet_metadata,
208			"transfer_allow_death",
209			extra_parameter
210		)
211		.unwrap();
212		assert_eq!(expected_call_one, call_one);
213		let expected_call_two = ([4, 8], extra_parameter);
214		let call_two = compose_call_for_pallet_metadata!(
215			&pallet_metadata,
216			"force_set_balance",
217			extra_parameter
218		)
219		.unwrap();
220		assert_eq!(expected_call_two, call_two);
221	}
222
223	#[test]
224	fn macro_compose_call_for_pallet_metadata_returns_none_for_unknown_function() {
225		let encoded_metadata = fs::read("../ksm_metadata_v14.bin").unwrap();
226		let runtime_metadata_prefixed =
227			RuntimeMetadataPrefixed::decode(&mut encoded_metadata.as_slice()).unwrap();
228		let metadata = Metadata::try_from(runtime_metadata_prefixed).unwrap();
229
230		let pallet_metadata = metadata.pallet_by_name("Balances").unwrap();
231		let non_existent_function = "obladi";
232
233		let option = compose_call_for_pallet_metadata!(&pallet_metadata, non_existent_function);
234		assert!(option.is_none());
235	}
236
237	#[test]
238	fn macro_compose_call_returns_none_for_unknown_function() {
239		let encoded_metadata = fs::read("../ksm_metadata_v14.bin").unwrap();
240		let runtime_metadata_prefixed =
241			RuntimeMetadataPrefixed::decode(&mut encoded_metadata.as_slice()).unwrap();
242		let metadata = Metadata::try_from(runtime_metadata_prefixed).unwrap();
243
244		let pallet_name = "Balances";
245		let non_existent_function = "obladi";
246
247		let option = compose_call!(&metadata, pallet_name, non_existent_function);
248		assert!(option.is_none());
249	}
250
251	#[test]
252	fn macro_compose_call_returns_none_for_unknown_pallet() {
253		let encoded_metadata = fs::read("../ksm_metadata_v14.bin").unwrap();
254		let runtime_metadata_prefixed =
255			RuntimeMetadataPrefixed::decode(&mut encoded_metadata.as_slice()).unwrap();
256		let metadata = Metadata::try_from(runtime_metadata_prefixed).unwrap();
257
258		let pallet_name = "Balance";
259		let non_existent_function = "force_set_balance";
260
261		let option = compose_call!(&metadata, pallet_name, non_existent_function);
262		assert!(option.is_none());
263	}
264
265	#[test]
266	fn macro_compose_call_works_for_valid_input() {
267		let encoded_metadata = fs::read("../ksm_metadata_v14.bin").unwrap();
268		let runtime_metadata_prefixed =
269			RuntimeMetadataPrefixed::decode(&mut encoded_metadata.as_slice()).unwrap();
270		let metadata = Metadata::try_from(runtime_metadata_prefixed).unwrap();
271
272		let pallet_name = "Balances";
273		let non_existent_function = "force_set_balance";
274		let extra_parameter = 10000;
275
276		let expected_call = ([4, 8], extra_parameter);
277		let call =
278			compose_call!(&metadata, pallet_name, non_existent_function, extra_parameter).unwrap();
279		assert_eq!(call, expected_call);
280	}
281}