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