← Bond stages

programs/bond_servicing/src/instructions/pay_coupon.rs

Source snapshot · revision 5e22d3ea5fa4.

SHA-256 d329e12eced70f7735bf93d9ff19f5fe49b9ba12072334452801fa7f8c3aff98

1//! Pay one holder one coupon. Anyone may call it, at any time after payment opens, in any order: the
2//! position comes from the holder's own history (its quantity at the record moment), the amount from the terms.
3//! Writes only the holder's record (the paid bit), the event's vault and the holder's cash account; the bond
4//! and the event are read-only, so payments do not conflict with trading.
5//! The cash transfer and the paid bit are one atomic transaction. The holder's cash account is created if
6//! missing; the holder's bond account is not needed, so a closed bond account cannot block the payment.
7
8use anchor_lang::prelude::*;
9use anchor_spl::associated_token::AssociatedToken;
10use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface};
11
12use crate::constants::*;
13use crate::error::BondError;
14use crate::events::CouponPaid;
15use crate::settlement::{pay_cash, Cash};
16use crate::state::{Bond, Event, EventKind, HolderRecord};
17
18#[derive(Accounts)]
19pub struct PayCoupon<'info> {
20    #[account(mut)]
21    pub payer: Signer<'info>,
22
23    #[account(has_one = cash_mint)]
24    pub bond: Box<Account<'info, Bond>>,
25
26    #[account(has_one = bond, has_one = vault, seeds = [EVENT_SEED, bond.key().as_ref(), &event.index.to_le_bytes()], bump = event.bump)]
27    pub event: Box<Account<'info, Event>>,
28
29    #[account(mut, has_one = bond, has_one = owner, seeds = [HOLDER_SEED, bond.mint.as_ref(), owner.key().as_ref()], bump = holder.bump)]
30    pub holder: Box<Account<'info, HolderRecord>>,
31
32    /// CHECK: the holder's wallet, checked by `has_one = owner` on the holder record.
33    pub owner: UncheckedAccount<'info>,
34
35    #[account(mut)]
36    pub vault: Box<InterfaceAccount<'info, TokenAccount>>,
37
38    pub cash_mint: Box<InterfaceAccount<'info, Mint>>,
39
40    #[account(
41        init_if_needed,
42        payer = payer,
43        associated_token::mint = cash_mint,
44        associated_token::authority = owner,
45        associated_token::token_program = cash_token_program,
46    )]
47    pub holder_cash: Box<InterfaceAccount<'info, TokenAccount>>,
48
49    /// CHECK: SPL Memo program; needed only when the holder's cash account requires memos (checked when used).
50    pub memo_program: Option<UncheckedAccount<'info>>,
51
52    pub cash_token_program: Interface<'info, TokenInterface>,
53    pub associated_token_program: Program<'info, AssociatedToken>,
54    pub system_program: Program<'info, System>,
55}
56
57pub fn handle_pay_coupon(ctx: Context<PayCoupon>) -> Result<()> {
58    let bond = &ctx.accounts.bond;
59    let event = &ctx.accounts.event;
60    require!(event.kind == EventKind::Coupon, BondError::WrongEventKind);
61    require!(event.funded, BondError::NotFunded);
62    require!(bond.now()? >= event.payment_time, BondError::PaymentNotOpen);
63    let e = event.index;
64    require!(!ctx.accounts.holder.is_paid(e), BondError::AlreadyPaid);
65    let position = ctx.accounts.holder.position.at_moment(e);
66    let amount = bond.coupon(position, e)?;
67    require!(amount > 0, BondError::NothingToPay);
68
69    let mint_key = bond.mint;
70    let seeds: &[&[u8]] = &[BOND_SEED, mint_key.as_ref(), &[bond.bump]];
71    let memo_info = ctx.accounts.memo_program.as_ref().map(|m| m.to_account_info());
72    pay_cash(
73        &Cash {
74            cash_token_program: &ctx.accounts.cash_token_program.to_account_info(),
75            vault: &ctx.accounts.vault.to_account_info(),
76            cash_mint: &ctx.accounts.cash_mint.to_account_info(),
77            holder_cash: &ctx.accounts.holder_cash.to_account_info(),
78            bond: &ctx.accounts.bond.to_account_info(),
79            memo_program: memo_info.as_ref(),
80        },
81        amount,
82        ctx.accounts.cash_mint.decimals,
83        &format!("bond_servicing: coupon {e}"),
84        seeds,
85    )?;
86
87    let holder = &mut ctx.accounts.holder;
88    holder.paid |= 1u64 << e;
89    emit!(CouponPaid { bond: ctx.accounts.bond.key(), event: e, owner: holder.owner, position, amount });
90    Ok(())
91}