"unlocked_scaled_factor" was a confusing name because the value is
also used when computing vote weight for locked deposits. Rename to
"baseline_vote_weight_scaled_factor" and generally change "unlocked"
to "baseline" in several places.

Also rename "lockup_scaled_factor" to
"max_extra_lockup_vote_weight_scaled_factor" to highlight that it's just
the maximum contribution and that it's "extra" - on top of baseline.
This commit is contained in:
Christian Kamm 2022-02-14 10:17:53 +01:00
parent 4c5d896912
commit dd51d3cbdd
10 changed files with 82 additions and 64 deletions

View File

@ -3,8 +3,10 @@ use anchor_lang::prelude::*;
#[event]
#[derive(Debug)]
pub struct VoterInfo {
/// Voter's total voting power
pub voting_power: u64,
pub voting_power_unlocked_only: u64,
/// Voter's total voting power, when ignoring any effects from lockup
pub voting_power_baseline: u64,
}
#[derive(AnchorSerialize, AnchorDeserialize, Debug)]
@ -34,8 +36,8 @@ pub struct DepositEntryInfo {
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_unlocked_only: u64,
/// Voting power without any adjustments for lockup
pub voting_power_baseline: u64,
/// Information about locking, if any
pub locking: Option<LockingInfo>,
}

View File

