diff --git a/pallets/dapp-staking-v3/src/lib.rs b/pallets/dapp-staking-v3/src/lib.rs index fa46eb88e5..4890fb301e 100644 --- a/pallets/dapp-staking-v3/src/lib.rs +++ b/pallets/dapp-staking-v3/src/lib.rs @@ -455,7 +455,6 @@ pub mod pallet { Self::ensure_pallet_enabled()?; let account = ensure_signed(origin)?; - let state = ActiveProtocolState::::get(); let mut ledger = Ledger::::get(&account); // Calculate & check amount available for locking @@ -464,11 +463,8 @@ pub mod pallet { let amount_to_lock = available_balance.min(amount); ensure!(!amount_to_lock.is_zero(), Error::::ZeroAmount); - // Only lock for the next era onwards. - let lock_era = state.era.saturating_add(1); - ledger - .add_lock_amount(amount_to_lock, lock_era) - .map_err(|_| Error::::TooManyLockedBalanceChunks)?; + ledger.add_lock_amount(amount_to_lock); + ensure!( ledger.active_locked_amount() >= T::MinimumLockedAmount::get(), Error::::LockedAmountBelowThreshold @@ -522,9 +518,7 @@ pub mod pallet { ensure!(!amount_to_unlock.is_zero(), Error::::ZeroAmount); // Update ledger with new lock and unlocking amounts - ledger - .subtract_lock_amount(amount_to_unlock, state.era) - .map_err(|_| Error::::TooManyLockedBalanceChunks)?; + ledger.subtract_lock_amount(amount_to_unlock); let current_block = frame_system::Pallet::::block_number(); let unlock_block = current_block.saturating_add(T::UnlockingPeriod::get()); @@ -577,18 +571,13 @@ pub mod pallet { Self::ensure_pallet_enabled()?; let account = ensure_signed(origin)?; - let state = ActiveProtocolState::::get(); let mut ledger = Ledger::::get(&account); ensure!(!ledger.unlocking.is_empty(), Error::::NoUnlockingChunks); - // Only lock for the next era onwards. - let lock_era = state.era.saturating_add(1); let amount = ledger.consume_unlocking_chunks(); - ledger - .add_lock_amount(amount, lock_era) - .map_err(|_| Error::::TooManyLockedBalanceChunks)?; + ledger.add_lock_amount(amount); ensure!( ledger.active_locked_amount() >= T::MinimumLockedAmount::get(), Error::::LockedAmountBelowThreshold @@ -626,15 +615,14 @@ pub mod pallet { let protocol_state = ActiveProtocolState::::get(); let mut ledger = Ledger::::get(&account); + let stake_era = protocol_state.era.saturating_add(1); + + // TODO: staker should be staking from the NEXT era // 1. - // Increase stake amount for the current era & period in staker's ledger + // Increase stake amount for the next era & current period in staker's ledger ledger - .add_stake_amount( - amount, - protocol_state.era, - protocol_state.period_info.number, - ) + .add_stake_amount(amount, stake_era, protocol_state.period_info.number) .map_err(|err| match err { AccountLedgerError::InvalidPeriod => { Error::::UnclaimedRewardsFromPastPeriods @@ -646,10 +634,9 @@ pub mod pallet { // 2. // Update `StakerInfo` storage with the new stake amount on the specified contract. + // TODO: this might need to be modified - if rewards were claimed for the past period, it should be ok to overwrite the old storage item. let new_staking_info = if let Some(mut staking_info) = StakerInfo::::get(&account, &smart_contract) { - // TODO: do I need to check for which period this is for? Not sure, but maybe it's safer to do so. - // TODO2: revisit this later. ensure!( staking_info.period_number() == protocol_state.period_info.number, Error::::InternalStakeError @@ -674,7 +661,7 @@ pub mod pallet { let mut contract_stake_info = ContractStake::::get(&smart_contract); ensure!( contract_stake_info - .stake(amount, protocol_state.period_info, protocol_state.era) + .stake(amount, protocol_state.period_info, stake_era) .is_ok(), Error::::InternalStakeError ); @@ -700,9 +687,7 @@ pub mod pallet { // TODO: maybe keep track of pending bonus rewards in the AccountLedger struct? // That way it's easy to check if stake can even be called - bonus-rewards should be zero & last staked era should be None or current one. - // is it voting or b&e period? - // how much does user have available for staking? - // has user claimed past rewards? Can we force them to do it before they start staking again? + // TODO: has user claimed past rewards? Can we force them to do it before they start staking again? Ok(()) } diff --git a/pallets/dapp-staking-v3/src/test/testing_utils.rs b/pallets/dapp-staking-v3/src/test/testing_utils.rs index 466fce6c6a..e377a8a555 100644 --- a/pallets/dapp-staking-v3/src/test/testing_utils.rs +++ b/pallets/dapp-staking-v3/src/test/testing_utils.rs @@ -197,14 +197,6 @@ pub(crate) fn assert_lock(account: AccountId, amount: Balance) { locked_balance + expected_lock_amount, "Locked balance should be increased by the amount locked." ); - assert_eq!( - post_snapshot - .ledger - .get(&account) - .expect("Ledger entry has to exist after succcessful lock call") - .lock_era(), - post_snapshot.active_protocol_state.era + 1 - ); assert_eq!( post_snapshot.current_era_info.total_locked, @@ -382,10 +374,6 @@ pub(crate) fn assert_relock_unlocking(account: AccountId) { post_ledger.active_locked_amount(), pre_snapshot.ledger[&account].active_locked_amount() + amount ); - assert_eq!( - post_ledger.lock_era(), - post_snapshot.active_protocol_state.era + 1 - ); // Current era info assert_eq!( diff --git a/pallets/dapp-staking-v3/src/test/tests.rs b/pallets/dapp-staking-v3/src/test/tests.rs index 1d3b757b4d..508b830707 100644 --- a/pallets/dapp-staking-v3/src/test/tests.rs +++ b/pallets/dapp-staking-v3/src/test/tests.rs @@ -355,35 +355,6 @@ fn lock_with_incorrect_amount_fails() { }) } -#[test] -fn lock_with_too_many_chunks_fails() { - ExtBuilder::build().execute_with(|| { - let max_locked_chunks = ::MaxLockedChunks::get(); - let minimum_locked_amount: Balance = - ::MinimumLockedAmount::get(); - - // Fill up the locked chunks to the limit - let locker = 1; - assert_lock(locker, minimum_locked_amount); - for current_era in 1..max_locked_chunks { - advance_to_era(current_era + 1); - assert_lock(locker, 1); - } - - // Ensure we can still lock in the current era since number of chunks should not increase - for _ in 0..10 { - assert_lock(locker, 1); - } - - // Advance to the next era and ensure it's not possible to add additional chunks - advance_to_era(ActiveProtocolState::::get().era + 1); - assert_noop!( - DappStaking::lock(RuntimeOrigin::signed(locker), 1), - Error::::TooManyLockedBalanceChunks, - ); - }) -} - #[test] fn unlock_basic_example_is_ok() { ExtBuilder::build().execute_with(|| { @@ -558,33 +529,6 @@ fn unlock_with_zero_amount_fails() { }) } -#[test] -fn unlock_with_exceeding_locked_storage_limits_fails() { - ExtBuilder::build().execute_with(|| { - let account = 2; - let lock_amount = 103; - assert_lock(account, lock_amount); - - let unlock_amount = 3; - for _ in 0..::MaxLockedChunks::get() { - advance_to_era(ActiveProtocolState::::get().era + 1); - assert_unlock(account, unlock_amount); - } - - // We can still unlock in the current era, theoretically - for _ in 0..5 { - assert_unlock(account, unlock_amount); - } - - // Following unlock should fail due to exceeding storage limits - advance_to_era(ActiveProtocolState::::get().era + 1); - assert_noop!( - DappStaking::unlock(RuntimeOrigin::signed(account), unlock_amount), - Error::::TooManyLockedBalanceChunks, - ); - }) -} - #[test] fn unlock_with_exceeding_unlocking_chunks_storage_limits_fails() { ExtBuilder::build().execute_with(|| { @@ -709,26 +653,6 @@ fn relock_unlocking_no_chunks_fails() { }) } -#[test] -fn relock_unlocking_too_many_chunks_fails() { - ExtBuilder::build().execute_with(|| { - let max_locked_chunks = ::MaxLockedChunks::get(); - - // Fill up the locked chunks to the limit - let account = 3; - for current_era in 1..=max_locked_chunks { - assert_lock(account, 11); - advance_to_era(current_era + 1); - } - assert_unlock(account, 7); - - assert_noop!( - DappStaking::relock_unlocking(RuntimeOrigin::signed(account)), - Error::::TooManyLockedBalanceChunks, - ); - }) -} - #[test] fn relock_unlocking_insufficient_lock_amount_fails() { ExtBuilder::build().execute_with(|| { diff --git a/pallets/dapp-staking-v3/src/test/tests_types.rs b/pallets/dapp-staking-v3/src/test/tests_types.rs index eaaf97ef1a..81e153f8e1 100644 --- a/pallets/dapp-staking-v3/src/test/tests_types.rs +++ b/pallets/dapp-staking-v3/src/test/tests_types.rs @@ -315,110 +315,52 @@ fn protocol_state_default() { #[test] fn account_ledger_default() { - get_u32_type!(LockedDummy, 5); get_u32_type!(UnlockingDummy, 5); get_u32_type!(StakingDummy, 8); - let acc_ledger = - AccountLedger::::default(); + let acc_ledger = AccountLedger::::default(); assert!(acc_ledger.is_empty()); assert!(acc_ledger.active_locked_amount().is_zero()); - assert!(acc_ledger.lock_era().is_zero()); - assert!(acc_ledger.latest_locked_chunk().is_none()); } #[test] fn account_ledger_add_lock_amount_works() { - get_u32_type!(LockedDummy, 5); get_u32_type!(UnlockingDummy, 5); get_u32_type!(StakingDummy, 8); - let mut acc_ledger = - AccountLedger::::default(); + let mut acc_ledger = AccountLedger::::default(); // First step, sanity checks - let first_era = 1; assert!(acc_ledger.active_locked_amount().is_zero()); assert!(acc_ledger.total_locked_amount().is_zero()); - assert!(acc_ledger.add_lock_amount(0, first_era).is_ok()); + acc_ledger.add_lock_amount(0); assert!(acc_ledger.active_locked_amount().is_zero()); // Adding lock value works as expected let init_amount = 20; - assert!(acc_ledger.add_lock_amount(init_amount, first_era).is_ok()); + acc_ledger.add_lock_amount(init_amount); assert_eq!(acc_ledger.active_locked_amount(), init_amount); assert_eq!(acc_ledger.total_locked_amount(), init_amount); - assert_eq!(acc_ledger.lock_era(), first_era); assert!(!acc_ledger.is_empty()); - assert_eq!(acc_ledger.locked.0.len(), 1); - assert_eq!( - acc_ledger.latest_locked_chunk(), - Some(&LockedChunk { - amount: init_amount, - era: first_era, - }) - ); - - // Add to the same era - let addition = 7; - assert!(acc_ledger.add_lock_amount(addition, first_era).is_ok()); - assert_eq!(acc_ledger.active_locked_amount(), init_amount + addition); - assert_eq!(acc_ledger.total_locked_amount(), init_amount + addition); - assert_eq!(acc_ledger.lock_era(), first_era); - assert_eq!(acc_ledger.locked.0.len(), 1); - - // Adding to previous era should fail - assert_eq!( - acc_ledger.add_lock_amount(addition, first_era - 1), - Err(AccountLedgerError::OldEra) - ); - - // Add up to storage limit - for i in 2..=LockedDummy::get() { - assert!(acc_ledger.add_lock_amount(addition, first_era + i).is_ok()); - assert_eq!( - acc_ledger.active_locked_amount(), - init_amount + addition * i as u128 - ); - assert_eq!(acc_ledger.lock_era(), first_era + i); - assert_eq!(acc_ledger.locked.0.len(), i as usize); - } - - // Any further additions should fail due to exhausting bounded storage capacity - let acc_ledger_clone = acc_ledger.clone(); - assert_eq!( - acc_ledger.add_lock_amount(addition, acc_ledger.lock_era() + 1), - Err(AccountLedgerError::NoCapacity) - ); - assert_eq!(acc_ledger, acc_ledger_clone); } #[test] fn account_ledger_subtract_lock_amount_basic_usage_works() { - get_u32_type!(LockedDummy, 5); get_u32_type!(UnlockingDummy, 5); get_u32_type!(StakingDummy, 8); - let mut acc_ledger = - AccountLedger::::default(); + let mut acc_ledger = AccountLedger::::default(); // Sanity check scenario // Cannot reduce if there is nothing locked, should be a noop - assert!(acc_ledger.subtract_lock_amount(0, 1).is_ok()); - assert!(acc_ledger.subtract_lock_amount(10, 1).is_ok()); - assert!(acc_ledger.locked.0.len().is_zero()); + acc_ledger.subtract_lock_amount(0); + acc_ledger.subtract_lock_amount(10); assert!(acc_ledger.is_empty()); // First basic scenario - // Add some lock amount, then reduce it for the same era - let first_era = 1; + // Add some lock amount, then reduce it let first_lock_amount = 19; let unlock_amount = 7; - assert!(acc_ledger - .add_lock_amount(first_lock_amount, first_era) - .is_ok()); - assert!(acc_ledger - .subtract_lock_amount(unlock_amount, first_era) - .is_ok()); - assert_eq!(acc_ledger.locked.0.len(), 1); + acc_ledger.add_lock_amount(first_lock_amount); + acc_ledger.subtract_lock_amount(unlock_amount); assert_eq!( acc_ledger.total_locked_amount(), first_lock_amount - unlock_amount @@ -430,321 +372,24 @@ fn account_ledger_subtract_lock_amount_basic_usage_works() { assert_eq!(acc_ledger.unlocking_amount(), 0); // Second basic scenario - // Reduce the lock from the era which isn't latest in the vector let first_lock_amount = first_lock_amount - unlock_amount; let second_lock_amount = 31; - let second_era = 2; - assert!(acc_ledger - .add_lock_amount(second_lock_amount - first_lock_amount, second_era) - .is_ok()); + acc_ledger.add_lock_amount(second_lock_amount - first_lock_amount); assert_eq!(acc_ledger.active_locked_amount(), second_lock_amount); - assert_eq!(acc_ledger.locked.0.len(), 2); // Subtract from the first era and verify state is as expected - assert!(acc_ledger - .subtract_lock_amount(unlock_amount, first_era) - .is_ok()); - assert_eq!(acc_ledger.locked.0.len(), 2); + acc_ledger.subtract_lock_amount(unlock_amount); assert_eq!( acc_ledger.active_locked_amount(), second_lock_amount - unlock_amount ); - assert_eq!( - acc_ledger.locked.0[0].amount, - first_lock_amount - unlock_amount - ); - assert_eq!( - acc_ledger.locked.0[1].amount, - second_lock_amount - unlock_amount - ); - - // Third basic scenario - // Reduce the the latest era, don't expect the first one to change - assert!(acc_ledger - .subtract_lock_amount(unlock_amount, second_era) - .is_ok()); - assert_eq!(acc_ledger.locked.0.len(), 2); - assert_eq!( - acc_ledger.active_locked_amount(), - second_lock_amount - unlock_amount * 2 - ); - assert_eq!( - acc_ledger.locked.0[0].amount, - first_lock_amount - unlock_amount - ); - assert_eq!( - acc_ledger.locked.0[1].amount, - second_lock_amount - unlock_amount * 2 - ); -} - -#[test] -fn account_ledger_subtract_lock_amount_overflow_fails() { - get_u32_type!(LockedDummy, 5); - get_u32_type!(UnlockingDummy, 5); - get_u32_type!(StakingDummy, 8); - let mut acc_ledger = - AccountLedger::::default(); - - let first_lock_amount = 17 * 19; - let era = 1; - let unlock_amount = 5; - assert!(acc_ledger.add_lock_amount(first_lock_amount, era).is_ok()); - for idx in 1..=LockedDummy::get() { - assert!(acc_ledger.subtract_lock_amount(unlock_amount, idx).is_ok()); - assert_eq!(acc_ledger.locked.0.len(), idx as usize); - assert_eq!( - acc_ledger.active_locked_amount(), - first_lock_amount - unlock_amount * idx as u128 - ); - } - - // Updating existing lock should still work - let locked_snapshot = acc_ledger.locked.0.clone(); - for i in 1..10 { - assert!(acc_ledger - .subtract_lock_amount(unlock_amount, LockedDummy::get()) - .is_ok()); - assert_eq!(acc_ledger.locked.0.len(), LockedDummy::get() as usize); - - let last_idx = LockedDummy::get() as usize - 1; - assert_eq!( - &acc_ledger.locked.0[0..last_idx], - &locked_snapshot[0..last_idx] - ); - assert_eq!( - acc_ledger.locked.0[last_idx].amount as u128 + unlock_amount * i, - locked_snapshot[last_idx].amount - ); - } - - // Attempt to add additional chunks should fail, and is a noop. - let acc_ledger_clone = acc_ledger.clone(); - assert_eq!( - acc_ledger.subtract_lock_amount(unlock_amount, LockedDummy::get() + 1), - Err(AccountLedgerError::NoCapacity) - ); - assert_eq!(acc_ledger, acc_ledger_clone); -} - -#[test] -fn account_ledger_subtract_lock_amount_advanced_example_works() { - get_u32_type!(LockedDummy, 5); - get_u32_type!(UnlockingDummy, 5); - get_u32_type!(StakingDummy, 8); - let mut acc_ledger = - AccountLedger::::default(); - - // Prepare an example where we have two non-consecutive entries, and we unlock in the era right before the second entry. - // This covers a scenario where user has called `lock` in the current era, - // creating an entry for the next era, and then decides to immediately unlock a portion of the locked amount. - let first_lock_amount = 17; - let second_lock_amount = 23; - let first_era = 1; - let second_era = 5; - let unlock_era = second_era - 1; - let unlock_amount = 5; - assert!(acc_ledger - .add_lock_amount(first_lock_amount, first_era) - .is_ok()); - assert!(acc_ledger - .add_lock_amount(second_lock_amount, second_era) - .is_ok()); - assert_eq!(acc_ledger.locked.0.len(), 2); - - assert!(acc_ledger - .subtract_lock_amount(unlock_amount, unlock_era) - .is_ok()); - assert_eq!( - acc_ledger.active_locked_amount(), - first_lock_amount + second_lock_amount - unlock_amount - ); - - // Check entries in more detail - assert_eq!(acc_ledger.locked.0.len(), 3); - assert_eq!(acc_ledger.locked.0[0].amount, first_lock_amount,); - assert_eq!( - acc_ledger.locked.0[2].amount, - first_lock_amount + second_lock_amount - unlock_amount - ); - // Verify the new entry is as expected - assert_eq!( - acc_ledger.locked.0[1].amount, - first_lock_amount - unlock_amount - ); - assert_eq!(acc_ledger.locked.0[1].era, unlock_era); -} - -#[test] -fn account_ledger_subtract_lock_amount_with_only_one_locked_chunk() { - get_u32_type!(LockedDummy, 5); - get_u32_type!(UnlockingDummy, 5); - get_u32_type!(StakingDummy, 8); - let mut acc_ledger = - AccountLedger::::default(); - - // Scenario: user locks for era 2 while era 1 is active, immediately followed by unlock call. - // Locked amount should be updated for the next era, but active locked amount should be unchanged (zero). - let lock_amount = 17; - let unlock_amount = 5; - let lock_era = 2; - let unlock_era = 1; - assert!(acc_ledger.add_lock_amount(lock_amount, lock_era).is_ok()); - assert!(acc_ledger - .subtract_lock_amount(unlock_amount, unlock_era) - .is_ok()); - - assert_eq!(acc_ledger.locked.0.len(), 1); - assert_eq!( - acc_ledger.locked.0[0], - LockedChunk { - amount: lock_amount - unlock_amount, - era: lock_era, - } - ); -} - -#[test] -fn account_ledger_subtract_lock_amount_correct_zero_cleanup() { - get_u32_type!(LockedDummy, 5); - get_u32_type!(UnlockingDummy, 5); - get_u32_type!(StakingDummy, 8); - let mut acc_ledger = - AccountLedger::::default(); - - // Ensure that zero entries are cleaned up correctly when required. - // There are a couple of distinct scenarios: - // 1. There is only one entry, and it's zero. The vector should be cleared & empty. - // 2. There are multiple entries, and the last one is zero. It's valid since it marks when someone fully unlocked. - // 3. Zero entry can exist in between two non-zero entries (not covered in this UT). - - // 1st scenario (A) - only one zero entry, unlock is in the same era - let lock_amount = 17; - let lock_era = 2; - assert!(acc_ledger.add_lock_amount(lock_amount, lock_era).is_ok()); - assert!(acc_ledger - .subtract_lock_amount(lock_amount, lock_era) - .is_ok()); - assert!(acc_ledger.locked.0.is_empty()); - - // 1st scenario (B) - only one zero entry, unlock is in the previous era - assert!(acc_ledger.add_lock_amount(lock_amount, lock_era).is_ok()); - assert!(acc_ledger - .subtract_lock_amount(lock_amount, lock_era - 1) - .is_ok()); - assert!(acc_ledger.locked.0.is_empty()); - - // 2nd scenario - last entry is zero - let first_lock_era = 3; - let second_lock_era = 11; - let unlock_era = second_lock_era + 2; - assert!(acc_ledger - .add_lock_amount(lock_amount, first_lock_era) - .is_ok()); - assert!(acc_ledger - .add_lock_amount(lock_amount, second_lock_era) - .is_ok()); - // Following should add new entry, to mark when the user fully unlocked - assert!(acc_ledger - .subtract_lock_amount(acc_ledger.active_locked_amount(), unlock_era) - .is_ok()); - assert_eq!(acc_ledger.locked.0.len(), 3); - assert!(acc_ledger.active_locked_amount().is_zero()); -} - -#[test] -fn account_ledger_subtract_lock_amount_zero_entry_between_two_non_zero() { - get_u32_type!(LockedDummy, 5); - get_u32_type!(UnlockingDummy, 5); - get_u32_type!(StakingDummy, 8); - let mut acc_ledger = - AccountLedger::::default(); - - let (first_lock_amount, second_lock_amount, third_lock_amount) = (17, 23, 29); - let (first_lock_era, second_lock_era, third_lock_era) = (1, 3, 7); - - // Prepare scenario with 3 locked chunks - assert!(acc_ledger - .add_lock_amount(first_lock_amount, first_lock_era) - .is_ok()); - assert!(acc_ledger - .add_lock_amount(second_lock_amount, second_lock_era) - .is_ok()); - assert!(acc_ledger - .add_lock_amount(third_lock_amount, third_lock_era) - .is_ok()); - assert_eq!(acc_ledger.locked.0.len(), 3); - - // Unlock everything for the era right before the latest chunk era - // This should result in scenario like: - // [17, 17 + 23, 0, 29] - assert!(acc_ledger - .subtract_lock_amount(first_lock_amount + second_lock_amount, third_lock_era - 1) - .is_ok()); - assert_eq!(acc_ledger.locked.0.len(), 4); - assert_eq!(acc_ledger.active_locked_amount(), third_lock_amount); - assert_eq!( - acc_ledger.locked.0[0], - LockedChunk { - amount: first_lock_amount, - era: first_lock_era - } - ); - assert_eq!( - acc_ledger.locked.0[1], - LockedChunk { - amount: first_lock_amount + second_lock_amount, - era: second_lock_era - } - ); - assert_eq!( - acc_ledger.locked.0[2], - LockedChunk { - amount: 0, - era: third_lock_era - 1 - } - ); - assert_eq!( - acc_ledger.locked.0[3], - LockedChunk { - amount: third_lock_amount, - era: third_lock_era - } - ); -} - -#[test] -fn account_ledger_subtract_lock_amount_consecutive_zeroes_merged() { - get_u32_type!(LockedDummy, 5); - get_u32_type!(UnlockingDummy, 5); - get_u32_type!(StakingDummy, 8); - let mut acc_ledger = - AccountLedger::::default(); - - // Prepare scenario with 3 locked chunks, where the middle one is zero - let lock_amount = 61; - let last_era = 11; - assert!(acc_ledger.add_lock_amount(lock_amount, 2).is_ok()); - assert!(acc_ledger.subtract_lock_amount(lock_amount, 5).is_ok()); - assert!(acc_ledger.add_lock_amount(lock_amount, last_era).is_ok()); - let second_chunk = acc_ledger.locked.0[1]; - - // Unlock everything in the era right before the latest chunk era, but that chunk should not persist - // [61, 0, 61] --> [61, 0, 0, 61] shouldn't happen since the 2nd zero is redundant. - assert!(acc_ledger - .subtract_lock_amount(lock_amount, last_era - 1) - .is_ok()); - assert_eq!(acc_ledger.locked.0.len(), 2); - assert_eq!(acc_ledger.locked.0[1], second_chunk); } #[test] fn account_ledger_add_unlocking_chunk_works() { - get_u32_type!(LockedDummy, 5); get_u32_type!(UnlockingDummy, 5); get_u32_type!(StakingDummy, 8); - let mut acc_ledger = - AccountLedger::::default(); + let mut acc_ledger = AccountLedger::::default(); // Sanity check scenario // Cannot reduce if there is nothing locked, should be a noop @@ -806,11 +451,9 @@ fn account_ledger_add_unlocking_chunk_works() { #[test] fn active_stake_works() { - get_u32_type!(LockedDummy, 5); get_u32_type!(UnlockingDummy, 5); get_u32_type!(StakingDummy, 8); - let mut acc_ledger = - AccountLedger::::default(); + let mut acc_ledger = AccountLedger::::default(); // Sanity check assert!(acc_ledger.active_stake(0).is_zero()); @@ -833,20 +476,17 @@ fn active_stake_works() { #[test] fn stakeable_amount_works() { - get_u32_type!(LockedDummy, 5); get_u32_type!(UnlockingDummy, 5); get_u32_type!(StakingDummy, 8); - let mut acc_ledger = - AccountLedger::::default(); + let mut acc_ledger = AccountLedger::::default(); // Sanity check for empty ledger assert!(acc_ledger.stakeable_amount(1).is_zero()); // First scenario - some locked amount, no staking chunks - let first_era = 1; let first_period = 1; let locked_amount = 19; - assert!(acc_ledger.add_lock_amount(locked_amount, first_era).is_ok()); + acc_ledger.add_lock_amount(locked_amount); assert_eq!( acc_ledger.stakeable_amount(first_period), locked_amount, @@ -854,6 +494,7 @@ fn stakeable_amount_works() { ); // Second scenario - some staked amount is introduced, period is still valid + let first_era = 1; let staked_amount = 7; acc_ledger.staked = SparseBoundedAmountEraVec( BoundedVec::try_from(vec![StakeChunk { @@ -880,11 +521,9 @@ fn stakeable_amount_works() { #[test] fn staked_amount_works() { - get_u32_type!(LockedDummy, 5); get_u32_type!(UnlockingDummy, 5); get_u32_type!(StakingDummy, 8); - let mut acc_ledger = - AccountLedger::::default(); + let mut acc_ledger = AccountLedger::::default(); // Sanity check for empty ledger assert!(acc_ledger.staked_amount(1).is_zero()); @@ -894,7 +533,7 @@ fn staked_amount_works() { let first_period = 1; let locked_amount = 19; let staked_amount = 13; - assert!(acc_ledger.add_lock_amount(locked_amount, first_era).is_ok()); + acc_ledger.add_lock_amount(locked_amount); acc_ledger.staked = SparseBoundedAmountEraVec( BoundedVec::try_from(vec![StakeChunk { amount: staked_amount, @@ -912,11 +551,9 @@ fn staked_amount_works() { #[test] fn add_stake_amount_works() { - get_u32_type!(LockedDummy, 5); get_u32_type!(UnlockingDummy, 5); get_u32_type!(StakingDummy, 8); - let mut acc_ledger = - AccountLedger::::default(); + let mut acc_ledger = AccountLedger::::default(); // Sanity check assert!(acc_ledger.add_stake_amount(0, 0, 0).is_ok()); @@ -928,7 +565,7 @@ fn add_stake_amount_works() { let first_period = 1; let lock_amount = 17; let stake_amount = 11; - assert!(acc_ledger.add_lock_amount(lock_amount, first_era).is_ok()); + acc_ledger.add_lock_amount(lock_amount); assert!(acc_ledger .add_stake_amount(stake_amount, first_era, first_period) @@ -963,18 +600,16 @@ fn add_stake_amount_works() { #[test] fn add_stake_amount_invalid_era_fails() { - get_u32_type!(LockedDummy, 5); get_u32_type!(UnlockingDummy, 5); get_u32_type!(StakingDummy, 8); - let mut acc_ledger = - AccountLedger::::default(); + let mut acc_ledger = AccountLedger::::default(); // Prep actions let first_era = 5; let first_period = 2; let lock_amount = 13; let stake_amount = 7; - assert!(acc_ledger.add_lock_amount(lock_amount, first_era).is_ok()); + acc_ledger.add_lock_amount(lock_amount); assert!(acc_ledger .add_stake_amount(stake_amount, first_era, first_period) .is_ok()); @@ -1003,11 +638,9 @@ fn add_stake_amount_invalid_era_fails() { #[test] fn add_stake_amount_too_large_amount_fails() { - get_u32_type!(LockedDummy, 5); get_u32_type!(UnlockingDummy, 5); get_u32_type!(StakingDummy, 8); - let mut acc_ledger = - AccountLedger::::default(); + let mut acc_ledger = AccountLedger::::default(); // Sanity check assert_eq!( @@ -1019,7 +652,7 @@ fn add_stake_amount_too_large_amount_fails() { let first_era = 5; let first_period = 2; let lock_amount = 13; - assert!(acc_ledger.add_lock_amount(lock_amount, first_era).is_ok()); + acc_ledger.add_lock_amount(lock_amount); assert_eq!( acc_ledger.add_stake_amount(lock_amount + 1, first_era, first_period), Err(AccountLedgerError::UnavailableStakeFunds) @@ -1037,11 +670,9 @@ fn add_stake_amount_too_large_amount_fails() { #[test] fn add_stake_amount_while_exceeding_capacity_fails() { - get_u32_type!(LockedDummy, 5); get_u32_type!(UnlockingDummy, 5); get_u32_type!(StakingDummy, 8); - let mut acc_ledger = - AccountLedger::::default(); + let mut acc_ledger = AccountLedger::::default(); // Try to stake up to the capacity, it should work // Lock some amount, and try to stake more than that @@ -1049,7 +680,7 @@ fn add_stake_amount_while_exceeding_capacity_fails() { let first_period = 2; let lock_amount = 31; let stake_amount = 3; - assert!(acc_ledger.add_lock_amount(lock_amount, first_era).is_ok()); + acc_ledger.add_lock_amount(lock_amount); for inc in 0..StakingDummy::get() { assert!(acc_ledger .add_stake_amount(stake_amount, first_era + inc, first_period) @@ -1078,11 +709,9 @@ fn add_stake_amount_while_exceeding_capacity_fails() { #[test] fn unlockable_amount_works() { - get_u32_type!(LockedDummy, 5); get_u32_type!(UnlockingDummy, 5); get_u32_type!(StakingDummy, 8); - let mut acc_ledger = - AccountLedger::::default(); + let mut acc_ledger = AccountLedger::::default(); // Sanity check scenario assert!(acc_ledger.unlockable_amount(0).is_zero()); @@ -1090,7 +719,7 @@ fn unlockable_amount_works() { // Nothing is staked let lock_amount = 29; let lock_era = 3; - assert!(acc_ledger.add_lock_amount(lock_amount, lock_era).is_ok()); + acc_ledger.add_lock_amount(lock_amount); assert_eq!(acc_ledger.unlockable_amount(0), lock_amount); // Some amount is staked, period matches @@ -1114,7 +743,7 @@ fn unlockable_amount_works() { assert_eq!(acc_ledger.unlockable_amount(stake_period + 2), lock_amount); // Absurd example, for the sake of completeness - staked without any lock - acc_ledger.locked.0 = Default::default(); + acc_ledger.locked = Balance::zero(); assert!(acc_ledger.unlockable_amount(stake_period).is_zero()); assert!(acc_ledger.unlockable_amount(stake_period - 2).is_zero()); assert!(acc_ledger.unlockable_amount(stake_period + 1).is_zero()); @@ -1122,11 +751,9 @@ fn unlockable_amount_works() { #[test] fn claim_unlocked_works() { - get_u32_type!(LockedDummy, 5); get_u32_type!(UnlockingDummy, 5); get_u32_type!(StakingDummy, 8); - let mut acc_ledger = - AccountLedger::::default(); + let mut acc_ledger = AccountLedger::::default(); // Sanity check scenario assert!(acc_ledger.claim_unlocked(0).is_zero()); @@ -1159,11 +786,9 @@ fn claim_unlocked_works() { #[test] fn consume_unlocking_chunks_works() { - get_u32_type!(LockedDummy, 5); get_u32_type!(UnlockingDummy, 5); get_u32_type!(StakingDummy, 8); - let mut acc_ledger = - AccountLedger::::default(); + let mut acc_ledger = AccountLedger::::default(); // Sanity check scenario assert!(acc_ledger.consume_unlocking_chunks().is_zero()); @@ -1178,7 +803,7 @@ fn consume_unlocking_chunks_works() { } #[test] -fn era_info_manipulation_works() { +fn era_info_lock_unlock_works() { let mut era_info = EraInfo::default(); // Sanity check @@ -1230,6 +855,32 @@ fn era_info_manipulation_works() { assert_eq!(era_info.active_era_locked, old_era_info.active_era_locked); } +#[test] +fn era_info_stake_works() { + let mut era_info = EraInfo::default(); + + // Sanity check + assert!(era_info.total_locked.is_zero()); + + // Add some voting period stake + let vp_stake_amount = 7; + era_info.add_stake_amount(vp_stake_amount, PeriodType::Voting); + assert_eq!(era_info.total_staked_amount(), vp_stake_amount); + assert_eq!(era_info.staked_amount(PeriodType::Voting), vp_stake_amount); + + // Add some build&earn period stake + let bep_stake_amount = 13; + era_info.add_stake_amount(bep_stake_amount, PeriodType::BuildAndEarn); + assert_eq!( + era_info.total_staked_amount(), + vp_stake_amount + bep_stake_amount + ); + assert_eq!( + era_info.staked_amount(PeriodType::BuildAndEarn), + bep_stake_amount + ); +} + #[test] fn singular_staking_info_basics_are_ok() { let period_number = 3; @@ -1508,3 +1159,29 @@ fn contract_staking_info_series_stake_is_ok() { let entry_4_1 = *series.get_for_era(era_4).unwrap(); assert_eq!(entry_4_1.total_staked_amount(), amount); } + +#[test] +fn contract_staking_info_series_inconsistent_data_fails() { + let mut series = ContractStakingInfoSeries::default(); + + // Create an entry with some staked amount + let era = 5; + let period_info = PeriodInfo { + number: 7, + period_type: PeriodType::Voting, + ending_era: 31, + }; + let amount = 37; + assert!(series.stake(amount, period_info, era).is_ok()); + + // 1st scenario - attempt to stake using old era + assert!(series.stake(amount, period_info, era - 1).is_err()); + + // 2nd scenario - attempt to stake using old period + let period_info = PeriodInfo { + number: period_info.number - 1, + period_type: PeriodType::Voting, + ending_era: 31, + }; + assert!(series.stake(amount, period_info, era).is_err()); +} diff --git a/pallets/dapp-staking-v3/src/types.rs b/pallets/dapp-staking-v3/src/types.rs index 46fc0c5a7a..b04f0e9850 100644 --- a/pallets/dapp-staking-v3/src/types.rs +++ b/pallets/dapp-staking-v3/src/types.rs @@ -33,7 +33,6 @@ use crate::pallet::Config; /// Convenience type for `AccountLedger` usage. pub type AccountLedgerFor = AccountLedger< BlockNumberFor, - ::MaxLockedChunks, ::MaxUnlockingChunks, ::MaxStakingChunks, >; @@ -409,15 +408,14 @@ impl AmountEraPair for StakeChunk { /// General info about user's stakes #[derive(Encode, Decode, MaxEncodedLen, Clone, Debug, PartialEq, Eq, TypeInfo)] -#[scale_info(skip_type_params(LockedLen, UnlockingLen, StakedLen))] +#[scale_info(skip_type_params(UnlockingLen, StakedLen))] pub struct AccountLedger< BlockNumber: AtLeast32BitUnsigned + MaxEncodedLen + Copy, - LockedLen: Get, UnlockingLen: Get, StakedLen: Get, > { - /// How much was staked in each era - pub locked: SparseBoundedAmountEraVec, + /// How much active locked amount an account has. + pub locked: Balance, /// How much started unlocking on a certain block pub unlocking: BoundedVec, UnlockingLen>, /// How much user had staked in some period @@ -426,17 +424,16 @@ pub struct AccountLedger< pub staked_period: Option, } -impl Default - for AccountLedger +impl Default + for AccountLedger where BlockNumber: AtLeast32BitUnsigned + MaxEncodedLen + Copy, - LockedLen: Get, UnlockingLen: Get, StakedLen: Get, { fn default() -> Self { Self { - locked: SparseBoundedAmountEraVec(BoundedVec::::default()), + locked: Balance::zero(), unlocking: BoundedVec::, UnlockingLen>::default(), staked: SparseBoundedAmountEraVec(BoundedVec::::default()), staked_period: None, @@ -444,32 +441,23 @@ where } } -impl - AccountLedger +impl AccountLedger where BlockNumber: AtLeast32BitUnsigned + MaxEncodedLen + Copy, - LockedLen: Get, UnlockingLen: Get, StakedLen: Get, { /// Empty if no locked/unlocking/staked info exists. pub fn is_empty(&self) -> bool { - self.locked.0.is_empty() && self.unlocking.is_empty() && self.staked.0.is_empty() - } - - /// Returns latest locked chunk if it exists, `None` otherwise - pub fn latest_locked_chunk(&self) -> Option<&LockedChunk> { - self.locked.0.last() + self.locked.is_zero() && self.unlocking.is_empty() && self.staked.0.is_empty() } /// Returns active locked amount. /// If `zero`, means that associated account hasn't got any active locked funds. /// - /// This value will always refer to the latest known locked amount. - /// It might be relevant for the current period, or for the next one. + /// It is possible that some funds are undergoing the unlocking period, but they aren't considered active in that case. pub fn active_locked_amount(&self) -> Balance { - self.latest_locked_chunk() - .map_or(Balance::zero(), |locked| locked.amount) + self.locked } /// Returns unlocking amount. @@ -487,12 +475,6 @@ where .saturating_add(self.unlocking_amount()) } - /// Returns latest era in which locked amount was updated or zero in case no lock amount exists - pub fn lock_era(&self) -> EraNumber { - self.latest_locked_chunk() - .map_or(EraNumber::zero(), |locked| locked.era) - } - /// Active staked balance. /// /// In case latest stored information is from the past period, active stake is considered to be zero. @@ -563,33 +545,14 @@ where Ok(()) } - /// Adds the specified amount to the total locked amount, if possible. - /// Caller must ensure that the era matches the next one, not the current one. - /// - /// If entry for the specified era already exists, it's updated. - /// - /// If entry for the specified era doesn't exist, it's created and insertion is attempted. - /// In case vector has no more capacity, error is returned, and whole operation is a noop. - pub fn add_lock_amount( - &mut self, - amount: Balance, - era: EraNumber, - ) -> Result<(), AccountLedgerError> { - self.locked.add_amount(amount, era) + /// Adds the specified amount to the total locked amount. + pub fn add_lock_amount(&mut self, amount: Balance) { + self.locked.saturating_accrue(amount); } - /// Subtracts the specified amount of the total locked amount, if possible. - /// - /// If entry for the specified era already exists, it's updated. - /// - /// If entry for the specified era doesn't exist, it's created and insertion is attempted. - /// In case vector has no more capacity, error is returned, and whole operation is a noop. - pub fn subtract_lock_amount( - &mut self, - amount: Balance, - era: EraNumber, - ) -> Result<(), AccountLedgerError> { - self.locked.subtract_amount(amount, era) + /// Subtracts the specified amount of the total locked amount. + pub fn subtract_lock_amount(&mut self, amount: Balance) { + self.locked.saturating_reduce(amount); } /// Adds the specified amount to the unlocking chunks. @@ -723,6 +686,19 @@ impl EraInfo { PeriodType::BuildAndEarn => self.bep_staked.saturating_accrue(amount), } } + + /// Total staked amount in this era. + pub fn total_staked_amount(&self) -> Balance { + self.vp_staked.saturating_add(self.bep_staked) + } + + /// Staked amount of specified `type` in this era. + pub fn staked_amount(&self, period_type: PeriodType) -> Balance { + match period_type { + PeriodType::Voting => self.vp_staked, + PeriodType::BuildAndEarn => self.bep_staked, + } + } } /// Information about how much a particular staker staked on a particular smart contract. @@ -909,6 +885,7 @@ impl ContractStakingInfo { } } +// TODO: the limit `2` might need to be bumped to `3` since we also create one "future" entry when calling "stake" /// Composite type that holds information about how much was staked on a contract during some past eras & periods, including the current era & period. #[derive(Encode, Decode, MaxEncodedLen, Clone, Debug, PartialEq, Eq, TypeInfo, Default)] pub struct ContractStakingInfoSeries(WeakBoundedVec>);