-
Notifications
You must be signed in to change notification settings - Fork 49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Split accounts test #258
Split accounts test #258
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -607,6 +607,14 @@ pub mod staking { | |
config.unlocking_duration, | ||
)?; | ||
|
||
// Check that there aren't any positions (i.e., staked tokens) in the source account. | ||
// This check allows us to create an empty positions account on behalf of the recipient and | ||
// not worry about moving positions from the source account to the new account. | ||
require!( | ||
source_stake_account_positions.num_positions()? == 0, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's better to use |
||
ErrorCode::SplitWithStake | ||
); | ||
|
||
require!(split_request.amount > 0, ErrorCode::SplitZeroTokens); | ||
require!( | ||
split_request.amount < source_stake_account_custody.amount, | ||
|
@@ -626,18 +634,6 @@ pub mod staking { | |
source_stake_account_metadata.set_lock(source_vesting_account); | ||
new_stake_account_metadata.set_lock(new_vesting_account); | ||
|
||
// Split positions | ||
source_stake_account_positions.split( | ||
new_stake_account_positions, | ||
&mut source_stake_account_metadata.next_index, | ||
&mut new_stake_account_metadata.next_index, | ||
remaining_amount, | ||
split_request.amount, | ||
source_stake_account_custody.amount, | ||
current_epoch, | ||
config.unlocking_duration, | ||
)?; | ||
|
||
|
||
{ | ||
transfer( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,7 +20,7 @@ use { | |
/// Represents how a given initial balance vests over time | ||
/// It is unit-less, but units must be consistent | ||
#[repr(u8)] | ||
#[derive(AnchorSerialize, AnchorDeserialize, Debug, Clone, Copy, BorshSchema)] | ||
#[derive(AnchorSerialize, AnchorDeserialize, Debug, Clone, Copy, BorshSchema, PartialEq)] | ||
pub enum VestingSchedule { | ||
/// No vesting, i.e. balance is fully vested at all time | ||
FullyVested, | ||
|
@@ -247,6 +247,10 @@ impl VestingSchedule { | |
== total_amount, | ||
ErrorCode::SanityCheckFailed | ||
); | ||
// Note that the arithmetic below may lose precision. The calculations round down | ||
// the number of vesting tokens for both of the new accounts, which means that splitting | ||
// may vest some dust (1 of the smallest decimal point) of PYTH for both the source and | ||
// destination accounts. | ||
match self { | ||
VestingSchedule::FullyVested => { | ||
Ok((VestingSchedule::FullyVested, VestingSchedule::FullyVested)) | ||
|
@@ -299,8 +303,14 @@ pub mod tests { | |
use { | ||
crate::state::vesting::{ | ||
VestingEvent, | ||
VestingSchedule, | ||
VestingSchedule::{ | ||
self, | ||
PeriodicVesting, | ||
PeriodicVestingAfterListing, | ||
}, | ||
}, | ||
quickcheck::TestResult, | ||
quickcheck_macros::quickcheck, | ||
std::convert::TryInto, | ||
}; | ||
|
||
|
@@ -629,4 +639,147 @@ pub mod tests { | |
None | ||
); | ||
} | ||
|
||
#[quickcheck] | ||
fn test_split_with_args(transferred: u64, total: u64, initial_balance: u64) -> TestResult { | ||
if transferred > total || total == 0 { | ||
return TestResult::discard(); | ||
} | ||
|
||
let schedule = VestingSchedule::FullyVested; | ||
let (remaining_schedule, transferred_schedule) = schedule | ||
.split_vesting_schedule(total - transferred, transferred, total) | ||
.unwrap(); | ||
|
||
assert_eq!(remaining_schedule, VestingSchedule::FullyVested); | ||
assert_eq!(transferred_schedule, VestingSchedule::FullyVested); | ||
|
||
let schedule = PeriodicVesting { | ||
initial_balance, | ||
// all of these fields should be preserved in the result | ||
start_date: 203, | ||
period_duration: 100, | ||
num_periods: 12, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps use the parameters we're going to use in the real vesting schedules for this test There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't do this one because it makes the invariant test below much more expensive. I don't think the length of the start_date or duration should matter for the splitting logic though, so i think it's better to have the invariant test and not have this. |
||
}; | ||
let (remaining_schedule, transferred_schedule) = schedule | ||
.split_vesting_schedule(total - transferred, transferred, total) | ||
.unwrap(); | ||
|
||
match (remaining_schedule, transferred_schedule) { | ||
( | ||
PeriodicVesting { | ||
initial_balance: r, .. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd like to see a test that for an arbitrary t |
||
}, | ||
PeriodicVesting { | ||
initial_balance: t, .. | ||
}, | ||
) => { | ||
let sum = r + t; | ||
assert!(initial_balance.saturating_sub(2) <= sum && sum <= initial_balance); | ||
} | ||
_ => { | ||
panic!("Test failed"); | ||
} | ||
} | ||
|
||
let schedule = PeriodicVestingAfterListing { | ||
initial_balance, | ||
// all of these fields should be preserved in the result | ||
period_duration: 100, | ||
num_periods: 12, | ||
}; | ||
let (remaining_schedule, transferred_schedule) = schedule | ||
.split_vesting_schedule(total - transferred, transferred, total) | ||
.unwrap(); | ||
|
||
match (remaining_schedule, transferred_schedule) { | ||
( | ||
PeriodicVestingAfterListing { | ||
initial_balance: r, .. | ||
}, | ||
PeriodicVestingAfterListing { | ||
initial_balance: t, .. | ||
}, | ||
) => { | ||
let sum = r + t; | ||
assert!(initial_balance.saturating_sub(2) <= sum && sum <= initial_balance); | ||
} | ||
_ => { | ||
panic!("Test failed"); | ||
} | ||
} | ||
|
||
TestResult::passed() | ||
} | ||
|
||
fn test_split_helper( | ||
transferred: u64, | ||
total: u64, | ||
initial_balance: u64, | ||
expected_remaining: u64, | ||
expected_transferred: u64, | ||
) { | ||
let schedule = PeriodicVesting { | ||
initial_balance, | ||
// all of these fields should be preserved in the result | ||
start_date: 203, | ||
period_duration: 100, | ||
num_periods: 12, | ||
}; | ||
let (remaining_schedule, transferred_schedule) = schedule | ||
.split_vesting_schedule(total - transferred, transferred, total) | ||
.unwrap(); | ||
|
||
let t = PeriodicVesting { | ||
initial_balance: expected_transferred, | ||
start_date: 203, | ||
period_duration: 100, | ||
num_periods: 12, | ||
}; | ||
let r = PeriodicVesting { | ||
initial_balance: expected_remaining, | ||
start_date: 203, | ||
period_duration: 100, | ||
num_periods: 12, | ||
}; | ||
|
||
assert_eq!(remaining_schedule, r); | ||
assert_eq!(transferred_schedule, t); | ||
|
||
let schedule = PeriodicVestingAfterListing { | ||
initial_balance, | ||
period_duration: 100, | ||
num_periods: 12, | ||
}; | ||
let (remaining_schedule, transferred_schedule) = schedule | ||
.split_vesting_schedule(total - transferred, transferred, total) | ||
.unwrap(); | ||
|
||
let t = PeriodicVestingAfterListing { | ||
initial_balance: expected_transferred, | ||
period_duration: 100, | ||
num_periods: 12, | ||
}; | ||
let r = PeriodicVestingAfterListing { | ||
initial_balance: expected_remaining, | ||
period_duration: 100, | ||
num_periods: 12, | ||
}; | ||
|
||
assert_eq!(remaining_schedule, r); | ||
assert_eq!(transferred_schedule, t); | ||
} | ||
|
||
#[test] | ||
fn test_split() { | ||
test_split_helper(10, 100, 100, 90, 10); | ||
test_split_helper(10, 1000, 100, 99, 1); | ||
test_split_helper(1, 1000, 100, 99, 0); | ||
|
||
test_split_helper(10, 10, 1000, 0, 1000); | ||
test_split_helper(9, 10, 1000, 100, 900); | ||
test_split_helper(10, 100, 1000, 900, 100); | ||
|
||
test_split_helper(1, 3, 1000, 666, 333); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd just move Other to be the last error