git.delta.rocks / unique-network / refs/commits / 35da4ee40eea

difftreelog

code refactor & comments

PraetorP2022-09-08parent: #f980616.patch.diff
in: master

12 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -12147,7 +12147,7 @@
 
 [[package]]
 name = "uc-rpc"
-version = "0.1.3"
+version = "0.1.4"
 dependencies = [
  "anyhow",
  "app-promotion-rpc",
modifiedclient/rpc/CHANGELOG.mddiffbeforeafterboth
--- a/client/rpc/CHANGELOG.md
+++ b/client/rpc/CHANGELOG.md
@@ -3,15 +3,21 @@
 All notable changes to this project will be documented in this file.
 
 <!-- bureaucrate goes here -->
+
+## [v0.1.4] 2022-09-08
+
+### Added
+-  Support RPC for `AppPromotion` pallet. 
+
 ## [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
 
-- build: Upgrade polkadot to v0.9.25 cdfb9bdc7b205ff1b5134f034ef9973d769e5e6b
+-   build: Upgrade polkadot to v0.9.25 cdfb9bdc7b205ff1b5134f034ef9973d769e5e6b
 
 ## [0.1.2] - 2022-08-12
 
modifiedclient/rpc/Cargo.tomldiffbeforeafterboth
--- a/client/rpc/Cargo.toml
+++ b/client/rpc/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "uc-rpc"
-version = "0.1.3"
+version = "0.1.4"
 license = "GPLv3"
 edition = "2021"
 
modifiedpallets/app-promotion/src/lib.rsdiffbeforeafterboth
before · 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		#[pallet::constant]114		type Nominal: Get<BalanceOf<Self>>;115116		#[pallet::constant]117		type IntervalIncome: Get<Perbill>;118119		/// Weight information for extrinsics in this pallet.120		type WeightInfo: WeightInfo;121122		// The relay block number provider123		type RelayBlockNumberProvider: BlockNumberProvider<BlockNumber = Self::BlockNumber>;124125		/// Events compatible with [`frame_system::Config::Event`].126		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;127	}128129	#[pallet::pallet]130	#[pallet::generate_store(pub(super) trait Store)]131	pub struct Pallet<T>(_);132133	#[pallet::event]134	#[pallet::generate_deposit(fn deposit_event)]135	pub enum Event<T: Config> {136		StakingRecalculation(137			/// An recalculated staker138			T::AccountId,139			/// Base on which interest is calculated140			BalanceOf<T>,141			/// Amount of accrued interest142			BalanceOf<T>,143		),144		Stake(T::AccountId, BalanceOf<T>),145		Unstake(T::AccountId, BalanceOf<T>),146		SetAdmin(T::AccountId),147	}148149	#[pallet::error]150	pub enum Error<T> {151		/// Error due to action requiring admin to be set152		AdminNotSet,153		/// No permission to perform an action154		NoPermission,155		/// Insufficient funds to perform an action156		NotSufficientFunds,157		PendingForBlockOverflow,158		/// An error related to the fact that an invalid argument was passed to perform an action159		SponsorNotSet,160	}161162	#[pallet::storage]163	pub type TotalStaked<T: Config> = StorageValue<Value = BalanceOf<T>, QueryKind = ValueQuery>;164165	#[pallet::storage]166	pub type Admin<T: Config> = StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;167168	/// Amount of tokens staked by account in the blocknumber.169	#[pallet::storage]170	pub type Staked<T: Config> = StorageNMap<171		Key = (172			Key<Blake2_128Concat, T::AccountId>,173			Key<Twox64Concat, T::BlockNumber>,174		),175		Value = (BalanceOf<T>, T::BlockNumber),176		QueryKind = ValueQuery,177	>;178	/// Amount of stakes for an Account179	#[pallet::storage]180	pub type StakesPerAccount<T: Config> =181		StorageMap<_, Blake2_128Concat, T::AccountId, u8, ValueQuery>;182183	#[pallet::storage]184	pub type PendingUnstake<T: Config> = StorageMap<185		_,186		Twox64Concat,187		T::BlockNumber,188		BoundedVec<(T::AccountId, BalanceOf<T>), ConstU32<PENDING_LIMIT_PER_BLOCK>>,189		ValueQuery,190	>;191192	/// Stores hash a record for which the last revenue recalculation was performed.193	/// If `None`, then recalculation has not yet been performed or calculations have been completed for all stakers.194	#[pallet::storage]195	#[pallet::getter(fn get_next_calculated_record)]196	pub type NextCalculatedRecord<T: Config> =197		StorageValue<Value = (T::AccountId, T::BlockNumber), QueryKind = OptionQuery>;198199	#[pallet::hooks]200	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {201		fn on_initialize(current_block_number: T::BlockNumber) -> Weight202		where203			<T as frame_system::Config>::BlockNumber: From<u32>,204		{205			let block_pending = PendingUnstake::<T>::take(current_block_number);206			let counter = block_pending.len() as u32;207208			if !block_pending.is_empty() {209				block_pending.into_iter().for_each(|(staker, amount)| {210					<T::Currency as ReservableCurrency<T::AccountId>>::unreserve(&staker, amount);211				});212			}213214			T::WeightInfo::on_initialize(counter)215		}216	}217218	#[pallet::call]219	impl<T: Config> Pallet<T>220	where221		T::BlockNumber: From<u32> + Into<u32>,222		<<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum + From<u128>,223	{224		#[pallet::weight(T::WeightInfo::set_admin_address())]225		pub fn set_admin_address(origin: OriginFor<T>, admin: T::CrossAccountId) -> DispatchResult {226			ensure_root(origin)?;227228			<Admin<T>>::set(Some(admin.as_sub().to_owned()));229230			Self::deposit_event(Event::SetAdmin(admin.as_sub().to_owned()));231232			Ok(())233		}234235		#[pallet::weight(T::WeightInfo::stake())]236		pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {237			let staker_id = ensure_signed(staker)?;238239			ensure!(240				StakesPerAccount::<T>::get(&staker_id) < 10,241				Error::<T>::NoPermission242			);243244			ensure!(245				amount >= Into::<BalanceOf<T>>::into(100u128) * T::Nominal::get(),246				ArithmeticError::Underflow247			);248249			let balance =250				<<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);251252			// ensure!(balance >= amount, ArithmeticError::Underflow);253254			<<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(255				&staker_id,256				amount,257				WithdrawReasons::all(),258				balance259					.checked_sub(&amount)260					.ok_or(ArithmeticError::Underflow)?,261			)?;262263			Self::add_lock_balance(&staker_id, amount)?;264265			let block_number = T::RelayBlockNumberProvider::current_block_number();266267			let recalculate_after_interval: T::BlockNumber =268				if block_number % T::RecalculationInterval::get() == 0u32.into() {269					1u32.into()270				} else {271					2u32.into()272				};273274			let recalc_block = (block_number / T::RecalculationInterval::get()275				+ recalculate_after_interval)276				* T::RecalculationInterval::get();277278			<Staked<T>>::insert((&staker_id, block_number), {279				let mut balance_and_recalc_block = <Staked<T>>::get((&staker_id, block_number));280				balance_and_recalc_block.0 = balance_and_recalc_block281					.0282					.checked_add(&amount)283					.ok_or(ArithmeticError::Overflow)?;284				balance_and_recalc_block.1 = recalc_block;285				balance_and_recalc_block286			});287288			<TotalStaked<T>>::set(289				<TotalStaked<T>>::get()290					.checked_add(&amount)291					.ok_or(ArithmeticError::Overflow)?,292			);293294			StakesPerAccount::<T>::mutate(&staker_id, |stakes| *stakes += 1);295296			Self::deposit_event(Event::Stake(staker_id, amount));297298			Ok(())299		}300301		#[pallet::weight(T::WeightInfo::unstake())]302		pub fn unstake(staker: OriginFor<T>) -> DispatchResultWithPostInfo {303			let staker_id = ensure_signed(staker)?;304			let block = <frame_system::Pallet<T>>::block_number() + T::PendingInterval::get();305			let mut pendings = <PendingUnstake<T>>::get(block);306307			ensure!(!pendings.is_full(), Error::<T>::PendingForBlockOverflow);308309			let mut total_stakes = 0u64;310311			let total_staked: BalanceOf<T> = Staked::<T>::drain_prefix((&staker_id,))312				.map(|(_, (amount, _))| {313					total_stakes += 1;314					amount315				})316				.sum();317318			if total_staked.is_zero() {319				return Ok(None.into()); // TO-DO320			}321322			pendings323				.try_push((staker_id.clone(), total_staked))324				.map_err(|_| Error::<T>::PendingForBlockOverflow)?;325326			<PendingUnstake<T>>::insert(block, pendings);327328			Self::unlock_balance_unchecked(&staker_id, total_staked);329330			<T::Currency as ReservableCurrency<T::AccountId>>::reserve(&staker_id, total_staked)?;331332			TotalStaked::<T>::set(333				TotalStaked::<T>::get()334					.checked_sub(&total_staked)335					.ok_or(ArithmeticError::Underflow)?,336			);337338			StakesPerAccount::<T>::remove(&staker_id);339340			Self::deposit_event(Event::Unstake(staker_id, total_staked));341342			Ok(None.into())343		}344345		#[pallet::weight(T::WeightInfo::sponsor_collection())]346		pub fn sponsor_collection(347			admin: OriginFor<T>,348			collection_id: CollectionId,349		) -> DispatchResult {350			let admin_id = ensure_signed(admin)?;351			ensure!(352				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,353				Error::<T>::NoPermission354			);355356			T::CollectionHandler::set_sponsor(Self::account_id(), collection_id)357		}358		#[pallet::weight(T::WeightInfo::stop_sponsoring_collection())]359		pub fn stop_sponsoring_collection(360			admin: OriginFor<T>,361			collection_id: CollectionId,362		) -> DispatchResult {363			let admin_id = ensure_signed(admin)?;364365			ensure!(366				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,367				Error::<T>::NoPermission368			);369370			ensure!(371				T::CollectionHandler::sponsor(collection_id)?.ok_or(<Error<T>>::SponsorNotSet)?372					== Self::account_id(),373				<Error<T>>::NoPermission374			);375			T::CollectionHandler::remove_collection_sponsor(collection_id)376		}377378		#[pallet::weight(T::WeightInfo::sponsor_contract())]379		pub fn sponsor_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {380			let admin_id = ensure_signed(admin)?;381382			ensure!(383				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,384				Error::<T>::NoPermission385			);386387			T::ContractHandler::set_sponsor(388				T::CrossAccountId::from_sub(Self::account_id()),389				contract_id,390			)391		}392393		#[pallet::weight(T::WeightInfo::stop_sponsoring_contract())]394		pub fn stop_sponsoring_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {395			let admin_id = ensure_signed(admin)?;396397			ensure!(398				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,399				Error::<T>::NoPermission400			);401402			ensure!(403				T::ContractHandler::sponsor(contract_id)?.ok_or(<Error<T>>::SponsorNotSet)?404					== T::CrossAccountId::from_sub(Self::account_id()),405				<Error<T>>::NoPermission406			);407			T::ContractHandler::remove_contract_sponsor(contract_id)408		}409410		#[pallet::weight(T::WeightInfo::payout_stakers(stakers_number.unwrap_or(20) as u32))]411		pub fn payout_stakers(admin: OriginFor<T>, stakers_number: Option<u8>) -> DispatchResult {412			let admin_id = ensure_signed(admin)?;413414			ensure!(415				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,416				Error::<T>::NoPermission417			);418419			let current_recalc_block =420				Self::get_current_recalc_block(T::RelayBlockNumberProvider::current_block_number());421			let next_recalc_block = current_recalc_block + T::RecalculationInterval::get();422423			let mut storage_iterator = Self::get_next_calculated_key()424				.map_or(Staked::<T>::iter(), |key| Staked::<T>::iter_from(key));425426			NextCalculatedRecord::<T>::set(None);427428			// {429			// 	let mut stakers_number = stakers_number.unwrap_or(20);430			// 	let mut last_id = admin_id;431			// 	let mut income_acc = BalanceOf::<T>::default();432			// 	let mut amount_acc = BalanceOf::<T>::default();433434			// 	while let Some((435			// 		(current_id, staked_block),436			// 		(amount, next_recalc_block_for_stake),437			// 	)) = storage_iterator.next()438			// 	{439			// 		if last_id != current_id {440			// 			if income_acc != BalanceOf::<T>::default() {441			// 				<T::Currency as Currency<T::AccountId>>::transfer(442			// 					&T::TreasuryAccountId::get(),443			// 					&last_id,444			// 					income_acc,445			// 					ExistenceRequirement::KeepAlive,446			// 				)447			// 				.and_then(|_| Self::add_lock_balance(&last_id, income_acc))?;448449			// 				Self::deposit_event(Event::StakingRecalculation(450			// 					last_id, amount, income_acc,451			// 				));452			// 			}453454			// 			if stakers_number == 0 {455			// 				NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));456			// 				break;457			// 			}458			// 			stakers_number -= 1;459			// 			income_acc = BalanceOf::<T>::default();460			// 			last_id = current_id;461			// 		};462			// 		if current_recalc_block >= next_recalc_block_for_stake {463			// 			Self::recalculate_and_insert_stake(464			// 				&last_id,465			// 				staked_block,466			// 				next_recalc_block,467			// 				amount,468			// 				((current_recalc_block - next_recalc_block_for_stake)469			// 					/ T::RecalculationInterval::get())470			// 				.into() + 1,471			// 				&mut income_acc,472			// 			);473			// 		}474			// 	}475			// }476477			// {478			// 	let mut stakers_number = stakers_number.unwrap_or(20);479			// 	let last_id = RefCell::new(None);480			// 	let income_acc = RefCell::new(BalanceOf::<T>::default());481			// 	let amount_acc = RefCell::new(BalanceOf::<T>::default());482483			// 	let flush_stake = || -> DispatchResult {484			// 		if let Some(last_id) = &*last_id.borrow() {485			// 			if !income_acc.borrow().is_zero() {486			// 				<T::Currency as Currency<T::AccountId>>::transfer(487			// 					&T::TreasuryAccountId::get(),488			// 					last_id,489			// 					*income_acc.borrow(),490			// 					ExistenceRequirement::KeepAlive,491			// 				)492			// 				.and_then(|_| {493			// 					Self::add_lock_balance(last_id, *income_acc.borrow());494			// 					<TotalStaked<T>>::try_mutate(|staked| {495			// 						staked496			// 							.checked_add(&*income_acc.borrow())497			// 							.ok_or(ArithmeticError::Overflow.into())498			// 					})499			// 				})?;500501			// 				Self::deposit_event(Event::StakingRecalculation(502			// 					last_id.clone(),503			// 					*amount_acc.borrow(),504			// 					*income_acc.borrow(),505			// 				));506			// 			}507508			// 			*income_acc.borrow_mut() = BalanceOf::<T>::default();509			// 			*amount_acc.borrow_mut() = BalanceOf::<T>::default();510			// 		}511			// 		Ok(())512			// 	};513514			// 	while let Some((515			// 		(current_id, staked_block),516			// 		(amount, next_recalc_block_for_stake),517			// 	)) = storage_iterator.next()518			// 	{519			// 		if stakers_number == 0 {520			// 			NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));521			// 			break;522			// 		}523			// 		stakers_number -= 1;524			// 		if last_id.borrow().as_ref() != Some(&current_id) {525			// 			flush_stake()?;526			// 		};527			// 		*last_id.borrow_mut() = Some(current_id.clone());528			// 		if current_recalc_block >= next_recalc_block_for_stake {529			// 			*amount_acc.borrow_mut() += amount;530			// 			Self::recalculate_and_insert_stake(531			// 				&current_id,532			// 				staked_block,533			// 				next_recalc_block,534			// 				amount,535			// 				((current_recalc_block - next_recalc_block_for_stake)536			// 					/ T::RecalculationInterval::get())537			// 				.into() + 1,538			// 				&mut *income_acc.borrow_mut(),539			// 			);540			// 		}541			// 	}542			// 	flush_stake()?;543			// }544545			{546				let mut stakers_number = stakers_number.unwrap_or(20);547				let last_id = RefCell::new(None);548				let income_acc = RefCell::new(BalanceOf::<T>::default());549				let amount_acc = RefCell::new(BalanceOf::<T>::default());550551				let flush_stake = || -> DispatchResult {552					if let Some(last_id) = &*last_id.borrow() {553						if !income_acc.borrow().is_zero() {554							<T::Currency as Currency<T::AccountId>>::transfer(555								&T::TreasuryAccountId::get(),556								last_id,557								*income_acc.borrow(),558								ExistenceRequirement::KeepAlive,559							)560							.and_then(|_| {561								Self::add_lock_balance(last_id, *income_acc.borrow())?;562								<TotalStaked<T>>::try_mutate(|staked| {563									staked564										.checked_add(&*income_acc.borrow())565										.ok_or(ArithmeticError::Overflow.into())566								})567							})?;568569							Self::deposit_event(Event::StakingRecalculation(570								last_id.clone(),571								*amount_acc.borrow(),572								*income_acc.borrow(),573							));574						}575576						*income_acc.borrow_mut() = BalanceOf::<T>::default();577						*amount_acc.borrow_mut() = BalanceOf::<T>::default();578					}579					Ok(())580				};581582				while let Some((583					(current_id, staked_block),584					(amount, next_recalc_block_for_stake),585				)) = storage_iterator.next()586				{587					if stakers_number == 0 {588						NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));589						break;590					}591					if last_id.borrow().as_ref() != Some(&current_id) {592						flush_stake()?;593						*last_id.borrow_mut() = Some(current_id.clone());594						stakers_number -= 1;595					};596					if current_recalc_block >= next_recalc_block_for_stake {597						*amount_acc.borrow_mut() += amount;598						Self::recalculate_and_insert_stake(599							&current_id,600							staked_block,601							next_recalc_block,602							amount,603							((current_recalc_block - next_recalc_block_for_stake)604								/ T::RecalculationInterval::get())605							.into() + 1,606							&mut *income_acc.borrow_mut(),607						);608					}609				}610				flush_stake()?;611			}612613			Ok(())614		}615	}616}617618impl<T: Config> Pallet<T> {619	pub fn account_id() -> T::AccountId {620		T::PalletId::get().into_account_truncating()621	}622623	fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {624		let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();625		locked_balance -= amount;626		Self::set_lock_unchecked(staker, locked_balance);627	}628629	fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {630		Self::get_locked_balance(staker)631			.map_or(<BalanceOf<T>>::default(), |l| l.amount)632			.checked_add(&amount)633			.map(|new_lock| Self::set_lock_unchecked(staker, new_lock))634			.ok_or(ArithmeticError::Overflow.into())635	}636637	fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {638		if amount.is_zero() {639			<T::Currency as LockableCurrency<T::AccountId>>::remove_lock(LOCK_IDENTIFIER, &staker);640		} else {641			<T::Currency as LockableCurrency<T::AccountId>>::set_lock(642				LOCK_IDENTIFIER,643				staker,644				amount,645				WithdrawReasons::all(),646			)647		}648	}649650	pub fn get_locked_balance(651		staker: impl EncodeLike<T::AccountId>,652	) -> Option<BalanceLock<BalanceOf<T>>> {653		<T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)654			.into_iter()655			.find(|l| l.id == LOCK_IDENTIFIER)656	}657658	pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {659		let staked = Staked::<T>::iter_prefix((staker,))660			.into_iter()661			.fold(<BalanceOf<T>>::default(), |acc, (_, (amount, _))| {662				acc + amount663			});664		if staked != <BalanceOf<T>>::default() {665			Some(staked)666		} else {667			None668		}669	}670671	pub fn total_staked_by_id_per_block(672		staker: impl EncodeLike<T::AccountId>,673	) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {674		let mut staked = Staked::<T>::iter_prefix((staker,))675			.into_iter()676			.map(|(block, (amount, _))| (block, amount))677			.collect::<Vec<_>>();678		staked.sort_by_key(|(block, _)| *block);679		if !staked.is_empty() {680			Some(staked)681		} else {682			None683		}684	}685686	pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {687		staker.map_or(Some(<TotalStaked<T>>::get()), |s| {688			Self::total_staked_by_id(s.as_sub())689		})690	}691692	// pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {693	// 	Self::get_locked_balance(staker.as_sub())694	// 		.map(|l| l.amount)695	// 		.unwrap_or_default()696	// }697698	pub fn cross_id_total_staked_per_block(699		staker: T::CrossAccountId,700	) -> Vec<(T::BlockNumber, BalanceOf<T>)> {701		Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()702	}703704	fn recalculate_and_insert_stake(705		staker: &T::AccountId,706		staked_block: T::BlockNumber,707		next_recalc_block: T::BlockNumber,708		base: BalanceOf<T>,709		iters: u32,710		income_acc: &mut BalanceOf<T>,711	) {712		let income = Self::calculate_income(base, iters);713714		base.checked_add(&income).map(|res| {715			<Staked<T>>::insert((staker, staked_block), (res, next_recalc_block));716			*income_acc += income;717		});718	}719720	fn calculate_income<I>(base: I, iters: u32) -> I721	where722		I: EncodeLike<BalanceOf<T>> + Balance,723	{724		let mut income = base;725726		(0..iters).for_each(|_| income += T::IntervalIncome::get() * income);727728		income - base729	}730731	fn get_current_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {732		(current_relay_block / T::RecalculationInterval::get()) * T::RecalculationInterval::get()733	}734735	// fn get_next_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {736	// 	Self::get_current_recalc_block(current_relay_block) + T::RecalculationInterval::get()737	// }738739	fn get_next_calculated_key() -> Option<Vec<u8>> {740		Self::get_next_calculated_record().map(|key| Staked::<T>::hashed_key_for(key))741	}742}743744impl<T: Config> Pallet<T>745where746	<<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum,747{748	pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {749		staker.map_or(750			PendingUnstake::<T>::iter_values()751				.flat_map(|pendings| pendings.into_iter().map(|(_, amount)| amount))752				.sum(),753			|s| {754				PendingUnstake::<T>::iter_values()755					.flatten()756					.filter_map(|(id, amount)| {757						if id == *s.as_sub() {758							Some(amount)759						} else {760							None761						}762					})763					.sum()764			},765		)766	}767768	pub fn cross_id_pending_unstake_per_block(769		staker: T::CrossAccountId,770	) -> Vec<(T::BlockNumber, BalanceOf<T>)> {771		let mut unsorted_res = vec![];772		PendingUnstake::<T>::iter().for_each(|(block, pendings)| {773			pendings.into_iter().for_each(|(id, amount)| {774				if id == *staker.as_sub() {775					unsorted_res.push((block, amount));776				};777			})778		});779780		unsorted_res.sort_by_key(|(block, _)| *block);781		unsorted_res782	}783}
modifiedpallets/unique/CHANGELOG.mddiffbeforeafterboth
--- a/pallets/unique/CHANGELOG.md
+++ b/pallets/unique/CHANGELOG.md
@@ -8,6 +8,8 @@
 
 ### Added
 
+-   Methods `force_set_sponsor` , `force_remove_collection_sponsor` to be able to administer sponsorships with other pallets. Added to implement `AppPromotion` pallet logic.
+
 ## [v0.1.3] 2022-08-16
 
 ### Other changes
modifiedtests/src/interfaces/augment-api-consts.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-consts.ts
+++ b/tests/src/interfaces/augment-api-consts.ts
@@ -16,14 +16,20 @@
 declare module '@polkadot/api-base/types/consts' {
   interface AugmentedConsts<ApiType extends ApiTypes> {
     appPromotion: {
+      /**
+       * Rate of return for interval in blocks defined in `RecalculationInterval`.
+       **/
       intervalIncome: Perbill & AugmentedConst<ApiType>;
+      /**
+       * Decimals for the `Currency`.
+       **/
       nominal: u128 & AugmentedConst<ApiType>;
       /**
        * The app's pallet id, used for deriving its sovereign account ID.
        **/
       palletId: FrameSupportPalletId & AugmentedConst<ApiType>;
       /**
-       * In relay blocks.
+       * In parachain blocks.
        **/
       pendingInterval: u32 & AugmentedConst<ApiType>;
       /**
modifiedtests/src/interfaces/augment-api-errors.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-errors.ts
+++ b/tests/src/interfaces/augment-api-errors.ts
@@ -13,23 +13,30 @@
   interface AugmentedErrors<ApiType extends ApiTypes> {
     appPromotion: {
       /**
-       * Error due to action requiring admin to be set
+       * Error due to action requiring admin to be set.
        **/
       AdminNotSet: AugmentedError<ApiType>;
       /**
-       * An error related to the fact that an invalid argument was passed to perform an action
+       * Errors caused by incorrect actions with a locked balance.
        **/
-      InvalidArgument: AugmentedError<ApiType>;
+      IncorrectLockedBalanceOperation: AugmentedError<ApiType>;
       /**
-       * No permission to perform an action
+       * No permission to perform an action.
        **/
       NoPermission: AugmentedError<ApiType>;
       /**
-       * Insufficient funds to perform an action
+       * Insufficient funds to perform an action.
        **/
       NotSufficientFunds: AugmentedError<ApiType>;
+      /**
+       * Occurs when a pending unstake cannot be added in this block. PENDING_LIMIT_PER_BLOCK` limits exceeded.
+       **/
       PendingForBlockOverflow: AugmentedError<ApiType>;
       /**
+       * The error is due to the fact that the collection/contract must already be sponsored in order to perform the action.
+       **/
+      SponsorNotSet: AugmentedError<ApiType>;
+      /**
        * Generic error
        **/
       [key: string]: AugmentedError<ApiType>;
modifiedtests/src/interfaces/augment-api-events.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-events.ts
+++ b/tests/src/interfaces/augment-api-events.ts
@@ -16,9 +16,37 @@
 declare module '@polkadot/api-base/types/events' {
   interface AugmentedEvents<ApiType extends ApiTypes> {
     appPromotion: {
+      /**
+       * The admin was set
+       * 
+       * # Arguments
+       * * AccountId: ID of the admin
+       **/
       SetAdmin: AugmentedEvent<ApiType, [AccountId32]>;
+      /**
+       * Staking was performed
+       * 
+       * # Arguments
+       * * AccountId: ID of the staker
+       * * Balance : staking amount
+       **/
       Stake: AugmentedEvent<ApiType, [AccountId32, u128]>;
+      /**
+       * Staking recalculation was performed
+       * 
+       * # Arguments
+       * * AccountId: ID of the staker.
+       * * Balance : recalculation base
+       * * Balance : total income
+       **/
       StakingRecalculation: AugmentedEvent<ApiType, [AccountId32, u128, u128]>;
+      /**
+       * Unstaking was performed
+       * 
+       * # Arguments
+       * * AccountId: ID of the staker
+       * * Balance : unstaking amount
+       **/
       Unstake: AugmentedEvent<ApiType, [AccountId32, u128]>;
       /**
        * Generic event
modifiedtests/src/interfaces/augment-api-query.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-query.ts
+++ b/tests/src/interfaces/augment-api-query.ts
@@ -20,13 +20,10 @@
     appPromotion: {
       admin: AugmentedQuery<ApiType, () => Observable<Option<AccountId32>>, []> & QueryableStorageEntry<ApiType, []>;
       /**
-       * Stores hash a record for which the last revenue recalculation was performed.
+       * Stores a key for record for which the next revenue recalculation would be performed.
        * If `None`, then recalculation has not yet been performed or calculations have been completed for all stakers.
        **/
       nextCalculatedRecord: AugmentedQuery<ApiType, () => Observable<Option<ITuple<[AccountId32, u32]>>>, []> & QueryableStorageEntry<ApiType, []>;
-      /**
-       * Amount of tokens pending unstake per user per block.
-       **/
       pendingUnstake: AugmentedQuery<ApiType, (arg: u32 | AnyNumber | Uint8Array) => Observable<Vec<ITuple<[AccountId32, u128]>>>, [u32]> & QueryableStorageEntry<ApiType, [u32]>;
       /**
        * Amount of tokens staked by account in the blocknumber.
modifiedtests/src/interfaces/default/types.tsdiffbeforeafterboth
--- a/tests/src/interfaces/default/types.ts
+++ b/tests/src/interfaces/default/types.ts
@@ -846,8 +846,9 @@
   readonly isNoPermission: boolean;
   readonly isNotSufficientFunds: boolean;
   readonly isPendingForBlockOverflow: boolean;
-  readonly isInvalidArgument: boolean;
-  readonly type: 'AdminNotSet' | 'NoPermission' | 'NotSufficientFunds' | 'PendingForBlockOverflow' | 'InvalidArgument';
+  readonly isSponsorNotSet: boolean;
+  readonly isIncorrectLockedBalanceOperation: boolean;
+  readonly type: 'AdminNotSet' | 'NoPermission' | 'NotSufficientFunds' | 'PendingForBlockOverflow' | 'SponsorNotSet' | 'IncorrectLockedBalanceOperation';
 }
 
 /** @name PalletAppPromotionEvent */
modifiedtests/src/interfaces/lookup.tsdiffbeforeafterboth
--- a/tests/src/interfaces/lookup.ts
+++ b/tests/src/interfaces/lookup.ts
@@ -3115,7 +3115,7 @@
    * Lookup416: pallet_app_promotion::pallet::Error<T>
    **/
   PalletAppPromotionError: {
-    _enum: ['AdminNotSet', 'NoPermission', 'NotSufficientFunds', 'PendingForBlockOverflow', 'InvalidArgument']
+    _enum: ['AdminNotSet', 'NoPermission', 'NotSufficientFunds', 'PendingForBlockOverflow', 'SponsorNotSet', 'IncorrectLockedBalanceOperation']
   },
   /**
    * Lookup419: pallet_evm::pallet::Error<T>
modifiedtests/src/interfaces/types-lookup.tsdiffbeforeafterboth
--- a/tests/src/interfaces/types-lookup.ts
+++ b/tests/src/interfaces/types-lookup.ts
@@ -3309,8 +3309,9 @@
     readonly isNoPermission: boolean;
     readonly isNotSufficientFunds: boolean;
     readonly isPendingForBlockOverflow: boolean;
-    readonly isInvalidArgument: boolean;
-    readonly type: 'AdminNotSet' | 'NoPermission' | 'NotSufficientFunds' | 'PendingForBlockOverflow' | 'InvalidArgument';
+    readonly isSponsorNotSet: boolean;
+    readonly isIncorrectLockedBalanceOperation: boolean;
+    readonly type: 'AdminNotSet' | 'NoPermission' | 'NotSufficientFunds' | 'PendingForBlockOverflow' | 'SponsorNotSet' | 'IncorrectLockedBalanceOperation';
   }
 
   /** @name PalletEvmError (419) */