From 1b47c9b8682459a66909986bf35809ac661b1270 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Thu, 30 Nov 2023 18:20:00 +0200 Subject: [PATCH 1/7] storage/fix: Use partial key instead of root key for iter Signed-off-by: Alexandru Vasile --- subxt/src/storage/storage_type.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subxt/src/storage/storage_type.rs b/subxt/src/storage/storage_type.rs index 43cc6e3367..f6a0b51555 100644 --- a/subxt/src/storage/storage_type.rs +++ b/subxt/src/storage/storage_type.rs @@ -229,7 +229,7 @@ where let return_type_id = return_type_from_storage_entry_type(entry.entry_type()); // The root pallet/entry bytes for this storage entry: - let address_root_bytes = super::utils::storage_address_root_bytes(&address); + let address_root_bytes = super::utils::storage_address_bytes(&address, &metadata)?; let s = client .backend() From 3a4a59d4a9f5cea439414f7959eda487b81a92b3 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Thu, 30 Nov 2023 18:20:08 +0200 Subject: [PATCH 2/7] storage: Allow partial key construction Signed-off-by: Alexandru Vasile --- subxt/src/storage/storage_address.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/subxt/src/storage/storage_address.rs b/subxt/src/storage/storage_address.rs index df6fdbb874..cdd3bf8c44 100644 --- a/subxt/src/storage/storage_address.rs +++ b/subxt/src/storage/storage_address.rs @@ -181,14 +181,6 @@ where _other => either::Either::Right(std::iter::once(*key_ty)), }; - if type_ids.len() != self.storage_entry_keys.len() { - return Err(StorageAddressError::WrongNumberOfKeys { - expected: type_ids.len(), - actual: self.storage_entry_keys.len(), - } - .into()); - } - if hashers.len() == 1 { // One hasher; hash a tuple of all SCALE encoded bytes with the one hash function. let mut input = Vec::new(); From 915aa0338a3d2b6997b3778edadb6f8f19573849 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Thu, 30 Nov 2023 18:20:12 +0200 Subject: [PATCH 3/7] storage: Allow less provided types than num of hashes Signed-off-by: Alexandru Vasile --- subxt/src/storage/storage_address.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/subxt/src/storage/storage_address.rs b/subxt/src/storage/storage_address.rs index cdd3bf8c44..ae68f65b99 100644 --- a/subxt/src/storage/storage_address.rs +++ b/subxt/src/storage/storage_address.rs @@ -190,7 +190,7 @@ where } hash_bytes(&input, &hashers[0], bytes); Ok(()) - } else if hashers.len() == type_ids.len() { + } else if hashers.len() >= type_ids.len() { let iter = self.storage_entry_keys.iter().zip(type_ids).zip(hashers); // A hasher per field; encode and hash each field independently. for ((key, type_id), hasher) in iter { @@ -200,7 +200,7 @@ where } Ok(()) } else { - // Mismatch; wrong number of hashers/fields. + // Provided more fields than hashers. Err(StorageAddressError::WrongNumberOfHashers { hashers: hashers.len(), fields: type_ids.len(), From fc76e833603b55a0c0fbce2ef3ff0a802ca79996 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Thu, 30 Nov 2023 18:20:17 +0200 Subject: [PATCH 4/7] storage: Error on more fields than types Signed-off-by: Alexandru Vasile --- subxt/src/storage/storage_address.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/subxt/src/storage/storage_address.rs b/subxt/src/storage/storage_address.rs index ae68f65b99..64529263a5 100644 --- a/subxt/src/storage/storage_address.rs +++ b/subxt/src/storage/storage_address.rs @@ -181,6 +181,15 @@ where _other => either::Either::Right(std::iter::once(*key_ty)), }; + if type_ids.len() < self.storage_entry_keys.len() { + // Provided more keys than fields. + return Err(StorageAddressError::WrongNumberOfKeys { + expected: type_ids.len(), + actual: self.storage_entry_keys.len(), + } + .into()); + } + if hashers.len() == 1 { // One hasher; hash a tuple of all SCALE encoded bytes with the one hash function. let mut input = Vec::new(); From 24ce668763665c3a74aee9715e2ff96920a96502 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Thu, 30 Nov 2023 18:20:27 +0200 Subject: [PATCH 5/7] testing: Check partial key iteration Signed-off-by: Alexandru Vasile --- .../src/full_client/storage/mod.rs | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/testing/integration-tests/src/full_client/storage/mod.rs b/testing/integration-tests/src/full_client/storage/mod.rs index dc697d6267..e1cec5f939 100644 --- a/testing/integration-tests/src/full_client/storage/mod.rs +++ b/testing/integration-tests/src/full_client/storage/mod.rs @@ -126,6 +126,79 @@ async fn storage_n_map_storage_lookup() -> Result<(), subxt::Error> { Ok(()) } +#[tokio::test] +async fn storage_partial_lookup() -> Result<(), subxt::Error> { + let ctx = test_context().await; + let api = ctx.client(); + + // Boilerplate; we create a new asset class with ID 99, and then + // we "approveTransfer" of some of this asset class. This gives us an + // entry in the `Approvals` StorageNMap that we can try to look up. + let signer = dev::alice(); + let alice: AccountId32 = dev::alice().public_key().into(); + let bob: AccountId32 = dev::bob().public_key().into(); + + // Create two assets; one with ID 99 and one with ID 100. + let assets = [ + (99, alice.clone(), bob.clone(), 123), + (100, bob.clone(), alice.clone(), 124), + ]; + for (asset_id, admin, delegate, amount) in assets.clone() { + let tx1 = node_runtime::tx() + .assets() + .create(asset_id, admin.into(), 1); + let tx2 = node_runtime::tx() + .assets() + .approve_transfer(asset_id, delegate.into(), amount); + api.tx() + .sign_and_submit_then_watch_default(&tx1, &signer) + .await? + .wait_for_finalized_success() + .await?; + api.tx() + .sign_and_submit_then_watch_default(&tx2, &signer) + .await? + .wait_for_finalized_success() + .await?; + } + + // Check all approvals. + let addr = node_runtime::storage().assets().approvals_iter(); + let addr_bytes = api.storage().address_bytes(&addr)?; + let mut results = api.storage().at_latest().await?.iter(addr).await?; + let mut approvals = Vec::new(); + while let Some(Ok((key, value))) = results.next().await { + assert!(key.starts_with(&addr_bytes)); + approvals.push(value); + } + assert_eq!(approvals.len(), assets.len()); + let mut amounts = approvals.iter().map(|a| a.amount).collect::>(); + amounts.sort(); + let mut expected = assets.iter().map(|a| a.3).collect::>(); + expected.sort(); + assert_eq!(amounts, expected); + + // Check all assets starting with ID 99. + for (asset_id, _, _, amount) in assets.clone() { + let addr = node_runtime::storage().assets().approvals_iter1(asset_id); + let second_addr_bytes = api.storage().address_bytes(&addr)?; + // Keys must be different, since we are adding to the root key. + assert_ne!(addr_bytes, second_addr_bytes); + + let mut results = api.storage().at_latest().await?.iter(addr).await?; + + let mut approvals = Vec::new(); + while let Some(Ok((key, value))) = results.next().await { + assert!(key.starts_with(&addr_bytes)); + approvals.push(value); + } + assert_eq!(approvals.len(), 1); + assert_eq!(approvals[0].amount, amount); + } + + Ok(()) +} + #[tokio::test] async fn storage_runtime_wasm_code() -> Result<(), subxt::Error> { let ctx = test_context().await; From bbd57729cbb35eb414e8331e0117f341f5b5f509 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Thu, 30 Nov 2023 18:20:33 +0200 Subject: [PATCH 6/7] storage: Rename variable wrt partial address bytes Signed-off-by: Alexandru Vasile --- subxt/src/storage/storage_type.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/subxt/src/storage/storage_type.rs b/subxt/src/storage/storage_type.rs index f6a0b51555..2669c1abce 100644 --- a/subxt/src/storage/storage_type.rs +++ b/subxt/src/storage/storage_type.rs @@ -228,12 +228,12 @@ where // in the iterator. let return_type_id = return_type_from_storage_entry_type(entry.entry_type()); - // The root pallet/entry bytes for this storage entry: - let address_root_bytes = super::utils::storage_address_bytes(&address, &metadata)?; + // The address bytes of this entry: + let address_bytes = super::utils::storage_address_bytes(&address, &metadata)?; let s = client .backend() - .storage_fetch_descendant_values(address_root_bytes, block_ref.hash()) + .storage_fetch_descendant_values(address_bytes, block_ref.hash()) .await? .map(move |kv| { let kv = match kv { From 05fb5360a9a7d241f68520a473db1c539d2ef16a Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Fri, 1 Dec 2023 13:42:17 +0200 Subject: [PATCH 7/7] storage: Identical storage key to root key if no keys are provided Signed-off-by: Alexandru Vasile --- subxt/src/storage/storage_address.rs | 6 ++++++ testing/integration-tests/src/full_client/client/mod.rs | 3 +++ 2 files changed, 9 insertions(+) diff --git a/subxt/src/storage/storage_address.rs b/subxt/src/storage/storage_address.rs index 64529263a5..893474b518 100644 --- a/subxt/src/storage/storage_address.rs +++ b/subxt/src/storage/storage_address.rs @@ -172,6 +172,12 @@ where .resolve(*key_ty) .ok_or(MetadataError::TypeNotFound(*key_ty))?; + // If the provided keys are empty, the storage address must be + // equal to the storage root address. + if self.storage_entry_keys.is_empty() { + return Ok(()); + } + // If the key is a tuple, we encode each value to the corresponding tuple type. // If the key is not a tuple, encode a single value to the key type. let type_ids = match &ty.type_def { diff --git a/testing/integration-tests/src/full_client/client/mod.rs b/testing/integration-tests/src/full_client/client/mod.rs index dd4fc71dff..cd9208da38 100644 --- a/testing/integration-tests/src/full_client/client/mod.rs +++ b/testing/integration-tests/src/full_client/client/mod.rs @@ -45,6 +45,9 @@ async fn storage_iter() { let api = ctx.client(); let addr = node_runtime::storage().system().account_iter(); + let addr_bytes = api.storage().address_bytes(&addr).unwrap(); + assert_eq!(addr_bytes, addr.to_root_bytes()); + let len = api .storage() .at_latest()