Renames for clarity

- withdrawable -> unlocked

  "withdrawable" was a bad name, since these funds - while unlocked -
  are not necessarily withdrawable if the voter is currently engaged in
  a vote.

- only_deposit -> only_unlocked

  Locked funds are technically also deposited. Make it clearer that this
  is talking about the unlocked parts of the funds on the account.
This commit is contained in:
Christian Kamm 2022-01-25 14:49:23 +01:00
parent 7b7ce7d8ce
commit 5b3d07ffb3
17 changed files with 113 additions and 77 deletions

View File

@ -28,7 +28,7 @@ pub enum ErrorCode {
UnusedDepositEntryIndex,
// 6008 / 0x1778
#[msg("")]
InsufficientVestedTokens,
InsufficientUnlockedTokens,
// 6009 / 0x1779
#[msg("")]
UnableToConvert,

View File

@ -4,7 +4,7 @@ use anchor_lang::prelude::*;
#[derive(Debug)]
pub struct VoterInfo {
pub voting_power: u64,
pub voting_power_deposit_only: u64,
pub voting_power_unlocked_only: u64,
}
#[derive(AnchorSerialize, AnchorDeserialize, Debug)]
@ -30,12 +30,12 @@ pub struct LockingInfo {
pub struct DepositEntryInfo {
pub deposit_entry_index: u8,
pub voting_mint_config_index: u8,
/// Amount that can be withdrawn directly
pub withdrawable: u64,
/// Amount that is unlocked
pub unlocked: u64,
/// Voting power implied by this deposit entry
pub voting_power: u64,
/// Voting power that is not based on lockup
pub voting_power_deposit_only: u64,
pub voting_power_unlocked_only: u64,
/// Information about locking, if any
pub locking: Option<LockingInfo>,
}

View File

@ -23,7 +23,7 @@ pub struct ConfigureVotingMint<'info> {
///
/// * `idx`: index of the rate to be set
/// * `digit_shift`: how many digits to shift the native token amount, see below
/// * `deposit_scaled_factor`: vote weight factor for deposits, in 1/1e9 units
/// * `unlocked_scaled_factor`: vote weight factor for unlocked deposits, in 1/1e9 units
/// * `lockup_scaled_factor`: max extra weight for lockups, in 1/1e9 units
/// * `lockup_saturation_secs`: lockup duration at which the full vote weight
/// bonus is given to locked up deposits
@ -35,7 +35,7 @@ pub struct ConfigureVotingMint<'info> {
/// ```
/// vote_weight =
/// amount * 10^(digit_shift)
/// * (deposit_scaled_factor/1e9
/// * (unlocked_scaled_factor/1e9
/// + lockup_duration_factor * lockup_scaled_factor/1e9)
/// ```
/// where lockup_duration_factor is a value between 0 and 1, depending on how long
@ -46,7 +46,7 @@ pub struct ConfigureVotingMint<'info> {
/// u64 limit! There is a check based on the supply of all configured mints, but
/// do your own checking too.
///
/// If you use a single mint, prefer digit_shift=0 and deposit_scaled_factor +
/// If you use a single mint, prefer digit_shift=0 and unlocked_scaled_factor +
/// lockup_scaled_factor <= 1e9. That way you won't have issues with overflow no
/// matter the size of the mint's supply.
///
@ -56,8 +56,8 @@ pub struct ConfigureVotingMint<'info> {
///
/// Example: If you have token A with 6 decimals and token B with 9 decimals, you
/// could set up:
/// * A with digit_shift=0, deposit_scaled_factor=2e9, lockup_scaled_factor=0
/// * B with digit_shift=-3, deposit_scaled_factor=1e9, lockup_scaled_factor=1e9
/// * A with digit_shift=0, unlocked_scaled_factor=2e9, lockup_scaled_factor=0
/// * B with digit_shift=-3, unlocked_scaled_factor=1e9, lockup_scaled_factor=1e9
///
/// That would make 1.0 decimaled tokens of A as valuable as 2.0 decimaled tokens
/// of B when unlocked. B tokens could be locked up to double their vote weight. As
@ -65,15 +65,15 @@ pub struct ConfigureVotingMint<'info> {
///
/// Note that in this example, you need 1000 native B tokens before receiving 1
/// unit of vote weight. If the supplies were significantly lower, you could use
/// * A with digit_shift=3, deposit_scaled_factor=2e9, lockup_scaled_factor=0
/// * B with digit_shift=0, deposit_scaled_factor=1e9, lockup_scaled_factor=1e9
/// * A with digit_shift=3, unlocked_scaled_factor=2e9, lockup_scaled_factor=0
/// * B with digit_shift=0, unlocked_scaled_factor=1e9, lockup_scaled_factor=1e9
/// to not lose precision on B tokens.
///
pub fn configure_voting_mint(
ctx: Context<ConfigureVotingMint>,
idx: u16,
digit_shift: i8,
deposit_scaled_factor: u64,
unlocked_scaled_factor: u64,
lockup_scaled_factor: u64,
lockup_saturation_secs: u64,
grant_authority: Option<Pubkey>,
@ -100,7 +100,7 @@ pub fn configure_voting_mint(
registrar.voting_mints[idx] = VotingMintConfig {
mint,
digit_shift,
deposit_scaled_factor,
unlocked_scaled_factor,
lockup_scaled_factor,
lockup_saturation_secs,
grant_authority: grant_authority.unwrap_or_default(),

View File

@ -40,8 +40,8 @@ pub fn internal_transfer_unlocked(
// Reduce source amounts
require!(
amount <= source.amount_withdrawable(curr_ts),
InsufficientVestedTokens
amount <= source.amount_unlocked(curr_ts),
InsufficientUnlockedTokens
);
source.amount_deposited_native = source.amount_deposited_native.checked_sub(amount).unwrap();

View File

@ -32,7 +32,7 @@ pub fn log_voter_info(ctx: Context<LogVoterInfo>, deposit_entry_begin: u8) -> Re
msg!("voter");
emit!(VoterInfo {
voting_power: voter.weight(&registrar)?,
voting_power_deposit_only: voter.weight_from_deposit(&registrar)?,
voting_power_unlocked_only: voter.weight_from_unlocked(&registrar)?,
});
msg!("deposit_entries");
@ -69,10 +69,10 @@ pub fn log_voter_info(ctx: Context<LogVoterInfo>, deposit_entry_begin: u8) -> Re
emit!(DepositEntryInfo {
deposit_entry_index: deposit_index as u8,
voting_mint_config_index: deposit.voting_mint_config_idx,
withdrawable: deposit.amount_withdrawable(curr_ts),
unlocked: deposit.amount_unlocked(curr_ts),
voting_power: deposit.voting_power(voting_mint_config, curr_ts)?,
voting_power_deposit_only: voting_mint_config
.deposit_vote_weight(deposit.amount_deposited_native)?,
voting_power_unlocked_only: voting_mint_config
.unlocked_vote_weight(deposit.amount_deposited_native)?,
locking: locking_info,
});
}

View File

@ -98,8 +98,8 @@ pub fn withdraw(ctx: Context<Withdraw>, deposit_entry_index: u8, amount: u64) ->
let curr_ts = registrar.clock_unix_timestamp();
let deposit_entry = voter.active_deposit_mut(deposit_entry_index)?;
require!(
deposit_entry.amount_withdrawable(curr_ts) >= amount,
InsufficientVestedTokens
deposit_entry.amount_unlocked(curr_ts) >= amount,
InsufficientUnlockedTokens
);
// Get the exchange rate for the token being withdrawn.

View File

@ -70,7 +70,7 @@ pub mod voter_stake_registry {
ctx: Context<ConfigureVotingMint>,
idx: u16,
digit_shift: i8,
deposit_scaled_factor: u64,
unlocked_scaled_factor: u64,
lockup_scaled_factor: u64,
lockup_saturation_secs: u64,
grant_authority: Option<Pubkey>,
@ -79,7 +79,7 @@ pub mod voter_stake_registry {
ctx,
idx,
digit_shift,
deposit_scaled_factor,
unlocked_scaled_factor,
lockup_scaled_factor,
lockup_saturation_secs,
grant_authority,

View File

@ -51,12 +51,12 @@ impl DepositEntry {
/// For each cliff-locked token, the vote weight is:
///
/// ```
/// voting_power = deposit_vote_weight
/// voting_power = unlocked_vote_weight
/// + lockup_duration_factor * max_lockup_vote_weight
/// ```
///
/// with
/// deposit_vote_weight and max_lockup_vote_weight from the
/// unlocked_vote_weight and max_lockup_vote_weight from the
/// VotingMintConfig
/// lockup_duration_factor = lockup_time_remaining / max_lockup_time
///
@ -89,8 +89,8 @@ impl DepositEntry {
/// voting_power_linear_vesting() below.
///
pub fn voting_power(&self, voting_mint_config: &VotingMintConfig, curr_ts: i64) -> Result<u64> {
let deposit_vote_weight =
voting_mint_config.deposit_vote_weight(self.amount_deposited_native)?;
let unlocked_vote_weight =
voting_mint_config.unlocked_vote_weight(self.amount_deposited_native)?;
let max_locked_vote_weight =
voting_mint_config.max_lockup_vote_weight(self.amount_initially_locked_native)?;
let locked_vote_weight = self.voting_power_locked(
@ -102,7 +102,7 @@ impl DepositEntry {
locked_vote_weight <= max_locked_vote_weight,
InternalErrorBadLockupVoteWeight
);
deposit_vote_weight
unlocked_vote_weight
.checked_add(locked_vote_weight)
.ok_or(Error::ErrorCode(ErrorCode::VoterWeightOverflow))
}
@ -307,10 +307,10 @@ impl DepositEntry {
.unwrap()
}
/// Returns the amount that may be withdrawn given current vesting
/// Returns native tokens that are unlocked given current vesting
/// and previous withdraws.
#[inline(always)]
pub fn amount_withdrawable(&self, curr_ts: i64) -> u64 {
pub fn amount_unlocked(&self, curr_ts: i64) -> u64 {
self.amount_deposited_native
.checked_sub(self.amount_locked(curr_ts))
.unwrap()
@ -357,7 +357,6 @@ impl DepositEntry {
mod tests {
use super::*;
use crate::LockupKind::Daily;
use std::str::FromStr;
#[test]
pub fn resolve_vesting() -> Result<()> {
@ -378,10 +377,10 @@ mod tests {
let mut time = 1001;
assert_eq!(deposit.vested(time).unwrap(), 0);
assert_eq!(deposit.amount_withdrawable(time), 5);
assert_eq!(deposit.amount_unlocked(time), 5);
deposit.resolve_vesting(time).unwrap(); // no effect
assert_eq!(deposit.vested(time).unwrap(), 0);
assert_eq!(deposit.amount_withdrawable(time), 5);
assert_eq!(deposit.amount_unlocked(time), 5);
assert_eq!(
deposit.lockup.seconds_left(time),
initial_deposit.lockup.seconds_left(time)
@ -396,7 +395,7 @@ mod tests {
assert_eq!(deposit.lockup.periods_total().unwrap(), 3);
deposit.resolve_vesting(time).unwrap();
assert_eq!(deposit.vested(time).unwrap(), 0);
assert_eq!(deposit.amount_withdrawable(time), 15);
assert_eq!(deposit.amount_unlocked(time), 15);
assert_eq!(
deposit.lockup.seconds_left(time),
initial_deposit.lockup.seconds_left(time)
@ -411,7 +410,7 @@ mod tests {
assert_eq!(deposit.lockup.periods_total().unwrap(), 2);
deposit.resolve_vesting(time).unwrap();
assert_eq!(deposit.vested(time).unwrap(), 0);
assert_eq!(deposit.amount_withdrawable(time), 35);
assert_eq!(deposit.amount_unlocked(time), 35);
assert_eq!(
deposit.lockup.seconds_left(time),
initial_deposit.lockup.seconds_left(time)
@ -447,22 +446,22 @@ mod tests {
let voting_mint_config = VotingMintConfig {
mint: Pubkey::default(),
grant_authority: Pubkey::default(),
deposit_scaled_factor: 1_000_000_000, // 1x
lockup_scaled_factor: 1_000_000_000, // 1x
unlocked_scaled_factor: 1_000_000_000, // 1x
lockup_scaled_factor: 1_000_000_000, // 1x
lockup_saturation_secs: saturation as u64,
digit_shift: 0,
padding: [0; 31],
};
let deposit_vote_weight =
voting_mint_config.deposit_vote_weight(deposit.amount_deposited_native)?;
assert_eq!(deposit_vote_weight, 10_000);
let unlocked_vote_weight =
voting_mint_config.unlocked_vote_weight(deposit.amount_deposited_native)?;
assert_eq!(unlocked_vote_weight, 10_000);
let max_locked_vote_weight =
voting_mint_config.max_lockup_vote_weight(deposit.amount_initially_locked_native)?;
assert_eq!(max_locked_vote_weight, 10_000);
// The timestamp 100_000 is very far before the lockup_start timestamp
let withdrawable = deposit.amount_withdrawable(100_000);
let withdrawable = deposit.amount_unlocked(100_000);
assert_eq!(withdrawable, 0);
let voting_power = deposit.voting_power(&voting_mint_config, 100_000).unwrap();
assert_eq!(voting_power, 20_000);

View File

@ -26,11 +26,7 @@ const_assert!(std::mem::size_of::<Registrar>() == 5 * 32 + 4 * 120 + 8 + 1 + 31)
impl Registrar {
pub fn clock_unix_timestamp(&self) -> i64 {
Clock::get()
.unwrap()
.unix_timestamp
.checked_add(self.time_offset)
.unwrap()
Clock::get().unwrap().unix_timestamp + self.time_offset
}
pub fn voting_mint_config_index(&self, mint: Pubkey) -> Result<usize> {
@ -53,7 +49,7 @@ impl Registrar {
.ok_or(Error::ErrorCode(ErrorCode::VotingMintNotFound))?;
let mint = Account::<Mint>::try_from(mint_account)?;
sum = sum
.checked_add(voting_mint_config.deposit_vote_weight(mint.supply)?)
.checked_add(voting_mint_config.unlocked_vote_weight(mint.supply)?)
.ok_or(Error::ErrorCode(ErrorCode::VoterWeightOverflow))?;
sum = sum
.checked_add(voting_mint_config.max_lockup_vote_weight(mint.supply)?)

View File

@ -31,13 +31,13 @@ impl Voter {
})
}
pub fn weight_from_deposit(&self, registrar: &Registrar) -> Result<u64> {
pub fn weight_from_unlocked(&self, registrar: &Registrar) -> Result<u64> {
self.deposits
.iter()
.filter(|d| d.is_used)
.try_fold(0u64, |sum, d| {
registrar.voting_mints[d.voting_mint_config_idx as usize]
.deposit_vote_weight(d.amount_deposited_native)
.unlocked_vote_weight(d.amount_deposited_native)
.map(|vp| sum.checked_add(vp).unwrap())
})
}

View File

@ -18,8 +18,8 @@ pub struct VotingMintConfig {
/// The authority that is allowed to push grants into voters
pub grant_authority: Pubkey,
/// Vote weight factor for deposits, in 1/SCALED_FACTOR_BASE units.
pub deposit_scaled_factor: u64,
/// Vote weight factor for unlocked deposits, in 1/SCALED_FACTOR_BASE units.
pub unlocked_scaled_factor: u64,
/// Maximum vote weight factor for lockups, in 1/SCALED_FACTOR_BASE units.
pub lockup_scaled_factor: u64,
@ -65,10 +65,10 @@ impl VotingMintConfig {
}
/// The vote weight a deposit of a number of native tokens should have.
pub fn deposit_vote_weight(&self, amount_native: u64) -> Result<u64> {
pub fn unlocked_vote_weight(&self, amount_native: u64) -> Result<u64> {
Self::apply_factor(
self.base_vote_weight(amount_native)?,
self.deposit_scaled_factor,
self.unlocked_scaled_factor,
)
}

View File

@ -99,7 +99,7 @@ impl AddinCookie {
index: u16,
mint: &MintCookie,
digit_shift: i8,
deposit_scaled_factor: f64,
unlocked_scaled_factor: f64,
lockup_scaled_factor: f64,
lockup_saturation_secs: u64,
grant_authority: Option<Pubkey>,
@ -110,7 +110,7 @@ impl AddinCookie {
&voter_stake_registry::instruction::ConfigureVotingMint {
idx: index,
digit_shift,
deposit_scaled_factor: (deposit_scaled_factor * 1e9) as u64,
unlocked_scaled_factor: (unlocked_scaled_factor * 1e9) as u64,
lockup_scaled_factor: (lockup_scaled_factor * 1e9) as u64,
lockup_saturation_secs,
grant_authority,
@ -647,7 +647,7 @@ impl AddinCookie {
}
#[allow(dead_code)]
pub async fn internal_transfer(
pub async fn internal_transfer_locked(
&self,
registrar: &RegistrarCookie,
voter: &VoterCookie,
@ -657,7 +657,7 @@ impl AddinCookie {
amount: u64,
) -> Result<(), TransportError> {
let data = anchor_lang::InstructionData::data(
&voter_stake_registry::instruction::InternalTransfer {
&voter_stake_registry::instruction::InternalTransferLocked {
source_deposit_entry_index,
target_deposit_entry_index,
amount,
@ -665,7 +665,48 @@ impl AddinCookie {
);
let accounts = anchor_lang::ToAccountMetas::to_account_metas(
&voter_stake_registry::accounts::InternalTransfer {
&voter_stake_registry::accounts::InternalTransferLocked {
registrar: registrar.address,
voter: voter.address,
voter_authority: authority.pubkey(),
},
None,
);
let instructions = vec![Instruction {
program_id: self.program_id,
accounts,
data,
}];
// clone the secrets
let signer = Keypair::from_base58_string(&authority.to_base58_string());
self.solana
.process_transaction(&instructions, Some(&[&signer]))
.await
}
#[allow(dead_code)]
pub async fn internal_transfer_unlocked(
&self,
registrar: &RegistrarCookie,
voter: &VoterCookie,
authority: &Keypair,
source_deposit_entry_index: u8,
target_deposit_entry_index: u8,
amount: u64,
) -> Result<(), TransportError> {
let data = anchor_lang::InstructionData::data(
&voter_stake_registry::instruction::InternalTransferUnlocked {
source_deposit_entry_index,
target_deposit_entry_index,
amount,
},
);
let accounts = anchor_lang::ToAccountMetas::to_account_metas(
&voter_stake_registry::accounts::InternalTransferUnlocked {
registrar: registrar.address,
voter: voter.address,
voter_authority: authority.pubkey(),

View File

@ -163,7 +163,7 @@ async fn test_grants() -> Result<(), TransportError> {
assert_eq!(deposit.lockup.kind, LockupKind::Monthly);
assert_eq!(deposit.lockup.periods_total().unwrap(), 12);
assert_eq!(deposit.lockup.periods_left(now as i64).unwrap(), 10);
assert_eq!(deposit.amount_withdrawable(now as i64), 2000);
assert_eq!(deposit.amount_unlocked(now as i64), 2000);
Ok(())
}

View File

@ -27,7 +27,7 @@ async fn get_lockup_data(
duration,
d.amount_initially_locked_native,
d.amount_deposited_native,
d.amount_withdrawable(now),
d.amount_unlocked(now),
)
}
@ -89,8 +89,8 @@ async fn test_internal_transfer() -> Result<(), TransportError> {
amount,
)
};
let internal_transfer = |source: u8, target: u8, amount: u64| {
addin.internal_transfer(&registrar, &voter, &voter_authority, source, target, amount)
let internal_transfer_locked = |source: u8, target: u8, amount: u64| {
addin.internal_transfer_locked(&registrar, &voter, &voter_authority, source, target, amount)
};
let time_offset = Arc::new(RefCell::new(0i64));
let advance_time = |extra: u64| {
@ -148,13 +148,13 @@ async fn test_internal_transfer() -> Result<(), TransportError> {
);
assert_eq!(lockup_status(1).await, (day + hour, 3 * day, 30, 30, 10));
internal_transfer(0, 1, 1)
internal_transfer_locked(0, 1, 1)
.await
.expect_err("can't make less strict/period");
internal_transfer(1, 0, 21)
internal_transfer_locked(1, 0, 21)
.await
.expect_err("can only transfer locked");
internal_transfer(1, 0, 10).await.unwrap();
internal_transfer_locked(1, 0, 10).await.unwrap();
context.solana.advance_clock_by_slots(2).await;
assert_eq!(
@ -198,7 +198,7 @@ async fn test_internal_transfer() -> Result<(), TransportError> {
assert_eq!(lockup_status(2).await, (0, 5 * day, 1000, 1000, 0));
assert_eq!(lockup_status(3).await, (0, 5 * day, 0, 0, 0));
internal_transfer(2, 3, 100).await.unwrap();
internal_transfer_locked(2, 3, 100).await.unwrap();
context.solana.advance_clock_by_slots(2).await;
assert_eq!(lockup_status(2).await, (0, 5 * day, 900, 900, 0));
@ -206,7 +206,7 @@ async fn test_internal_transfer() -> Result<(), TransportError> {
advance_time(2 * day + hour).await;
internal_transfer(2, 3, 100)
internal_transfer_locked(2, 3, 100)
.await
.expect_err("target deposit has not enough period left");
@ -224,7 +224,7 @@ async fn test_internal_transfer() -> Result<(), TransportError> {
)
.await
.unwrap();
internal_transfer(2, 4, 100).await.unwrap();
internal_transfer_locked(2, 4, 100).await.unwrap();
assert_eq!(lockup_status(2).await, (0, 5 * day, 800, 800, 0));
assert_eq!(
@ -237,7 +237,7 @@ async fn test_internal_transfer() -> Result<(), TransportError> {
context.solana.advance_clock_by_slots(2).await;
// still ok, cliff deposit 4 still has 7 days of lockup left, which is >= 5
internal_transfer(2, 4, 800).await.unwrap();
internal_transfer_locked(2, 4, 800).await.unwrap();
assert_eq!(lockup_status(2).await, (0, 5 * day, 0, 0, 0));
assert_eq!(lockup_status(4).await, (hour, 7 * day, 900, 900, 0));

View File

@ -136,7 +136,7 @@ async fn test_log_voter_info() -> Result<(), TransportError> {
let voter_event =
deserialize_event::<voter_stake_registry::events::VoterInfo>(&log[2]).unwrap();
assert_eq!(voter_event.voting_power_deposit_only, 12000);
assert_eq!(voter_event.voting_power_unlocked_only, 12000);
assert_eq!(
voter_event.voting_power,
12000 + (1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11) * 1000 / 12
@ -146,11 +146,11 @@ async fn test_log_voter_info() -> Result<(), TransportError> {
deserialize_event::<voter_stake_registry::events::DepositEntryInfo>(&log[4]).unwrap();
assert_eq!(deposit_event.deposit_entry_index, 0);
assert_eq!(deposit_event.voting_mint_config_index, 0);
assert_eq!(deposit_event.withdrawable, 1000);
assert_eq!(deposit_event.unlocked, 1000);
assert_eq!(deposit_event.voting_power, voter_event.voting_power);
assert_eq!(
deposit_event.voting_power_deposit_only,
voter_event.voting_power_deposit_only
deposit_event.voting_power_unlocked_only,
voter_event.voting_power_unlocked_only
);
assert!(deposit_event.locking.is_some());
let locking = deposit_event.locking.unwrap();

View File

@ -27,7 +27,7 @@ async fn get_lockup_data(
duration,
d.amount_initially_locked_native,
d.amount_deposited_native,
d.amount_withdrawable(now),
d.amount_unlocked(now),
)
}

View File

@ -1072,7 +1072,7 @@ export type VoterStakeRegistry = {
},
{
"code": 6008,
"name": "InsufficientVestedTokens",
"name": "InsufficientUnlockedTokens",
"msg": ""
},
{
@ -2277,7 +2277,7 @@ export const IDL: VoterStakeRegistry = {
},
{
"code": 6008,
"name": "InsufficientVestedTokens",
"name": "InsufficientUnlockedTokens",
"msg": ""
},
{