glin_contracts/
chain_info.rs

1// Query contract and code information from blockchain storage
2
3use anyhow::{Context, Result};
4use subxt::dynamic;
5use subxt_core::storage;
6
7use glin_client::GlinClient;
8
9/// Contract information stored on-chain
10#[derive(Debug, Clone)]
11pub struct ContractInfo {
12    pub code_hash: [u8; 32],
13    pub storage_deposit: u128,
14}
15
16/// Get contract info from blockchain storage
17pub async fn get_contract_info(
18    client: &GlinClient,
19    contract_address: &str,
20) -> Result<ContractInfo> {
21    // Parse contract address to bytes
22    let address_bytes = parse_address(contract_address)?;
23
24    // Create dynamic storage query for ContractInfoOf
25    let storage_addr = dynamic::storage(
26        "Contracts",
27        "ContractInfoOf",
28        vec![dynamic::Value::from_bytes(address_bytes)],
29    );
30
31    // Get the storage key bytes for the address
32    let lookup_bytes = storage::get_address_bytes(&storage_addr, &client.metadata())
33        .context("Failed to encode storage address")?;
34
35    // Fetch raw SCALE-encoded bytes from storage
36    let raw_bytes = client
37        .storage()
38        .at_latest()
39        .await?
40        .fetch_raw(lookup_bytes)
41        .await
42        .context("Failed to fetch contract info")?
43        .ok_or_else(|| anyhow::anyhow!("Contract not found at address: {}", contract_address))?;
44
45    // Decode the raw SCALE bytes into ContractInfo
46    decode_contract_info_from_bytes(&raw_bytes)
47}
48
49/// Decode ContractInfo from raw SCALE-encoded bytes
50///
51/// Note: For now, we'll extract the code_hash from the raw SCALE-encoded bytes.
52/// In a future version, we can use proper SCALE decoding with type registry.
53fn decode_contract_info_from_bytes(encoded: &[u8]) -> Result<ContractInfo> {
54    use scale::Decode;
55
56    // For ContractInfo structure, we need to decode:
57    // struct ContractInfo {
58    //     code_hash: H256,         // 32 bytes
59    //     storage_deposit: u128,   // 16 bytes (compact encoded)
60    //     ...other fields
61    // }
62
63    // Simple approach: extract first 32 bytes as code_hash
64    if encoded.len() < 32 {
65        anyhow::bail!(
66            "Encoded ContractInfo too short: {} bytes (expected at least 32)",
67            encoded.len()
68        );
69    }
70
71    let mut code_hash = [0u8; 32];
72    code_hash.copy_from_slice(&encoded[0..32]);
73
74    // Decode storage_deposit (u128 after code_hash)
75    let mut cursor = &encoded[32..];
76    let storage_deposit =
77        u128::decode(&mut cursor).context("Failed to decode storage_deposit from ContractInfo")?;
78
79    Ok(ContractInfo {
80        code_hash,
81        storage_deposit,
82    })
83}
84
85/// Parse contract address to bytes
86fn parse_address(address: &str) -> Result<Vec<u8>> {
87    // Remove "0x" prefix if present
88    let address = address.strip_prefix("0x").unwrap_or(address);
89
90    // Try hex decoding first
91    if let Ok(bytes) = hex::decode(address) {
92        if bytes.len() == 32 {
93            return Ok(bytes);
94        }
95    }
96
97    // Try SS58 decoding (Substrate addresses)
98    use std::str::FromStr;
99    use subxt::utils::AccountId32;
100
101    let account = AccountId32::from_str(address)
102        .context("Invalid contract address format (expected hex or SS58)")?;
103
104    Ok(account.0.to_vec())
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn test_parse_hex_address() {
113        let hex_addr = "0x1234567890123456789012345678901234567890123456789012345678901234";
114        let result = parse_address(hex_addr);
115        assert!(result.is_ok());
116        assert_eq!(result.unwrap().len(), 32);
117    }
118
119    #[test]
120    fn test_parse_address_without_prefix() {
121        let hex_addr = "1234567890123456789012345678901234567890123456789012345678901234";
122        let result = parse_address(hex_addr);
123        assert!(result.is_ok());
124        assert_eq!(result.unwrap().len(), 32);
125    }
126}