← Program logic

programs/bond_servicing/src/instructions/redeem_holder.rs

Source at revision e5ff2b3cb157, the source of the program deployed on devnet. SHA-256 of this file a23f42b356f2b3ae64cb743dc614fe3b7a34445707634ed26bf3d2acf398c1b5

1//! Redeem one holder at maturity. Anyone may call it once the redemption is funded and payment has opened.
2//! In one transaction the program burns the holder's free bonds (permissioned burn: the Bond PDA signs as burn
3//! authority and permanent delegate) and pays their principal from the redemption vault. No bond is burned unpaid,
4//! no principal is paid for a live bond.
5//!   - Bonds still committed to an offer (accepted, not yet bought) are left to that offer; the rest is redeemed
6//!     now, so a blocked offer cannot hold back the holder's redemption.
7//!   - Principal is computed on the cumulative quantity redeemed: principal(redeemed + now) - principal(redeemed),
8//!     so partial redemptions round exactly like one.
9//!   - The holder's history is saved first, so the final coupon stays payable whether it is paid before or after.
10
11use anchor_lang::prelude::*;
12use anchor_spl::associated_token::AssociatedToken;
13use anchor_spl::token_2022::Token2022;
14use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface};
15
16use crate::constants::*;
17use crate::error::BondError;
18use crate::events::BondsRedeemed;
19use crate::settlement::{burn_bonds, pay_cash, retire_quantity, Cash};
20use crate::state::{Bond, Event, EventKind, HolderRecord};
21
22#[derive(Accounts)]
23pub struct RedeemHolder<'info> {
24    #[account(mut)]
25    pub payer: Signer<'info>,
26
27    #[account(mut, has_one = cash_mint, has_one = mint)]
28    pub bond: Box<Account<'info, Bond>>,
29
30    #[account(mut)]
31    pub mint: Box<InterfaceAccount<'info, Mint>>,
32
33    #[account(has_one = bond, has_one = vault, seeds = [EVENT_SEED, bond.key().as_ref(), &event.index.to_le_bytes()], bump = event.bump)]
34    pub event: Box<Account<'info, Event>>,
35
36    #[account(mut, has_one = bond, has_one = owner, seeds = [HOLDER_SEED, mint.key().as_ref(), owner.key().as_ref()], bump = holder.bump)]
37    pub holder: Box<Account<'info, HolderRecord>>,
38
39    /// CHECK: the holder's wallet, checked by `has_one = owner` on the holder record.
40    pub owner: UncheckedAccount<'info>,
41
42    #[account(
43        mut,
44        associated_token::mint = mint,
45        associated_token::authority = owner,
46        associated_token::token_program = token_program,
47    )]
48    pub holder_bonds: Box<InterfaceAccount<'info, TokenAccount>>,
49
50    #[account(mut)]
51    pub vault: Box<InterfaceAccount<'info, TokenAccount>>,
52
53    pub cash_mint: Box<InterfaceAccount<'info, Mint>>,
54
55    #[account(
56        init_if_needed,
57        payer = payer,
58        associated_token::mint = cash_mint,
59        associated_token::authority = owner,
60        associated_token::token_program = cash_token_program,
61    )]
62    pub holder_cash: Box<InterfaceAccount<'info, TokenAccount>>,
63
64    /// CHECK: SPL Memo program; needed only when the holder's cash account requires memos (checked when used).
65    pub memo_program: Option<UncheckedAccount<'info>>,
66
67    pub token_program: Program<'info, Token2022>,
68    pub cash_token_program: Interface<'info, TokenInterface>,
69    pub associated_token_program: Program<'info, AssociatedToken>,
70    pub system_program: Program<'info, System>,
71}
72
73pub fn handle_redeem_holder(ctx: Context<RedeemHolder>) -> Result<()> {
74    let a = &mut *ctx.accounts;
75    require!(a.event.kind == EventKind::Redemption, BondError::WrongEventKind);
76    require!(a.event.funded, BondError::NotFunded);
77    require!(a.bond.now()? >= a.event.payment_time, BondError::PaymentNotOpen);
78    let passed = a.bond.periods();
79    let position = a.holder.position.quantity;
80    require!(a.holder_bonds.amount == position, BondError::PositionMismatch);
81    let free = position.checked_sub(a.holder.locked).ok_or(BondError::Overflow)?;
82    require!(free > 0, BondError::NothingToPay);
83    let before = a.holder.redeemed;
84    let after = before.checked_add(free).ok_or(BondError::Overflow)?;
85    let principal = a.bond.principal(after)? - a.bond.principal(before)?;
86
87    let mint_key = a.bond.mint;
88    let bond_key = a.bond.key();
89    let bump = a.bond.bump;
90    let seeds: &[&[u8]] = &[BOND_SEED, mint_key.as_ref(), &[bump]];
91    let bond_info = a.bond.to_account_info();
92    burn_bonds(
93        &a.token_program.to_account_info(),
94        &a.holder_bonds.to_account_info(),
95        &a.mint.to_account_info(),
96        &bond_info,
97        free,
98        seeds,
99    )?;
100    let memo_info = a.memo_program.as_ref().map(|m| m.to_account_info());
101    pay_cash(
102        &Cash {
103            cash_token_program: &a.cash_token_program.to_account_info(),
104            vault: &a.vault.to_account_info(),
105            cash_mint: &a.cash_mint.to_account_info(),
106            holder_cash: &a.holder_cash.to_account_info(),
107            bond: &bond_info,
108            memo_program: memo_info.as_ref(),
109        },
110        principal,
111        a.cash_mint.decimals,
112        "bond_servicing: redemption",
113        seeds,
114    )?;
115    retire_quantity(&mut a.holder, &mut a.bond, bond_key, passed, free)?;
116    a.holder.redeemed = after;
117    emit!(BondsRedeemed { bond: bond_key, owner: a.holder.owner, burned: free, principal });
118    Ok(())
119}