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