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
- changing the whole deposit entry to `Cliff` with `ResetLockup`, or
- 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
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
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
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)
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::*;
#[derive(Accounts)]
pub struct InternalTransfer<'info> {
pub struct InternalTransferLocked<'info> {
pub registrar: AccountLoader<'info, Registrar>,
// 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"
/// locked deposit entry to start the unlocking process (reset_lockup could only
/// change the whole deposit entry to "cliff")
pub fn internal_transfer(
ctx: Context<InternalTransfer>,
pub fn internal_transfer_locked(
ctx: Context<InternalTransferLocked>,
source_deposit_entry_index: u8,
target_deposit_entry_index: u8,
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 deposit::*;
pub use grant::*;
pub use internal_transfer::*;
pub use internal_transfer_locked::*;
pub use internal_transfer_unlocked::*;
pub use log_voter_info::*;
pub use reset_lockup::*;
pub use set_time_offset::*;
@ -24,7 +25,8 @@ mod create_registrar;
mod create_voter;
mod deposit;
mod grant;
mod internal_transfer;
mod internal_transfer_locked;
mod internal_transfer_unlocked;
mod log_voter_info;
mod reset_lockup;
mod set_time_offset;

View File

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