Skip to content

Commit

Permalink
Update vesting rounding (#248)
Browse files Browse the repository at this point in the history
* Update vesting rounding

* Fix veting test

* Fix clippy

* Fix comment
  • Loading branch information
guibescos authored Oct 23, 2023
1 parent a34d07d commit 393e3af
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 98 deletions.
90 changes: 43 additions & 47 deletions staking/programs/staking/src/state/vesting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,19 +160,19 @@ impl VestingSchedule {
0
} else {
// Amount that vests per period is (initial_balance / num_periods),
// but again, we need to do the math in 128 bit precision and make sure
// we round the vested balance down, so as to round the unvested balance up.

// Since we're in this branch, periods_passed <= num_periods, so vested <=
// initial_balance. Thus we know it can fit in a u64, so the unwrap
// can't fail.
let vested = (((periods_passed as u128) * (initial_balance as u128))
/ (num_periods as u128))
// but again, we need to do the math in 128 bit precision.

// Since we're in this branch, 0 <= periods_passed <= num_periods, so
// 0 <= remaining_periods <= num_periods.
// Additionally 0 <= initial_balance <= u64::MAX, so
// 0 <= unvested <= initial_balance <= u64::MAX
// therefore the unwrap can't fail.
// We round the unvested amount down, this makes the arithmetic for splitting accounts simpler.
let remaining_periods = num_periods.saturating_sub(periods_passed);

(((remaining_periods as u128) * (initial_balance as u128)) / (num_periods as u128))
.try_into()
.unwrap();
// We also know then 0 <= vested <= initial_balance, so this unwrap can't fail
// either I still feel safer with the unwrap though
initial_balance.checked_sub(vested).unwrap()
.unwrap()
}
}
}
Expand Down Expand Up @@ -211,24 +211,22 @@ impl VestingSchedule {
)
.map_err(|_| error!(ErrorCode::GenericOverflow))?;

let current_vested: u64 = (((periods_passed as u128) * (initial_balance as u128))
/ (num_periods as u128))
.try_into()
.map_err(|_| error!(ErrorCode::GenericOverflow))?;

let periods_passed_incremented = periods_passed
.checked_add(1)
.ok_or_else(|| error!(ErrorCode::GenericOverflow))?;

let next_period_vested: u64 = (((periods_passed_incremented as u128)
* (initial_balance as u128))
/ (num_periods as u128))
.try_into()
.map_err(|_| error!(ErrorCode::GenericOverflow))?;
let current_unvested: u64 = Self::periodic_vesting_helper(
current_time,
initial_balance,
start_date,
period_duration,
num_periods,
);
let next_period_unvested = Self::periodic_vesting_helper(
start_of_next_period,
initial_balance,
start_date,
period_duration,
num_periods,
);

let amount: u64 = next_period_vested
.checked_sub(current_vested)
.ok_or_else(|| error!(ErrorCode::GenericOverflow))?;
let amount: u64 = current_unvested.saturating_sub(next_period_unvested);

