1use crate::Error;
9use crate::error::MetadataError;
10use crate::metadata::Metadata;
11use alloc::borrow::{Cow, ToOwned};
12use alloc::boxed::Box;
13use alloc::string::String;
14
15use alloc::vec::Vec;
16use codec::Encode;
17use scale_encode::EncodeAsFields;
18use scale_value::{Composite, Value, ValueDef, Variant};
19
20pub trait Payload {
23 fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error>;
25
26 fn encode_call_data(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
29 let mut v = Vec::new();
30 self.encode_call_data_to(metadata, &mut v)?;
31 Ok(v)
32 }
33
34 fn validation_details(&self) -> Option<ValidationDetails<'_>> {
38 None
39 }
40}
41
42macro_rules! boxed_payload {
43 ($ty:path) => {
44 impl<T: Payload + ?Sized> Payload for $ty {
45 fn encode_call_data_to(
46 &self,
47 metadata: &Metadata,
48 out: &mut Vec<u8>,
49 ) -> Result<(), Error> {
50 self.as_ref().encode_call_data_to(metadata, out)
51 }
52 fn encode_call_data(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
53 self.as_ref().encode_call_data(metadata)
54 }
55 fn validation_details(&self) -> Option<ValidationDetails<'_>> {
56 self.as_ref().validation_details()
57 }
58 }
59 };
60}
61
62boxed_payload!(Box<T>);
63#[cfg(feature = "std")]
64boxed_payload!(std::sync::Arc<T>);
65#[cfg(feature = "std")]
66boxed_payload!(std::rc::Rc<T>);
67
68pub struct ValidationDetails<'a> {
70 pub pallet_name: &'a str,
72 pub call_name: &'a str,
74 pub hash: [u8; 32],
77}
78
79#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
81pub struct DefaultPayload<CallData> {
82 pallet_name: Cow<'static, str>,
83 call_name: Cow<'static, str>,
84 call_data: CallData,
85 validation_hash: Option<[u8; 32]>,
86}
87
88pub type StaticPayload<Calldata> = DefaultPayload<Calldata>;
90pub type DynamicPayload = DefaultPayload<Composite<()>>;
92
93impl<CallData> DefaultPayload<CallData> {
94 pub fn new(
96 pallet_name: impl Into<String>,
97 call_name: impl Into<String>,
98 call_data: CallData,
99 ) -> Self {
100 DefaultPayload {
101 pallet_name: Cow::Owned(pallet_name.into()),
102 call_name: Cow::Owned(call_name.into()),
103 call_data,
104 validation_hash: None,
105 }
106 }
107
108 #[doc(hidden)]
111 pub fn new_static(
112 pallet_name: &'static str,
113 call_name: &'static str,
114 call_data: CallData,
115 validation_hash: [u8; 32],
116 ) -> Self {
117 DefaultPayload {
118 pallet_name: Cow::Borrowed(pallet_name),
119 call_name: Cow::Borrowed(call_name),
120 call_data,
121 validation_hash: Some(validation_hash),
122 }
123 }
124
125 pub fn unvalidated(self) -> Self {
127 Self {
128 validation_hash: None,
129 ..self
130 }
131 }
132
133 pub fn call_data(&self) -> &CallData {
135 &self.call_data
136 }
137
138 pub fn pallet_name(&self) -> &str {
140 &self.pallet_name
141 }
142
143 pub fn call_name(&self) -> &str {
145 &self.call_name
146 }
147}
148
149impl DefaultPayload<Composite<()>> {
150 pub fn into_value(self) -> Value<()> {
154 let call = Value {
155 context: (),
156 value: ValueDef::Variant(Variant {
157 name: self.call_name.into_owned(),
158 values: self.call_data,
159 }),
160 };
161
162 Value::unnamed_variant(self.pallet_name, [call])
163 }
164}
165
166impl<CallData: EncodeAsFields> Payload for DefaultPayload<CallData> {
167 fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error> {
168 let pallet = metadata.pallet_by_name_err(&self.pallet_name)?;
169 let call = pallet
170 .call_variant_by_name(&self.call_name)
171 .ok_or_else(|| MetadataError::CallNameNotFound((*self.call_name).to_owned()))?;
172
173 let pallet_index = pallet.index();
174 let call_index = call.index;
175
176 pallet_index.encode_to(out);
177 call_index.encode_to(out);
178
179 let mut fields = call
180 .fields
181 .iter()
182 .map(|f| scale_encode::Field::new(f.ty.id, f.name.as_deref()));
183
184 self.call_data
185 .encode_as_fields_to(&mut fields, metadata.types(), out)?;
186 Ok(())
187 }
188
189 fn validation_details(&self) -> Option<ValidationDetails<'_>> {
190 self.validation_hash.map(|hash| ValidationDetails {
191 pallet_name: &self.pallet_name,
192 call_name: &self.call_name,
193 hash,
194 })
195 }
196}
197
198pub fn dynamic(
201 pallet_name: impl Into<String>,
202 call_name: impl Into<String>,
203 call_data: impl Into<Composite<()>>,
204) -> DynamicPayload {
205 DefaultPayload::new(pallet_name, call_name, call_data.into())
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211 use crate::metadata::Metadata;
212 use codec::Decode;
213 use scale_value::Composite;
214
215 fn test_metadata() -> Metadata {
216 let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
217 Metadata::decode(&mut &metadata_bytes[..]).expect("Valid metadata")
218 }
219
220 #[test]
221 fn encode_call_with_incompatible_types_returns_error() {
222 let metadata = test_metadata();
223
224 let incompatible_data = Composite::named([
225 ("dest", scale_value::Value::bool(true)), ("value", scale_value::Value::string("not_a_number")), ]);
228
229 let payload = DefaultPayload::new("Balances", "transfer_allow_death", incompatible_data);
230
231 let mut out = Vec::new();
232 let result = payload.encode_call_data_to(&metadata, &mut out);
233
234 assert!(
235 result.is_err(),
236 "Expected error when encoding with incompatible types"
237 );
238 }
239
240 #[test]
241 fn encode_call_with_valid_data_succeeds() {
242 let metadata = test_metadata();
243
244 let valid_address =
247 scale_value::Value::unnamed_variant("Id", [scale_value::Value::from_bytes([0u8; 32])]);
248
249 let valid_data = Composite::named([
250 ("dest", valid_address),
251 ("value", scale_value::Value::u128(1000)),
252 ]);
253
254 let payload = DefaultPayload::new("Balances", "transfer_allow_death", valid_data);
255
256 let mut out = Vec::new();
258 let result = payload.encode_call_data_to(&metadata, &mut out);
259
260 assert!(
261 result.is_ok(),
262 "Expected success when encoding with valid data"
263 );
264 assert!(!out.is_empty(), "Expected encoded output to be non-empty");
265 }
266}