quarry_redeemer/
lib.rs

1//! Program for redeeming IOU tokens for an underlying token.
2#![deny(rustdoc::all)]
3#![allow(rustdoc::missing_doc_code_examples)]
4
5use anchor_lang::prelude::*;
6use anchor_spl::token::{Mint, Token, TokenAccount};
7use vipers::prelude::*;
8
9mod account_validators;
10mod macros;
11mod redeem_cpi;
12mod state;
13
14pub use state::*;
15
16declare_id!("QRDxhMw1P2NEfiw5mYXG79bwfgHTdasY2xNP76XSea9");
17
18#[cfg(not(feature = "no-entrypoint"))]
19solana_security_txt::security_txt! {
20    name: "Quarry Redeemer",
21    project_url: "https://quarry.so",
22    contacts: "email:team@quarry.so",
23    policy: "https://github.com/QuarryProtocol/quarry/blob/master/SECURITY.md",
24
25    source_code: "https://github.com/QuarryProtocol/quarry",
26    auditors: "Quantstamp"
27}
28
29/// Quarry Redeemer program.
30#[program]
31pub mod quarry_redeemer {
32    use super::*;
33
34    /// Creates a new [Redeemer].
35    #[access_control(ctx.accounts.validate())]
36    pub fn create_redeemer(ctx: Context<CreateRedeemer>, _bump: u8) -> Result<()> {
37        let redeemer = &mut ctx.accounts.redeemer;
38        redeemer.iou_mint = ctx.accounts.iou_mint.key();
39        redeemer.redemption_mint = ctx.accounts.redemption_mint.key();
40        redeemer.bump = unwrap_bump!(ctx, "redeemer");
41
42        redeemer.total_tokens_redeemed = 0;
43        Ok(())
44    }
45
46    /// Redeems some of a user's tokens from the redemption vault.
47    #[access_control(ctx.accounts.validate())]
48    pub fn redeem_tokens(ctx: Context<RedeemTokens>, amount: u64) -> Result<()> {
49        invariant!(
50            amount <= ctx.accounts.iou_source.amount,
51            "insufficient iou_source balance"
52        );
53        invariant!(
54            amount <= ctx.accounts.redemption_vault.amount,
55            "insufficient redemption_vault balance"
56        );
57
58        ctx.accounts.burn_iou_tokens(amount)?;
59        ctx.accounts.transfer_redemption_tokens(amount)?;
60
61        let redeemer = &mut ctx.accounts.redeemer;
62        redeemer.total_tokens_redeemed =
63            unwrap_int!(redeemer.total_tokens_redeemed.checked_add(amount));
64
65        let redeemer = &ctx.accounts.redeemer;
66        emit!(RedeemTokensEvent {
67            user: ctx.accounts.source_authority.key(),
68            iou_mint: redeemer.iou_mint,
69            redemption_mint: redeemer.redemption_mint,
70            amount,
71            timestamp: Clock::get()?.unix_timestamp
72        });
73
74        Ok(())
75    }
76
77    /// Redeems all of a user's tokens against the redemption vault.
78    pub fn redeem_all_tokens(ctx: Context<RedeemTokens>) -> Result<()> {
79        let amount = ctx.accounts.iou_source.amount;
80        redeem_tokens(ctx, amount)
81    }
82}
83
84// --------------------------------
85// Accounts
86// --------------------------------
87
88// --------------------------------
89// Instructions
90// --------------------------------
91
92#[derive(Accounts)]
93pub struct CreateRedeemer<'info> {
94    /// Redeemer PDA.
95    #[account(
96        init,
97        seeds = [
98            b"Redeemer".as_ref(),
99            iou_mint.to_account_info().key.as_ref(),
100            redemption_mint.to_account_info().key.as_ref()
101        ],
102        bump,
103        payer = payer,
104        space = 8 + Redeemer::LEN
105    )]
106    pub redeemer: Account<'info, Redeemer>,
107    /// [Mint] of the IOU token.
108    pub iou_mint: Account<'info, Mint>,
109    /// [Mint] of the redemption token.
110    pub redemption_mint: Account<'info, Mint>,
111    /// Payer.
112    #[account(mut)]
113    pub payer: Signer<'info>,
114    /// [System] program.
115    pub system_program: Program<'info, System>,
116}
117
118#[derive(Accounts)]
119pub struct RedeemTokens<'info> {
120    /// Redeemer PDA.
121    #[account(mut)]
122    pub redeemer: Account<'info, Redeemer>,
123
124    /// Authority of the source of the redeemed tokens.
125    pub source_authority: Signer<'info>,
126    /// [Mint] of the IOU token.
127    #[account(mut)]
128    pub iou_mint: Account<'info, Mint>,
129    /// Source of the IOU tokens.
130    #[account(mut)]
131    pub iou_source: Account<'info, TokenAccount>,
132
133    /// [TokenAccount] holding the [Redeemer]'s redemption tokens.
134    #[account(mut)]
135    pub redemption_vault: Account<'info, TokenAccount>,
136    /// Destination of the IOU tokens.
137    #[account(mut, constraint = redemption_destination.key() != redemption_vault.key())]
138    pub redemption_destination: Account<'info, TokenAccount>,
139
140    /// The spl_token program corresponding to [Token].
141    pub token_program: Program<'info, Token>,
142}
143
144// --------------------------------
145// Events
146// --------------------------------
147
148#[event]
149/// Emitted when tokens are redeemed.
150pub struct RedeemTokensEvent {
151    /// User which redeemed.
152    #[index]
153    pub user: Pubkey,
154    /// IOU
155    pub iou_mint: Pubkey,
156    /// Redemption mint
157    pub redemption_mint: Pubkey,
158    /// Amount of tokens
159    pub amount: u64,
160    /// When the tokens were redeemed.
161    pub timestamp: i64,
162}
163
164/// Errors
165#[error_code]
166pub enum ErrorCode {
167    #[msg("Unauthorized.")]
168    Unauthorized,
169}