Ok(Some(VestingEvent {
time: start_of_next_period,
Expand Down Expand Up @@ -271,15 +269,15 @@ pub mod tests {
v.get_next_vesting(t, None).unwrap(),
Some(VestingEvent {
time: 6,
amount: 3,
amount: 4,
})
);
} else if t <= 10 {
// Linearly interpolate between (5, 20) and (11, 0)
let locked_float = 20.0 + (t - 5) as f64 * -20.0 / 6.0;
assert_eq!(
v.get_unvested_balance(t, None).unwrap(),
locked_float.ceil() as u64
locked_float.floor() as u64
);
assert_eq!(
v.get_next_vesting(t, None).unwrap(),
Expand Down Expand Up @@ -342,15 +340,15 @@ pub mod tests {
v.get_next_vesting(0, None).unwrap(),
Some(VestingEvent {
time: 8,
amount: 2,
amount: 3,
})
);
assert_eq!(v.get_unvested_balance(5, None).unwrap(), 20);
assert_eq!(
v.get_next_vesting(5, None).unwrap(),
Some(VestingEvent {
time: 8,
amount: 2,
amount: 3,
})
);
assert_eq!(v.get_unvested_balance(5 + 7 * 3, None).unwrap(), 0);
Expand All @@ -362,7 +360,7 @@ pub mod tests {
let locked_for_period = v.get_unvested_balance(t, None).unwrap();
// Linearly interpolate from (0, 20) to (7, 0)
let locked_float = f64::max(20.0 * (1.0 - period as f64 / 7.0), 0.0);
assert_eq!(locked_for_period, locked_float.ceil() as u64);
assert_eq!(locked_for_period, locked_float.floor() as u64);
for _t_in_period in 0..3 {
assert_eq!(v.get_unvested_balance(t, None).unwrap(), locked_for_period);
if period < 7 {
Expand Down Expand Up @@ -394,12 +392,12 @@ pub mod tests {
assert_eq!(v.get_unvested_balance(5 + 7 * 3, None).unwrap(), 20);
assert_eq!(v.get_unvested_balance(4, Some(5)).unwrap(), 20);
assert_eq!(v.get_unvested_balance(5 + 7 * 3, Some(5)).unwrap(), 0);
assert_eq!(v.get_unvested_balance(5 + 7 * 3, Some(6)).unwrap(), 3);
assert_eq!(v.get_unvested_balance(5 + 7 * 3, Some(6)).unwrap(), 2);
assert_eq!(
v.get_next_vesting(4 + 7 * 3, Some(5)).unwrap(),
Some(VestingEvent {
time: 26,
amount: 3,
amount: 2,
})
);
assert_eq!(v.get_next_vesting(4 + 7 * 3, None).unwrap(), None);
Expand All @@ -411,7 +409,7 @@ pub mod tests {
let locked_for_period = v.get_unvested_balance(t, Some(5)).unwrap();
// Linearly interpolate from (0, 20) to (7, 0)
let locked_float = f64::max(20.0 * (1.0 - period as f64 / 7.0), 0.0);
assert_eq!(locked_for_period, locked_float.ceil() as u64);
assert_eq!(locked_for_period, locked_float.floor() as u64);
for _t_in_period in 0..3 {
assert_eq!(
v.get_unvested_balance(t, Some(5)).unwrap(),
Expand Down Expand Up @@ -461,7 +459,7 @@ pub mod tests {
v.get_next_vesting(1 << 60, None).unwrap(),
Some(VestingEvent {
time: (1 << 60) + 1,
amount: 0,
amount: 1,
})
)
}
Expand All @@ -476,15 +474,13 @@ pub mod tests {
num_periods: 72,
};

let tokens_per_period = 1_000 / 72;

let value = v.get_unvested_balance(1, None).unwrap();
assert_eq!(value, 1000);
assert_eq!(
v.get_next_vesting(1, None).unwrap(),
Some(VestingEvent {
time: one_month.try_into().unwrap(),
amount: 13,
amount: 14,
})
);

Expand All @@ -497,14 +493,14 @@ pub mod tests {
.unwrap(),
Some(VestingEvent {
time: one_month.try_into().unwrap(),
amount: 13,
amount: 14,
})
);

let value = v
.get_unvested_balance(one_month.try_into().unwrap(), None)
.unwrap();
assert_eq!(value, 1000 - tokens_per_period);
assert_eq!(value, 986);
assert_eq!(
v.get_next_vesting(one_month.try_into().unwrap(), None)
.unwrap(),
Expand All @@ -517,7 +513,7 @@ pub mod tests {
let value = v
.get_unvested_balance((one_month * 2 - 1).try_into().unwrap(), None)
.unwrap();
assert_eq!(value, 1000 - tokens_per_period);
assert_eq!(value, 986);
assert_eq!(
v.get_next_vesting((one_month * 2 - 1).try_into().unwrap(), None)
.unwrap(),
Expand All @@ -530,7 +526,7 @@ pub mod tests {
let value = v
.get_unvested_balance((one_month * 2).try_into().unwrap(), None)
.unwrap();
assert_eq!(value, 973);
assert_eq!(value, 972);
assert_eq!(
v.get_next_vesting((one_month * 2).try_into().unwrap(), None)
.unwrap(),
Expand All @@ -543,14 +539,14 @@ pub mod tests {
let value = v
.get_unvested_balance((one_month * 72 - 1).try_into().unwrap(), None)
.unwrap();
assert_eq!(value, 14);
assert_eq!(value, 13);

assert_eq!(
v.get_next_vesting((one_month * 72 - 1).try_into().unwrap(), None)
.unwrap(),
Some(VestingEvent {
time: (one_month * 72).try_into().unwrap(),
amount: 14,
amount: 13,
})
);

Expand Down
Loading

1 comment on commit 393e3af

@vercel
Copy link

@vercel vercel bot commented on 393e3af Oct 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

staking-devnet – ./

staking-devnet-git-main-pyth-web.vercel.app
staking-devnet-pyth-web.vercel.app
governance-nu.vercel.app

Please sign in to comment.