light_program_test/utils/
load_accounts.rs

1use std::{collections::HashMap, fs, path::PathBuf};
2
3use light_client::rpc::RpcError;
4use serde::{Deserialize, Serialize};
5use solana_sdk::{account::Account, pubkey::Pubkey};
6
7#[derive(Debug, Serialize, Deserialize)]
8struct AccountData {
9    pubkey: String,
10    account: AccountInfo,
11}
12
13#[derive(Debug, Serialize, Deserialize)]
14struct AccountInfo {
15    lamports: u64,
16    data: (String, String), // (data, encoding) where encoding is typically "base64"
17    owner: String,
18    executable: bool,
19    #[serde(rename = "rentEpoch")]
20    rent_epoch: u64,
21}
22
23pub fn find_accounts_dir() -> Option<PathBuf> {
24    #[cfg(not(feature = "devenv"))]
25    {
26        use std::process::Command;
27        let output = Command::new("which")
28            .arg("light")
29            .output()
30            .expect("Failed to execute 'which light'");
31
32        if !output.status.success() {
33            return None;
34        }
35
36        let light_path = String::from_utf8_lossy(&output.stdout).trim().to_string();
37        let mut light_bin_path = PathBuf::from(light_path);
38        light_bin_path.pop();
39
40        let accounts_dir =
41            light_bin_path.join("../lib/node_modules/@lightprotocol/zk-compression-cli/accounts");
42
43        Some(accounts_dir.canonicalize().unwrap_or(accounts_dir))
44    }
45    #[cfg(feature = "devenv")]
46    {
47        println!("Use only in light protocol monorepo. Using 'git rev-parse --show-toplevel' to find the accounts directory");
48        let light_protocol_toplevel = String::from_utf8_lossy(
49            &std::process::Command::new("git")
50                .arg("rev-parse")
51                .arg("--show-toplevel")
52                .output()
53                .expect("Failed to get top-level directory")
54                .stdout,
55        )
56        .trim()
57        .to_string();
58
59        // In devenv mode, we don't load accounts from directory
60        // This path won't be used as we initialize accounts directly
61        let accounts_path = PathBuf::from(format!("{}/cli/accounts/", light_protocol_toplevel));
62        Some(accounts_path)
63    }
64}
65
66/// Load all accounts from the accounts directory
67/// Returns a HashMap of Pubkey -> Account
68pub fn load_all_accounts_from_dir() -> Result<HashMap<Pubkey, Account>, RpcError> {
69    let accounts_dir = find_accounts_dir().ok_or_else(|| {
70        RpcError::CustomError(
71            "Failed to find accounts directory. Make sure light CLI is installed.".to_string(),
72        )
73    })?;
74
75    let mut accounts = HashMap::new();
76
77    let entries = fs::read_dir(&accounts_dir).map_err(|e| {
78        RpcError::CustomError(format!(
79            "Failed to read accounts directory at {:?}: {}",
80            accounts_dir, e
81        ))
82    })?;
83
84    for entry in entries {
85        let entry = entry
86            .map_err(|e| RpcError::CustomError(format!("Failed to read directory entry: {}", e)))?;
87        let path = entry.path();
88
89        if path.extension().and_then(|s| s.to_str()) == Some("json") {
90            let contents = fs::read_to_string(&path).map_err(|e| {
91                RpcError::CustomError(format!("Failed to read file {:?}: {}", path, e))
92            })?;
93
94            let account_data: AccountData = serde_json::from_str(&contents).map_err(|e| {
95                RpcError::CustomError(format!(
96                    "Failed to parse account JSON from {:?}: {}",
97                    path, e
98                ))
99            })?;
100
101            let pubkey = account_data
102                .pubkey
103                .parse::<Pubkey>()
104                .map_err(|e| RpcError::CustomError(format!("Invalid pubkey: {}", e)))?;
105
106            let owner = account_data
107                .account
108                .owner
109                .parse::<Pubkey>()
110                .map_err(|e| RpcError::CustomError(format!("Invalid owner pubkey: {}", e)))?;
111
112            // Decode base64 data
113            let data = if account_data.account.data.1 == "base64" {
114                use base64::{engine::general_purpose, Engine as _};
115                general_purpose::STANDARD
116                    .decode(&account_data.account.data.0)
117                    .map_err(|e| {
118                        RpcError::CustomError(format!("Failed to decode base64 data: {}", e))
119                    })?
120            } else {
121                return Err(RpcError::CustomError(format!(
122                    "Unsupported encoding: {}",
123                    account_data.account.data.1
124                )));
125            };
126
127            let account = Account {
128                lamports: account_data.account.lamports,
129                data,
130                owner,
131                executable: account_data.account.executable,
132                rent_epoch: account_data.account.rent_epoch,
133            };
134
135            accounts.insert(pubkey, account);
136        }
137    }
138
139    Ok(accounts)
140}