solana_accountgen/
lib.rs

1//! # solana-accountgen
2//!
3//! A utility crate for generating mock Solana accounts for testing purposes.
4//!
5//! ## Features
6//!
7//! - Create accounts with custom balances, owners, and data using a fluent API
8//! - Serialize account data using Borsh (with JSON support for the bincode module)
9//! - Support for creating PDAs (Program Derived Addresses)
10//! - Integration with solana-program-test for end-to-end testing
11//! - Support for Anchor programs with discriminator handling
12//!
13//! ## Example
14//!
15//! ```rust,no_run
16//! use solana_accountgen::AccountBuilder;
17//! use solana_pubkey::Pubkey;
18//! use borsh::{BorshSerialize, BorshDeserialize};
19//!
20//! #[derive(BorshSerialize, BorshDeserialize)]
21//! struct MyData { value: u64 }
22//!
23//! let program_id = Pubkey::new_unique();
24//! let account = AccountBuilder::new()
25//!     .balance(100_000_000)
26//!     .owner(program_id)
27//!     .data(MyData { value: 42 })
28//!     .unwrap()
29//!     .build();
30//! ```
31//!
32//! ## Anchor Program Testing
33//!
34//! solana-accountgen provides special support for testing Anchor programs:
35//!
36//! ```rust,no_run
37//! use solana_accountgen::extensions::anchor::{create_anchor_account, create_anchor_instruction};
38//! use solana_pubkey::Pubkey;
39//! use borsh::{BorshSerialize, BorshDeserialize};
40//! use solana_instruction::AccountMeta;
41//!
42//! #[derive(BorshSerialize, BorshDeserialize)]
43//! struct GameState {
44//!     player: Pubkey,
45//!     score: u64,
46//! }
47//!
48//! // Create an account with Anchor's discriminator
49//! let program_id = Pubkey::new_unique();
50//! let player = Pubkey::new_unique();
51//! let game_state = GameState {
52//!     player,
53//!     score: 100,
54//! };
55//!
56//! let account = create_anchor_account(
57//!     "game",  // Account type name in Anchor program
58//!     program_id,
59//!     game_state,
60//!     100_000, // lamports
61//! ).unwrap();
62//!
63//! // Create an instruction with Anchor's method discriminator
64//! let ix = create_anchor_instruction(
65//!     program_id,
66//!     "update_score",  // Method name in Anchor program
67//!     vec![
68//!         AccountMeta::new(Pubkey::new_unique(), false),
69//!         AccountMeta::new_readonly(player, true),
70//!     ],
71//!     42u64, // Instruction data
72//! ).unwrap();
73//! ```
74
75mod account_builder;
76mod account_map;
77mod error;
78pub mod extensions;
79pub mod serialization;
80
81pub use account_builder::AccountBuilder;
82pub use account_map::AccountMap;
83pub use error::AccountGenError;
84
85// Re-export dependencies that users will likely need
86pub use borsh;
87use solana_account::Account;
88use solana_pubkey::Pubkey;
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93    use crate::serialization::borsh as borsh_serialization;
94    use base64;
95    use borsh::{BorshDeserialize, BorshSerialize};
96    use serde::{Deserialize, Serialize};
97    use serde_json;
98    use solana_pubkey::Pubkey;
99    use solana_rent::Rent;
100    use solana_sdk_ids::system_program;
101
102    // Test data structures
103    #[derive(BorshSerialize, BorshDeserialize, Debug, PartialEq, Clone)]
104    struct TestBorshData {
105        value: u64,
106        name: String,
107    }
108
109    #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
110    struct TestBincodeData {
111        value: u64,
112        name: String,
113    }
114
115    #[test]
116    fn test_account_builder_basic() {
117        let program_id = Pubkey::new_unique();
118        let balance = 100_000_000;
119
120        let account = AccountBuilder::new()
121            .balance(balance)
122            .owner(program_id)
123            .build();
124
125        assert_eq!(account.lamports, balance);
126        assert_eq!(account.owner, program_id);
127        assert_eq!(account.data.len(), 0);
128        assert_eq!(account.executable, false);
129    }
130
131    #[test]
132    fn test_account_builder_with_borsh_data() {
133        let program_id = Pubkey::new_unique();
134        let test_data = TestBorshData {
135            value: 42,
136            name: "Test Account".to_string(),
137        };
138
139        let account = AccountBuilder::new()
140            .balance(100_000)
141            .owner(program_id)
142            .data(test_data.clone())
143            .unwrap()
144            .build();
145
146        // Deserialize and verify the data
147        let deserialized: TestBorshData = TestBorshData::try_from_slice(&account.data).unwrap();
148        assert_eq!(deserialized, test_data);
149    }
150
151    #[test]
152    fn test_account_builder_with_raw_data() {
153        let program_id = Pubkey::new_unique();
154        let raw_data = vec![1, 2, 3, 4, 5];
155
156        let account = AccountBuilder::new()
157            .balance(100_000)
158            .owner(program_id)
159            .data_raw(raw_data.clone())
160            .build();
161
162        assert_eq!(account.data, raw_data);
163    }
164
165    #[test]
166    fn test_account_builder_executable() {
167        let program_id = Pubkey::new_unique();
168
169        let account = AccountBuilder::new()
170            .balance(100_000)
171            .owner(program_id)
172            .executable(true)
173            .build();
174
175        assert_eq!(account.executable, true);
176    }
177
178    #[test]
179    fn test_account_builder_rent_epoch() {
180        let program_id = Pubkey::new_unique();
181        let rent_epoch = 123;
182
183        let account = AccountBuilder::new()
184            .balance(100_000)
185            .owner(program_id)
186            .rent_epoch(rent_epoch)
187            .build();
188
189        assert_eq!(account.rent_epoch, rent_epoch);
190    }
191
192    #[test]
193    fn test_try_build_with_missing_owner() {
194        // Create an account without specifying an owner
195        let result = AccountBuilder::new().balance(100_000).try_build();
196
197        // Now it should succeed with the default system program owner
198        assert!(result.is_ok());
199        if let Ok(account) = result {
200            assert_eq!(account.owner, system_program::id());
201        }
202    }
203
204    #[test]
205    fn test_create_pda() {
206        let program_id = Pubkey::new_unique();
207        let user = Pubkey::new_unique();
208        let seeds = &[b"test", user.as_ref()];
209        let test_data = TestBorshData {
210            value: 42,
211            name: "PDA Account".to_string(),
212        };
213
214        let (pda, bump, account) =
215            AccountBuilder::create_pda(&program_id, seeds, 100_000, test_data.clone()).unwrap();
216
217        // Verify PDA derivation
218        let (expected_pda, expected_bump) = Pubkey::find_program_address(seeds, &program_id);
219        assert_eq!(pda, expected_pda);
220        assert_eq!(bump, expected_bump);
221
222        // Verify account properties
223        assert_eq!(account.owner, program_id);
224        assert_eq!(account.lamports, 100_000);
225
226        // Verify data
227        let deserialized: TestBorshData = TestBorshData::try_from_slice(&account.data).unwrap();
228        assert_eq!(deserialized, test_data);
229    }
230
231    #[test]
232    fn test_borsh_serialization() {
233        let program_id = Pubkey::new_unique();
234        let test_data = TestBorshData {
235            value: 42,
236            name: "Test Account".to_string(),
237        };
238
239        let account = AccountBuilder::new()
240            .balance(100_000)
241            .owner(program_id)
242            .data(test_data.clone())
243            .unwrap()
244            .build();
245
246        let deserialized =
247            borsh_serialization::deserialize_account_data::<TestBorshData>(&account).unwrap();
248        assert_eq!(deserialized, test_data);
249    }
250
251    #[test]
252    fn test_account_base64_encoding() {
253        let program_id = Pubkey::new_unique();
254        let balance = 100_000_000;
255        let data = vec![1, 2, 3, 4, 5];
256
257        // Create an account
258        let account = AccountBuilder::new()
259            .balance(balance)
260            .owner(program_id)
261            .data_raw(data.clone())
262            .build();
263
264        // Serialize with serde_json
265        let account_bytes = serde_json::to_vec(&account).unwrap();
266
267        // Encode with base64
268        let base64_string = base64::encode(&account_bytes);
269
270        // Decode from base64
271        let decoded_bytes = base64::decode(&base64_string).unwrap();
272
273        // Deserialize with serde_json
274        let decoded_account: Account = serde_json::from_slice(&decoded_bytes).unwrap();
275
276        // Verify the account was correctly round-tripped
277        assert_eq!(account.lamports, decoded_account.lamports);
278        assert_eq!(account.owner, decoded_account.owner);
279        assert_eq!(account.data, decoded_account.data);
280        assert_eq!(account.executable, decoded_account.executable);
281        assert_eq!(account.rent_epoch, decoded_account.rent_epoch);
282    }
283
284    #[test]
285    fn test_account_builder_with_pubkey() {
286        let pubkey = Pubkey::new_unique();
287        let program_id = Pubkey::new_unique();
288
289        let (account_pubkey, account) = AccountBuilder::new()
290            .pubkey(pubkey)
291            .balance(100_000)
292            .owner(program_id)
293            .build_with_pubkey();
294
295        assert_eq!(account_pubkey, pubkey);
296        assert_eq!(account.lamports, 100_000);
297        assert_eq!(account.owner, program_id);
298    }
299
300    #[test]
301    fn test_create_account_helper() {
302        let pubkey = Pubkey::new_unique();
303        let program_id = Pubkey::new_unique();
304
305        let (account_pubkey, account) = create_account(
306            pubkey,
307            AccountBuilder::new().balance(100_000).owner(program_id),
308        )
309        .unwrap();
310
311        assert_eq!(account_pubkey, pubkey);
312        assert_eq!(account.lamports, 100_000);
313        assert_eq!(account.owner, program_id);
314    }
315
316    #[test]
317    fn test_account_map() {
318        let program_id = Pubkey::new_unique();
319        let pubkey1 = Pubkey::new_unique();
320        let pubkey2 = Pubkey::new_unique();
321
322        let mut account_map = AccountMap::new();
323
324        // Add accounts using different methods
325        account_map
326            .add_with_builder(
327                pubkey1,
328                AccountBuilder::new().balance(100_000).owner(program_id),
329            )
330            .unwrap();
331
332        let account2 = AccountBuilder::new()
333            .balance(200_000)
334            .owner(program_id)
335            .build();
336        account_map.set_account(pubkey2, account2);
337
338        // Test retrieval
339        let account1 = account_map.get_account(&pubkey1).unwrap();
340        assert_eq!(account1.lamports, 100_000);
341
342        let account2 = account_map.get_account(&pubkey2).unwrap();
343        assert_eq!(account2.lamports, 200_000);
344
345        // Test iteration
346        let mut total_lamports = 0;
347        for (_, account) in account_map.iter() {
348            total_lamports += account.lamports;
349        }
350        assert_eq!(total_lamports, 300_000);
351
352        // Test length
353        assert_eq!(account_map.len(), 2);
354
355        // Test removal
356        let removed = account_map.remove_account(&pubkey1).unwrap();
357        assert_eq!(removed.lamports, 100_000);
358        assert_eq!(account_map.len(), 1);
359    }
360
361    #[test]
362    fn test_create_accounts() {
363        let program_id = Pubkey::new_unique();
364        let pubkey1 = Pubkey::new_unique();
365        let pubkey2 = Pubkey::new_unique();
366
367        let accounts = create_accounts(vec![
368            (
369                pubkey1,
370                AccountBuilder::new().balance(100_000).owner(program_id),
371            ),
372            (
373                pubkey2,
374                AccountBuilder::new().balance(200_000).owner(program_id),
375            ),
376        ])
377        .unwrap();
378
379        assert_eq!(accounts.len(), 2);
380
381        let account1 = accounts.get_account(&pubkey1).unwrap();
382        assert_eq!(account1.lamports, 100_000);
383
384        let account2 = accounts.get_account(&pubkey2).unwrap();
385        assert_eq!(account2.lamports, 200_000);
386    }
387
388    #[test]
389    fn test_account_builder_default_owner() {
390        // Create an account without specifying an owner
391        let account = AccountBuilder::new().balance(100_000).build();
392
393        // Verify that the owner defaults to the system program
394        assert_eq!(account.owner, system_program::id());
395    }
396
397    #[test]
398    fn test_account_builder_default_balance() {
399        // Create test data
400        let test_data = TestBorshData {
401            value: 42,
402            name: "Test Account".to_string(),
403        };
404
405        // Create an account without specifying a balance
406        let account = AccountBuilder::new()
407            .owner(Pubkey::new_unique())
408            .data(test_data.clone())
409            .unwrap()
410            .build();
411
412        // Calculate the expected rent-exempt balance
413        let rent = Rent::default();
414        let data_size = borsh::to_vec(&test_data).unwrap().len();
415        let expected_balance = rent.minimum_balance(data_size);
416
417        // Verify that the balance defaults to rent-exempt
418        assert_eq!(account.lamports, expected_balance);
419    }
420
421    #[test]
422    fn test_account_builder_all_defaults() {
423        // Create test data
424        let test_data = TestBorshData {
425            value: 42,
426            name: "Test Account".to_string(),
427        };
428
429        // Create an account with only data specified
430        let account = AccountBuilder::new()
431            .data(test_data.clone())
432            .unwrap()
433            .build();
434
435        // Verify defaults
436        assert_eq!(account.owner, system_program::id());
437        assert_eq!(account.executable, false);
438        assert_eq!(account.rent_epoch, 0);
439
440        // Calculate the expected rent-exempt balance
441        let rent = Rent::default();
442        let data_size = borsh::to_vec(&test_data).unwrap().len();
443        let expected_balance = rent.minimum_balance(data_size);
444
445        // Verify that the balance defaults to rent-exempt
446        assert_eq!(account.lamports, expected_balance);
447    }
448
449    #[test]
450    fn test_account_builder_explicit_overrides_defaults() {
451        // Create test data
452        let test_data = TestBorshData {
453            value: 42,
454            name: "Test Account".to_string(),
455        };
456
457        let custom_owner = Pubkey::new_unique();
458        let custom_balance = 999_999;
459
460        // Create an account with explicit values
461        let account = AccountBuilder::new()
462            .owner(custom_owner)
463            .balance(custom_balance)
464            .data(test_data.clone())
465            .unwrap()
466            .build();
467
468        // Verify explicit values are used instead of defaults
469        assert_eq!(account.owner, custom_owner);
470        assert_eq!(account.lamports, custom_balance);
471    }
472}
473
474/// Creates an account with the given pubkey and properties.
475///
476/// # Example
477///
478/// ```
479/// use solana_accountgen::{create_account, AccountBuilder};
480/// use solana_pubkey::Pubkey;
481///
482/// let pubkey = Pubkey::new_unique();
483/// let program_id = Pubkey::new_unique();
484/// let (account_pubkey, account) = create_account(
485///     pubkey,
486///     AccountBuilder::new()
487///         .balance(100_000_000)
488///         .owner(program_id)
489/// ).unwrap();
490/// ```
491pub fn create_account(
492    pubkey: Pubkey,
493    builder: AccountBuilder,
494) -> Result<(Pubkey, Account), AccountGenError> {
495    builder.pubkey(pubkey).try_build_with_pubkey()
496}
497
498/// Creates multiple accounts with their pubkeys.
499///
500/// # Example
501///
502/// ```
503/// use solana_accountgen::{create_accounts, AccountBuilder};
504/// use solana_pubkey::Pubkey;
505///
506/// let program_id = Pubkey::new_unique();
507/// let accounts = create_accounts(vec![
508///     (Pubkey::new_unique(), AccountBuilder::new().balance(100_000).owner(program_id)),
509///     (Pubkey::new_unique(), AccountBuilder::new().balance(200_000).owner(program_id)),
510/// ]).unwrap();
511///
512/// assert_eq!(accounts.len(), 2);
513/// ```
514pub fn create_accounts(
515    accounts: Vec<(Pubkey, AccountBuilder)>,
516) -> Result<AccountMap, AccountGenError> {
517    let mut account_map = AccountMap::new();
518
519    for (pubkey, builder) in accounts {
520        account_map.add_with_builder(pubkey, builder)?;
521    }
522
523    Ok(account_map)
524}