Skip to main content

crucible_test_context/
account_builders.rs

1use crate::TestContext;
2use anchor_lang::solana_program::program_pack::Pack;
3use anyhow::Result;
4use solana_account::Account;
5use solana_pubkey::Pubkey;
6use spl_token::solana_program::program_option::COption;
7
8pub struct GenericAccountBuilder<'a> {
9    pub(crate) ctx: &'a mut TestContext,
10    pub(crate) address: Pubkey,
11    pub(crate) account_state: Account,
12}
13
14pub trait AccountBuilderBase: Sized {
15    fn account_state_mut(&mut self) -> &mut Account;
16    fn address_mut(&mut self) -> &mut Pubkey;
17
18    fn pubkey(mut self, pk: Pubkey) -> Self {
19        *self.address_mut() = pk;
20        self
21    }
22
23    fn owner(mut self, pk: Pubkey) -> Self {
24        self.account_state_mut().owner = pk;
25        self
26    }
27
28    fn executable(mut self, val: bool) -> Self {
29        self.account_state_mut().executable = val;
30        self
31    }
32
33    fn rent_epoch(mut self, val: u64) -> Self {
34        self.account_state_mut().rent_epoch = val;
35        self
36    }
37
38    fn lamports(mut self, amount: u64) -> Self {
39        self.account_state_mut().lamports = amount;
40        self
41    }
42
43    fn size(mut self, length: usize) -> Self {
44        self.account_state_mut().data = vec![0; length];
45        self
46    }
47
48    fn data(mut self, bytes: &[u8]) -> Self {
49        self.account_state_mut().data = Vec::from(bytes);
50        self
51    }
52}
53
54impl AccountBuilderBase for GenericAccountBuilder<'_> {
55    fn account_state_mut(&mut self) -> &mut Account {
56        &mut self.account_state
57    }
58    fn address_mut(&mut self) -> &mut Pubkey {
59        &mut self.address
60    }
61}
62
63impl GenericAccountBuilder<'_> {
64    pub fn create(self) -> Result<Pubkey> {
65        // Ensure address has been set
66        if self.address == Pubkey::default() {
67            return Err(anyhow::anyhow!("Address must be set with .pubkey()"));
68        }
69        self.ctx.track_account(self.address);
70        let _ = self.ctx.svm.set_account(self.address, self.account_state);
71        Ok(self.address)
72    }
73}
74
75pub struct MintAccountBuilder<'a> {
76    pub(crate) ctx: &'a mut TestContext,
77    pub(crate) address: Pubkey,
78    pub(crate) account_state: Account,
79    pub(crate) mint: spl_token::state::Mint,
80}
81
82impl AccountBuilderBase for MintAccountBuilder<'_> {
83    fn account_state_mut(&mut self) -> &mut Account {
84        &mut self.account_state
85    }
86    fn address_mut(&mut self) -> &mut Pubkey {
87        &mut self.address
88    }
89}
90
91impl MintAccountBuilder<'_> {
92    pub fn create(self) -> Result<Pubkey> {
93        if self.address == Pubkey::default() {
94            return Err(anyhow::anyhow!("Address must be set with .pubkey()"));
95        }
96
97        let mut account = self.account_state;
98        spl_token::state::Mint::pack(self.mint, &mut account.data)?;
99        self.ctx.track_account(self.address);
100        let _ = self.ctx.svm.set_account(self.address, account);
101        Ok(self.address)
102    }
103
104    pub fn mint_authority(mut self, authority: Pubkey) -> Self {
105        self.mint.mint_authority = COption::Some(authority);
106        self
107    }
108
109    pub fn supply(mut self, supply: u64) -> Self {
110        self.mint.supply = supply;
111        self
112    }
113
114    pub fn decimals(mut self, decimals: u8) -> Self {
115        self.mint.decimals = decimals;
116        self
117    }
118
119    pub fn is_initialized(mut self, initialized: bool) -> Self {
120        self.mint.is_initialized = initialized;
121        self
122    }
123
124    pub fn freeze_authority(mut self, authority: Option<Pubkey>) -> Self {
125        self.mint.freeze_authority = match authority {
126            Some(pk) => COption::Some(pk),
127            None => COption::None,
128        };
129        self
130    }
131}
132
133pub struct TokenAccountBuilder<'a> {
134    pub(crate) ctx: &'a mut TestContext,
135    pub(crate) address: Pubkey,
136    pub(crate) account_state: Account,
137    pub(crate) token_state: spl_token::state::Account,
138}
139
140impl AccountBuilderBase for TokenAccountBuilder<'_> {
141    fn account_state_mut(&mut self) -> &mut Account {
142        &mut self.account_state
143    }
144    fn address_mut(&mut self) -> &mut Pubkey {
145        &mut self.address
146    }
147}
148
149impl TokenAccountBuilder<'_> {
150    pub fn create(self) -> Result<Pubkey> {
151        if self.address == Pubkey::default() {
152            return Err(anyhow::anyhow!("Address must be set with .pubkey()"));
153        }
154
155        if self.token_state.mint == Pubkey::default() {
156            return Err(anyhow::anyhow!("Mint must be set with .mint()"));
157        }
158
159        if self.token_state.owner == Pubkey::default() {
160            return Err(anyhow::anyhow!("Owner must be set with .token_owner()"));
161        }
162
163        let mut account = self.account_state;
164        spl_token::state::Account::pack(self.token_state, &mut account.data)?;
165        self.ctx.track_account(self.address);
166        let _ = self.ctx.svm.set_account(self.address, account);
167        Ok(self.address)
168    }
169
170    pub fn mint(mut self, mint: Pubkey) -> Self {
171        self.token_state.mint = mint;
172        self
173    }
174
175    pub fn token_owner(mut self, owner: Pubkey) -> Self {
176        self.token_state.owner = owner;
177        self
178    }
179
180    pub fn amount(mut self, amount: u64) -> Self {
181        self.token_state.amount = amount;
182        self
183    }
184
185    pub fn delegate(mut self, delegate: Option<Pubkey>) -> Self {
186        self.token_state.delegate = match delegate {
187            Some(pk) => COption::Some(pk),
188            None => COption::None,
189        };
190        self
191    }
192
193    pub fn state(mut self, state: spl_token::state::AccountState) -> Self {
194        self.token_state.state = state;
195        self
196    }
197
198    pub fn is_native(mut self, native_amount: Option<u64>) -> Self {
199        self.token_state.is_native = match native_amount {
200            Some(amount) => COption::Some(amount),
201            None => COption::None,
202        };
203        self
204    }
205
206    pub fn delegated_amount(mut self, amount: u64) -> Self {
207        self.token_state.delegated_amount = amount;
208        self
209    }
210
211    pub fn close_authority(mut self, authority: Option<Pubkey>) -> Self {
212        self.token_state.close_authority = match authority {
213            Some(pk) => COption::Some(pk),
214            None => COption::None,
215        };
216        self
217    }
218}