From 7b7ce7d8ce0785534bda5b3a7dc0db07bdc315f9 Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Tue, 25 Jan 2022 14:36:45 +0100 Subject: [PATCH] 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. --- README.md | 9 ++- ...ransfer.rs => internal_transfer_locked.rs} | 6 +- .../internal_transfer_unlocked.rs | 59 +++++++++++++++++++ .../src/instructions/mod.rs | 6 +- programs/voter-stake-registry/src/lib.rs | 20 ++++++- 5 files changed, 90 insertions(+), 10 deletions(-) rename programs/voter-stake-registry/src/instructions/{internal_transfer.rs => internal_transfer_locked.rs} (96%) create mode 100644 programs/voter-stake-registry/src/instructions/internal_transfer_unlocked.rs diff --git a/README.md b/README.md index 59db98a..9089728 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/programs/voter-stake-registry/src/instructions/internal_transfer.rs b/programs/voter-stake-registry/src/instructions/internal_transfer_locked.rs similarity index 96% rename from programs/voter-stake-registry/src/instructions/internal_transfer.rs rename to programs/voter-stake-registry/src/instructions/internal_transfer_locked.rs index 65f7eaf..11a2319 100644 --- a/programs/voter-stake-registry/src/instructions/internal_transfer.rs +++ b/programs/voter-stake-registry/src/instructions/internal_transfer_locked.rs @@ -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, +pub fn internal_transfer_locked( + ctx: Context, source_deposit_entry_index: u8, target_deposit_entry_index: u8, amount: u64, diff --git a/programs/voter-stake-registry/src/instructions/internal_transfer_unlocked.rs b/programs/voter-stake-registry/src/instructions/internal_transfer_unlocked.rs new file mode 100644 index 0000000..c9493b3 --- /dev/null +++ b/programs/voter-stake-registry/src/instructions/internal_transfer_unlocked.rs @@ -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, + 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(()) +} diff --git a/programs/voter-stake-registry/src/instructions/mod.rs b/programs/voter-stake-registry/src/instructions/mod.rs index a2db3af..ff21e3d 100644 --- a/programs/voter-stake-registry/src/instructions/mod.rs +++ b/programs/voter-stake-registry/src/instructions/mod.rs @@ -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; diff --git a/programs/voter-stake-registry/src/lib.rs b/programs/voter-stake-registry/src/lib.rs index f600241..bf62798 100644 --- a/programs/voter-stake-registry/src/lib.rs +++ b/programs/voter-stake-registry/src/lib.rs @@ -162,13 +162,27 @@ pub mod voter_stake_registry { instructions::reset_lockup(ctx, deposit_entry_index, kind, periods) } - pub fn internal_transfer( - ctx: Context, + pub fn internal_transfer_locked( + ctx: Context, 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, + 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,