git.delta.rocks / unique-network / refs/commits / ebc4cdb8a3c4

difftreelog

add force methods for collection in `Unique` , add events Stake, Unstake, SetAdmin

PraetorP2022-09-05parent: #9704432.patch.diff
in: master

8 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -6473,7 +6473,7 @@
 
 [[package]]
 name = "pallet-unique"
-version = "0.1.3"
+version = "0.1.4"
 dependencies = [
  "ethereum",
  "evm-coder",
modifiedpallets/app-promotion/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/app-promotion/src/benchmarking.rs
+++ b/pallets/app-promotion/src/benchmarking.rs
@@ -54,7 +54,7 @@
 
 	payout_stakers{
 		let pallet_admin = account::<T::AccountId>("admin", 0, SEED);
-		let share = Perbill::from_rational(1u32, 10);
+		let share = Perbill::from_rational(1u32, 100);
 		PromototionPallet::<T>::set_admin_address(RawOrigin::Root.into(), T::CrossAccountId::from_sub(pallet_admin.clone()))?;
 		let _ = <T as Config>::Currency::make_free_balance_be(&pallet_admin,  Perbill::from_rational(1u32, 2) * BalanceOf::<T>::max_value());
 		let staker: T::AccountId = account("caller", 0, SEED);
modifiedpallets/app-promotion/src/lib.rsdiffbeforeafterboth
after · pallets/app-promotion/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # App promotion18//!19//! The app promotion pallet is designed to ... .20//!21//! ## Interface22//!23//! ### Dispatchable Functions24//!25//! * `start_inflation` - This method sets the inflation start date. Can be only called once.26//! Inflation start block can be backdated and will catch up. The method will create Treasury27//!	account if it does not exist and perform the first inflation deposit.2829// #![recursion_limit = "1024"]30#![cfg_attr(not(feature = "std"), no_std)]3132#[cfg(feature = "runtime-benchmarks")]33mod benchmarking;34#[cfg(test)]35mod tests;36pub mod types;37pub mod weights;3839use sp_std::{40	vec::{Vec},41	vec,42	iter::Sum,43	borrow::ToOwned,44	cell::RefCell,45};46use sp_core::H160;47use codec::EncodeLike;48use pallet_balances::BalanceLock;49pub use types::*;5051// use up_common::constants::{DAYS, UNIQUE};52use up_data_structs::CollectionId;5354use frame_support::{55	dispatch::{DispatchResult},56	traits::{57		Currency, Get, LockableCurrency, WithdrawReasons, tokens::Balance, ExistenceRequirement,58	},59	ensure,60};6162use weights::WeightInfo;6364pub use pallet::*;65use pallet_evm::account::CrossAccountId;66use sp_runtime::{67	Perbill,68	traits::{BlockNumberProvider, CheckedAdd, CheckedSub, AccountIdConversion, Zero},69	ArithmeticError,70};7172pub const LOCK_IDENTIFIER: [u8; 8] = *b"appstake";7374const PENDING_LIMIT_PER_BLOCK: u32 = 3;7576type BalanceOf<T> =77	<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;7879#[frame_support::pallet]80pub mod pallet {81	use super::*;82	use frame_support::{83		Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, PalletId,84		traits::ReservableCurrency,85	};86	use frame_system::pallet_prelude::*;8788	#[pallet::config]89	pub trait Config: frame_system::Config + pallet_evm::account::Config {90		type Currency: ExtendedLockableCurrency<Self::AccountId>91			+ ReservableCurrency<Self::AccountId>;9293		type CollectionHandler: CollectionHandler<94			AccountId = Self::AccountId,95			CollectionId = CollectionId,96		>;9798		type ContractHandler: ContractHandler<AccountId = Self::CrossAccountId, ContractId = H160>;99100		type TreasuryAccountId: Get<Self::AccountId>;101102		/// The app's pallet id, used for deriving its sovereign account ID.103		#[pallet::constant]104		type PalletId: Get<PalletId>;105106		/// In relay blocks.107		#[pallet::constant]108		type RecalculationInterval: Get<Self::BlockNumber>;109		/// In relay blocks.110		#[pallet::constant]111		type PendingInterval: Get<Self::BlockNumber>;112113		// /// In chain blocks.114		// #[pallet::constant]115		// type Day: Get<Self::BlockNumber>; // useless116117		#[pallet::constant]118		type Nominal: Get<BalanceOf<Self>>;119120		#[pallet::constant]121		type IntervalIncome: Get<Perbill>;122123		/// Weight information for extrinsics in this pallet.124		type WeightInfo: WeightInfo;125126		// The relay block number provider127		type RelayBlockNumberProvider: BlockNumberProvider<BlockNumber = Self::BlockNumber>;128129		/// Events compatible with [`frame_system::Config::Event`].130		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;131	}132133	#[pallet::pallet]134	#[pallet::generate_store(pub(super) trait Store)]135	pub struct Pallet<T>(_);136137	#[pallet::event]138	#[pallet::generate_deposit(fn deposit_event)]139	pub enum Event<T: Config> {140		StakingRecalculation(141			/// An recalculated staker142			T::AccountId,143			/// Base on which interest is calculated144			BalanceOf<T>,145			/// Amount of accrued interest146			BalanceOf<T>,147		),148		Stake(T::AccountId, BalanceOf<T>),149		Unstake(T::AccountId, BalanceOf<T>),150		SetAdmin(T::AccountId),151	}152153	#[pallet::error]154	pub enum Error<T> {155		/// Error due to action requiring admin to be set156		AdminNotSet,157		/// No permission to perform an action158		NoPermission,159		/// Insufficient funds to perform an action160		NotSufficientFounds,161		PendingForBlockOverflow,162		/// An error related to the fact that an invalid argument was passed to perform an action163		InvalidArgument,164	}165166	#[pallet::storage]167	pub type TotalStaked<T: Config> = StorageValue<Value = BalanceOf<T>, QueryKind = ValueQuery>;168169	#[pallet::storage]170	pub type Admin<T: Config> = StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;171172	/// Amount of tokens staked by account in the blocknumber.173	#[pallet::storage]174	pub type Staked<T: Config> = StorageNMap<175		Key = (176			Key<Blake2_128Concat, T::AccountId>,177			Key<Twox64Concat, T::BlockNumber>,178		),179		Value = (BalanceOf<T>, T::BlockNumber),180		QueryKind = ValueQuery,181	>;182	/// Amount of stakes for an Account183	#[pallet::storage]184	pub type StakesPerAccount<T: Config> =185		StorageMap<_, Blake2_128Concat, T::AccountId, u8, ValueQuery>;186187	/// Amount of tokens pending unstake per user per block.188	// #[pallet::storage]189	// pub type PendingUnstake<T: Config> = StorageNMap<190	// 	Key = (191	// 		Key<Blake2_128Concat, T::AccountId>,192	// 		Key<Twox64Concat, T::BlockNumber>,193	// 	),194	// 	Value = BalanceOf<T>,195	// 	QueryKind = ValueQuery,196	// >;197	#[pallet::storage]198	pub type PendingUnstake<T: Config> = StorageMap<199		_,200		Twox64Concat,201		T::BlockNumber,202		BoundedVec<(T::AccountId, BalanceOf<T>), ConstU32<PENDING_LIMIT_PER_BLOCK>>,203		ValueQuery,204	>;205206	// /// A block when app-promotion has started .I think this is redundant, because we only need `NextInterestBlock`.207	// #[pallet::storage]208	// pub type StartBlock<T: Config> = StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;209210	// /// Next target block when interest is recalculated211	// #[pallet::storage]212	// #[pallet::getter(fn get_interest_block)]213	// pub type NextInterestBlock<T: Config> =214	// 	StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;215216	/// Stores hash a record for which the last revenue recalculation was performed.217	/// If `None`, then recalculation has not yet been performed or calculations have been completed for all stakers.218	#[pallet::storage]219	#[pallet::getter(fn get_next_calculated_record)]220	pub type NextCalculatedRecord<T: Config> =221		StorageValue<Value = (T::AccountId, T::BlockNumber), QueryKind = OptionQuery>;222223	#[pallet::hooks]224	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {225		fn on_initialize(current_block_number: T::BlockNumber) -> Weight226		where227			<T as frame_system::Config>::BlockNumber: From<u32>,228		{229			let mut consumed_weight = 0;230			let mut add_weight = |reads, writes, weight| {231				consumed_weight += T::DbWeight::get().reads_writes(reads, writes);232				consumed_weight += weight;233			};234235			let block_pending = PendingUnstake::<T>::take(current_block_number);236237			add_weight(0, 1, 0);238239			if !block_pending.is_empty() {240				block_pending.into_iter().for_each(|(staker, amount)| {241					<T::Currency as ReservableCurrency<T::AccountId>>::unreserve(&staker, amount);242				});243			}244245			consumed_weight246		}247	}248249	#[pallet::call]250	impl<T: Config> Pallet<T>251	where252		T::BlockNumber: From<u32> + Into<u32>,253		<<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum + From<u128>,254	{255		#[pallet::weight(T::WeightInfo::set_admin_address())]256		pub fn set_admin_address(origin: OriginFor<T>, admin: T::CrossAccountId) -> DispatchResult {257			ensure_root(origin)?;258259			<Admin<T>>::set(Some(admin.as_sub().to_owned()));260261			Self::deposit_event(Event::SetAdmin(admin.as_sub().to_owned()));262263			Ok(())264		}265266		#[pallet::weight(T::WeightInfo::stake())]267		pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {268			let staker_id = ensure_signed(staker)?;269270			ensure!(271				StakesPerAccount::<T>::get(&staker_id) < 10,272				Error::<T>::NoPermission273			);274275			ensure!(276				amount >= Into::<BalanceOf<T>>::into(100u128) * T::Nominal::get(),277				ArithmeticError::Underflow278			);279280			let balance =281				<<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);282283			ensure!(balance >= amount, ArithmeticError::Underflow);284285			<<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(286				&staker_id,287				amount,288				WithdrawReasons::all(),289				balance - amount,290			)?;291292			Self::add_lock_balance(&staker_id, amount)?;293294			let block_number = T::RelayBlockNumberProvider::current_block_number();295			let recalc_block = (block_number / T::RecalculationInterval::get() + 2u32.into())296				* T::RecalculationInterval::get();297298			<Staked<T>>::insert((&staker_id, block_number), {299				let mut balance_and_recalc_block = <Staked<T>>::get((&staker_id, block_number));300				balance_and_recalc_block.0 = balance_and_recalc_block301					.0302					.checked_add(&amount)303					.ok_or(ArithmeticError::Overflow)?;304				balance_and_recalc_block.1 = recalc_block;305				balance_and_recalc_block306			});307308			<TotalStaked<T>>::set(309				<TotalStaked<T>>::get()310					.checked_add(&amount)311					.ok_or(ArithmeticError::Overflow)?,312			);313314			StakesPerAccount::<T>::mutate(&staker_id, |stakes| *stakes += 1);315316			Self::deposit_event(Event::Stake(staker_id, amount));317318			Ok(())319		}320321		#[pallet::weight(T::WeightInfo::unstake())]322		pub fn unstake(staker: OriginFor<T>) -> DispatchResultWithPostInfo {323			let staker_id = ensure_signed(staker)?;324			let block = <frame_system::Pallet<T>>::block_number() + T::PendingInterval::get();325			let mut pendings = <PendingUnstake<T>>::get(block);326327			ensure!(!pendings.is_full(), Error::<T>::PendingForBlockOverflow);328329			let mut total_stakes = 0u64;330331			let total_staked: BalanceOf<T> = Staked::<T>::drain_prefix((&staker_id,))332				.map(|(_, (amount, _))| {333					total_stakes += 1;334					amount335				})336				.sum();337338			if total_staked.is_zero() {339				return Ok(None.into()); // TO-DO340			}341342			pendings343				.try_push((staker_id.clone(), total_staked))344				.map_err(|_| Error::<T>::PendingForBlockOverflow)?;345346			<PendingUnstake<T>>::insert(block, pendings);347348			Self::unlock_balance_unchecked(&staker_id, total_staked);349350			<T::Currency as ReservableCurrency<T::AccountId>>::reserve(&staker_id, total_staked)?;351352			TotalStaked::<T>::set(353				TotalStaked::<T>::get()354					.checked_sub(&total_staked)355					.ok_or(ArithmeticError::Underflow)?,356			);357358			StakesPerAccount::<T>::remove(&staker_id);359360			Self::deposit_event(Event::Unstake(staker_id, total_staked));361362			Ok(None.into())363		}364365		#[pallet::weight(T::WeightInfo::sponsor_collection())]366		pub fn sponsor_collection(367			admin: OriginFor<T>,368			collection_id: CollectionId,369		) -> DispatchResult {370			let admin_id = ensure_signed(admin)?;371			ensure!(372				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,373				Error::<T>::NoPermission374			);375376			T::CollectionHandler::set_sponsor(Self::account_id(), collection_id)377		}378		#[pallet::weight(T::WeightInfo::stop_sponsoring_collection())]379		pub fn stop_sponsoring_collection(380			admin: OriginFor<T>,381			collection_id: CollectionId,382		) -> DispatchResult {383			let admin_id = ensure_signed(admin)?;384385			ensure!(386				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,387				Error::<T>::NoPermission388			);389390			ensure!(391				T::CollectionHandler::get_sponsor(collection_id)?392					.ok_or(<Error<T>>::InvalidArgument)?393					== Self::account_id(),394				<Error<T>>::NoPermission395			);396			T::CollectionHandler::remove_collection_sponsor(collection_id)397		}398399		#[pallet::weight(T::WeightInfo::sponsor_contract())]400		pub fn sponsor_conract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {401			let admin_id = ensure_signed(admin)?;402403			ensure!(404				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,405				Error::<T>::NoPermission406			);407408			T::ContractHandler::set_sponsor(409				T::CrossAccountId::from_sub(Self::account_id()),410				contract_id,411			)412		}413414		#[pallet::weight(T::WeightInfo::stop_sponsoring_contract())]415		pub fn stop_sponsoring_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {416			let admin_id = ensure_signed(admin)?;417418			ensure!(419				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,420				Error::<T>::NoPermission421			);422423			ensure!(424				T::ContractHandler::get_sponsor(contract_id)?.ok_or(<Error<T>>::InvalidArgument)?425					== T::CrossAccountId::from_sub(Self::account_id()),426				<Error<T>>::NoPermission427			);428			T::ContractHandler::remove_contract_sponsor(contract_id)429		}430431		#[pallet::weight(0)]432		pub fn payout_stakers(admin: OriginFor<T>, stakers_number: Option<u8>) -> DispatchResult {433			let admin_id = ensure_signed(admin)?;434435			ensure!(436				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,437				Error::<T>::NoPermission438			);439440			let current_recalc_block =441				Self::get_current_recalc_block(T::RelayBlockNumberProvider::current_block_number());442			let next_recalc_block = current_recalc_block + T::RecalculationInterval::get();443444			let mut storage_iterator = Self::get_next_calculated_key()445				.map_or(Staked::<T>::iter(), |key| Staked::<T>::iter_from(key));446447			NextCalculatedRecord::<T>::set(None);448449			// {450			// 	let mut stakers_number = stakers_number.unwrap_or(20);451			// 	let mut last_id = admin_id;452			// 	let mut income_acc = BalanceOf::<T>::default();453			// 	let mut amount_acc = BalanceOf::<T>::default();454455			// 	while let Some((456			// 		(current_id, staked_block),457			// 		(amount, next_recalc_block_for_stake),458			// 	)) = storage_iterator.next()459			// 	{460			// 		if last_id != current_id {461			// 			if income_acc != BalanceOf::<T>::default() {462			// 				<T::Currency as Currency<T::AccountId>>::transfer(463			// 					&T::TreasuryAccountId::get(),464			// 					&last_id,465			// 					income_acc,466			// 					ExistenceRequirement::KeepAlive,467			// 				)468			// 				.and_then(|_| Self::add_lock_balance(&last_id, income_acc))?;469470			// 				Self::deposit_event(Event::StakingRecalculation(471			// 					last_id, amount, income_acc,472			// 				));473			// 			}474475			// 			if stakers_number == 0 {476			// 				NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));477			// 				break;478			// 			}479			// 			stakers_number -= 1;480			// 			income_acc = BalanceOf::<T>::default();481			// 			last_id = current_id;482			// 		};483			// 		if current_recalc_block >= next_recalc_block_for_stake {484			// 			Self::recalculate_and_insert_stake(485			// 				&last_id,486			// 				staked_block,487			// 				next_recalc_block,488			// 				amount,489			// 				((current_recalc_block - next_recalc_block_for_stake)490			// 					/ T::RecalculationInterval::get())491			// 				.into() + 1,492			// 				&mut income_acc,493			// 			);494			// 		}495			// 	}496			// }497498			{499				let mut stakers_number = stakers_number.unwrap_or(20);500				let last_id = RefCell::new(None);501				let income_acc = RefCell::new(BalanceOf::<T>::default());502				let amount_acc = RefCell::new(BalanceOf::<T>::default());503504				let flush_stake = || -> DispatchResult {505					if let Some(last_id) = &*last_id.borrow() {506						if !income_acc.borrow().is_zero() {507							<T::Currency as Currency<T::AccountId>>::transfer(508								&T::TreasuryAccountId::get(),509								last_id,510								*income_acc.borrow(),511								ExistenceRequirement::KeepAlive,512							)513							.and_then(|_| Self::add_lock_balance(last_id, *income_acc.borrow()))?;514515							Self::deposit_event(Event::StakingRecalculation(516								last_id.clone(),517								*amount_acc.borrow(),518								*income_acc.borrow(),519							));520						}521522						*income_acc.borrow_mut() = BalanceOf::<T>::default();523						*amount_acc.borrow_mut() = BalanceOf::<T>::default();524					}525					Ok(())526				};527528				while let Some((529					(current_id, staked_block),530					(amount, next_recalc_block_for_stake),531				)) = storage_iterator.next()532				{533					if stakers_number == 0 {534						NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));535						break;536					}537					stakers_number -= 1;538					if last_id.borrow().as_ref() != Some(&current_id) {539						flush_stake()?;540					};541					*last_id.borrow_mut() = Some(current_id.clone());542					if current_recalc_block >= next_recalc_block_for_stake {543						*amount_acc.borrow_mut() += amount;544						Self::recalculate_and_insert_stake(545							&current_id,546							staked_block,547							next_recalc_block,548							amount,549							((current_recalc_block - next_recalc_block_for_stake)550								/ T::RecalculationInterval::get())551							.into() + 1,552							&mut *income_acc.borrow_mut(),553						);554					}555				}556				flush_stake()?;557			}558559			Ok(())560		}561	}562}563564impl<T: Config> Pallet<T> {565	pub fn account_id() -> T::AccountId {566		T::PalletId::get().into_account_truncating()567	}568569	fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {570		let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();571		locked_balance -= amount;572		Self::set_lock_unchecked(staker, locked_balance);573	}574575	fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {576		Self::get_locked_balance(staker)577			.map_or(<BalanceOf<T>>::default(), |l| l.amount)578			.checked_add(&amount)579			.map(|new_lock| Self::set_lock_unchecked(staker, new_lock))580			.ok_or(ArithmeticError::Overflow.into())581	}582583	fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {584		if amount.is_zero() {585			<T::Currency as LockableCurrency<T::AccountId>>::remove_lock(LOCK_IDENTIFIER, &staker);586		} else {587			<T::Currency as LockableCurrency<T::AccountId>>::set_lock(588				LOCK_IDENTIFIER,589				staker,590				amount,591				WithdrawReasons::all(),592			)593		}594	}595596	pub fn get_locked_balance(597		staker: impl EncodeLike<T::AccountId>,598	) -> Option<BalanceLock<BalanceOf<T>>> {599		<T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)600			.into_iter()601			.find(|l| l.id == LOCK_IDENTIFIER)602	}603604	pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {605		let staked = Staked::<T>::iter_prefix((staker,))606			.into_iter()607			.fold(<BalanceOf<T>>::default(), |acc, (_, (amount, _))| {608				acc + amount609			});610		if staked != <BalanceOf<T>>::default() {611			Some(staked)612		} else {613			None614		}615	}616617	pub fn total_staked_by_id_per_block(618		staker: impl EncodeLike<T::AccountId>,619	) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {620		let mut staked = Staked::<T>::iter_prefix((staker,))621			.into_iter()622			.map(|(block, (amount, _))| (block, amount))623			.collect::<Vec<_>>();624		staked.sort_by_key(|(block, _)| *block);625		if !staked.is_empty() {626			Some(staked)627		} else {628			None629		}630	}631632	pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {633		staker.map_or(Some(<TotalStaked<T>>::get()), |s| {634			Self::total_staked_by_id(s.as_sub())635		})636		// Self::total_staked_by_id(staker.as_sub())637	}638639	pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {640		Self::get_locked_balance(staker.as_sub())641			.map(|l| l.amount)642			.unwrap_or_default()643	}644645	pub fn cross_id_total_staked_per_block(646		staker: T::CrossAccountId,647	) -> Vec<(T::BlockNumber, BalanceOf<T>)> {648		Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()649	}650651	fn recalculate_and_insert_stake(652		staker: &T::AccountId,653		staked_block: T::BlockNumber,654		next_recalc_block: T::BlockNumber,655		base: BalanceOf<T>,656		iters: u32,657		income_acc: &mut BalanceOf<T>,658	) {659		let income = Self::calculate_income(base, iters);660661		base.checked_add(&income).map(|res| {662			<Staked<T>>::insert((staker, staked_block), (res, next_recalc_block));663			*income_acc += income;664		});665	}666667	fn calculate_income<I>(base: I, iters: u32) -> I668	where669		I: EncodeLike<BalanceOf<T>> + Balance,670	{671		let mut income = base;672673		(0..iters).for_each(|_| income += T::IntervalIncome::get() * income);674675		income - base676	}677678	fn get_current_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {679		(current_relay_block / T::RecalculationInterval::get()) * T::RecalculationInterval::get()680	}681682	// fn get_next_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {683	// 	Self::get_current_recalc_block(current_relay_block) + T::RecalculationInterval::get()684	// }685686	fn get_next_calculated_key() -> Option<Vec<u8>> {687		Self::get_next_calculated_record().map(|key| Staked::<T>::hashed_key_for(key))688	}689}690691impl<T: Config> Pallet<T>692where693	<<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum,694{695	pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {696		staker.map_or(697			PendingUnstake::<T>::iter_values()698				.flat_map(|pendings| pendings.into_iter().map(|(_, amount)| amount))699				.sum(),700			|s| {701				PendingUnstake::<T>::iter_values()702					.flatten()703					.filter_map(|(id, amount)| {704						if id == *s.as_sub() {705							Some(amount)706						} else {707							None708						}709					})710					.sum()711			},712		)713	}714715	pub fn cross_id_pending_unstake_per_block(716		staker: T::CrossAccountId,717	) -> Vec<(T::BlockNumber, BalanceOf<T>)> {718		let mut unsorted_res = vec![];719		PendingUnstake::<T>::iter().for_each(|(block, pendings)| {720			pendings.into_iter().for_each(|(id, amount)| {721				if id == *staker.as_sub() {722					unsorted_res.push((block, amount));723				};724			})725		});726727		unsorted_res.sort_by_key(|(block, _)| *block);728		unsorted_res729	}730}
modifiedpallets/app-promotion/src/types.rsdiffbeforeafterboth
--- a/pallets/app-promotion/src/types.rs
+++ b/pallets/app-promotion/src/types.rs
@@ -5,7 +5,7 @@
 
 use pallet_balances::{BalanceLock, Config as BalancesConfig, Pallet as PalletBalances};
 use pallet_common::CollectionHandle;
-use pallet_unique::{Event as UniqueEvent, Error as UniqueError};
+
 use sp_runtime::DispatchError;
 use up_data_structs::{CollectionId, SponsorshipState};
 use sp_std::borrow::ToOwned;
@@ -53,36 +53,11 @@
 		sponsor_id: Self::AccountId,
 		collection_id: Self::CollectionId,
 	) -> DispatchResult {
-		let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
-		target_collection.check_is_internal()?;
-		target_collection.set_sponsor(sponsor_id.clone())?;
-
-		Self::deposit_event(UniqueEvent::<T>::CollectionSponsorSet(
-			collection_id,
-			sponsor_id.clone(),
-		));
-
-		ensure!(
-			target_collection.confirm_sponsorship(&sponsor_id)?,
-			UniqueError::<T>::ConfirmUnsetSponsorFail
-		);
-
-		Self::deposit_event(UniqueEvent::<T>::SponsorshipConfirmed(
-			collection_id,
-			sponsor_id,
-		));
-
-		target_collection.save()
+		Self::force_set_sponsor(sponsor_id, collection_id)
 	}
 
 	fn remove_collection_sponsor(collection_id: Self::CollectionId) -> DispatchResult {
-		let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
-		target_collection.check_is_internal()?;
-		target_collection.sponsorship = SponsorshipState::Disabled;
-
-		Self::deposit_event(UniqueEvent::<T>::CollectionSponsorRemoved(collection_id));
-
-		target_collection.save()
+		Self::force_remove_collection_sponsor(collection_id)
 	}
 
 	fn get_sponsor(
modifiedpallets/unique/CHANGELOG.mddiffbeforeafterboth
--- a/pallets/unique/CHANGELOG.md
+++ b/pallets/unique/CHANGELOG.md
@@ -3,22 +3,27 @@
 All notable changes to this project will be documented in this file.
 
 <!-- bureaucrate goes here -->
+
+## [v0.1.4] 2022-09-5
+
+### Added
+
 ## [v0.1.3] 2022-08-16
 
 ### Other changes
 
-- build: Upgrade polkadot to v0.9.27 2c498572636f2b34d53b1c51b7283a761a7dc90a
+-   build: Upgrade polkadot to v0.9.27 2c498572636f2b34d53b1c51b7283a761a7dc90a
 
-- build: Upgrade polkadot to v0.9.26 85515e54c4ca1b82a2630034e55dcc804c643bf8
+-   build: Upgrade polkadot to v0.9.26 85515e54c4ca1b82a2630034e55dcc804c643bf8
 
-- refactor: Remove `#[transactional]` from extrinsics 7fd36cea2f6e00c02c67ccc1de9649ae404efd31
+-   refactor: Remove `#[transactional]` from extrinsics 7fd36cea2f6e00c02c67ccc1de9649ae404efd31
 
 Every extrinsic now runs in transaction implicitly, and
 `#[transactional]` on pallet dispatchable is now meaningless
 
 Upstream-Change: https://github.com/paritytech/substrate/issues/10806
 
-- refactor: Switch to new prefix removal methods 26734e9567589d75cdd99e404eabf11d5a97d975
+-   refactor: Switch to new prefix removal methods 26734e9567589d75cdd99e404eabf11d5a97d975
 
 New methods allows to call `remove_prefix` with limit multiple times
 in the same block
@@ -27,10 +32,12 @@
 
 Upstream-Change: https://github.com/paritytech/substrate/pull/11490
 
-- build: Upgrade polkadot to v0.9.25 cdfb9bdc7b205ff1b5134f034ef9973d769e5e6b
+-   build: Upgrade polkadot to v0.9.25 cdfb9bdc7b205ff1b5134f034ef9973d769e5e6b
 
 ## [v0.1.1] - 2022-07-25
+
 ### Added
-- Method for creating `ERC721Metadata` compatible NFT collection.
-- Method for creating `ERC721Metadata` compatible ReFungible collection.
-- Method for creating ReFungible collection.
+
+-   Method for creating `ERC721Metadata` compatible NFT collection.
+-   Method for creating `ERC721Metadata` compatible ReFungible collection.
+-   Method for creating ReFungible collection.
modifiedpallets/unique/Cargo.tomldiffbeforeafterboth
--- a/pallets/unique/Cargo.toml
+++ b/pallets/unique/Cargo.toml
@@ -9,7 +9,7 @@
 license = 'GPLv3'
 name = 'pallet-unique'
 repository = 'https://github.com/UniqueNetwork/unique-chain'
-version = "0.1.3"
+version = "0.1.4"
 
 [package.metadata.docs.rs]
 targets = ['x86_64-unknown-linux-gnu']
modifiedpallets/unique/src/lib.rsdiffbeforeafterboth
--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -1103,3 +1103,35 @@
 		}
 	}
 }
