#![deny(rustdoc::all)]
#![allow(rustdoc::missing_doc_code_examples)]
#![allow(deprecated)]
use anchor_lang::prelude::*;
use anchor_lang::{accounts::cpi_state::CpiState, solana_program::pubkey::PUBKEY_BYTES};
use anchor_spl::token::{self, Mint, Token, TokenAccount};
use mint_proxy::mint_proxy::MintProxy;
use mint_proxy::MinterInfo;
use vipers::prelude::*;
mod account_validators;
mod macros;
mod mut_token_pair;
declare_id!("RDM23yr8pr1kEAmhnFpaabPny6C9UVcEcok3Py5v86X");
#[program]
pub mod redeemer {
use super::*;
#[access_control(ctx.accounts.validate())]
pub fn create_redeemer(ctx: Context<CreateRedeemer>, _bump: u8) -> Result<()> {
let tokens = &ctx.accounts.tokens;
let redeemer = &mut ctx.accounts.redeemer;
redeemer.bump = *unwrap_int!(ctx.bumps.get("redeemer"));
redeemer.iou_mint = tokens.iou_mint.key();
redeemer.redemption_mint = tokens.redemption_mint.key();
redeemer.redemption_vault = tokens.redemption_vault.key();
Ok(())
}
#[access_control(ctx.accounts.validate())]
pub fn redeem_tokens(ctx: Context<RedeemTokens>, amount: u64) -> Result<()> {
ctx.accounts.tokens.burn_iou_tokens(
ctx.accounts.iou_source.to_account_info(),
ctx.accounts.source_authority.to_account_info(),
amount,
)?;
let cpi_accounts = token::Transfer {
from: ctx.accounts.tokens.redemption_vault.to_account_info(),
to: ctx.accounts.redemption_destination.to_account_info(),
authority: ctx.accounts.redeemer.to_account_info(),
};
let seeds = gen_redeemer_signer_seeds!(ctx.accounts.redeemer);
let signer_seeds = &[&seeds[..]];
let cpi_ctx = CpiContext::new_with_signer(
ctx.accounts.tokens.token_program.to_account_info(),
cpi_accounts,
signer_seeds,
);
token::transfer(cpi_ctx, amount)?;
let redeemer = &ctx.accounts.redeemer;
emit!(RedeemTokensEvent {
user: *ctx.accounts.source_authority.key,
iou_mint: redeemer.iou_mint,
destination_mint: redeemer.redemption_mint,
amount,
});
Ok(())
}
pub fn redeem_tokens_from_mint_proxy(
ctx: Context<RedeemTokensFromMintProxy>,
amount: u64,
) -> Result<()> {
ctx.accounts.validate()?;
ctx.accounts.redeem_ctx.tokens.burn_iou_tokens(
ctx.accounts.redeem_ctx.iou_source.to_account_info(),
ctx.accounts.redeem_ctx.source_authority.to_account_info(),
amount,
)?;
let redeemer = &ctx.accounts.redeem_ctx.redeemer;
let seeds = gen_redeemer_signer_seeds!(redeemer);
let signer_seeds = &[&seeds[..]];
let cpi_accounts = mint_proxy::cpi::accounts::PerformMint {
proxy_mint_authority: ctx.accounts.proxy_mint_authority.to_account_info(),
minter: ctx.accounts.redeem_ctx.redeemer.to_account_info(),
token_mint: ctx
.accounts
.redeem_ctx
.tokens
.redemption_mint
.to_account_info(),
destination: ctx
.accounts
.redeem_ctx
.redemption_destination
.to_account_info(),
minter_info: ctx.accounts.minter_info.to_account_info(),
token_program: ctx
.accounts
.redeem_ctx
.tokens
.token_program
.to_account_info(),
};
let cpi_program = ctx.accounts.mint_proxy_program.to_account_info();
mint_proxy::invoke_perform_mint(
CpiContext::new_with_signer(cpi_program, cpi_accounts, signer_seeds),
ctx.accounts.mint_proxy_state.to_account_info(),
amount,
)?;
emit!(RedeemTokensEvent {
user: *ctx.accounts.redeem_ctx.source_authority.key,
iou_mint: redeemer.iou_mint,
destination_mint: redeemer.redemption_mint,
amount,
});
Ok(())
}
pub fn redeem_all_tokens_from_mint_proxy(
ctx: Context<RedeemTokensFromMintProxy>,
) -> Result<()> {
let amount = ctx.accounts.redeem_ctx.iou_source.amount;
redeem_tokens_from_mint_proxy(ctx, amount)
}
}
#[account]
#[derive(Default)]
pub struct Redeemer {
pub bump: u8,
pub iou_mint: Pubkey,
pub redemption_mint: Pubkey,
pub redemption_vault: Pubkey,
}
impl Redeemer {
pub const LEN: usize = 1 + PUBKEY_BYTES * 3;
}
#[derive(Accounts)]
pub struct MutTokenPair<'info> {
#[account(mut)]
pub iou_mint: Account<'info, Mint>,
#[account(mut)]
pub redemption_mint: Account<'info, Mint>,
#[account(mut)]
pub redemption_vault: Account<'info, TokenAccount>,
pub token_program: Program<'info, Token>,
}
#[derive(Accounts)]
pub struct ReadonlyTokenPair<'info> {
pub iou_mint: Account<'info, Mint>,
pub redemption_mint: Account<'info, Mint>,
pub redemption_vault: Account<'info, TokenAccount>,
}
#[derive(Accounts)]
pub struct CreateRedeemer<'info> {
#[account(
init,
seeds = [
b"Redeemer".as_ref(),
tokens.iou_mint.to_account_info().key.as_ref(),
tokens.redemption_mint.to_account_info().key.as_ref()
],
bump,
space = 8 + Redeemer::LEN,
payer = payer
)]
pub redeemer: Account<'info, Redeemer>,
pub tokens: ReadonlyTokenPair<'info>,
#[account(mut)]
pub payer: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct RedeemTokens<'info> {
pub redeemer: Account<'info, Redeemer>,
pub tokens: MutTokenPair<'info>,
pub source_authority: Signer<'info>,
#[account(mut)]
pub iou_source: Box<Account<'info, TokenAccount>>,
#[account(mut)]
pub redemption_destination: Box<Account<'info, TokenAccount>>,
}
#[derive(Accounts)]
pub struct RedeemTokensFromMintProxy<'info> {
pub redeem_ctx: RedeemTokens<'info>,
#[allow(deprecated)]
pub mint_proxy_state: CpiState<'info, MintProxy>,
pub proxy_mint_authority: UncheckedAccount<'info>,
pub mint_proxy_program: Program<'info, mint_proxy::program::MintProxy>,
#[account(mut)]
pub minter_info: Box<Account<'info, MinterInfo>>,
}
#[event]
pub struct RedeemTokensEvent {
#[index]
pub user: Pubkey,
pub iou_mint: Pubkey,
pub destination_mint: Pubkey,
pub amount: u64,
}
#[error_code]
pub enum ErrorCode {
#[msg("Unauthorized.")]
Unauthorized,
#[msg("Redemption token and IOU token decimals must match")]
DecimalsMismatch,
}