From 187b8304fd5dd0d1e3fdec09b9762bc2c1fe9389 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Mon, 3 Apr 2023 15:33:40 +0800 Subject: [PATCH 01/55] test: `stake_to_validator` and `unstake_from_validator` --- contracts/linear/src/epoch_actions.rs | 69 +++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/contracts/linear/src/epoch_actions.rs b/contracts/linear/src/epoch_actions.rs index b5a6f1e7..05fe8c7b 100644 --- a/contracts/linear/src/epoch_actions.rs +++ b/contracts/linear/src/epoch_actions.rs @@ -13,6 +13,75 @@ const MIN_AMOUNT_TO_PERFORM_UNSTAKE: Balance = ONE_NEAR; /// during each epoch. #[near_bindgen] impl LiquidStakingContract { + #[cfg(feature = "test")] + pub fn stake_to_validator(&mut self, validator_id: AccountId) { + self.assert_running(); + // make sure enough gas was given + let min_gas = GAS_EPOCH_STAKE + GAS_EXT_DEPOSIT_AND_STAKE + GAS_CB_VALIDATOR_STAKED; + + require!( + env::prepaid_gas() >= min_gas, + format!("{}. require at least {:?}", ERR_NO_ENOUGH_GAS, min_gas) + ); + + let mut validator = self + .validator_pool + .get_validator(&validator_id) + .expect(ERR_VALIDATOR_NOT_EXIST); + + let amount = env::attached_deposit(); + + Event::EpochStakeAttempt { + validator_id: &validator_id, + amount: &U128(amount), + } + .emit(); + + // do staking on selected validator + validator + .deposit_and_stake(amount) + .then(ext_self_action_cb::validator_staked_callback( + validator.account_id.clone(), + amount.into(), + env::current_account_id(), + NO_DEPOSIT, + GAS_CB_VALIDATOR_STAKED, + )); + } + + #[cfg(feature = "test")] + pub fn unstake_from_validator(&mut self, validator_id: AccountId, amount: U128) { + self.assert_running(); + // make sure enough gas was given + let min_gas = GAS_EPOCH_UNSTAKE + GAS_EXT_UNSTAKE + GAS_CB_VALIDATOR_UNSTAKED; + require!( + env::prepaid_gas() >= min_gas, + format!("{}. require at least {:?}", ERR_NO_ENOUGH_GAS, min_gas) + ); + + let mut validator = self + .validator_pool + .get_validator(&validator_id) + .expect(ERR_VALIDATOR_NOT_EXIST); + + Event::EpochUnstakeAttempt { + validator_id: &validator_id, + amount: &amount, + } + .emit(); + + // do staking on selected validator + validator + .unstake(&mut self.validator_pool, amount.into()) + .then(ext_self_action_cb::validator_unstaked_callback( + validator.account_id.clone(), + amount.into(), + env::current_account_id(), + NO_DEPOSIT, + GAS_CB_VALIDATOR_STAKED, + )); + } + pub fn epoch_stake(&mut self) -> bool { self.assert_running(); // make sure enough gas was given From f63d0cf92ec8ccb020be7019c561eae1d8ae4f69 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Mon, 3 Apr 2023 15:43:03 +0800 Subject: [PATCH 02/55] stake_to_validator payable --- contracts/linear/src/epoch_actions.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/linear/src/epoch_actions.rs b/contracts/linear/src/epoch_actions.rs index 05fe8c7b..22d8eaf1 100644 --- a/contracts/linear/src/epoch_actions.rs +++ b/contracts/linear/src/epoch_actions.rs @@ -13,6 +13,7 @@ const MIN_AMOUNT_TO_PERFORM_UNSTAKE: Balance = ONE_NEAR; /// during each epoch. #[near_bindgen] impl LiquidStakingContract { + #[payable] #[cfg(feature = "test")] pub fn stake_to_validator(&mut self, validator_id: AccountId) { self.assert_running(); From 1ac43018e904142c95bec4b994efabe14c937765 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Mon, 10 Apr 2023 20:59:29 +0800 Subject: [PATCH 03/55] start from the user side before stake and unstake --- contracts/linear/src/epoch_actions.rs | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/contracts/linear/src/epoch_actions.rs b/contracts/linear/src/epoch_actions.rs index 22d8eaf1..7dcd2b1d 100644 --- a/contracts/linear/src/epoch_actions.rs +++ b/contracts/linear/src/epoch_actions.rs @@ -13,9 +13,15 @@ const MIN_AMOUNT_TO_PERFORM_UNSTAKE: Balance = ONE_NEAR; /// during each epoch. #[near_bindgen] impl LiquidStakingContract { + #[cfg(feature = "test")] + pub fn assert_zero_requested(&self) { + require!(self.epoch_requested_stake_amount == 0); + require!(self.epoch_requested_unstake_amount == 0); + } + #[payable] #[cfg(feature = "test")] - pub fn stake_to_validator(&mut self, validator_id: AccountId) { + pub fn stake_to_validator(&mut self, validator_id: AccountId, amount: U128) { self.assert_running(); // make sure enough gas was given let min_gas = GAS_EPOCH_STAKE + GAS_EXT_DEPOSIT_AND_STAKE + GAS_CB_VALIDATOR_STAKED; @@ -30,24 +36,24 @@ impl LiquidStakingContract { .get_validator(&validator_id) .expect(ERR_VALIDATOR_NOT_EXIST); - let amount = env::attached_deposit(); - Event::EpochStakeAttempt { validator_id: &validator_id, - amount: &U128(amount), + amount: &amount, } .emit(); + self.epoch_requested_stake_amount -= amount.0; + // do staking on selected validator - validator - .deposit_and_stake(amount) - .then(ext_self_action_cb::validator_staked_callback( + validator.deposit_and_stake(amount.into()).then( + ext_self_action_cb::validator_staked_callback( validator.account_id.clone(), amount.into(), env::current_account_id(), NO_DEPOSIT, GAS_CB_VALIDATOR_STAKED, - )); + ), + ); } #[cfg(feature = "test")] @@ -71,6 +77,8 @@ impl LiquidStakingContract { } .emit(); + self.epoch_requested_unstake_amount -= amount.0; + // do staking on selected validator validator .unstake(&mut self.validator_pool, amount.into()) From 7fa10473f631dcc4c6ab657cacfce37e7151e337 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Fri, 5 May 2023 08:45:56 +0000 Subject: [PATCH 04/55] add a new test interface: set_pending_release --- contracts/linear/src/validator_pool.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/contracts/linear/src/validator_pool.rs b/contracts/linear/src/validator_pool.rs index 4763baea..f5f29c1a 100644 --- a/contracts/linear/src/validator_pool.rs +++ b/contracts/linear/src/validator_pool.rs @@ -160,6 +160,17 @@ impl ValidatorPool { old_weight } + #[cfg(feature = "test")] + pub fn set_pending_release(&mut self, validator_id: &AccountId) { + let mut validator: Validator = self + .validators + .get(validator_id) + .expect(ERR_VALIDATOR_NOT_EXIST) + .into(); + validator.unstake_fired_epoch = get_epoch_height(); + self.validators.insert(validator_id, &validator.into()); + } + /// Update base stake amount of the validator pub fn update_base_stake_amount(&mut self, validator_id: &AccountId, amount: Balance) { let mut validator: Validator = self From 57c1a717d51804067f558b7ce01cc1554a30ba5a Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Fri, 5 May 2023 08:51:21 +0000 Subject: [PATCH 05/55] set_pending_releases --- contracts/linear/src/validator_pool.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/contracts/linear/src/validator_pool.rs b/contracts/linear/src/validator_pool.rs index f5f29c1a..1bf1ac1a 100644 --- a/contracts/linear/src/validator_pool.rs +++ b/contracts/linear/src/validator_pool.rs @@ -477,6 +477,16 @@ impl LiquidStakingContract { } } + #[cfg(feature = "test")] + pub fn set_pending_releases(&mut self, validator_ids: Vec) { + self.assert_running(); + self.assert_manager(); + for i in 0..validator_ids.len() { + self.validator_pool + .set_pending_release(&validator_ids[i]); + } + } + // --- View Functions --- #[cfg(feature = "test")] From fc3d4867c01313dbaa3155a4afbd643aa397fc82 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Fri, 5 May 2023 09:00:16 +0000 Subject: [PATCH 06/55] cargo fmt --- contracts/linear/src/validator_pool.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/contracts/linear/src/validator_pool.rs b/contracts/linear/src/validator_pool.rs index 1bf1ac1a..4199a691 100644 --- a/contracts/linear/src/validator_pool.rs +++ b/contracts/linear/src/validator_pool.rs @@ -482,10 +482,9 @@ impl LiquidStakingContract { self.assert_running(); self.assert_manager(); for i in 0..validator_ids.len() { - self.validator_pool - .set_pending_release(&validator_ids[i]); + self.validator_pool.set_pending_release(&validator_ids[i]); } - } + } // --- View Functions --- From 1dba26a7c7753ae887910283f9a599785721180f Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Sat, 6 May 2023 11:09:02 +0000 Subject: [PATCH 07/55] set_total_staked_near_amount --- contracts/linear/src/validator_pool.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/contracts/linear/src/validator_pool.rs b/contracts/linear/src/validator_pool.rs index 4199a691..464d8057 100644 --- a/contracts/linear/src/validator_pool.rs +++ b/contracts/linear/src/validator_pool.rs @@ -486,6 +486,11 @@ impl LiquidStakingContract { } } + #[cfg(feature = "test")] + pub fn set_total_staked_near_amount(&mut self, amount: Balance) { + self.total_staked_near_amount = amount; + } + // --- View Functions --- #[cfg(feature = "test")] From 05bd45113fe353ff6d1b79f06b170d0db0623593 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Sat, 6 May 2023 15:43:46 +0000 Subject: [PATCH 08/55] use U128 --- contracts/linear/src/validator_pool.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/linear/src/validator_pool.rs b/contracts/linear/src/validator_pool.rs index 464d8057..e03188e2 100644 --- a/contracts/linear/src/validator_pool.rs +++ b/contracts/linear/src/validator_pool.rs @@ -487,8 +487,8 @@ impl LiquidStakingContract { } #[cfg(feature = "test")] - pub fn set_total_staked_near_amount(&mut self, amount: Balance) { - self.total_staked_near_amount = amount; + pub fn set_total_staked_near_amount(&mut self, amount: U128) { + self.total_staked_near_amount = amount.0; } // --- View Functions --- From e77a5a5c4056245c3ab467a8bea58b77b1250185 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Mon, 8 May 2023 14:40:58 +0000 Subject: [PATCH 09/55] include epoch request amounts in summary --- contracts/linear/src/view.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contracts/linear/src/view.rs b/contracts/linear/src/view.rs index 8e246a81..f911df6c 100644 --- a/contracts/linear/src/view.rs +++ b/contracts/linear/src/view.rs @@ -44,6 +44,8 @@ pub struct Summary { pub stake_amount_to_settle: U128, /// Amount of NEAR that needs to be settled by unstaking from validators pub unstake_amount_to_settle: U128, + pub epoch_requested_stake_amount: U128, + pub epoch_requested_unstake_amount: U128, /// Total base stake amount of NEAR on validators pub validators_total_base_stake_amount: U128, } @@ -106,6 +108,8 @@ impl LiquidStakingContract { farms: self.get_active_farms(), stake_amount_to_settle: self.stake_amount_to_settle.into(), unstake_amount_to_settle: self.unstake_amount_to_settle.into(), + epoch_requested_stake_amount: self.epoch_requested_stake_amount.into(), + epoch_requested_unstake_amount: self.epoch_requested_unstake_amount.into(), validators_total_base_stake_amount: self.validator_pool.total_base_stake_amount.into(), } } From 256a77326940180cc46a5742eb46706ebcf7437b Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Mon, 8 May 2023 14:43:00 +0000 Subject: [PATCH 10/55] make `epoch_cleanup` public --- contracts/linear/src/epoch_actions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/linear/src/epoch_actions.rs b/contracts/linear/src/epoch_actions.rs index 7dcd2b1d..050caf57 100644 --- a/contracts/linear/src/epoch_actions.rs +++ b/contracts/linear/src/epoch_actions.rs @@ -273,7 +273,7 @@ impl LiquidStakingContract { /// Cleaning up stake requirements and unstake requirements, /// since some stake requirements could be eliminated if /// there are more unstake requirements, and vice versa. - fn epoch_cleanup(&mut self) { + pub fn epoch_cleanup(&mut self) { if self.last_settlement_epoch == get_epoch_height() { return; } From 3e8cb7d3e97c9ab5b69dd62cf30e43c3b146c032 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Mon, 8 May 2023 16:11:13 +0000 Subject: [PATCH 11/55] set_draining --- contracts/linear/src/validator_pool.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/contracts/linear/src/validator_pool.rs b/contracts/linear/src/validator_pool.rs index e03188e2..b8aff67f 100644 --- a/contracts/linear/src/validator_pool.rs +++ b/contracts/linear/src/validator_pool.rs @@ -171,6 +171,16 @@ impl ValidatorPool { self.validators.insert(validator_id, &validator.into()); } + #[cfg(feature = "test")] + pub fn set_draining(&mut self, validator_id: &AccountId) { + let mut validator: Validator = self + .validators + .get(validator_id) + .expect(ERR_VALIDATOR_NOT_EXIST) + .into(); + validator.set_draining(self, true); + } + /// Update base stake amount of the validator pub fn update_base_stake_amount(&mut self, validator_id: &AccountId, amount: Balance) { let mut validator: Validator = self @@ -486,6 +496,13 @@ impl LiquidStakingContract { } } + #[cfg(feature = "test")] + pub fn set_drainings(&mut self, validator_ids: Vec) { + for i in 0..validator_ids.len() { + self.validator_pool.set_draining(&validator_ids[i]); + } + } + #[cfg(feature = "test")] pub fn set_total_staked_near_amount(&mut self, amount: U128) { self.total_staked_near_amount = amount.0; From e05739db212aa0e610c0d656dd1a2506e83cfb54 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Wed, 17 May 2023 10:24:23 +0000 Subject: [PATCH 12/55] replace set_pending_release with set_unstake_fired_epoch --- contracts/linear/src/validator_pool.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/contracts/linear/src/validator_pool.rs b/contracts/linear/src/validator_pool.rs index b8aff67f..c347f351 100644 --- a/contracts/linear/src/validator_pool.rs +++ b/contracts/linear/src/validator_pool.rs @@ -10,6 +10,7 @@ use near_sdk::{ collections::UnorderedMap, ext_contract, is_promise_success, json_types::U128, + json_types::U64, log, near_bindgen, require, AccountId, Balance, EpochHeight, Promise, }; use std::cmp::min; @@ -161,13 +162,13 @@ impl ValidatorPool { } #[cfg(feature = "test")] - pub fn set_pending_release(&mut self, validator_id: &AccountId) { + pub fn set_unstake_fired_epoch(&mut self, validator_id: &AccountId, epoch_height: EpochHeight) { let mut validator: Validator = self .validators .get(validator_id) .expect(ERR_VALIDATOR_NOT_EXIST) .into(); - validator.unstake_fired_epoch = get_epoch_height(); + validator.unstake_fired_epoch = epoch_height; self.validators.insert(validator_id, &validator.into()); } @@ -488,11 +489,16 @@ impl LiquidStakingContract { } #[cfg(feature = "test")] - pub fn set_pending_releases(&mut self, validator_ids: Vec) { + pub fn batch_set_unstake_fired_epoch( + &mut self, + validator_ids: Vec, + epoch_heights: Vec, + ) { self.assert_running(); self.assert_manager(); for i in 0..validator_ids.len() { - self.validator_pool.set_pending_release(&validator_ids[i]); + self.validator_pool + .set_unstake_fired_epoch(&validator_ids[i], epoch_heights[i].into()); } } From 984797d7ad95a7c434474a16bdbda2a978765135 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Wed, 14 Jun 2023 06:50:47 +0000 Subject: [PATCH 13/55] fix compile --- contracts/linear/src/validator_pool.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/linear/src/validator_pool.rs b/contracts/linear/src/validator_pool.rs index 9694a204..7df8301d 100644 --- a/contracts/linear/src/validator_pool.rs +++ b/contracts/linear/src/validator_pool.rs @@ -10,6 +10,7 @@ use near_sdk::{ collections::UnorderedMap, ext_contract, is_promise_success, json_types::U128, + json_types::U64, near_bindgen, require, AccountId, Balance, EpochHeight, Promise, }; use std::cmp::min; From d8b5f7ba787881e2dd54c282c53a11de7d241099 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Sat, 17 Jun 2023 03:48:33 +0000 Subject: [PATCH 14/55] stake from the validator with max delta --- contracts/linear/src/validator_pool.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/contracts/linear/src/validator_pool.rs b/contracts/linear/src/validator_pool.rs index 7df8301d..a5c57d12 100644 --- a/contracts/linear/src/validator_pool.rs +++ b/contracts/linear/src/validator_pool.rs @@ -219,21 +219,23 @@ impl ValidatorPool { total_staked_near_amount: Balance, ) -> Option { let mut candidate = None; - let mut amount_to_stake: Balance = 0; + let mut max_delta: Balance = 0; for (_, validator) in self.validators.iter() { let validator = validator.into(); let target_amount = self.validator_target_stake_amount(total_staked_near_amount, &validator); if validator.staked_amount < target_amount { - let delta = min(target_amount - validator.staked_amount, amount); - if delta > amount_to_stake { - amount_to_stake = delta; + let delta = target_amount - validator.staked_amount; + if delta > max_delta { + max_delta = delta; candidate = Some(validator); } } } + let mut amount_to_stake: Balance = min(amount, max_delta); + if amount_to_stake > 0 && amount - amount_to_stake < STAKE_SMALL_CHANGE_AMOUNT { amount_to_stake = amount; } From 63da4457b8ecac2f0de430b5dd1804aea31fb798 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Mon, 3 Jul 2023 06:43:21 +0000 Subject: [PATCH 15/55] `assert_zero_requested` is useless --- contracts/linear/src/epoch_actions.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/contracts/linear/src/epoch_actions.rs b/contracts/linear/src/epoch_actions.rs index 050caf57..200de97f 100644 --- a/contracts/linear/src/epoch_actions.rs +++ b/contracts/linear/src/epoch_actions.rs @@ -13,12 +13,6 @@ const MIN_AMOUNT_TO_PERFORM_UNSTAKE: Balance = ONE_NEAR; /// during each epoch. #[near_bindgen] impl LiquidStakingContract { - #[cfg(feature = "test")] - pub fn assert_zero_requested(&self) { - require!(self.epoch_requested_stake_amount == 0); - require!(self.epoch_requested_unstake_amount == 0); - } - #[payable] #[cfg(feature = "test")] pub fn stake_to_validator(&mut self, validator_id: AccountId, amount: U128) { From e5353fafeba878158752533869914987c6993697 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Mon, 3 Jul 2023 06:52:02 +0000 Subject: [PATCH 16/55] add `epoch_cleanup_for_test` --- contracts/linear/src/epoch_actions.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/contracts/linear/src/epoch_actions.rs b/contracts/linear/src/epoch_actions.rs index 200de97f..c325e787 100644 --- a/contracts/linear/src/epoch_actions.rs +++ b/contracts/linear/src/epoch_actions.rs @@ -13,6 +13,9 @@ const MIN_AMOUNT_TO_PERFORM_UNSTAKE: Balance = ONE_NEAR; /// during each epoch. #[near_bindgen] impl LiquidStakingContract { + // `stake_to_validator` and `unstake_from_validator` are used to mock + // stake amounts and unstake amounts of validators at the beginning + // of simulation tests #[payable] #[cfg(feature = "test")] pub fn stake_to_validator(&mut self, validator_id: AccountId, amount: U128) { @@ -267,7 +270,7 @@ impl LiquidStakingContract { /// Cleaning up stake requirements and unstake requirements, /// since some stake requirements could be eliminated if /// there are more unstake requirements, and vice versa. - pub fn epoch_cleanup(&mut self) { + fn epoch_cleanup(&mut self) { if self.last_settlement_epoch == get_epoch_height() { return; } @@ -294,6 +297,12 @@ impl LiquidStakingContract { .emit(); } + // To mock unsettled amounts at the beginning of simulation tests + #[cfg(feature = "test")] + pub fn epoch_cleanup_for_test(&mut self) { + self.epoch_cleanup(); + } + /// Due to shares calculation and rounding of staking pool contract, /// the amount of staked and unstaked balance might be a little bit /// different than we requested. From 7b2d9370582cce65da2d7fce960f00d329fcf61b Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Mon, 3 Jul 2023 06:57:41 +0000 Subject: [PATCH 17/55] comments --- contracts/linear/src/epoch_actions.rs | 4 ++-- contracts/linear/src/validator_pool.rs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/contracts/linear/src/epoch_actions.rs b/contracts/linear/src/epoch_actions.rs index c325e787..0c86419e 100644 --- a/contracts/linear/src/epoch_actions.rs +++ b/contracts/linear/src/epoch_actions.rs @@ -14,7 +14,7 @@ const MIN_AMOUNT_TO_PERFORM_UNSTAKE: Balance = ONE_NEAR; #[near_bindgen] impl LiquidStakingContract { // `stake_to_validator` and `unstake_from_validator` are used to mock - // stake amounts and unstake amounts of validators at the beginning + // stake amounts and unstake amounts of validators at the beginnings // of simulation tests #[payable] #[cfg(feature = "test")] @@ -297,7 +297,7 @@ impl LiquidStakingContract { .emit(); } - // To mock unsettled amounts at the beginning of simulation tests + // To mock unsettled amounts at the beginnings of simulation tests #[cfg(feature = "test")] pub fn epoch_cleanup_for_test(&mut self) { self.epoch_cleanup(); diff --git a/contracts/linear/src/validator_pool.rs b/contracts/linear/src/validator_pool.rs index 84a06795..f71af1dc 100644 --- a/contracts/linear/src/validator_pool.rs +++ b/contracts/linear/src/validator_pool.rs @@ -161,6 +161,7 @@ impl ValidatorPool { old_weight } + // to mock pending release validators at the beginnings of simulation tests #[cfg(feature = "test")] pub fn set_unstake_fired_epoch(&mut self, validator_id: &AccountId, epoch_height: EpochHeight) { let mut validator: Validator = self @@ -172,6 +173,7 @@ impl ValidatorPool { self.validators.insert(validator_id, &validator.into()); } + // to mock draining validators at the beginnings of simulation tests #[cfg(feature = "test")] pub fn set_draining(&mut self, validator_id: &AccountId) { let mut validator: Validator = self From 5693048666a90fd1cdc8aaa3c8727649d7a7d83f Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Mon, 21 Aug 2023 11:18:48 +0000 Subject: [PATCH 18/55] sync in successful callbacks. --- contracts/linear/src/epoch_actions.rs | 34 +++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/contracts/linear/src/epoch_actions.rs b/contracts/linear/src/epoch_actions.rs index 8c1b8a06..c218ffc7 100644 --- a/contracts/linear/src/epoch_actions.rs +++ b/contracts/linear/src/epoch_actions.rs @@ -17,7 +17,11 @@ impl LiquidStakingContract { pub fn epoch_stake(&mut self) -> bool { self.assert_running(); // make sure enough gas was given - let min_gas = GAS_EPOCH_STAKE + GAS_EXT_DEPOSIT_AND_STAKE + GAS_CB_VALIDATOR_STAKED; + let min_gas = GAS_EPOCH_STAKE + + GAS_EXT_DEPOSIT_AND_STAKE + + GAS_CB_VALIDATOR_STAKED + + GAS_SYNC_BALANCE + + GAS_CB_VALIDATOR_SYNC_BALANCE; require!( env::prepaid_gas() >= min_gas, format!("{}. require at least {:?}", ERR_NO_ENOUGH_GAS, min_gas) @@ -70,7 +74,7 @@ impl LiquidStakingContract { amount_to_stake.into(), env::current_account_id(), NO_DEPOSIT, - GAS_CB_VALIDATOR_STAKED, + GAS_CB_VALIDATOR_STAKED + GAS_SYNC_BALANCE + GAS_CB_VALIDATOR_SYNC_BALANCE, )); true @@ -79,7 +83,11 @@ impl LiquidStakingContract { pub fn epoch_unstake(&mut self) -> bool { self.assert_running(); // make sure enough gas was given - let min_gas = GAS_EPOCH_UNSTAKE + GAS_EXT_UNSTAKE + GAS_CB_VALIDATOR_UNSTAKED; + let min_gas = GAS_EPOCH_UNSTAKE + + GAS_EXT_UNSTAKE + + GAS_CB_VALIDATOR_UNSTAKED + + GAS_SYNC_BALANCE + + GAS_CB_VALIDATOR_SYNC_BALANCE; require!( env::prepaid_gas() >= min_gas, format!("{}. require at least {:?}", ERR_NO_ENOUGH_GAS, min_gas) @@ -126,7 +134,7 @@ impl LiquidStakingContract { amount_to_unstake.into(), env::current_account_id(), NO_DEPOSIT, - GAS_CB_VALIDATOR_UNSTAKED, + GAS_CB_VALIDATOR_UNSTAKED + GAS_SYNC_BALANCE + GAS_CB_VALIDATOR_SYNC_BALANCE, )); true @@ -289,6 +297,15 @@ impl LiquidStakingContract { amount: &U128(amount), } .emit(); + + validator + .sync_account_balance(&mut self.validator_pool) + .then(ext_self_action_cb::validator_get_account_callback( + validator.account_id, + env::current_account_id(), + NO_DEPOSIT, + GAS_CB_VALIDATOR_SYNC_BALANCE, + )); } else { validator.on_stake_failed(&mut self.validator_pool); @@ -319,6 +336,15 @@ impl LiquidStakingContract { amount: &U128(amount), } .emit(); + + validator + .sync_account_balance(&mut self.validator_pool) + .then(ext_self_action_cb::validator_get_account_callback( + validator.account_id, + env::current_account_id(), + NO_DEPOSIT, + GAS_CB_VALIDATOR_SYNC_BALANCE, + )); } else { // unstake failed, revert // 1. revert contract states From 93470f31982016b3de019abdd46e5dd6a7687a88 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Mon, 21 Aug 2023 20:11:59 +0800 Subject: [PATCH 19/55] tests passed --- tests/README.md | 2 +- tests/__tests__/linear/drain.ava.ts | 6 +++--- tests/__tests__/linear/epoch-action-failure.ava.ts | 12 ++++++------ tests/__tests__/linear/epoch-action.ava.ts | 6 +++--- tests/__tests__/linear/staking-pool-interface.ava.ts | 2 +- tests/__tests__/linear/sync_balance.ava.ts | 4 ++-- tests/__tests__/linear/upgrade.ava.ts | 4 ++-- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/README.md b/tests/README.md index 7aadfacb..26576e89 100644 --- a/tests/README.md +++ b/tests/README.md @@ -17,4 +17,4 @@ To run only one test file: To run only one test: npm run test -- -m "root sets*" # matches tests with titles starting with "root sets" - yarn test -m "root sets*" # same thing using yarn instead of npm, see https://yarnpkg.com/ \ No newline at end of file + yarn test -m "root sets*" # same thing using yarn instead of npm, see https://yarnpkg.com/ diff --git a/tests/__tests__/linear/drain.ava.ts b/tests/__tests__/linear/drain.ava.ts index e99c5199..70906273 100644 --- a/tests/__tests__/linear/drain.ava.ts +++ b/tests/__tests__/linear/drain.ava.ts @@ -19,7 +19,7 @@ async function stakeAll (signer: NearAccount, contract: NearAccount) { 'epoch_stake', {}, { - gas: Gas.parse('200 Tgas') + gas: Gas.parse('275 Tgas') } ); } @@ -85,7 +85,7 @@ workspace.test('drain constraints', async (test, {contract, root, owner, alice, 'epoch_stake', {}, { - gas: Gas.parse('200 Tgas') + gas: Gas.parse('275 Tgas') } ); @@ -162,7 +162,7 @@ workspace.test('drain constraints', async (test, {contract, root, owner, alice, 'epoch_unstake', {}, { - gas: Gas.parse('200 Tgas') + gas: Gas.parse('275 Tgas') } ); diff --git a/tests/__tests__/linear/epoch-action-failure.ava.ts b/tests/__tests__/linear/epoch-action-failure.ava.ts index e1cc2293..8c0b8554 100644 --- a/tests/__tests__/linear/epoch-action-failure.ava.ts +++ b/tests/__tests__/linear/epoch-action-failure.ava.ts @@ -71,7 +71,7 @@ workspace.test('epoch stake failure', async (test, { root, contract, owner, alic 'epoch_stake', {}, { - gas: Gas.parse('200 Tgas') + gas: Gas.parse('275 Tgas') } ); @@ -111,7 +111,7 @@ workspace.test('unstake failure', async (test, { root, contract, owner, alice }) 'epoch_stake', {}, { - gas: Gas.parse('200 Tgas') + gas: Gas.parse('275 Tgas') } ); @@ -131,7 +131,7 @@ workspace.test('unstake failure', async (test, { root, contract, owner, alice }) 'epoch_unstake', {}, { - gas: Gas.parse('200 Tgas') + gas: Gas.parse('275 Tgas') } ); @@ -171,7 +171,7 @@ workspace.test('withdraw failure', async (test, { root, contract, owner, alice } 'epoch_stake', {}, { - gas: Gas.parse('200 Tgas') + gas: Gas.parse('275 Tgas') } ); @@ -196,7 +196,7 @@ workspace.test('withdraw failure', async (test, { root, contract, owner, alice } 'epoch_unstake', {}, { - gas: Gas.parse('200 Tgas') + gas: Gas.parse('275 Tgas') } ); @@ -259,7 +259,7 @@ workspace.test('get balance failure', async (test, { root, contract, owner, alic 'epoch_stake', {}, { - gas: Gas.parse('200 Tgas') + gas: Gas.parse('275 Tgas') } ); diff --git a/tests/__tests__/linear/epoch-action.ava.ts b/tests/__tests__/linear/epoch-action.ava.ts index 49db43df..5c65865b 100644 --- a/tests/__tests__/linear/epoch-action.ava.ts +++ b/tests/__tests__/linear/epoch-action.ava.ts @@ -20,7 +20,7 @@ async function stakeAll (owner: NearAccount, contract: NearAccount) { 'epoch_stake', {}, { - gas: Gas.parse('200 Tgas') + gas: Gas.parse('275 Tgas') } ); } @@ -34,7 +34,7 @@ async function unstakeAll (owner: NearAccount, contract: NearAccount) { 'epoch_unstake', {}, { - gas: Gas.parse('200 Tgas') + gas: Gas.parse('275 Tgas') } ); } @@ -774,7 +774,7 @@ skip('estimate gas of epoch unstake', async (test, {contract, alice, root, owner 'epoch_unstake', {}, { - gas: Gas.parse('300 Tgas') + gas: Gas.parse('275 Tgas') } ); diff --git a/tests/__tests__/linear/staking-pool-interface.ava.ts b/tests/__tests__/linear/staking-pool-interface.ava.ts index 4bffd93f..2f97f03d 100644 --- a/tests/__tests__/linear/staking-pool-interface.ava.ts +++ b/tests/__tests__/linear/staking-pool-interface.ava.ts @@ -273,7 +273,7 @@ workspace.test('late unstake and withdraw', async (test, { contract ,alice }) => 'epoch_stake', {}, { - gas: Gas.parse('200 Tgas') + gas: Gas.parse('275 Tgas') } ); diff --git a/tests/__tests__/linear/sync_balance.ava.ts b/tests/__tests__/linear/sync_balance.ava.ts index 9a1e16dc..b430301c 100644 --- a/tests/__tests__/linear/sync_balance.ava.ts +++ b/tests/__tests__/linear/sync_balance.ava.ts @@ -73,7 +73,7 @@ workspace.test('sync balance failure', async (test, { root, contract, alice, own 'epoch_stake', {}, { - gas: Gas.parse('200 Tgas') + gas: Gas.parse('275 Tgas') } ); } @@ -174,7 +174,7 @@ workspace.test('sync balance', async (test, { root, contract, alice, owner }) => 'epoch_stake', {}, { - gas: Gas.parse('200 Tgas') + gas: Gas.parse('275 Tgas') } ); } diff --git a/tests/__tests__/linear/upgrade.ava.ts b/tests/__tests__/linear/upgrade.ava.ts index b4c41744..4218ca26 100644 --- a/tests/__tests__/linear/upgrade.ava.ts +++ b/tests/__tests__/linear/upgrade.ava.ts @@ -39,7 +39,7 @@ async function stakeAll (signer: NearAccount, contract: NearAccount) { 'epoch_stake', {}, { - gas: Gas.parse('300 Tgas') + gas: Gas.parse('275 Tgas') } ); } @@ -397,7 +397,7 @@ skip('upgrade from v1.4.4 to v1.5.0', async (test, context) => { 'epoch_stake', {}, { - gas: Gas.parse('300 Tgas') + gas: Gas.parse('275 Tgas') } ); } From 0d23384189c6aa902fb9580002162941923a3ba9 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Tue, 22 Aug 2023 10:26:14 +0800 Subject: [PATCH 20/55] remove public sync_balance --- contracts/linear/src/epoch_actions.rs | 58 ---------------------- contracts/linear/src/errors.rs | 1 + contracts/linear/src/validator_pool.rs | 41 ++++++++++++--- tests/__tests__/linear/sync_balance.ava.ts | 2 + 4 files changed, 38 insertions(+), 64 deletions(-) diff --git a/contracts/linear/src/epoch_actions.rs b/contracts/linear/src/epoch_actions.rs index c218ffc7..c1a0ee7e 100644 --- a/contracts/linear/src/epoch_actions.rs +++ b/contracts/linear/src/epoch_actions.rs @@ -232,34 +232,6 @@ impl LiquidStakingContract { } .emit(); } - - /// Due to shares calculation and rounding of staking pool contract, - /// the amount of staked and unstaked balance might be a little bit - /// different than we requested. - /// This method is to sync the actual numbers with the validator. - pub fn sync_balance_from_validator(&mut self, validator_id: AccountId) { - self.assert_running(); - - let min_gas = GAS_SYNC_BALANCE + GAS_EXT_GET_ACCOUNT + GAS_CB_VALIDATOR_SYNC_BALANCE; - require!( - env::prepaid_gas() >= min_gas, - format!("{}. require at least {:?}", ERR_NO_ENOUGH_GAS, min_gas) - ); - - let mut validator = self - .validator_pool - .get_validator(&validator_id) - .expect(ERR_VALIDATOR_NOT_EXIST); - - validator - .sync_account_balance(&mut self.validator_pool) - .then(ext_self_action_cb::validator_get_account_callback( - validator.account_id, - env::current_account_id(), - NO_DEPOSIT, - GAS_CB_VALIDATOR_SYNC_BALANCE, - )); - } } /// -- callbacks @@ -291,21 +263,6 @@ impl LiquidStakingContract { if is_promise_success() { validator.on_stake_success(&mut self.validator_pool, amount); - - Event::EpochStakeSuccess { - validator_id: &validator_id, - amount: &U128(amount), - } - .emit(); - - validator - .sync_account_balance(&mut self.validator_pool) - .then(ext_self_action_cb::validator_get_account_callback( - validator.account_id, - env::current_account_id(), - NO_DEPOSIT, - GAS_CB_VALIDATOR_SYNC_BALANCE, - )); } else { validator.on_stake_failed(&mut self.validator_pool); @@ -330,21 +287,6 @@ impl LiquidStakingContract { if is_promise_success() { validator.on_unstake_success(&mut self.validator_pool, amount); - - Event::EpochUnstakeSuccess { - validator_id: &validator_id, - amount: &U128(amount), - } - .emit(); - - validator - .sync_account_balance(&mut self.validator_pool) - .then(ext_self_action_cb::validator_get_account_callback( - validator.account_id, - env::current_account_id(), - NO_DEPOSIT, - GAS_CB_VALIDATOR_SYNC_BALANCE, - )); } else { // unstake failed, revert // 1. revert contract states diff --git a/contracts/linear/src/errors.rs b/contracts/linear/src/errors.rs index afcaa3e9..9369d496 100644 --- a/contracts/linear/src/errors.rs +++ b/contracts/linear/src/errors.rs @@ -80,6 +80,7 @@ pub const ERR_VALIDATOR_UNSTAKE_WHEN_LOCKED: &str = pub const ERR_VALIDATOR_WITHDRAW_WHEN_LOCKED: &str = "Cannot withdraw from a pending release validator"; pub const ERR_VALIDATOR_ALREADY_EXECUTING_ACTION: &str = "Validator is already executing action"; +pub const ERR_VALIDATOR_SYNC_WHEN_UNLOCKED: &str = "Validator sync balance when unlocked"; // liquidity pool pub const ERR_NON_POSITIVE_MIN_FEE: &str = "The min fee basis points should be positive"; diff --git a/contracts/linear/src/validator_pool.rs b/contracts/linear/src/validator_pool.rs index 2116d1a3..0abe11dd 100644 --- a/contracts/linear/src/validator_pool.rs +++ b/contracts/linear/src/validator_pool.rs @@ -1,3 +1,4 @@ +use crate::epoch_actions::ext_self_action_cb; use crate::errors::*; use crate::events::Event; use crate::legacy::ValidatorV1_0_0; @@ -927,10 +928,22 @@ impl Validator { } pub fn on_stake_success(&mut self, pool: &mut ValidatorPool, amount: Balance) { - self.post_execution(pool); - self.staked_amount += amount; pool.save_validator(self); + + Event::EpochStakeSuccess { + validator_id: &self.account_id, + amount: &U128(amount), + } + .emit(); + + self.sync_account_balance() + .then(ext_self_action_cb::validator_get_account_callback( + self.account_id.clone(), + env::current_account_id(), + NO_DEPOSIT, + GAS_CB_VALIDATOR_SYNC_BALANCE, + )); } pub fn on_stake_failed(&mut self, pool: &mut ValidatorPool) { @@ -965,11 +978,23 @@ impl Validator { } pub fn on_unstake_success(&mut self, pool: &mut ValidatorPool, amount: Balance) { - self.post_execution(pool); - self.staked_amount -= amount; self.unstaked_amount += amount; pool.save_validator(self); + + Event::EpochUnstakeSuccess { + validator_id: &self.account_id, + amount: &U128(amount), + } + .emit(); + + self.sync_account_balance() + .then(ext_self_action_cb::validator_get_account_callback( + self.account_id.clone(), + env::current_account_id(), + NO_DEPOSIT, + GAS_CB_VALIDATOR_SYNC_BALANCE, + )); } pub fn on_unstake_failed(&mut self, pool: &mut ValidatorPool) { @@ -1000,8 +1025,12 @@ impl Validator { pool.save_validator(self); } - pub fn sync_account_balance(&mut self, pool: &mut ValidatorPool) -> Promise { - self.pre_execution(pool); + /// Due to shares calculation and rounding of staking pool contract, + /// the amount of staked and unstaked balance might be a little bit + /// different than we requested. + /// This method is to sync the actual numbers with the validator. + fn sync_account_balance(&mut self) -> Promise { + require!(self.executing, ERR_VALIDATOR_SYNC_WHEN_UNLOCKED); ext_staking_pool::get_account( env::current_account_id(), diff --git a/tests/__tests__/linear/sync_balance.ava.ts b/tests/__tests__/linear/sync_balance.ava.ts index b430301c..83dc7455 100644 --- a/tests/__tests__/linear/sync_balance.ava.ts +++ b/tests/__tests__/linear/sync_balance.ava.ts @@ -30,6 +30,7 @@ function assertValidatorAmountHelper( } workspace.test('sync balance failure', async (test, { root, contract, alice, owner }) => { + /* const assertValidator = assertValidatorAmountHelper(test, contract, owner); const v1 = await createStakingPool(root, 'v1'); const v2 = await createStakingPool(root, 'v2'); @@ -203,4 +204,5 @@ workspace.test('sync balance', async (test, { root, contract, alice, owner }) => ); await assertValidator(v2, NEAR.parse("30").sub(diff).toString(10), diff.toString(10)); + */ }); From cb6b24d38eb3288a60c30e15d1a3e504baab20aa Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Tue, 22 Aug 2023 11:06:09 +0800 Subject: [PATCH 21/55] refactor --- contracts/linear/src/epoch_actions.rs | 42 ++++++++++++++++++- contracts/linear/src/validator_pool.rs | 58 +++++++++++--------------- 2 files changed, 65 insertions(+), 35 deletions(-) diff --git a/contracts/linear/src/epoch_actions.rs b/contracts/linear/src/epoch_actions.rs index c1a0ee7e..4ed90ee5 100644 --- a/contracts/linear/src/epoch_actions.rs +++ b/contracts/linear/src/epoch_actions.rs @@ -262,7 +262,26 @@ impl LiquidStakingContract { .unwrap_or_else(|| panic!("{}: {}", ERR_VALIDATOR_NOT_EXIST, &validator_id)); if is_promise_success() { - validator.on_stake_success(&mut self.validator_pool, amount); + validator.on_stake_success( + &mut self.validator_pool, + amount, + false, /* release_lock */ + ); + + Event::EpochStakeSuccess { + validator_id: &validator_id, + amount: &U128(amount), + } + .emit(); + + validator.sync_account_balance().then( + ext_self_action_cb::validator_get_account_callback( + validator_id, + env::current_account_id(), + NO_DEPOSIT, + GAS_CB_VALIDATOR_SYNC_BALANCE, + ), + ); } else { validator.on_stake_failed(&mut self.validator_pool); @@ -286,7 +305,26 @@ impl LiquidStakingContract { .unwrap_or_else(|| panic!("{}: {}", ERR_VALIDATOR_NOT_EXIST, &validator_id)); if is_promise_success() { - validator.on_unstake_success(&mut self.validator_pool, amount); + validator.on_unstake_success( + &mut self.validator_pool, + amount, + false, /* release_lock */ + ); + + Event::EpochUnstakeSuccess { + validator_id: &validator_id, + amount: &U128(amount), + } + .emit(); + + validator.sync_account_balance().then( + ext_self_action_cb::validator_get_account_callback( + validator_id, + env::current_account_id(), + NO_DEPOSIT, + GAS_CB_VALIDATOR_SYNC_BALANCE, + ), + ); } else { // unstake failed, revert // 1. revert contract states diff --git a/contracts/linear/src/validator_pool.rs b/contracts/linear/src/validator_pool.rs index 0abe11dd..3596900d 100644 --- a/contracts/linear/src/validator_pool.rs +++ b/contracts/linear/src/validator_pool.rs @@ -1,4 +1,3 @@ -use crate::epoch_actions::ext_self_action_cb; use crate::errors::*; use crate::events::Event; use crate::legacy::ValidatorV1_0_0; @@ -744,7 +743,11 @@ impl LiquidStakingContract { .unwrap_or_else(|| panic!("{}: {}", ERR_VALIDATOR_NOT_EXIST, &validator_id)); if is_promise_success() { - validator.on_unstake_success(&mut self.validator_pool, amount); + validator.on_unstake_success( + &mut self.validator_pool, + amount, + true, /* release_lock */ + ); validator.set_draining(&mut self.validator_pool, true); Event::DrainUnstakeSuccess { @@ -927,23 +930,17 @@ impl Validator { ) } - pub fn on_stake_success(&mut self, pool: &mut ValidatorPool, amount: Balance) { + pub fn on_stake_success( + &mut self, + pool: &mut ValidatorPool, + amount: Balance, + release_lock: bool, + ) { + if release_lock { + self.post_execution(pool); + } self.staked_amount += amount; pool.save_validator(self); - - Event::EpochStakeSuccess { - validator_id: &self.account_id, - amount: &U128(amount), - } - .emit(); - - self.sync_account_balance() - .then(ext_self_action_cb::validator_get_account_callback( - self.account_id.clone(), - env::current_account_id(), - NO_DEPOSIT, - GAS_CB_VALIDATOR_SYNC_BALANCE, - )); } pub fn on_stake_failed(&mut self, pool: &mut ValidatorPool) { @@ -977,24 +974,19 @@ impl Validator { ) } - pub fn on_unstake_success(&mut self, pool: &mut ValidatorPool, amount: Balance) { + pub fn on_unstake_success( + &mut self, + pool: &mut ValidatorPool, + amount: Balance, + release_lock: bool, + ) { + if release_lock { + self.post_execution(pool); + } + self.staked_amount -= amount; self.unstaked_amount += amount; pool.save_validator(self); - - Event::EpochUnstakeSuccess { - validator_id: &self.account_id, - amount: &U128(amount), - } - .emit(); - - self.sync_account_balance() - .then(ext_self_action_cb::validator_get_account_callback( - self.account_id.clone(), - env::current_account_id(), - NO_DEPOSIT, - GAS_CB_VALIDATOR_SYNC_BALANCE, - )); } pub fn on_unstake_failed(&mut self, pool: &mut ValidatorPool) { @@ -1029,7 +1021,7 @@ impl Validator { /// the amount of staked and unstaked balance might be a little bit /// different than we requested. /// This method is to sync the actual numbers with the validator. - fn sync_account_balance(&mut self) -> Promise { + pub fn sync_account_balance(&mut self) -> Promise { require!(self.executing, ERR_VALIDATOR_SYNC_WHEN_UNLOCKED); ext_staking_pool::get_account( From ab3a1de10359f16981ae7d1fcf005879f048c466 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Tue, 22 Aug 2023 11:15:16 +0800 Subject: [PATCH 22/55] refactor From 15bf3878ba785072a9ac2bec3fb0e9eb653f8b24 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Tue, 22 Aug 2023 13:44:54 +0800 Subject: [PATCH 23/55] tests --- contracts/mock-staking-pool/src/lib.rs | 23 ++-- tests/__tests__/linear/sync_balance.ava.ts | 122 +++++++++++---------- 2 files changed, 76 insertions(+), 69 deletions(-) diff --git a/contracts/mock-staking-pool/src/lib.rs b/contracts/mock-staking-pool/src/lib.rs index 43e7acb6..edbad140 100644 --- a/contracts/mock-staking-pool/src/lib.rs +++ b/contracts/mock-staking-pool/src/lib.rs @@ -49,6 +49,9 @@ pub struct MockStakingPool { staked: LookupMap, /// for testing purpose, simulates contract panic panic: bool, + + staked_delta: u128, + unstaked_delta: u128, } #[near_bindgen] @@ -59,6 +62,8 @@ impl MockStakingPool { deposits: LookupMap::new(b"d"), staked: LookupMap::new(b"s"), panic: false, + staked_delta: 0, + unstaked_delta: 0, } } } @@ -157,17 +162,9 @@ impl MockStakingPool { self.panic = panic; } - pub fn adjust_balance( - &mut self, - account_id: AccountId, - staked_delta: U128, - unstaked_delta: U128, - ) { - let staked_amount = self.internal_get_staked(&account_id) - staked_delta.0; - let unstaked_amount = self.internal_get_unstaked_deposit(&account_id) + unstaked_delta.0; - - self.staked.insert(&account_id, &staked_amount); - self.deposits.insert(&account_id, &unstaked_amount); + pub fn set_balance_delta(&mut self, staked_delta: U128, unstaked_delta: U128) { + self.staked_delta = staked_delta.0; + self.unstaked_delta = unstaked_delta.0; } } @@ -190,7 +187,7 @@ impl MockStakingPool { assert!(unstaked_deposit >= amount); let new_deposit = unstaked_deposit - amount; - let new_staked = self.internal_get_staked(&account_id) + amount; + let new_staked = self.internal_get_staked(&account_id) + amount - self.staked_delta; self.deposits.insert(&account_id, &new_deposit); self.staked.insert(&account_id, &new_staked); @@ -202,7 +199,7 @@ impl MockStakingPool { assert!(staked >= amount); let unstaked_deposit = self.internal_get_unstaked_deposit(&account_id); - let new_deposit = unstaked_deposit + amount; + let new_deposit = unstaked_deposit + amount + self.unstaked_delta; let new_staked = staked - amount; self.deposits.insert(&account_id, &new_deposit); diff --git a/tests/__tests__/linear/sync_balance.ava.ts b/tests/__tests__/linear/sync_balance.ava.ts index 83dc7455..a98e4221 100644 --- a/tests/__tests__/linear/sync_balance.ava.ts +++ b/tests/__tests__/linear/sync_balance.ava.ts @@ -30,7 +30,6 @@ function assertValidatorAmountHelper( } workspace.test('sync balance failure', async (test, { root, contract, alice, owner }) => { - /* const assertValidator = assertValidatorAmountHelper(test, contract, owner); const v1 = await createStakingPool(root, 'v1'); const v2 = await createStakingPool(root, 'v2'); @@ -68,6 +67,18 @@ workspace.test('sync balance failure', async (test, { root, contract, alice, own } ); + // -- 1. total balance diff > MAX_SYNC_BALANCE_DIFF + const diff = MAX_SYNC_BALANCE_DIFF.addn(1); + + await owner.call( + v1, + 'set_balance_delta', + { + staked_delta: diff.toString(10), + unstaked_delta: diff.toString(10), + }, + ); + for (let i = 0; i < 2; i++) { await owner.call( contract, @@ -79,56 +90,34 @@ workspace.test('sync balance failure', async (test, { root, contract, alice, own ); } - // -- 1. total balance diff > MAX_SYNC_BALANCE_DIFF - const diff = MAX_SYNC_BALANCE_DIFF.addn(1); - await owner.call( - v1, - 'adjust_balance', - { - account_id: contract.accountId, - staked_delta: "0", - unstaked_delta: diff.toString(10) - }, - ); - - await owner.call( - contract, - 'sync_balance_from_validator', - { - validator_id: v1.accountId - }, - { - gas: Gas.parse('200 Tgas') - } - ); - // v1 amount should not change await assertValidator(v1, '30000000000000000000000000', '0'); - // -- 2. amount balance diff > MAX_SYNC_BALANCE_DIFF await owner.call( - v2, - 'adjust_balance', - { - account_id: contract.accountId, - staked_delta: diff.toString(10), - unstaked_delta: diff.toString(10) - }, + contract, + 'set_epoch_height', + { epoch: 11 } ); - await owner.call( + await alice.call( contract, - 'sync_balance_from_validator', - { - validator_id: v2.accountId - }, - { - gas: Gas.parse('200 Tgas') - } + 'unstake_all', + {}, ); + for (let i = 0; i < 2; i++) { + await owner.call( + contract, + 'epoch_unstake', + {}, + { + gas: Gas.parse('275 Tgas') + } + ); + } + // v2 amount should not change - await assertValidator(v2, '30000000000000000000000000', '0'); + await assertValidator(v2, '5000000000000000000000000', '25000000000000000000000000'); }); workspace.test('sync balance', async (test, { root, contract, alice, owner }) => { @@ -169,6 +158,17 @@ workspace.test('sync balance', async (test, { root, contract, alice, owner }) => } ); + // -- amount balance diff < MAX_SYNC_BALANCE_DIFF + const diff = MAX_SYNC_BALANCE_DIFF.subn(1); + await owner.call( + v2, + 'set_balance_delta', + { + staked_delta: diff.toString(10), + unstaked_delta: diff.toString(10), + }, + ); + for (let i = 0; i < 2; i++) { await owner.call( contract, @@ -180,29 +180,39 @@ workspace.test('sync balance', async (test, { root, contract, alice, owner }) => ); } - // -- amount balance diff < MAX_SYNC_BALANCE_DIFF - const diff = MAX_SYNC_BALANCE_DIFF.subn(1); + await assertValidator(v2, NEAR.parse("30").sub(diff).toString(10), '0'); + + await owner.call( + contract, + 'set_epoch_height', + { epoch: 11 } + ); + await owner.call( - v2, - 'adjust_balance', + v1, + 'set_balance_delta', { - account_id: contract.accountId, staked_delta: diff.toString(10), unstaked_delta: diff.toString(10), }, ); - await owner.call( + await alice.call( contract, - 'sync_balance_from_validator', - { - validator_id: v2.accountId - }, - { - gas: Gas.parse('200 Tgas') - } + 'unstake_all', + {}, ); - await assertValidator(v2, NEAR.parse("30").sub(diff).toString(10), diff.toString(10)); - */ + for (let i = 0; i < 2; i++) { + await owner.call( + contract, + 'epoch_unstake', + {}, + { + gas: Gas.parse('275 Tgas') + } + ); + } + + await assertValidator(v1, NEAR.parse("5").toString(10), NEAR.parse("25").add(diff).toString(10)); }); From 4860e08c6793806cd646d4caf1d896efb99e82b8 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Tue, 22 Aug 2023 13:50:56 +0800 Subject: [PATCH 24/55] prettier --- tests/__tests__/linear/sync_balance.ava.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/__tests__/linear/sync_balance.ava.ts b/tests/__tests__/linear/sync_balance.ava.ts index a98e4221..ff7a9b08 100644 --- a/tests/__tests__/linear/sync_balance.ava.ts +++ b/tests/__tests__/linear/sync_balance.ava.ts @@ -91,7 +91,7 @@ workspace.test('sync balance failure', async (test, { root, contract, alice, own } // v1 amount should not change - await assertValidator(v1, '30000000000000000000000000', '0'); + await assertValidator(v1, NEAR.parse('30').toString(10), '0'); await owner.call( contract, @@ -117,7 +117,7 @@ workspace.test('sync balance failure', async (test, { root, contract, alice, own } // v2 amount should not change - await assertValidator(v2, '5000000000000000000000000', '25000000000000000000000000'); + await assertValidator(v2, NEAR.parse('5').toString(10), NEAR.parse('25').toString(10)); }); workspace.test('sync balance', async (test, { root, contract, alice, owner }) => { From bf15024156177948353eeea1562cb7ba246c8246 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Fri, 25 Aug 2023 16:00:01 +0800 Subject: [PATCH 25/55] sync balance in drain_unstake --- contracts/linear/src/validator_pool.rs | 18 ++++++++++++++++-- tests/__tests__/linear/drain.ava.ts | 13 ++++++++----- tests/__tests__/linear/upgrade.ava.ts | 2 +- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/contracts/linear/src/validator_pool.rs b/contracts/linear/src/validator_pool.rs index 3596900d..08c8fa0b 100644 --- a/contracts/linear/src/validator_pool.rs +++ b/contracts/linear/src/validator_pool.rs @@ -1,3 +1,4 @@ +use crate::epoch_actions::ext_self_action_cb; use crate::errors::*; use crate::events::Event; use crate::legacy::ValidatorV1_0_0; @@ -626,7 +627,11 @@ impl LiquidStakingContract { self.assert_manager(); // make sure enough gas was given - let min_gas = GAS_DRAIN_UNSTAKE + GAS_EXT_UNSTAKE + GAS_CB_VALIDATOR_UNSTAKED; + let min_gas = GAS_DRAIN_UNSTAKE + + GAS_EXT_UNSTAKE + + GAS_CB_VALIDATOR_UNSTAKED + + GAS_SYNC_BALANCE + + GAS_CB_VALIDATOR_SYNC_BALANCE; require!( env::prepaid_gas() >= min_gas, format!("{}. require at least {:?}", ERR_NO_ENOUGH_GAS, min_gas) @@ -676,7 +681,7 @@ impl LiquidStakingContract { unstake_amount.into(), env::current_account_id(), NO_DEPOSIT, - GAS_CB_VALIDATOR_UNSTAKED, + GAS_CB_VALIDATOR_UNSTAKED + GAS_SYNC_BALANCE + GAS_CB_VALIDATOR_SYNC_BALANCE, ), ); } @@ -755,6 +760,15 @@ impl LiquidStakingContract { amount: &U128(amount), } .emit(); + + validator.sync_account_balance().then( + ext_self_action_cb::validator_get_account_callback( + validator_id, + env::current_account_id(), + NO_DEPOSIT, + GAS_CB_VALIDATOR_SYNC_BALANCE, + ), + ); } else { // unstake failed, revert validator.on_unstake_failed(&mut self.validator_pool); diff --git a/tests/__tests__/linear/drain.ava.ts b/tests/__tests__/linear/drain.ava.ts index 70906273..55a57fa0 100644 --- a/tests/__tests__/linear/drain.ava.ts +++ b/tests/__tests__/linear/drain.ava.ts @@ -33,6 +33,9 @@ workspace.test('Non-manager call drain methods', async (test, {contract, alice}) 'drain_unstake', { validator_id: 'foo' + }, + { + gas: "275 Tgas" } ), 'Only manager can perform this action' @@ -99,7 +102,7 @@ workspace.test('drain constraints', async (test, {contract, root, owner, alice, validator_id: v1.accountId }, { - gas: Gas.parse('200 Tgas') + gas: Gas.parse('275 Tgas') } ), 'Validator weight must be zero for drain operation' @@ -125,7 +128,7 @@ workspace.test('drain constraints', async (test, {contract, root, owner, alice, validator_id: v1.accountId }, { - gas: Gas.parse('200 Tgas') + gas: Gas.parse('275 Tgas') } ), 'Validator base stake amount must be zero for drain operation' @@ -180,7 +183,7 @@ workspace.test('drain constraints', async (test, {contract, root, owner, alice, validator_id: v1.accountId }, { - gas: Gas.parse('200 Tgas') + gas: Gas.parse('275 Tgas') } ), 'Cannot unstake from a pending release validator' @@ -203,7 +206,7 @@ workspace.test('drain constraints', async (test, {contract, root, owner, alice, validator_id: v1.accountId }, { - gas: Gas.parse('200 Tgas') + gas: Gas.parse('275 Tgas') } ), 'Validator unstaked amount too large for drain unstake' @@ -302,7 +305,7 @@ workspace.test('drain unstake and withdraw', async (test, {contract, root, owner validator_id: v1.accountId }, { - gas: Gas.parse('200 Tgas') + gas: Gas.parse('275 Tgas') } ); diff --git a/tests/__tests__/linear/upgrade.ava.ts b/tests/__tests__/linear/upgrade.ava.ts index 4218ca26..3c39cf80 100644 --- a/tests/__tests__/linear/upgrade.ava.ts +++ b/tests/__tests__/linear/upgrade.ava.ts @@ -300,7 +300,7 @@ skip('upgrade from v1.3.3 to v1.4.0', async (test, context) => { validator_id: targetValidator.accountId }, { - gas: Gas.parse('200 Tgas') + gas: Gas.parse('275 Tgas') } ); From 476f8ba1cc1d900df979bd14677b5aa0d8a23048 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Fri, 25 Aug 2023 16:05:50 +0800 Subject: [PATCH 26/55] resolve comments --- contracts/linear/src/epoch_actions.rs | 6 +----- contracts/linear/src/validator_pool.rs | 11 ++--------- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/contracts/linear/src/epoch_actions.rs b/contracts/linear/src/epoch_actions.rs index 4ed90ee5..f672bce4 100644 --- a/contracts/linear/src/epoch_actions.rs +++ b/contracts/linear/src/epoch_actions.rs @@ -262,11 +262,7 @@ impl LiquidStakingContract { .unwrap_or_else(|| panic!("{}: {}", ERR_VALIDATOR_NOT_EXIST, &validator_id)); if is_promise_success() { - validator.on_stake_success( - &mut self.validator_pool, - amount, - false, /* release_lock */ - ); + validator.on_stake_success(&mut self.validator_pool, amount); Event::EpochStakeSuccess { validator_id: &validator_id, diff --git a/contracts/linear/src/validator_pool.rs b/contracts/linear/src/validator_pool.rs index 08c8fa0b..6297b192 100644 --- a/contracts/linear/src/validator_pool.rs +++ b/contracts/linear/src/validator_pool.rs @@ -944,15 +944,8 @@ impl Validator { ) } - pub fn on_stake_success( - &mut self, - pool: &mut ValidatorPool, - amount: Balance, - release_lock: bool, - ) { - if release_lock { - self.post_execution(pool); - } + pub fn on_stake_success(&mut self, pool: &mut ValidatorPool, amount: Balance) { + // Do not post execution here because we need to sync account balance later self.staked_amount += amount; pool.save_validator(self); } From e5e11489720d73358fde04dcf401be7360c77f7a Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Mon, 28 Aug 2023 11:38:49 +0800 Subject: [PATCH 27/55] drain_unstake return promise --- contracts/linear/src/validator_pool.rs | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/contracts/linear/src/validator_pool.rs b/contracts/linear/src/validator_pool.rs index 6297b192..c31bc8a0 100644 --- a/contracts/linear/src/validator_pool.rs +++ b/contracts/linear/src/validator_pool.rs @@ -7,6 +7,7 @@ use crate::legacy::ValidatorV1_4_0; use crate::types::*; use crate::utils::*; use crate::*; +use near_sdk::PromiseOrValue; use near_sdk::{ borsh::{self, BorshDeserialize, BorshSerialize}, collections::UnorderedMap, @@ -622,7 +623,7 @@ impl LiquidStakingContract { /// This method is designed to drain a validator. /// The weight of target validator should be set to 0 before calling this. /// And a following call to drain_withdraw MUST be made after 4 epoches. - pub fn drain_unstake(&mut self, validator_id: AccountId) { + pub fn drain_unstake(&mut self, validator_id: AccountId) -> Promise { self.assert_running(); self.assert_manager(); @@ -683,7 +684,7 @@ impl LiquidStakingContract { NO_DEPOSIT, GAS_CB_VALIDATOR_UNSTAKED + GAS_SYNC_BALANCE + GAS_CB_VALIDATOR_SYNC_BALANCE, ), - ); + ) } /// Withdraw from a drained validator @@ -740,7 +741,11 @@ impl LiquidStakingContract { } #[private] - pub fn validator_drain_unstaked_callback(&mut self, validator_id: AccountId, amount: U128) { + pub fn validator_drain_unstaked_callback( + &mut self, + validator_id: AccountId, + amount: U128, + ) -> PromiseOrValue<()> { let amount = amount.into(); let mut validator = self .validator_pool @@ -761,14 +766,15 @@ impl LiquidStakingContract { } .emit(); - validator.sync_account_balance().then( - ext_self_action_cb::validator_get_account_callback( + validator + .sync_account_balance() + .then(ext_self_action_cb::validator_get_account_callback( validator_id, env::current_account_id(), NO_DEPOSIT, GAS_CB_VALIDATOR_SYNC_BALANCE, - ), - ); + )) + .into() } else { // unstake failed, revert validator.on_unstake_failed(&mut self.validator_pool); @@ -778,6 +784,8 @@ impl LiquidStakingContract { amount: &U128(amount), } .emit(); + + PromiseOrValue::Value(()) } } From 1d1d5e128563dadb5f546ef78b70cee7002a4515 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Mon, 28 Aug 2023 15:51:38 +0800 Subject: [PATCH 28/55] remove on_unstake_success release_lock --- contracts/linear/src/epoch_actions.rs | 6 +----- contracts/linear/src/validator_pool.rs | 17 ++--------------- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/contracts/linear/src/epoch_actions.rs b/contracts/linear/src/epoch_actions.rs index f672bce4..69702a73 100644 --- a/contracts/linear/src/epoch_actions.rs +++ b/contracts/linear/src/epoch_actions.rs @@ -301,11 +301,7 @@ impl LiquidStakingContract { .unwrap_or_else(|| panic!("{}: {}", ERR_VALIDATOR_NOT_EXIST, &validator_id)); if is_promise_success() { - validator.on_unstake_success( - &mut self.validator_pool, - amount, - false, /* release_lock */ - ); + validator.on_unstake_success(&mut self.validator_pool, amount); Event::EpochUnstakeSuccess { validator_id: &validator_id, diff --git a/contracts/linear/src/validator_pool.rs b/contracts/linear/src/validator_pool.rs index c31bc8a0..11922fa3 100644 --- a/contracts/linear/src/validator_pool.rs +++ b/contracts/linear/src/validator_pool.rs @@ -753,11 +753,7 @@ impl LiquidStakingContract { .unwrap_or_else(|| panic!("{}: {}", ERR_VALIDATOR_NOT_EXIST, &validator_id)); if is_promise_success() { - validator.on_unstake_success( - &mut self.validator_pool, - amount, - true, /* release_lock */ - ); + validator.on_unstake_success(&mut self.validator_pool, amount); validator.set_draining(&mut self.validator_pool, true); Event::DrainUnstakeSuccess { @@ -989,16 +985,7 @@ impl Validator { ) } - pub fn on_unstake_success( - &mut self, - pool: &mut ValidatorPool, - amount: Balance, - release_lock: bool, - ) { - if release_lock { - self.post_execution(pool); - } - + pub fn on_unstake_success(&mut self, pool: &mut ValidatorPool, amount: Balance) { self.staked_amount -= amount; self.unstaked_amount += amount; pool.save_validator(self); From e6f137439f7ea003cce39d93c4cc377dd84f3c93 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Mon, 28 Aug 2023 16:27:33 +0800 Subject: [PATCH 29/55] epoch_stake chain --- contracts/linear/src/epoch_actions.rs | 46 +++++++++++++------ contracts/linear/src/types.rs | 1 + tests/__tests__/linear/drain.ava.ts | 4 +- .../linear/epoch-action-failure.ava.ts | 8 ++-- tests/__tests__/linear/epoch-action.ava.ts | 2 +- .../linear/staking-pool-interface.ava.ts | 2 +- tests/__tests__/linear/sync_balance.ava.ts | 4 +- tests/__tests__/linear/upgrade.ava.ts | 4 +- 8 files changed, 46 insertions(+), 25 deletions(-) diff --git a/contracts/linear/src/epoch_actions.rs b/contracts/linear/src/epoch_actions.rs index 69702a73..c6bb7dde 100644 --- a/contracts/linear/src/epoch_actions.rs +++ b/contracts/linear/src/epoch_actions.rs @@ -1,4 +1,5 @@ use crate::*; +use near_sdk::PromiseOrValue; use near_sdk::{is_promise_success, log, near_bindgen, Balance}; use crate::errors::*; @@ -14,14 +15,15 @@ const MAX_SYNC_BALANCE_DIFF: Balance = 100; /// during each epoch. #[near_bindgen] impl LiquidStakingContract { - pub fn epoch_stake(&mut self) -> bool { + pub fn epoch_stake(&mut self) -> PromiseOrValue { self.assert_running(); // make sure enough gas was given let min_gas = GAS_EPOCH_STAKE + GAS_EXT_DEPOSIT_AND_STAKE + GAS_CB_VALIDATOR_STAKED + GAS_SYNC_BALANCE - + GAS_CB_VALIDATOR_SYNC_BALANCE; + + GAS_CB_VALIDATOR_SYNC_BALANCE + + GAS_CB_VALIDATOR_RETURN_TRUE; require!( env::prepaid_gas() >= min_gas, format!("{}. require at least {:?}", ERR_NO_ENOUGH_GAS, min_gas) @@ -31,7 +33,7 @@ impl LiquidStakingContract { // after cleanup, there might be no need to stake if self.stake_amount_to_settle == 0 { log!("no need to stake, amount to settle is zero"); - return false; + return PromiseOrValue::Value(false); } let candidate = self @@ -40,7 +42,7 @@ impl LiquidStakingContract { if candidate.is_none() { log!("no candidate found to stake"); - return false; + return PromiseOrValue::Value(false); } let mut candidate = candidate.unwrap(); @@ -48,7 +50,7 @@ impl LiquidStakingContract { if amount_to_stake < MIN_AMOUNT_TO_PERFORM_STAKE { log!("stake amount too low: {}", amount_to_stake); - return false; + return PromiseOrValue::Value(false); } require!( @@ -75,9 +77,13 @@ impl LiquidStakingContract { env::current_account_id(), NO_DEPOSIT, GAS_CB_VALIDATOR_STAKED + GAS_SYNC_BALANCE + GAS_CB_VALIDATOR_SYNC_BALANCE, - )); - - true + )) + .then(ext_self_action_cb::validator_return_true_callback( + env::current_account_id(), + NO_DEPOSIT, + GAS_CB_VALIDATOR_RETURN_TRUE, + )) + .into() } pub fn epoch_unstake(&mut self) -> bool { @@ -242,6 +248,8 @@ trait EpochActionCallbacks { fn validator_unstaked_callback(&mut self, validator_id: AccountId, amount: U128); + fn validator_return_true_callback(&mut self) -> bool; + fn validator_get_balance_callback(&mut self, validator_id: AccountId); fn validator_get_account_callback(&mut self, validator_id: AccountId); @@ -254,7 +262,16 @@ trait EpochActionCallbacks { #[near_bindgen] impl LiquidStakingContract { #[private] - pub fn validator_staked_callback(&mut self, validator_id: AccountId, amount: U128) { + pub fn validator_return_true_callback(&mut self) -> bool { + true + } + + #[private] + pub fn validator_staked_callback( + &mut self, + validator_id: AccountId, + amount: U128, + ) -> PromiseOrValue<()> { let amount = amount.into(); let mut validator = self .validator_pool @@ -270,14 +287,15 @@ impl LiquidStakingContract { } .emit(); - validator.sync_account_balance().then( - ext_self_action_cb::validator_get_account_callback( + validator + .sync_account_balance() + .then(ext_self_action_cb::validator_get_account_callback( validator_id, env::current_account_id(), NO_DEPOSIT, GAS_CB_VALIDATOR_SYNC_BALANCE, - ), - ); + )) + .into() } else { validator.on_stake_failed(&mut self.validator_pool); @@ -289,6 +307,8 @@ impl LiquidStakingContract { amount: &U128(amount), } .emit(); + + PromiseOrValue::Value(()) } } diff --git a/contracts/linear/src/types.rs b/contracts/linear/src/types.rs index ed6eb0df..02fcff64 100644 --- a/contracts/linear/src/types.rs +++ b/contracts/linear/src/types.rs @@ -64,6 +64,7 @@ pub const GAS_CB_VALIDATOR_GET_BALANCE: Gas = Gas(25 * TGAS); pub const GAS_CB_VALIDATOR_SYNC_BALANCE: Gas = Gas(25 * TGAS); pub const GAS_CB_VALIDATOR_WITHDRAW: Gas = Gas(25 * TGAS); pub const GAS_CB_WHITELIST: Gas = Gas(15 * TGAS); +pub const GAS_CB_VALIDATOR_RETURN_TRUE: Gas = Gas(5 * TGAS); // -- COMMON TYPES diff --git a/tests/__tests__/linear/drain.ava.ts b/tests/__tests__/linear/drain.ava.ts index 55a57fa0..ffb09f83 100644 --- a/tests/__tests__/linear/drain.ava.ts +++ b/tests/__tests__/linear/drain.ava.ts @@ -19,7 +19,7 @@ async function stakeAll (signer: NearAccount, contract: NearAccount) { 'epoch_stake', {}, { - gas: Gas.parse('275 Tgas') + gas: Gas.parse('280 Tgas') } ); } @@ -88,7 +88,7 @@ workspace.test('drain constraints', async (test, {contract, root, owner, alice, 'epoch_stake', {}, { - gas: Gas.parse('275 Tgas') + gas: Gas.parse('280 Tgas') } ); diff --git a/tests/__tests__/linear/epoch-action-failure.ava.ts b/tests/__tests__/linear/epoch-action-failure.ava.ts index 8c0b8554..a0cf736f 100644 --- a/tests/__tests__/linear/epoch-action-failure.ava.ts +++ b/tests/__tests__/linear/epoch-action-failure.ava.ts @@ -71,7 +71,7 @@ workspace.test('epoch stake failure', async (test, { root, contract, owner, alic 'epoch_stake', {}, { - gas: Gas.parse('275 Tgas') + gas: Gas.parse('280 Tgas') } ); @@ -111,7 +111,7 @@ workspace.test('unstake failure', async (test, { root, contract, owner, alice }) 'epoch_stake', {}, { - gas: Gas.parse('275 Tgas') + gas: Gas.parse('280 Tgas') } ); @@ -171,7 +171,7 @@ workspace.test('withdraw failure', async (test, { root, contract, owner, alice } 'epoch_stake', {}, { - gas: Gas.parse('275 Tgas') + gas: Gas.parse('280 Tgas') } ); @@ -259,7 +259,7 @@ workspace.test('get balance failure', async (test, { root, contract, owner, alic 'epoch_stake', {}, { - gas: Gas.parse('275 Tgas') + gas: Gas.parse('280 Tgas') } ); diff --git a/tests/__tests__/linear/epoch-action.ava.ts b/tests/__tests__/linear/epoch-action.ava.ts index 5c65865b..b169529b 100644 --- a/tests/__tests__/linear/epoch-action.ava.ts +++ b/tests/__tests__/linear/epoch-action.ava.ts @@ -20,7 +20,7 @@ async function stakeAll (owner: NearAccount, contract: NearAccount) { 'epoch_stake', {}, { - gas: Gas.parse('275 Tgas') + gas: Gas.parse('280 Tgas') } ); } diff --git a/tests/__tests__/linear/staking-pool-interface.ava.ts b/tests/__tests__/linear/staking-pool-interface.ava.ts index 2f97f03d..a8677b91 100644 --- a/tests/__tests__/linear/staking-pool-interface.ava.ts +++ b/tests/__tests__/linear/staking-pool-interface.ava.ts @@ -273,7 +273,7 @@ workspace.test('late unstake and withdraw', async (test, { contract ,alice }) => 'epoch_stake', {}, { - gas: Gas.parse('275 Tgas') + gas: Gas.parse('280 Tgas') } ); diff --git a/tests/__tests__/linear/sync_balance.ava.ts b/tests/__tests__/linear/sync_balance.ava.ts index ff7a9b08..fb007087 100644 --- a/tests/__tests__/linear/sync_balance.ava.ts +++ b/tests/__tests__/linear/sync_balance.ava.ts @@ -85,7 +85,7 @@ workspace.test('sync balance failure', async (test, { root, contract, alice, own 'epoch_stake', {}, { - gas: Gas.parse('275 Tgas') + gas: Gas.parse('280 Tgas') } ); } @@ -175,7 +175,7 @@ workspace.test('sync balance', async (test, { root, contract, alice, owner }) => 'epoch_stake', {}, { - gas: Gas.parse('275 Tgas') + gas: Gas.parse('280 Tgas') } ); } diff --git a/tests/__tests__/linear/upgrade.ava.ts b/tests/__tests__/linear/upgrade.ava.ts index 3c39cf80..6569f326 100644 --- a/tests/__tests__/linear/upgrade.ava.ts +++ b/tests/__tests__/linear/upgrade.ava.ts @@ -39,7 +39,7 @@ async function stakeAll (signer: NearAccount, contract: NearAccount) { 'epoch_stake', {}, { - gas: Gas.parse('275 Tgas') + gas: Gas.parse('280 Tgas') } ); } @@ -397,7 +397,7 @@ skip('upgrade from v1.4.4 to v1.5.0', async (test, context) => { 'epoch_stake', {}, { - gas: Gas.parse('275 Tgas') + gas: Gas.parse('280 Tgas') } ); } From 610ee736eda82b4cb54ca6d19a58a56ec11d1c30 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Mon, 28 Aug 2023 16:45:49 +0800 Subject: [PATCH 30/55] epoch_unstake chain --- contracts/linear/src/epoch_actions.rs | 38 ++++++++++++------- tests/__tests__/linear/drain.ava.ts | 2 +- .../linear/epoch-action-failure.ava.ts | 4 +- tests/__tests__/linear/epoch-action.ava.ts | 4 +- tests/__tests__/linear/sync_balance.ava.ts | 4 +- 5 files changed, 32 insertions(+), 20 deletions(-) diff --git a/contracts/linear/src/epoch_actions.rs b/contracts/linear/src/epoch_actions.rs index c6bb7dde..d3292278 100644 --- a/contracts/linear/src/epoch_actions.rs +++ b/contracts/linear/src/epoch_actions.rs @@ -86,14 +86,15 @@ impl LiquidStakingContract { .into() } - pub fn epoch_unstake(&mut self) -> bool { + pub fn epoch_unstake(&mut self) -> PromiseOrValue { self.assert_running(); // make sure enough gas was given let min_gas = GAS_EPOCH_UNSTAKE + GAS_EXT_UNSTAKE + GAS_CB_VALIDATOR_UNSTAKED + GAS_SYNC_BALANCE - + GAS_CB_VALIDATOR_SYNC_BALANCE; + + GAS_CB_VALIDATOR_SYNC_BALANCE + + GAS_CB_VALIDATOR_RETURN_TRUE; require!( env::prepaid_gas() >= min_gas, format!("{}. require at least {:?}", ERR_NO_ENOUGH_GAS, min_gas) @@ -103,7 +104,7 @@ impl LiquidStakingContract { // after cleanup, there might be no need to unstake if self.unstake_amount_to_settle == 0 { log!("no need to unstake, amount to settle is zero"); - return false; + return PromiseOrValue::Value(false); } let candidate = self.validator_pool.get_candidate_to_unstake_v2( @@ -112,14 +113,14 @@ impl LiquidStakingContract { ); if candidate.is_none() { log!("no candidate found to unstake"); - return false; + return PromiseOrValue::Value(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); - return false; + return PromiseOrValue::Value(false); } // update internal state @@ -141,9 +142,13 @@ impl LiquidStakingContract { env::current_account_id(), NO_DEPOSIT, GAS_CB_VALIDATOR_UNSTAKED + GAS_SYNC_BALANCE + GAS_CB_VALIDATOR_SYNC_BALANCE, - )); - - true + )) + .then(ext_self_action_cb::validator_return_true_callback( + env::current_account_id(), + NO_DEPOSIT, + GAS_CB_VALIDATOR_RETURN_TRUE, + )) + .into() } pub fn epoch_update_rewards(&mut self, validator_id: AccountId) { @@ -313,7 +318,11 @@ impl LiquidStakingContract { } #[private] - pub fn validator_unstaked_callback(&mut self, validator_id: AccountId, amount: U128) { + pub fn validator_unstaked_callback( + &mut self, + validator_id: AccountId, + amount: U128, + ) -> PromiseOrValue<()> { let amount = amount.into(); let mut validator = self .validator_pool @@ -329,14 +338,15 @@ impl LiquidStakingContract { } .emit(); - validator.sync_account_balance().then( - ext_self_action_cb::validator_get_account_callback( + validator + .sync_account_balance() + .then(ext_self_action_cb::validator_get_account_callback( validator_id, env::current_account_id(), NO_DEPOSIT, GAS_CB_VALIDATOR_SYNC_BALANCE, - ), - ); + )) + .into() } else { // unstake failed, revert // 1. revert contract states @@ -350,6 +360,8 @@ impl LiquidStakingContract { amount: &U128(amount), } .emit(); + + PromiseOrValue::Value(()) } } diff --git a/tests/__tests__/linear/drain.ava.ts b/tests/__tests__/linear/drain.ava.ts index ffb09f83..8b0b6bf8 100644 --- a/tests/__tests__/linear/drain.ava.ts +++ b/tests/__tests__/linear/drain.ava.ts @@ -165,7 +165,7 @@ workspace.test('drain constraints', async (test, {contract, root, owner, alice, 'epoch_unstake', {}, { - gas: Gas.parse('275 Tgas') + gas: Gas.parse('280 Tgas') } ); diff --git a/tests/__tests__/linear/epoch-action-failure.ava.ts b/tests/__tests__/linear/epoch-action-failure.ava.ts index a0cf736f..622a9b91 100644 --- a/tests/__tests__/linear/epoch-action-failure.ava.ts +++ b/tests/__tests__/linear/epoch-action-failure.ava.ts @@ -131,7 +131,7 @@ workspace.test('unstake failure', async (test, { root, contract, owner, alice }) 'epoch_unstake', {}, { - gas: Gas.parse('275 Tgas') + gas: Gas.parse('280 Tgas') } ); @@ -196,7 +196,7 @@ workspace.test('withdraw failure', async (test, { root, contract, owner, alice } 'epoch_unstake', {}, { - gas: Gas.parse('275 Tgas') + gas: Gas.parse('280 Tgas') } ); diff --git a/tests/__tests__/linear/epoch-action.ava.ts b/tests/__tests__/linear/epoch-action.ava.ts index b169529b..29bf9f5c 100644 --- a/tests/__tests__/linear/epoch-action.ava.ts +++ b/tests/__tests__/linear/epoch-action.ava.ts @@ -34,7 +34,7 @@ async function unstakeAll (owner: NearAccount, contract: NearAccount) { 'epoch_unstake', {}, { - gas: Gas.parse('275 Tgas') + gas: Gas.parse('280 Tgas') } ); } @@ -774,7 +774,7 @@ skip('estimate gas of epoch unstake', async (test, {contract, alice, root, owner 'epoch_unstake', {}, { - gas: Gas.parse('275 Tgas') + gas: Gas.parse('280 Tgas') } ); diff --git a/tests/__tests__/linear/sync_balance.ava.ts b/tests/__tests__/linear/sync_balance.ava.ts index fb007087..5d61a73e 100644 --- a/tests/__tests__/linear/sync_balance.ava.ts +++ b/tests/__tests__/linear/sync_balance.ava.ts @@ -111,7 +111,7 @@ workspace.test('sync balance failure', async (test, { root, contract, alice, own 'epoch_unstake', {}, { - gas: Gas.parse('275 Tgas') + gas: Gas.parse('280 Tgas') } ); } @@ -209,7 +209,7 @@ workspace.test('sync balance', async (test, { root, contract, alice, owner }) => 'epoch_unstake', {}, { - gas: Gas.parse('275 Tgas') + gas: Gas.parse('280 Tgas') } ); } From f7e1167fdd8826719e34de3b94f356fbd07cefdf Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Wed, 30 Aug 2023 12:48:08 +0800 Subject: [PATCH 31/55] epoch_stake return Option --- contracts/linear/src/epoch_actions.rs | 126 ++++++++++-------- contracts/linear/src/events.rs | 8 +- contracts/mock-staking-pool/src/lib.rs | 10 ++ .../linear/epoch-action-failure.ava.ts | 110 ++++++++++++++- tests/__tests__/linear/epoch-action.ava.ts | 3 + 5 files changed, 199 insertions(+), 58 deletions(-) diff --git a/contracts/linear/src/epoch_actions.rs b/contracts/linear/src/epoch_actions.rs index d3292278..0c2e3258 100644 --- a/contracts/linear/src/epoch_actions.rs +++ b/contracts/linear/src/epoch_actions.rs @@ -15,7 +15,7 @@ const MAX_SYNC_BALANCE_DIFF: Balance = 100; /// during each epoch. #[near_bindgen] impl LiquidStakingContract { - pub fn epoch_stake(&mut self) -> PromiseOrValue { + pub fn epoch_stake(&mut self) -> PromiseOrValue> { self.assert_running(); // make sure enough gas was given let min_gas = GAS_EPOCH_STAKE @@ -33,7 +33,7 @@ impl LiquidStakingContract { // after cleanup, there might be no need to stake if self.stake_amount_to_settle == 0 { log!("no need to stake, amount to settle is zero"); - return PromiseOrValue::Value(false); + return PromiseOrValue::Value(Some(false)); } let candidate = self @@ -42,7 +42,7 @@ impl LiquidStakingContract { if candidate.is_none() { log!("no candidate found to stake"); - return PromiseOrValue::Value(false); + return PromiseOrValue::Value(Some(false)); } let mut candidate = candidate.unwrap(); @@ -50,7 +50,7 @@ impl LiquidStakingContract { if amount_to_stake < MIN_AMOUNT_TO_PERFORM_STAKE { log!("stake amount too low: {}", amount_to_stake); - return PromiseOrValue::Value(false); + return PromiseOrValue::Value(Some(false)); } require!( @@ -78,11 +78,6 @@ impl LiquidStakingContract { NO_DEPOSIT, GAS_CB_VALIDATOR_STAKED + GAS_SYNC_BALANCE + GAS_CB_VALIDATOR_SYNC_BALANCE, )) - .then(ext_self_action_cb::validator_return_true_callback( - env::current_account_id(), - NO_DEPOSIT, - GAS_CB_VALIDATOR_RETURN_TRUE, - )) .into() } @@ -249,7 +244,11 @@ impl LiquidStakingContract { #[ext_contract(ext_self_action_cb)] trait EpochActionCallbacks { - fn validator_staked_callback(&mut self, validator_id: AccountId, amount: U128); + fn validator_staked_callback( + &mut self, + validator_id: AccountId, + amount: U128, + ) -> PromiseOrValue>; fn validator_unstaked_callback(&mut self, validator_id: AccountId, amount: U128); @@ -257,7 +256,7 @@ trait EpochActionCallbacks { fn validator_get_balance_callback(&mut self, validator_id: AccountId); - fn validator_get_account_callback(&mut self, validator_id: AccountId); + fn validator_get_account_callback(&mut self, validator_id: AccountId) -> Option; fn validator_withdraw_callback(&mut self, validator_id: AccountId, amount: U128); } @@ -276,7 +275,7 @@ impl LiquidStakingContract { &mut self, validator_id: AccountId, amount: U128, - ) -> PromiseOrValue<()> { + ) -> PromiseOrValue> { let amount = amount.into(); let mut validator = self .validator_pool @@ -313,7 +312,7 @@ impl LiquidStakingContract { } .emit(); - PromiseOrValue::Value(()) + PromiseOrValue::Value(None) } } @@ -400,55 +399,72 @@ impl LiquidStakingContract { pub fn validator_get_account_callback( &mut self, validator_id: AccountId, - #[callback] account: HumanReadableAccount, - ) { + #[callback_result] result: Result, + ) -> Option { let mut validator = self .validator_pool .get_validator(&validator_id) .unwrap_or_else(|| panic!("{}: {}", ERR_VALIDATOR_NOT_EXIST, &validator_id)); - // allow at most MAX_SYNC_BALANCE_DIFF diff in total balance, staked balance and unstake balance - let new_total_balance = account.staked_balance.0 + account.unstaked_balance.0; - if abs_diff_eq( - new_total_balance, - validator.total_balance(), - MAX_SYNC_BALANCE_DIFF, - ) && abs_diff_eq( - account.staked_balance.0, - validator.staked_amount, - MAX_SYNC_BALANCE_DIFF, - ) && abs_diff_eq( - account.unstaked_balance.0, - validator.unstaked_amount, - MAX_SYNC_BALANCE_DIFF, - ) { - Event::SyncValidatorBalanceSuccess { - validator_id: &validator_id, - old_staked_balance: &validator.staked_amount.into(), - old_unstaked_balance: &validator.unstaked_amount.into(), - old_total_balance: &validator.total_balance().into(), - new_staked_balance: &account.staked_balance, - new_unstaked_balance: &account.unstaked_balance, - new_total_balance: &new_total_balance.into(), + match result { + Ok(account) => { + // allow at most MAX_SYNC_BALANCE_DIFF diff in total balance, staked balance and unstake balance + let new_total_balance = account.staked_balance.0 + account.unstaked_balance.0; + if abs_diff_eq( + new_total_balance, + validator.total_balance(), + MAX_SYNC_BALANCE_DIFF, + ) && abs_diff_eq( + account.staked_balance.0, + validator.staked_amount, + MAX_SYNC_BALANCE_DIFF, + ) && abs_diff_eq( + account.unstaked_balance.0, + validator.unstaked_amount, + MAX_SYNC_BALANCE_DIFF, + ) { + Event::SyncValidatorBalanceSuccess { + validator_id: &validator_id, + old_staked_balance: &validator.staked_amount.into(), + old_unstaked_balance: &validator.unstaked_amount.into(), + old_total_balance: &validator.total_balance().into(), + new_staked_balance: &account.staked_balance, + new_unstaked_balance: &account.unstaked_balance, + new_total_balance: &new_total_balance.into(), + } + .emit(); + validator.on_sync_account_balance_success( + &mut self.validator_pool, + account.staked_balance.0, + account.unstaked_balance.0, + ); + Some(true) + } else { + Event::SyncValidatorBalanceFailedLargeDiff { + validator_id: &validator_id, + old_staked_balance: &validator.staked_amount.into(), + old_unstaked_balance: &validator.unstaked_amount.into(), + old_total_balance: &validator.total_balance().into(), + new_staked_balance: &account.staked_balance, + new_unstaked_balance: &account.unstaked_balance, + new_total_balance: &new_total_balance.into(), + } + .emit(); + validator.on_sync_account_balance_failed(&mut self.validator_pool); + None + } } - .emit(); - validator.on_sync_account_balance_success( - &mut self.validator_pool, - account.staked_balance.0, - account.unstaked_balance.0, - ); - } else { - Event::SyncValidatorBalanceFailed { - validator_id: &validator_id, - old_staked_balance: &validator.staked_amount.into(), - old_unstaked_balance: &validator.unstaked_amount.into(), - old_total_balance: &validator.total_balance().into(), - new_staked_balance: &account.staked_balance, - new_unstaked_balance: &account.unstaked_balance, - new_total_balance: &new_total_balance.into(), + Err(_) => { + Event::SyncValidatorBalanceFailedCantGetAccount { + validator_id: &validator_id, + old_staked_balance: &validator.staked_amount.into(), + old_unstaked_balance: &validator.unstaked_amount.into(), + old_total_balance: &validator.total_balance().into(), + } + .emit(); + validator.on_sync_account_balance_failed(&mut self.validator_pool); + None } - .emit(); - validator.on_sync_account_balance_failed(&mut self.validator_pool); } } diff --git a/contracts/linear/src/events.rs b/contracts/linear/src/events.rs index e087d534..3c60fb6a 100644 --- a/contracts/linear/src/events.rs +++ b/contracts/linear/src/events.rs @@ -90,7 +90,7 @@ pub enum Event<'a> { new_unstaked_balance: &'a U128, new_total_balance: &'a U128, }, - SyncValidatorBalanceFailed { + SyncValidatorBalanceFailedLargeDiff { validator_id: &'a AccountId, old_staked_balance: &'a U128, old_unstaked_balance: &'a U128, @@ -99,6 +99,12 @@ pub enum Event<'a> { new_unstaked_balance: &'a U128, new_total_balance: &'a U128, }, + SyncValidatorBalanceFailedCantGetAccount { + validator_id: &'a AccountId, + old_staked_balance: &'a U128, + old_unstaked_balance: &'a U128, + old_total_balance: &'a U128, + }, // Staking Pool Interface Deposit { account_id: &'a AccountId, diff --git a/contracts/mock-staking-pool/src/lib.rs b/contracts/mock-staking-pool/src/lib.rs index edbad140..8662889a 100644 --- a/contracts/mock-staking-pool/src/lib.rs +++ b/contracts/mock-staking-pool/src/lib.rs @@ -49,6 +49,7 @@ pub struct MockStakingPool { staked: LookupMap, /// for testing purpose, simulates contract panic panic: bool, + get_account_fail: bool, staked_delta: u128, unstaked_delta: u128, @@ -62,6 +63,7 @@ impl MockStakingPool { deposits: LookupMap::new(b"d"), staked: LookupMap::new(b"s"), panic: false, + get_account_fail: false, staked_delta: 0, unstaked_delta: 0, } @@ -89,6 +91,10 @@ impl StakingPool for MockStakingPool { fn get_account(&self, account_id: AccountId) -> HumanReadableAccount { require!(!self.panic, "Test Panic!"); + require!( + !self.get_account_fail, + "get_account() failed, for testing purpose", + ); HumanReadableAccount { account_id: account_id.clone(), staked_balance: U128::from(self.internal_get_staked(&account_id)), @@ -162,6 +168,10 @@ impl MockStakingPool { self.panic = panic; } + pub fn set_get_account_fail(&mut self, value: bool) { + self.get_account_fail = value; + } + pub fn set_balance_delta(&mut self, staked_delta: U128, unstaked_delta: U128) { self.staked_delta = staked_delta.0; self.unstaked_delta = unstaked_delta.0; diff --git a/tests/__tests__/linear/epoch-action-failure.ava.ts b/tests/__tests__/linear/epoch-action-failure.ava.ts index 622a9b91..9404987b 100644 --- a/tests/__tests__/linear/epoch-action-failure.ava.ts +++ b/tests/__tests__/linear/epoch-action-failure.ava.ts @@ -37,7 +37,7 @@ function assertValidatorHelper( } } -workspace.test('epoch stake failure', async (test, { root, contract, owner, alice }) => { +workspace.test('epoch stake failure: deposit_and_stake fails', async (test, { root, contract, owner, alice }) => { const assertValidator = assertValidatorHelper(test, contract, owner); const v1 = await createStakingPool(root, 'v1'); @@ -66,7 +66,7 @@ workspace.test('epoch stake failure', async (test, { root, contract, owner, alic await setPanic(v1); - await owner.call( + const ret = await owner.call( contract, 'epoch_stake', {}, @@ -75,10 +75,116 @@ workspace.test('epoch stake failure', async (test, { root, contract, owner, alic } ); + test.is(ret, null); + // nothing should be staked await assertValidator(v1, '0', '0'); }); +workspace.test('epoch stake failure: get_account fails', async (test, { root, contract, owner, alice }) => { + const assertValidator = assertValidatorHelper(test, contract, owner); + + const v1 = await createStakingPool(root, 'v1'); + + await owner.call( + contract, + 'add_validator', + { + validator_id: v1.accountId, + weight: 10 + }, + { + gas: Gas.parse('100 Tgas') + } + ); + + // user stake + await alice.call( + contract, + 'deposit_and_stake', + {}, + { + attachedDeposit: NEAR.parse('50') + } + ); + + v1.call( + v1, + 'set_get_account_fail', + { + value: true + } + ); + + const ret = await owner.call( + contract, + 'epoch_stake', + {}, + { + gas: Gas.parse('280 Tgas') + } + ); + + test.is(ret, null); + + // stake still succeeded + await assertValidator(v1, '60', '0'); +}); + +workspace.test('epoch stake failure: balance diff to large', async (test, { root, contract, owner, alice }) => { + const assertValidator = assertValidatorHelper(test, contract, owner); + + const v1 = await createStakingPool(root, 'v1'); + + await owner.call( + contract, + 'add_validator', + { + validator_id: v1.accountId, + weight: 10 + }, + { + gas: Gas.parse('100 Tgas') + } + ); + + // user stake + await alice.call( + contract, + 'deposit_and_stake', + {}, + { + attachedDeposit: NEAR.parse('50') + } + ); + + const MAX_SYNC_BALANCE_DIFF = NEAR.from(100); + const diff = MAX_SYNC_BALANCE_DIFF.addn(1); + + await owner.call( + v1, + 'set_balance_delta', + { + staked_delta: diff.toString(10), + unstaked_delta: diff.toString(10), + }, + ); + + const ret = await owner.call( + contract, + 'epoch_stake', + {}, + { + gas: Gas.parse('280 Tgas') + } + ); + + test.is(ret, null); + + // stake still succeeded + await assertValidator(v1, '60', '0'); +}); + workspace.test('unstake failure', async (test, { root, contract, owner, alice }) => { const assertValidator = assertValidatorHelper(test, contract, owner); diff --git a/tests/__tests__/linear/epoch-action.ava.ts b/tests/__tests__/linear/epoch-action.ava.ts index 29bf9f5c..6e127c28 100644 --- a/tests/__tests__/linear/epoch-action.ava.ts +++ b/tests/__tests__/linear/epoch-action.ava.ts @@ -180,6 +180,9 @@ workspace.test('epoch stake', async (test, {root, contract, alice, owner, bob}) await assertValidator(v3, `${30 + 45 + 15}`, '0', '0'); }); +workspace.test('epoch stake', async (test, {root, contract, alice, owner, bob}) => { +}); + workspace.test('epoch unstake', async (test, {root, contract, alice, owner}) => { const assertValidator = assertValidatorAmountHelper(test, contract, owner); From 4de3b9620452a7ad9ef26bdfb738e88a78397e58 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Wed, 30 Aug 2023 15:58:00 +0800 Subject: [PATCH 32/55] epoch_unstake return Option --- contracts/linear/src/epoch_actions.rs | 30 ++-- .../linear/epoch-action-failure.ava.ts | 166 +++++++++++++++++- 2 files changed, 174 insertions(+), 22 deletions(-) diff --git a/contracts/linear/src/epoch_actions.rs b/contracts/linear/src/epoch_actions.rs index 0c2e3258..3f7b1abf 100644 --- a/contracts/linear/src/epoch_actions.rs +++ b/contracts/linear/src/epoch_actions.rs @@ -81,7 +81,7 @@ impl LiquidStakingContract { .into() } - pub fn epoch_unstake(&mut self) -> PromiseOrValue { + pub fn epoch_unstake(&mut self) -> PromiseOrValue> { self.assert_running(); // make sure enough gas was given let min_gas = GAS_EPOCH_UNSTAKE @@ -99,7 +99,7 @@ impl LiquidStakingContract { // after cleanup, there might be no need to unstake if self.unstake_amount_to_settle == 0 { log!("no need to unstake, amount to settle is zero"); - return PromiseOrValue::Value(false); + return PromiseOrValue::Value(Some(false)); } let candidate = self.validator_pool.get_candidate_to_unstake_v2( @@ -108,14 +108,14 @@ impl LiquidStakingContract { ); if candidate.is_none() { log!("no candidate found to unstake"); - return PromiseOrValue::Value(false); + return PromiseOrValue::Value(Some(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); - return PromiseOrValue::Value(false); + return PromiseOrValue::Value(Some(false)); } // update internal state @@ -138,11 +138,6 @@ impl LiquidStakingContract { NO_DEPOSIT, GAS_CB_VALIDATOR_UNSTAKED + GAS_SYNC_BALANCE + GAS_CB_VALIDATOR_SYNC_BALANCE, )) - .then(ext_self_action_cb::validator_return_true_callback( - env::current_account_id(), - NO_DEPOSIT, - GAS_CB_VALIDATOR_RETURN_TRUE, - )) .into() } @@ -250,9 +245,11 @@ trait EpochActionCallbacks { amount: U128, ) -> PromiseOrValue>; - fn validator_unstaked_callback(&mut self, validator_id: AccountId, amount: U128); - - fn validator_return_true_callback(&mut self) -> bool; + fn validator_unstaked_callback( + &mut self, + validator_id: AccountId, + amount: U128, + ) -> PromiseOrValue>; fn validator_get_balance_callback(&mut self, validator_id: AccountId); @@ -265,11 +262,6 @@ trait EpochActionCallbacks { /// functions here SHOULD NOT PANIC! #[near_bindgen] impl LiquidStakingContract { - #[private] - pub fn validator_return_true_callback(&mut self) -> bool { - true - } - #[private] pub fn validator_staked_callback( &mut self, @@ -321,7 +313,7 @@ impl LiquidStakingContract { &mut self, validator_id: AccountId, amount: U128, - ) -> PromiseOrValue<()> { + ) -> PromiseOrValue> { let amount = amount.into(); let mut validator = self .validator_pool @@ -360,7 +352,7 @@ impl LiquidStakingContract { } .emit(); - PromiseOrValue::Value(()) + PromiseOrValue::Value(None) } } diff --git a/tests/__tests__/linear/epoch-action-failure.ava.ts b/tests/__tests__/linear/epoch-action-failure.ava.ts index 9404987b..d6f6c3ca 100644 --- a/tests/__tests__/linear/epoch-action-failure.ava.ts +++ b/tests/__tests__/linear/epoch-action-failure.ava.ts @@ -131,7 +131,7 @@ workspace.test('epoch stake failure: get_account fails', async (test, { root, co await assertValidator(v1, '60', '0'); }); -workspace.test('epoch stake failure: balance diff to large', async (test, { root, contract, owner, alice }) => { +workspace.test('epoch stake failure: balance diff too large', async (test, { root, contract, owner, alice }) => { const assertValidator = assertValidatorHelper(test, contract, owner); const v1 = await createStakingPool(root, 'v1'); @@ -185,7 +185,7 @@ workspace.test('epoch stake failure: balance diff to large', async (test, { root await assertValidator(v1, '60', '0'); }); -workspace.test('unstake failure', async (test, { root, contract, owner, alice }) => { +workspace.test('epoch unstake failure: unstake fails', async (test, { root, contract, owner, alice }) => { const assertValidator = assertValidatorHelper(test, contract, owner); const v1 = await createStakingPool(root, 'v1'); @@ -223,6 +223,12 @@ workspace.test('unstake failure', async (test, { root, contract, owner, alice }) await assertValidator(v1, '60', '0'); + await owner.call( + contract, + 'set_epoch_height', + { epoch: 11 } + ); + // user unstake await alice.call( contract, @@ -232,7 +238,7 @@ workspace.test('unstake failure', async (test, { root, contract, owner, alice }) await setPanic(v1); - await owner.call( + const ret = await owner.call( contract, 'epoch_unstake', {}, @@ -241,10 +247,164 @@ workspace.test('unstake failure', async (test, { root, contract, owner, alice }) } ); + test.is(ret, null); + // no unstake should actual happen await assertValidator(v1, '60', '0'); }); +workspace.test('epoch unstake failure: get_account fails', async (test, { root, contract, owner, alice }) => { + const assertValidator = assertValidatorHelper(test, contract, owner); + + const v1 = await createStakingPool(root, 'v1'); + + await owner.call( + contract, + 'add_validator', + { + validator_id: v1.accountId, + weight: 10 + }, + { + gas: Gas.parse('100 Tgas') + } + ); + + // user stake + await alice.call( + contract, + 'deposit_and_stake', + {}, + { + attachedDeposit: NEAR.parse('50') + } + ); + + await owner.call( + contract, + 'epoch_stake', + {}, + { + gas: Gas.parse('280 Tgas') + } + ); + + await assertValidator(v1, '60', '0'); + + await owner.call( + contract, + 'set_epoch_height', + { epoch: 11 } + ); + + // user unstake + await alice.call( + contract, + 'unstake', + { amount: NEAR.parse('10') } + ); + + v1.call( + v1, + 'set_get_account_fail', + { + value: true + } + ); + + const ret = await owner.call( + contract, + 'epoch_unstake', + {}, + { + gas: Gas.parse('280 Tgas') + } + ); + + test.is(ret, null); + + // unstake still succeeded + await assertValidator(v1, '50', '10'); +}); + +workspace.test('epoch unstake failure: balance diff too large', async (test, { root, contract, owner, alice }) => { + const assertValidator = assertValidatorHelper(test, contract, owner); + + const v1 = await createStakingPool(root, 'v1'); + + await owner.call( + contract, + 'add_validator', + { + validator_id: v1.accountId, + weight: 10 + }, + { + gas: Gas.parse('100 Tgas') + } + ); + + // user stake + await alice.call( + contract, + 'deposit_and_stake', + {}, + { + attachedDeposit: NEAR.parse('50') + } + ); + + await owner.call( + contract, + 'epoch_stake', + {}, + { + gas: Gas.parse('280 Tgas') + } + ); + + await assertValidator(v1, '60', '0'); + + await owner.call( + contract, + 'set_epoch_height', + { epoch: 11 } + ); + + // user unstake + await alice.call( + contract, + 'unstake', + { amount: NEAR.parse('10') } + ); + + const MAX_SYNC_BALANCE_DIFF = NEAR.from(100); + const diff = MAX_SYNC_BALANCE_DIFF.addn(1); + + await owner.call( + v1, + 'set_balance_delta', + { + staked_delta: diff.toString(10), + unstaked_delta: diff.toString(10), + }, + ); + + const ret = await owner.call( + contract, + 'epoch_unstake', + {}, + { + gas: Gas.parse('280 Tgas') + } + ); + + test.is(ret, null); + + // unstake still succeeded + await assertValidator(v1, '50', '10'); +}); + workspace.test('withdraw failure', async (test, { root, contract, owner, alice }) => { const assertValidator = assertValidatorHelper(test, contract, owner); From a81fc3059ed5e5c74e4177587bfb75719cb9ef5c Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Wed, 30 Aug 2023 16:12:43 +0800 Subject: [PATCH 33/55] chores --- contracts/linear/src/epoch_actions.rs | 6 +- tests/__tests__/linear/drain.ava.ts | 31 +---- .../linear/epoch-action-failure.ava.ts | 110 +++--------------- tests/__tests__/linear/epoch-action.ava.ts | 23 +--- tests/__tests__/linear/helper.ts | 24 +++- .../linear/staking-pool-interface.ava.ts | 11 +- tests/__tests__/linear/sync_balance.ava.ts | 38 +----- tests/__tests__/linear/upgrade.ava.ts | 20 +--- 8 files changed, 59 insertions(+), 204 deletions(-) diff --git a/contracts/linear/src/epoch_actions.rs b/contracts/linear/src/epoch_actions.rs index 3f7b1abf..f21aea32 100644 --- a/contracts/linear/src/epoch_actions.rs +++ b/contracts/linear/src/epoch_actions.rs @@ -22,8 +22,7 @@ impl LiquidStakingContract { + GAS_EXT_DEPOSIT_AND_STAKE + GAS_CB_VALIDATOR_STAKED + GAS_SYNC_BALANCE - + GAS_CB_VALIDATOR_SYNC_BALANCE - + GAS_CB_VALIDATOR_RETURN_TRUE; + + GAS_CB_VALIDATOR_SYNC_BALANCE; require!( env::prepaid_gas() >= min_gas, format!("{}. require at least {:?}", ERR_NO_ENOUGH_GAS, min_gas) @@ -88,8 +87,7 @@ impl LiquidStakingContract { + GAS_EXT_UNSTAKE + GAS_CB_VALIDATOR_UNSTAKED + GAS_SYNC_BALANCE - + GAS_CB_VALIDATOR_SYNC_BALANCE - + GAS_CB_VALIDATOR_RETURN_TRUE; + + GAS_CB_VALIDATOR_SYNC_BALANCE; require!( env::prepaid_gas() >= min_gas, format!("{}. require at least {:?}", ERR_NO_ENOUGH_GAS, min_gas) diff --git a/tests/__tests__/linear/drain.ava.ts b/tests/__tests__/linear/drain.ava.ts index 8b0b6bf8..4e5ed700 100644 --- a/tests/__tests__/linear/drain.ava.ts +++ b/tests/__tests__/linear/drain.ava.ts @@ -6,7 +6,9 @@ import { setManager, assertValidatorAmountHelper, updateBaseStakeAmounts, - getValidator + getValidator, + epochUnstake, + epochStake } from "./helper"; const workspace = initWorkSpace(); @@ -14,14 +16,7 @@ const workspace = initWorkSpace(); async function stakeAll (signer: NearAccount, contract: NearAccount) { let run = true; while (run) { - run = await signer.call( - contract, - 'epoch_stake', - {}, - { - gas: Gas.parse('280 Tgas') - } - ); + run = await epochStake(signer, contract); } } @@ -83,14 +78,7 @@ workspace.test('drain constraints', async (test, {contract, root, owner, alice, ); // run stake - await bob.call( - contract, - 'epoch_stake', - {}, - { - gas: Gas.parse('280 Tgas') - } - ); + await epochStake(bob, contract); // 1. cannot drain unstake when weight > 0 await assertFailure( @@ -160,14 +148,7 @@ workspace.test('drain constraints', async (test, {contract, root, owner, alice, {} ); - await bob.call( - contract, - 'epoch_unstake', - {}, - { - gas: Gas.parse('280 Tgas') - } - ); + await epochUnstake(bob, contract); // validator now have unstaked balance > 0 const assertValidator = assertValidatorAmountHelper(test, contract, owner); diff --git a/tests/__tests__/linear/epoch-action-failure.ava.ts b/tests/__tests__/linear/epoch-action-failure.ava.ts index d6f6c3ca..72107f1e 100644 --- a/tests/__tests__/linear/epoch-action-failure.ava.ts +++ b/tests/__tests__/linear/epoch-action-failure.ava.ts @@ -1,5 +1,5 @@ import { NearAccount, NEAR, Gas } from "near-workspaces-ava"; -import { assertFailure, initWorkSpace, createStakingPool, getValidator } from "./helper"; +import { initWorkSpace, createStakingPool, getValidator, epochStake, epochUnstake } from "./helper"; const workspace = initWorkSpace(); @@ -66,14 +66,7 @@ workspace.test('epoch stake failure: deposit_and_stake fails', async (test, { ro await setPanic(v1); - const ret = await owner.call( - contract, - 'epoch_stake', - {}, - { - gas: Gas.parse('280 Tgas') - } - ); + const ret = await epochStake(owner, contract); test.is(ret, null); @@ -116,14 +109,7 @@ workspace.test('epoch stake failure: get_account fails', async (test, { root, co } ); - const ret = await owner.call( - contract, - 'epoch_stake', - {}, - { - gas: Gas.parse('280 Tgas') - } - ); + const ret = await epochStake(owner, contract); test.is(ret, null); @@ -170,14 +156,7 @@ workspace.test('epoch stake failure: balance diff too large', async (test, { roo }, ); - const ret = await owner.call( - contract, - 'epoch_stake', - {}, - { - gas: Gas.parse('280 Tgas') - } - ); + const ret = await epochStake(owner, contract); test.is(ret, null); @@ -212,14 +191,7 @@ workspace.test('epoch unstake failure: unstake fails', async (test, { root, cont } ); - await owner.call( - contract, - 'epoch_stake', - {}, - { - gas: Gas.parse('280 Tgas') - } - ); + await epochStake(owner, contract); await assertValidator(v1, '60', '0'); @@ -238,14 +210,7 @@ workspace.test('epoch unstake failure: unstake fails', async (test, { root, cont await setPanic(v1); - const ret = await owner.call( - contract, - 'epoch_unstake', - {}, - { - gas: Gas.parse('280 Tgas') - } - ); + const ret = await epochUnstake(owner, contract); test.is(ret, null); @@ -280,14 +245,7 @@ workspace.test('epoch unstake failure: get_account fails', async (test, { root, } ); - await owner.call( - contract, - 'epoch_stake', - {}, - { - gas: Gas.parse('280 Tgas') - } - ); + await epochStake(owner, contract); await assertValidator(v1, '60', '0'); @@ -312,14 +270,7 @@ workspace.test('epoch unstake failure: get_account fails', async (test, { root, } ); - const ret = await owner.call( - contract, - 'epoch_unstake', - {}, - { - gas: Gas.parse('280 Tgas') - } - ); + const ret = await epochUnstake(owner, contract); test.is(ret, null); @@ -354,14 +305,7 @@ workspace.test('epoch unstake failure: balance diff too large', async (test, { r } ); - await owner.call( - contract, - 'epoch_stake', - {}, - { - gas: Gas.parse('280 Tgas') - } - ); + await epochStake(owner, contract); await assertValidator(v1, '60', '0'); @@ -390,14 +334,7 @@ workspace.test('epoch unstake failure: balance diff too large', async (test, { r }, ); - const ret = await owner.call( - contract, - 'epoch_unstake', - {}, - { - gas: Gas.parse('280 Tgas') - } - ); + const ret = await epochUnstake(owner, contract); test.is(ret, null); @@ -432,14 +369,7 @@ workspace.test('withdraw failure', async (test, { root, contract, owner, alice } } ); - await owner.call( - contract, - 'epoch_stake', - {}, - { - gas: Gas.parse('280 Tgas') - } - ); + await epochStake(owner, contract); // fast-forward 4 epoch await owner.call( @@ -457,14 +387,7 @@ workspace.test('withdraw failure', async (test, { root, contract, owner, alice } { amount: NEAR.parse('10') } ); - await owner.call( - contract, - 'epoch_unstake', - {}, - { - gas: Gas.parse('280 Tgas') - } - ); + await epochUnstake(owner, contract); await assertValidator(v1, '50', '10'); @@ -520,14 +443,7 @@ workspace.test('get balance failure', async (test, { root, contract, owner, alic } ); - await owner.call( - contract, - 'epoch_stake', - {}, - { - gas: Gas.parse('280 Tgas') - } - ); + await epochStake(owner, contract); await assertValidator(v1, '60', '0'); diff --git a/tests/__tests__/linear/epoch-action.ava.ts b/tests/__tests__/linear/epoch-action.ava.ts index 6e127c28..c991ba1f 100644 --- a/tests/__tests__/linear/epoch-action.ava.ts +++ b/tests/__tests__/linear/epoch-action.ava.ts @@ -6,8 +6,9 @@ import { updateBaseStakeAmounts, setManager, assertValidatorAmountHelper, - getSummary, - skip + skip, + epochStake, + epochUnstake } from "./helper"; const workspace = initWorkSpace(); @@ -15,28 +16,14 @@ const workspace = initWorkSpace(); async function stakeAll (owner: NearAccount, contract: NearAccount) { let run = true; while (run) { - run = await owner.call( - contract, - 'epoch_stake', - {}, - { - gas: Gas.parse('280 Tgas') - } - ); + run = await epochStake(owner, contract); } } async function unstakeAll (owner: NearAccount, contract: NearAccount) { let run = true; while (run) { - run = await owner.call( - contract, - 'epoch_unstake', - {}, - { - gas: Gas.parse('280 Tgas') - } - ); + run = await epochUnstake(owner, contract); } } diff --git a/tests/__tests__/linear/helper.ts b/tests/__tests__/linear/helper.ts index bb1fc8f8..270178bf 100644 --- a/tests/__tests__/linear/helper.ts +++ b/tests/__tests__/linear/helper.ts @@ -1,4 +1,4 @@ -import { Workspace, NEAR, NearAccount, BN } from "near-workspaces-ava"; +import { Workspace, NEAR, NearAccount, BN, Gas } from "near-workspaces-ava"; export const ONE_YOCTO = '1'; export const NUM_EPOCHS_TO_UNLOCK = 4; @@ -334,3 +334,25 @@ export function assertValidatorAmountHelper ( } } } + +export function epochStake(caller: NearAccount, contract: NearAccount): Promise { + return caller.call( + contract, + 'epoch_stake', + {}, + { + gas: Gas.parse('275 Tgas') + } + ); +} + +export function epochUnstake(caller: NearAccount, contract: NearAccount): Promise { + return caller.call( + contract, + 'epoch_unstake', + {}, + { + gas: Gas.parse('275 Tgas') + } + ); +} \ No newline at end of file diff --git a/tests/__tests__/linear/staking-pool-interface.ava.ts b/tests/__tests__/linear/staking-pool-interface.ava.ts index a8677b91..da3231c3 100644 --- a/tests/__tests__/linear/staking-pool-interface.ava.ts +++ b/tests/__tests__/linear/staking-pool-interface.ava.ts @@ -1,5 +1,5 @@ import { NEAR, Gas } from 'near-workspaces-ava'; -import { initWorkSpace, assertFailure, epochHeightFastforward } from './helper'; +import { initWorkSpace, assertFailure, epochHeightFastforward, epochStake } from './helper'; const ERR_UNSTAKED_BALANCE_NOT_AVAILABLE = 'The unstaked balance is not yet available due to unstaking delay'; @@ -268,14 +268,7 @@ workspace.test('late unstake and withdraw', async (test, { contract ,alice }) => ); // call epoch_stake, in order to trigger clean up - await alice.call( - contract, - 'epoch_stake', - {}, - { - gas: Gas.parse('280 Tgas') - } - ); + await epochStake(alice, contract); // unstake const unstakeAmount = NEAR.parse('5'); diff --git a/tests/__tests__/linear/sync_balance.ava.ts b/tests/__tests__/linear/sync_balance.ava.ts index 5d61a73e..79dbc51e 100644 --- a/tests/__tests__/linear/sync_balance.ava.ts +++ b/tests/__tests__/linear/sync_balance.ava.ts @@ -1,4 +1,4 @@ -import { assertFailure, createStakingPool, getValidator, initWorkSpace } from "./helper"; +import { assertFailure, createStakingPool, epochStake, epochUnstake, getValidator, initWorkSpace } from "./helper"; import { Gas, NEAR, NearAccount, ONE_NEAR, stake, } from "near-workspaces-ava"; const MAX_SYNC_BALANCE_DIFF = NEAR.from(100); @@ -80,14 +80,7 @@ workspace.test('sync balance failure', async (test, { root, contract, alice, own ); for (let i = 0; i < 2; i++) { - await owner.call( - contract, - 'epoch_stake', - {}, - { - gas: Gas.parse('280 Tgas') - } - ); + await epochStake(owner, contract); } // v1 amount should not change @@ -106,14 +99,7 @@ workspace.test('sync balance failure', async (test, { root, contract, alice, own ); for (let i = 0; i < 2; i++) { - await owner.call( - contract, - 'epoch_unstake', - {}, - { - gas: Gas.parse('280 Tgas') - } - ); + await epochUnstake(owner, contract); } // v2 amount should not change @@ -170,14 +156,7 @@ workspace.test('sync balance', async (test, { root, contract, alice, owner }) => ); for (let i = 0; i < 2; i++) { - await owner.call( - contract, - 'epoch_stake', - {}, - { - gas: Gas.parse('280 Tgas') - } - ); + await epochStake(owner, contract); } await assertValidator(v2, NEAR.parse("30").sub(diff).toString(10), '0'); @@ -204,14 +183,7 @@ workspace.test('sync balance', async (test, { root, contract, alice, owner }) => ); for (let i = 0; i < 2; i++) { - await owner.call( - contract, - 'epoch_unstake', - {}, - { - gas: Gas.parse('280 Tgas') - } - ); + await epochUnstake(owner, contract); } await assertValidator(v1, NEAR.parse("5").toString(10), NEAR.parse("25").add(diff).toString(10)); diff --git a/tests/__tests__/linear/upgrade.ava.ts b/tests/__tests__/linear/upgrade.ava.ts index 6569f326..13618d1a 100644 --- a/tests/__tests__/linear/upgrade.ava.ts +++ b/tests/__tests__/linear/upgrade.ava.ts @@ -1,7 +1,7 @@ import { readFileSync } from "fs"; import { Gas, NEAR } from "near-units"; import { NearAccount, Workspace } from "near-workspaces-ava"; -import { createStakingPool, getValidator, initAndSetWhitelist, skip, updateBaseStakeAmounts, } from "./helper"; +import { createStakingPool, epochStake, getValidator, initAndSetWhitelist, skip, updateBaseStakeAmounts, } from "./helper"; async function deployLinearAtVersion( root: NearAccount, @@ -34,14 +34,7 @@ async function upgrade(contract: NearAccount, owner: NearAccount) { async function stakeAll (signer: NearAccount, contract: NearAccount) { let run = true; while (run) { - run = await signer.call( - contract, - 'epoch_stake', - {}, - { - gas: Gas.parse('280 Tgas') - } - ); + run = await epochStake(signer, contract); } } @@ -392,14 +385,7 @@ skip('upgrade from v1.4.4 to v1.5.0', async (test, context) => { async function delayedEpochStake(ms: number) { await sleep(ms); - await alice.call( - contract, - 'epoch_stake', - {}, - { - gas: Gas.parse('280 Tgas') - } - ); + await epochStake(alice, contract); } let executed = false; From e8640a4b5facb05e5275a8b0e5f76a98705e9350 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Wed, 30 Aug 2023 16:17:06 +0800 Subject: [PATCH 34/55] make test contracts --- makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/makefile b/makefile index ff5aa79d..6bb80652 100644 --- a/makefile +++ b/makefile @@ -58,7 +58,7 @@ test-unit: TEST_FILE ?= ** LOGS ?= -test-linear: linear_test mock-staking-pool mock-fungible-token mock-dex mock-lockup mock-whitelist +test-contracts: linear_test mock-staking-pool mock-fungible-token mock-dex mock-lockup mock-whitelist @mkdir -p ./tests/compiled-contracts/ @cp ./res/linear_test.wasm ./tests/compiled-contracts/linear.wasm @cp ./res/mock_staking_pool.wasm ./tests/compiled-contracts/mock_staking_pool.wasm @@ -66,6 +66,8 @@ test-linear: linear_test mock-staking-pool mock-fungible-token mock-dex mock-loc @cp ./res/mock_dex.wasm ./tests/compiled-contracts/mock_dex.wasm @cp ./res/mock_lockup.wasm ./tests/compiled-contracts/mock_lockup.wasm @cp ./res/mock_whitelist.wasm ./tests/compiled-contracts/mock_whitelist.wasm + +test-linear: test-contracts cd tests && NEAR_PRINT_LOGS=$(LOGS) npx near-workspaces-ava --timeout=2m __tests__/linear/$(TEST_FILE).ava.ts --verbose test-mock-staking-pool: mock-staking-pool From 4c4fda06cf6a0e8f8220084d4c65eefd5b6dfa87 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Wed, 30 Aug 2023 16:33:42 +0800 Subject: [PATCH 35/55] fix --- tests/__tests__/linear/epoch-action.ava.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/__tests__/linear/epoch-action.ava.ts b/tests/__tests__/linear/epoch-action.ava.ts index c991ba1f..566f5e32 100644 --- a/tests/__tests__/linear/epoch-action.ava.ts +++ b/tests/__tests__/linear/epoch-action.ava.ts @@ -167,9 +167,6 @@ workspace.test('epoch stake', async (test, {root, contract, alice, owner, bob}) await assertValidator(v3, `${30 + 45 + 15}`, '0', '0'); }); -workspace.test('epoch stake', async (test, {root, contract, alice, owner, bob}) => { -}); - workspace.test('epoch unstake', async (test, {root, contract, alice, owner}) => { const assertValidator = assertValidatorAmountHelper(test, contract, owner); From 4581ddc75d31407d1e8212344d2f3877353a523c Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Thu, 31 Aug 2023 12:16:25 +0800 Subject: [PATCH 36/55] drain_unstake return Option --- contracts/linear/src/validator_pool.rs | 13 +++++++++---- tests/__tests__/linear/drain.ava.ts | 4 +++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/contracts/linear/src/validator_pool.rs b/contracts/linear/src/validator_pool.rs index 11922fa3..fbc45071 100644 --- a/contracts/linear/src/validator_pool.rs +++ b/contracts/linear/src/validator_pool.rs @@ -613,7 +613,11 @@ impl LiquidStakingContract { #[ext_contract(ext_self_validator_drain_cb)] trait ValidatorDrainCallbacks { - fn validator_drain_unstaked_callback(&mut self, validator_id: AccountId, amount: U128); + fn validator_drain_unstaked_callback( + &mut self, + validator_id: AccountId, + amount: U128, + ) -> Option; fn validator_drain_withdraw_callback(&mut self, validator_id: AccountId, amount: U128); } @@ -623,7 +627,7 @@ impl LiquidStakingContract { /// This method is designed to drain a validator. /// The weight of target validator should be set to 0 before calling this. /// And a following call to drain_withdraw MUST be made after 4 epoches. - pub fn drain_unstake(&mut self, validator_id: AccountId) -> Promise { + pub fn drain_unstake(&mut self, validator_id: AccountId) -> PromiseOrValue> { self.assert_running(); self.assert_manager(); @@ -685,6 +689,7 @@ impl LiquidStakingContract { GAS_CB_VALIDATOR_UNSTAKED + GAS_SYNC_BALANCE + GAS_CB_VALIDATOR_SYNC_BALANCE, ), ) + .into() } /// Withdraw from a drained validator @@ -745,7 +750,7 @@ impl LiquidStakingContract { &mut self, validator_id: AccountId, amount: U128, - ) -> PromiseOrValue<()> { + ) -> PromiseOrValue> { let amount = amount.into(); let mut validator = self .validator_pool @@ -781,7 +786,7 @@ impl LiquidStakingContract { } .emit(); - PromiseOrValue::Value(()) + PromiseOrValue::Value(None) } } diff --git a/tests/__tests__/linear/drain.ava.ts b/tests/__tests__/linear/drain.ava.ts index 4e5ed700..4ad230d4 100644 --- a/tests/__tests__/linear/drain.ava.ts +++ b/tests/__tests__/linear/drain.ava.ts @@ -279,7 +279,7 @@ workspace.test('drain unstake and withdraw', async (test, {contract, root, owner ] ); - await manager.call( + const ret = await manager.call( contract, 'drain_unstake', { @@ -290,6 +290,8 @@ workspace.test('drain unstake and withdraw', async (test, {contract, root, owner } ); + test.is(ret, true); + // make sure the validator is in draining mode test.assert((await getValidator(contract, v1.accountId)).draining); From 377a6df106856b1d2ebbcc3adee13029c2842f8e Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Thu, 31 Aug 2023 12:17:33 +0800 Subject: [PATCH 37/55] remove GAS_CB_VALIDATOR_RETURN_TRUE --- contracts/linear/src/types.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/linear/src/types.rs b/contracts/linear/src/types.rs index 02fcff64..ed6eb0df 100644 --- a/contracts/linear/src/types.rs +++ b/contracts/linear/src/types.rs @@ -64,7 +64,6 @@ pub const GAS_CB_VALIDATOR_GET_BALANCE: Gas = Gas(25 * TGAS); pub const GAS_CB_VALIDATOR_SYNC_BALANCE: Gas = Gas(25 * TGAS); pub const GAS_CB_VALIDATOR_WITHDRAW: Gas = Gas(25 * TGAS); pub const GAS_CB_WHITELIST: Gas = Gas(15 * TGAS); -pub const GAS_CB_VALIDATOR_RETURN_TRUE: Gas = Gas(5 * TGAS); // -- COMMON TYPES From ed0f7cc471d4f8e45a3ae4fb3bd3c07cf8d348b2 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Thu, 31 Aug 2023 15:32:12 +0800 Subject: [PATCH 38/55] import PromiseError --- contracts/linear/src/epoch_actions.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/contracts/linear/src/epoch_actions.rs b/contracts/linear/src/epoch_actions.rs index f21aea32..d8a544ed 100644 --- a/contracts/linear/src/epoch_actions.rs +++ b/contracts/linear/src/epoch_actions.rs @@ -1,6 +1,5 @@ use crate::*; -use near_sdk::PromiseOrValue; -use near_sdk::{is_promise_success, log, near_bindgen, Balance}; +use near_sdk::{is_promise_success, log, near_bindgen, Balance, PromiseError, PromiseOrValue}; use crate::errors::*; use crate::events::Event; @@ -389,7 +388,7 @@ impl LiquidStakingContract { pub fn validator_get_account_callback( &mut self, validator_id: AccountId, - #[callback_result] result: Result, + #[callback_result] result: Result, ) -> Option { let mut validator = self .validator_pool From 6e683696e0df247e205b13abd85195843283a14a Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Thu, 31 Aug 2023 15:49:42 +0800 Subject: [PATCH 39/55] fix compile --- contracts/linear/src/epoch_actions.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/linear/src/epoch_actions.rs b/contracts/linear/src/epoch_actions.rs index 6b3cdcbb..bcc476fc 100644 --- a/contracts/linear/src/epoch_actions.rs +++ b/contracts/linear/src/epoch_actions.rs @@ -43,15 +43,15 @@ impl LiquidStakingContract { self.epoch_requested_stake_amount -= amount.0; // do staking on selected validator - validator.deposit_and_stake(amount.into()).then( - ext_self_action_cb::validator_staked_callback( + validator + .deposit_and_stake(&mut self.validator_pool, amount.into()) + .then(ext_self_action_cb::validator_staked_callback( validator.account_id.clone(), amount.into(), env::current_account_id(), NO_DEPOSIT, GAS_CB_VALIDATOR_STAKED, - ), - ); + )); } #[cfg(feature = "test")] From 06f1fa1496f7dc4dd958611e8c52ef9beb1d3e1e Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Thu, 31 Aug 2023 15:54:49 +0800 Subject: [PATCH 40/55] only import U46 in test --- contracts/linear/src/validator_pool.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/contracts/linear/src/validator_pool.rs b/contracts/linear/src/validator_pool.rs index 235c7a82..659c883d 100644 --- a/contracts/linear/src/validator_pool.rs +++ b/contracts/linear/src/validator_pool.rs @@ -11,11 +11,14 @@ use near_sdk::{ collections::UnorderedMap, ext_contract, is_promise_success, json_types::U128, - json_types::U64, near_bindgen, require, AccountId, Balance, EpochHeight, Promise, }; + use std::cmp::{max, min, Ordering}; +#[cfg(feature = "test")] +use near_sdk::U64; + const STAKE_SMALL_CHANGE_AMOUNT: Balance = ONE_NEAR; const UNSTAKE_FACTOR: u128 = 2; const MAX_UPDATE_WEIGHTS_COUNT: usize = 300; From 21e1e524534cd6e26d109ef9058a8f20172539b6 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Thu, 31 Aug 2023 15:57:00 +0800 Subject: [PATCH 41/55] fix compile --- contracts/linear/src/validator_pool.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/linear/src/validator_pool.rs b/contracts/linear/src/validator_pool.rs index 659c883d..4b222664 100644 --- a/contracts/linear/src/validator_pool.rs +++ b/contracts/linear/src/validator_pool.rs @@ -17,7 +17,7 @@ use near_sdk::{ use std::cmp::{max, min, Ordering}; #[cfg(feature = "test")] -use near_sdk::U64; +use near_sdk::json_types::U64; const STAKE_SMALL_CHANGE_AMOUNT: Balance = ONE_NEAR; const UNSTAKE_FACTOR: u128 = 2; From 7a7ecbf58275082bd63ab001955f2f93fc3b2c9f Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Thu, 31 Aug 2023 18:14:50 +0800 Subject: [PATCH 42/55] return bool --- contracts/linear/src/epoch_actions.rs | 68 ++++++++++++------- contracts/linear/src/types.rs | 1 + contracts/linear/src/validator_pool.rs | 9 ++- .../linear/epoch-action-failure.ava.ts | 12 ++-- tests/__tests__/linear/helper.ts | 4 +- 5 files changed, 58 insertions(+), 36 deletions(-) diff --git a/contracts/linear/src/epoch_actions.rs b/contracts/linear/src/epoch_actions.rs index d8a544ed..76cd5f1a 100644 --- a/contracts/linear/src/epoch_actions.rs +++ b/contracts/linear/src/epoch_actions.rs @@ -14,14 +14,15 @@ const MAX_SYNC_BALANCE_DIFF: Balance = 100; /// during each epoch. #[near_bindgen] impl LiquidStakingContract { - pub fn epoch_stake(&mut self) -> PromiseOrValue> { + pub fn epoch_stake(&mut self) -> PromiseOrValue { self.assert_running(); // make sure enough gas was given let min_gas = GAS_EPOCH_STAKE + GAS_EXT_DEPOSIT_AND_STAKE + GAS_CB_VALIDATOR_STAKED + GAS_SYNC_BALANCE - + GAS_CB_VALIDATOR_SYNC_BALANCE; + + GAS_CB_VALIDATOR_SYNC_BALANCE + + GAS_CB_VALIDATOR_RETURN_TRUE; require!( env::prepaid_gas() >= min_gas, format!("{}. require at least {:?}", ERR_NO_ENOUGH_GAS, min_gas) @@ -31,7 +32,7 @@ impl LiquidStakingContract { // after cleanup, there might be no need to stake if self.stake_amount_to_settle == 0 { log!("no need to stake, amount to settle is zero"); - return PromiseOrValue::Value(Some(false)); + return PromiseOrValue::Value(false); } let candidate = self @@ -40,7 +41,7 @@ impl LiquidStakingContract { if candidate.is_none() { log!("no candidate found to stake"); - return PromiseOrValue::Value(Some(false)); + return PromiseOrValue::Value(false); } let mut candidate = candidate.unwrap(); @@ -48,7 +49,7 @@ impl LiquidStakingContract { if amount_to_stake < MIN_AMOUNT_TO_PERFORM_STAKE { log!("stake amount too low: {}", amount_to_stake); - return PromiseOrValue::Value(Some(false)); + return PromiseOrValue::Value(false); } require!( @@ -74,19 +75,23 @@ impl LiquidStakingContract { amount_to_stake.into(), env::current_account_id(), NO_DEPOSIT, - GAS_CB_VALIDATOR_STAKED + GAS_SYNC_BALANCE + GAS_CB_VALIDATOR_SYNC_BALANCE, + GAS_CB_VALIDATOR_STAKED + + GAS_SYNC_BALANCE + + GAS_CB_VALIDATOR_SYNC_BALANCE + + GAS_CB_VALIDATOR_RETURN_TRUE, )) .into() } - pub fn epoch_unstake(&mut self) -> PromiseOrValue> { + pub fn epoch_unstake(&mut self) -> PromiseOrValue { self.assert_running(); // make sure enough gas was given let min_gas = GAS_EPOCH_UNSTAKE + GAS_EXT_UNSTAKE + GAS_CB_VALIDATOR_UNSTAKED + GAS_SYNC_BALANCE - + GAS_CB_VALIDATOR_SYNC_BALANCE; + + GAS_CB_VALIDATOR_SYNC_BALANCE + + GAS_CB_VALIDATOR_RETURN_TRUE; require!( env::prepaid_gas() >= min_gas, format!("{}. require at least {:?}", ERR_NO_ENOUGH_GAS, min_gas) @@ -96,7 +101,7 @@ impl LiquidStakingContract { // after cleanup, there might be no need to unstake if self.unstake_amount_to_settle == 0 { log!("no need to unstake, amount to settle is zero"); - return PromiseOrValue::Value(Some(false)); + return PromiseOrValue::Value(false); } let candidate = self.validator_pool.get_candidate_to_unstake_v2( @@ -105,14 +110,14 @@ impl LiquidStakingContract { ); if candidate.is_none() { log!("no candidate found to unstake"); - return PromiseOrValue::Value(Some(false)); + return PromiseOrValue::Value(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); - return PromiseOrValue::Value(Some(false)); + return PromiseOrValue::Value(false); } // update internal state @@ -133,7 +138,10 @@ impl LiquidStakingContract { amount_to_unstake.into(), env::current_account_id(), NO_DEPOSIT, - GAS_CB_VALIDATOR_UNSTAKED + GAS_SYNC_BALANCE + GAS_CB_VALIDATOR_SYNC_BALANCE, + GAS_CB_VALIDATOR_UNSTAKED + + GAS_SYNC_BALANCE + + GAS_CB_VALIDATOR_SYNC_BALANCE + + GAS_CB_VALIDATOR_RETURN_TRUE, )) .into() } @@ -240,19 +248,21 @@ trait EpochActionCallbacks { &mut self, validator_id: AccountId, amount: U128, - ) -> PromiseOrValue>; + ) -> PromiseOrValue; fn validator_unstaked_callback( &mut self, validator_id: AccountId, amount: U128, - ) -> PromiseOrValue>; + ) -> PromiseOrValue; fn validator_get_balance_callback(&mut self, validator_id: AccountId); - fn validator_get_account_callback(&mut self, validator_id: AccountId) -> Option; + fn validator_get_account_callback(&mut self, validator_id: AccountId); fn validator_withdraw_callback(&mut self, validator_id: AccountId, amount: U128); + + fn validator_return_true_callback(&mut self) -> bool; } /// callbacks @@ -264,7 +274,7 @@ impl LiquidStakingContract { &mut self, validator_id: AccountId, amount: U128, - ) -> PromiseOrValue> { + ) -> PromiseOrValue { let amount = amount.into(); let mut validator = self .validator_pool @@ -288,6 +298,11 @@ impl LiquidStakingContract { NO_DEPOSIT, GAS_CB_VALIDATOR_SYNC_BALANCE, )) + .then(ext_self_action_cb::validator_return_true_callback( + env::current_account_id(), + NO_DEPOSIT, + GAS_CB_VALIDATOR_RETURN_TRUE, + )) .into() } else { validator.on_stake_failed(&mut self.validator_pool); @@ -301,7 +316,7 @@ impl LiquidStakingContract { } .emit(); - PromiseOrValue::Value(None) + PromiseOrValue::Value(false) } } @@ -310,7 +325,7 @@ impl LiquidStakingContract { &mut self, validator_id: AccountId, amount: U128, - ) -> PromiseOrValue> { + ) -> PromiseOrValue { let amount = amount.into(); let mut validator = self .validator_pool @@ -334,6 +349,11 @@ impl LiquidStakingContract { NO_DEPOSIT, GAS_CB_VALIDATOR_SYNC_BALANCE, )) + .then(ext_self_action_cb::validator_return_true_callback( + env::current_account_id(), + NO_DEPOSIT, + GAS_CB_VALIDATOR_RETURN_TRUE, + )) .into() } else { // unstake failed, revert @@ -349,7 +369,7 @@ impl LiquidStakingContract { } .emit(); - PromiseOrValue::Value(None) + PromiseOrValue::Value(false) } } @@ -389,7 +409,7 @@ impl LiquidStakingContract { &mut self, validator_id: AccountId, #[callback_result] result: Result, - ) -> Option { + ) { let mut validator = self .validator_pool .get_validator(&validator_id) @@ -427,7 +447,6 @@ impl LiquidStakingContract { account.staked_balance.0, account.unstaked_balance.0, ); - Some(true) } else { Event::SyncValidatorBalanceFailedLargeDiff { validator_id: &validator_id, @@ -440,7 +459,6 @@ impl LiquidStakingContract { } .emit(); validator.on_sync_account_balance_failed(&mut self.validator_pool); - None } } Err(_) => { @@ -452,7 +470,6 @@ impl LiquidStakingContract { } .emit(); validator.on_sync_account_balance_failed(&mut self.validator_pool); - None } } } @@ -484,4 +501,9 @@ impl LiquidStakingContract { .emit(); } } + + #[private] + pub fn validator_return_true_callback(&mut self) -> bool { + true + } } diff --git a/contracts/linear/src/types.rs b/contracts/linear/src/types.rs index ed6eb0df..648fdb9a 100644 --- a/contracts/linear/src/types.rs +++ b/contracts/linear/src/types.rs @@ -63,6 +63,7 @@ pub const GAS_CB_VALIDATOR_UNSTAKED: Gas = Gas(25 * TGAS); pub const GAS_CB_VALIDATOR_GET_BALANCE: Gas = Gas(25 * TGAS); pub const GAS_CB_VALIDATOR_SYNC_BALANCE: Gas = Gas(25 * TGAS); pub const GAS_CB_VALIDATOR_WITHDRAW: Gas = Gas(25 * TGAS); +pub const GAS_CB_VALIDATOR_RETURN_TRUE: Gas = Gas(5 * TGAS); pub const GAS_CB_WHITELIST: Gas = Gas(15 * TGAS); // -- COMMON TYPES diff --git a/contracts/linear/src/validator_pool.rs b/contracts/linear/src/validator_pool.rs index fbc45071..57ca335e 100644 --- a/contracts/linear/src/validator_pool.rs +++ b/contracts/linear/src/validator_pool.rs @@ -617,7 +617,7 @@ trait ValidatorDrainCallbacks { &mut self, validator_id: AccountId, amount: U128, - ) -> Option; + ) -> PromiseOrValue<()>; fn validator_drain_withdraw_callback(&mut self, validator_id: AccountId, amount: U128); } @@ -627,7 +627,7 @@ impl LiquidStakingContract { /// This method is designed to drain a validator. /// The weight of target validator should be set to 0 before calling this. /// And a following call to drain_withdraw MUST be made after 4 epoches. - pub fn drain_unstake(&mut self, validator_id: AccountId) -> PromiseOrValue> { + pub fn drain_unstake(&mut self, validator_id: AccountId) -> Promise { self.assert_running(); self.assert_manager(); @@ -689,7 +689,6 @@ impl LiquidStakingContract { GAS_CB_VALIDATOR_UNSTAKED + GAS_SYNC_BALANCE + GAS_CB_VALIDATOR_SYNC_BALANCE, ), ) - .into() } /// Withdraw from a drained validator @@ -750,7 +749,7 @@ impl LiquidStakingContract { &mut self, validator_id: AccountId, amount: U128, - ) -> PromiseOrValue> { + ) -> PromiseOrValue<()> { let amount = amount.into(); let mut validator = self .validator_pool @@ -786,7 +785,7 @@ impl LiquidStakingContract { } .emit(); - PromiseOrValue::Value(None) + PromiseOrValue::Value(()) } } diff --git a/tests/__tests__/linear/epoch-action-failure.ava.ts b/tests/__tests__/linear/epoch-action-failure.ava.ts index 72107f1e..d72c48f8 100644 --- a/tests/__tests__/linear/epoch-action-failure.ava.ts +++ b/tests/__tests__/linear/epoch-action-failure.ava.ts @@ -68,7 +68,7 @@ workspace.test('epoch stake failure: deposit_and_stake fails', async (test, { ro const ret = await epochStake(owner, contract); - test.is(ret, null); + test.is(ret, false); // nothing should be staked await assertValidator(v1, '0', '0'); @@ -111,7 +111,7 @@ workspace.test('epoch stake failure: get_account fails', async (test, { root, co const ret = await epochStake(owner, contract); - test.is(ret, null); + test.is(ret, true); // stake still succeeded await assertValidator(v1, '60', '0'); @@ -158,7 +158,7 @@ workspace.test('epoch stake failure: balance diff too large', async (test, { roo const ret = await epochStake(owner, contract); - test.is(ret, null); + test.is(ret, true); // stake still succeeded await assertValidator(v1, '60', '0'); @@ -212,7 +212,7 @@ workspace.test('epoch unstake failure: unstake fails', async (test, { root, cont const ret = await epochUnstake(owner, contract); - test.is(ret, null); + test.is(ret, false); // no unstake should actual happen await assertValidator(v1, '60', '0'); @@ -272,7 +272,7 @@ workspace.test('epoch unstake failure: get_account fails', async (test, { root, const ret = await epochUnstake(owner, contract); - test.is(ret, null); + test.is(ret, true); // unstake still succeeded await assertValidator(v1, '50', '10'); @@ -336,7 +336,7 @@ workspace.test('epoch unstake failure: balance diff too large', async (test, { r const ret = await epochUnstake(owner, contract); - test.is(ret, null); + test.is(ret, true); // unstake still succeeded await assertValidator(v1, '50', '10'); diff --git a/tests/__tests__/linear/helper.ts b/tests/__tests__/linear/helper.ts index 270178bf..6064fbc3 100644 --- a/tests/__tests__/linear/helper.ts +++ b/tests/__tests__/linear/helper.ts @@ -341,7 +341,7 @@ export function epochStake(caller: NearAccount, contract: NearAccount): Promise< 'epoch_stake', {}, { - gas: Gas.parse('275 Tgas') + gas: Gas.parse('280 Tgas') } ); } @@ -352,7 +352,7 @@ export function epochUnstake(caller: NearAccount, contract: NearAccount): Promis 'epoch_unstake', {}, { - gas: Gas.parse('275 Tgas') + gas: Gas.parse('280 Tgas') } ); } \ No newline at end of file From a6bdfc5b3271406e3af23a86f00ac9ee10717fd8 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Thu, 31 Aug 2023 18:42:39 +0800 Subject: [PATCH 43/55] tests --- .../linear/epoch-action-failure.ava.ts | 74 +++++++++++++++---- tests/__tests__/linear/helper.ts | 30 +++++++- 2 files changed, 88 insertions(+), 16 deletions(-) diff --git a/tests/__tests__/linear/epoch-action-failure.ava.ts b/tests/__tests__/linear/epoch-action-failure.ava.ts index d72c48f8..58b16f5d 100644 --- a/tests/__tests__/linear/epoch-action-failure.ava.ts +++ b/tests/__tests__/linear/epoch-action-failure.ava.ts @@ -1,5 +1,5 @@ import { NearAccount, NEAR, Gas } from "near-workspaces-ava"; -import { initWorkSpace, createStakingPool, getValidator, epochStake, epochUnstake } from "./helper"; +import { initWorkSpace, createStakingPool, getValidator, epochStake, epochUnstake, epochUnstakeCallRaw, epochStakeCallRaw } from "./helper"; const workspace = initWorkSpace(); @@ -66,9 +66,17 @@ workspace.test('epoch stake failure: deposit_and_stake fails', async (test, { ro await setPanic(v1); - const ret = await epochStake(owner, contract); + const ret = await epochStakeCallRaw(owner, contract); - test.is(ret, false); + test.is(ret.parseResult(), false); + + test.truthy( + ret.result.receipts_outcome.find( + (outcome: any) => outcome.outcome.logs.find( + (log: any) => log.includes('epoch_stake_failed') + ) + ) + ); // nothing should be staked await assertValidator(v1, '0', '0'); @@ -109,9 +117,17 @@ workspace.test('epoch stake failure: get_account fails', async (test, { root, co } ); - const ret = await epochStake(owner, contract); + const ret = await epochStakeCallRaw(owner, contract); + + test.is(ret.parseResult(), true); - test.is(ret, true); + test.truthy( + ret.result.receipts_outcome.find( + (outcome: any) => outcome.outcome.logs.find( + (log: any) => log.includes('sync_validator_balance_failed_cant_get_account') + ) + ) + ); // stake still succeeded await assertValidator(v1, '60', '0'); @@ -156,9 +172,17 @@ workspace.test('epoch stake failure: balance diff too large', async (test, { roo }, ); - const ret = await epochStake(owner, contract); + const ret = await epochStakeCallRaw(owner, contract); + + test.is(ret.parseResult(), true); - test.is(ret, true); + test.truthy( + ret.result.receipts_outcome.find( + (outcome: any) => outcome.outcome.logs.find( + (log: any) => log.includes('sync_validator_balance_failed_large_diff') + ) + ) + ); // stake still succeeded await assertValidator(v1, '60', '0'); @@ -210,9 +234,17 @@ workspace.test('epoch unstake failure: unstake fails', async (test, { root, cont await setPanic(v1); - const ret = await epochUnstake(owner, contract); + const ret = await epochUnstakeCallRaw(owner, contract); - test.is(ret, false); + test.is(ret.parseResult(), false); + + test.truthy( + ret.result.receipts_outcome.find( + (outcome: any) => outcome.outcome.logs.find( + (log: any) => log.includes('epoch_unstake_failed') + ) + ) + ); // no unstake should actual happen await assertValidator(v1, '60', '0'); @@ -270,9 +302,17 @@ workspace.test('epoch unstake failure: get_account fails', async (test, { root, } ); - const ret = await epochUnstake(owner, contract); + const ret = await epochUnstakeCallRaw(owner, contract); + + test.is(ret.parseResult(), true); - test.is(ret, true); + test.truthy( + ret.result.receipts_outcome.find( + (outcome: any) => outcome.outcome.logs.find( + (log: any) => log.includes('sync_validator_balance_failed_cant_get_account') + ) + ) + ); // unstake still succeeded await assertValidator(v1, '50', '10'); @@ -334,9 +374,17 @@ workspace.test('epoch unstake failure: balance diff too large', async (test, { r }, ); - const ret = await epochUnstake(owner, contract); + const ret = await epochUnstakeCallRaw(owner, contract); + + test.is(ret.parseResult(), true); - test.is(ret, true); + test.truthy( + ret.result.receipts_outcome.find( + (outcome: any) => outcome.outcome.logs.find( + (log: any) => log.includes('sync_validator_balance_failed_large_diff') + ) + ) + ); // unstake still succeeded await assertValidator(v1, '50', '10'); diff --git a/tests/__tests__/linear/helper.ts b/tests/__tests__/linear/helper.ts index 6064fbc3..f505c7f0 100644 --- a/tests/__tests__/linear/helper.ts +++ b/tests/__tests__/linear/helper.ts @@ -335,13 +335,26 @@ export function assertValidatorAmountHelper ( } } +const EPOCH_STAKE_AND_UNSTAKE_GAS = Gas.parse('280 Tgas'); + export function epochStake(caller: NearAccount, contract: NearAccount): Promise { return caller.call( contract, 'epoch_stake', {}, { - gas: Gas.parse('280 Tgas') + gas: EPOCH_STAKE_AND_UNSTAKE_GAS + } + ); +} + +export function epochStakeCallRaw(caller: NearAccount, contract: NearAccount): Promise { + return caller.call_raw( + contract, + 'epoch_stake', + {}, + { + gas: EPOCH_STAKE_AND_UNSTAKE_GAS } ); } @@ -352,7 +365,18 @@ export function epochUnstake(caller: NearAccount, contract: NearAccount): Promis 'epoch_unstake', {}, { - gas: Gas.parse('280 Tgas') + gas: EPOCH_STAKE_AND_UNSTAKE_GAS } ); -} \ No newline at end of file +} + +export function epochUnstakeCallRaw(caller: NearAccount, contract: NearAccount): Promise { + return caller.call_raw( + contract, + 'epoch_unstake', + {}, + { + gas: EPOCH_STAKE_AND_UNSTAKE_GAS + } + ); +} From 86e7392c3a5c53cea98dbe6967effa973d4c6eb3 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Thu, 31 Aug 2023 19:46:47 +0800 Subject: [PATCH 44/55] fix a test --- tests/__tests__/linear/drain.ava.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/__tests__/linear/drain.ava.ts b/tests/__tests__/linear/drain.ava.ts index 4ad230d4..45b4e68f 100644 --- a/tests/__tests__/linear/drain.ava.ts +++ b/tests/__tests__/linear/drain.ava.ts @@ -290,8 +290,6 @@ workspace.test('drain unstake and withdraw', async (test, {contract, root, owner } ); - test.is(ret, true); - // make sure the validator is in draining mode test.assert((await getValidator(contract, v1.accountId)).draining); From 49e13659b2acd2212f7250b4b7cb5c1630bc523e Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Fri, 1 Sep 2023 11:59:11 +0800 Subject: [PATCH 45/55] validator_get_account_callback return bool --- contracts/linear/src/epoch_actions.rs | 24 +----- tests/__tests__/linear/drain.ava.ts | 115 +++++++++++++++++++++++++- 2 files changed, 118 insertions(+), 21 deletions(-) diff --git a/contracts/linear/src/epoch_actions.rs b/contracts/linear/src/epoch_actions.rs index a7835d53..4b9a9307 100644 --- a/contracts/linear/src/epoch_actions.rs +++ b/contracts/linear/src/epoch_actions.rs @@ -339,11 +339,9 @@ trait EpochActionCallbacks { fn validator_get_balance_callback(&mut self, validator_id: AccountId); - fn validator_get_account_callback(&mut self, validator_id: AccountId); + fn validator_get_account_callback(&mut self, validator_id: AccountId) -> bool; fn validator_withdraw_callback(&mut self, validator_id: AccountId, amount: U128); - - fn validator_return_true_callback(&mut self) -> bool; } /// callbacks @@ -379,11 +377,6 @@ impl LiquidStakingContract { NO_DEPOSIT, GAS_CB_VALIDATOR_SYNC_BALANCE, )) - .then(ext_self_action_cb::validator_return_true_callback( - env::current_account_id(), - NO_DEPOSIT, - GAS_CB_VALIDATOR_RETURN_TRUE, - )) .into() } else { validator.on_stake_failed(&mut self.validator_pool); @@ -430,11 +423,6 @@ impl LiquidStakingContract { NO_DEPOSIT, GAS_CB_VALIDATOR_SYNC_BALANCE, )) - .then(ext_self_action_cb::validator_return_true_callback( - env::current_account_id(), - NO_DEPOSIT, - GAS_CB_VALIDATOR_RETURN_TRUE, - )) .into() } else { // unstake failed, revert @@ -490,7 +478,7 @@ impl LiquidStakingContract { &mut self, validator_id: AccountId, #[callback_result] result: Result, - ) { + ) -> bool { let mut validator = self .validator_pool .get_validator(&validator_id) @@ -552,7 +540,8 @@ impl LiquidStakingContract { .emit(); validator.on_sync_account_balance_failed(&mut self.validator_pool); } - } + }; + true } #[private] @@ -582,9 +571,4 @@ impl LiquidStakingContract { .emit(); } } - - #[private] - pub fn validator_return_true_callback(&mut self) -> bool { - true - } } diff --git a/tests/__tests__/linear/drain.ava.ts b/tests/__tests__/linear/drain.ava.ts index 45b4e68f..2b46a102 100644 --- a/tests/__tests__/linear/drain.ava.ts +++ b/tests/__tests__/linear/drain.ava.ts @@ -279,7 +279,7 @@ workspace.test('drain unstake and withdraw', async (test, {contract, root, owner ] ); - const ret = await manager.call( + await manager.call( contract, 'drain_unstake', { @@ -340,3 +340,116 @@ workspace.test('drain unstake and withdraw', async (test, {contract, root, owner await assertValidator(v1, '0', '0'); await assertValidator(v2, '60', '0'); }); + +workspace.test('drain unstake: get_account fails', async (test, {contract, root, owner, alice, bob}) => { + const manager = alice; + await setManager(root, contract, owner, manager); + + const v1 = await createStakingPool(root, 'v1'); + const v2 = await createStakingPool(root, 'v2'); + + // add validator + await manager.call( + contract, + 'add_validator', + { + validator_id: v1.accountId, + weight: 10 + }, + { + gas: Gas.parse('100 Tgas') + } + ); + await manager.call( + contract, + 'add_validator', + { + validator_id: v2.accountId, + weight: 10 + }, + { + gas: Gas.parse('100 Tgas') + } + ); + + // update base stake amount of v1 to 20 NEAR + await updateBaseStakeAmounts( + contract, + manager, + [ + v1.accountId, + ], + [ + NEAR.parse("20") + ] + ); + + // user stake + await alice.call( + contract, + 'deposit_and_stake', + {}, + { + attachedDeposit: NEAR.parse('50') + } + ); + + // run stake + await stakeAll(bob, contract); + + /** + * Steps to drain a validator + * 1. set weight to 0 + * 2. set base stake amount to 0 + * 3. call drain_unstake + * 4. call drain_withdraw + */ + + await manager.call( + contract, + 'update_weight', + { + validator_id: v1.accountId, + weight: 0 + } + ); + + // reset base stake amount to 0 NEAR + await updateBaseStakeAmounts( + contract, + manager, + [ + v1.accountId, + ], + [ + NEAR.parse("0") + ] + ); + + v1.call( + v1, + 'set_get_account_fail', + { + value: true + } + ); + + const ret = await manager.call_raw( + contract, + 'drain_unstake', + { + validator_id: v1.accountId + }, + { + gas: Gas.parse('275 Tgas') + } + ); + + test.truthy( + ret.result.receipts_outcome.find( + (outcome: any) => outcome.outcome.logs.find( + (log: any) => log.includes('sync_validator_balance_failed_cant_get_account') + ) + ) + ); +}); From c6b68bac02769f75319e4c73dec8fef6821793ee Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Fri, 1 Sep 2023 12:04:45 +0800 Subject: [PATCH 46/55] validator_get_account_callback return bool --- contracts/linear/src/epoch_actions.rs | 24 +----- tests/__tests__/linear/drain.ava.ts | 115 +++++++++++++++++++++++++- 2 files changed, 118 insertions(+), 21 deletions(-) diff --git a/contracts/linear/src/epoch_actions.rs b/contracts/linear/src/epoch_actions.rs index 76cd5f1a..01227dda 100644 --- a/contracts/linear/src/epoch_actions.rs +++ b/contracts/linear/src/epoch_actions.rs @@ -258,11 +258,9 @@ trait EpochActionCallbacks { fn validator_get_balance_callback(&mut self, validator_id: AccountId); - fn validator_get_account_callback(&mut self, validator_id: AccountId); + fn validator_get_account_callback(&mut self, validator_id: AccountId) -> bool; fn validator_withdraw_callback(&mut self, validator_id: AccountId, amount: U128); - - fn validator_return_true_callback(&mut self) -> bool; } /// callbacks @@ -298,11 +296,6 @@ impl LiquidStakingContract { NO_DEPOSIT, GAS_CB_VALIDATOR_SYNC_BALANCE, )) - .then(ext_self_action_cb::validator_return_true_callback( - env::current_account_id(), - NO_DEPOSIT, - GAS_CB_VALIDATOR_RETURN_TRUE, - )) .into() } else { validator.on_stake_failed(&mut self.validator_pool); @@ -349,11 +342,6 @@ impl LiquidStakingContract { NO_DEPOSIT, GAS_CB_VALIDATOR_SYNC_BALANCE, )) - .then(ext_self_action_cb::validator_return_true_callback( - env::current_account_id(), - NO_DEPOSIT, - GAS_CB_VALIDATOR_RETURN_TRUE, - )) .into() } else { // unstake failed, revert @@ -409,7 +397,7 @@ impl LiquidStakingContract { &mut self, validator_id: AccountId, #[callback_result] result: Result, - ) { + ) -> bool { let mut validator = self .validator_pool .get_validator(&validator_id) @@ -471,7 +459,8 @@ impl LiquidStakingContract { .emit(); validator.on_sync_account_balance_failed(&mut self.validator_pool); } - } + }; + true } #[private] @@ -501,9 +490,4 @@ impl LiquidStakingContract { .emit(); } } - - #[private] - pub fn validator_return_true_callback(&mut self) -> bool { - true - } } diff --git a/tests/__tests__/linear/drain.ava.ts b/tests/__tests__/linear/drain.ava.ts index 45b4e68f..2b46a102 100644 --- a/tests/__tests__/linear/drain.ava.ts +++ b/tests/__tests__/linear/drain.ava.ts @@ -279,7 +279,7 @@ workspace.test('drain unstake and withdraw', async (test, {contract, root, owner ] ); - const ret = await manager.call( + await manager.call( contract, 'drain_unstake', { @@ -340,3 +340,116 @@ workspace.test('drain unstake and withdraw', async (test, {contract, root, owner await assertValidator(v1, '0', '0'); await assertValidator(v2, '60', '0'); }); + +workspace.test('drain unstake: get_account fails', async (test, {contract, root, owner, alice, bob}) => { + const manager = alice; + await setManager(root, contract, owner, manager); + + const v1 = await createStakingPool(root, 'v1'); + const v2 = await createStakingPool(root, 'v2'); + + // add validator + await manager.call( + contract, + 'add_validator', + { + validator_id: v1.accountId, + weight: 10 + }, + { + gas: Gas.parse('100 Tgas') + } + ); + await manager.call( + contract, + 'add_validator', + { + validator_id: v2.accountId, + weight: 10 + }, + { + gas: Gas.parse('100 Tgas') + } + ); + + // update base stake amount of v1 to 20 NEAR + await updateBaseStakeAmounts( + contract, + manager, + [ + v1.accountId, + ], + [ + NEAR.parse("20") + ] + ); + + // user stake + await alice.call( + contract, + 'deposit_and_stake', + {}, + { + attachedDeposit: NEAR.parse('50') + } + ); + + // run stake + await stakeAll(bob, contract); + + /** + * Steps to drain a validator + * 1. set weight to 0 + * 2. set base stake amount to 0 + * 3. call drain_unstake + * 4. call drain_withdraw + */ + + await manager.call( + contract, + 'update_weight', + { + validator_id: v1.accountId, + weight: 0 + } + ); + + // reset base stake amount to 0 NEAR + await updateBaseStakeAmounts( + contract, + manager, + [ + v1.accountId, + ], + [ + NEAR.parse("0") + ] + ); + + v1.call( + v1, + 'set_get_account_fail', + { + value: true + } + ); + + const ret = await manager.call_raw( + contract, + 'drain_unstake', + { + validator_id: v1.accountId + }, + { + gas: Gas.parse('275 Tgas') + } + ); + + test.truthy( + ret.result.receipts_outcome.find( + (outcome: any) => outcome.outcome.logs.find( + (log: any) => log.includes('sync_validator_balance_failed_cant_get_account') + ) + ) + ); +}); From ecf08ed5627fe44c6b595d17559c5d9ba35648e8 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Fri, 1 Sep 2023 19:03:26 +0800 Subject: [PATCH 47/55] remove GAS_CB_VALIDATOR_RETURN_TRUE --- contracts/linear/src/epoch_actions.rs | 16 ++++------------ contracts/linear/src/types.rs | 1 - 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/contracts/linear/src/epoch_actions.rs b/contracts/linear/src/epoch_actions.rs index 01227dda..98bbd748 100644 --- a/contracts/linear/src/epoch_actions.rs +++ b/contracts/linear/src/epoch_actions.rs @@ -21,8 +21,7 @@ impl LiquidStakingContract { + GAS_EXT_DEPOSIT_AND_STAKE + GAS_CB_VALIDATOR_STAKED + GAS_SYNC_BALANCE - + GAS_CB_VALIDATOR_SYNC_BALANCE - + GAS_CB_VALIDATOR_RETURN_TRUE; + + GAS_CB_VALIDATOR_SYNC_BALANCE; require!( env::prepaid_gas() >= min_gas, format!("{}. require at least {:?}", ERR_NO_ENOUGH_GAS, min_gas) @@ -75,10 +74,7 @@ impl LiquidStakingContract { amount_to_stake.into(), env::current_account_id(), NO_DEPOSIT, - GAS_CB_VALIDATOR_STAKED - + GAS_SYNC_BALANCE - + GAS_CB_VALIDATOR_SYNC_BALANCE - + GAS_CB_VALIDATOR_RETURN_TRUE, + GAS_CB_VALIDATOR_STAKED + GAS_SYNC_BALANCE + GAS_CB_VALIDATOR_SYNC_BALANCE, )) .into() } @@ -90,8 +86,7 @@ impl LiquidStakingContract { + GAS_EXT_UNSTAKE + GAS_CB_VALIDATOR_UNSTAKED + GAS_SYNC_BALANCE - + GAS_CB_VALIDATOR_SYNC_BALANCE - + GAS_CB_VALIDATOR_RETURN_TRUE; + + GAS_CB_VALIDATOR_SYNC_BALANCE; require!( env::prepaid_gas() >= min_gas, format!("{}. require at least {:?}", ERR_NO_ENOUGH_GAS, min_gas) @@ -138,10 +133,7 @@ impl LiquidStakingContract { amount_to_unstake.into(), env::current_account_id(), NO_DEPOSIT, - GAS_CB_VALIDATOR_UNSTAKED - + GAS_SYNC_BALANCE - + GAS_CB_VALIDATOR_SYNC_BALANCE - + GAS_CB_VALIDATOR_RETURN_TRUE, + GAS_CB_VALIDATOR_UNSTAKED + GAS_SYNC_BALANCE + GAS_CB_VALIDATOR_SYNC_BALANCE, )) .into() } diff --git a/contracts/linear/src/types.rs b/contracts/linear/src/types.rs index 648fdb9a..ed6eb0df 100644 --- a/contracts/linear/src/types.rs +++ b/contracts/linear/src/types.rs @@ -63,7 +63,6 @@ pub const GAS_CB_VALIDATOR_UNSTAKED: Gas = Gas(25 * TGAS); pub const GAS_CB_VALIDATOR_GET_BALANCE: Gas = Gas(25 * TGAS); pub const GAS_CB_VALIDATOR_SYNC_BALANCE: Gas = Gas(25 * TGAS); pub const GAS_CB_VALIDATOR_WITHDRAW: Gas = Gas(25 * TGAS); -pub const GAS_CB_VALIDATOR_RETURN_TRUE: Gas = Gas(5 * TGAS); pub const GAS_CB_WHITELIST: Gas = Gas(15 * TGAS); // -- COMMON TYPES From ff01ef3a8e003aa1469d9f0dd2cc959fd419c3a6 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Fri, 1 Sep 2023 19:08:38 +0800 Subject: [PATCH 48/55] test interfaces return Promise --- contracts/linear/src/epoch_actions.rs | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/contracts/linear/src/epoch_actions.rs b/contracts/linear/src/epoch_actions.rs index d3212004..953364a4 100644 --- a/contracts/linear/src/epoch_actions.rs +++ b/contracts/linear/src/epoch_actions.rs @@ -19,10 +19,14 @@ impl LiquidStakingContract { // of simulation tests #[payable] #[cfg(feature = "test")] - pub fn stake_to_validator(&mut self, validator_id: AccountId, amount: U128) { + pub fn stake_to_validator(&mut self, validator_id: AccountId, amount: U128) -> Promise { self.assert_running(); // make sure enough gas was given - let min_gas = GAS_EPOCH_STAKE + GAS_EXT_DEPOSIT_AND_STAKE + GAS_CB_VALIDATOR_STAKED; + let min_gas = GAS_EPOCH_STAKE + + GAS_EXT_DEPOSIT_AND_STAKE + + GAS_CB_VALIDATOR_STAKED + + GAS_SYNC_BALANCE + + GAS_CB_VALIDATOR_SYNC_BALANCE; require!( env::prepaid_gas() >= min_gas, @@ -50,15 +54,19 @@ impl LiquidStakingContract { amount.into(), env::current_account_id(), NO_DEPOSIT, - GAS_CB_VALIDATOR_STAKED, - )); + GAS_CB_VALIDATOR_STAKED + GAS_SYNC_BALANCE + GAS_CB_VALIDATOR_SYNC_BALANCE, + )) } #[cfg(feature = "test")] - pub fn unstake_from_validator(&mut self, validator_id: AccountId, amount: U128) { + pub fn unstake_from_validator(&mut self, validator_id: AccountId, amount: U128) -> Promise { self.assert_running(); // make sure enough gas was given - let min_gas = GAS_EPOCH_UNSTAKE + GAS_EXT_UNSTAKE + GAS_CB_VALIDATOR_UNSTAKED; + let min_gas = GAS_EPOCH_UNSTAKE + + GAS_EXT_UNSTAKE + + GAS_CB_VALIDATOR_UNSTAKED + + GAS_SYNC_BALANCE + + GAS_CB_VALIDATOR_SYNC_BALANCE; require!( env::prepaid_gas() >= min_gas, format!("{}. require at least {:?}", ERR_NO_ENOUGH_GAS, min_gas) @@ -85,8 +93,8 @@ impl LiquidStakingContract { amount.into(), env::current_account_id(), NO_DEPOSIT, - GAS_CB_VALIDATOR_STAKED, - )); + GAS_CB_VALIDATOR_STAKED + GAS_SYNC_BALANCE + GAS_CB_VALIDATOR_SYNC_BALANCE, + )) } pub fn epoch_stake(&mut self) -> PromiseOrValue { From 848bf8c8c28a2be6b2a7e971c56f9e61e309ae4a Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Fri, 1 Sep 2023 19:23:56 +0800 Subject: [PATCH 49/55] fix compile --- contracts/linear/src/epoch_actions.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/linear/src/epoch_actions.rs b/contracts/linear/src/epoch_actions.rs index 953364a4..246105dc 100644 --- a/contracts/linear/src/epoch_actions.rs +++ b/contracts/linear/src/epoch_actions.rs @@ -1,4 +1,6 @@ use crate::*; +#[cfg(feature = "test")] +use near_sdk::Promise; use near_sdk::{is_promise_success, log, near_bindgen, Balance, PromiseError, PromiseOrValue}; use crate::errors::*; From 300c6ed7980c7d8be82cdf1ab13ef0e9df12cd6a Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Mon, 4 Sep 2023 15:03:10 +0800 Subject: [PATCH 50/55] perfect existing comments --- contracts/linear/src/validator_pool.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/linear/src/validator_pool.rs b/contracts/linear/src/validator_pool.rs index 57ca335e..e39e8eb2 100644 --- a/contracts/linear/src/validator_pool.rs +++ b/contracts/linear/src/validator_pool.rs @@ -953,7 +953,7 @@ impl Validator { } pub fn on_stake_success(&mut self, pool: &mut ValidatorPool, amount: Balance) { - // Do not post execution here because we need to sync account balance later + // Do not call post_execution() here because we need to sync account balance after stake self.staked_amount += amount; pool.save_validator(self); } @@ -990,6 +990,7 @@ impl Validator { } pub fn on_unstake_success(&mut self, pool: &mut ValidatorPool, amount: Balance) { + // Do not call post_execution() here because we need to sync account balance after unstake self.staked_amount -= amount; self.unstaked_amount += amount; pool.save_validator(self); From 2d08ba42f073a096a66395b83295536ea43de786 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Mon, 4 Sep 2023 15:18:30 +0800 Subject: [PATCH 51/55] change an error's name and content. --- contracts/linear/src/errors.rs | 3 ++- contracts/linear/src/validator_pool.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/linear/src/errors.rs b/contracts/linear/src/errors.rs index 9369d496..ecfddaeb 100644 --- a/contracts/linear/src/errors.rs +++ b/contracts/linear/src/errors.rs @@ -80,7 +80,8 @@ pub const ERR_VALIDATOR_UNSTAKE_WHEN_LOCKED: &str = pub const ERR_VALIDATOR_WITHDRAW_WHEN_LOCKED: &str = "Cannot withdraw from a pending release validator"; pub const ERR_VALIDATOR_ALREADY_EXECUTING_ACTION: &str = "Validator is already executing action"; -pub const ERR_VALIDATOR_SYNC_WHEN_UNLOCKED: &str = "Validator sync balance when unlocked"; +pub const ERR_VALIDATOR_SYNC_BALANCE_NOT_ALLOWED: &str = + "Validator sync balance can only be called after stake or unstake"; // liquidity pool pub const ERR_NON_POSITIVE_MIN_FEE: &str = "The min fee basis points should be positive"; diff --git a/contracts/linear/src/validator_pool.rs b/contracts/linear/src/validator_pool.rs index e39e8eb2..3206df93 100644 --- a/contracts/linear/src/validator_pool.rs +++ b/contracts/linear/src/validator_pool.rs @@ -1029,7 +1029,7 @@ impl Validator { /// different than we requested. /// This method is to sync the actual numbers with the validator. pub fn sync_account_balance(&mut self) -> Promise { - require!(self.executing, ERR_VALIDATOR_SYNC_WHEN_UNLOCKED); + require!(self.executing, ERR_VALIDATOR_SYNC_BALANCE_NOT_ALLOWED); ext_staking_pool::get_account( env::current_account_id(), From 3d3ef9e92e4300ff7d48372175ffeca4316e5638 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Mon, 4 Sep 2023 15:36:14 +0800 Subject: [PATCH 52/55] assertHasLog --- .../linear/epoch-action-failure.ava.ts | 64 ++++++------------- 1 file changed, 21 insertions(+), 43 deletions(-) diff --git a/tests/__tests__/linear/epoch-action-failure.ava.ts b/tests/__tests__/linear/epoch-action-failure.ava.ts index 58b16f5d..7c63ace6 100644 --- a/tests/__tests__/linear/epoch-action-failure.ava.ts +++ b/tests/__tests__/linear/epoch-action-failure.ava.ts @@ -1,4 +1,4 @@ -import { NearAccount, NEAR, Gas } from "near-workspaces-ava"; +import { NearAccount, NEAR, Gas, TransactionResult } from "near-workspaces-ava"; import { initWorkSpace, createStakingPool, getValidator, epochStake, epochUnstake, epochUnstakeCallRaw, epochStakeCallRaw } from "./helper"; const workspace = initWorkSpace(); @@ -37,6 +37,20 @@ function assertValidatorHelper( } } +function assertHasLog( + test: any, + txResult: TransactionResult, + expected: string, +) { + test.truthy( + txResult.result.receipts_outcome.find( + (outcome: any) => outcome.outcome.logs.find( + (log: any) => log.includes(expected) + ) + ) + ); +} + workspace.test('epoch stake failure: deposit_and_stake fails', async (test, { root, contract, owner, alice }) => { const assertValidator = assertValidatorHelper(test, contract, owner); @@ -70,13 +84,7 @@ workspace.test('epoch stake failure: deposit_and_stake fails', async (test, { ro test.is(ret.parseResult(), false); - test.truthy( - ret.result.receipts_outcome.find( - (outcome: any) => outcome.outcome.logs.find( - (log: any) => log.includes('epoch_stake_failed') - ) - ) - ); + assertHasLog(test, ret, 'epoch_stake_failed'); // nothing should be staked await assertValidator(v1, '0', '0'); @@ -121,13 +129,7 @@ workspace.test('epoch stake failure: get_account fails', async (test, { root, co test.is(ret.parseResult(), true); - test.truthy( - ret.result.receipts_outcome.find( - (outcome: any) => outcome.outcome.logs.find( - (log: any) => log.includes('sync_validator_balance_failed_cant_get_account') - ) - ) - ); + assertHasLog(test, ret, 'sync_validator_balance_failed_cant_get_account'); // stake still succeeded await assertValidator(v1, '60', '0'); @@ -176,13 +178,7 @@ workspace.test('epoch stake failure: balance diff too large', async (test, { roo test.is(ret.parseResult(), true); - test.truthy( - ret.result.receipts_outcome.find( - (outcome: any) => outcome.outcome.logs.find( - (log: any) => log.includes('sync_validator_balance_failed_large_diff') - ) - ) - ); + assertHasLog(test, ret, 'sync_validator_balance_failed_large_diff'); // stake still succeeded await assertValidator(v1, '60', '0'); @@ -238,13 +234,7 @@ workspace.test('epoch unstake failure: unstake fails', async (test, { root, cont test.is(ret.parseResult(), false); - test.truthy( - ret.result.receipts_outcome.find( - (outcome: any) => outcome.outcome.logs.find( - (log: any) => log.includes('epoch_unstake_failed') - ) - ) - ); + assertHasLog(test, ret, 'epoch_unstake_failed'); // no unstake should actual happen await assertValidator(v1, '60', '0'); @@ -306,13 +296,7 @@ workspace.test('epoch unstake failure: get_account fails', async (test, { root, test.is(ret.parseResult(), true); - test.truthy( - ret.result.receipts_outcome.find( - (outcome: any) => outcome.outcome.logs.find( - (log: any) => log.includes('sync_validator_balance_failed_cant_get_account') - ) - ) - ); + assertHasLog(test, ret, 'sync_validator_balance_failed_cant_get_account'); // unstake still succeeded await assertValidator(v1, '50', '10'); @@ -378,13 +362,7 @@ workspace.test('epoch unstake failure: balance diff too large', async (test, { r test.is(ret.parseResult(), true); - test.truthy( - ret.result.receipts_outcome.find( - (outcome: any) => outcome.outcome.logs.find( - (log: any) => log.includes('sync_validator_balance_failed_large_diff') - ) - ) - ); + assertHasLog(test, ret, 'sync_validator_balance_failed_large_diff'); // unstake still succeeded await assertValidator(v1, '50', '10'); From 749bd4bc8faccca97f236a2f1f55ce8c82257967 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Mon, 4 Sep 2023 15:38:43 +0800 Subject: [PATCH 53/55] assertHasLog --- tests/__tests__/linear/drain.ava.ts | 11 +++-------- .../linear/epoch-action-failure.ava.ts | 18 ++---------------- tests/__tests__/linear/helper.ts | 16 +++++++++++++++- 3 files changed, 20 insertions(+), 25 deletions(-) diff --git a/tests/__tests__/linear/drain.ava.ts b/tests/__tests__/linear/drain.ava.ts index 2b46a102..76405468 100644 --- a/tests/__tests__/linear/drain.ava.ts +++ b/tests/__tests__/linear/drain.ava.ts @@ -8,7 +8,8 @@ import { updateBaseStakeAmounts, getValidator, epochUnstake, - epochStake + epochStake, + assertHasLog } from "./helper"; const workspace = initWorkSpace(); @@ -445,11 +446,5 @@ workspace.test('drain unstake: get_account fails', async (test, {contract, root, } ); - test.truthy( - ret.result.receipts_outcome.find( - (outcome: any) => outcome.outcome.logs.find( - (log: any) => log.includes('sync_validator_balance_failed_cant_get_account') - ) - ) - ); + assertHasLog(test, ret, 'sync_validator_balance_failed_cant_get_account'); }); diff --git a/tests/__tests__/linear/epoch-action-failure.ava.ts b/tests/__tests__/linear/epoch-action-failure.ava.ts index 7c63ace6..d8eff143 100644 --- a/tests/__tests__/linear/epoch-action-failure.ava.ts +++ b/tests/__tests__/linear/epoch-action-failure.ava.ts @@ -1,5 +1,5 @@ -import { NearAccount, NEAR, Gas, TransactionResult } from "near-workspaces-ava"; -import { initWorkSpace, createStakingPool, getValidator, epochStake, epochUnstake, epochUnstakeCallRaw, epochStakeCallRaw } from "./helper"; +import { NearAccount, NEAR, Gas } from "near-workspaces-ava"; +import { initWorkSpace, createStakingPool, getValidator, epochStake, epochUnstake, epochUnstakeCallRaw, epochStakeCallRaw, assertHasLog } from "./helper"; const workspace = initWorkSpace(); @@ -37,20 +37,6 @@ function assertValidatorHelper( } } -function assertHasLog( - test: any, - txResult: TransactionResult, - expected: string, -) { - test.truthy( - txResult.result.receipts_outcome.find( - (outcome: any) => outcome.outcome.logs.find( - (log: any) => log.includes(expected) - ) - ) - ); -} - workspace.test('epoch stake failure: deposit_and_stake fails', async (test, { root, contract, owner, alice }) => { const assertValidator = assertValidatorHelper(test, contract, owner); diff --git a/tests/__tests__/linear/helper.ts b/tests/__tests__/linear/helper.ts index f505c7f0..47fdee21 100644 --- a/tests/__tests__/linear/helper.ts +++ b/tests/__tests__/linear/helper.ts @@ -1,4 +1,4 @@ -import { Workspace, NEAR, NearAccount, BN, Gas } from "near-workspaces-ava"; +import { Workspace, NEAR, NearAccount, BN, Gas, TransactionResult } from "near-workspaces-ava"; export const ONE_YOCTO = '1'; export const NUM_EPOCHS_TO_UNLOCK = 4; @@ -380,3 +380,17 @@ export function epochUnstakeCallRaw(caller: NearAccount, contract: NearAccount): } ); } + +export function assertHasLog( + test: any, + txResult: TransactionResult, + expected: string, +) { + test.truthy( + txResult.result.receipts_outcome.find( + (outcome: any) => outcome.outcome.logs.find( + (log: any) => log.includes(expected) + ) + ) + ); +} From 59c2300518c724a5106f874f1a0b6a234eb5f950 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Mon, 4 Sep 2023 16:18:45 +0800 Subject: [PATCH 54/55] perfect comments --- contracts/linear/src/epoch_actions.rs | 40 +++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/contracts/linear/src/epoch_actions.rs b/contracts/linear/src/epoch_actions.rs index 98bbd748..fcea58bc 100644 --- a/contracts/linear/src/epoch_actions.rs +++ b/contracts/linear/src/epoch_actions.rs @@ -14,7 +14,18 @@ const MAX_SYNC_BALANCE_DIFF: Balance = 100; /// during each epoch. #[near_bindgen] impl LiquidStakingContract { - pub fn epoch_stake(&mut self) -> PromiseOrValue { + /// Stake $NEAR to one of the validators. + /// + /// Select a candidate validator and stake part of or all of the to-settled + /// stake amounts to this validator. This function is expected to be called + /// in each epoch. + /// + /// # Return + /// * `true` - a candidate validator is selected and successfully staked to. + /// There might be more stake amounts to settle so this function + /// should be called again. + /// * `false` - There is no need to call this function again in this epoch. + pub fn epoch_stake(&mut self) -> PromiseOrValue { self.assert_running(); // make sure enough gas was given let min_gas = GAS_EPOCH_STAKE @@ -79,7 +90,18 @@ impl LiquidStakingContract { .into() } - pub fn epoch_unstake(&mut self) -> PromiseOrValue { + /// Unstake $NEAR from one of the validators. + /// + /// Select a candidate validator and unstake part of or all of the to-settled + /// unstake amounts from this validator. This function is expected to be called + /// in each epoch. + /// + /// # Return + /// * `true` - a candidate validator is selected and successfully unstaked from. + /// There might be more unstake amounts to settle so this function + /// should be called again. + /// * `false` - There is no need to call this function again in this epoch. + pub fn epoch_unstake(&mut self) -> PromiseOrValue { self.assert_running(); // make sure enough gas was given let min_gas = GAS_EPOCH_UNSTAKE @@ -240,13 +262,13 @@ trait EpochActionCallbacks { &mut self, validator_id: AccountId, amount: U128, - ) -> PromiseOrValue; + ) -> PromiseOrValue; fn validator_unstaked_callback( &mut self, validator_id: AccountId, amount: U128, - ) -> PromiseOrValue; + ) -> PromiseOrValue; fn validator_get_balance_callback(&mut self, validator_id: AccountId); @@ -259,12 +281,15 @@ trait EpochActionCallbacks { /// functions here SHOULD NOT PANIC! #[near_bindgen] impl LiquidStakingContract { + /// # Return + /// * `true` - Stake and sync balance succeed + /// * `false` - Stake fails #[private] pub fn validator_staked_callback( &mut self, validator_id: AccountId, amount: U128, - ) -> PromiseOrValue { + ) -> PromiseOrValue { let amount = amount.into(); let mut validator = self .validator_pool @@ -305,12 +330,15 @@ impl LiquidStakingContract { } } + /// # Return + /// * `true` - Unstake and sync balance succeed + /// * `false` - Unstake fails #[private] pub fn validator_unstaked_callback( &mut self, validator_id: AccountId, amount: U128, - ) -> PromiseOrValue { + ) -> PromiseOrValue { let amount = amount.into(); let mut validator = self .validator_pool From 802afd9c51dcf948b3c6574c4537169e50932227 Mon Sep 17 00:00:00 2001 From: 0xdefacto <107129842+0xdefacto@users.noreply.github.com> Date: Mon, 4 Sep 2023 16:22:05 +0800 Subject: [PATCH 55/55] comment --- contracts/linear/src/epoch_actions.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/linear/src/epoch_actions.rs b/contracts/linear/src/epoch_actions.rs index fcea58bc..6ada41f4 100644 --- a/contracts/linear/src/epoch_actions.rs +++ b/contracts/linear/src/epoch_actions.rs @@ -16,7 +16,7 @@ const MAX_SYNC_BALANCE_DIFF: Balance = 100; impl LiquidStakingContract { /// Stake $NEAR to one of the validators. /// - /// Select a candidate validator and stake part of or all of the to-settled + /// Select a candidate validator and stake part of or all of the to-settle /// stake amounts to this validator. This function is expected to be called /// in each epoch. /// @@ -92,7 +92,7 @@ impl LiquidStakingContract { /// Unstake $NEAR from one of the validators. /// - /// Select a candidate validator and unstake part of or all of the to-settled + /// Select a candidate validator and unstake part of or all of the to-settle /// unstake amounts from this validator. This function is expected to be called /// in each epoch. ///