1#![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#[program]
31pub mod quarry_redeemer {
32 use super::*;
33
34 #[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 #[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 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#[derive(Accounts)]
93pub struct CreateRedeemer<'info> {
94 #[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 pub iou_mint: Account<'info, Mint>,
109 pub redemption_mint: Account<'info, Mint>,
111 #[account(mut)]
113 pub payer: Signer<'info>,
114 pub system_program: Program<'info, System>,
116}
117
118#[derive(Accounts)]
119pub struct RedeemTokens<'info> {
120 #[account(mut)]
122 pub redeemer: Account<'info, Redeemer>,
123
124 pub source_authority: Signer<'info>,
126 #[account(mut)]
128 pub iou_mint: Account<'info, Mint>,
129 #[account(mut)]
131 pub iou_source: Account<'info, TokenAccount>,
132
133 #[account(mut)]
135 pub redemption_vault: Account<'info, TokenAccount>,
136 #[account(mut, constraint = redemption_destination.key() != redemption_vault.key())]
138 pub redemption_destination: Account<'info, TokenAccount>,
139
140 pub token_program: Program<'info, Token>,
142}
143
144#[event]
149pub struct RedeemTokensEvent {
151 #[index]
153 pub user: Pubkey,
154 pub iou_mint: Pubkey,
156 pub redemption_mint: Pubkey,
158 pub amount: u64,
160 pub timestamp: i64,
162}
163
164#[error_code]
166pub enum ErrorCode {
167 #[msg("Unauthorized.")]
168 Unauthorized,
169}