Add internal_transfer_unlocked

Rename internal_transfer -> internal_transfer_locked

The new instruction can move only unlocked funds and is useful to avoid
needing to withdraw funds if they should be re-locked in a different
deposit entry.

Withdrawing can be impossible when a voter is engaged in proposals.
This commit is contained in:
Christian Kamm 2022-01-25 14:36:45 +01:00
parent e7ffe744e9
commit 7b7ce7d8ce
5 changed files with 90 additions and 10 deletions

View File

@ -132,7 +132,7 @@ If you want access to the tokens again, you need to start the unlocking process
by either by either
- changing the whole deposit entry to `Cliff` with `ResetLockup`, or - changing the whole deposit entry to `Cliff` with `ResetLockup`, or
- creating a new `Cliff` deposit entry and transfering some locked tokens from - creating a new `Cliff` deposit entry and transfering some locked tokens from
your `Constant` deposit entry over with `InternalTransfer`. your `Constant` deposit entry over with `InternalTransferLocked`.
In both cases you'll need to wait for the cliff to be reached before being able In both cases you'll need to wait for the cliff to be reached before being able
to access the tokens again. to access the tokens again.
@ -176,11 +176,16 @@ to access the tokens again.
Re-lock tokens where the lockup has expired, or increase the duration of the lockup or Re-lock tokens where the lockup has expired, or increase the duration of the lockup or
change the lockup kind. change the lockup kind.
- [`InternalTransfer`](programs/voter-stake-registry/src/instructions/internal_transfer.rs) - [`InternalTransferLocked`](programs/voter-stake-registry/src/instructions/internal_transfer_locked.rs)
Transfer locked tokens from one deposit entry to another. Useful for splitting off a Transfer locked tokens from one deposit entry to another. Useful for splitting off a
chunk of a "constant" lockup deposit entry that you want to start the unlock process on. chunk of a "constant" lockup deposit entry that you want to start the unlock process on.
- [`InternalTransferUnocked`](programs/voter-stake-registry/src/instructions/internal_transfer_unlocked.rs)
Transfer unlocked tokens from one deposit entry to another. Useful for splitting off a
chunk to be locked again in a different deposit entry without having to withdraw and redeposit.
- [`UpdateVoterWeightRecord`](programs/voter-stake-registry/src/instructions/update_voter_weight_record.rs) - [`UpdateVoterWeightRecord`](programs/voter-stake-registry/src/instructions/update_voter_weight_record.rs)
Write the current voter weight to the account that spl-governance can read to Write the current voter weight to the account that spl-governance can read to

View File

