forge_api/
loaders.rs

1
2use forge_utils::AccountDeserialize;
3use solana_program::{
4    account_info::AccountInfo,
5    program_error::ProgramError,
6    program_pack::Pack,
7    pubkey::Pubkey,
8    system_program,
9    sysvar,
10    msg,
11};
12use spl_token::state::Mint;
13use mpl_core::{Asset, types::UpdateAuthority};
14
15use crate::{
16    consts::*, state::{Config, Enhancer, Treasury}, utils::Discriminator
17};
18
19/// Errors if:
20/// - Account is not a signer.
21pub fn load_signer<'a, 'info>(info: &'a AccountInfo<'info>) -> Result<(), ProgramError> {
22    if !info.is_signer {
23        return Err(ProgramError::MissingRequiredSignature);
24    }
25
26    Ok(())
27}
28
29/// Errors if:
30/// - Owner is not SPL token program.
31/// - Address does not match the expected mint address.
32/// - Data is empty.
33/// - Data cannot deserialize into a mint account.
34/// - Expected to be writable, but is not.
35pub fn load_mint<'a, 'info>(
36    info: &'a AccountInfo<'info>,
37    address: Pubkey,
38    is_writable: bool,
39) -> Result<(), ProgramError> {
40    if info.owner.ne(&spl_token::id()) {
41        return Err(ProgramError::InvalidAccountOwner);
42    }
43
44    if info.key.ne(&address) {
45        return Err(ProgramError::InvalidSeeds);
46    }
47
48    if info.data_is_empty() {
49        return Err(ProgramError::UninitializedAccount);
50    }
51
52    Mint::unpack(&info.data.borrow())?;
53
54    if is_writable && !info.is_writable {
55        return Err(ProgramError::InvalidAccountData);
56    }
57
58    Ok(())
59}
60
61/// Errors if:
62/// - Owner is not SPL token program.
63/// - Data is empty.
64/// - Data cannot deserialize into a token account.
65/// - Token account owner does not match the expected owner address.
66/// - Token account mint does not match the expected mint address.
67/// - Expected to be writable, but is not.
68pub fn load_token_account<'a, 'info>(
69    info: &'a AccountInfo<'info>,
70    owner: Option<&Pubkey>,
71    mint: &Pubkey,
72    is_writable: bool,
73) -> Result<(), ProgramError> {
74    if info.owner.ne(&spl_token::id()) {
75        return Err(ProgramError::InvalidAccountOwner);
76    }
77
78    if info.data_is_empty() {
79        return Err(ProgramError::UninitializedAccount);
80    }
81
82    let account_data = info.data.borrow();
83    let account = spl_token::state::Account::unpack(&account_data)?;
84
85    if account.mint.ne(&mint) {
86        msg!("Invalid mint: {:?} == {:?}", account.mint, mint);
87        return Err(ProgramError::InvalidAccountData);
88    }
89
90    if let Some(owner) = owner {
91        if account.owner.ne(owner) {
92            msg!("Invalid owner: {:?} == {:?}", account.owner, owner);
93            return Err(ProgramError::InvalidAccountData);
94        }
95    }
96
97    if is_writable && !info.is_writable {
98        msg!("Invalid writable: {:?} == {:?}", info.is_writable, is_writable);
99        return Err(ProgramError::InvalidAccountData);
100    }
101
102    Ok(())
103}
104
105/// Errors if:
106/// - Address does not match the expected treasury tokens address.
107/// - Cannot load as a token accoun
108pub fn load_treasury_token_account<'a, 'info>(
109    info: &'a AccountInfo<'info>,
110    mint: Pubkey,
111    is_writable: bool,
112) -> Result<(), ProgramError> {
113    let treasury_tokens = spl_associated_token_account::get_associated_token_address(&TREASURY_ADDRESS, &mint);
114    
115    if info.key.ne(&treasury_tokens) {
116        return Err(ProgramError::InvalidSeeds);
117    }
118
119    load_token_account(info, Some(&TREASURY_ADDRESS), &mint, is_writable)
120}
121
122pub fn load_collection_authority<'a, 'info>(
123    info: &'a AccountInfo<'info>,
124    seeds: &[&[u8]],
125    bump: u8,
126    program_id: &Pubkey,
127) -> Result<(), ProgramError> {
128    let pda = Pubkey::find_program_address(seeds, program_id);
129
130    if info.key.ne(&pda.0) {
131        return Err(ProgramError::InvalidSeeds);
132    }
133
134    if bump.ne(&pda.1) {
135        return Err(ProgramError::InvalidSeeds);
136    }
137
138    Ok(())
139}
140
141/// Errors if:
142/// - Address does not match PDA derived from provided seeds.
143/// - Cannot load as an uninitialized account.
144pub fn load_uninitialized_pda<'a, 'info>(
145    info: &'a AccountInfo<'info>,
146    seeds: &[&[u8]],
147    bump: u8,
148    program_id: &Pubkey,
149) -> Result<(), ProgramError> {
150    let pda = Pubkey::find_program_address(seeds, program_id);
151
152    if info.key.ne(&pda.0) {
153        return Err(ProgramError::InvalidSeeds);
154    }
155
156    if bump.ne(&pda.1) {
157        return Err(ProgramError::InvalidSeeds);
158    }
159
160    load_system_account(info, true)
161}
162
163/// Errors if:
164/// - Owner is not the system program.
165/// - Data is not empty.
166/// - Account is not writable.
167pub fn load_system_account<'a, 'info>(
168    info: &'a AccountInfo<'info>,
169    is_writable: bool,
170) -> Result<(), ProgramError> {
171    if info.owner.ne(&system_program::id()) {
172        return Err(ProgramError::InvalidAccountOwner);
173    }
174
175    if !info.data_is_empty() {
176        return Err(ProgramError::AccountAlreadyInitialized);
177    }
178
179    if is_writable && !info.is_writable {
180        return Err(ProgramError::InvalidAccountData);
181    }
182
183    Ok(())
184}
185
186/// Errors if:
187/// - Address does not match the expected value.
188/// - Account is not executable.
189pub fn load_program<'a, 'info>(
190    info: &'a AccountInfo<'info>,
191    key: Pubkey,
192) -> Result<(), ProgramError> {
193    if info.key.ne(&key) {
194        return Err(ProgramError::IncorrectProgramId);
195    }
196
197    if !info.executable {
198        return Err(ProgramError::InvalidAccountData);
199    }
200
201    Ok(())
202}
203
204/// Errors if:
205/// - Owner is not Ore program.
206/// - Address does not match the expected address.
207/// - Data is empty.
208/// - Data cannot deserialize into a coal config account.
209/// - Expected to be writable, but is not.
210pub fn load_config<'a, 'info>(
211    info: &'a AccountInfo<'info>,
212    collection: Pubkey,
213    is_writable: bool,
214) -> Result<(), ProgramError> {
215    msg!("config_info: {:?}", info.key);
216    if info.owner.ne(&crate::id()) {
217        return Err(ProgramError::InvalidAccountOwner);
218    }
219
220    let pda = Pubkey::find_program_address(&[CONFIG_SEED, collection.as_ref()], &crate::id()).0;
221    if info.key.ne(&pda) {
222        return Err(ProgramError::InvalidSeeds);
223    }
224
225    if info.data_is_empty() {
226        return Err(ProgramError::UninitializedAccount);
227    }
228
229    if info.data.borrow()[0].ne(&(Config::discriminator() as u8)) {
230        return Err(solana_program::program_error::ProgramError::InvalidAccountData);
231    }
232
233    if is_writable && !info.is_writable {
234        return Err(ProgramError::InvalidAccountData);
235    }
236
237    Ok(())
238}
239
240/// Errors if:
241/// - Owner is not Ore program.
242/// - Address does not match the expected address.
243/// - Data is empty.
244/// - Data cannot deserialize into a treasury account.
245/// - Expected to be writable, but is not.
246pub fn load_treasury<'a, 'info>(
247    info: &'a AccountInfo<'info>,
248    is_writable: bool,
249) -> Result<(), ProgramError> {
250    if info.owner.ne(&crate::id()) {
251        return Err(ProgramError::InvalidAccountOwner);
252    }
253
254    if info.key.ne(&TREASURY_ADDRESS) {
255        return Err(ProgramError::InvalidSeeds);
256    }
257
258    if info.data_is_empty() {
259        return Err(ProgramError::UninitializedAccount);
260    }
261
262    if info.data.borrow()[0].ne(&(Treasury::discriminator() as u8)) {
263        return Err(solana_program::program_error::ProgramError::InvalidAccountData);
264    }
265
266    if is_writable && !info.is_writable {
267        return Err(ProgramError::InvalidAccountData);
268    }
269
270    Ok(())
271}
272
273/// Errors if:
274/// - Owner is not Coal program.
275/// - Data is empty.
276/// - Data cannot deserialize into a proof account.
277/// - Proof authority does not match the expected address.
278/// - Expected to be writable, but is not.
279pub fn load_enhance<'a, 'info>(
280    info: &'a AccountInfo<'info>,
281    authority: &Pubkey,
282    asset: &Pubkey,
283    is_writable: bool,
284) -> Result<(), ProgramError> {
285    if info.owner.ne(&crate::id()) {
286        return Err(ProgramError::InvalidAccountOwner);
287    }
288
289    if info.data_is_empty() {
290        return Err(ProgramError::UninitializedAccount);
291    }
292
293    let data = info.data.borrow();
294    let enhancer = Enhancer::try_from_bytes(&data)?;
295
296    if enhancer.authority.ne(&authority) {
297        return Err(ProgramError::InvalidAccountData);
298    }
299
300    if enhancer.asset.ne(&asset) {
301        return Err(ProgramError::InvalidAccountData);
302    }
303
304    if is_writable && !info.is_writable {
305        return Err(ProgramError::InvalidAccountData);
306    }
307
308    Ok(())
309}
310
311/// Errors if:
312/// - Data is empty.
313/// - Update authority is not the forge pickaxe collection.
314/// - Attributes plugin is not present.
315/// - Durability attribute is not present.
316/// - Multiplier attribute is not present.
317pub fn load_asset<'a, 'info>(
318    info: &'a AccountInfo<'info>,
319) -> Result<(f64, u64, String), ProgramError> {
320    if info.owner.ne(&mpl_core::ID) {
321        return Err(ProgramError::InvalidAccountOwner);
322    }
323
324    if info.data_is_empty() {
325        return Err(ProgramError::UninitializedAccount);
326    }
327
328    let asset = Asset::from_bytes(&info.data.borrow()).unwrap();
329
330    match asset.base.update_authority {
331        UpdateAuthority::Collection(address) => {
332            if address.ne(&COLLECTION) {
333                msg!("Invalid collection: {:?} == {:?}", address, COLLECTION);
334                return Err(ProgramError::InvalidAccountData);
335            }
336        }
337        _ => return Err(ProgramError::InvalidAccountData),
338    }
339
340    if asset.plugin_list.attributes.is_none() {
341        return Err(ProgramError::InvalidAccountData);
342    }
343
344	let attributes_plugin = asset.plugin_list.attributes.unwrap();
345	let durability_attr = attributes_plugin.attributes.attribute_list.iter().find(|attr| attr.key == "durability");
346	let multiplier_attr = attributes_plugin.attributes.attribute_list.iter().find(|attr| attr.key == "multiplier");
347    let resource_attr = attributes_plugin.attributes.attribute_list.iter().find(|attr| attr.key == "resource");
348    let durability = durability_attr.unwrap().value.parse::<f64>().unwrap();
349    let multiplier = multiplier_attr.unwrap().value.parse::<u64>().unwrap();
350    let resource = resource_attr.unwrap().value.clone();
351    
352    Ok((durability, multiplier, resource))
353}
354
355/// Errors if:
356/// - Owner is not the sysvar address.
357/// - Account cannot load with the expected address.
358pub fn load_sysvar<'a, 'info>(
359    info: &'a AccountInfo<'info>,
360    key: Pubkey,
361) -> Result<(), ProgramError> {
362    if info.owner.ne(&sysvar::id()) {
363        return Err(ProgramError::InvalidAccountOwner);
364    }
365
366    load_account(info, key, false)
367}
368
369/// Errors if:
370/// - Address does not match the expected value.
371/// - Expected to be writable, but is not.
372pub fn load_account<'a, 'info>(
373    info: &'a AccountInfo<'info>,
374    key: Pubkey,
375    is_writable: bool,
376) -> Result<(), ProgramError> {
377    if info.key.ne(&key) {
378        return Err(ProgramError::InvalidAccountData);
379    }
380
381    if is_writable && !info.is_writable {
382        return Err(ProgramError::InvalidAccountData);
383    }
384
385    Ok(())
386}
387