avail_rust_core/
decoded_transaction.rs1use 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 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>>;
17 fn decode_hex_call(call: &str) -> Option<Box<Self>>;
22 fn decode_call_data(call_data: &[u8]) -> Option<Box<Self>>;
24 fn decode_hex_transaction(transaction: &str) -> Option<Box<Self>>;
29 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 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 try_decode_transaction(call_data)
75 }
76}
77
78#[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#[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 pub signature: Option<TransactionSigned>,
105 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 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 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 let serialized = serde_json::to_string(&tx).unwrap();
310 assert_eq!(serialized.trim_matches('"'), expected_serialized);
311
312 let tx_deserialized: Transaction = serde_json::from_str(&serialized).unwrap();
314 assert_eq!(encoded_tx, tx_deserialized.encode());
315
316 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 let opaque_deserialized: OpaqueTransaction = serde_json::from_str(&serialized).unwrap();
323 assert_eq!(encoded_tx, opaque_deserialized.encode());
324 }
325}