@ -3,7 +3,7 @@ use crate::state::*;
use anchor_lang::prelude::*; use anchor_lang::prelude::*;
#[derive(Accounts)] #[derive(Accounts)]
pub struct InternalTransfer<'info> { pub struct InternalTransferLocked<'info> {
pub registrar: AccountLoader<'info, Registrar>, pub registrar: AccountLoader<'info, Registrar>,
// checking the PDA address it just an extra precaution, // checking the PDA address it just an extra precaution,
@ -31,8 +31,8 @@ pub struct InternalTransfer<'info> {
/// - transfering a small part of a big "constant" lockup deposit entry into a "cliff" /// - transfering a small part of a big "constant" lockup deposit entry into a "cliff"
/// locked deposit entry to start the unlocking process (reset_lockup could only /// locked deposit entry to start the unlocking process (reset_lockup could only
/// change the whole deposit entry to "cliff") /// change the whole deposit entry to "cliff")
pub fn internal_transfer( pub fn internal_transfer_locked(
ctx: Context<InternalTransfer>, ctx: Context<InternalTransferLocked>,
source_deposit_entry_index: u8, source_deposit_entry_index: u8,
target_deposit_entry_index: u8, target_deposit_entry_index: u8,
amount: u64, amount: u64,

View File

@ -0,0 +1,59 @@
use crate::error::*;
use crate::state::*;
use anchor_lang::prelude::*;
#[derive(Accounts)]
pub struct InternalTransferUnlocked<'info> {
pub registrar: AccountLoader<'info, Registrar>,
// checking the PDA address it just an extra precaution,
// the other constraints must be exhaustive
#[account(
mut,
seeds = [registrar.key().as_ref(), b"voter".as_ref(), voter_authority.key().as_ref()],
bump = voter.load()?.voter_bump,
has_one = voter_authority,
has_one = registrar)]
pub voter: AccountLoader<'info, Voter>,
pub voter_authority: Signer<'info>,
}
/// Transfers unlocked tokens from the source deposit entry to the target deposit entry.
///
/// Note that this never transfers locked tokens, only tokens that are unlocked.
///
/// The primary usecase is moving some deposited funds from one deposit entry to
/// another because the user wants to lock them, without having to withdraw and re-deposit
/// them.
pub fn internal_transfer_unlocked(
ctx: Context<InternalTransferUnlocked>,
source_deposit_entry_index: u8,
target_deposit_entry_index: u8,
amount: u64,
) -> Result<()> {
let registrar = &ctx.accounts.registrar.load()?;
let voter = &mut ctx.accounts.voter.load_mut()?;
let curr_ts = registrar.clock_unix_timestamp();
let source = voter.active_deposit_mut(source_deposit_entry_index)?;
let source_mint_idx = source.voting_mint_config_idx;
// Reduce source amounts
require!(
amount <= source.amount_withdrawable(curr_ts),
InsufficientVestedTokens
);
source.amount_deposited_native = source.amount_deposited_native.checked_sub(amount).unwrap();
// Check target compatibility
let target = voter.active_deposit_mut(target_deposit_entry_index)?;
require!(
target.voting_mint_config_idx == source_mint_idx,
InvalidMint
);
// Add target amounts
target.amount_deposited_native = target.amount_deposited_native.checked_add(amount).unwrap();
Ok(())
}

View File

@ -7,7 +7,8 @@ pub use create_registrar::*;
pub use create_voter::*; pub use create_voter::*;
pub use deposit::*; pub use deposit::*;
pub use grant::*; pub use grant::*;
pub use internal_transfer::*; pub use internal_transfer_locked::*;
pub use internal_transfer_unlocked::*;
pub use log_voter_info::*; pub use log_voter_info::*;
pub use reset_lockup::*; pub use reset_lockup::*;
pub use set_time_offset::*; pub use set_time_offset::*;
@ -24,7 +25,8 @@ mod create_registrar;
mod create_voter; mod create_voter;
mod deposit; mod deposit;
mod grant; mod grant;
mod internal_transfer; mod internal_transfer_locked;
mod internal_transfer_unlocked;
mod log_voter_info; mod log_voter_info;
mod reset_lockup; mod reset_lockup;
mod set_time_offset; mod set_time_offset;

View File

@ -162,13 +162,27 @@ pub mod voter_stake_registry {
instructions::reset_lockup(ctx, deposit_entry_index, kind, periods) instructions::reset_lockup(ctx, deposit_entry_index, kind, periods)
} }
pub fn internal_transfer( pub fn internal_transfer_locked(
ctx: Context<InternalTransfer>, ctx: Context<InternalTransferLocked>,
source_deposit_entry_index: u8, source_deposit_entry_index: u8,
target_deposit_entry_index: u8, target_deposit_entry_index: u8,
amount: u64, amount: u64,
) -> Result<()> { ) -> Result<()> {
instructions::internal_transfer( instructions::internal_transfer_locked(
ctx,
source_deposit_entry_index,
target_deposit_entry_index,
amount,
)
}
pub fn internal_transfer_unlocked(
ctx: Context<InternalTransferUnlocked>,
source_deposit_entry_index: u8,
target_deposit_entry_index: u8,
amount: u64,
) -> Result<()> {
instructions::internal_transfer_unlocked(
ctx, ctx,
source_deposit_entry_index, source_deposit_entry_index,
target_deposit_entry_index, target_deposit_entry_index,