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(|| {
44            anyhow::anyhow!("Contract not found at address: {}", contract_address)
45        })?;
46
47    // Decode the raw SCALE bytes into ContractInfo
48    decode_contract_info_from_bytes(&raw_bytes)
49}
50
51/// Decode ContractInfo from raw SCALE-encoded bytes
52///
53/// Note: For now, we'll extract the code_hash from the raw SCALE-encoded bytes.
54/// In a future version, we can use proper SCALE decoding with type registry.
55fn decode_contract_info_from_bytes(encoded: &[u8]) -> Result<ContractInfo> {
56    use scale::Decode;
57
58    // For ContractInfo structure, we need to decode:
59    // struct ContractInfo {
60    //     code_hash: H256,         // 32 bytes
61    //     storage_deposit: u128,   // 16 bytes (compact encoded)
62    //     ...other fields
63    // }
64
65    // Simple approach: extract first 32 bytes as code_hash
66    if encoded.len() < 32 {
67        anyhow::bail!(
68            "Encoded ContractInfo too short: {} bytes (expected at least 32)",
69            encoded.len()
70        );
71    }
72
73    let mut code_hash = [0u8; 32];
74    code_hash.copy_from_slice(&encoded[0..32]);
75
76    // Decode storage_deposit (u128 after code_hash)
77    let mut cursor = &encoded[32..];
78    let storage_deposit = u128::decode(&mut cursor)
79        .context("Failed to decode storage_deposit from ContractInfo")?;
80
81    Ok(ContractInfo {
82        code_hash,
83        storage_deposit,
84    })
85}
86
87/// Parse contract address to bytes
88fn parse_address(address: &str) -> Result<Vec<u8>> {
89    // Remove "0x" prefix if present
90    let address = address.strip_prefix("0x").unwrap_or(address);
91
92    // Try hex decoding first
93    if let Ok(bytes) = hex::decode(address) {
94        if bytes.len() == 32 {
95            return Ok(bytes);
96        }
97    }
98
99    // Try SS58 decoding (Substrate addresses)
100    use subxt::utils::AccountId32;
101    use std::str::FromStr;
102
103    let account = AccountId32::from_str(address)
104        .context("Invalid contract address format (expected hex or SS58)")?;
105
106    Ok(account.0.to_vec())
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn test_parse_hex_address() {
115        let hex_addr = "0x1234567890123456789012345678901234567890123456789012345678901234";
116        let result = parse_address(hex_addr);
117        assert!(result.is_ok());
118        assert_eq!(result.unwrap().len(), 32);
119    }
120
121    #[test]
122    fn test_parse_address_without_prefix() {
123        let hex_addr = "1234567890123456789012345678901234567890123456789012345678901234";
124        let result = parse_address(hex_addr);
125        assert!(result.is_ok());
126        assert_eq!(result.unwrap().len(), 32);
127    }
128}