diff --git a/subxt/src/storage/storage_type.rs b/subxt/src/storage/storage_type.rs index 7beb923873..1c510428bc 100644 --- a/subxt/src/storage/storage_type.rs +++ b/subxt/src/storage/storage_type.rs @@ -3,6 +3,7 @@ // see LICENSE for license details. use super::storage_address::{StorageAddress, Yes}; + use crate::{ client::OnlineClientT, error::{Error, MetadataError}, @@ -10,6 +11,7 @@ use crate::{ rpc::types::{StorageData, StorageKey}, Config, }; +use codec::Decode; use derivative::Derivative; use std::{future::Future, marker::PhantomData}; use subxt_metadata::{PalletMetadata, StorageEntryMetadata, StorageEntryType}; @@ -237,6 +239,42 @@ where }) } } + + /// The storage version of a pallet. + /// The storage version refers to the `frame_support::traits::Metadata::StorageVersion` type. + pub async fn storage_version(&self, pallet_name: impl AsRef) -> Result { + // check that the pallet exists in the metadata: + self.client + .metadata() + .pallet_by_name(pallet_name.as_ref()) + .ok_or_else(|| MetadataError::PalletNameNotFound(pallet_name.as_ref().into()))?; + + // construct the storage key. This is done similarly in `frame_support::traits::metadata::StorageVersion::storage_key()`. + pub const STORAGE_VERSION_STORAGE_KEY_POSTFIX: &[u8] = b":__STORAGE_VERSION__:"; + let mut key_bytes: Vec = vec![]; + key_bytes.extend(&sp_core_hashing::twox_128(pallet_name.as_ref().as_bytes())); + key_bytes.extend(&sp_core_hashing::twox_128( + STORAGE_VERSION_STORAGE_KEY_POSTFIX, + )); + + // fetch the raw bytes and decode them into the StorageVersion struct: + let storage_version_bytes = self.fetch_raw(&key_bytes).await?.ok_or_else(|| { + format!( + "Unexpected: entry for storage version in pallet \"{}\" not found", + pallet_name.as_ref() + ) + })?; + u16::decode(&mut &storage_version_bytes[..]).map_err(Into::into) + } + + /// Fetches the Wasm code of the runtime. + pub async fn runtime_wasm_code(&self) -> Result, Error> { + // note: this should match the `CODE` constant in `sp_core::storage::well_known_keys` + const CODE: &str = ":code"; + self.fetch_raw(CODE.as_bytes()).await?.ok_or_else(|| { + format!("Unexpected: entry for well known key \"{CODE}\" not found").into() + }) + } } /// Iterates over key value pairs in a map. @@ -338,7 +376,7 @@ fn validate_storage( hash: [u8; 32], ) -> Result<(), Error> { let Some(expected_hash) = pallet.storage_hash(storage_name) else { - return Err(MetadataError::IncompatibleCodegen.into()) + return Err(MetadataError::IncompatibleCodegen.into()); }; if expected_hash != hash { return Err(MetadataError::IncompatibleCodegen.into()); diff --git a/subxt/src/tx/tx_progress.rs b/subxt/src/tx/tx_progress.rs index 561bec2f95..a93d6fefe6 100644 --- a/subxt/src/tx/tx_progress.rs +++ b/subxt/src/tx/tx_progress.rs @@ -389,7 +389,7 @@ impl> TxInBlock { .iter() .position(|ext| { use crate::config::Hasher; - let Ok((_,stripped)) = strip_compact_prefix(&ext.0) else { + let Ok((_, stripped)) = strip_compact_prefix(&ext.0) else { return false; }; let hash = T::Hasher::hash_of(&stripped); diff --git a/subxt/src/utils/wrapper_opaque.rs b/subxt/src/utils/wrapper_opaque.rs index 982a551094..9257405715 100644 --- a/subxt/src/utils/wrapper_opaque.rs +++ b/subxt/src/utils/wrapper_opaque.rs @@ -83,7 +83,7 @@ impl EncodeAsType for WrapperKeepOpaque { use scale_encode::error::{Error, ErrorKind, Kind}; let Some(ty) = types.resolve(type_id) else { - return Err(Error::new(ErrorKind::TypeNotFound(type_id))) + return Err(Error::new(ErrorKind::TypeNotFound(type_id))); }; // Do a basic check that the target shape lines up. @@ -91,7 +91,7 @@ impl EncodeAsType for WrapperKeepOpaque { return Err(Error::new(ErrorKind::WrongShape { actual: Kind::Struct, expected: type_id, - })) + })); }; // Check that the name also lines up. diff --git a/testing/integration-tests/src/full_client/storage/mod.rs b/testing/integration-tests/src/full_client/storage/mod.rs index 125d44a0a2..dc697d6267 100644 --- a/testing/integration-tests/src/full_client/storage/mod.rs +++ b/testing/integration-tests/src/full_client/storage/mod.rs @@ -125,3 +125,33 @@ async fn storage_n_map_storage_lookup() -> Result<(), subxt::Error> { assert_eq!(entry.map(|a| a.amount), Some(123)); Ok(()) } + +#[tokio::test] +async fn storage_runtime_wasm_code() -> Result<(), subxt::Error> { + let ctx = test_context().await; + let api = ctx.client(); + let wasm_blob = api.storage().at_latest().await?.runtime_wasm_code().await?; + assert!(wasm_blob.len() > 1000); // the wasm should be super big + Ok(()) +} + +#[tokio::test] +async fn storage_pallet_storage_version() -> Result<(), subxt::Error> { + let ctx = test_context().await; + let api = ctx.client(); + + // cannot assume anything about version number, but should work to fetch it + let _version = api + .storage() + .at_latest() + .await? + .storage_version("System") + .await?; + let _version = api + .storage() + .at_latest() + .await? + .storage_version("Balances") + .await?; + Ok(()) +}