solana_farm_sdk/program/
pda.rs

1//! Common PDA functions
2
3use {
4    crate::program::account,
5    solana_program::{
6        account_info::AccountInfo, entrypoint::ProgramResult, program, program_error::ProgramError,
7        program_pack::Pack, pubkey::Pubkey, rent::Rent, system_instruction, sysvar, sysvar::Sysvar,
8    },
9};
10
11pub fn init_token_account<'a, 'b>(
12    funding_account: &'a AccountInfo<'b>,
13    target_account: &'a AccountInfo<'b>,
14    mint_account: &'a AccountInfo<'b>,
15    owner_account: &'a AccountInfo<'b>,
16    rent_program: &'a AccountInfo<'b>,
17    base_address: &Pubkey,
18    seeds: &[&[u8]],
19) -> ProgramResult {
20    if account::exists(target_account)? {
21        if !account::check_token_account_owner(target_account, owner_account.key)? {
22            return Err(ProgramError::IllegalOwner);
23        }
24        if target_account.data_len() != spl_token::state::Account::get_packed_len()
25            || mint_account.key != &account::get_token_account_mint(target_account)?
26        {
27            return Err(ProgramError::InvalidAccountData);
28        }
29        return Ok(());
30    }
31
32    init_system_account(
33        funding_account,
34        target_account,
35        &spl_token::id(),
36        base_address,
37        seeds,
38        spl_token::state::Account::get_packed_len(),
39    )?;
40
41    program::invoke(
42        &spl_token::instruction::initialize_account(
43            &spl_token::id(),
44            target_account.key,
45            mint_account.key,
46            owner_account.key,
47        )?,
48        &[
49            target_account.clone(),
50            mint_account.clone(),
51            owner_account.clone(),
52            rent_program.clone(),
53        ],
54    )
55}
56
57pub fn init_associated_token_account<'a, 'b>(
58    funding_account: &'a AccountInfo<'b>,
59    wallet_account: &'a AccountInfo<'b>,
60    target_account: &'a AccountInfo<'b>,
61    mint_account: &'a AccountInfo<'b>,
62    rent_program: &'a AccountInfo<'b>,
63) -> ProgramResult {
64    if account::exists(target_account)? {
65        if !account::check_token_account_owner(target_account, wallet_account.key)? {
66            return Err(ProgramError::IllegalOwner);
67        }
68        if target_account.data_len() != spl_token::state::Account::get_packed_len()
69            || mint_account.key != &account::get_token_account_mint(target_account)?
70        {
71            return Err(ProgramError::InvalidAccountData);
72        }
73        return Ok(());
74    }
75
76    program::invoke(
77        &spl_associated_token_account::create_associated_token_account(
78            funding_account.key,
79            wallet_account.key,
80            mint_account.key,
81        ),
82        &[
83            funding_account.clone(),
84            target_account.clone(),
85            wallet_account.clone(),
86            mint_account.clone(),
87            rent_program.clone(),
88        ],
89    )
90}
91
92pub fn close_token_account_with_seeds<'a, 'b>(
93    receiving_account: &'a AccountInfo<'b>,
94    target_account: &'a AccountInfo<'b>,
95    authority_account: &'a AccountInfo<'b>,
96    seeds: &[&[&[u8]]],
97) -> ProgramResult {
98    if !account::exists(target_account)? {
99        return Ok(());
100    }
101
102    program::invoke_signed(
103        &spl_token::instruction::close_account(
104            &spl_token::id(),
105            target_account.key,
106            receiving_account.key,
107            authority_account.key,
108            &[],
109        )?,
110        &[
111            target_account.clone(),
112            receiving_account.clone(),
113            authority_account.clone(),
114        ],
115        seeds,
116    )
117}
118
119pub fn close_token_account<'a, 'b>(
120    receiving_account: &'a AccountInfo<'b>,
121    target_account: &'a AccountInfo<'b>,
122    authority_account: &'a AccountInfo<'b>,
123    base_address: &Pubkey,
124    seeds: &[&[u8]],
125) -> Result<u8, ProgramError> {
126    let (_, bump) = Pubkey::find_program_address(seeds, base_address);
127
128    close_token_account_with_seeds(
129        receiving_account,
130        target_account,
131        authority_account,
132        &[&[seeds, &[&[bump]]].concat()],
133    )?;
134
135    Ok(bump)
136}
137
138pub fn transfer_tokens_with_seeds<'a, 'b>(
139    source_account: &'a AccountInfo<'b>,
140    destination_account: &'a AccountInfo<'b>,
141    authority_account: &'a AccountInfo<'b>,
142    seeds: &[&[&[u8]]],
143    amount: u64,
144) -> ProgramResult {
145    if source_account.key == destination_account.key {
146        return Err(ProgramError::InvalidArgument);
147    }
148    program::invoke_signed(
149        &spl_token::instruction::transfer(
150            &spl_token::id(),
151            source_account.key,
152            destination_account.key,
153            authority_account.key,
154            &[],
155            amount,
156        )?,
157        &[
158            source_account.clone(),
159            destination_account.clone(),
160            authority_account.clone(),
161        ],
162        seeds,
163    )
164}
165
166pub fn transfer_tokens<'a, 'b>(
167    source_account: &'a AccountInfo<'b>,
168    destination_account: &'a AccountInfo<'b>,
169    authority_account: &'a AccountInfo<'b>,
170    base_address: &Pubkey,
171    seeds: &[&[u8]],
172    amount: u64,
173) -> Result<u8, ProgramError> {
174    let (_, bump) = Pubkey::find_program_address(seeds, base_address);
175
176    transfer_tokens_with_seeds(
177        source_account,
178        destination_account,
179        authority_account,
180        &[&[seeds, &[&[bump]]].concat()],
181        amount,
182    )?;
183
184    Ok(bump)
185}
186
187pub fn init_system_account<'a, 'b>(
188    funding_account: &'a AccountInfo<'b>,
189    target_account: &'a AccountInfo<'b>,
190    owner_key: &Pubkey,
191    base_address: &Pubkey,
192    seeds: &[&[u8]],
193    data_size: usize,
194) -> Result<u8, ProgramError> {
195    if account::exists(target_account)? {
196        if target_account.owner != owner_key {
197            return Err(ProgramError::IllegalOwner);
198        }
199        if target_account.data_len() != data_size {
200            return Err(ProgramError::InvalidAccountData);
201        }
202        return Ok(Pubkey::find_program_address(seeds, base_address).1);
203    }
204
205    let (key, bump) = Pubkey::find_program_address(seeds, base_address);
206    if target_account.key != &key {
207        return Err(ProgramError::InvalidSeeds);
208    }
209
210    let min_balance = sysvar::rent::Rent::get()
211        .unwrap()
212        .minimum_balance(data_size);
213    program::invoke_signed(
214        &system_instruction::create_account(
215            funding_account.key,
216            target_account.key,
217            min_balance,
218            data_size as u64,
219            owner_key,
220        ),
221        &[funding_account.clone(), target_account.clone()],
222        &[&[seeds, &[&[bump]]].concat()],
223    )?;
224
225    Ok(bump)
226}
227
228pub fn init_mint<'a, 'b>(
229    funding_account: &'a AccountInfo<'b>,
230    mint_account: &'a AccountInfo<'b>,
231    authority_account: &'a AccountInfo<'b>,
232    rent_program: &'a AccountInfo<'b>,
233    base_address: &Pubkey,
234    seeds: &[&[u8]],
235    decimals: u8,
236) -> ProgramResult {
237    if account::exists(mint_account)? {
238        if !account::check_mint_authority(mint_account, Some(*authority_account.key))? {
239            return Err(ProgramError::IllegalOwner);
240        }
241        if mint_account.data_len() != spl_token::state::Mint::get_packed_len() {
242            return Err(ProgramError::InvalidAccountData);
243        }
244        return Ok(());
245    }
246
247    let acc_size = spl_token::state::Mint::get_packed_len();
248    init_system_account(
249        funding_account,
250        mint_account,
251        &spl_token::id(),
252        base_address,
253        seeds,
254        acc_size,
255    )?;
256
257    program::invoke(
258        &spl_token::instruction::initialize_mint(
259            &spl_token::id(),
260            mint_account.key,
261            authority_account.key,
262            Some(authority_account.key),
263            decimals,
264        )?,
265        &[
266            mint_account.clone(),
267            authority_account.clone(),
268            rent_program.clone(),
269        ],
270    )
271}
272
273pub fn mint_to_with_seeds<'a, 'b>(
274    target_token_account: &'a AccountInfo<'b>,
275    mint_account: &'a AccountInfo<'b>,
276    mint_authority_account: &'a AccountInfo<'b>,
277    seeds: &[&[&[u8]]],
278    amount: u64,
279) -> ProgramResult {
280    solana_program::program::invoke_signed(
281        &spl_token::instruction::mint_to(
282            &spl_token::id(),
283            mint_account.key,
284            target_token_account.key,
285            mint_authority_account.key,
286            &[],
287            amount,
288        )?,
289        &[
290            mint_account.clone(),
291            target_token_account.clone(),
292            mint_authority_account.clone(),
293        ],
294        seeds,
295    )
296}
297
298pub fn mint_to<'a, 'b>(
299    target_token_account: &'a AccountInfo<'b>,
300    mint_account: &'a AccountInfo<'b>,
301    mint_authority_account: &'a AccountInfo<'b>,
302    base_address: &Pubkey,
303    seeds: &[&[u8]],
304    amount: u64,
305) -> Result<u8, ProgramError> {
306    let (_, bump) = Pubkey::find_program_address(seeds, base_address);
307
308    mint_to_with_seeds(
309        target_token_account,
310        mint_account,
311        mint_authority_account,
312        &[&[seeds, &[&[bump]]].concat()],
313        amount,
314    )?;
315
316    Ok(bump)
317}
318
319pub fn burn_tokens_with_seeds<'a, 'b>(
320    from_token_account: &'a AccountInfo<'b>,
321    mint_account: &'a AccountInfo<'b>,
322    authority_account: &'a AccountInfo<'b>,
323    seeds: &[&[&[u8]]],
324    amount: u64,
325) -> ProgramResult {
326    solana_program::program::invoke_signed(
327        &spl_token::instruction::burn(
328            &spl_token::id(),
329            from_token_account.key,
330            mint_account.key,
331            authority_account.key,
332            &[],
333            amount,
334        )?,
335        &[
336            from_token_account.clone(),
337            mint_account.clone(),
338            authority_account.clone(),
339        ],
340        seeds,
341    )
342}
343
344pub fn burn_tokens<'a, 'b>(
345    from_token_account: &'a AccountInfo<'b>,
346    mint_account: &'a AccountInfo<'b>,
347    authority_account: &'a AccountInfo<'b>,
348    base_address: &Pubkey,
349    seeds: &[&[u8]],
350    amount: u64,
351) -> Result<u8, ProgramError> {
352    let (_, bump) = Pubkey::find_program_address(seeds, base_address);
353
354    burn_tokens_with_seeds(
355        from_token_account,
356        mint_account,
357        authority_account,
358        &[&[seeds, &[&[bump]]].concat()],
359        amount,
360    )?;
361
362    Ok(bump)
363}
364
365pub fn approve_delegate_with_seeds<'a, 'b>(
366    source_account: &'a AccountInfo<'b>,
367    delegate_account: &'a AccountInfo<'b>,
368    authority_account: &'a AccountInfo<'b>,
369    seeds: &[&[&[u8]]],
370    amount: u64,
371) -> ProgramResult {
372    solana_program::program::invoke_signed(
373        &spl_token::instruction::approve(
374            &spl_token::id(),
375            source_account.key,
376            delegate_account.key,
377            authority_account.key,
378            &[],
379            amount,
380        )?,
381        &[
382            source_account.clone(),
383            delegate_account.clone(),
384            authority_account.clone(),
385        ],
386        seeds,
387    )
388}
389
390pub fn approve_delegate<'a, 'b>(
391    source_account: &'a AccountInfo<'b>,
392    delegate_account: &'a AccountInfo<'b>,
393    authority_account: &'a AccountInfo<'b>,
394    base_address: &Pubkey,
395    seeds: &[&[u8]],
396    amount: u64,
397) -> Result<u8, ProgramError> {
398    let (_, bump) = Pubkey::find_program_address(seeds, base_address);
399
400    approve_delegate_with_seeds(
401        source_account,
402        delegate_account,
403        authority_account,
404        &[&[seeds, &[&[bump]]].concat()],
405        amount,
406    )?;
407
408    Ok(bump)
409}
410
411pub fn revoke_delegate_with_seeds<'a, 'b>(
412    source_account: &'a AccountInfo<'b>,
413    authority_account: &'a AccountInfo<'b>,
414    seeds: &[&[&[u8]]],
415) -> ProgramResult {
416    solana_program::program::invoke_signed(
417        &spl_token::instruction::revoke(
418            &spl_token::id(),
419            source_account.key,
420            authority_account.key,
421            &[],
422        )?,
423        &[source_account.clone(), authority_account.clone()],
424        seeds,
425    )
426}
427
428pub fn revoke_delegate<'a, 'b>(
429    source_account: &'a AccountInfo<'b>,
430    authority_account: &'a AccountInfo<'b>,
431    base_address: &Pubkey,
432    seeds: &[&[u8]],
433) -> Result<u8, ProgramError> {
434    let (_, bump) = Pubkey::find_program_address(seeds, base_address);
435
436    revoke_delegate_with_seeds(
437        source_account,
438        authority_account,
439        &[&[seeds, &[&[bump]]].concat()],
440    )?;
441
442    Ok(bump)
443}
444
445pub fn check_pda_data_size<'a, 'b>(
446    target_account: &'a AccountInfo<'b>,
447    seeds: &[&[u8]],
448    data_size: usize,
449    fix: bool,
450) -> ProgramResult {
451    if fix && target_account.data_is_empty() {
452        program::invoke_signed(
453            &system_instruction::allocate(target_account.key, data_size as u64),
454            &[target_account.clone()],
455            &[seeds],
456        )?;
457    }
458    if target_account.data_len() < data_size {
459        Err(ProgramError::AccountDataTooSmall)
460    } else {
461        Ok(())
462    }
463}
464
465pub fn check_pda_rent_exempt<'a, 'b>(
466    signer_account: &'a AccountInfo<'b>,
467    target_account: &'a AccountInfo<'b>,
468    seeds: &[&[u8]],
469    data_size: usize,
470    fix: bool,
471) -> ProgramResult {
472    let rent = Rent::get()?;
473    let cur_balance = target_account.try_lamports()?;
474    let min_balance = rent.minimum_balance(data_size);
475    if cur_balance < min_balance {
476        let signer_balance = signer_account.try_lamports()?;
477        let signer_min_balance = rent.minimum_balance(signer_account.data_len());
478        if !fix
479            || signer_balance <= signer_min_balance
480            || min_balance.checked_sub(cur_balance).unwrap()
481                > signer_balance.checked_sub(signer_min_balance).unwrap()
482        {
483            return Err(ProgramError::InsufficientFunds);
484        }
485        program::invoke_signed(
486            &system_instruction::transfer(
487                signer_account.key,
488                target_account.key,
489                min_balance.checked_sub(cur_balance).unwrap(),
490            ),
491            &[signer_account.clone(), target_account.clone()],
492            &[seeds],
493        )?;
494        assert!(target_account.try_lamports()? >= min_balance);
495    }
496    Ok(())
497}
498
499pub fn check_pda_owner<'a, 'b>(
500    program_id: &Pubkey,
501    target_account: &'a AccountInfo<'b>,
502    seeds: &[&[u8]],
503    fix: bool,
504) -> ProgramResult {
505    if *target_account.owner != *program_id {
506        if fix {
507            program::invoke_signed(
508                &system_instruction::assign(target_account.key, program_id),
509                &[target_account.clone()],
510                &[seeds],
511            )?;
512            assert!(*target_account.owner == *program_id);
513        } else {
514            return Err(ProgramError::IllegalOwner);
515        }
516    }
517    Ok(())
518}