Add function to compute the guaranteed locked vote power bonus

This is useful for other programs that may want to make decisions purely
based on the amount of weight generated from guaranteed-to-be-locked
tokens at a specific time.
This commit is contained in:
Christian Kamm 2022-03-09 10:08:12 +01:00
parent 2ea39ddc99
commit b0660df3b9
3 changed files with 107 additions and 1 deletions

View File

@ -113,4 +113,7 @@ pub enum ErrorCode {
// 6036 / 0x1794
#[msg("")]
VaultTokenNonZero,
// 6037 / 0x1795
#[msg("")]
InvalidTimestampArguments,
}

View File

@ -133,6 +133,34 @@ impl DepositEntry {
}
}
/// Vote power contribution from locked funds only at `at_ts`, assuming the user does everything
/// they can to unlock as quickly as possible at `curr_ts`.
///
/// Currently that means that Constant lockups get turned into Cliff lockups.
pub fn voting_power_locked_guaranteed(
&self,
curr_ts: i64,
at_ts: i64,
max_locked_vote_weight: u64,
lockup_saturation_secs: u64,
) -> Result<u64> {
let mut altered = self.clone();
// Trigger the unlock phase for constant lockups
if self.lockup.kind == LockupKind::Constant {
altered.lockup.kind = LockupKind::Cliff;
altered.lockup.start_ts = curr_ts;
altered.lockup.end_ts = curr_ts
.checked_add(i64::try_from(self.lockup.seconds_left(curr_ts)).unwrap())
.unwrap();
}
// Other lockup types don't need changes, because the user
// cannot reduce their lockup strength.
altered.voting_power_locked(at_ts, max_locked_vote_weight, lockup_saturation_secs)
}
/// Vote power contribution from funds with linear vesting.
fn voting_power_cliff(
&self,
@ -351,7 +379,7 @@ impl DepositEntry {
#[cfg(test)]
mod tests {
use super::*;
use crate::LockupKind::Daily;
use crate::LockupKind::{Constant, Daily};
#[test]
pub fn resolve_vesting() -> Result<()> {
@ -489,4 +517,53 @@ mod tests {
Ok(())
}
#[test]
pub fn guaranteed_lockup_test() -> Result<()> {
// Check that constant lockups are handled correctly.
let day: i64 = 86_400;
let saturation = (10 * day) as u64;
let start = 10_000_000_000; // arbitrary point
let deposit = DepositEntry {
amount_deposited_native: 10_000,
amount_initially_locked_native: 10_000,
lockup: Lockup {
start_ts: start,
end_ts: start + 5 * day,
kind: Constant,
reserved: [0; 15],
},
is_used: true,
allow_clawback: false,
voting_mint_config_idx: 0,
reserved: [0; 29],
};
let v = |curr_offset, at_offset| {
deposit
.voting_power_locked_guaranteed(
start + curr_offset,
start + at_offset,
100,
saturation,
)
.unwrap()
};
assert_eq!(v(0, 0), 50);
assert_eq!(v(-day, 0), 40);
assert_eq!(v(-100 * day, 0), 0);
assert_eq!(v(-100 * day, -98 * day), 30);
assert_eq!(v(0, day), 40);
assert_eq!(v(0, 5 * day), 0);
assert_eq!(v(0, 50 * day), 0);
assert_eq!(v(day, day), 50);
assert_eq!(v(day, 2 * day,), 40);
assert_eq!(v(day, 20 * day), 0);
assert_eq!(v(50 * day, 50 * day), 50);
assert_eq!(v(50 * day, 51 * day), 40);
assert_eq!(v(50 * day, 80 * day), 0);
Ok(())
}
}

View File

@ -45,6 +45,32 @@ impl Voter {
})
}
/// The extra lockup vote weight that the user is guaranteed to have at `at_ts`, assuming
/// they withdraw and unlock as much as possible starting from `curr_ts`.
pub fn weight_locked_guaranteed(
&self,
registrar: &Registrar,
curr_ts: i64,
at_ts: i64,
) -> Result<u64> {
require!(at_ts >= curr_ts, InvalidTimestampArguments);
self.deposits
.iter()
.filter(|d| d.is_used)
.try_fold(0u64, |sum, d| {
let mint_config = &registrar.voting_mints[d.voting_mint_config_idx as usize];
let max_locked_vote_weight =
mint_config.max_extra_lockup_vote_weight(d.amount_initially_locked_native)?;
let amount = d.voting_power_locked_guaranteed(
curr_ts,
at_ts,
max_locked_vote_weight,
mint_config.lockup_saturation_secs,
)?;
Ok(sum.checked_add(amount).unwrap())
})
}
pub fn active_deposit_mut(&mut self, index: u8) -> Result<&mut DepositEntry> {
let index = index as usize;
require!(index < self.deposits.len(), OutOfBoundsDepositEntryIndex);