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