1use crate::{
21 generic::CheckedExtrinsic,
22 traits::{
23 self, Checkable, Extrinsic, ExtrinsicMetadata, IdentifyAccount, MaybeDisplay, Member,
24 SignaturePayload, SignedExtension,
25 },
26 transaction_validity::{InvalidTransaction, TransactionValidityError},
27 OpaqueExtrinsic,
28};
29#[cfg(all(not(feature = "std"), feature = "serde"))]
30use alloc::format;
31use alloc::{vec, vec::Vec};
32use codec::{Compact, Decode, Encode, EncodeLike, Error, Input};
33use core::fmt;
34use scale_info::{build::Fields, meta_type, Path, StaticTypeInfo, Type, TypeInfo, TypeParameter};
35use sp_io::hashing::blake2_256;
36
37const EXTRINSIC_FORMAT_VERSION: u8 = 4;
43
44type UncheckedSignaturePayload<Address, Signature, Extra> = (Address, Signature, Extra);
46
47#[cfg_attr(all(feature = "std", not(windows)), doc = simple_mermaid::mermaid!("../../docs/mermaid/extrinsics.mmd"))]
62#[derive(PartialEq, Eq, Clone)]
70pub struct UncheckedExtrinsic<Address, Call, Signature, Extra>
71where
72 Extra: SignedExtension,
73{
74 pub signature: Option<UncheckedSignaturePayload<Address, Signature, Extra>>,
79 pub function: Call,
81}
82
83impl<Address: TypeInfo, Signature: TypeInfo, Extra: TypeInfo> SignaturePayload
84 for UncheckedSignaturePayload<Address, Signature, Extra>
85{
86 type SignatureAddress = Address;
87 type Signature = Signature;
88 type SignatureExtra = Extra;
89}
90
91impl<Address, Call, Signature, Extra> TypeInfo
96 for UncheckedExtrinsic<Address, Call, Signature, Extra>
97where
98 Address: StaticTypeInfo,
99 Call: StaticTypeInfo,
100 Signature: StaticTypeInfo,
101 Extra: SignedExtension + StaticTypeInfo,
102{
103 type Identity = UncheckedExtrinsic<Address, Call, Signature, Extra>;
104
105 fn type_info() -> Type {
106 Type::builder()
107 .path(Path::new("UncheckedExtrinsic", module_path!()))
108 .type_params(vec![
112 TypeParameter::new("Address", Some(meta_type::<Address>())),
113 TypeParameter::new("Call", Some(meta_type::<Call>())),
114 TypeParameter::new("Signature", Some(meta_type::<Signature>())),
115 TypeParameter::new("Extra", Some(meta_type::<Extra>())),
116 ])
117 .docs(&["UncheckedExtrinsic raw bytes, requires custom decoding routine"])
118 .composite(Fields::unnamed().field(|f| f.ty::<Vec<u8>>()))
122 }
123}
124
125impl<Address, Call, Signature, Extra: SignedExtension>
126 UncheckedExtrinsic<Address, Call, Signature, Extra>
127{
128 pub fn new_signed(function: Call, signed: Address, signature: Signature, extra: Extra) -> Self {
130 Self { signature: Some((signed, signature, extra)), function }
131 }
132
133 pub fn new_unsigned(function: Call) -> Self {
135 Self { signature: None, function }
136 }
137}
138
139impl<Address: TypeInfo, Call: TypeInfo, Signature: TypeInfo, Extra: SignedExtension + TypeInfo>
140 Extrinsic for UncheckedExtrinsic<Address, Call, Signature, Extra>
141{
142 type Call = Call;
143
144 type SignaturePayload = UncheckedSignaturePayload<Address, Signature, Extra>;
145
146 fn is_signed(&self) -> Option<bool> {
147 Some(self.signature.is_some())
148 }
149
150 fn new(function: Call, signed_data: Option<Self::SignaturePayload>) -> Option<Self> {
151 Some(if let Some((address, signature, extra)) = signed_data {
152 Self::new_signed(function, address, signature, extra)
153 } else {
154 Self::new_unsigned(function)
155 })
156 }
157}
158
159impl<LookupSource, AccountId, Call, Signature, Extra, Lookup> Checkable<Lookup>
160 for UncheckedExtrinsic<LookupSource, Call, Signature, Extra>
161where
162 LookupSource: Member + MaybeDisplay,
163 Call: Encode + Member,
164 Signature: Member + traits::Verify,
165 <Signature as traits::Verify>::Signer: IdentifyAccount<AccountId = AccountId>,
166 Extra: SignedExtension<AccountId = AccountId>,
167 AccountId: Member + MaybeDisplay,
168 Lookup: traits::Lookup<Source = LookupSource, Target = AccountId>,
169{
170 type Checked = CheckedExtrinsic<AccountId, Call, Extra>;
171
172 fn check(self, lookup: &Lookup) -> Result<Self::Checked, TransactionValidityError> {
173 Ok(match self.signature {
174 Some((signed, signature, extra)) => {
175 let signed = lookup.lookup(signed)?;
176 let raw_payload = SignedPayload::new(self.function, extra)?;
177 if !raw_payload.using_encoded(|payload| signature.verify(payload, &signed)) {
178 return Err(InvalidTransaction::BadProof.into())
179 }
180
181 let (function, extra, _) = raw_payload.deconstruct();
182 CheckedExtrinsic { signed: Some((signed, extra)), function }
183 },
184 None => CheckedExtrinsic { signed: None, function: self.function },
185 })
186 }
187
188 #[cfg(feature = "try-runtime")]
189 fn unchecked_into_checked_i_know_what_i_am_doing(
190 self,
191 lookup: &Lookup,
192 ) -> Result<Self::Checked, TransactionValidityError> {
193 Ok(match self.signature {
194 Some((signed, _, extra)) => {
195 let signed = lookup.lookup(signed)?;
196 let raw_payload = SignedPayload::new(self.function, extra)?;
197 let (function, extra, _) = raw_payload.deconstruct();
198 CheckedExtrinsic { signed: Some((signed, extra)), function }
199 },
200 None => CheckedExtrinsic { signed: None, function: self.function },
201 })
202 }
203}
204
205impl<Address, Call, Signature, Extra> ExtrinsicMetadata
206 for UncheckedExtrinsic<Address, Call, Signature, Extra>
207where
208 Extra: SignedExtension,
209{
210 const VERSION: u8 = EXTRINSIC_FORMAT_VERSION;
211 type SignedExtensions = Extra;
212}
213
214pub struct SignedPayload<Call, Extra: SignedExtension>((Call, Extra, Extra::AdditionalSigned));
220
221impl<Call, Extra> SignedPayload<Call, Extra>
222where
223 Call: Encode,
224 Extra: SignedExtension,
225{
226 pub fn new(call: Call, extra: Extra) -> Result<Self, TransactionValidityError> {
230 let additional_signed = extra.additional_signed()?;
231 let raw_payload = (call, extra, additional_signed);
232 Ok(Self(raw_payload))
233 }
234
235 pub fn from_raw(call: Call, extra: Extra, additional_signed: Extra::AdditionalSigned) -> Self {
237 Self((call, extra, additional_signed))
238 }
239
240 pub fn deconstruct(self) -> (Call, Extra, Extra::AdditionalSigned) {
242 self.0
243 }
244}
245
246impl<Call, Extra> Encode for SignedPayload<Call, Extra>
247where
248 Call: Encode,
249 Extra: SignedExtension,
250{
251 fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
255 self.0.using_encoded(|payload| {
256 if payload.len() > 256 {
257 f(&blake2_256(payload)[..])
258 } else {
259 f(payload)
260 }
261 })
262 }
263}
264
265impl<Call, Extra> EncodeLike for SignedPayload<Call, Extra>
266where
267 Call: Encode,
268 Extra: SignedExtension,
269{
270}
271
272impl<Address, Call, Signature, Extra> Decode for UncheckedExtrinsic<Address, Call, Signature, Extra>
273where
274 Address: Decode,
275 Signature: Decode,
276 Call: Decode,
277 Extra: SignedExtension,
278{
279 fn decode<I: Input>(input: &mut I) -> Result<Self, Error> {
280 let expected_length: Compact<u32> = Decode::decode(input)?;
284 let before_length = input.remaining_len()?;
285
286 let version = input.read_byte()?;
287
288 let is_signed = version & 0b1000_0000 != 0;
289 let version = version & 0b0111_1111;
290 if version != EXTRINSIC_FORMAT_VERSION {
291 return Err("Invalid transaction version".into())
292 }
293
294 let signature = is_signed.then(|| Decode::decode(input)).transpose()?;
295 let function = Decode::decode(input)?;
296
297 if let Some((before_length, after_length)) =
298 input.remaining_len()?.and_then(|a| before_length.map(|b| (b, a)))
299 {
300 let length = before_length.saturating_sub(after_length);
301
302 if length != expected_length.0 as usize {
303 return Err("Invalid length prefix".into())
304 }
305 }
306
307 Ok(Self { signature, function })
308 }
309}
310
311#[docify::export(unchecked_extrinsic_encode_impl)]
312impl<Address, Call, Signature, Extra> Encode for UncheckedExtrinsic<Address, Call, Signature, Extra>
313where
314 Address: Encode,
315 Signature: Encode,
316 Call: Encode,
317 Extra: SignedExtension,
318{
319 fn encode(&self) -> Vec<u8> {
320 let mut tmp = Vec::with_capacity(core::mem::size_of::<Self>());
321
322 match self.signature.as_ref() {
324 Some(s) => {
325 tmp.push(EXTRINSIC_FORMAT_VERSION | 0b1000_0000);
326 s.encode_to(&mut tmp);
327 },
328 None => {
329 tmp.push(EXTRINSIC_FORMAT_VERSION & 0b0111_1111);
330 },
331 }
332 self.function.encode_to(&mut tmp);
333
334 let compact_len = codec::Compact::<u32>(tmp.len() as u32);
335
336 let mut output = Vec::with_capacity(compact_len.size_hint() + tmp.len());
338
339 compact_len.encode_to(&mut output);
340 output.extend(tmp);
341
342 output
343 }
344}
345
346impl<Address, Call, Signature, Extra> EncodeLike
347 for UncheckedExtrinsic<Address, Call, Signature, Extra>
348where
349 Address: Encode,
350 Signature: Encode,
351 Call: Encode,
352 Extra: SignedExtension,
353{
354}
355
356#[cfg(feature = "serde")]
357impl<Address: Encode, Signature: Encode, Call: Encode, Extra: SignedExtension> serde::Serialize
358 for UncheckedExtrinsic<Address, Call, Signature, Extra>
359{
360 fn serialize<S>(&self, seq: S) -> Result<S::Ok, S::Error>
361 where
362 S: ::serde::Serializer,
363 {
364 self.using_encoded(|bytes| seq.serialize_bytes(bytes))
365 }
366}
367
368#[cfg(feature = "serde")]
369impl<'a, Address: Decode, Signature: Decode, Call: Decode, Extra: SignedExtension>
370 serde::Deserialize<'a> for UncheckedExtrinsic<Address, Call, Signature, Extra>
371{
372 fn deserialize<D>(de: D) -> Result<Self, D::Error>
373 where
374 D: serde::Deserializer<'a>,
375 {
376 let r = sp_core::bytes::deserialize(de)?;
377 Decode::decode(&mut &r[..])
378 .map_err(|e| serde::de::Error::custom(format!("Decode error: {}", e)))
379 }
380}
381
382impl<Address, Call, Signature, Extra> fmt::Debug
383 for UncheckedExtrinsic<Address, Call, Signature, Extra>
384where
385 Address: fmt::Debug,
386 Call: fmt::Debug,
387 Extra: SignedExtension,
388{
389 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
390 write!(
391 f,
392 "UncheckedExtrinsic({:?}, {:?})",
393 self.signature.as_ref().map(|x| (&x.0, &x.2)),
394 self.function,
395 )
396 }
397}
398
399impl<Address, Call, Signature, Extra> From<UncheckedExtrinsic<Address, Call, Signature, Extra>>
400 for OpaqueExtrinsic
401where
402 Address: Encode,
403 Signature: Encode,
404 Call: Encode,
405 Extra: SignedExtension,
406{
407 fn from(extrinsic: UncheckedExtrinsic<Address, Call, Signature, Extra>) -> Self {
408 Self::from_bytes(extrinsic.encode().as_slice()).expect(
409 "both OpaqueExtrinsic and UncheckedExtrinsic have encoding that is compatible with \
410 raw Vec<u8> encoding; qed",
411 )
412 }
413}
414
415#[cfg(test)]
416mod tests {
417 use super::*;
418 use crate::{
419 codec::{Decode, Encode},
420 testing::TestSignature as TestSig,
421 traits::{DispatchInfoOf, IdentityLookup, SignedExtension},
422 };
423 use sp_io::hashing::blake2_256;
424
425 type TestContext = IdentityLookup<u64>;
426 type TestAccountId = u64;
427 type TestCall = Vec<u8>;
428
429 const TEST_ACCOUNT: TestAccountId = 0;
430
431 #[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, Ord, PartialOrd, TypeInfo)]
433 struct TestExtra;
434 impl SignedExtension for TestExtra {
435 const IDENTIFIER: &'static str = "TestExtra";
436 type AccountId = u64;
437 type Call = ();
438 type AdditionalSigned = ();
439 type Pre = ();
440
441 fn additional_signed(&self) -> core::result::Result<(), TransactionValidityError> {
442 Ok(())
443 }
444
445 fn pre_dispatch(
446 self,
447 who: &Self::AccountId,
448 call: &Self::Call,
449 info: &DispatchInfoOf<Self::Call>,
450 len: usize,
451 ) -> Result<Self::Pre, TransactionValidityError> {
452 self.validate(who, call, info, len).map(|_| ())
453 }
454 }
455
456 type Ex = UncheckedExtrinsic<TestAccountId, TestCall, TestSig, TestExtra>;
457 type CEx = CheckedExtrinsic<TestAccountId, TestCall, TestExtra>;
458
459 #[test]
460 fn unsigned_codec_should_work() {
461 let ux = Ex::new_unsigned(vec![0u8; 0]);
462 let encoded = ux.encode();
463 assert_eq!(Ex::decode(&mut &encoded[..]), Ok(ux));
464 }
465
466 #[test]
467 fn invalid_length_prefix_is_detected() {
468 let ux = Ex::new_unsigned(vec![0u8; 0]);
469 let mut encoded = ux.encode();
470
471 let length = Compact::<u32>::decode(&mut &encoded[..]).unwrap();
472 Compact(length.0 + 10).encode_to(&mut &mut encoded[..1]);
473
474 assert_eq!(Ex::decode(&mut &encoded[..]), Err("Invalid length prefix".into()));
475 }
476
477 #[test]
478 fn signed_codec_should_work() {
479 let ux = Ex::new_signed(
480 vec![0u8; 0],
481 TEST_ACCOUNT,
482 TestSig(TEST_ACCOUNT, (vec![0u8; 0], TestExtra).encode()),
483 TestExtra,
484 );
485 let encoded = ux.encode();
486 assert_eq!(Ex::decode(&mut &encoded[..]), Ok(ux));
487 }
488
489 #[test]
490 fn large_signed_codec_should_work() {
491 let ux = Ex::new_signed(
492 vec![0u8; 0],
493 TEST_ACCOUNT,
494 TestSig(
495 TEST_ACCOUNT,
496 (vec![0u8; 257], TestExtra).using_encoded(blake2_256)[..].to_owned(),
497 ),
498 TestExtra,
499 );
500 let encoded = ux.encode();
501 assert_eq!(Ex::decode(&mut &encoded[..]), Ok(ux));
502 }
503
504 #[test]
505 fn unsigned_check_should_work() {
506 let ux = Ex::new_unsigned(vec![0u8; 0]);
507 assert!(!ux.is_signed().unwrap_or(false));
508 assert!(<Ex as Checkable<TestContext>>::check(ux, &Default::default()).is_ok());
509 }
510
511 #[test]
512 fn badly_signed_check_should_fail() {
513 let ux = Ex::new_signed(
514 vec![0u8; 0],
515 TEST_ACCOUNT,
516 TestSig(TEST_ACCOUNT, vec![0u8; 0]),
517 TestExtra,
518 );
519 assert!(ux.is_signed().unwrap_or(false));
520 assert_eq!(
521 <Ex as Checkable<TestContext>>::check(ux, &Default::default()),
522 Err(InvalidTransaction::BadProof.into()),
523 );
524 }
525
526 #[test]
527 fn signed_check_should_work() {
528 let ux = Ex::new_signed(
529 vec![0u8; 0],
530 TEST_ACCOUNT,
531 TestSig(TEST_ACCOUNT, (vec![0u8; 0], TestExtra).encode()),
532 TestExtra,
533 );
534 assert!(ux.is_signed().unwrap_or(false));
535 assert_eq!(
536 <Ex as Checkable<TestContext>>::check(ux, &Default::default()),
537 Ok(CEx { signed: Some((TEST_ACCOUNT, TestExtra)), function: vec![0u8; 0] }),
538 );
539 }
540
541 #[test]
542 fn encoding_matches_vec() {
543 let ex = Ex::new_unsigned(vec![0u8; 0]);
544 let encoded = ex.encode();
545 let decoded = Ex::decode(&mut encoded.as_slice()).unwrap();
546 assert_eq!(decoded, ex);
547 let as_vec: Vec<u8> = Decode::decode(&mut encoded.as_slice()).unwrap();
548 assert_eq!(as_vec.encode(), encoded);
549 }
550
551 #[test]
552 fn conversion_to_opaque() {
553 let ux = Ex::new_unsigned(vec![0u8; 0]);
554 let encoded = ux.encode();
555 let opaque: OpaqueExtrinsic = ux.into();
556 let opaque_encoded = opaque.encode();
557 assert_eq!(opaque_encoded, encoded);
558 }
559
560 #[test]
561 fn large_bad_prefix_should_work() {
562 let encoded = Compact::<u32>::from(u32::MAX).encode();
563 assert_eq!(
564 Ex::decode(&mut &encoded[..]),
565 Err(Error::from("Not enough data to fill buffer"))
566 );
567 }
568}