crate_redeem_in_kind/
lib.rs1#![deny(rustdoc::all)]
3#![allow(rustdoc::missing_doc_code_examples)]
4
5mod account_validators;
6
7pub mod events;
8
9use anchor_lang::prelude::*;
10use anchor_lang::solana_program;
11use anchor_lang::solana_program::account_info::next_account_infos;
12use anchor_spl::token::{self, Mint, Token, TokenAccount};
13use num_traits::cast::ToPrimitive;
14use static_pubkey::static_pubkey;
15use vipers::validate::Validate;
16use vipers::{invariant, unwrap_int};
17
18use events::*;
19
20declare_id!("1NKyU3qShZC3oJgvCCftAHDi5TFxcJwfyUz2FeZsiwE");
21
22pub static WITHDRAW_AUTHORITY_ADDRESS: Pubkey =
24 static_pubkey!("2amCDqmgpQ2qkryLArCcYeX8DzyNqvjuy7yKq6hsonqF");
25
26pub const WITHDRAW_AUTHORITY_ADDRESS_BUMP: u8 = 255;
28
29pub static WITHDRAW_AUTHORITY_SIGNER_SEEDS: &[&[&[u8]]] =
31 &[&[b"CrateRedeemInKind", &[WITHDRAW_AUTHORITY_ADDRESS_BUMP]]];
32
33#[program]
35pub mod crate_redeem_in_kind {
36 use std::collections::BTreeMap;
37
38 use super::*;
39
40 #[access_control(ctx.accounts.validate())]
44 pub fn redeem<'info>(
45 ctx: Context<'_, '_, '_, 'info, Redeem<'info>>,
46 amount: u64,
47 ) -> Result<()> {
48 let burn = token::Burn {
49 mint: ctx.accounts.crate_mint.to_account_info(),
50 from: ctx.accounts.crate_source.to_account_info(),
51 authority: ctx.accounts.owner.to_account_info(),
52 };
53
54 token::burn(
55 CpiContext::new(ctx.accounts.token_program.to_account_info(), burn),
56 amount,
57 )?;
58
59 let num_remaining_accounts = ctx.remaining_accounts.len();
61 if num_remaining_accounts == 0 {
62 return Ok(());
63 }
64 invariant!(
65 num_remaining_accounts % 4 == 0,
66 "must have even number of tokens"
67 );
68 let num_tokens = unwrap_int!(num_remaining_accounts.checked_div(4));
69 let remaining_accounts_iter = &mut ctx.remaining_accounts.iter();
72
73 for _i in 0..num_tokens {
74 let bumps = &mut BTreeMap::new();
77 let asset: RedeemAsset = Accounts::try_accounts(
78 &crate::ID,
79 &mut next_account_infos(remaining_accounts_iter, 4)?,
80 &[],
81 bumps,
82 )?;
83
84 let share: u64 = unwrap_int!((asset.crate_underlying.amount as u128)
85 .checked_mul(amount.into())
86 .and_then(|num| num.checked_div(ctx.accounts.crate_mint.supply.into()))
87 .and_then(|num| num.to_u64()));
88
89 crate_token::cpi::withdraw(
90 CpiContext::new_with_signer(
91 ctx.accounts.crate_token_program.to_account_info(),
92 crate_token::cpi::accounts::Withdraw {
93 crate_token: ctx.accounts.crate_token.to_account_info(),
94 crate_underlying: asset.crate_underlying.to_account_info(),
95 withdraw_authority: ctx.accounts.withdraw_authority.to_account_info(),
96 withdraw_destination: asset.withdraw_destination.to_account_info(),
97 author_fee_destination: asset.author_fee_destination.to_account_info(),
98 protocol_fee_destination: asset.protocol_fee_destination.to_account_info(),
99 token_program: ctx.accounts.token_program.to_account_info(),
100 },
101 WITHDRAW_AUTHORITY_SIGNER_SEEDS,
102 ),
103 share,
104 )?;
105 }
106
107 emit!(RedeemEvent {
108 crate_key: ctx.accounts.crate_token.key(),
109 source: ctx.accounts.crate_source.key(),
110 amount
111 });
112
113 Ok(())
114 }
115}
116
117#[derive(Accounts)]
123pub struct Redeem<'info> {
124 pub withdraw_authority: UncheckedAccount<'info>,
127
128 #[account(has_one = withdraw_authority)]
130 pub crate_token: Account<'info, crate_token::CrateToken>,
131
132 #[account(mut)]
134 pub crate_mint: Account<'info, Mint>,
135
136 #[account(mut)]
138 pub crate_source: Account<'info, TokenAccount>,
139
140 pub owner: Signer<'info>,
142
143 pub token_program: Program<'info, Token>,
145
146 pub crate_token_program: Program<'info, crate_token::program::CrateToken>,
148}
149
150#[derive(Accounts)]
152pub struct RedeemAsset<'info> {
153 #[account(mut)]
155 pub crate_underlying: Account<'info, TokenAccount>,
156
157 #[account(mut)]
159 pub withdraw_destination: Account<'info, TokenAccount>,
160
161 #[account(mut)]
163 pub author_fee_destination: Account<'info, TokenAccount>,
164
165 #[account(mut)]
167 pub protocol_fee_destination: Account<'info, TokenAccount>,
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173 #[test]
174 fn test_withdraw_authority_address() {
175 let (key, bump) = Pubkey::find_program_address(&[b"CrateRedeemInKind"], &crate::ID);
176 assert_eq!(key, WITHDRAW_AUTHORITY_ADDRESS);
177 assert_eq!(bump, WITHDRAW_AUTHORITY_ADDRESS_BUMP);
178 }
179}