1use crate::{Error, Metrics, Result};
10use subxt::dynamic::At as _;
11use subxt::{OnlineClient, PolkadotConfig};
12use tracing::debug;
13
14pub struct StorageClient {
16 client: OnlineClient<PolkadotConfig>,
17 metrics: Metrics,
18}
19
20impl StorageClient {
21 pub fn new(client: OnlineClient<PolkadotConfig>, metrics: Metrics) -> Self {
23 Self { client, metrics }
24 }
25
26 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 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 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 if let Some(value) = result {
58 let account_data = value
61 .to_value()
62 .map_err(|e| Error::Storage(format!("Failed to decode account value: {}", e)))?;
63
64 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 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 debug!("Account {} not found, returning default", address);
97 Ok(AccountInfo::default())
98 }
99 }
100
101 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 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 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 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 pub fn get_existential_deposit(&self) -> Result<u128> {
160 let value = self.get_constant("Balances", "ExistentialDeposit")?;
161
162 decode_u128_from_bytes(&value).ok_or_else(|| {
164 Error::Storage("Failed to decode ExistentialDeposit as u128".to_string())
165 })
166 }
167
168 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 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 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 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 let pallet_metadata = metadata
244 .pallet_by_name(pallet)
245 .ok_or_else(|| Error::Metadata(format!("Pallet '{}' not found", pallet)))?;
246
247 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 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#[derive(Debug, Clone, Default)]
288pub struct AccountInfo {
289 pub nonce: u64,
291 pub consumers: u32,
293 pub providers: u32,
295 pub sufficients: u32,
297 pub free: u128,
299 pub reserved: u128,
301 pub frozen: u128,
303}
304
305impl AccountInfo {
306 pub fn total(&self) -> u128 {
308 self.free.saturating_add(self.reserved)
309 }
310
311 pub fn transferable(&self) -> u128 {
313 self.free.saturating_sub(self.frozen)
314 }
315}
316
317#[derive(Debug, Clone)]
319pub struct PalletMetadata {
320 pub name: String,
322 pub index: u8,
324 pub storage_count: usize,
326 pub call_count: usize,
328 pub event_count: usize,
330 pub constant_count: usize,
332 pub error_count: usize,
334}
335
336pub struct StorageQuery {
338 pallet: String,
339 item: String,
340 keys: Vec<subxt::dynamic::Value>,
341}
342
343impl StorageQuery {
344 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 pub fn key(mut self, key: subxt::dynamic::Value) -> Self {
355 self.keys.push(key);
356 self
357 }
358
359 pub fn keys(mut self, keys: Vec<subxt::dynamic::Value>) -> Self {
361 self.keys.extend(keys);
362 self
363 }
364
365 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
373fn parse_block_hash(hash_hex: &str) -> Result<subxt::config::substrate::H256> {
375 use subxt::config::substrate::H256;
376
377 let hash_hex = hash_hex.strip_prefix("0x").unwrap_or(hash_hex);
379
380 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
388fn 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
416fn decode_u128_from_bytes(bytes: &[u8]) -> Option<u128> {
418 if bytes.is_empty() {
419 return None;
420 }
421
422 match bytes.len() {
425 1 => {
427 let byte = bytes[0];
428 if byte & 0b11 == 0b00 {
429 Some((byte >> 2) as u128)
430 } else {
431 None
432 }
433 }
434 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 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 Some(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as u128)
451 }
452 }
453 8 => {
455 let mut arr = [0u8; 8];
456 arr.copy_from_slice(bytes);
457 Some(u64::from_le_bytes(arr) as u128)
458 }
459 16 => {
461 let mut arr = [0u8; 16];
462 arr.copy_from_slice(bytes);
463 Some(u128::from_le_bytes(arr))
464 }
465 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}