diff --git a/pallets/bb-bnc/src/lib.rs b/pallets/bb-bnc/src/lib.rs index 96c25531d..f693991e6 100644 --- a/pallets/bb-bnc/src/lib.rs +++ b/pallets/bb-bnc/src/lib.rs @@ -82,7 +82,7 @@ const BB_LOCK_ID: LockIdentifier = *b"bbbnclck"; const MARKUP_LOCK_ID: LockIdentifier = *b"bbbncmkp"; pub const BB_BNC_SYSTEM_POOL_ID: PoolId = u32::MAX; pub type PositionId = u128; -const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); +const STORAGE_VERSION: StorageVersion = StorageVersion::new(2); #[derive( Clone, @@ -452,10 +452,22 @@ pub mod pallet { RewardTooSmallToLock, } - /// Total supply of locked tokens + /// Raw total amount of locked bbBNC principal. + /// + /// This excludes markup and is used for lock accounting invariants with + /// `Locked` and `UserLocked`. Reward distribution must not use this as the + /// denominator when markup is enabled. #[pallet::storage] pub type Supply = StorageValue<_, BalanceOf, ValueQuery>; + /// Current total amount of locked bbBNC after applying user markup. + /// + /// This is the O(1) source of truth for writing global bbBNC point + /// snapshots. `PointHistory.amount` records this value at checkpoints so + /// `total_supply` uses the same boosted unit as user balances. + #[pallet::storage] + pub type BoostedSupply = StorageValue<_, BalanceOf, ValueQuery>; + /// Configurations #[pallet::storage] pub type BbConfigs = @@ -758,7 +770,7 @@ pub mod pallet { #[cfg(any(test, feature = "try-runtime"))] impl Pallet { - fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> { + pub(crate) fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> { for (owner, positions) in UserPositions::::iter() { let mut computed_user_locked = BalanceOf::::zero(); for position in positions.iter() { @@ -789,6 +801,22 @@ pub mod pallet { sp_runtime::TryRuntimeError::Other("UserLocked total mismatch with Supply") ); + let mut boosted_supply_sum = BalanceOf::::zero(); + for position in 0..Position::::get() { + let epoch = UserPointEpoch::::get(position); + if epoch.is_zero() { + continue; + } + boosted_supply_sum = boosted_supply_sum + .saturating_add(UserPointHistory::::get(position, epoch).amount); + } + frame_support::ensure!( + boosted_supply_sum == BoostedSupply::::get(), + sp_runtime::TryRuntimeError::Other( + "UserPointHistory total mismatch with BoostedSupply" + ) + ); + for (end_block, position, _) in ExpiringPositions::::iter() { let lock = Locked::::get(position); frame_support::ensure!( @@ -1170,13 +1198,14 @@ pub mod pallet { } impl Pallet { - pub fn checkpoint( + pub(crate) fn checkpoint( who: &AccountIdOf, position: PositionId, old_locked: LockedBalance, BlockNumberFor>, new_locked: LockedBalance, BlockNumberFor>, ) -> DispatchResult { Self::update_reward_all(who)?; + Self::update_boosted_supply(&old_locked, &new_locked)?; let mut u_old = Point::, BlockNumberFor>::default(); let mut u_new = Point::, BlockNumberFor>::default(); @@ -1245,7 +1274,7 @@ pub mod pallet { if g_epoch > U256::zero() { last_point = PointHistory::::get(g_epoch); } else { - last_point.amount = Supply::::get(); + last_point.amount = BoostedSupply::::get(); } let mut last_checkpoint = last_point.block; let mut t_i: BlockNumberFor = last_checkpoint @@ -1299,7 +1328,7 @@ pub mod pallet { // Fill for the current block, if applicable if t_i == current_block_number { - last_point.amount = Supply::::get(); + last_point.amount = BoostedSupply::::get(); break; } else { PointHistory::::insert(g_epoch, last_point); @@ -1360,6 +1389,31 @@ pub mod pallet { Ok(()) } + fn update_boosted_supply( + old_locked: &LockedBalance, BlockNumberFor>, + new_locked: &LockedBalance, BlockNumberFor>, + ) -> DispatchResult { + // `old_locked` and `new_locked` are expected to use markup-adjusted + // amounts. Keep raw principal accounting in `Supply`. + let old_amount = old_locked.amount; + let new_amount = new_locked.amount; + if new_amount >= old_amount { + BoostedSupply::::try_mutate(|supply| -> DispatchResult { + *supply = supply + .checked_add(new_amount.saturating_sub(old_amount)) + .ok_or(ArithmeticError::Overflow)?; + Ok(()) + }) + } else { + BoostedSupply::::try_mutate(|supply| -> DispatchResult { + *supply = supply + .checked_sub(old_amount.saturating_sub(new_amount)) + .ok_or(ArithmeticError::Underflow)?; + Ok(()) + }) + } + } + pub fn deposit_for_inner( who: &AccountIdOf, position: PositionId, @@ -1399,12 +1453,11 @@ pub mod pallet { Self::set_ve_locked(who, new_locked_balance)?; } - Self::markup_calc( + Self::checkpoint_with_current_markup( who, position, old_locked.clone(), locked.clone(), - UserMarkupInfos::::get(who).as_ref(), )?; Self::deposit_event(Event::Minted { @@ -1577,28 +1630,65 @@ pub mod pallet { Ok(balance) } - pub fn markup_calc( + pub(crate) fn markup_calc( who: &AccountIdOf, position: PositionId, - mut old_locked: LockedBalance, BlockNumberFor>, - mut new_locked: LockedBalance, BlockNumberFor>, + old_locked: LockedBalance, BlockNumberFor>, + new_locked: LockedBalance, BlockNumberFor>, user_markup_info: Option<&UserMarkupInfo>, ) -> DispatchResult { if let Some(info) = user_markup_info { - old_locked.amount = info - .old_markup_coefficient - .checked_mul_int(old_locked.amount) - .and_then(|x| x.checked_add(old_locked.amount)) - .ok_or(ArithmeticError::Overflow)?; - new_locked.amount = info - .markup_coefficient - .checked_mul_int(new_locked.amount) - .and_then(|x| x.checked_add(new_locked.amount)) - .ok_or(ArithmeticError::Overflow)?; + Self::checkpoint_with_markup_coefficients( + who, + position, + old_locked, + new_locked, + info.old_markup_coefficient, + info.markup_coefficient, + ) + } else { + Self::checkpoint(who, position, old_locked, new_locked) } + } - Self::checkpoint(who, position, old_locked.clone(), new_locked.clone())?; - Ok(()) + fn checkpoint_with_current_markup( + who: &AccountIdOf, + position: PositionId, + old_locked: LockedBalance, BlockNumberFor>, + new_locked: LockedBalance, BlockNumberFor>, + ) -> DispatchResult { + let current_markup_coefficient = UserMarkupInfos::::get(who) + .map(|info| info.markup_coefficient) + .unwrap_or_else(FixedU128::zero); + + Self::checkpoint_with_markup_coefficients( + who, + position, + old_locked, + new_locked, + current_markup_coefficient, + current_markup_coefficient, + ) + } + + fn checkpoint_with_markup_coefficients( + who: &AccountIdOf, + position: PositionId, + mut old_locked: LockedBalance, BlockNumberFor>, + mut new_locked: LockedBalance, BlockNumberFor>, + old_markup_coefficient: FixedU128, + new_markup_coefficient: FixedU128, + ) -> DispatchResult { + old_locked.amount = old_markup_coefficient + .checked_mul_int(old_locked.amount) + .and_then(|x| x.checked_add(old_locked.amount)) + .ok_or(ArithmeticError::Overflow)?; + new_locked.amount = new_markup_coefficient + .checked_mul_int(new_locked.amount) + .and_then(|x| x.checked_add(new_locked.amount)) + .ok_or(ArithmeticError::Overflow)?; + + Self::checkpoint(who, position, old_locked, new_locked) } pub fn deposit_markup_inner( @@ -2034,7 +2124,7 @@ pub mod pallet { } } - Self::checkpoint(who, position, old_locked, locked.clone())?; + Self::checkpoint_with_current_markup(who, position, old_locked, locked.clone())?; T::FarmingInfo::refresh_gauge_pool(who)?; diff --git a/pallets/bb-bnc/src/migrations/mod.rs b/pallets/bb-bnc/src/migrations/mod.rs index 379d174c5..8303032c3 100644 --- a/pallets/bb-bnc/src/migrations/mod.rs +++ b/pallets/bb-bnc/src/migrations/mod.rs @@ -17,3 +17,4 @@ // along with this program. If not, see . pub mod v1; +pub mod v2; diff --git a/pallets/bb-bnc/src/migrations/v1.rs b/pallets/bb-bnc/src/migrations/v1.rs index d1672d228..a9e11b629 100644 --- a/pallets/bb-bnc/src/migrations/v1.rs +++ b/pallets/bb-bnc/src/migrations/v1.rs @@ -164,3 +164,68 @@ impl OnRuntimeUpgrade for MigrateToV1 { Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{mock::*, Pallet as BbBNCPallet}; + use frame_support::traits::{OnRuntimeUpgrade, StorageVersion}; + use sp_core::ConstU32; + + #[test] + fn skips_when_in_code_version_is_newer() { + ExtBuilder::default().build().execute_with(|| { + StorageVersion::new(0).put::>(); + v0::ExpiringPositions::::insert( + 10, + BoundedVec::>::try_from(vec![1, 2, 3]).unwrap(), + ); + v0::ExpiringPositions::::insert( + 20, + BoundedVec::>::try_from(vec![4, 5]).unwrap(), + ); + + let _ = MigrateToV1::::on_runtime_upgrade(); + + assert_eq!(BbBNCPallet::::on_chain_storage_version(), 0); + assert!(frame_support::storage::unhashed::exists( + &v0::ExpiringPositions::::hashed_key_for(10) + )); + assert!(frame_support::storage::unhashed::exists( + &v0::ExpiringPositions::::hashed_key_for(20) + )); + assert_eq!(ExpiringPositions::::iter().count(), 0); + + let _ = MigrateToV1::::on_runtime_upgrade(); + + assert_eq!(BbBNCPallet::::on_chain_storage_version(), 0); + assert_eq!(ExpiringPositions::::iter().count(), 0); + }); + } + + #[test] + fn empty_storage_skips_when_in_code_version_is_newer() { + ExtBuilder::default().build().execute_with(|| { + StorageVersion::new(0).put::>(); + + let _ = MigrateToV1::::on_runtime_upgrade(); + + assert_eq!(BbBNCPallet::::on_chain_storage_version(), 0); + assert_eq!(ExpiringPositions::::iter().count(), 0); + }); + } + + #[cfg(feature = "try-runtime")] + #[test] + fn try_runtime_rejects_when_in_code_version_is_newer() { + ExtBuilder::default().build().execute_with(|| { + StorageVersion::new(0).put::>(); + v0::ExpiringPositions::::insert( + 10, + BoundedVec::>::try_from(vec![1, 2, 3]).unwrap(), + ); + + assert!(MigrateToV1::::pre_upgrade().is_err()); + }); + } +} diff --git a/pallets/bb-bnc/src/migrations/v2.rs b/pallets/bb-bnc/src/migrations/v2.rs new file mode 100644 index 000000000..27d2445f1 --- /dev/null +++ b/pallets/bb-bnc/src/migrations/v2.rs @@ -0,0 +1,212 @@ +// This file is part of Bifrost. + +// Copyright (C) Liebi Technologies PTE. LTD. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use crate::*; +use frame_support::{pallet_prelude::*, traits::OnRuntimeUpgrade, weights::Weight}; +#[cfg(feature = "try-runtime")] +use parity_scale_codec::{Decode, Encode}; +#[cfg(feature = "try-runtime")] +use sp_runtime::TryRuntimeError; +use sp_std::marker::PhantomData; + +const LOG_TARGET: &str = "bb-bnc::migration::v2"; + +pub struct MigrateToV2(PhantomData); + +impl MigrateToV2 { + fn rebuild_boosted_supply() -> (BalanceOf, u64, u64) { + let mut boosted_supply = BalanceOf::::zero(); + let mut scanned_positions = 0u64; + let mut active_positions = 0u64; + + for position in 0..Position::::get() { + scanned_positions = scanned_positions.saturating_add(1); + let epoch = UserPointEpoch::::get(position); + if epoch.is_zero() { + continue; + } + + active_positions = active_positions.saturating_add(1); + boosted_supply = + boosted_supply.saturating_add(UserPointHistory::::get(position, epoch).amount); + } + + (boosted_supply, scanned_positions, active_positions) + } +} + +impl OnRuntimeUpgrade for MigrateToV2 { + fn on_runtime_upgrade() -> Weight { + let on_chain_version = Pallet::::on_chain_storage_version(); + let in_code_version = Pallet::::in_code_storage_version(); + + if on_chain_version == 1 && in_code_version == 2 { + let (boosted_supply, scanned_positions, active_positions) = + Self::rebuild_boosted_supply(); + BoostedSupply::::set(boosted_supply); + + let epoch = Epoch::::get(); + let mut point = PointHistory::::get(epoch); + point.amount = boosted_supply; + PointHistory::::insert(epoch, point); + + StorageVersion::new(2).put::>(); + log::info!( + target: LOG_TARGET, + "Rebuilt boosted supply {boosted_supply:?} from {active_positions} active positions after scanning {scanned_positions} position ids", + ); + + T::DbWeight::get().reads_writes( + scanned_positions + .saturating_add(active_positions) + .saturating_add(4), + 3, + ) + } else { + log::warn!( + target: LOG_TARGET, + "Skipping migration because on-chain version is {on_chain_version:?} and in-code version is {in_code_version:?}", + ); + T::DbWeight::get().reads(2) + } + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, TryRuntimeError> { + let on_chain_version = Pallet::::on_chain_storage_version(); + ensure!( + Pallet::::in_code_storage_version() == 2, + "BbBNC v2 migration requires in-code storage version 2" + ); + + if on_chain_version == 2 { + log::info!( + target: LOG_TARGET, + "pre_upgrade skipping already-applied migration", + ); + return Ok(Option::>::None.encode()); + } + + ensure!( + on_chain_version == 1, + "BbBNC v2 migration requires on-chain storage version 1" + ); + + let (boosted_supply, _, _) = Self::rebuild_boosted_supply(); + Ok(Some(boosted_supply).encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: Vec) -> Result<(), TryRuntimeError> { + let expected = Option::>::decode(&mut &state[..]) + .expect("pre_upgrade provides a valid encoded boosted supply"); + + ensure!( + Pallet::::on_chain_storage_version() == 2, + "BbBNC storage version should be 2 after migration" + ); + let Some(expected) = expected else { + log::info!( + target: LOG_TARGET, + "post_upgrade verified already-applied migration", + ); + return Ok(()); + }; + + ensure!( + BoostedSupply::::get() == expected, + "BoostedSupply should match rebuilt boosted supply" + ); + ensure!( + PointHistory::::get(Epoch::::get()).amount == expected, + "Current global point amount should match rebuilt boosted supply" + ); + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{mock::*, Pallet as BbBNCPallet}; + use frame_support::{ + assert_ok, + traits::{OnRuntimeUpgrade, StorageVersion}, + }; + use sp_runtime::FixedU128; + + const POSITIONID0: u128 = 0; + const RWI: FixedU128 = FixedU128::from_inner(100_000_000_000_000_000); + + #[test] + fn rebuilds_boosted_supply() { + ExtBuilder::default() + .one_hundred_for_alice_n_bob() + .build() + .execute_with(|| { + asset_registry(); + System::set_block_number(System::block_number() + 20); + assert_ok!(BbBNC::set_config( + RuntimeOrigin::root(), + Some(0), + Some(7 * DAYS), + Some(10) + )); + assert_ok!(BbBNC::set_markup_coefficient( + RuntimeOrigin::root(), + VBNC, + FixedU128::from_inner(100_000_000_000_000_000), + FixedU128::saturating_from_integer(1), + RWI, + )); + assert_ok!(BbBNC::create_lock_inner( + &BOB, + 10_000_000_000_000, + System::block_number() + 365 * DAYS, + )); + assert_ok!(BbBNC::deposit_markup( + RuntimeOrigin::signed(BOB), + VBNC, + 10_000_000_000_000 + )); + + let expected_boosted_supply = UserPointHistory::::get( + POSITIONID0, + UserPointEpoch::::get(POSITIONID0), + ) + .amount; + assert!(expected_boosted_supply > Supply::::get()); + + BoostedSupply::::set(0); + PointHistory::::mutate(Epoch::::get(), |point| { + point.amount = Supply::::get(); + }); + StorageVersion::new(1).put::>(); + + let _ = MigrateToV2::::on_runtime_upgrade(); + + assert_eq!(BbBNCPallet::::on_chain_storage_version(), 2); + assert_eq!(BoostedSupply::::get(), expected_boosted_supply); + assert_eq!( + PointHistory::::get(Epoch::::get()).amount, + expected_boosted_supply + ); + }); + } +} diff --git a/pallets/bb-bnc/src/mock.rs b/pallets/bb-bnc/src/mock.rs index 1f4b7423b..30683db1e 100644 --- a/pallets/bb-bnc/src/mock.rs +++ b/pallets/bb-bnc/src/mock.rs @@ -22,7 +22,7 @@ #![allow(non_upper_case_globals)] use crate as bb_bnc; -use bifrost_asset_registry::AssetIdMaps; +use bifrost_asset_registry::{AssetIdMaps, AssetMetadata}; use bifrost_primitives::{ BifrostEntranceAccount, BifrostExitAccount, BifrostFeeAccount, BuyBackAccount, IncentivePalletId, IncentivePoolAccount, MoonbeamChainId, @@ -440,6 +440,32 @@ impl ExtBuilder { } } +pub fn asset_registry() { + let items = vec![ + ( + DOT, + AssetMetadata { + name: b"Polkadot DOT".to_vec(), + symbol: b"DOT".to_vec(), + decimals: 10, + minimal_balance: 100_000_000, // 0.01 DOT + }, + ), + ( + BNC, + AssetMetadata { + name: b"Bifrost".to_vec(), + symbol: b"BNC".to_vec(), + decimals: 12, + minimal_balance: 10 * milli::(BNC), + }, + ), + ]; + for (currency_id, metadata) in items.iter() { + AssetRegistry::do_register_metadata(*currency_id, metadata).expect("Token register"); + } +} + #[cfg(feature = "runtime-benchmarks")] pub fn new_test_ext_benchmark() -> sp_io::TestExternalities { ExtBuilder::default().build() diff --git a/pallets/bb-bnc/src/tests.rs b/pallets/bb-bnc/src/tests.rs index 69fe66cec..45f0eabd4 100644 --- a/pallets/bb-bnc/src/tests.rs +++ b/pallets/bb-bnc/src/tests.rs @@ -21,15 +21,9 @@ #![cfg(test)] use crate::{impls::BbBNCInterface, mock::*, *}; -use bifrost_asset_registry::AssetMetadata; use bifrost_primitives::{BuyBackAccount, IncentivePalletId}; -use bifrost_runtime_common::milli; -use frame_support::{ - assert_noop, assert_ok, - traits::{Get, OnRuntimeUpgrade, StorageVersion}, -}; -use sp_core::ConstU32; +use frame_support::{assert_noop, assert_ok, traits::Get}; use sp_runtime::traits::AccountIdConversion; const POSITIONID0: u128 = 0; @@ -896,32 +890,6 @@ fn update_reward() { }); } -fn asset_registry() { - let items = vec![ - ( - DOT, - AssetMetadata { - name: b"Polkadot DOT".to_vec(), - symbol: b"DOT".to_vec(), - decimals: 10, - minimal_balance: 100_000_000, // 0.01 DOT - }, - ), - ( - BNC, - AssetMetadata { - name: b"Bifrost".to_vec(), - symbol: b"BNC".to_vec(), - decimals: 12, - minimal_balance: 10 * milli::(BNC), - }, - ), - ]; - for (currency_id, metadata) in items.iter() { - AssetRegistry::do_register_metadata(*currency_id, metadata).expect("Token register"); - } -} - #[test] fn notify_reward_amount() { ExtBuilder::default() @@ -1120,6 +1088,92 @@ fn create_lock_to_withdraw() { }); } +#[test] +fn withdraw_with_markup_clears_raw_and_boosted_supply() { + ExtBuilder::default() + .one_hundred_for_alice_n_bob() + .build() + .execute_with(|| { + asset_registry(); + System::set_block_number(System::block_number() + 7 * DAYS); + assert_ok!(BbBNC::set_config( + RuntimeOrigin::root(), + Some(0), + Some(7 * DAYS), + Some(10) + )); + assert_ok!(BbBNC::set_markup_coefficient( + RuntimeOrigin::root(), + VBNC, + FixedU128::from_inner(100_000_000_000_000_000), + FixedU128::saturating_from_integer(1), + RWI, + )); + + let locked_amount = 50_000_000_000_000; + assert_ok!(BbBNC::create_lock_inner( + &BOB, + locked_amount, + System::block_number() + 365 * DAYS + )); + assert_ok!(BbBNC::deposit_markup( + RuntimeOrigin::signed(BOB), + VBNC, + 10_000_000_000_000 + )); + + assert_eq!(Supply::::get(), locked_amount); + assert!(BoostedSupply::::get() > Supply::::get()); + + System::set_block_number(Locked::::get(POSITIONID0).end); + assert_ok!(BbBNC::withdraw(RuntimeOrigin::signed(BOB), POSITIONID0)); + + assert_eq!(Supply::::get(), 0); + assert_eq!(BoostedSupply::::get(), 0); + assert_eq!(BbBNC::total_supply(Some(System::block_number())), Ok(0)); + }); +} + +#[test] +fn try_state_detects_boosted_supply_mismatch() { + ExtBuilder::default() + .one_hundred_for_alice_n_bob() + .build() + .execute_with(|| { + asset_registry(); + System::set_block_number(System::block_number() + 7 * DAYS); + assert_ok!(BbBNC::set_config( + RuntimeOrigin::root(), + Some(0), + Some(7 * DAYS), + Some(10) + )); + assert_ok!(BbBNC::set_markup_coefficient( + RuntimeOrigin::root(), + VBNC, + FixedU128::from_inner(100_000_000_000_000_000), + FixedU128::saturating_from_integer(1), + RWI, + )); + + assert_ok!(BbBNC::create_lock_inner( + &BOB, + 50_000_000_000_000, + System::block_number() + 365 * DAYS + )); + assert_ok!(BbBNC::deposit_markup( + RuntimeOrigin::signed(BOB), + VBNC, + 10_000_000_000_000 + )); + assert_ok!(BbBNC::do_try_state()); + + BoostedSupply::::set(Supply::::get()); + + assert!(BbBNC::do_try_state().is_err()); + }); +} + #[test] fn overflow() { ExtBuilder::default() @@ -1841,10 +1895,33 @@ fn multiple_positions_with_complex_rewards_should_work() { // Advance time and check rewards System::set_block_number(System::block_number() + 6 * DAYS); + let pending_rewards = BbBNC::query_pending_rewards(&BOB).unwrap(); + let dot_pending_reward = pending_rewards + .iter() + .find(|(currency_id, _)| *currency_id == DOT) + .map(|(_, amount)| *amount) + .unwrap_or(0); + let dot_balance_before = Tokens::free_balance(DOT, &BOB); assert_ok!(BbBNC::get_rewards( RuntimeOrigin::signed(BOB), direct_claim() )); + let dot_balance_after = Tokens::free_balance(DOT, &BOB); + let claimed_reward = dot_balance_after.saturating_sub(dot_balance_before); + let emitted_reward = IncentiveConfigs::::get(BB_BNC_SYSTEM_POOL_ID) + .reward_rate + .get(&DOT) + .copied() + .unwrap() + .saturating_mul((6 * DAYS) as BalanceOf); + + assert_eq!(claimed_reward, dot_pending_reward); + assert!( + claimed_reward <= emitted_reward, + "claimed reward ({}) must not exceed emitted reward ({})", + claimed_reward, + emitted_reward + ); // Verify total balance considers both positions and markup assert_eq!( @@ -1854,7 +1931,7 @@ fn multiple_positions_with_complex_rewards_should_work() { assert_eq!( BbBNC::total_supply(Some(System::block_number())), - Ok(14026044152175) + Ok(14402294152174) ); // Verify individual position balances @@ -2935,79 +3012,6 @@ fn on_initialize_processes_expiring_positions_across_blocks() { }); } -#[test] -fn migrate_expiring_positions_v0_to_v1_preserves_pairs_and_is_idempotent() { - ExtBuilder::default().build().execute_with(|| { - use crate::migrations::v1::{v0::ExpiringPositions as ExpiringPositionsV0, MigrateToV1}; - - StorageVersion::new(0).put::>(); - ExpiringPositionsV0::::insert( - 10, - BoundedVec::>::try_from(vec![1, 2, 3]).unwrap(), - ); - ExpiringPositionsV0::::insert( - 20, - BoundedVec::>::try_from(vec![4, 5]).unwrap(), - ); - - let _ = MigrateToV1::::on_runtime_upgrade(); - - assert_eq!(Pallet::::on_chain_storage_version(), 1); - assert!(!frame_support::storage::unhashed::exists( - &ExpiringPositionsV0::::hashed_key_for(10) - )); - assert!(!frame_support::storage::unhashed::exists( - &ExpiringPositionsV0::::hashed_key_for(20) - )); - for (block, position) in [(10, 1), (10, 2), (10, 3), (20, 4), (20, 5)] { - assert!(expiring_contains(block, position)); - } - assert_eq!(ExpiringPositions::::iter().count(), 5); - - let _ = MigrateToV1::::on_runtime_upgrade(); - - assert_eq!(ExpiringPositions::::iter().count(), 5); - }); -} - -#[test] -fn migrate_expiring_positions_v0_to_v1_handles_empty_storage() { - ExtBuilder::default().build().execute_with(|| { - use crate::migrations::v1::MigrateToV1; - - StorageVersion::new(0).put::>(); - - let _ = MigrateToV1::::on_runtime_upgrade(); - - assert_eq!(Pallet::::on_chain_storage_version(), 1); - assert_eq!(ExpiringPositions::::iter().count(), 0); - }); -} - -#[cfg(feature = "try-runtime")] -#[test] -fn migrate_expiring_positions_try_runtime_is_idempotent() { - ExtBuilder::default().build().execute_with(|| { - use crate::migrations::v1::{v0::ExpiringPositions as ExpiringPositionsV0, MigrateToV1}; - - StorageVersion::new(0).put::>(); - ExpiringPositionsV0::::insert( - 10, - BoundedVec::>::try_from(vec![1, 2, 3]).unwrap(), - ); - - let state = MigrateToV1::::pre_upgrade().unwrap(); - let _ = MigrateToV1::::on_runtime_upgrade(); - MigrateToV1::::post_upgrade(state).unwrap(); - - let state = MigrateToV1::::pre_upgrade().unwrap(); - let _ = MigrateToV1::::on_runtime_upgrade(); - MigrateToV1::::post_upgrade(state).unwrap(); - - assert_eq!(ExpiringPositions::::iter().count(), 3); - }); -} - #[test] fn test_owner_search_for_expired_positions() { // Use much higher balances to avoid "less than existential deposit" error diff --git a/runtime/bifrost-paseo/src/lib.rs b/runtime/bifrost-paseo/src/lib.rs index c96046eb9..1e78f5fee 100644 --- a/runtime/bifrost-paseo/src/lib.rs +++ b/runtime/bifrost-paseo/src/lib.rs @@ -1750,6 +1750,7 @@ pub mod migrations { /// Unreleased migrations. Add new ones here: pub type Unreleased = ( + bb_bnc::migrations::v2::MigrateToV2, // permanent migration, do not remove pallet_xcm::migration::MigrateToLatestXcmVersion, ); diff --git a/runtime/bifrost-polkadot/src/lib.rs b/runtime/bifrost-polkadot/src/lib.rs index 59e43c5dd..e0043b93f 100644 --- a/runtime/bifrost-polkadot/src/lib.rs +++ b/runtime/bifrost-polkadot/src/lib.rs @@ -1766,6 +1766,7 @@ pub mod migrations { /// Unreleased migrations. Add new ones here: pub type Unreleased = ( + bb_bnc::migrations::v2::MigrateToV2, // permanent migration, do not remove pallet_xcm::migration::MigrateToLatestXcmVersion, );