diff --git a/prdoc/pr_10390.prdoc b/prdoc/pr_10390.prdoc
new file mode 100644
index 0000000000000..f7d267c13d1cf
--- /dev/null
+++ b/prdoc/pr_10390.prdoc
@@ -0,0 +1,9 @@
+title: 'UncheckedExtrinsic: Ensure that encoding leads to same bytes an ext was decoded
+ from'
+doc:
+- audience: Runtime Dev
+ description: |-
+ This pull request ensures that when decoding an `UncheckedExtrinsic` from a certain byte string, it also encodes to the same byte string. This is achieved by keeping the original bytes used to decode the `call` around and then directly encode these.
+crates:
+- name: sp-runtime
+ bump: major
diff --git a/substrate/primitives/runtime/src/generic/mod.rs b/substrate/primitives/runtime/src/generic/mod.rs
index 449e876eb8024..9e9da2643494b 100644
--- a/substrate/primitives/runtime/src/generic/mod.rs
+++ b/substrate/primitives/runtime/src/generic/mod.rs
@@ -34,7 +34,8 @@ pub use self::{
era::{Era, Phase},
header::Header,
unchecked_extrinsic::{
- ExtensionVersion, Preamble, SignedPayload, UncheckedExtrinsic, EXTRINSIC_FORMAT_VERSION,
+ CallAndMaybeEncoded, ExtensionVersion, Preamble, SignedPayload, UncheckedExtrinsic,
+ EXTRINSIC_FORMAT_VERSION,
},
};
pub use unchecked_extrinsic::UncheckedSignaturePayload;
diff --git a/substrate/primitives/runtime/src/generic/unchecked_extrinsic.rs b/substrate/primitives/runtime/src/generic/unchecked_extrinsic.rs
index b97b78dc372e0..01f4bf9c50488 100644
--- a/substrate/primitives/runtime/src/generic/unchecked_extrinsic.rs
+++ b/substrate/primitives/runtime/src/generic/unchecked_extrinsic.rs
@@ -34,7 +34,7 @@ use codec::{
Compact, CountedInput, Decode, DecodeWithMemLimit, DecodeWithMemTracking, Encode, EncodeLike,
Input,
};
-use core::fmt;
+use core::fmt::{self, Debug};
use scale_info::{build::Fields, meta_type, Path, StaticTypeInfo, Type, TypeInfo, TypeParameter};
use sp_io::hashing::blake2_256;
use sp_weights::Weight;
@@ -226,7 +226,7 @@ where
/// This can be checked using [`Checkable`], yielding a [`CheckedExtrinsic`], which is the
/// counterpart of this type after its signature (and other non-negotiable validity checks) have
/// passed.
-#[derive(DecodeWithMemTracking, PartialEq, Eq, Clone, Debug)]
+#[derive(DecodeWithMemTracking, Eq, Clone)]
#[codec(decode_with_mem_tracking_bound(
Address: DecodeWithMemTracking,
Call: DecodeWithMemTracking,
@@ -245,6 +245,43 @@ pub struct UncheckedExtrinsic<
pub preamble: Preamble
,
/// The function that should be called.
pub function: Call,
+ /// Stores the raw encoded call.
+ ///
+ /// This is mainly interesting if this extrinsic was created by decoding it from bytes. In this
+ /// case this field should be set to `Some` holding the original bytes used to decode the
+ /// [`Self::function`]. This is done to protect against decode implementations of `Call` that
+ /// are not bijective (encodes to the exact same bytes it was encoded from). If this `field`
+ /// is set, it is being used when re-encoding this transaction.
+ pub encoded_call: Option>,
+}
+
+impl<
+ Address: Debug,
+ Call: Debug,
+ Signature: Debug,
+ Extension: Debug,
+ const MAX_CALL_SIZE: usize,
+ > Debug for UncheckedExtrinsic
+{
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("UncheckedExtrinsic")
+ .field("preamble", &self.preamble)
+ .field("function", &self.function)
+ .finish()
+ }
+}
+
+impl<
+ Address: PartialEq,
+ Call: PartialEq,
+ Signature: PartialEq,
+ Extension: PartialEq,
+ const MAX_CALL_SIZE: usize,
+ > PartialEq for UncheckedExtrinsic
+{
+ fn eq(&self, other: &Self) -> bool {
+ self.preamble == other.preamble && self.function == other.function
+ }
}
/// Manual [`TypeInfo`] implementation because of custom encoding. The data is a valid encoded
@@ -305,7 +342,7 @@ impl
/// Create an `UncheckedExtrinsic` from a `Preamble` and the actual `Call`.
pub fn from_parts(function: Call, preamble: Preamble) -> Self {
- Self { preamble, function }
+ Self { preamble, function, encoded_call: None }
}
/// New instance of a bare (ne unsigned) extrinsic.
@@ -328,7 +365,7 @@ impl
Self::from_parts(function, Preamble::Signed(signed, signature, tx_ext))
}
- /// New instance of an new-school unsigned transaction.
+ /// New instance of a new-school unsigned transaction.
pub fn new_transaction(function: Call, tx_ext: Extension) -> Self {
Self::from_parts(function, Preamble::General(EXTENSION_VERSION, tx_ext))
}
@@ -341,15 +378,47 @@ impl
let mut input = CountedInput::new(input);
let preamble = Decode::decode(&mut input)?;
+
+ struct CloneBytes<'a, I>(&'a mut I, Vec);
+ impl Input for CloneBytes<'_, I> {
+ fn remaining_len(&mut self) -> Result