add monthly vesting
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
This commit is contained in:
parent
23ad57302d
commit
bff99fc155
|
@ -10,16 +10,23 @@ use std::convert::TryFrom;
|
|||
vote_weight_record!(crate::ID);
|
||||
|
||||
/// Seconds in one day.
|
||||
/// for localnet, to make testing of vesting possible,
|
||||
/// set a low value so tests can just sleep for 10s to simulate a day
|
||||
#[cfg(feature = "localnet")]
|
||||
pub const SECS_PER_DAY: i64 = 10;
|
||||
#[cfg(not(feature = "localnet"))]
|
||||
pub const SECS_PER_DAY: i64 = 86_400;
|
||||
|
||||
/// Seconds in one month.
|
||||
#[cfg(feature = "localnet")]
|
||||
pub const SECS_PER_MONTH: i64 = 10;
|
||||
#[cfg(not(feature = "localnet"))]
|
||||
pub const SECS_PER_MONTH: i64 = 86_400 * 30;
|
||||
|
||||
/// Maximum number of days one can lock for.
|
||||
pub const MAX_DAYS_LOCKED: u64 = 2555;
|
||||
|
||||
/// Maximum number of months one can lock for.
|
||||
pub const MAX_MONTHS_LOCKED: u64 = 2555;
|
||||
|
||||
/// Instance of a voting rights distributor.
|
||||
#[account(zero_copy)]
|
||||
pub struct Registrar {
|
||||
|
@ -199,6 +206,7 @@ impl DepositEntry {
|
|||
}
|
||||
match self.lockup.kind {
|
||||
LockupKind::Daily => self.voting_power_daily(curr_ts),
|
||||
LockupKind::Monthly => self.voting_power_monthly(curr_ts),
|
||||
LockupKind::Cliff => self.voting_power_cliff(curr_ts),
|
||||
}
|
||||
}
|
||||
|
@ -229,6 +237,32 @@ impl DepositEntry {
|
|||
Ok(decayed_vote_weight)
|
||||
}
|
||||
|
||||
fn voting_power_monthly(&self, curr_ts: i64) -> Result<u64> {
|
||||
let m = MAX_MONTHS_LOCKED;
|
||||
let n = self.lockup.months_left(curr_ts)?;
|
||||
|
||||
if n == 0 {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let decayed_vote_weight = self
|
||||
.amount_scaled
|
||||
.checked_mul(
|
||||
// Ok to divide by two here because, if n is zero, then the
|
||||
// voting power is zero. And if n is one or above, then the
|
||||
// numerator is 2 or above.
|
||||
n.checked_mul(n.checked_add(1).unwrap())
|
||||
.unwrap()
|
||||
.checked_div(2)
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap()
|
||||
.checked_div(m.checked_mul(n).unwrap()) //.checked_mul(2).unwrap())
|
||||
.unwrap();
|
||||
|
||||
Ok(decayed_vote_weight)
|
||||
}
|
||||
|
||||
fn voting_power_cliff(&self, curr_ts: i64) -> Result<u64> {
|
||||
let decayed_voting_weight = self
|
||||
.lockup
|
||||
|
@ -250,6 +284,7 @@ impl DepositEntry {
|
|||
}
|
||||
match self.lockup.kind {
|
||||
LockupKind::Daily => self.vested_daily(curr_ts),
|
||||
LockupKind::Monthly => self.vested_monthly(curr_ts),
|
||||
LockupKind::Cliff => self.vested_cliff(),
|
||||
}
|
||||
}
|
||||
|
@ -269,6 +304,21 @@ impl DepositEntry {
|
|||
Ok(vested)
|
||||
}
|
||||
|
||||
fn vested_monthly(&self, curr_ts: i64) -> Result<u64> {
|
||||
let month_current = self.lockup.month_current(curr_ts)?;
|
||||
let months_total = self.lockup.months_total()?;
|
||||
if month_current >= months_total {
|
||||
return Ok(self.amount_deposited);
|
||||
}
|
||||
let vested = self
|
||||
.amount_deposited
|
||||
.checked_mul(month_current)
|
||||
.unwrap()
|
||||
.checked_div(months_total)
|
||||
.unwrap();
|
||||
Ok(vested)
|
||||
}
|
||||
|
||||
fn vested_cliff(&self) -> Result<u64> {
|
||||
let curr_ts = Clock::get()?.unix_timestamp;
|
||||
if curr_ts < self.lockup.end_ts {
|
||||
|
@ -326,12 +376,43 @@ impl Lockup {
|
|||
|
||||
Ok(lockup_days)
|
||||
}
|
||||
|
||||
/// Returns the number of months left on the lockup.
|
||||
pub fn months_left(&self, curr_ts: i64) -> Result<u64> {
|
||||
Ok(self
|
||||
.months_total()?
|
||||
.saturating_sub(self.month_current(curr_ts)?))
|
||||
}
|
||||
|
||||
/// Returns the current month in the vesting schedule.
|
||||
pub fn month_current(&self, curr_ts: i64) -> Result<u64> {
|
||||
let d = u64::try_from({
|
||||
let secs_elapsed = curr_ts.saturating_sub(self.start_ts);
|
||||
secs_elapsed.checked_div(SECS_PER_MONTH).unwrap()
|
||||
})
|
||||
.map_err(|_| ErrorCode::UnableToConvert)?;
|
||||
Ok(d)
|
||||
}
|
||||
|
||||
/// Returns the total amount of months in the lockup period.
|
||||
pub fn months_total(&self) -> Result<u64> {
|
||||
// Number of seconds in the entire lockup.
|
||||
let lockup_secs = self.end_ts.checked_sub(self.start_ts).unwrap();
|
||||
require!(lockup_secs % SECS_PER_MONTH == 0, InvalidLockupPeriod);
|
||||
|
||||
// Total months in the entire lockup.
|
||||
let lockup_months =
|
||||
u64::try_from(lockup_secs.checked_div(SECS_PER_MONTH).unwrap()).unwrap();
|
||||
|
||||
Ok(lockup_months)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(AnchorSerialize, AnchorDeserialize, Debug, Clone, Copy)]
|
||||
pub enum LockupKind {
|
||||
Daily,
|
||||
Monthly,
|
||||
Cliff,
|
||||
}
|
||||
|
||||
|
@ -398,7 +479,7 @@ mod tests {
|
|||
run_test_days_left(TestDaysLeft {
|
||||
expected_days_left: 1,
|
||||
days_total: 10.0,
|
||||
curr_day: 9.1,
|
||||
curr_day: 9.9,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -420,6 +501,51 @@ mod tests {
|
|||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn months_left_start() -> Result<()> {
|
||||
run_test_months_left(TestMonthsLeft {
|
||||
expected_months_left: 10,
|
||||
months_total: 10.0,
|
||||
curr_month: 0.,
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn months_left_one_half() -> Result<()> {
|
||||
run_test_months_left(TestMonthsLeft {
|
||||
expected_months_left: 10,
|
||||
months_total: 10.0,
|
||||
curr_month: 0.5,
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn months_left_one_and_a_half() -> Result<()> {
|
||||
run_test_months_left(TestMonthsLeft {
|
||||
expected_months_left: 9,
|
||||
months_total: 10.0,
|
||||
curr_month: 1.5,
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn months_left_ten() -> Result<()> {
|
||||
run_test_months_left(TestMonthsLeft {
|
||||
expected_months_left: 9,
|
||||
months_total: 10.0,
|
||||
curr_month: 1.5,
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn months_left_eleven() -> Result<()> {
|
||||
run_test_months_left(TestMonthsLeft {
|
||||
expected_months_left: 0,
|
||||
months_total: 10.0,
|
||||
curr_month: 11.,
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn voting_power_cliff_warmup() -> Result<()> {
|
||||
// 10 tokens with 6 decimals.
|
||||
|
@ -736,6 +862,12 @@ mod tests {
|
|||
curr_day: f64,
|
||||
}
|
||||
|
||||
struct TestMonthsLeft {
|
||||
expected_months_left: u64,
|
||||
months_total: f64,
|
||||
curr_month: f64,
|
||||
}
|
||||
|
||||
struct TestVotingPower {
|
||||
amount_deposited: u64,
|
||||
days_total: f64,
|
||||
|
@ -759,6 +891,21 @@ mod tests {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn run_test_months_left(t: TestMonthsLeft) -> Result<()> {
|
||||
let start_ts = 1634929833;
|
||||
let end_ts = start_ts + months_to_secs(t.months_total);
|
||||
let curr_ts = start_ts + months_to_secs(t.curr_month);
|
||||
let l = Lockup {
|
||||
kind: LockupKind::Monthly,
|
||||
start_ts,
|
||||
end_ts,
|
||||
padding: [0u8; 16],
|
||||
};
|
||||
let months_left = l.months_left(curr_ts)?;
|
||||
assert_eq!(months_left, t.expected_months_left);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_test_voting_power(t: TestVotingPower) -> Result<()> {
|
||||
let start_ts = 1634929833;
|
||||
let end_ts = start_ts + days_to_secs(t.days_total);
|
||||
|
@ -782,7 +929,12 @@ mod tests {
|
|||
}
|
||||
|
||||
fn days_to_secs(days: f64) -> i64 {
|
||||
let d = 86_400.0 * days;
|
||||
let d = 86_400. * days;
|
||||
d.round() as i64
|
||||
}
|
||||
|
||||
fn months_to_secs(months: f64) -> i64 {
|
||||
let d = 86_400. * 30. * months;
|
||||
d.round() as i64
|
||||
}
|
||||
|
||||
|
|
|
@ -249,6 +249,7 @@ pub mod governance_registry {
|
|||
// Get the deposit being withdrawn from.
|
||||
let deposit_entry = &mut voter.deposits[deposit_id as usize];
|
||||
require!(deposit_entry.is_used, InvalidDepositId);
|
||||
msg!("deposit_entry.vested() {:?}", deposit_entry.vested());
|
||||
require!(deposit_entry.vested()? >= amount, InsufficientVestedTokens);
|
||||
require!(
|
||||
deposit_entry.amount_left() >= amount,
|
||||
|
|
|
@ -358,6 +358,64 @@ describe("voting-rights", () => {
|
|||
assert.ok(vtAccount.amount.toNumber() === 0);
|
||||
});
|
||||
|
||||
it("Deposits monthly locked A tokens", async () => {
|
||||
const amount = new BN(10);
|
||||
const kind = { monthly: {} };
|
||||
const months = 1;
|
||||
await program.rpc.createDeposit(kind, amount, months, {
|
||||
accounts: {
|
||||
deposit: {
|
||||
voter,
|
||||
exchangeVault: exchangeVaultA,
|
||||
depositToken: godA,
|
||||
votingToken,
|
||||
authority: program.provider.wallet.publicKey,
|
||||
registrar,
|
||||
depositMint: mintA,
|
||||
votingMint: votingMintA,
|
||||
tokenProgram,
|
||||
systemProgram,
|
||||
associatedTokenProgram,
|
||||
rent,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const voterAccount = await program.account.voter.fetch(voter);
|
||||
const deposit = voterAccount.deposits[2];
|
||||
assert.ok(deposit.isUsed);
|
||||
assert.ok(deposit.amountDeposited.toNumber() === 10);
|
||||
assert.ok(deposit.rateIdx === 0);
|
||||
});
|
||||
|
||||
it("Withdraws monthly locked A tokens", async () => {
|
||||
await sleep(10000);
|
||||
const depositId = 2;
|
||||
const amount = new BN(10);
|
||||
await program.rpc.withdraw(depositId, amount, {
|
||||
accounts: {
|
||||
registrar,
|
||||
voter,
|
||||
exchangeVault: exchangeVaultA,
|
||||
withdrawMint: mintA,
|
||||
votingToken,
|
||||
votingMint: votingMintA,
|
||||
destination: godA,
|
||||
authority: program.provider.wallet.publicKey,
|
||||
tokenProgram,
|
||||
},
|
||||
});
|
||||
|
||||
const voterAccount = await program.account.voter.fetch(voter);
|
||||
const deposit = voterAccount.deposits[0];
|
||||
assert.ok(deposit.isUsed);
|
||||
assert.ok(deposit.amountDeposited.toNumber() === 0);
|
||||
assert.ok(deposit.rateIdx === 0);
|
||||
|
||||
const vtAccount = await votingTokenClientA.getAccountInfo(votingToken);
|
||||
assert.ok(vtAccount.amount.toNumber() === 0);
|
||||
});
|
||||
|
||||
it("Updates a vote weight record", async () => {
|
||||
await program.rpc.updateVoterWeightRecord({
|
||||
accounts: {
|
||||
|
|
Loading…
Reference in New Issue