+
+impl<T: Config> Pallet<T> {
+	pub fn force_set_sponsor(sponsor: T::AccountId, collection_id: CollectionId) -> DispatchResult {
+		let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
+		target_collection.check_is_internal()?;
+		target_collection.set_sponsor(sponsor.clone())?;
+
+		Self::deposit_event(Event::<T>::CollectionSponsorSet(
+			collection_id,
+			sponsor.clone(),
+		));
+
+		ensure!(
+			target_collection.confirm_sponsorship(&sponsor)?,
+			Error::<T>::ConfirmUnsetSponsorFail
+		);
+
+		Self::deposit_event(Event::<T>::SponsorshipConfirmed(collection_id, sponsor));
+
+		target_collection.save()
+	}
+
+	pub fn force_remove_collection_sponsor(collection_id: CollectionId) -> DispatchResult {
+		let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
+		target_collection.check_is_internal()?;
+		target_collection.sponsorship = SponsorshipState::Disabled;
+
+		Self::deposit_event(Event::<T>::CollectionSponsorRemoved(collection_id));
+
+		target_collection.save()
+	}
+}
modifiedruntime/common/config/pallets/app_promotion.rsdiffbeforeafterboth
--- a/runtime/common/config/pallets/app_promotion.rs
+++ b/runtime/common/config/pallets/app_promotion.rs
@@ -22,7 +22,7 @@
 use frame_support::{parameter_types, PalletId};
 use sp_arithmetic::Perbill;
 use up_common::{
-	constants::{DAYS, UNIQUE, RELAY_DAYS},
+	constants::{ UNIQUE, RELAY_DAYS},
 	types::Balance,
 };
 
