testsvm_core/
account_ref.rs

1//! # Account References
2//!
3//! Type-safe account references for TestSVM with automatic deserialization support.
4//!
5//! This module provides the `AccountRef` type, which acts as a lightweight handle to
6//! on-chain accounts with built-in type safety and Anchor deserialization. It simplifies
7//! working with strongly-typed accounts in tests by providing convenient methods for
8//! loading and verifying account state.
9//!
10//! ## Key Features
11//!
12//! - **Type Safety**: Generic over Anchor account types for compile-time safety
13//! - **Loading**: Simple access to account state
14//! - **Address Book Integration**: Automatic labeling for better debugging
15
16use crate::TestSVM;
17use anchor_lang::Key;
18use anyhow::{Context, Result};
19use solana_sdk::pubkey::Pubkey;
20use std::fmt;
21use std::marker::PhantomData;
22
23/// A reference to an account on-chain.
24#[derive(Copy, Clone, Debug)]
25pub struct AccountRef<T: anchor_lang::AccountDeserialize> {
26    pub key: Pubkey,
27    _phantom: PhantomData<T>,
28}
29
30impl<T: anchor_lang::AccountDeserialize> Key for AccountRef<T> {
31    fn key(&self) -> Pubkey {
32        self.key
33    }
34}
35
36impl<T: anchor_lang::AccountDeserialize> From<AccountRef<T>> for Pubkey {
37    fn from(val: AccountRef<T>) -> Self {
38        val.key
39    }
40}
41
42impl<T: anchor_lang::AccountDeserialize> From<&AccountRef<T>> for Pubkey {
43    fn from(val: &AccountRef<T>) -> Self {
44        val.key
45    }
46}
47
48impl<T: anchor_lang::AccountDeserialize> AccountRef<T> {
49    /// Create a new account reference
50    pub fn new(key: Pubkey) -> Self {
51        Self {
52            key,
53            _phantom: PhantomData,
54        }
55    }
56
57    /// Loads the latest account state from the chain, failing if account doesn't exist
58    pub fn load(&self, env: &TestSVM) -> Result<T> {
59        self.maybe_load(env)?
60            .with_context(|| format!("Account not found: {}", self.key))
61    }
62
63    /// Attempts to load the latest account state from the chain, returning None if account doesn't exist
64    pub fn maybe_load(&self, env: &TestSVM) -> Result<Option<T>> {
65        match env.svm.get_account(&self.key) {
66            Some(account) => {
67                let mut data = &account.data[..];
68                Ok(Some(
69                    T::try_deserialize(&mut data).context("Failed to deserialize account")?,
70                ))
71            }
72            None => Ok(None),
73        }
74    }
75}
76
77impl<T: anchor_lang::AccountDeserialize> fmt::Display for AccountRef<T> {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        write!(f, "{}", self.key)
80    }
81}
82
83impl<T: anchor_lang::AccountDeserialize> AsRef<[u8]> for AccountRef<T> {
84    fn as_ref(&self) -> &[u8] {
85        self.key.as_ref()
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use crate::AccountRef;
92    use anchor_lang::prelude::*;
93    use solana_address_book::pda_seeds::find_pda_with_bump_and_strings;
94
95    // Dummy type for testing
96    #[derive(Debug, Clone)]
97    struct DummyAccount;
98
99    impl anchor_lang::AccountDeserialize for DummyAccount {
100        fn try_deserialize_unchecked(_buf: &mut &[u8]) -> Result<Self> {
101            Ok(DummyAccount)
102        }
103    }
104
105    impl anchor_lang::AccountSerialize for DummyAccount {
106        fn try_serialize<W: std::io::Write>(&self, _writer: &mut W) -> Result<()> {
107            Ok(())
108        }
109    }
110
111    impl anchor_lang::Owner for DummyAccount {
112        fn owner() -> Pubkey {
113            Pubkey::default()
114        }
115    }
116
117    #[test]
118    fn test_account_ref_as_pda_seed() {
119        let program_id = Pubkey::new_unique();
120        let account_pubkey = Pubkey::new_unique();
121        let account_ref: AccountRef<DummyAccount> = AccountRef::new(account_pubkey);
122
123        // Test that AccountRef can be used as a seed
124        let (pda, bump) =
125            Pubkey::find_program_address(&[b"prefix", account_ref.key.as_ref()], &program_id);
126
127        // Verify it matches manual calculation
128        let (expected_pda, expected_bump) =
129            Pubkey::find_program_address(&[b"prefix", account_pubkey.as_ref()], &program_id);
130
131        assert_eq!(pda, expected_pda);
132        assert_eq!(bump, expected_bump);
133
134        // Test with find_pda_with_bump_and_strings
135        let derived_pda =
136            find_pda_with_bump_and_strings(&[b"prefix", account_ref.as_ref()], &program_id);
137
138        assert_eq!(derived_pda.key, expected_pda);
139        assert_eq!(derived_pda.bump, expected_bump);
140
141        // Verify string representation
142        assert_eq!(derived_pda.seed_strings[0], "prefix");
143        assert_eq!(derived_pda.seed_strings[1], account_pubkey.to_string());
144
145        // Verify raw seeds
146        assert_eq!(derived_pda.seeds[0], b"prefix");
147        assert_eq!(derived_pda.seeds[1], account_pubkey.as_ref());
148    }
149
150    #[test]
151    fn test_account_ref_mixed_seeds() {
152        let program_id = Pubkey::new_unique();
153        let vault_account = AccountRef::<DummyAccount>::new(Pubkey::new_unique());
154        let owner_account = AccountRef::<DummyAccount>::new(Pubkey::new_unique());
155        let nonce: u64 = 42;
156        let nonce_bytes = nonce.to_le_bytes();
157
158        // Use multiple AccountRefs in PDA derivation
159        let derived_pda = find_pda_with_bump_and_strings(
160            &[
161                b"vault",
162                vault_account.as_ref(),
163                owner_account.as_ref(),
164                nonce_bytes.as_ref(),
165            ],
166            &program_id,
167        );
168
169        // Manual verification
170        let (expected_pda, expected_bump) = Pubkey::find_program_address(
171            &[
172                b"vault",
173                vault_account.key.as_ref(),
174                owner_account.key.as_ref(),
175                &nonce_bytes,
176            ],
177            &program_id,
178        );
179
180        assert_eq!(derived_pda.key, expected_pda);
181        assert_eq!(derived_pda.bump, expected_bump);
182        assert!(derived_pda.verify(&program_id));
183    }
184}