From 6a9e5c2e7dd24c0be804897da7b5ce0671a7d5af Mon Sep 17 00:00:00 2001 From: DenzelPenzel Date: Thu, 13 Aug 2026 22:02:05 +0100 Subject: [PATCH 1/2] feat: support custom transaction extension values --- subxt/src/config.rs | 3 +- .../config/default_transaction_extensions.rs | 574 +++++++++++++++++- 2 files changed, 555 insertions(+), 22 deletions(-) diff --git a/subxt/src/config.rs b/subxt/src/config.rs index 90d7c9efed0..459a1efe6a7 100644 --- a/subxt/src/config.rs +++ b/subxt/src/config.rs @@ -63,7 +63,8 @@ use std::{fmt::Display, marker::PhantomData}; use subxt_rpcs::RpcConfig; pub use default_transaction_extensions::{ - DefaultExtrinsicParamsBuilder, DefaultTransactionExtensions, + DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder, DefaultTransactionExtensions, + KnownDefaultExtrinsicParams, KnownDefaultTransactionExtensions, }; pub use polkadot::{PolkadotConfig, PolkadotExtrinsicParams, PolkadotExtrinsicParamsBuilder}; pub use substrate::{SubstrateConfig, SubstrateExtrinsicParams, SubstrateExtrinsicParamsBuilder}; diff --git a/subxt/src/config/default_transaction_extensions.rs b/subxt/src/config/default_transaction_extensions.rs index 1ee7b4bdc5f..1254b76e915 100644 --- a/subxt/src/config/default_transaction_extensions.rs +++ b/subxt/src/config/default_transaction_extensions.rs @@ -2,14 +2,20 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -use super::{Config, HashFor}; -use super::{TransactionExtensions, transaction_extensions}; +use super::{Config, HashFor, TransactionExtensions, transaction_extensions}; +use crate::config::transaction_extension_traits::Params; use crate::config::transaction_extensions::CheckMortalityParams; +use crate::error::TransactionExtensionError; +use crate::transactions::DefaultParams; +use scale_encode::EncodeAsType; +use scale_info::PortableRegistry; +use scale_value::Value; +use std::collections::BTreeMap; -/// A set of transaction extensions which is applicable in most default cases. This can be used -/// as our [`Config::TransactionExtensions`] type, and then users can configure transactions by -/// using the corresponding [`DefaultExtrinsicParamsBuilder`]. -pub type DefaultTransactionExtensions = ( +/// The known transaction extensions used by [`DefaultTransactionExtensions`]. +/// +/// This is exposed for users who need to manually compose the default typed extensions. +pub type KnownDefaultTransactionExtensions = ( transaction_extensions::VerifySignature, transaction_extensions::CheckSpecVersion, transaction_extensions::CheckTxVersion, @@ -21,6 +27,194 @@ pub type DefaultTransactionExtensions = ( transaction_extensions::CheckMetadataHash, ); +/// The parameters used to construct [`KnownDefaultTransactionExtensions`]. +pub type KnownDefaultExtrinsicParams = + as TransactionExtensions>::Params; + +/// The default set of transaction extensions, along with any custom extensions supplied for a +/// specific transaction. +pub struct DefaultTransactionExtensions { + known: KnownDefaultTransactionExtensions, + custom: BTreeMap, +} + +/// Parameters used to construct [`DefaultTransactionExtensions`]. +pub struct DefaultExtrinsicParams { + known: KnownDefaultExtrinsicParams, + custom: Vec<(String, CustomTransactionExtensionValue)>, +} + +enum CustomTransactionExtensionValue { + Dynamic(Value), +} + +impl DefaultExtrinsicParams { + /// Return the parameters for the known default transaction extensions. + pub fn known(&self) -> &KnownDefaultExtrinsicParams { + &self.known + } +} + +impl Default for DefaultExtrinsicParams { + fn default() -> Self { + DefaultExtrinsicParamsBuilder::new().build() + } +} + +impl DefaultParams for DefaultExtrinsicParams { + fn default_params() -> Self { + Self::default() + } +} + +impl Params for DefaultExtrinsicParams { + fn inject_account_nonce(&mut self, nonce: u64) { + self.known.inject_account_nonce(nonce); + } + + fn inject_block(&mut self, number: u64, hash: HashFor) { + self.known.inject_block(number, hash); + } +} + +impl TransactionExtensions for DefaultTransactionExtensions { + type Params = DefaultExtrinsicParams; + + fn new( + client: &super::ClientState, + params: Self::Params, + ) -> Result { + let known = as TransactionExtensions>::new( + client, + params.known, + )?; + let mut custom = BTreeMap::new(); + + for (name, value) in params.custom { + if frame_decode::extrinsics::TransactionExtensions::contains_extension(&known, &name) { + return Err(TransactionExtensionError::custom(format!( + "Custom transaction extension '{name}' conflicts with a known transaction extension" + ))); + } + if custom.insert(name.clone(), value).is_some() { + return Err(TransactionExtensionError::custom(format!( + "Custom transaction extension '{name}' was provided more than once" + ))); + } + } + + Ok(Self { known, custom }) + } + + fn inject_signature(&mut self, account_id: &T::AccountId, signature: &T::Signature) { + self.known.inject_signature(account_id, signature); + } +} + +impl DefaultTransactionExtensions { + fn encode_custom_value_to( + &self, + name: &str, + type_id: u32, + type_resolver: &PortableRegistry, + out: &mut Vec, + ) -> Result<(), frame_decode::extrinsics::TransactionExtensionsError> { + let value = self.custom.get(name).ok_or_else(|| { + frame_decode::extrinsics::TransactionExtensionsError::NotFound(name.to_owned()) + })?; + let original_len = out.len(); + let result = match value { + CustomTransactionExtensionValue::Dynamic(value) => { + value.encode_as_type_to(type_id, type_resolver, out) + } + }; + result.map_err(|error| { + out.truncate(original_len); + frame_decode::extrinsics::TransactionExtensionsError::Other { + extension_name: name.to_owned(), + error: Box::new(error), + } + }) + } +} + +impl frame_decode::extrinsics::TransactionExtensions + for DefaultTransactionExtensions +{ + fn contains_extension(&self, name: &str) -> bool { + frame_decode::extrinsics::TransactionExtensions::contains_extension(&self.known, name) + || self.custom.contains_key(name) + } + + fn encode_extension_value_to( + &self, + name: &str, + type_id: u32, + type_resolver: &PortableRegistry, + out: &mut Vec, + ) -> Result<(), frame_decode::extrinsics::TransactionExtensionsError> { + if frame_decode::extrinsics::TransactionExtensions::contains_extension(&self.known, name) { + frame_decode::extrinsics::TransactionExtensions::encode_extension_value_to( + &self.known, + name, + type_id, + type_resolver, + out, + ) + } else { + self.encode_custom_value_to(name, type_id, type_resolver, out) + } + } + + fn encode_extension_value_for_signer_payload_to( + &self, + name: &str, + type_id: u32, + type_resolver: &PortableRegistry, + out: &mut Vec, + ) -> Result<(), frame_decode::extrinsics::TransactionExtensionsError> { + if frame_decode::extrinsics::TransactionExtensions::contains_extension(&self.known, name) { + frame_decode::extrinsics::TransactionExtensions::encode_extension_value_for_signer_payload_to( + &self.known, + name, + type_id, + type_resolver, + out, + ) + } else { + self.encode_custom_value_to(name, type_id, type_resolver, out) + } + } + + fn encode_extension_implicit_to( + &self, + name: &str, + type_id: u32, + type_resolver: &PortableRegistry, + out: &mut Vec, + ) -> Result<(), frame_decode::extrinsics::TransactionExtensionsError> { + if frame_decode::extrinsics::TransactionExtensions::contains_extension(&self.known, name) { + frame_decode::extrinsics::TransactionExtensions::encode_extension_implicit_to( + &self.known, + name, + type_id, + type_resolver, + out, + ) + } else if self.custom.contains_key(name) { + Err(frame_decode::extrinsics::TransactionExtensionsError::Other { + extension_name: name.to_owned(), + error: format!( + "Custom transaction extension '{name}' requires non-empty implicit data, which is not supported" + ) + .into(), + }) + } else { + Err(frame_decode::extrinsics::TransactionExtensionsError::NotFound(name.to_owned())) + } + } +} + /// A builder that outputs the set of parameters required to configure transactions when /// [`DefaultTransactionExtensions`] is used. This may expose methods that aren't applicable /// to the current chain; such values will simply be ignored if so. @@ -34,6 +228,7 @@ pub struct DefaultExtrinsicParamsBuilder { tip_of: u128, /// A fallback tip used when no Asset ID is given (or the chain doesn't support it). tip: u128, + custom: Vec<(String, CustomTransactionExtensionValue)>, } impl Default for DefaultExtrinsicParamsBuilder { @@ -44,6 +239,7 @@ impl Default for DefaultExtrinsicParamsBuilder { tip_of: 0, tip_of_asset_id: None, nonce: None, + custom: Vec::new(), } } } @@ -119,8 +315,22 @@ impl DefaultExtrinsicParamsBuilder { self } + /// Provide a metadata-aware value for a custom transaction extension. + /// + /// Values for extensions which are not present in the runtime metadata are ignored. Custom + /// authorization extensions and extensions with non-empty implicit data are not supported. + /// Creating the transaction extensions returns an error if the name is provided more than once + /// or conflicts with one of the known default transaction extensions. + pub fn custom_extension(mut self, name: impl Into, value: impl Into) -> Self { + self.custom.push(( + name.into(), + CustomTransactionExtensionValue::Dynamic(value.into()), + )); + self + } + /// Build the extrinsic parameters. - pub fn build(self) -> as TransactionExtensions>::Params { + pub fn build(self) -> DefaultExtrinsicParams { let check_mortality_params = self.mortality; let charge_asset_tx_params = if let Some(asset_id) = self.tip_of_asset_id { @@ -138,33 +348,354 @@ impl DefaultExtrinsicParamsBuilder { transaction_extensions::CheckNonceParams::from_chain() }; - ( - (), - (), - (), - check_nonce_params, - (), - check_mortality_params, - charge_asset_tx_params, - charge_transaction_params, - (), - ) + DefaultExtrinsicParams { + known: ( + (), + (), + (), + check_nonce_params, + (), + check_mortality_params, + charge_asset_tx_params, + charge_transaction_params, + (), + ), + custom: self.custom, + } } } #[cfg(test)] mod test { use super::*; - use crate::config::PolkadotConfig; + use crate::config::polkadot::H256; + use crate::config::{ClientState, PolkadotConfig}; + use crate::metadata::Metadata; + use crate::utils::{AccountId32, MultiSignature}; + use assert_matches::assert_matches; + use codec::Decode; + use frame_decode::extrinsics::{ + ExtrinsicCallInfo, ExtrinsicEncodeError, ExtrinsicExtensionInfo, ExtrinsicExtensionInfoArg, + ExtrinsicSignatureInfo, TransactionExtensionsError, + }; + use scale_info::{MetaType, Registry}; + use scale_value::Composite; + use std::borrow::Cow; + use std::sync::Arc; fn assert_default(_t: T) {} + fn client_state() -> ClientState { + let metadata = Metadata::decode( + &mut &include_bytes!("../../../artifacts/polkadot_metadata_small.scale")[..], + ) + .unwrap(); + + ClientState { + genesis_hash: H256::zero(), + spec_version: 0, + transaction_version: 0, + metadata: Arc::new(metadata), + } + } + + fn type_info() -> (u32, PortableRegistry) { + let mut types = Registry::new(); + let id = types.register_type(&MetaType::new::()); + (id.id, types.into()) + } + + fn encoding_info() -> ( + ExtrinsicCallInfo<'static, u32>, + ExtrinsicExtensionInfo<'static, u32>, + ExtrinsicSignatureInfo, + PortableRegistry, + ) { + let mut types = Registry::new(); + let bool_id = types.register_type(&MetaType::new::()).id; + let unit_id = types.register_type(&MetaType::new::<()>()).id; + let u8_id = types.register_type(&MetaType::new::()).id; + + ( + ExtrinsicCallInfo { + pallet_index: 1, + call_index: 2, + pallet_name: Cow::Borrowed("Test"), + call_name: Cow::Borrowed("call"), + args: Vec::new(), + }, + ExtrinsicExtensionInfo { + extension_ids: vec![ExtrinsicExtensionInfoArg { + name: Cow::Borrowed("RestrictOrigins"), + id: bool_id, + implicit_id: unit_id, + }], + }, + ExtrinsicSignatureInfo { + address_id: u8_id, + signature_id: u8_id, + }, + types.into(), + ) + } + #[test] fn params_are_default() { let params = DefaultExtrinsicParamsBuilder::::new().build(); assert_default(params) } + // This is the symptom reported in https://github.com/paritytech/subxt/issues/2265: an extension + // that Subxt doesn't know about, whose value is neither empty nor an `Option<_>`, makes signing + // fail outright. Supplying a custom value is the only way out, so the failure must remain in + // place when no value is given + #[test] + fn unknown_extension_without_custom_value_still_errors() { + let params = DefaultExtrinsicParamsBuilder::::new().build(); + let extensions = DefaultTransactionExtensions::new(&client_state(), params).unwrap(); + let (call_info, extension_info, _, types) = encoding_info(); + let call_data = Composite::<()>::Unnamed(Vec::new()); + + let error = frame_decode::extrinsics::encode_v4_signer_payload_with_info( + &call_data, + &extensions, + &types, + &call_info, + &extension_info, + ) + .unwrap_err(); + + assert_matches!( + error, + ExtrinsicEncodeError::TransactionExtensions(TransactionExtensionsError::NotFound(name)) + if name == "RestrictOrigins" + ); + } + + #[test] + fn known_signer_payload_encoding_is_forwarded() { + let params = DefaultExtrinsicParamsBuilder::::new().build(); + let extensions = DefaultTransactionExtensions::new(&client_state(), params).unwrap(); + let mut out = vec![42]; + let (_, types) = type_info::(); + + frame_decode::extrinsics::TransactionExtensions::encode_extension_value_for_signer_payload_to( + &extensions, + "VerifyMultiSignature", + 0, + &types, + &mut out, + ) + .unwrap(); + + assert!(out.is_empty()); + } + + #[test] + fn signature_injection_is_forwarded() { + #[allow(dead_code)] + #[derive(scale_info::TypeInfo)] + enum SignatureDetails { + Signed { + signature: MultiSignature, + account: AccountId32, + }, + Disabled, + } + + let params = DefaultExtrinsicParamsBuilder::::new().build(); + let mut extensions = DefaultTransactionExtensions::new(&client_state(), params).unwrap(); + extensions.inject_signature( + &AccountId32::from([1; 32]), + &MultiSignature::Sr25519([2; 64]), + ); + let (type_id, types) = type_info::(); + let mut out = Vec::new(); + + frame_decode::extrinsics::TransactionExtensions::encode_extension_value_to( + &extensions, + "VerifyMultiSignature", + type_id, + &types, + &mut out, + ) + .unwrap(); + + let mut expected = vec![0, 1]; + expected.extend([2; 64]); + expected.extend([1; 32]); + assert_eq!(out, expected); + } + + #[test] + fn params_forward_injected_nonce_and_block() { + let mut params = DefaultExtrinsicParamsBuilder::::new() + .custom_extension("RestrictOrigins", true) + .build(); + params.inject_account_nonce(7); + params.inject_block(10, H256::repeat_byte(1)); + let extensions = DefaultTransactionExtensions::new(&client_state(), params).unwrap(); + let mut nonce = Vec::new(); + let mut mortality = Vec::new(); + let (_, types) = type_info::(); + + frame_decode::extrinsics::TransactionExtensions::encode_extension_value_to( + &extensions, + "CheckNonce", + 0, + &types, + &mut nonce, + ) + .unwrap(); + frame_decode::extrinsics::TransactionExtensions::encode_extension_value_to( + &extensions, + "CheckMortality", + 0, + &types, + &mut mortality, + ) + .unwrap(); + + assert_eq!(nonce, [28]); + assert_ne!(mortality, [0]); + } + + #[test] + fn custom_extension_cannot_override_known_extension() { + let params = DefaultExtrinsicParamsBuilder::::new() + .custom_extension("CheckNonce", 1u128) + .build(); + + let error = DefaultTransactionExtensions::new(&client_state(), params) + .err() + .unwrap(); + + assert!(error.to_string().contains("conflicts with a known")); + } + + #[test] + fn custom_extension_name_cannot_be_repeated() { + let params = DefaultExtrinsicParamsBuilder::::new() + .custom_extension("RestrictOrigins", true) + .custom_extension("RestrictOrigins", false) + .build(); + + let error = DefaultTransactionExtensions::new(&client_state(), params) + .err() + .unwrap(); + + assert!(error.to_string().contains("provided more than once")); + } + + #[test] + fn contains_known_and_custom_extensions() { + let params = DefaultExtrinsicParamsBuilder::::new() + .custom_extension("RestrictOrigins", true) + .build(); + let extensions = DefaultTransactionExtensions::new(&client_state(), params).unwrap(); + + assert!( + frame_decode::extrinsics::TransactionExtensions::contains_extension( + &extensions, + "CheckNonce" + ) + ); + assert!( + frame_decode::extrinsics::TransactionExtensions::contains_extension( + &extensions, + "RestrictOrigins" + ) + ); + assert!( + !frame_decode::extrinsics::TransactionExtensions::contains_extension( + &extensions, + "Unknown" + ) + ); + } + + #[test] + fn nonempty_custom_implicit_has_a_specific_error() { + let params = DefaultExtrinsicParamsBuilder::::new() + .custom_extension("Custom", true) + .build(); + let extensions = DefaultTransactionExtensions::new(&client_state(), params).unwrap(); + let (type_id, types) = type_info::(); + let mut out = Vec::new(); + + let error = frame_decode::extrinsics::TransactionExtensions::encode_extension_implicit_to( + &extensions, + "Custom", + type_id, + &types, + &mut out, + ) + .unwrap_err(); + + assert!( + error + .to_string() + .contains("requires non-empty implicit data") + ); + } + + #[test] + fn custom_encoding_error_does_not_modify_output() { + let params = DefaultExtrinsicParamsBuilder::::new() + .custom_extension("Custom", Value::u128(1)) + .build(); + let extensions = DefaultTransactionExtensions::new(&client_state(), params).unwrap(); + let (type_id, types) = type_info::(); + let mut out = vec![42]; + + frame_decode::extrinsics::TransactionExtensions::encode_extension_value_to( + &extensions, + "Custom", + type_id, + &types, + &mut out, + ) + .unwrap_err(); + + assert_eq!(out, [42]); + } + + #[test] + fn custom_extension_is_used_in_v4_payload_and_extrinsic() { + let params = DefaultExtrinsicParamsBuilder::::new() + .custom_extension("RestrictOrigins", true) + .build(); + let extensions = DefaultTransactionExtensions::new(&client_state(), params).unwrap(); + let (call_info, extension_info, signature_info, types) = encoding_info(); + let call_data = Composite::<()>::Unnamed(Vec::new()); + + let payload = frame_decode::extrinsics::encode_v4_signer_payload_with_info( + &call_data, + &extensions, + &types, + &call_info, + &extension_info, + ) + .unwrap(); + let mut extrinsic = Vec::new(); + frame_decode::extrinsics::encode_v4_signed_with_info_to( + &call_data, + &extensions, + &3u8, + &4u8, + &types, + &call_info, + &signature_info, + &extension_info, + &mut extrinsic, + ) + .unwrap(); + let inner = Vec::::decode(&mut &*extrinsic).unwrap(); + + assert_eq!(payload, [1, 2, 1]); + assert_eq!(inner, [0x84, 3, 4, 1, 1, 2]); + } + #[test] fn tip_of_sets_correct_tip_on_charge_asset_tx_payment() { let params = DefaultExtrinsicParamsBuilder::::new() @@ -173,9 +704,10 @@ mod test { .build(); // Type signatures here ensure we're getting the params we think we are: - let charge_asset_params: &transaction_extensions::ChargeAssetTxPaymentParams<_> = ¶ms.6; + let known = params.known(); + let charge_asset_params: &transaction_extensions::ChargeAssetTxPaymentParams<_> = &known.6; let charge_transaction_params: &transaction_extensions::ChargeTransactionPaymentParams = - ¶ms.7; + &known.7; // Verify that the params are properly set: assert_eq!( From ce9a555d4d6ca0ac95fc56176c6c6aca3edc830a Mon Sep 17 00:00:00 2001 From: DenzelPenzel Date: Fri, 14 Aug 2026 14:34:38 +0100 Subject: [PATCH 2/2] fix: preserve access to known transaction extension params --- .../config/default_transaction_extensions.rs | 90 ++++++++++--------- subxt/src/config/transaction_extensions.rs | 10 ++- 2 files changed, 54 insertions(+), 46 deletions(-) diff --git a/subxt/src/config/default_transaction_extensions.rs b/subxt/src/config/default_transaction_extensions.rs index 1254b76e915..0cb6b956662 100644 --- a/subxt/src/config/default_transaction_extensions.rs +++ b/subxt/src/config/default_transaction_extensions.rs @@ -49,10 +49,24 @@ enum CustomTransactionExtensionValue { } impl DefaultExtrinsicParams { + /// Construct parameters from the known default transaction extension parameters. + pub fn from_known(known: KnownDefaultExtrinsicParams) -> Self { + Self { + known, + custom: Vec::new(), + } + } + /// Return the parameters for the known default transaction extensions. pub fn known(&self) -> &KnownDefaultExtrinsicParams { &self.known } + + /// Return a mutable reference to the parameters for the known default + /// transaction extensions. + pub fn known_mut(&mut self) -> &mut KnownDefaultExtrinsicParams { + &mut self.known + } } impl Default for DefaultExtrinsicParams { @@ -146,27 +160,14 @@ impl frame_decode::extrinsics::TransactionExtensions, - ) -> Result<(), frame_decode::extrinsics::TransactionExtensionsError> { - if frame_decode::extrinsics::TransactionExtensions::contains_extension(&self.known, name) { - frame_decode::extrinsics::TransactionExtensions::encode_extension_value_to( - &self.known, - name, - type_id, - type_resolver, - out, - ) - } else { - self.encode_custom_value_to(name, type_id, type_resolver, out) - } + fn is_authorization_extension(&self, name: &str) -> bool { + frame_decode::extrinsics::TransactionExtensions::is_authorization_extension( + &self.known, + name, + ) } - fn encode_extension_value_for_signer_payload_to( + fn encode_extension_value_to( &self, name: &str, type_id: u32, @@ -174,7 +175,7 @@ impl frame_decode::extrinsics::TransactionExtensions, ) -> Result<(), frame_decode::extrinsics::TransactionExtensionsError> { if frame_decode::extrinsics::TransactionExtensions::contains_extension(&self.known, name) { - frame_decode::extrinsics::TransactionExtensions::encode_extension_value_for_signer_payload_to( + frame_decode::extrinsics::TransactionExtensions::encode_extension_value_to( &self.known, name, type_id, @@ -317,10 +318,8 @@ impl DefaultExtrinsicParamsBuilder { /// Provide a metadata-aware value for a custom transaction extension. /// - /// Values for extensions which are not present in the runtime metadata are ignored. Custom - /// authorization extensions and extensions with non-empty implicit data are not supported. - /// Creating the transaction extensions returns an error if the name is provided more than once - /// or conflicts with one of the known default transaction extensions. + /// Values absent from runtime metadata are ignored. Known or duplicate names and extensions + /// with non-empty implicit data are rejected. Custom authorization extensions are unsupported. pub fn custom_extension(mut self, name: impl Into, value: impl Into) -> Self { self.custom.push(( name.into(), @@ -445,10 +444,6 @@ mod test { assert_default(params) } - // This is the symptom reported in https://github.com/paritytech/subxt/issues/2265: an extension - // that Subxt doesn't know about, whose value is neither empty nor an `Option<_>`, makes signing - // fail outright. Supplying a custom value is the only way out, so the failure must remain in - // place when no value is given #[test] fn unknown_extension_without_custom_value_still_errors() { let params = DefaultExtrinsicParamsBuilder::::new().build(); @@ -473,22 +468,24 @@ mod test { } #[test] - fn known_signer_payload_encoding_is_forwarded() { - let params = DefaultExtrinsicParamsBuilder::::new().build(); + fn authorization_extension_check_is_forwarded() { + let params = DefaultExtrinsicParamsBuilder::::new() + .custom_extension("RestrictOrigins", true) + .build(); let extensions = DefaultTransactionExtensions::new(&client_state(), params).unwrap(); - let mut out = vec![42]; - let (_, types) = type_info::(); - - frame_decode::extrinsics::TransactionExtensions::encode_extension_value_for_signer_payload_to( - &extensions, - "VerifyMultiSignature", - 0, - &types, - &mut out, - ) - .unwrap(); - - assert!(out.is_empty()); + assert!( + frame_decode::extrinsics::TransactionExtensions::is_authorization_extension( + &extensions, + "VerifyMultiSignature" + ) + ); + // Custom extensions are never authorization extensions. + assert!( + !frame_decode::extrinsics::TransactionExtensions::is_authorization_extension( + &extensions, + "RestrictOrigins" + ) + ); } #[test] @@ -648,7 +645,7 @@ mod test { let (type_id, types) = type_info::(); let mut out = vec![42]; - frame_decode::extrinsics::TransactionExtensions::encode_extension_value_to( + let error = frame_decode::extrinsics::TransactionExtensions::encode_extension_value_to( &extensions, "Custom", type_id, @@ -658,6 +655,11 @@ mod test { .unwrap_err(); assert_eq!(out, [42]); + assert_matches!( + error, + TransactionExtensionsError::Other { extension_name, .. } + if extension_name == "Custom" + ); } #[test] diff --git a/subxt/src/config/transaction_extensions.rs b/subxt/src/config/transaction_extensions.rs index 1fbb475936e..12afbf5f475 100644 --- a/subxt/src/config/transaction_extensions.rs +++ b/subxt/src/config/transaction_extensions.rs @@ -704,7 +704,10 @@ mod test { let ext = VerifySignature::(VerifySignatureDetails::Disabled); let mut buf = Vec::new(); ext.encode_implicit_to(0, ®istry, &mut buf).unwrap(); - assert!(buf.is_empty(), "VerifyMultiSignature implicit should be empty (Disabled)"); + assert!( + buf.is_empty(), + "VerifyMultiSignature implicit should be empty (Disabled)" + ); // After inject_signature: implicit should still be empty. let mut ext = VerifySignature::(VerifySignatureDetails::Disabled); @@ -715,7 +718,10 @@ mod test { ext.inject_signature(&account, &signature); let mut buf = Vec::new(); ext.encode_implicit_to(0, ®istry, &mut buf).unwrap(); - assert!(buf.is_empty(), "VerifyMultiSignature implicit should be empty (Signed)"); + assert!( + buf.is_empty(), + "VerifyMultiSignature implicit should be empty (Signed)" + ); } #[test]