@ -23,8 +23,8 @@ 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
/// * `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
/// * `baseline_vote_weight_scaled_factor`: vote weight factor for all funds in vault, in 1/1e9 units
/// * `max_extra_lockup_vote_weight_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,8 +35,8 @@ pub struct ConfigureVotingMint<'info> {
/// ```
/// vote_weight =
/// amount * 10^(digit_shift)
/// * (unlocked_scaled_factor/1e9
/// + lockup_duration_factor * lockup_scaled_factor/1e9)
/// * (baseline_vote_weight_scaled_factor/1e9
/// + lockup_duration_factor * max_extra_lockup_vote_weight_scaled_factor/1e9)
/// ```
/// where lockup_duration_factor is a value between 0 and 1, depending on how long
/// the amount is locked up. It is 1 when the lockup duration is greater or equal
@ -46,8 +46,8 @@ 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 unlocked_scaled_factor +
/// lockup_scaled_factor <= 1e9. That way you won't have issues with overflow no
/// If you use a single mint, prefer digit_shift=0 and baseline_vote_weight_scaled_factor +
/// max_extra_lockup_vote_weight_scaled_factor <= 1e9. That way you won't have issues with overflow no
/// matter the size of the mint's supply.
///
/// Digit shifting is particularly useful when using several voting token mints
@ -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, unlocked_scaled_factor=2e9, lockup_scaled_factor=0
/// * B with digit_shift=-3, unlocked_scaled_factor=1e9, lockup_scaled_factor=1e9
/// * A with digit_shift=0, baseline_vote_weight_scaled_factor=2e9, max_extra_lockup_vote_weight_scaled_factor=0
/// * B with digit_shift=-3, baseline_vote_weight_scaled_factor=1e9, max_extra_lockup_vote_weight_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,16 +65,16 @@ 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, unlocked_scaled_factor=2e9, lockup_scaled_factor=0
/// * B with digit_shift=0, unlocked_scaled_factor=1e9, lockup_scaled_factor=1e9
/// * A with digit_shift=3, baseline_vote_weight_scaled_factor=2e9, max_extra_lockup_vote_weight_scaled_factor=0
/// * B with digit_shift=0, baseline_vote_weight_scaled_factor=1e9, max_extra_lockup_vote_weight_scaled_factor=1e9
/// to not lose precision on B tokens.
///
pub fn configure_voting_mint(
ctx: Context<ConfigureVotingMint>,
idx: u16,
digit_shift: i8,
unlocked_scaled_factor: u64,
lockup_scaled_factor: u64,
baseline_vote_weight_scaled_factor: u64,
max_extra_lockup_vote_weight_scaled_factor: u64,
lockup_saturation_secs: u64,
grant_authority: Option<Pubkey>,
) -> Result<()> {
@ -100,8 +100,8 @@ pub fn configure_voting_mint(
registrar.voting_mints[idx] = VotingMintConfig {
mint,
digit_shift,
unlocked_scaled_factor,
lockup_scaled_factor,
baseline_vote_weight_scaled_factor,
max_extra_lockup_vote_weight_scaled_factor,
lockup_saturation_secs,
grant_authority: grant_authority.unwrap_or_default(),
reserved1: [0; 7],

View File

@ -32,7 +32,7 @@ pub fn log_voter_info(
msg!("voter");
emit!(VoterInfo {
voting_power: voter.weight(registrar)?,
voting_power_unlocked_only: voter.weight_from_unlocked(registrar)?,
voting_power_baseline: voter.weight_baseline(registrar)?,
});
msg!("deposit_entries");
@ -71,8 +71,8 @@ pub fn log_voter_info(
voting_mint_config_index: deposit.voting_mint_config_idx,
unlocked: deposit.amount_unlocked(curr_ts),
voting_power: deposit.voting_power(voting_mint_config, curr_ts)?,
voting_power_unlocked_only: voting_mint_config
.unlocked_vote_weight(deposit.amount_deposited_native)?,
voting_power_baseline: voting_mint_config
.baseline_vote_weight(deposit.amount_deposited_native)?,
locking: locking_info,
});
}

View File

@ -71,8 +71,8 @@ pub mod voter_stake_registry {
ctx: Context<ConfigureVotingMint>,
idx: u16,
digit_shift: i8,
unlocked_scaled_factor: u64,
lockup_scaled_factor: u64,
baseline_vote_weight_scaled_factor: u64,
max_extra_lockup_vote_weight_scaled_factor: u64,
lockup_saturation_secs: u64,
grant_authority: Option<Pubkey>,
) -> Result<()> {
@ -80,8 +80,8 @@ pub mod voter_stake_registry {
ctx,
idx,
digit_shift,
unlocked_scaled_factor,
lockup_scaled_factor,
baseline_vote_weight_scaled_factor,
max_extra_lockup_vote_weight_scaled_factor,
lockup_saturation_secs,
grant_authority,
)

View File

@ -52,12 +52,12 @@ impl DepositEntry {
/// For each cliff-locked token, the vote weight is:
///
/// ```
/// voting_power = unlocked_vote_weight
/// + lockup_duration_factor * max_lockup_vote_weight
/// voting_power = baseline_vote_weight
/// + lockup_duration_factor * max_extra_lockup_vote_weight
/// ```
///
/// with
/// unlocked_vote_weight and max_lockup_vote_weight from the
/// baseline_vote_weight and max_extra_lockup_vote_weight from the
/// VotingMintConfig
/// lockup_duration_factor = lockup_time_remaining / max_lockup_time
///
@ -90,10 +90,10 @@ impl DepositEntry {
/// voting_power_linear_vesting() below.
///
pub fn voting_power(&self, voting_mint_config: &VotingMintConfig, curr_ts: i64) -> Result<u64> {
let unlocked_vote_weight =
voting_mint_config.unlocked_vote_weight(self.amount_deposited_native)?;
let baseline_vote_weight =
voting_mint_config.baseline_vote_weight(self.amount_deposited_native)?;
let max_locked_vote_weight =
voting_mint_config.max_lockup_vote_weight(self.amount_initially_locked_native)?;
voting_mint_config.max_extra_lockup_vote_weight(self.amount_initially_locked_native)?;
let locked_vote_weight = self.voting_power_locked(
curr_ts,
max_locked_vote_weight,
@ -103,7 +103,7 @@ impl DepositEntry {
locked_vote_weight <= max_locked_vote_weight,
InternalErrorBadLockupVoteWeight
);
unlocked_vote_weight
baseline_vote_weight
.checked_add(locked_vote_weight)
.ok_or(Error::ErrorCode(ErrorCode::VoterWeightOverflow))
}
@ -447,19 +447,19 @@ mod tests {
let voting_mint_config = VotingMintConfig {
mint: Pubkey::default(),
grant_authority: Pubkey::default(),
unlocked_scaled_factor: 1_000_000_000, // 1x
lockup_scaled_factor: 1_000_000_000, // 1x
baseline_vote_weight_scaled_factor: 1_000_000_000, // 1x
max_extra_lockup_vote_weight_scaled_factor: 1_000_000_000, // 1x
lockup_saturation_secs: saturation as u64,
digit_shift: 0,
reserved1: [0; 7],
reserved2: [0; 7],
};
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)?;
let baseline_vote_weight =
voting_mint_config.baseline_vote_weight(deposit.amount_deposited_native)?;
assert_eq!(baseline_vote_weight, 10_000);
let max_locked_vote_weight = voting_mint_config
.max_extra_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

View File

@ -55,10 +55,10 @@ impl Registrar {
.ok_or(Error::ErrorCode(ErrorCode::VotingMintNotFound))?;
let mint = Account::<Mint>::try_from(mint_account)?;
sum = sum
.checked_add(voting_mint_config.unlocked_vote_weight(mint.supply)?)
.checked_add(voting_mint_config.baseline_vote_weight(mint.supply)?)
.ok_or(Error::ErrorCode(ErrorCode::VoterWeightOverflow))?;
sum = sum
.checked_add(voting_mint_config.max_lockup_vote_weight(mint.supply)?)
.checked_add(voting_mint_config.max_extra_lockup_vote_weight(mint.supply)?)
.ok_or(Error::ErrorCode(ErrorCode::VoterWeightOverflow))?;
Ok(sum)
})

View File

@ -18,6 +18,7 @@ const_assert!(std::mem::size_of::<Voter>() == 2 * 32 + 32 * 80 + 2 + 94);
const_assert!(std::mem::size_of::<Voter>() % 8 == 0);
impl Voter {
/// The full vote weight available to the voter
pub fn weight(&self, registrar: &Registrar) -> Result<u64> {
let curr_ts = registrar.clock_unix_timestamp();
self.deposits
@ -32,13 +33,14 @@ impl Voter {
})
}
pub fn weight_from_unlocked(&self, registrar: &Registrar) -> Result<u64> {
/// The vote weight available to the voter when ignoring any lockup effects
pub fn weight_baseline(&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]
.unlocked_vote_weight(d.amount_deposited_native)
.baseline_vote_weight(d.amount_deposited_native)
.map(|vp| sum.checked_add(vp).unwrap())
})
}

View File

@ -18,11 +18,19 @@ pub struct VotingMintConfig {
/// The authority that is allowed to push grants into voters
pub grant_authority: Pubkey,
/// Vote weight factor for unlocked deposits, in 1/SCALED_FACTOR_BASE units.
pub unlocked_scaled_factor: u64,
/// Vote weight factor for all funds in the account, no matter if locked or not.
///
/// In 1/SCALED_FACTOR_BASE units.
pub baseline_vote_weight_scaled_factor: u64,
/// Maximum vote weight factor for lockups, in 1/SCALED_FACTOR_BASE units.
pub lockup_scaled_factor: u64,
/// Maximum extra vote weight factor for lockups.
///
/// This is the extra votes gained for lockups lasting lockup_saturation_secs or
/// longer. Shorter lockups receive only a fraction of the maximum extra vote weight,
/// based on lockup_time divided by lockup_saturation_secs.
///
/// In 1/SCALED_FACTOR_BASE units.
pub max_extra_lockup_vote_weight_scaled_factor: u64,
/// Number of seconds of lockup needed to reach the maximum lockup bonus.
pub lockup_saturation_secs: u64,
@ -41,7 +49,7 @@ impl VotingMintConfig {
/// Converts an amount in this voting mints's native currency
/// to the base vote weight (without the deposit or lockup scalings)
/// by applying the digit_shift factor.
pub fn base_vote_weight(&self, amount_native: u64) -> Result<u64> {
fn digit_shift_native(&self, amount_native: u64) -> Result<u64> {
let compute = || -> Option<u64> {
let val = if self.digit_shift < 0 {
(amount_native as u128).checked_div(10u128.pow((-self.digit_shift) as u32))?
@ -54,10 +62,10 @@ impl VotingMintConfig {
}
/// Apply a factor in SCALED_FACTOR_BASE units.
fn apply_factor(base_vote_weight: u64, factor: u64) -> Result<u64> {
fn apply_factor(base: u64, factor: u64) -> Result<u64> {
let compute = || -> Option<u64> {
u64::try_from(
(base_vote_weight as u128)
(base as u128)
.checked_mul(factor as u128)?
.checked_div(SCALED_FACTOR_BASE as u128)?,
)
@ -67,19 +75,22 @@ impl VotingMintConfig {
}
/// The vote weight a deposit of a number of native tokens should have.
pub fn unlocked_vote_weight(&self, amount_native: u64) -> Result<u64> {
///
/// This vote_weight is a component for all funds in a voter account, no
/// matter if locked up or not.
pub fn baseline_vote_weight(&self, amount_native: u64) -> Result<u64> {
Self::apply_factor(
self.base_vote_weight(amount_native)?,
self.unlocked_scaled_factor,
self.digit_shift_native(amount_native)?,
self.baseline_vote_weight_scaled_factor,
)
}
/// The maximum vote weight a number of locked up native tokens can have.
/// The maximum extra vote weight a number of locked up native tokens can have.
/// Will be multiplied with a factor between 0 and 1 for the lockup duration.
pub fn max_lockup_vote_weight(&self, amount_native: u64) -> Result<u64> {
pub fn max_extra_lockup_vote_weight(&self, amount_native: u64) -> Result<u64> {
Self::apply_factor(
self.base_vote_weight(amount_native)?,
self.lockup_scaled_factor,
self.digit_shift_native(amount_native)?,
self.max_extra_lockup_vote_weight_scaled_factor,
)
}
@ -94,7 +105,8 @@ impl VotingMintConfig {
/// want to use the grant / vesting / clawback functionality for non-voting
/// tokens like USDC.
pub fn grants_vote_weight(&self) -> bool {
self.unlocked_scaled_factor > 0 || self.lockup_scaled_factor > 0
self.baseline_vote_weight_scaled_factor > 0
|| self.max_extra_lockup_vote_weight_scaled_factor > 0
}
}

View File

@ -99,8 +99,8 @@ impl AddinCookie {
index: u16,
mint: &MintCookie,
digit_shift: i8,
unlocked_scaled_factor: f64,
lockup_scaled_factor: f64,
baseline_vote_weight_scaled_factor: f64,
max_extra_lockup_vote_weight_scaled_factor: f64,
lockup_saturation_secs: u64,
grant_authority: Option<Pubkey>,
other_mints: Option<&[Pubkey]>,
@ -111,8 +111,10 @@ impl AddinCookie {
&voter_stake_registry::instruction::ConfigureVotingMint {
idx: index,
digit_shift,
unlocked_scaled_factor: (unlocked_scaled_factor * 1e9) as u64,
lockup_scaled_factor: (lockup_scaled_factor * 1e9) as u64,
baseline_vote_weight_scaled_factor: (baseline_vote_weight_scaled_factor * 1e9)
as u64,
max_extra_lockup_vote_weight_scaled_factor:
(max_extra_lockup_vote_weight_scaled_factor * 1e9) as u64,
lockup_saturation_secs,
grant_authority,
},

View File

@ -137,7 +137,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_unlocked_only, 12000);
assert_eq!(voter_event.voting_power_baseline, 12000);
assert_eq!(
voter_event.voting_power,
12000 + (1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11) * 1000 / 12
@ -150,8 +150,8 @@ async fn test_log_voter_info() -> Result<(), TransportError> {
assert_eq!(deposit_event.unlocked, 1000);
assert_eq!(deposit_event.voting_power, voter_event.voting_power);
assert_eq!(
deposit_event.voting_power_unlocked_only,
voter_event.voting_power_unlocked_only
deposit_event.voting_power_baseline,
voter_event.voting_power_baseline
);
assert!(deposit_event.locking.is_some());
let locking = deposit_event.locking.unwrap();