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                base64::decode(&account_data.account.data.0).map_err(|e| {
115                    RpcError::CustomError(format!("Failed to decode base64 data: {}", e))
116                })?
117            } else {
118                return Err(RpcError::CustomError(format!(
119                    "Unsupported encoding: {}",
120                    account_data.account.data.1
121                )));
122            };
123
124            let account = Account {
125                lamports: account_data.account.lamports,
126                data,
127                owner,
128                executable: account_data.account.executable,
129                rent_epoch: account_data.account.rent_epoch,
130            };
131
132            accounts.insert(pubkey, account);
133        }
134    }
135
136    Ok(accounts)
137}
138
139/// Load a specific account by pubkey from the accounts directory
140/// Optionally provide a prefix for the filename (e.g. "address_merkle_tree")
141pub fn load_account_from_dir(pubkey: &Pubkey, prefix: Option<&str>) -> Result<Account, RpcError> {
142    let accounts_dir = find_accounts_dir().ok_or_else(|| {
143        RpcError::CustomError(
144            "Failed to find accounts directory. Make sure light CLI is installed.".to_string(),
145        )
146    })?;
147
148    let filename = if let Some(prefix) = prefix {
149        format!("{}_{}.json", prefix, pubkey)
150    } else {
151        format!("{}.json", pubkey)
152    };
153    let path = accounts_dir.join(&filename);
154
155    let contents = fs::read_to_string(&path).map_err(|e| {
156        RpcError::CustomError(format!("Failed to read account file {:?}: {}", path, e))
157    })?;
158
159    let account_data: AccountData = serde_json::from_str(&contents).map_err(|e| {
160        RpcError::CustomError(format!(
161            "Failed to parse account JSON from {:?}: {}",
162            path, e
163        ))
164    })?;
165
166    let owner = account_data
167        .account
168        .owner
169        .parse::<Pubkey>()
170        .map_err(|e| RpcError::CustomError(format!("Invalid owner pubkey: {}", e)))?;
171
172    // Decode base64 data
173    let data = if account_data.account.data.1 == "base64" {
174        base64::decode(&account_data.account.data.0)
175            .map_err(|e| RpcError::CustomError(format!("Failed to decode base64 data: {}", e)))?
176    } else {
177        return Err(RpcError::CustomError(format!(
178            "Unsupported encoding: {}",
179            account_data.account.data.1
180        )));
181    };
182
183    Ok(Account {
184        lamports: account_data.account.lamports,
185        data,
186        owner,
187        executable: account_data.account.executable,
188        rent_epoch: account_data.account.rent_epoch,
189    })
190}