Skip to content
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

refactor: more accurate return type of get_candidate_to_xxx functions #140

Merged
merged 3 commits into from
Jan 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions contracts/linear/src/epoch_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,17 @@ impl LiquidStakingContract {
return false;
}

let (candidate, amount_to_stake) = self
let candidate = self
.validator_pool
.get_candidate_to_stake(self.stake_amount_to_settle, self.total_staked_near_amount);

if candidate.is_none() {
log!("no candidate found to stake {}", amount_to_stake);
log!("no candidate found to stake");
return false;
}

let mut candidate = candidate.unwrap();
let amount_to_stake = candidate.amount;

if amount_to_stake < MIN_AMOUNT_TO_PERFORM_STAKE {
log!("stake amount too low: {}", amount_to_stake);
Expand All @@ -53,15 +55,15 @@ impl LiquidStakingContract {
self.stake_amount_to_settle -= amount_to_stake;

Event::EpochStakeAttempt {
validator_id: &candidate.account_id,
validator_id: &candidate.validator.account_id,
amount: &U128(amount_to_stake),
}
.emit();

// do staking on selected validator
candidate.deposit_and_stake(amount_to_stake).then(
candidate.validator.deposit_and_stake(amount_to_stake).then(
ext_self_action_cb::validator_staked_callback(
candidate.account_id.clone(),
candidate.validator.account_id.clone(),
amount_to_stake.into(),
env::current_account_id(),
NO_DEPOSIT,
Expand All @@ -88,14 +90,15 @@ impl LiquidStakingContract {
return false;
}

let (candidate, amount_to_unstake) = self
let candidate = self
.validator_pool
.get_candidate_to_unstake(self.unstake_amount_to_settle, self.total_staked_near_amount);
if candidate.is_none() {
log!("no candidate found to unstake {}", amount_to_unstake);
log!("no candidate found to unstake");
return false;
}
let mut candidate = candidate.unwrap();
let amount_to_unstake = candidate.amount;

if amount_to_unstake < MIN_AMOUNT_TO_PERFORM_UNSTAKE {
log!("unstake amount too low: {}", amount_to_unstake);
Expand All @@ -106,16 +109,17 @@ impl LiquidStakingContract {
self.unstake_amount_to_settle -= amount_to_unstake;

Event::EpochUnstakeAttempt {
validator_id: &candidate.account_id,
validator_id: &candidate.validator.account_id,
amount: &U128(amount_to_unstake),
}
.emit();

// do unstaking on selected validator
candidate
.validator
.unstake(&mut self.validator_pool, amount_to_unstake)
.then(ext_self_action_cb::validator_unstaked_callback(
candidate.account_id,
candidate.validator.account_id,
amount_to_unstake.into(),
env::current_account_id(),
NO_DEPOSIT,
Expand Down
135 changes: 73 additions & 62 deletions contracts/linear/src/validator_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ pub struct ValidatorPool {
pub total_base_stake_amount: Balance,
}

pub struct CandidateValidator {
pub validator: Validator,
pub amount: u128,
}

impl Default for ValidatorPool {
fn default() -> Self {
Self::new()
Expand Down Expand Up @@ -195,7 +200,7 @@ impl ValidatorPool {
&self,
amount: Balance,
total_staked_near_amount: Balance,
) -> (Option<Validator>, Balance) {
) -> Option<CandidateValidator> {
let mut candidate = None;
let mut amount_to_stake: Balance = 0;

Expand All @@ -216,15 +221,17 @@ impl ValidatorPool {
amount_to_stake = amount;
}

// Note that it's possible that no validator is available
(candidate, amount_to_stake)
candidate.map(|candidate| CandidateValidator {
validator: candidate,
amount: amount_to_stake,
})
}

pub fn get_candidate_to_unstake(
&self,
amount: Balance,
total_staked_near_amount: Balance,
) -> (Option<Validator>, Balance) {
) -> Option<CandidateValidator> {
let mut candidate = None;
let mut amount_to_unstake: Balance = 0;

Expand Down Expand Up @@ -257,7 +264,10 @@ impl ValidatorPool {
amount_to_unstake = min(amount, candidate.as_ref().unwrap().staked_amount);
}

(candidate, amount_to_unstake)
candidate.map(|candidate| CandidateValidator {
validator: candidate,
amount: amount_to_unstake,
})
}

/// **formula: target stake amount = base stake amount + dynamic stake amount.**
Expand Down Expand Up @@ -902,11 +912,11 @@ mod tests {
// we have currently 600 in total, 500 already staked, 100 to stake,
// each weight point should be 150, thus zoo is the most unbalanced one.

let (candidate, amount) =
validator_pool.get_candidate_to_stake(100 * ONE_NEAR, 600 * ONE_NEAR);
let candidate = validator_pool.get_candidate_to_stake(100 * ONE_NEAR, 600 * ONE_NEAR);
assert!(candidate.is_some());
assert_eq!(candidate.unwrap().account_id, zoo.account_id);
assert_eq!(amount, 100 * ONE_NEAR);
let candidate = candidate.unwrap();
assert_eq!(candidate.validator.account_id, zoo.account_id);
assert_eq!(candidate.amount, 100 * ONE_NEAR);

// reset staked amount
foo.staked_amount = 0; // target is 150
Expand All @@ -925,11 +935,11 @@ mod tests {
// we have currently 600 in total, 500 already staked, 100 to stake,
// each weight point should be 150, thus zoo is the most unbalanced one.

let (candidate, amount) =
validator_pool.get_candidate_to_stake(100 * ONE_NEAR, 600 * ONE_NEAR);
let candidate = validator_pool.get_candidate_to_stake(100 * ONE_NEAR, 600 * ONE_NEAR);
assert!(candidate.is_some());
assert_eq!(candidate.unwrap().account_id, foo.account_id);
assert_eq!(amount, 100 * ONE_NEAR);
let candidate = candidate.unwrap();
assert_eq!(candidate.validator.account_id, foo.account_id);
assert_eq!(candidate.amount, 100 * ONE_NEAR);

// reset staked amount
foo.staked_amount = 200 * ONE_NEAR; // target is 150
Expand All @@ -947,8 +957,9 @@ mod tests {

// in case no staking is needed

let (candidate, _) = validator_pool.get_candidate_to_stake(100 * ONE_NEAR, 600 * ONE_NEAR);
assert!(candidate.is_none());
assert!(validator_pool
.get_candidate_to_stake(100 * ONE_NEAR, 600 * ONE_NEAR)
.is_none());
}

#[test]
Expand Down Expand Up @@ -984,11 +995,11 @@ mod tests {
// we have currently 1000 in total, 550 already staked, 250 to stake,
// each weight point should be 200, thus foo is the most unbalanced one.

let (candidate, amount) =
validator_pool.get_candidate_to_stake(250 * ONE_NEAR, 1000 * ONE_NEAR);
let candidate = validator_pool.get_candidate_to_stake(250 * ONE_NEAR, 1000 * ONE_NEAR);
assert!(candidate.is_some());
assert_eq!(candidate.unwrap().account_id, foo.account_id);
assert_eq!(amount, 250 * ONE_NEAR);
let candidate = candidate.unwrap();
assert_eq!(candidate.validator.account_id, foo.account_id);
assert_eq!(candidate.amount, 250 * ONE_NEAR);

// reset staked amount
foo.staked_amount = 0; // target is 350
Expand All @@ -1007,11 +1018,11 @@ mod tests {
// we have currently 800 in total, 500 already staked, 200 to stake,
// each weight point should be 150, thus foo is the most unbalanced one.

let (candidate, amount) =
validator_pool.get_candidate_to_stake(200 * ONE_NEAR, 800 * ONE_NEAR);
let candidate = validator_pool.get_candidate_to_stake(200 * ONE_NEAR, 800 * ONE_NEAR);
assert!(candidate.is_some());
assert_eq!(candidate.unwrap().account_id, foo.account_id);
assert_eq!(amount, 200 * ONE_NEAR);
let candidate = candidate.unwrap();
assert_eq!(candidate.validator.account_id, foo.account_id);
assert_eq!(candidate.amount, 200 * ONE_NEAR);

// reset staked amount
foo.staked_amount = 500 * ONE_NEAR; // target is 400
Expand All @@ -1029,8 +1040,9 @@ mod tests {

// in case no staking is needed

let (candidate, _) = validator_pool.get_candidate_to_stake(100 * ONE_NEAR, 1000 * ONE_NEAR);
assert!(candidate.is_none());
assert!(validator_pool
.get_candidate_to_stake(100 * ONE_NEAR, 1000 * ONE_NEAR)
.is_none());

// 2. total stake amount < total base stake amount

Expand All @@ -1052,11 +1064,11 @@ mod tests {
// the total stake amount is less than total base stake amount, satisfay base stake amount first.
// thus foo is the most unbalanced one.

let (candidate, amount) =
validator_pool.get_candidate_to_stake(50 * ONE_NEAR, 100 * ONE_NEAR);
let candidate = validator_pool.get_candidate_to_stake(50 * ONE_NEAR, 100 * ONE_NEAR);
assert!(candidate.is_some());
assert_eq!(candidate.unwrap().account_id, foo.account_id);
assert_eq!(amount, 50 * ONE_NEAR);
let candidate = candidate.unwrap();
assert_eq!(candidate.validator.account_id, foo.account_id);
assert_eq!(candidate.amount, 50 * ONE_NEAR);

// set bar's base stake amount to 100
validator_pool.update_base_stake_amount(&bar.account_id, 100 * ONE_NEAR);
Expand All @@ -1082,11 +1094,11 @@ mod tests {
// the total stake amount is less than total base stake amount, satisfay base stake amount first.
// thus bar is the most unbalanced one.

let (candidate, amount) =
validator_pool.get_candidate_to_stake(50 * ONE_NEAR, 150 * ONE_NEAR);
let candidate = validator_pool.get_candidate_to_stake(50 * ONE_NEAR, 150 * ONE_NEAR);
assert!(candidate.is_some());
assert_eq!(candidate.unwrap().account_id, bar.account_id);
assert_eq!(amount, 30 * ONE_NEAR);
let candidate = candidate.unwrap();
assert_eq!(candidate.validator.account_id, bar.account_id);
assert_eq!(candidate.amount, 30 * ONE_NEAR);
}

#[test]
Expand Down Expand Up @@ -1114,11 +1126,11 @@ mod tests {
// we have currently 510 already staked, 110 to unstake, target total 400,
// each weight point should be 100, thus zoo is the most unbalanced one.

let (candidate, amount) =
validator_pool.get_candidate_to_unstake(110 * ONE_NEAR, 400 * ONE_NEAR);
let candidate = validator_pool.get_candidate_to_unstake(110 * ONE_NEAR, 400 * ONE_NEAR);
assert!(candidate.is_some());
assert_eq!(candidate.unwrap().account_id, zoo.account_id);
assert_eq!(amount, 20 * ONE_NEAR);
let candidate = candidate.unwrap();
assert_eq!(candidate.validator.account_id, zoo.account_id);
assert_eq!(candidate.amount, 20 * ONE_NEAR);

// reset staked amount
foo.staked_amount = 100; // target is 100
Expand All @@ -1137,11 +1149,11 @@ mod tests {
// we have currently 500 already staked, 100 to unstake, target total 400,
// each weight point should be 100, thus bar is the most unbalanced one.

let (candidate, amount) =
validator_pool.get_candidate_to_unstake(100 * ONE_NEAR, 400 * ONE_NEAR);
let candidate = validator_pool.get_candidate_to_unstake(100 * ONE_NEAR, 400 * ONE_NEAR);
assert!(candidate.is_some());
assert_eq!(candidate.unwrap().account_id, bar.account_id);
assert_eq!(amount, 100 * ONE_NEAR);
let candidate = candidate.unwrap();
assert_eq!(candidate.validator.account_id, bar.account_id);
assert_eq!(candidate.amount, 100 * ONE_NEAR);

// reset staked amount
foo.staked_amount = 100;
Expand All @@ -1159,8 +1171,7 @@ mod tests {

// in case no unstaking is needed

let (candidate, _) = validator_pool.get_candidate_to_unstake(100, 400);
assert!(candidate.is_none());
assert!(validator_pool.get_candidate_to_unstake(100, 400).is_none());
}

#[test]
Expand Down Expand Up @@ -1196,11 +1207,11 @@ mod tests {
// we have currently 510 already staked, 110 to unstake, target total 400,
// each weight point should be 50, thus zoo is the most unbalanced one.

let (candidate, amount) =
validator_pool.get_candidate_to_unstake(110 * ONE_NEAR, 400 * ONE_NEAR);
let candidate = validator_pool.get_candidate_to_unstake(110 * ONE_NEAR, 400 * ONE_NEAR);
assert!(candidate.is_some());
assert_eq!(candidate.unwrap().account_id, zoo.account_id);
assert_eq!(amount, 110 * ONE_NEAR);
let candidate = candidate.unwrap();
assert_eq!(candidate.validator.account_id, zoo.account_id);
assert_eq!(candidate.amount, 110 * ONE_NEAR);

// reset staked amount
foo.staked_amount = 100 * ONE_NEAR; // target is 250
Expand All @@ -1219,11 +1230,11 @@ mod tests {
// we have currently 500 already staked, 100 to unstake, target total 400,
// each weight point should be 50, thus bar is the most unbalanced one.

let (candidate, amount) =
validator_pool.get_candidate_to_unstake(100 * ONE_NEAR, 400 * ONE_NEAR);
let candidate = validator_pool.get_candidate_to_unstake(100 * ONE_NEAR, 400 * ONE_NEAR);
assert!(candidate.is_some());
assert_eq!(candidate.unwrap().account_id, bar.account_id);
assert_eq!(amount, 100 * ONE_NEAR);
let candidate = candidate.unwrap();
assert_eq!(candidate.validator.account_id, bar.account_id);
assert_eq!(candidate.amount, 100 * ONE_NEAR);

// reset staked amount
foo.staked_amount = 100 * ONE_NEAR;
Expand All @@ -1241,9 +1252,9 @@ mod tests {

// in case no unstaking is needed

let (candidate, _) =
validator_pool.get_candidate_to_unstake(100 * ONE_NEAR, 400 * ONE_NEAR);
assert!(candidate.is_none());
assert!(validator_pool
.get_candidate_to_unstake(100 * ONE_NEAR, 400 * ONE_NEAR)
.is_none());

// 2. total stake amount < total base stake amount

Expand All @@ -1265,11 +1276,11 @@ mod tests {
// the total stake amount is less than total base stake amount, satisfay base stake amount first,
// thus zoo is the most unbalanced one.

let (candidate, amount) =
validator_pool.get_candidate_to_unstake(200 * ONE_NEAR, 200 * ONE_NEAR);
let candidate = validator_pool.get_candidate_to_unstake(200 * ONE_NEAR, 200 * ONE_NEAR);
assert!(candidate.is_some());
assert_eq!(candidate.unwrap().account_id, zoo.account_id);
assert_eq!(amount, 200 * ONE_NEAR);
let candidate = candidate.unwrap();
assert_eq!(candidate.validator.account_id, zoo.account_id);
assert_eq!(candidate.amount, 200 * ONE_NEAR);

// set bar's base stake amount to 100
validator_pool.update_base_stake_amount(&bar.account_id, 100 * ONE_NEAR);
Expand All @@ -1295,11 +1306,11 @@ mod tests {
// the total stake amount is less than total base stake amount, satisfay base stake amount first,
// thus bar is the most unbalanced one.

let (candidate, amount) =
validator_pool.get_candidate_to_unstake(150 * ONE_NEAR, 150 * ONE_NEAR);
let candidate = validator_pool.get_candidate_to_unstake(150 * ONE_NEAR, 150 * ONE_NEAR);
assert!(candidate.is_some());
let candidate = candidate.unwrap();
// to avoid blocking unstake, more amount than the delta (100 NEAR) will be unstaked.
assert_eq!(amount, 150 * ONE_NEAR);
assert_eq!(candidate.unwrap().account_id, bar.account_id);
assert_eq!(candidate.amount, 150 * ONE_NEAR);
assert_eq!(candidate.validator.account_id, bar.account_id);
}
}