git.delta.rocks / unique-network / refs/commits / 4d07c60d3d95

difftreelog

source

pallets/app-promotion/src/lib.rs14.9 KiBsourcehistory
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::{vec::Vec, iter::Sum};40use codec::EncodeLike;41use pallet_balances::BalanceLock;42pub use types::ExtendedLockableCurrency;4344use frame_support::{45	dispatch::{DispatchResult},46	traits::{47		Currency, Get, LockableCurrency, WithdrawReasons, tokens::Balance, ExistenceRequirement,48	},49	ensure,50};5152use weights::WeightInfo;5354pub use pallet::*;55use pallet_evm::account::CrossAccountId;56use sp_runtime::{57	Perbill,58	traits::{BlockNumberProvider, CheckedAdd, CheckedSub},59	ArithmeticError,60};6162type BalanceOf<T> =63	<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;6465const SECONDS_TO_BLOCK: u32 = 6;66const DAY: u32 = 60 * 60 * 24 / SECONDS_TO_BLOCK;67const WEEK: u32 = 7 * DAY;68const TWO_WEEK: u32 = 2 * WEEK;69const YEAR: u32 = DAY * 365;7071pub const LOCK_IDENTIFIER: [u8; 8] = *b"appstake";7273#[frame_support::pallet]74pub mod pallet {75	use super::*;76	use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};77	use frame_system::pallet_prelude::*;7879	#[pallet::config]80	pub trait Config: frame_system::Config + pallet_evm::account::Config {81		type Currency: ExtendedLockableCurrency<Self::AccountId>;8283		type TreasuryAccountId: Get<Self::AccountId>;8485		/// Weight information for extrinsics in this pallet.86		type WeightInfo: WeightInfo;8788		// The block number provider89		type BlockNumberProvider: BlockNumberProvider<BlockNumber = Self::BlockNumber>;9091		// /// Number of blocks that pass between treasury balance updates due to inflation92		// #[pallet::constant]93		// type InterestBlockInterval: Get<Self::BlockNumber>;9495		// // Weight information for functions of this pallet.96		// type WeightInfo: WeightInfo;97	}9899	#[pallet::pallet]100	#[pallet::generate_store(pub(super) trait Store)]101	pub struct Pallet<T>(_);102103	#[pallet::storage]104	pub type TotalStaked<T: Config> = StorageValue<Value = BalanceOf<T>, QueryKind = ValueQuery>;105106	#[pallet::storage]107	pub type Admin<T: Config> = StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;108109	/// Amount of tokens staked by account in the blocknumber.110	#[pallet::storage]111	pub type Staked<T: Config> = StorageNMap<112		Key = (113			Key<Blake2_128Concat, T::AccountId>,114			Key<Twox64Concat, T::BlockNumber>,115		),116		Value = BalanceOf<T>,117		QueryKind = ValueQuery,118	>;119120	#[pallet::storage]121	pub type PendingUnstake<T: Config> = StorageNMap<122		Key = (123			Key<Blake2_128Concat, T::AccountId>,124			Key<Twox64Concat, T::BlockNumber>,125		),126		Value = BalanceOf<T>,127		QueryKind = ValueQuery,128	>;129130	/// A block when app-promotion has started131	#[pallet::storage]132	pub type StartBlock<T: Config> = StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;133134	/// Next target block when interest is recalculated135	#[pallet::storage]136	#[pallet::getter(fn get_interest_block)]137	pub type NextInterestBlock<T: Config> =138		StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;139140	#[pallet::hooks]141	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {142		fn on_initialize(current_block: T::BlockNumber) -> Weight143		where144			<T as frame_system::Config>::BlockNumber: From<u32>,145			// <<T as pallet::Config>::Currency as Currency<T::AccountId>>::Balance: Sum,146		{147			PendingUnstake::<T>::iter()148				.filter_map(|((staker, block), amount)| {149					if block <= current_block {150						Some((staker, block, amount))151					} else {152						None153					}154				})155				.for_each(|(staker, block, amount)| {156					Self::unlock_balance_unchecked(&staker, amount); // TO-DO : Replace with a method that will check that the unstack is less than it was blocked, otherwise take the delta from the treasuries157					<PendingUnstake<T>>::remove((staker, block));158				});159160			let next_interest_block = Self::get_interest_block();161162			if next_interest_block != 0.into() && current_block >= next_interest_block {163				let mut acc = <BalanceOf<T>>::default();164				let mut weight: Weight = 0;165				NextInterestBlock::<T>::set(current_block + DAY.into());166				Staked::<T>::iter()167					.filter(|((_, block), _)| *block + DAY.into() <= current_block)168					.for_each(|((staker, block), amount)| {169						Self::recalculate_stake(&staker, block, amount, &mut acc);170						// weight += recalculate_stake();171					});172				<TotalStaked<T>>::get()173					.checked_add(&acc)174					.map(|res| <TotalStaked<T>>::set(res));175			};176			0177		}178	}179180	#[pallet::call]181	impl<T: Config> Pallet<T> {182		#[pallet::weight(T::WeightInfo::set_admin_address())]183		pub fn set_admin_address(origin: OriginFor<T>, admin: T::AccountId) -> DispatchResult {184			ensure_root(origin)?;185			<Admin<T>>::set(Some(admin));186187			Ok(())188		}189190		#[pallet::weight(T::WeightInfo::start_app_promotion())]191		pub fn start_app_promotion(192			origin: OriginFor<T>,193			promotion_start_relay_block: T::BlockNumber,194		) -> DispatchResult195		where196			<T as frame_system::Config>::BlockNumber: From<u32>,197		{198			ensure_root(origin)?;199200			// Start app-promotion mechanics if it has not been yet initialized201			if <StartBlock<T>>::get() == 0u32.into() {202				// Set promotion global start block203				<StartBlock<T>>::set(promotion_start_relay_block);204205				<NextInterestBlock<T>>::set(promotion_start_relay_block + DAY.into());206			}207208			Ok(())209		}210211		#[pallet::weight(T::WeightInfo::stake())]212		pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {213			let staker_id = ensure_signed(staker)?;214215			let balance =216				<<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);217218			ensure!(balance >= amount, ArithmeticError::Underflow);219220			<<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(221				&staker_id,222				amount,223				WithdrawReasons::all(),224				balance - amount,225			)?;226227			Self::add_lock_balance(&staker_id, amount)?;228229			let block_number = frame_system::Pallet::<T>::block_number();230231			<Staked<T>>::insert(232				(&staker_id, block_number),233				<Staked<T>>::get((&staker_id, block_number))234					.checked_add(&amount)235					.ok_or(ArithmeticError::Overflow)?,236			);237238			<TotalStaked<T>>::set(239				<TotalStaked<T>>::get()240					.checked_add(&amount)241					.ok_or(ArithmeticError::Overflow)?,242			);243244			Ok(())245		}246247		#[pallet::weight(T::WeightInfo::unstake())]248		pub fn unstake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {249			let staker_id = ensure_signed(staker)?;250251			let mut stakes = Staked::<T>::iter_prefix((&staker_id,)).collect::<Vec<_>>();252253			let total_staked = stakes254				.iter()255				.fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + *amount);256257			ensure!(total_staked >= amount, ArithmeticError::Underflow);258259			<TotalStaked<T>>::set(260				<TotalStaked<T>>::get()261					.checked_sub(&amount)262					.ok_or(ArithmeticError::Underflow)?,263			);264265			let block = frame_system::Pallet::<T>::block_number() + WEEK.into();266			<PendingUnstake<T>>::insert(267				(&staker_id, block),268				<PendingUnstake<T>>::get((&staker_id, block))269					.checked_add(&amount)270					.ok_or(ArithmeticError::Overflow)?,271			);272273			stakes.sort_by_key(|(block, _)| *block);274275			let mut acc_amount = amount;276			let new_state = stakes277				.into_iter()278				.map_while(|(block, balance_per_block)| {279					if acc_amount == <BalanceOf<T>>::default() {280						return None;281					}282					if acc_amount <= balance_per_block {283						let res = (block, balance_per_block - acc_amount, acc_amount);284						acc_amount = <BalanceOf<T>>::default();285						return Some(res);286					} else {287						acc_amount -= balance_per_block;288						return Some((block, <BalanceOf<T>>::default(), acc_amount));289					}290				})291				.collect::<Vec<_>>();292293			new_state294				.into_iter()295				.for_each(|(block, to_staked, _to_pending)| {296					if to_staked == <BalanceOf<T>>::default() {297						<Staked<T>>::remove((&staker_id, block));298					} else {299						<Staked<T>>::insert((&staker_id, block), to_staked);300					}301				});302303			Ok(())304		}305	}306}307308impl<T: Config> Pallet<T> {309	// pub fn stake(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {310	// 	let balance = <<T as Config>::Currency as Currency<T::AccountId>>::free_balance(staker);311312	// 	ensure!(balance >= amount, ArithmeticError::Underflow);313314	// 	Self::set_lock_unchecked(staker, amount);315316	// 	let block_number = <T::BlockNumberProvider as BlockNumberProvider>::current_block_number();317318	// 	<Staked<T>>::insert(319	// 		(staker, block_number),320	// 		<Staked<T>>::get((staker, block_number))321	// 			.checked_add(&amount)322	// 			.ok_or(ArithmeticError::Overflow)?,323	// 	);324325	// 	<TotalStaked<T>>::set(326	// 		<TotalStaked<T>>::get()327	// 			.checked_add(&amount)328	// 			.ok_or(ArithmeticError::Overflow)?,329	// 	);330331	// 	Ok(())332	// }333334	// pub fn unstake(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {335	// 	let mut stakes = Staked::<T>::iter_prefix((staker,)).collect::<Vec<_>>();336337	// 	let total_staked = stakes338	// 		.iter()339	// 		.fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + *amount);340341	// 	ensure!(total_staked >= amount, ArithmeticError::Underflow);342343	// 	<TotalStaked<T>>::set(344	// 		<TotalStaked<T>>::get()345	// 			.checked_sub(&amount)346	// 			.ok_or(ArithmeticError::Underflow)?,347	// 	);348349	// 	let block = <T::BlockNumberProvider>::current_block_number() + WEEK.into();350	// 	<PendingUnstake<T>>::insert(351	// 		(staker, block),352	// 		<PendingUnstake<T>>::get((staker, block))353	// 			.checked_add(&amount)354	// 			.ok_or(ArithmeticError::Overflow)?,355	// 	);356357	// 	stakes.sort_by_key(|(block, _)| *block);358359	// 	let mut acc_amount = amount;360	// 	let new_state = stakes361	// 		.into_iter()362	// 		.map_while(|(block, balance_per_block)| {363	// 			if acc_amount == <BalanceOf<T>>::default() {364	// 				return None;365	// 			}366	// 			if acc_amount <= balance_per_block {367	// 				let res = (block, balance_per_block - acc_amount, acc_amount);368	// 				acc_amount = <BalanceOf<T>>::default();369	// 				return Some(res);370	// 			} else {371	// 				acc_amount -= balance_per_block;372	// 				return Some((block, <BalanceOf<T>>::default(), acc_amount));373	// 			}374	// 		})375	// 		.collect::<Vec<_>>();376377	// 	new_state378	// 		.into_iter()379	// 		.for_each(|(block, to_staked, _to_pending)| {380	// 			if to_staked == <BalanceOf<T>>::default() {381	// 				<Staked<T>>::remove((staker, block));382	// 			} else {383	// 				<Staked<T>>::insert((staker, block), to_staked);384	// 			}385	// 		});386387	// 	Ok(())388	// }389390	pub fn sponsor_collection(admin: T::AccountId, collection_id: u32) -> DispatchResult {391		Ok(())392	}393394	pub fn stop_sponsorign_collection(admin: T::AccountId, collection_id: u32) -> DispatchResult {395		Ok(())396	}397398	pub fn sponsor_conract(admin: T::AccountId, app_id: u32) -> DispatchResult {399		Ok(())400	}401402	pub fn stop_sponsorign_contract(admin: T::AccountId, app_id: u32) -> DispatchResult {403		Ok(())404	}405}406407impl<T: Config> Pallet<T> {408	fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {409		let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();410		locked_balance -= amount;411		Self::set_lock_unchecked(staker, locked_balance);412	}413414	fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {415		Self::get_locked_balance(staker)416			.map_or(<BalanceOf<T>>::default(), |l| l.amount)417			.checked_add(&amount)418			.map(|new_lock| Self::set_lock_unchecked(staker, new_lock))419			.ok_or(ArithmeticError::Overflow.into())420	}421422	fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {423		<T::Currency as LockableCurrency<T::AccountId>>::set_lock(424			LOCK_IDENTIFIER,425			staker,426			amount,427			WithdrawReasons::all(),428		)429	}430431	pub fn get_locked_balance(432		staker: impl EncodeLike<T::AccountId>,433	) -> Option<BalanceLock<BalanceOf<T>>> {434		<T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)435			.into_iter()436			.find(|l| l.id == LOCK_IDENTIFIER)437	}438439	pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {440		let staked = Staked::<T>::iter_prefix((staker,))441			.into_iter()442			.fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + amount);443		if staked != <BalanceOf<T>>::default() {444			Some(staked)445		} else {446			None447		}448	}449450	pub fn total_staked_by_id_per_block(451		staker: impl EncodeLike<T::AccountId>,452	) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {453		let mut staked = Staked::<T>::iter_prefix((staker,))454			.into_iter()455			.map(|(block, amount)| (block, amount))456			.collect::<Vec<_>>();457		staked.sort_by_key(|(block, _)| *block);458		if !staked.is_empty() {459			Some(staked)460		} else {461			None462		}463	}464465	pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {466		staker.map_or(Some(<TotalStaked<T>>::get()), |s| {467			Self::total_staked_by_id(s.as_sub())468		})469		// Self::total_staked_by_id(staker.as_sub())470	}471472	pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {473		Self::get_locked_balance(staker.as_sub())474			.map(|l| l.amount)475			.unwrap_or_default()476	}477478	pub fn cross_id_total_staked_per_block(479		staker: T::CrossAccountId,480	) -> Vec<(T::BlockNumber, BalanceOf<T>)> {481		Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()482	}483484	fn recalculate_stake(485		staker: &T::AccountId,486		block: T::BlockNumber,487		base: BalanceOf<T>,488		income_acc: &mut BalanceOf<T>,489	) {490		let income = Self::calculate_income(base);491		base.checked_add(&income).map(|res| {492			<Staked<T>>::insert((staker, block), res);493			*income_acc += income;494			<T::Currency as Currency<T::AccountId>>::transfer(495				&T::TreasuryAccountId::get(),496				staker,497				income,498				ExistenceRequirement::KeepAlive,499			)500			.and_then(|_| Self::add_lock_balance(staker, income));501		});502	}503504	fn calculate_income<I>(base: I) -> I505	where506		I: EncodeLike<BalanceOf<T>> + Balance,507	{508		let day_rate = Perbill::from_rational(5u32, 1_0000);509		day_rate * base510	}511}512513impl<T: Config> Pallet<T>514where515	<<T as pallet::Config>::Currency as Currency<T::AccountId>>::Balance: Sum,516{517	pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {518		staker.map_or(PendingUnstake::<T>::iter_values().sum(), |s| {519			PendingUnstake::<T>::iter_prefix_values((s.as_sub(),)).sum()520		})521	}522}