use crate::account::*; use crate::error::*; use anchor_lang::prelude::*; use anchor_spl::token::{self, Mint, Token, TokenAccount}; use spl_governance::state::token_owner_record; #[derive(Accounts)] pub struct WithdrawOrClawback<'info> { pub registrar: Box>, #[account(mut, has_one = registrar)] pub voter: AccountLoader<'info, Voter>, // token_owner_record is validated in the instruction: // - owned by registrar.governance_program_id // - for the registrar.realm // - for the registrar.realm_governing_token_mint // - governing_token_owner is voter_authority pub token_owner_record: UncheckedAccount<'info>, // The address is verified in the instructions. // For withdraw: must be voter_authority // For clawback: must be registrar.clawback_authority pub authority: Signer<'info>, #[account( mut, associated_token::authority = registrar, associated_token::mint = withdraw_mint, )] pub vault: Box>, pub withdraw_mint: Box>, #[account(mut)] pub destination: Box>, pub token_program: Program<'info, Token>, } impl<'info> WithdrawOrClawback<'info> { pub fn transfer_ctx(&self) -> CpiContext<'_, '_, '_, 'info, token::Transfer<'info>> { let program = self.token_program.to_account_info(); let accounts = token::Transfer { from: self.vault.to_account_info(), to: self.destination.to_account_info(), authority: self.registrar.to_account_info(), }; CpiContext::new(program, accounts) } } /// Withdraws tokens from a deposit entry, if they are unlocked according /// to a vesting schedule. /// /// `amount` is in units of the native currency being withdrawn. pub fn withdraw(ctx: Context, deposit_id: u8, amount: u64) -> Result<()> { msg!("--------withdraw--------"); // Load the accounts. let registrar = &ctx.accounts.registrar; let voter = &mut ctx.accounts.voter.load_mut()?; require!(voter.deposits.len() > deposit_id.into(), InvalidDepositId); require!( ctx.accounts.authority.key() == voter.voter_authority, InvalidAuthority ); // Governance may forbid withdraws, for example when engaged in a vote. let token_owner_record_data = token_owner_record::get_token_owner_record_data_for_realm_and_governing_mint( ®istrar.governance_program_id, &ctx.accounts.token_owner_record.to_account_info(), ®istrar.realm, ®istrar.realm_governing_token_mint, )?; let token_owner = voter.voter_authority; require!( token_owner_record_data.governing_token_owner == token_owner, InvalidTokenOwnerRecord ); token_owner_record_data.assert_can_withdraw_governing_tokens()?; // Must not withdraw in the same slot as depositing, to prevent people // depositing, having the vote weight updated, withdrawing and then // voting. require!( voter.last_deposit_slot < Clock::get()?.slot, ErrorCode::InvalidToDepositAndWithdrawInOneSlot ); // Get the deposit being withdrawn from. let curr_ts = registrar.clock_unix_timestamp(); let deposit_entry = &mut voter.deposits[deposit_id as usize]; require!(deposit_entry.is_used, InvalidDepositId); require!( deposit_entry.amount_withdrawable(curr_ts) >= amount, InsufficientVestedTokens ); // technically unnecessary require!( deposit_entry.amount_deposited_native >= amount, InsufficientVestedTokens ); // Get the exchange rate for the token being withdrawn. let er_idx = registrar .rates .iter() .position(|r| r.mint == ctx.accounts.withdraw_mint.key()) .ok_or(ErrorCode::ExchangeRateEntryNotFound)?; let _er_entry = registrar.rates[er_idx]; require!( er_idx == deposit_entry.rate_idx as usize, ErrorCode::InvalidMint ); // Update deposit book keeping. deposit_entry.amount_deposited_native -= amount; // Transfer the tokens to withdraw. let registrar_seeds = registrar_seeds!(registrar); token::transfer( ctx.accounts.transfer_ctx().with_signer(&[registrar_seeds]), amount, )?; Ok(()) }