avail_rust_core/
decoded_transaction.rs

1use super::transaction::{AlreadyEncoded, EXTRINSIC_FORMAT_VERSION, TransactionSigned};
2use crate::TransactionCall;
3use codec::{Compact, Decode, Encode, Error, Input};
4use serde::{Deserialize, Serialize};
5
6pub trait HasTxDispatchIndex {
7	// Pallet ID, Call ID
8	const DISPATCH_INDEX: (u8, u8);
9}
10
11pub trait TransactionConvertible {
12	fn to_call(&self) -> TransactionCall;
13}
14
15pub trait TransactionDecodable {
16	/// Decodes the SCALE encoded Transaction Call
17	///
18	/// If you need to decode hex string call `decode_hex_call`
19	fn decode_call(call: &[u8]) -> Option<Box<Self>>;
20
21	/// Decodes the Hex and SCALE encoded Transaction Call
22	/// This is equal to Hex::decode + Self::decode_call
23	///
24	/// If you need to decode bytes call `decode_call`
25	fn decode_hex_call(call: &str) -> Option<Box<Self>>;
26
27	/// Decodes only the SCALE encoded Transaction Call Data
28	fn decode_call_data(call_data: &[u8]) -> Option<Box<Self>>;
29
30	/// Decodes the whole Hex and SCALE encoded Transaction.
31	/// This is equal to Hex::decode + OpaqueTransaction::try_from + Self::decode_call
32	///
33	/// If you need to decode bytes call `decode_transaction`
34	fn decode_hex_transaction(transaction: &str) -> Option<Box<Self>>;
35
36	/// Decodes the whole SCALE encoded Transaction.
37	/// This is equal to OpaqueTransaction::try_from + Self::decode_call
38	///
39	/// If you need to decode Hex string call `decode_hex_transaction`
40	fn decode_transaction(transaction: &[u8]) -> Option<Box<Self>>;
41}
42
43impl<T: HasTxDispatchIndex + Encode> TransactionConvertible for T {
44	fn to_call(&self) -> TransactionCall {
45		TransactionCall::new(Self::DISPATCH_INDEX.0, Self::DISPATCH_INDEX.1, self.encode())
46	}
47}
48
49impl<T: HasTxDispatchIndex + Decode> TransactionDecodable for T {
50	#[inline(always)]
51	fn decode_hex_transaction(transaction: &str) -> Option<Box<T>> {
52		let opaque = OpaqueTransaction::try_from(transaction).ok()?;
53		Self::decode_call(&opaque.call)
54	}
55
56	#[inline(always)]
57	fn decode_transaction(transaction_bytes: &[u8]) -> Option<Box<T>> {
58		let opaque = OpaqueTransaction::try_from(transaction_bytes).ok()?;
59		Self::decode_call(&opaque.call)
60	}
61
62	#[inline(always)]
63	fn decode_hex_call(call: &str) -> Option<Box<T>> {
64		let hex_decoded = hex::decode(call.trim_start_matches("0x")).ok()?;
65		Self::decode_call(&hex_decoded)
66	}
67
68	fn decode_call(call: &[u8]) -> Option<Box<T>> {
69		// This was moved out in order to decrease compilation times
70		if !tx_filter_in(call, Self::DISPATCH_INDEX) {
71			return None;
72		}
73
74		if call.len() <= 2 {
75			try_decode_transaction(&[])
76		} else {
77			try_decode_transaction(&call[2..])
78		}
79	}
80
81	fn decode_call_data(call_data: &[u8]) -> Option<Box<Self>> {
82		// This was moved out in order to decrease compilation times
83		try_decode_transaction(call_data)
84	}
85}
86
87// Purely here to decrease compilation times
88#[inline(never)]
89fn try_decode_transaction<T: Decode>(mut event_data: &[u8]) -> Option<Box<T>> {
90	T::decode(&mut event_data).ok().map(Box::new)
91}
92
93// Purely here to decrease compilation times
94#[inline(never)]
95fn tx_filter_in(call: &[u8], dispatch_index: (u8, u8)) -> bool {
96	if call.len() < 3 {
97		return false;
98	}
99
100	let (pallet_id, call_id) = (call[0], call[1]);
101	if dispatch_index.0 != pallet_id || dispatch_index.1 != call_id {
102		return false;
103	}
104
105	true
106}
107
108#[derive(Clone)]
109pub struct OpaqueTransaction {
110	/// The signature, address, number of extrinsics have come before from
111	/// the same signer and an era describing the longevity of this transaction,
112	/// if this is a signed extrinsic.
113	pub signature: Option<TransactionSigned>,
114	/// The function that should be called.
115	pub call: Vec<u8>,
116}
117
118impl OpaqueTransaction {
119	pub fn pallet_index(&self) -> u8 {
120		self.call[0]
121	}
122
123	pub fn call_index(&self) -> u8 {
124		self.call[1]
125	}
126}
127
128impl TryFrom<Vec<u8>> for OpaqueTransaction {
129	type Error = codec::Error;
130
131	fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
132		Self::try_from(value.as_slice())
133	}
134}
135
136impl TryFrom<&Vec<u8>> for OpaqueTransaction {
137	type Error = codec::Error;
138
139	fn try_from(value: &Vec<u8>) -> Result<Self, Self::Error> {
140		Self::try_from(value.as_slice())
141	}
142}
143
144impl TryFrom<&[u8]> for OpaqueTransaction {
145	type Error = codec::Error;
146
147	fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
148		let mut value = value;
149		Self::decode(&mut value)
150	}
151}
152
153impl TryFrom<String> for OpaqueTransaction {
154	type Error = codec::Error;
155
156	fn try_from(value: String) -> Result<Self, Self::Error> {
157		Self::try_from(value.as_str())
158	}
159}
160
161impl TryFrom<&str> for OpaqueTransaction {
162	type Error = codec::Error;
163
164	fn try_from(value: &str) -> Result<Self, Self::Error> {
165		let Ok(hex_decoded) = hex::decode(value.trim_start_matches("0x")) else {
166			return Err("Failed to hex decode transaction".into());
167		};
168
169		Self::decode(&mut hex_decoded.as_slice())
170	}
171}
172
173impl Decode for OpaqueTransaction {
174	fn decode<I: Input>(input: &mut I) -> Result<Self, Error> {
175		// This is a little more complicated than usual since the binary format must be compatible
176		// with SCALE's generic `Vec<u8>` type. Basically this just means accepting that there
177		// will be a prefix of vector length.
178		let expected_length: Compact<u32> = Decode::decode(input)?;
179		let before_length = input.remaining_len()?;
180
181		let version = input.read_byte()?;
182
183		let is_signed = version & 0b1000_0000 != 0;
184		let version = version & 0b0111_1111;
185		if version != EXTRINSIC_FORMAT_VERSION {
186			return Err("Invalid transaction version".into());
187		}
188
189		let signature = is_signed.then(|| Decode::decode(input)).transpose()?;
190		let call: AlreadyEncoded = Decode::decode(input)?;
191
192		if let Some((before_length, after_length)) = input.remaining_len()?.and_then(|a| before_length.map(|b| (b, a)))
193		{
194			let length = before_length.saturating_sub(after_length);
195
196			if length != expected_length.0 as usize {
197				return Err("Invalid length prefix".into());
198			}
199		}
200
201		Ok(Self {
202			signature,
203			call: call.0,
204		})
205	}
206}
207
208impl Encode for OpaqueTransaction {
209	fn encode_to<T: codec::Output + ?Sized>(&self, dest: &mut T) {
210		let mut encoded_tx_inner = Vec::new();
211		if let Some(signed) = &self.signature {
212			0x84u8.encode_to(&mut encoded_tx_inner);
213			signed.address.encode_to(&mut encoded_tx_inner);
214			signed.signature.encode_to(&mut encoded_tx_inner);
215			signed.tx_extra.encode_to(&mut encoded_tx_inner);
216		} else {
217			0x4u8.encode_to(&mut encoded_tx_inner);
218		}
219
220		encoded_tx_inner.extend(&self.call);
221		let mut encoded_tx = Compact(encoded_tx_inner.len() as u32).encode();
222		encoded_tx.append(&mut encoded_tx_inner);
223
224		dest.write(&encoded_tx)
225	}
226}
227
228impl Serialize for OpaqueTransaction {
229	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
230	where
231		S: serde::Serializer,
232	{
233		let bytes = self.encode();
234		impl_serde::serialize::serialize(&bytes, serializer)
235	}
236}
237
238impl<'a> Deserialize<'a> for OpaqueTransaction {
239	fn deserialize<D>(de: D) -> Result<Self, D::Error>
240	where
241		D: serde::Deserializer<'a>,
242	{
243		let r = impl_serde::serialize::deserialize(de)?;
244		Decode::decode(&mut &r[..]).map_err(|e| serde::de::Error::custom(format!("Decode error: {}", e)))
245	}
246}
247
248#[cfg(test)]
249pub mod test {
250	use super::TransactionConvertible;
251	use std::borrow::Cow;
252
253	use codec::Encode;
254	use subxt_core::utils::{AccountId32, Era};
255
256	use crate::{
257		MultiAddress, MultiSignature, Transaction, TransactionExtra, avail::data_availability::tx::SubmitData,
258		decoded_transaction::OpaqueTransaction, transaction::TransactionSigned,
259	};
260
261	#[test]
262	fn test_encoding_decoding() {
263		let call = SubmitData { data: vec![0, 1, 2, 3] }.to_call();
264
265		let account_id = AccountId32([1u8; 32]);
266		let signature = [1u8; 64];
267		let signed = TransactionSigned {
268			address: MultiAddress::Id(account_id),
269			signature: MultiSignature::Sr25519(signature),
270			tx_extra: TransactionExtra {
271				era: Era::Mortal { period: 4, phase: 2 },
272				nonce: 1,
273				tip: 2u128,
274				app_id: 3,
275			},
276		};
277
278		let tx = Transaction {
279			signed: Some(signed.clone()),
280			call: Cow::Owned(call.clone()),
281		};
282
283		let encoded_tx = tx.encode();
284
285		// Opaque Transaction
286		let opaque = OpaqueTransaction::try_from(&encoded_tx).unwrap();
287		let opaque_encoded = opaque.encode();
288
289		assert_eq!(encoded_tx, opaque_encoded);
290	}
291
292	#[test]
293	fn test_serialize_deserialize() {
294		let call = SubmitData { data: vec![0, 1, 2, 3] }.to_call();
295
296		let account_id = AccountId32([1u8; 32]);
297		let signature = [1u8; 64];
298		let signed = TransactionSigned {
299			address: MultiAddress::Id(account_id),
300			signature: MultiSignature::Sr25519(signature),
301			tx_extra: TransactionExtra {
302				era: Era::Mortal { period: 4, phase: 2 },
303				nonce: 1,
304				tip: 2u128,
305				app_id: 3,
306			},
307		};
308
309		let tx = Transaction {
310			signed: Some(signed.clone()),
311			call: Cow::Owned(call.clone()),
312		};
313
314		let encoded_tx = tx.encode();
315		let expected_serialized = std::format!("0x{}", hex::encode(&encoded_tx));
316
317		// Transaction Serialized
318		let serialized = serde_json::to_string(&tx).unwrap();
319		assert_eq!(serialized.trim_matches('"'), expected_serialized);
320
321		// Transaction Deserialized
322		let tx_deserialized: Transaction = serde_json::from_str(&serialized).unwrap();
323		assert_eq!(encoded_tx, tx_deserialized.encode());
324
325		// Opaque Serialized
326		let opaque = OpaqueTransaction::try_from(&encoded_tx).unwrap();
327		let serialized = serde_json::to_string(&opaque).unwrap();
328		assert_eq!(serialized.trim_matches('"'), expected_serialized);
329
330		// Opaque Deserialized
331		let opaque_deserialized: OpaqueTransaction = serde_json::from_str(&serialized).unwrap();
332		assert_eq!(encoded_tx, opaque_deserialized.encode());
333	}
334}