@@ -32,7 +32,7 @@
 	pub const RecalculationInterval: BlockNumber = 20;
 	pub const PendingInterval: BlockNumber = 10;
 	pub const Nominal: Balance = UNIQUE;
-	pub const Day: BlockNumber = DAYS;
+	// pub const Day: BlockNumber = DAYS;
 	pub IntervalIncome: Perbill = Perbill::from_rational(RecalculationInterval::get(), RELAY_DAYS) * Perbill::from_rational(5u32, 10_000);
 }
 
@@ -42,7 +42,7 @@
 	pub const RecalculationInterval: BlockNumber = RELAY_DAYS;
 	pub const PendingInterval: BlockNumber = 7 * RELAY_DAYS;
 	pub const Nominal: Balance = UNIQUE;
-	pub const Day: BlockNumber = RELAY_DAYS;
+	// pub const Day: BlockNumber = RELAY_DAYS;
 	pub IntervalIncome: Perbill = Perbill::from_rational(5u32, 10_000);
 }
 
@@ -56,7 +56,7 @@
 	type RelayBlockNumberProvider = RelayChainBlockNumberProvider<Runtime>;
 	type RecalculationInterval = RecalculationInterval;
 	type PendingInterval = PendingInterval;
-	type Day = Day;
+	// type Day = Day;
 	type Nominal = Nominal;
 	type IntervalIncome = IntervalIncome;
 	type Event = Event;