From e81d801a62b2c733c8574afc8f5eb63872d29407 Mon Sep 17 00:00:00 2001 From: gangov <6922910+gangov@users.noreply.github.com> Date: Tue, 10 Dec 2024 15:37:55 +0200 Subject: [PATCH 01/11] consolidate stakes --- contracts/stake/src/contract.rs | 58 +++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/contracts/stake/src/contract.rs b/contracts/stake/src/contract.rs index de24bf62..3344a39a 100644 --- a/contracts/stake/src/contract.rs +++ b/contracts/stake/src/contract.rs @@ -1,7 +1,7 @@ -use phoenix::ttl::{INSTANCE_BUMP_AMOUNT, INSTANCE_LIFETIME_THRESHOLD}; +use phoenix::ttl::{DAY_IN_LEDGERS, INSTANCE_BUMP_AMOUNT, INSTANCE_LIFETIME_THRESHOLD}; use soroban_sdk::{ contract, contractimpl, contractmeta, log, map, panic_with_error, vec, Address, BytesN, Env, - Vec, + String, Vec, }; use crate::{ @@ -22,6 +22,9 @@ use crate::{ token_contract, }; +const SIXTY_DAYS_IN_LEDGER_TIMESTAMP: u64 = DAY_IN_LEDGERS as u64 * 60u64; // safe to be done with + // `as` + // Metadata that is added on to the WASM custom section contractmeta!( key = "Description", @@ -56,6 +59,8 @@ pub trait StakingTrait { fn withdraw_rewards(env: Env, sender: Address); + fn consolidate_stakes(env: Env, sender: Address, stake_timestamps: Vec); + // QUERIES fn query_config(env: Env) -> ConfigResponse; @@ -285,6 +290,55 @@ impl StakingTrait for Staking { save_stakes(&env, &sender, &stakes); } + fn consolidate_stakes(env: Env, sender: Address, stake_timestamps: Vec) { + sender.require_auth(); + + let mut user_bonding_info = get_stakes(&env, &sender); + let current_timestamp = env.ledger().timestamp(); + + // remove the stakes the sender sends us from storage + + // not sure how useful that is, but we + // should be starting our iterator from the + // oldest stake + for st in stake_timestamps.iter().rev() { + // verify that the stakes we are about to remove are > 60 days + assert!( + current_timestamp - st >= SIXTY_DAYS_IN_LEDGER_TIMESTAMP, + "Stake: Consodlidate Stake: Cannot consolidate stake {st} -> less than 60 days for stake." + ); + + let idx_to_remove = user_bonding_info + .stakes + .iter() + .position(|el| el.stake_timestamp == st) + .unwrap_or_else(|| { + log!( + &env, + "Stake: Consolidate Stakes: Cannot find stake for given timestamp: {}", + st + ); + panic_with_error!(&env, ContractError::StakeNotFound); + }); + + user_bonding_info + .stakes + .remove(idx_to_remove as u32) + .unwrap_or_else(|| { + log!( + &env, + "Stake: Consolidate Stakes: Cannot remove stake with given timestamp: {}", + st + ); + panic_with_error!(&env, ContractError::StakeNotFound); + }); + } + + // 2. consolidate the stakes from sender into a single stake + // 3. update the storage again using the longest stake + todo!() + } + // QUERIES fn query_config(env: Env) -> ConfigResponse { From 24370591fd8c8f0a146ac025a8f6203cc5dcd664 Mon Sep 17 00:00:00 2001 From: gangov <6922910+gangov@users.noreply.github.com> Date: Tue, 10 Dec 2024 16:16:39 +0200 Subject: [PATCH 02/11] logic for consolidation --- contracts/stake/src/contract.rs | 45 +++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/contracts/stake/src/contract.rs b/contracts/stake/src/contract.rs index 3344a39a..142283e9 100644 --- a/contracts/stake/src/contract.rs +++ b/contracts/stake/src/contract.rs @@ -294,33 +294,33 @@ impl StakingTrait for Staking { sender.require_auth(); let mut user_bonding_info = get_stakes(&env, &sender); - let current_timestamp = env.ledger().timestamp(); - - // remove the stakes the sender sends us from storage + let present_timestamp = env.ledger().timestamp(); + let mut removed_stakes: Vec = Vec::new(&env); - // not sure how useful that is, but we - // should be starting our iterator from the - // oldest stake - for st in stake_timestamps.iter().rev() { + // remove the stakes the sender sends us from storage in reverse order + for stake_timestamp in stake_timestamps.iter().rev() { // verify that the stakes we are about to remove are > 60 days assert!( - current_timestamp - st >= SIXTY_DAYS_IN_LEDGER_TIMESTAMP, - "Stake: Consodlidate Stake: Cannot consolidate stake {st} -> less than 60 days for stake." + present_timestamp - stake_timestamp >= SIXTY_DAYS_IN_LEDGER_TIMESTAMP, + "Stake: Consodlidate Stake: Cannot consolidate stakes -> less than 60 days for stake." ); - let idx_to_remove = user_bonding_info + let (idx_to_remove, stake_to_remove) = user_bonding_info .stakes .iter() - .position(|el| el.stake_timestamp == st) + .enumerate() + .find(|(_, el)| el.stake_timestamp == stake_timestamp) .unwrap_or_else(|| { log!( &env, "Stake: Consolidate Stakes: Cannot find stake for given timestamp: {}", - st + stake_timestamp ); panic_with_error!(&env, ContractError::StakeNotFound); }); + removed_stakes.push_back(stake_to_remove); + user_bonding_info .stakes .remove(idx_to_remove as u32) @@ -328,15 +328,28 @@ impl StakingTrait for Staking { log!( &env, "Stake: Consolidate Stakes: Cannot remove stake with given timestamp: {}", - st + stake_timestamp ); panic_with_error!(&env, ContractError::StakeNotFound); }); } - // 2. consolidate the stakes from sender into a single stake - // 3. update the storage again using the longest stake - todo!() + let mut new_stake = Stake { + stake: 0, + stake_timestamp: env.ledger().timestamp(), // just a placeholder + }; + + // consolidate the stakes from sender into a single stake + let _ = removed_stakes.iter().map(|old_stake| { + new_stake.stake += old_stake.stake; + if new_stake.stake_timestamp > old_stake.stake_timestamp { + new_stake.stake_timestamp = old_stake.stake_timestamp; + } + }); + + // update the storage again using the new consolidated stake + user_bonding_info.stakes.push_back(new_stake); + save_stakes(&env, &sender, &user_bonding_info); } // QUERIES From 17b8be412dc7fd4179c30dd911fa19a29dceb6b3 Mon Sep 17 00:00:00 2001 From: gangov <6922910+gangov@users.noreply.github.com> Date: Tue, 10 Dec 2024 16:19:20 +0200 Subject: [PATCH 03/11] bumps the storage ttl --- contracts/stake/src/contract.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contracts/stake/src/contract.rs b/contracts/stake/src/contract.rs index 142283e9..3fc629b3 100644 --- a/contracts/stake/src/contract.rs +++ b/contracts/stake/src/contract.rs @@ -292,6 +292,9 @@ impl StakingTrait for Staking { fn consolidate_stakes(env: Env, sender: Address, stake_timestamps: Vec) { sender.require_auth(); + env.storage() + .instance() + .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT); let mut user_bonding_info = get_stakes(&env, &sender); let present_timestamp = env.ledger().timestamp(); From 18eb8f14234a94851b1ca05e8a65225ad1fe0f87 Mon Sep 17 00:00:00 2001 From: gangov <6922910+gangov@users.noreply.github.com> Date: Tue, 10 Dec 2024 16:24:48 +0200 Subject: [PATCH 04/11] lints --- contracts/stake/src/contract.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/stake/src/contract.rs b/contracts/stake/src/contract.rs index 3fc629b3..7b96c6a2 100644 --- a/contracts/stake/src/contract.rs +++ b/contracts/stake/src/contract.rs @@ -1,7 +1,7 @@ use phoenix::ttl::{DAY_IN_LEDGERS, INSTANCE_BUMP_AMOUNT, INSTANCE_LIFETIME_THRESHOLD}; use soroban_sdk::{ contract, contractimpl, contractmeta, log, map, panic_with_error, vec, Address, BytesN, Env, - String, Vec, + Vec, }; use crate::{ From 2c4daf70dae57c15fc6197b2ba7317571913ceb2 Mon Sep 17 00:00:00 2001 From: gangov <6922910+gangov@users.noreply.github.com> Date: Tue, 10 Dec 2024 16:36:53 +0200 Subject: [PATCH 05/11] scenarios for testing --- contracts/stake/src/tests/bond.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/contracts/stake/src/tests/bond.rs b/contracts/stake/src/tests/bond.rs index 74dba88b..48bed95f 100644 --- a/contracts/stake/src/tests/bond.rs +++ b/contracts/stake/src/tests/bond.rs @@ -459,3 +459,15 @@ fn initialize_staking_contract_should_panic_when_max_complexity_invalid() { &0u32, ); } + +// cases for the consolidation +// 1. happy path - all stakes are after 60 days +// 2. many stakes, just a few are after 60 days +// 2.1. many stakes, many are after 60 days +// 3. all stakes are less than 60 days +// 4. mixture of valid and invalid stakes +// 5. non-existing timestamp +// 6. no stakes at all +// 7. just one stake at the 60 days threshold +// 8. few valid stakes and one `invalid` stake +// 9. different user tries to consolidate 3rd party stakes From 8dae473f62dc66540dce8d8055df07cc91802086 Mon Sep 17 00:00:00 2001 From: gangov <6922910+gangov@users.noreply.github.com> Date: Tue, 10 Dec 2024 23:33:07 +0200 Subject: [PATCH 06/11] first test --- contracts/stake/src/contract.rs | 10 +- contracts/stake/src/tests/bond.rs | 142 ++++++++++++++++++++++++++++- contracts/stake/src/tests/setup.rs | 2 +- 3 files changed, 143 insertions(+), 11 deletions(-) diff --git a/contracts/stake/src/contract.rs b/contracts/stake/src/contract.rs index 7b96c6a2..73d8ac78 100644 --- a/contracts/stake/src/contract.rs +++ b/contracts/stake/src/contract.rs @@ -301,7 +301,7 @@ impl StakingTrait for Staking { let mut removed_stakes: Vec = Vec::new(&env); // remove the stakes the sender sends us from storage in reverse order - for stake_timestamp in stake_timestamps.iter().rev() { + for stake_timestamp in stake_timestamps.iter() { // verify that the stakes we are about to remove are > 60 days assert!( present_timestamp - stake_timestamp >= SIXTY_DAYS_IN_LEDGER_TIMESTAMP, @@ -339,16 +339,16 @@ impl StakingTrait for Staking { let mut new_stake = Stake { stake: 0, - stake_timestamp: env.ledger().timestamp(), // just a placeholder + stake_timestamp: 0, // just a placeholder }; // consolidate the stakes from sender into a single stake - let _ = removed_stakes.iter().map(|old_stake| { + for old_stake in removed_stakes.iter() { new_stake.stake += old_stake.stake; - if new_stake.stake_timestamp > old_stake.stake_timestamp { + if new_stake.stake_timestamp < old_stake.stake_timestamp { new_stake.stake_timestamp = old_stake.stake_timestamp; } - }); + } // update the storage again using the new consolidated stake user_bonding_info.stakes.push_back(new_stake); diff --git a/contracts/stake/src/tests/bond.rs b/contracts/stake/src/tests/bond.rs index 48bed95f..30c7a415 100644 --- a/contracts/stake/src/tests/bond.rs +++ b/contracts/stake/src/tests/bond.rs @@ -13,7 +13,7 @@ use crate::{ contract::{Staking, StakingClient}, msg::{ConfigResponse, StakedResponse}, storage::{Config, Stake}, - tests::setup::{ONE_DAY, ONE_WEEK}, + tests::setup::{ONE_DAY, ONE_WEEK, SIXTY_DAYS}, }; const DEFAULT_COMPLEXITY: u32 = 7; @@ -460,10 +460,142 @@ fn initialize_staking_contract_should_panic_when_max_complexity_invalid() { ); } -// cases for the consolidation -// 1. happy path - all stakes are after 60 days -// 2. many stakes, just a few are after 60 days -// 2.1. many stakes, many are after 60 days +#[test] +fn should_consolidate_all_stakes_after_sixty_days() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let user = Address::generate(&env); + let lp_token = deploy_token_contract(&env, &admin); + let manager = Address::generate(&env); + let owner = Address::generate(&env); + + let staking = deploy_staking_contract( + &env, + admin.clone(), + &lp_token.address, + &manager, + &owner, + &DEFAULT_COMPLEXITY, + ); + + lp_token.mint(&user, &100_000); + let mut user_stakes: Vec = Vec::new(&env); + + // stake 25 days + for _ in 0..5 { + env.ledger().with_mut(|li| { + li.timestamp += ONE_DAY * 5; + }); + + staking.bond(&user, &1_000); + user_stakes.push_back(Stake { + stake: 1_000, + stake_timestamp: env.ledger().timestamp(), + }) + } + + // move forward to day 60 + env.ledger().with_mut(|li| li.timestamp = SIXTY_DAYS); + + // 5 more stakes after day #60 + for _ in 0..5 { + env.ledger().with_mut(|li| { + li.timestamp += ONE_DAY * 5; + }); + + staking.bond(&user, &1_000); + user_stakes.push_back(Stake { + stake: 1_000, + stake_timestamp: env.ledger().timestamp(), + }) + } + + env.ledger().with_mut(|li| { + li.timestamp += SIXTY_DAYS * 5; + }); + + assert_eq!(staking.query_staked(&user).stakes, user_stakes); + + let mut last_three: Vec = Vec::new(&env); + + for stake in user_stakes.iter().rev().take(3) { + last_three.push_front(stake.stake_timestamp); + } + + staking.consolidate_stakes(&user, &last_three); + let updated_stakes = vec![ + &env, + Stake { + stake: 1_000, + stake_timestamp: 432000, + }, + Stake { + stake: 1_000, + stake_timestamp: 864000, + }, + Stake { + stake: 1_000, + stake_timestamp: 1296000, + }, + Stake { + stake: 1_000, + stake_timestamp: 1728000, + }, + Stake { + stake: 1_000, + stake_timestamp: 2160000, + }, + Stake { + stake: 1_000, + stake_timestamp: 5616000, + }, + Stake { + stake: 1_000, + stake_timestamp: 6048000, + }, + Stake { + stake: 3_000, + stake_timestamp: 7344000, + }, + ]; + assert_eq!(staking.query_staked(&user).stakes, updated_stakes); + + // consolidating one more time + staking.consolidate_stakes(&user, &vec![&env, 7344000u64, 6048000u64, 5616000u64]); + + assert_eq!( + staking.query_staked(&user).stakes, + vec![ + &env, + Stake { + stake: 1_000, + stake_timestamp: 432000 + }, + Stake { + stake: 1_000, + stake_timestamp: 864000 + }, + Stake { + stake: 1_000, + stake_timestamp: 1296000 + }, + Stake { + stake: 1_000, + stake_timestamp: 1728000 + }, + Stake { + stake: 1_000, + stake_timestamp: 2160000 + }, + Stake { + stake: 5_000, + stake_timestamp: 7344000 + } + ] + ); +} // 3. all stakes are less than 60 days // 4. mixture of valid and invalid stakes // 5. non-existing timestamp diff --git a/contracts/stake/src/tests/setup.rs b/contracts/stake/src/tests/setup.rs index 9d393a5a..240a075b 100644 --- a/contracts/stake/src/tests/setup.rs +++ b/contracts/stake/src/tests/setup.rs @@ -27,8 +27,8 @@ fn install_stake_latest_wasm(env: &Env) -> BytesN<32> { const MIN_BOND: i128 = 1000; const MIN_REWARD: i128 = 1000; -pub const ONE_WEEK: u64 = 604800; pub const ONE_DAY: u64 = 86400; +pub const ONE_WEEK: u64 = 604800; pub const SIXTY_DAYS: u64 = 60 * ONE_DAY; pub fn deploy_staking_contract<'a>( From 9cd15ab4d5c1f68c41c9d72ad7941bae8ca04162 Mon Sep 17 00:00:00 2001 From: gangov <6922910+gangov@users.noreply.github.com> Date: Tue, 10 Dec 2024 23:48:33 +0200 Subject: [PATCH 07/11] invalid test case --- contracts/stake/src/tests/bond.rs | 50 +++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/contracts/stake/src/tests/bond.rs b/contracts/stake/src/tests/bond.rs index 30c7a415..470a24dc 100644 --- a/contracts/stake/src/tests/bond.rs +++ b/contracts/stake/src/tests/bond.rs @@ -597,6 +597,56 @@ fn should_consolidate_all_stakes_after_sixty_days() { ); } // 3. all stakes are less than 60 days + +#[test] +#[should_panic( + expected = "Stake: Consodlidate Stake: Cannot consolidate stakes -> less than 60 days for stake." +)] +fn should_fail_consolidation_when_all_stakes_are_less_than_60_days() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let user = Address::generate(&env); + let lp_token = deploy_token_contract(&env, &admin); + let manager = Address::generate(&env); + let owner = Address::generate(&env); + + let staking = deploy_staking_contract( + &env, + admin.clone(), + &lp_token.address, + &manager, + &owner, + &DEFAULT_COMPLEXITY, + ); + + lp_token.mint(&user, &50_000); + + // ensure all stakes are less than 60 days old + let mut user_stakes: Vec = Vec::new(&env); + for _ in 0..5 { + env.ledger().with_mut(|li| { + li.timestamp += ONE_DAY * 5; + }); + + staking.bond(&user, &1_000); + user_stakes.push_back(Stake { + stake: 1_000, + stake_timestamp: env.ledger().timestamp(), + }); + } + + assert_eq!(staking.query_staked(&user).stakes, user_stakes); + + let mut stake_timestamps: Vec = Vec::new(&env); + for stake in user_stakes.iter() { + stake_timestamps.push_front(stake.stake_timestamp); + } + + staking.consolidate_stakes(&user, &stake_timestamps); +} + // 4. mixture of valid and invalid stakes // 5. non-existing timestamp // 6. no stakes at all From 455b109fe28d0472bd5611ab1344527cf746e60e Mon Sep 17 00:00:00 2001 From: gangov <6922910+gangov@users.noreply.github.com> Date: Wed, 11 Dec 2024 11:50:43 +0200 Subject: [PATCH 08/11] test with consolidating and distributing rewards --- contracts/stake/src/tests/bond.rs | 1 - contracts/stake/src/tests/distribution.rs | 90 ++++++++++++++++++++++- 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/contracts/stake/src/tests/bond.rs b/contracts/stake/src/tests/bond.rs index 470a24dc..e7fd6753 100644 --- a/contracts/stake/src/tests/bond.rs +++ b/contracts/stake/src/tests/bond.rs @@ -596,7 +596,6 @@ fn should_consolidate_all_stakes_after_sixty_days() { ] ); } -// 3. all stakes are less than 60 days #[test] #[should_panic( diff --git a/contracts/stake/src/tests/distribution.rs b/contracts/stake/src/tests/distribution.rs index 9a20517d..95349ed1 100644 --- a/contracts/stake/src/tests/distribution.rs +++ b/contracts/stake/src/tests/distribution.rs @@ -8,8 +8,9 @@ use super::setup::{deploy_staking_contract, deploy_token_contract}; use pretty_assertions::assert_eq; use crate::{ + contract::{Staking, StakingClient}, msg::{WithdrawableReward, WithdrawableRewardsResponse}, - tests::setup::SIXTY_DAYS, + tests::setup::{ONE_WEEK, SIXTY_DAYS}, }; #[test] @@ -1194,3 +1195,90 @@ fn distribute_rewards_daily_multiple_times_same_stakes() { assert_eq!(reward_token.balance(&staking.address), 0); } + +// test should behave as `add_distribution_and_distribute_reward` but with consolidating the stakes +#[test] +fn consolidating_stakes_and_distribute_rewards() { + let env = Env::default(); + env.mock_all_auths(); + env.budget().reset_unlimited(); + + let admin = Address::generate(&env); + let user = Address::generate(&env); + let manager = Address::generate(&env); + let lp_token = deploy_token_contract(&env, &admin); + let reward_token = deploy_token_contract(&env, &admin); + + let staking = StakingClient::new(&env, &env.register(Staking, ())); + + staking.initialize( + &admin, + &lp_token.address, + &50, + &1_000, + &manager, + &admin, + &50u32, + ); + + staking.create_distribution_flow(&admin, &reward_token.address); + + let reward_amount: i128 = 100_000; + reward_token.mint(&admin, &reward_amount); + + lp_token.mint(&user, &1_000); + + // first 30 days + for _ in 0..3 { + staking.bond(&user, &100); + env.ledger().with_mut(|li| { + li.timestamp += ONE_WEEK; + }); + } + + // second 30 days + env.ledger().with_mut(|li| { + li.timestamp += ONE_WEEK * 2; + }); + + for _ in 0..4 { + staking.bond(&user, &100); + env.ledger().with_mut(|li| { + li.timestamp += ONE_WEEK; + }); + } + + // one month forward + env.ledger().with_mut(|li| { + li.timestamp += SIXTY_DAYS; + }); + + staking.consolidate_stakes( + &user, + &vec![&env, 3024000u64, 3628800u64, 4233600u64, 4838400u64], + ); + + for _ in 0..60 { + staking.distribute_rewards(&admin, &(reward_amount / 60i128), &reward_token.address); + env.ledger().with_mut(|li| { + li.timestamp += 3600 * 24; + }); + } + + assert_eq!( + staking.query_withdrawable_rewards(&user), + WithdrawableRewardsResponse { + rewards: vec![ + &env, + WithdrawableReward { + reward_address: reward_token.address.clone(), + reward_amount: 99_960_u128 + } + ] + } + ); + + staking.withdraw_rewards(&user); + + assert_eq!(reward_token.balance(&user), 99_960); +} From 3a70687f1572eff50db802cfba1892a7e6863881 Mon Sep 17 00:00:00 2001 From: gangov <6922910+gangov@users.noreply.github.com> Date: Wed, 11 Dec 2024 14:00:17 +0200 Subject: [PATCH 09/11] final tests --- contracts/stake/src/contract.rs | 2 +- contracts/stake/src/tests/bond.rs | 187 ++++++++++++++++++++++++++++-- 2 files changed, 181 insertions(+), 8 deletions(-) diff --git a/contracts/stake/src/contract.rs b/contracts/stake/src/contract.rs index 73d8ac78..f2bdd2c3 100644 --- a/contracts/stake/src/contract.rs +++ b/contracts/stake/src/contract.rs @@ -305,7 +305,7 @@ impl StakingTrait for Staking { // verify that the stakes we are about to remove are > 60 days assert!( present_timestamp - stake_timestamp >= SIXTY_DAYS_IN_LEDGER_TIMESTAMP, - "Stake: Consodlidate Stake: Cannot consolidate stakes -> less than 60 days for stake." + "Stake: Consolidate Stake: Cannot consolidate stakes -> less than 60 days for stake." ); let (idx_to_remove, stake_to_remove) = user_bonding_info diff --git a/contracts/stake/src/tests/bond.rs b/contracts/stake/src/tests/bond.rs index e7fd6753..8871853f 100644 --- a/contracts/stake/src/tests/bond.rs +++ b/contracts/stake/src/tests/bond.rs @@ -599,7 +599,7 @@ fn should_consolidate_all_stakes_after_sixty_days() { #[test] #[should_panic( - expected = "Stake: Consodlidate Stake: Cannot consolidate stakes -> less than 60 days for stake." + expected = "Stake: Consolidate Stake: Cannot consolidate stakes -> less than 60 days for stake." )] fn should_fail_consolidation_when_all_stakes_are_less_than_60_days() { let env = Env::default(); @@ -646,9 +646,182 @@ fn should_fail_consolidation_when_all_stakes_are_less_than_60_days() { staking.consolidate_stakes(&user, &stake_timestamps); } -// 4. mixture of valid and invalid stakes -// 5. non-existing timestamp -// 6. no stakes at all -// 7. just one stake at the 60 days threshold -// 8. few valid stakes and one `invalid` stake -// 9. different user tries to consolidate 3rd party stakes +#[test] +#[should_panic( + expected = "Stake: Consolidate Stake: Cannot consolidate stakes -> less than 60 days for stake." +)] +fn should_fail_consolidation_with_mixture_of_valid_and_invalid_stakes() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let user = Address::generate(&env); + let lp_token = deploy_token_contract(&env, &admin); + let manager = Address::generate(&env); + let owner = Address::generate(&env); + + let staking = deploy_staking_contract( + &env, + admin.clone(), + &lp_token.address, + &manager, + &owner, + &DEFAULT_COMPLEXITY, + ); + + lp_token.mint(&user, &50_000); + + let mut user_stakes_timestamps: Vec = Vec::new(&env); + + // less than 60 days + for _ in 0..2 { + env.ledger().with_mut(|li| li.timestamp += ONE_WEEK); + staking.bond(&user, &1_000); + user_stakes_timestamps.push_back(env.ledger().timestamp()); + } + + // older than 60 days + env.ledger().with_mut(|li| li.timestamp += SIXTY_DAYS); + + for _ in 0..3 { + env.ledger().with_mut(|li| li.timestamp += ONE_WEEK); + staking.bond(&user, &1_000); + user_stakes_timestamps.push_back(env.ledger().timestamp()); + } + + staking.consolidate_stakes(&user, &user_stakes_timestamps); +} + +#[test] +#[should_panic(expected = "Stake: Consolidate Stakes: Cannot find stake for given timestamp")] +fn should_fail_consolidation_with_non_existing_timestamp() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let user = Address::generate(&env); + let lp_token = deploy_token_contract(&env, &admin); + let manager = Address::generate(&env); + let owner = Address::generate(&env); + + let staking = deploy_staking_contract( + &env, + admin.clone(), + &lp_token.address, + &manager, + &owner, + &DEFAULT_COMPLEXITY, + ); + + lp_token.mint(&user, &50_000); + + for _ in 0..3 { + env.ledger().with_mut(|li| li.timestamp += ONE_WEEK); + staking.bond(&user, &1_000); + } + + let invalid_timestamp = 0; // the non-existing stake + + staking.consolidate_stakes(&user, &vec![&env, invalid_timestamp]); +} + +#[test] +#[should_panic( + expected = "Stake: Consolidate Stake: Cannot consolidate stakes -> less than 60 days for stake." +)] +fn should_fail_consolidation_with_no_stakes() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let user = Address::generate(&env); + let lp_token = deploy_token_contract(&env, &admin); + let manager = Address::generate(&env); + let owner = Address::generate(&env); + + let staking = deploy_staking_contract( + &env, + admin.clone(), + &lp_token.address, + &manager, + &owner, + &DEFAULT_COMPLEXITY, + ); + + let timestamp = ONE_WEEK; + env.ledger().with_mut(|li| li.timestamp = timestamp); + + staking.consolidate_stakes(&user, &vec![&env, timestamp]); +} + +#[test] +fn should_consolidate_one_stake_at_sixty_days_threshold() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let user = Address::generate(&env); + let lp_token = deploy_token_contract(&env, &admin); + let manager = Address::generate(&env); + let owner = Address::generate(&env); + + let staking = deploy_staking_contract( + &env, + admin.clone(), + &lp_token.address, + &manager, + &owner, + &DEFAULT_COMPLEXITY, + ); + + lp_token.mint(&user, &50_000); + + env.ledger().with_mut(|li| li.timestamp = 0); + staking.bond(&user, &1_000); + + env.ledger().with_mut(|li| li.timestamp = SIXTY_DAYS); + + let stake_timestamps = vec![&env, 0]; + + staking.consolidate_stakes(&user, &stake_timestamps); + + assert_eq!( + staking.query_staked(&user).stakes, + vec![ + &env, + Stake { + stake: 1_000, + stake_timestamp: 0 + } + ] + ); +} + +#[test] +#[should_panic(expected = "HostError: Error(Auth, InvalidAction)")] +fn should_fail_consolidation_when_different_user_tries_to_consolidate() { + let env = Env::default(); + + let admin = Address::generate(&env); + let user1 = Address::generate(&env); + let user2 = Address::generate(&env); // Unauthorized user + let lp_token = deploy_token_contract(&env, &admin); + let manager = Address::generate(&env); + let owner = Address::generate(&env); + + let staking = deploy_staking_contract( + &env, + admin.clone(), + &lp_token.address, + &manager, + &owner, + &DEFAULT_COMPLEXITY, + ); + + lp_token.mint(&user1, &50_000); + + env.ledger().with_mut(|li| li.timestamp = SIXTY_DAYS); + staking.bond(&user1, &1_000); + + staking.consolidate_stakes(&user2, &vec![&env, SIXTY_DAYS]); +} From 3f23928065314d2a8b73ad7de5ec323ab565fbdb Mon Sep 17 00:00:00 2001 From: gangov <6922910+gangov@users.noreply.github.com> Date: Fri, 3 Jan 2025 13:35:52 +0200 Subject: [PATCH 10/11] [no ci] removes unnecessary comment --- contracts/stake/src/contract.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/stake/src/contract.rs b/contracts/stake/src/contract.rs index f2bdd2c3..f94fc163 100644 --- a/contracts/stake/src/contract.rs +++ b/contracts/stake/src/contract.rs @@ -22,8 +22,7 @@ use crate::{ token_contract, }; -const SIXTY_DAYS_IN_LEDGER_TIMESTAMP: u64 = DAY_IN_LEDGERS as u64 * 60u64; // safe to be done with - // `as` +const SIXTY_DAYS_IN_LEDGER_TIMESTAMP: u64 = DAY_IN_LEDGERS as u64 * 60u64; // Metadata that is added on to the WASM custom section contractmeta!( From bcca665a82efc8ccb916f22f8f00cd97fb68bd9e Mon Sep 17 00:00:00 2001 From: gangov <6922910+gangov@users.noreply.github.com> Date: Thu, 16 Jan 2025 11:10:18 +0200 Subject: [PATCH 11/11] fixes a deprecated method --- contracts/stake/src/tests/distribution.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/stake/src/tests/distribution.rs b/contracts/stake/src/tests/distribution.rs index 95349ed1..5b79b2e8 100644 --- a/contracts/stake/src/tests/distribution.rs +++ b/contracts/stake/src/tests/distribution.rs @@ -1201,7 +1201,7 @@ fn distribute_rewards_daily_multiple_times_same_stakes() { fn consolidating_stakes_and_distribute_rewards() { let env = Env::default(); env.mock_all_auths(); - env.budget().reset_unlimited(); + env.cost_estimate().budget().reset_unlimited(); let admin = Address::generate(&env); let user = Address::generate(&env);