Skip to content

Commit f1db922

Browse files
committed
refactor(finance/escrow): extract PDA-aware token transfer/close helpers
Add an owning_pda_seeds option to transfer_tokens and a new close_token_account helper in shared.rs, then use them across make_offer, take_offer, and cancel_offer. This removes the duplicated CpiContext::new_with_signer + transfer_checked/close_account boilerplate that was hand-rolled in each handler. No behavior change: per-maker PDA seeds and has_one checks are unchanged. Verified with cargo test (5 tests pass).
1 parent 53fbf2f commit f1db922

4 files changed

Lines changed: 87 additions & 86 deletions

File tree

finance/escrow/anchor/programs/escrow/src/instructions/cancel_offer.rs

Lines changed: 20 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@ use anchor_lang::prelude::*;
22

33
use anchor_spl::{
44
associated_token::AssociatedToken,
5-
token_interface::{
6-
close_account, transfer_checked, CloseAccount, Mint, TokenAccount, TokenInterface,
7-
TransferChecked,
8-
},
5+
token_interface::{Mint, TokenAccount, TokenInterface},
96
};
107

118
use crate::Offer;
129

10+
use super::{close_token_account, transfer_tokens};
11+
1312
// Cancel an outstanding offer. Without this handler, an abandoned offer would
1413
// keep the maker's token-A locked in the vault forever (and the offer
1514
// account's rent unclaimed). The maker signs, the vault tokens flow back to
@@ -55,45 +54,28 @@ pub struct CancelOffer<'info> {
5554
pub fn handle_cancel_offer(context: Context<CancelOffer>) -> Result<()> {
5655
let maker_key = context.accounts.maker.key();
5756
let id_bytes = context.accounts.offer.id.to_le_bytes();
58-
let seeds = &[
59-
b"offer".as_ref(),
60-
maker_key.as_ref(),
61-
id_bytes.as_ref(),
62-
&[context.accounts.offer.bump],
63-
];
64-
let signer_seeds = [&seeds[..]];
57+
let bump = [context.accounts.offer.bump];
58+
let offer_seeds: &[&[u8]] = &[b"offer", maker_key.as_ref(), id_bytes.as_ref(), &bump];
6559

6660
// Move all tokens back from the vault to the maker.
67-
let vault_amount = context.accounts.vault.amount;
68-
let transfer_accounts = TransferChecked {
69-
from: context.accounts.vault.to_account_info(),
70-
mint: context.accounts.token_mint_a.to_account_info(),
71-
to: context.accounts.maker_token_account_a.to_account_info(),
72-
authority: context.accounts.offer.to_account_info(),
73-
};
74-
let cpi_context = CpiContext::new_with_signer(
75-
context.accounts.token_program.key(),
76-
transfer_accounts,
77-
&signer_seeds,
78-
);
79-
transfer_checked(
80-
cpi_context,
81-
vault_amount,
82-
context.accounts.token_mint_a.decimals,
61+
transfer_tokens(
62+
&context.accounts.vault,
63+
&context.accounts.maker_token_account_a,
64+
&context.accounts.vault.amount,
65+
&context.accounts.token_mint_a,
66+
&context.accounts.offer.to_account_info(),
67+
&context.accounts.token_program,
68+
Some(offer_seeds),
8369
)?;
8470

8571
// Close the vault, sending its rent lamports back to the maker.
86-
let close_accounts = CloseAccount {
87-
account: context.accounts.vault.to_account_info(),
88-
destination: context.accounts.maker.to_account_info(),
89-
authority: context.accounts.offer.to_account_info(),
90-
};
91-
let cpi_context = CpiContext::new_with_signer(
92-
context.accounts.token_program.key(),
93-
close_accounts,
94-
&signer_seeds,
95-
);
96-
close_account(cpi_context)?;
72+
close_token_account(
73+
&context.accounts.vault,
74+
&context.accounts.maker.to_account_info(),
75+
&context.accounts.offer.to_account_info(),
76+
&context.accounts.token_program,
77+
Some(offer_seeds),
78+
)?;
9779

9880
// The offer account itself is closed by the `close = maker` constraint
9981
// above, which refunds its rent to the maker.

finance/escrow/anchor/programs/escrow/src/instructions/make_offer.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,9 @@ pub fn handle_send_offered_tokens_to_vault(
7676
&context.accounts.vault,
7777
&token_a_offered_amount,
7878
&context.accounts.token_mint_a,
79-
&context.accounts.maker,
79+
&context.accounts.maker.to_account_info(),
8080
&context.accounts.token_program,
81+
None,
8182
)
8283
}
8384

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
use anchor_lang::prelude::*;
22

33
use anchor_spl::token_interface::{
4-
transfer_checked, Mint, TokenAccount, TokenInterface, TransferChecked,
4+
close_account, transfer_checked, CloseAccount, Mint, TokenAccount, TokenInterface,
5+
TransferChecked,
56
};
67

8+
// Transfer tokens from one token account to another.
9+
// When transferring out of a token account owned by a PDA, pass the PDA's
10+
// signer seeds via owning_pda_seeds; otherwise pass None.
711
pub fn transfer_tokens<'info>(
812
from: &InterfaceAccount<'info, TokenAccount>,
913
to: &InterfaceAccount<'info, TokenAccount>,
1014
amount: &u64,
1115
mint: &InterfaceAccount<'info, Mint>,
12-
authority: &Signer<'info>,
16+
authority: &AccountInfo<'info>,
1317
token_program: &Interface<'info, TokenInterface>,
18+
owning_pda_seeds: Option<&[&[u8]]>,
1419
) -> Result<()> {
1520
let transfer_accounts = TransferChecked {
1621
from: from.to_account_info(),
@@ -19,7 +24,40 @@ pub fn transfer_tokens<'info>(
1924
authority: authority.to_account_info(),
2025
};
2126

22-
let cpi_context = CpiContext::new(token_program.key(), transfer_accounts);
27+
let signer_seeds = owning_pda_seeds.map(|seeds| [seeds]);
28+
let cpi_context = match signer_seeds.as_ref() {
29+
Some(signer_seeds) => {
30+
CpiContext::new_with_signer(token_program.key(), transfer_accounts, signer_seeds)
31+
}
32+
None => CpiContext::new(token_program.key(), transfer_accounts),
33+
};
2334

2435
transfer_checked(cpi_context, *amount, mint.decimals)
2536
}
37+
38+
// Close a token account, sending its rent lamports to destination.
39+
// When the token account is owned by a PDA, pass the PDA's signer seeds via
40+
// owning_pda_seeds; otherwise pass None.
41+
pub fn close_token_account<'info>(
42+
token_account: &InterfaceAccount<'info, TokenAccount>,
43+
destination: &AccountInfo<'info>,
44+
authority: &AccountInfo<'info>,
45+
token_program: &Interface<'info, TokenInterface>,
46+
owning_pda_seeds: Option<&[&[u8]]>,
47+
) -> Result<()> {
48+
let close_accounts = CloseAccount {
49+
account: token_account.to_account_info(),
50+
destination: destination.to_account_info(),
51+
authority: authority.to_account_info(),
52+
};
53+
54+
let signer_seeds = owning_pda_seeds.map(|seeds| [seeds]);
55+
let cpi_context = match signer_seeds.as_ref() {
56+
Some(signer_seeds) => {
57+
CpiContext::new_with_signer(token_program.key(), close_accounts, signer_seeds)
58+
}
59+
None => CpiContext::new(token_program.key(), close_accounts),
60+
};
61+
62+
close_account(cpi_context)
63+
}

