apex_sdk_substrate/
storage.rs

1//! Substrate storage queries and pallet interaction
2//!
3//! This module provides functionality for querying chain storage including:
4//! - Account information and balances
5//! - Storage item queries
6//! - Runtime constants
7//! - Metadata inspection
8
9use crate::{Error, Metrics, Result};
10use subxt::dynamic::At as _;
11use subxt::{OnlineClient, PolkadotConfig};
12use tracing::debug;
13
14/// Storage query client for accessing chain storage
15pub struct StorageClient {
16    client: OnlineClient<PolkadotConfig>,
17    metrics: Metrics,
18}
19
20impl StorageClient {
21    /// Create a new storage client
22    pub fn new(client: OnlineClient<PolkadotConfig>, metrics: Metrics) -> Self {
23        Self { client, metrics }
24    }
25
26    /// Query account information including balance and nonce
27    pub async fn get_account_info(&self, address: &str) -> Result<AccountInfo> {
28        debug!("Querying account info for: {}", address);
29        self.metrics.record_storage_query();
30
31        // Parse SS58 address to get AccountId32
32        use sp_core::crypto::{AccountId32, Ss58Codec};
33        let account_id = AccountId32::from_ss58check(address)
34            .map_err(|e| Error::Storage(format!("Invalid SS58 address: {}", e)))?;
35
36        // Query System::Account storage using dynamic API
37        let account_bytes: &[u8] = account_id.as_ref();
38        let storage_query = subxt::dynamic::storage(
39            "System",
40            "Account",
41            vec![subxt::dynamic::Value::from_bytes(account_bytes)],
42        );
43
44        let storage = self
45            .client
46            .storage()
47            .at_latest()
48            .await
49            .map_err(|e| Error::Connection(format!("Failed to fetch latest block: {}", e)))?;
50
51        let result = storage
52            .fetch(&storage_query)
53            .await
54            .map_err(|e| Error::Storage(format!("Failed to query account info: {}", e)))?;
55
56        // Decode the result
57        if let Some(value) = result {
58            // The System::Account storage returns AccountInfo structure
59            // We need to decode it from the dynamic value
60            let account_data = value
61                .to_value()
62                .map_err(|e| Error::Storage(format!("Failed to decode account value: {}", e)))?;
63
64            // Extract fields from the composite value
65            let nonce = extract_u64(&account_data, &["nonce"])
66                .ok_or_else(|| Error::Storage("Failed to extract 'nonce' field".to_string()))?;
67            let consumers = extract_u32(&account_data, &["consumers"])
68                .ok_or_else(|| Error::Storage("Failed to extract 'consumers' field".to_string()))?;
69            let providers = extract_u32(&account_data, &["providers"])
70                .ok_or_else(|| Error::Storage("Failed to extract 'providers' field".to_string()))?;
71            let sufficients = extract_u32(&account_data, &["sufficients"]).ok_or_else(|| {
72                Error::Storage("Failed to extract 'sufficients' field".to_string())
73            })?;
74
75            // Extract balance data (nested in "data" field)
76            let free = extract_u128(&account_data, &["data", "free"])
77                .ok_or_else(|| Error::Storage("Failed to extract 'data.free' field".to_string()))?;
78            let reserved = extract_u128(&account_data, &["data", "reserved"]).ok_or_else(|| {
79                Error::Storage("Failed to extract 'data.reserved' field".to_string())
80            })?;
81            let frozen = extract_u128(&account_data, &["data", "frozen"]).ok_or_else(|| {
82                Error::Storage("Failed to extract 'data.frozen' field".to_string())
83            })?;
84
85            Ok(AccountInfo {
86                nonce,
87                consumers,
88                providers,
89                sufficients,
90                free,
91                reserved,
92                frozen,
93            })
94        } else {
95            // Account doesn't exist, return default
96            debug!("Account {} not found, returning default", address);
97            Ok(AccountInfo::default())
98        }
99    }
100
101    /// Query account balance (free balance only)
102    pub async fn get_balance(&self, address: &str) -> Result<u128> {
103        let account_info = self.get_account_info(address).await?;
104        Ok(account_info.free)
105    }
106
107    /// Query account nonce
108    pub async fn get_nonce(&self, address: &str) -> Result<u64> {
109        let account_info = self.get_account_info(address).await?;
110        Ok(account_info.nonce)
111    }
112
113    /// Query a storage value by pallet and item name
114    pub async fn query_storage(
115        &self,
116        pallet: &str,
117        item: &str,
118        keys: Vec<subxt::dynamic::Value>,
119    ) -> Result<Option<Vec<u8>>> {
120        debug!("Querying storage: {}::{}", pallet, item);
121        self.metrics.record_storage_query();
122
123        let storage_query = subxt::dynamic::storage(pallet, item, keys);
124
125        let storage = self
126            .client
127            .storage()
128            .at_latest()
129            .await
130            .map_err(|e| Error::Connection(format!("Failed to fetch latest block: {}", e)))?;
131
132        let result = storage.fetch(&storage_query).await.map_err(|e| {
133            Error::Storage(format!(
134                "Failed to query storage {}::{}: {}",
135                pallet, item, e
136            ))
137        })?;
138
139        Ok(result.map(|v| v.encoded().to_vec()))
140    }
141
142    /// Get a runtime constant (returns raw bytes)
143    pub fn get_constant(&self, pallet: &str, constant: &str) -> Result<Vec<u8>> {
144        debug!("Getting constant: {}::{}", pallet, constant);
145        self.metrics.record_storage_query();
146
147        let constant_address = subxt::dynamic::constant(pallet, constant);
148
149        let value = self
150            .client
151            .constants()
152            .at(&constant_address)
153            .map_err(|e| Error::Storage(format!("Failed to get constant: {}", e)))?;
154
155        Ok(value.encoded().to_vec())
156    }
157
158    /// Get the existential deposit (minimum balance to keep account alive)
159    pub fn get_existential_deposit(&self) -> Result<u128> {
160        let value = self.get_constant("Balances", "ExistentialDeposit")?;
161
162        // Decode the constant value as u128 (SCALE encoded)
163        decode_u128_from_bytes(&value).ok_or_else(|| {
164            Error::Storage("Failed to decode ExistentialDeposit as u128".to_string())
165        })
166    }
167
168    /// Query storage at a specific block hash
169    pub async fn query_storage_at_block(
170        &self,
171        block_hash_hex: &str,
172        pallet: &str,
173        item: &str,
174        keys: Vec<subxt::dynamic::Value>,
175    ) -> Result<Option<Vec<u8>>> {
176        debug!(
177            "Querying storage at block {} for: {}::{}",
178            block_hash_hex, pallet, item
179        );
180        self.metrics.record_storage_query();
181
182        // Parse the block hash
183        let block_hash = parse_block_hash(block_hash_hex)?;
184
185        let storage_query = subxt::dynamic::storage(pallet, item, keys);
186
187        let result = self
188            .client
189            .storage()
190            .at(block_hash)
191            .fetch(&storage_query)
192            .await
193            .map_err(|e| {
194                Error::Storage(format!(
195                    "Failed to query storage {}::{} at block: {}",
196                    pallet, item, e
197                ))
198            })?;
199
200        Ok(result.map(|v| v.encoded().to_vec()))
201    }
202
203    /// Iterate over storage entries and return their keys and values
204    pub async fn iter_storage(&self, pallet: &str, item: &str) -> Result<Vec<(Vec<u8>, Vec<u8>)>> {
205        debug!("Iterating storage: {}::{}", pallet, item);
206        self.metrics.record_storage_query();
207
208        let storage_query =
209            subxt::dynamic::storage(pallet, item, Vec::<subxt::dynamic::Value>::new());
210
211        let mut results = Vec::new();
212        let storage = self
213            .client
214            .storage()
215            .at_latest()
216            .await
217            .map_err(|e| Error::Connection(format!("Failed to fetch latest block: {}", e)))?;
218
219        let mut iter = storage.iter(storage_query).await.map_err(|e| {
220            Error::Storage(format!(
221                "Failed to iterate storage {}::{}: {}",
222                pallet, item, e
223            ))
224        })?;
225
226        while let Some(result) = iter.next().await {
227            let kv_pair = result
228                .map_err(|e| Error::Storage(format!("Failed to fetch storage entry: {}", e)))?;
229            results.push((kv_pair.key_bytes, kv_pair.value.encoded().to_vec()));
230        }
231
232        debug!("Found {} entries in {}::{}", results.len(), pallet, item);
233        Ok(results)
234    }
235
236    /// Get metadata about a pallet
237    pub fn get_pallet_metadata(&self, pallet: &str) -> Result<PalletMetadata> {
238        debug!("Getting pallet metadata: {}", pallet);
239
240        let metadata = self.client.metadata();
241
242        // Check if pallet exists
243        let pallet_metadata = metadata
244            .pallet_by_name(pallet)
245            .ok_or_else(|| Error::Metadata(format!("Pallet '{}' not found", pallet)))?;
246
247        // Extract values before returning
248        let name = pallet.to_string();
249        let index = pallet_metadata.index();
250        let storage_count = pallet_metadata
251            .storage()
252            .map(|s| s.entries().len())
253            .unwrap_or(0);
254        let call_count = pallet_metadata
255            .call_variants()
256            .map(|c| c.len())
257            .unwrap_or(0);
258        let event_count = pallet_metadata
259            .event_variants()
260            .map(|e| e.len())
261            .unwrap_or(0);
262        let constant_count = pallet_metadata.constants().len();
263        let error_count = pallet_metadata
264            .error_variants()
265            .map(|e| e.len())
266            .unwrap_or(0);
267
268        Ok(PalletMetadata {
269            name,
270            index,
271            storage_count,
272            call_count,
273            event_count,
274            constant_count,
275            error_count,
276        })
277    }
278
279    /// List all available pallets
280    pub fn list_pallets(&self) -> Vec<String> {
281        let metadata = self.client.metadata();
282        metadata.pallets().map(|p| p.name().to_string()).collect()
283    }
284}
285
286/// Account information structure
287#[derive(Debug, Clone, Default)]
288pub struct AccountInfo {
289    /// The number of transactions this account has sent
290    pub nonce: u64,
291    /// The number of other modules that currently depend on this account's existence
292    pub consumers: u32,
293    /// The number of other modules that allow this account to exist
294    pub providers: u32,
295    /// The number of modules that allow this account to exist for their own purposes
296    pub sufficients: u32,
297    /// Free balance
298    pub free: u128,
299    /// Reserved balance (locked for staking, governance, etc.)
300    pub reserved: u128,
301    /// Frozen balance (for vesting, etc.)
302    pub frozen: u128,
303}
304
305impl AccountInfo {
306    /// Get total balance (free + reserved)
307    pub fn total(&self) -> u128 {
308        self.free.saturating_add(self.reserved)
309    }
310
311    /// Get transferable balance (free - frozen)
312    pub fn transferable(&self) -> u128 {
313        self.free.saturating_sub(self.frozen)
314    }
315}
316
317/// Pallet metadata information
318#[derive(Debug, Clone)]
319pub struct PalletMetadata {
320    /// Pallet name
321    pub name: String,
322    /// Pallet index
323    pub index: u8,
324    /// Number of storage items
325    pub storage_count: usize,
326    /// Number of callable functions
327    pub call_count: usize,
328    /// Number of events
329    pub event_count: usize,
330    /// Number of constants
331    pub constant_count: usize,
332    /// Number of errors
333    pub error_count: usize,
334}
335
336/// Storage query helper
337pub struct StorageQuery {
338    pallet: String,
339    item: String,
340    keys: Vec<subxt::dynamic::Value>,
341}
342
343impl StorageQuery {
344    /// Create a new storage query
345    pub fn new(pallet: impl Into<String>, item: impl Into<String>) -> Self {
346        Self {
347            pallet: pallet.into(),
348            item: item.into(),
349            keys: Vec::new(),
350        }
351    }
352
353    /// Add a key to the query
354    pub fn key(mut self, key: subxt::dynamic::Value) -> Self {
355        self.keys.push(key);
356        self
357    }
358
359    /// Add multiple keys
360    pub fn keys(mut self, keys: Vec<subxt::dynamic::Value>) -> Self {
361        self.keys.extend(keys);
362        self
363    }
364
365    /// Execute the query (returns raw bytes)
366    pub async fn execute(&self, client: &StorageClient) -> Result<Option<Vec<u8>>> {
367        client
368            .query_storage(&self.pallet, &self.item, self.keys.clone())
369            .await
370    }
371}
372
373// Helper function for parsing block hash from hex string
374fn parse_block_hash(hash_hex: &str) -> Result<subxt::config::substrate::H256> {
375    use subxt::config::substrate::H256;
376
377    // Remove 0x prefix if present
378    let hash_hex = hash_hex.strip_prefix("0x").unwrap_or(hash_hex);
379
380    // Parse hex string to bytes
381    let mut bytes = [0u8; 32];
382    hex::decode_to_slice(hash_hex, &mut bytes)
383        .map_err(|e| Error::Storage(format!("Invalid block hash hex: {}", e)))?;
384
385    Ok(H256::from(bytes))
386}
387
388// Helper functions for extracting values from subxt::dynamic::Value types
389fn extract_u64<T>(value: &subxt::dynamic::Value<T>, path: &[&str]) -> Option<u64> {
390    let mut current = value;
391    for &key in path {
392        current = current.at(key)?;
393    }
394
395    current.as_u128().and_then(|v| u64::try_from(v).ok())
396}
397
398fn extract_u32<T>(value: &subxt::dynamic::Value<T>, path: &[&str]) -> Option<u32> {
399    let mut current = value;
400    for &key in path {
401        current = current.at(key)?;
402    }
403
404    current.as_u128().and_then(|v| u32::try_from(v).ok())
405}
406
407fn extract_u128<T>(value: &subxt::dynamic::Value<T>, path: &[&str]) -> Option<u128> {
408    let mut current = value;
409    for &key in path {
410        current = current.at(key)?;
411    }
412
413    current.as_u128()
414}
415
416/// Decode a u128 value from SCALE-encoded bytes
417fn decode_u128_from_bytes(bytes: &[u8]) -> Option<u128> {
418    if bytes.is_empty() {
419        return None;
420    }
421
422    // SCALE encoding for u128 uses compact encoding for small values
423    // or fixed 16 bytes for larger values
424    match bytes.len() {
425        // Single byte: value 0-63 (compact)
426        1 => {
427            let byte = bytes[0];
428            if byte & 0b11 == 0b00 {
429                Some((byte >> 2) as u128)
430            } else {
431                None
432            }
433        }
434        // Two bytes: value 64-16383 (compact)
435        2 => {
436            if bytes[0] & 0b11 == 0b01 {
437                let value = u16::from_le_bytes([bytes[0], bytes[1]]);
438                Some((value >> 2) as u128)
439            } else {
440                None
441            }
442        }
443        // Four bytes: value 16384-1073741823 (compact)
444        4 => {
445            if bytes[0] & 0b11 == 0b10 {
446                let value = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
447                Some((value >> 2) as u128)
448            } else {
449                // Try as fixed u32
450                Some(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as u128)
451            }
452        }
453        // Eight bytes: fixed u64
454        8 => {
455            let mut arr = [0u8; 8];
456            arr.copy_from_slice(bytes);
457            Some(u64::from_le_bytes(arr) as u128)
458        }
459        // Sixteen bytes: fixed u128
460        16 => {
461            let mut arr = [0u8; 16];
462            arr.copy_from_slice(bytes);
463            Some(u128::from_le_bytes(arr))
464        }
465        // Variable length compact encoding (5+ bytes)
466        len if len >= 5 && bytes[0] & 0b11 == 0b11 => {
467            let byte_count = ((bytes[0] >> 2) + 4) as usize;
468            if len > byte_count {
469                let value_bytes = &bytes[1..byte_count + 1];
470                let mut arr = [0u8; 16];
471                let copy_len = value_bytes.len().min(16);
472                arr[..copy_len].copy_from_slice(&value_bytes[..copy_len]);
473                Some(u128::from_le_bytes(arr))
474            } else {
475                None
476            }
477        }
478        _ => None,
479    }
480}
481
482#[cfg(test)]
483mod tests {
484    use super::*;
485
486    #[test]
487    fn test_account_info() {
488        let info = AccountInfo {
489            nonce: 5,
490            consumers: 1,
491            providers: 1,
492            sufficients: 0,
493            free: 1_000_000_000_000,
494            reserved: 500_000_000_000,
495            frozen: 100_000_000_000,
496        };
497
498        assert_eq!(info.total(), 1_500_000_000_000);
499        assert_eq!(info.transferable(), 900_000_000_000);
500    }
501
502    #[test]
503    fn test_storage_query_builder() {
504        use subxt::dynamic::Value;
505
506        let query = StorageQuery::new("System", "Account").key(Value::from_bytes([0u8; 32]));
507
508        assert_eq!(query.pallet, "System");
509        assert_eq!(query.item, "Account");
510        assert_eq!(query.keys.len(), 1);
511    }
512}