finance/escrow/anchor/programs/escrow/src/instructions/take_offer.rs

Lines changed: 24 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,12 @@ use anchor_lang::prelude::*;
22

33
use anchor_spl::{
44
associated_token::AssociatedToken,
5-
token_interface::{
6-
close_account, transfer_checked, CloseAccount, Mint, TokenAccount, TokenInterface,
7-
TransferChecked,
8-
},
5+
token_interface::{Mint, TokenAccount, TokenInterface},
96
};
107

118
use crate::Offer;
129

13-
use super::transfer_tokens;
10+
use super::{close_token_account, transfer_tokens};
1411

1512
#[derive(Accounts)]
1613
pub struct TakeOffer<'info> {
@@ -82,50 +79,33 @@ pub fn handle_send_wanted_tokens_to_maker(context: &Context<TakeOffer>) -> Resul
8279
&context.accounts.maker_token_account_b,
8380
&context.accounts.offer.token_b_wanted_amount,
8481
&context.accounts.token_mint_b,
85-
&context.accounts.taker,
82+
&context.accounts.taker.to_account_info(),
8683
&context.accounts.token_program,
84+
None,
8785
)
8886
}
8987

9088
pub fn handle_withdraw_and_close_vault(context: Context<TakeOffer>) -> Result<()> {
91-
let seeds = &[
92-
b"offer",
93-
context.accounts.maker.to_account_info().key.as_ref(),
94-
&context.accounts.offer.id.to_le_bytes()[..],
95-
&[context.accounts.offer.bump],
96-
];
97-
let signer_seeds = [&seeds[..]];
98-
99-
let accounts = TransferChecked {
100-
from: context.accounts.vault.to_account_info(),
101-
mint: context.accounts.token_mint_a.to_account_info(),
102-
to: context.accounts.taker_token_account_a.to_account_info(),
103-
authority: context.accounts.offer.to_account_info(),
104-
};
105-
106-
let cpi_context = CpiContext::new_with_signer(
107-
context.accounts.token_program.key(),
108-
accounts,
109-
&signer_seeds,
110-
);
111-
112-
transfer_checked(
113-
cpi_context,
114-
context.accounts.vault.amount,
115-
context.accounts.token_mint_a.decimals,
116-
)?;
117-
118-
let accounts = CloseAccount {
119-
account: context.accounts.vault.to_account_info(),
120-
destination: context.accounts.taker.to_account_info(),
121-
authority: context.accounts.offer.to_account_info(),
122-
};
89+
let maker_key = context.accounts.maker.key();
90+
let id_bytes = context.accounts.offer.id.to_le_bytes();
91+
let bump = [context.accounts.offer.bump];
92+
let offer_seeds: &[&[u8]] = &[b"offer", maker_key.as_ref(), id_bytes.as_ref(), &bump];
12393

124-
let cpi_context = CpiContext::new_with_signer(
125-
context.accounts.token_program.key(),
126-
accounts,
127-
&signer_seeds,
128-
);
94+
transfer_tokens(
95+
&context.accounts.vault,
96+
&context.accounts.taker_token_account_a,
97+
&context.accounts.vault.amount,
98+
&context.accounts.token_mint_a,
99+
&context.accounts.offer.to_account_info(),
100+
&context.accounts.token_program,
101+
Some(offer_seeds),
102+
)?;
129103

130-
close_account(cpi_context)
104+
close_token_account(
105+
&context.accounts.vault,
106+
&context.accounts.taker.to_account_info(),
107+
&context.accounts.offer.to_account_info(),
108+
&context.accounts.token_program,
109+
Some(offer_seeds),
110+
)
131111
}

0 commit comments

Comments
 (0)