git.delta.rocks / unique-network / refs/commits / 8d226d67b0fa

difftreelog

source

pallets/app-promotion/src/lib.rs15.4 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	/// Amount of tokens pending unstake per user per block.121	#[pallet::storage]122	pub type PendingUnstake<T: Config> = StorageNMap<123		Key = (124			Key<Blake2_128Concat, T::AccountId>,125			Key<Twox64Concat, T::BlockNumber>,126		),127		Value = BalanceOf<T>,128		QueryKind = ValueQuery,129	>;130131	/// A block when app-promotion has started132	#[pallet::storage]133	pub type StartBlock<T: Config> = StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;134135	/// Next target block when interest is recalculated136	#[pallet::storage]137	#[pallet::getter(fn get_interest_block)]138	pub type NextInterestBlock<T: Config> =139		StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;140141	#[pallet::hooks]142	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {143		fn on_initialize(current_block: T::BlockNumber) -> Weight144		where145			<T as frame_system::Config>::BlockNumber: From<u32>,146		{147			let mut consumed_weight = 0;148			let mut add_weight = |reads, writes, weight| {149				consumed_weight += T::DbWeight::get().reads_writes(reads, writes);150				consumed_weight += weight;151			};152153			PendingUnstake::<T>::iter()154				.filter_map(|((staker, block), amount)| {155					if block <= current_block {156						Some((staker, block, amount))157					} else {158						None159					}160				})161				.for_each(|(staker, block, amount)| {162					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 treasuries163					<PendingUnstake<T>>::remove((staker, block));164				});165166			let next_interest_block = Self::get_interest_block();167168			if next_interest_block != 0.into() && current_block >= next_interest_block {169				let mut acc = <BalanceOf<T>>::default();170171				NextInterestBlock::<T>::set(current_block + DAY.into());172				add_weight(0, 1, 0);173174				Staked::<T>::iter()175					.filter(|((_, block), _)| *block + DAY.into() <= current_block)176					.for_each(|((staker, block), amount)| {177						Self::recalculate_stake(&staker, block, amount, &mut acc);178						add_weight(0, 0, T::WeightInfo::recalculate_stake());179					});180				<TotalStaked<T>>::get()181					.checked_add(&acc)182					.map(|res| <TotalStaked<T>>::set(res));183				add_weight(0, 1, 0);184			};185			consumed_weight186		}187	}188189	#[pallet::call]190	impl<T: Config> Pallet<T> {191		#[pallet::weight(T::WeightInfo::set_admin_address())]192		pub fn set_admin_address(origin: OriginFor<T>, admin: T::AccountId) -> DispatchResult {193			ensure_root(origin)?;194			<Admin<T>>::set(Some(admin));195196			Ok(())197		}198199		#[pallet::weight(T::WeightInfo::start_app_promotion())]200		pub fn start_app_promotion(201			origin: OriginFor<T>,202			promotion_start_relay_block: T::BlockNumber,203		) -> DispatchResult204		where205			<T as frame_system::Config>::BlockNumber: From<u32>,206		{207			ensure_root(origin)?;208209			// Start app-promotion mechanics if it has not been yet initialized210			if <StartBlock<T>>::get() == 0u32.into() {211				// Set promotion global start block212				<StartBlock<T>>::set(promotion_start_relay_block);213214				<NextInterestBlock<T>>::set(promotion_start_relay_block + DAY.into());215			}216217			Ok(())218		}219220		#[pallet::weight(T::WeightInfo::stake())]221		pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {222			let staker_id = ensure_signed(staker)?;223224			let balance =225				<<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);226227			ensure!(balance >= amount, ArithmeticError::Underflow);228229			<<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(230				&staker_id,231				amount,232				WithdrawReasons::all(),233				balance - amount,234			)?;235236			Self::add_lock_balance(&staker_id, amount)?;237238			let block_number = frame_system::Pallet::<T>::block_number();239240			<Staked<T>>::insert(241				(&staker_id, block_number),242				<Staked<T>>::get((&staker_id, block_number))243					.checked_add(&amount)244					.ok_or(ArithmeticError::Overflow)?,245			);246247			<TotalStaked<T>>::set(248				<TotalStaked<T>>::get()249					.checked_add(&amount)250					.ok_or(ArithmeticError::Overflow)?,251			);252253			Ok(())254		}255256		#[pallet::weight(T::WeightInfo::unstake())]257		pub fn unstake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {258			let staker_id = ensure_signed(staker)?;259260			let mut stakes = Staked::<T>::iter_prefix((&staker_id,)).collect::<Vec<_>>();261262			let total_staked = stakes263				.iter()264				.fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + *amount);265266			ensure!(total_staked >= amount, ArithmeticError::Underflow);267268			<TotalStaked<T>>::set(269				<TotalStaked<T>>::get()270					.checked_sub(&amount)271					.ok_or(ArithmeticError::Underflow)?,272			);273274			let block = frame_system::Pallet::<T>::block_number() + WEEK.into();275			<PendingUnstake<T>>::insert(276				(&staker_id, block),277				<PendingUnstake<T>>::get((&staker_id, block))278					.checked_add(&amount)279					.ok_or(ArithmeticError::Overflow)?,280			);281282			stakes.sort_by_key(|(block, _)| *block);283284			let mut acc_amount = amount;285			let new_state = stakes286				.into_iter()287				.map_while(|(block, balance_per_block)| {288					if acc_amount == <BalanceOf<T>>::default() {289						return None;290					}291					if acc_amount <= balance_per_block {292						let res = (block, balance_per_block - acc_amount, acc_amount);293						acc_amount = <BalanceOf<T>>::default();294						return Some(res);295					} else {296						acc_amount -= balance_per_block;297						return Some((block, <BalanceOf<T>>::default(), acc_amount));298					}299				})300				.collect::<Vec<_>>();301302			new_state303				.into_iter()304				.for_each(|(block, to_staked, _to_pending)| {305					if to_staked == <BalanceOf<T>>::default() {306						<Staked<T>>::remove((&staker_id, block));307					} else {308						<Staked<T>>::insert((&staker_id, block), to_staked);309					}310				});311312			Ok(())313		}314	}315}316317impl<T: Config> Pallet<T> {318	// pub fn stake(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {319	// 	let balance = <<T as Config>::Currency as Currency<T::AccountId>>::free_balance(staker);320321	// 	ensure!(balance >= amount, ArithmeticError::Underflow);322323	// 	Self::set_lock_unchecked(staker, amount);324325	// 	let block_number = <T::BlockNumberProvider as BlockNumberProvider>::current_block_number();326327	// 	<Staked<T>>::insert(328	// 		(staker, block_number),329	// 		<Staked<T>>::get((staker, block_number))330	// 			.checked_add(&amount)331	// 			.ok_or(ArithmeticError::Overflow)?,332	// 	);333334	// 	<TotalStaked<T>>::set(335	// 		<TotalStaked<T>>::get()336	// 			.checked_add(&amount)337	// 			.ok_or(ArithmeticError::Overflow)?,338	// 	);339340	// 	Ok(())341	// }342343	// pub fn unstake(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {344	// 	let mut stakes = Staked::<T>::iter_prefix((staker,)).collect::<Vec<_>>();345346	// 	let total_staked = stakes347	// 		.iter()348	// 		.fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + *amount);349350	// 	ensure!(total_staked >= amount, ArithmeticError::Underflow);351352	// 	<TotalStaked<T>>::set(353	// 		<TotalStaked<T>>::get()354	// 			.checked_sub(&amount)355	// 			.ok_or(ArithmeticError::Underflow)?,356	// 	);357358	// 	let block = <T::BlockNumberProvider>::current_block_number() + WEEK.into();359	// 	<PendingUnstake<T>>::insert(360	// 		(staker, block),361	// 		<PendingUnstake<T>>::get((staker, block))362	// 			.checked_add(&amount)363	// 			.ok_or(ArithmeticError::Overflow)?,364	// 	);365366	// 	stakes.sort_by_key(|(block, _)| *block);367368	// 	let mut acc_amount = amount;369	// 	let new_state = stakes370	// 		.into_iter()371	// 		.map_while(|(block, balance_per_block)| {372	// 			if acc_amount == <BalanceOf<T>>::default() {373	// 				return None;374	// 			}375	// 			if acc_amount <= balance_per_block {376	// 				let res = (block, balance_per_block - acc_amount, acc_amount);377	// 				acc_amount = <BalanceOf<T>>::default();378	// 				return Some(res);379	// 			} else {380	// 				acc_amount -= balance_per_block;381	// 				return Some((block, <BalanceOf<T>>::default(), acc_amount));382	// 			}383	// 		})384	// 		.collect::<Vec<_>>();385386	// 	new_state387	// 		.into_iter()388	// 		.for_each(|(block, to_staked, _to_pending)| {389	// 			if to_staked == <BalanceOf<T>>::default() {390	// 				<Staked<T>>::remove((staker, block));391	// 			} else {392	// 				<Staked<T>>::insert((staker, block), to_staked);393	// 			}394	// 		});395396	// 	Ok(())397	// }398399	pub fn sponsor_collection(admin: T::AccountId, collection_id: u32) -> DispatchResult {400		Ok(())401	}402403	pub fn stop_sponsorign_collection(admin: T::AccountId, collection_id: u32) -> DispatchResult {404		Ok(())405	}406407	pub fn sponsor_conract(admin: T::AccountId, app_id: u32) -> DispatchResult {408		Ok(())409	}410411	pub fn stop_sponsorign_contract(admin: T::AccountId, app_id: u32) -> DispatchResult {412		Ok(())413	}414}415416impl<T: Config> Pallet<T> {417	fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {418		let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();419		locked_balance -= amount;420		Self::set_lock_unchecked(staker, locked_balance);421	}422423	fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {424		Self::get_locked_balance(staker)425			.map_or(<BalanceOf<T>>::default(), |l| l.amount)426			.checked_add(&amount)427			.map(|new_lock| Self::set_lock_unchecked(staker, new_lock))428			.ok_or(ArithmeticError::Overflow.into())429	}430431	fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {432		<T::Currency as LockableCurrency<T::AccountId>>::set_lock(433			LOCK_IDENTIFIER,434			staker,435			amount,436			WithdrawReasons::all(),437		)438	}439440	pub fn get_locked_balance(441		staker: impl EncodeLike<T::AccountId>,442	) -> Option<BalanceLock<BalanceOf<T>>> {443		<T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)444			.into_iter()445			.find(|l| l.id == LOCK_IDENTIFIER)446	}447448	pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {449		let staked = Staked::<T>::iter_prefix((staker,))450			.into_iter()451			.fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + amount);452		if staked != <BalanceOf<T>>::default() {453			Some(staked)454		} else {455			None456		}457	}458459	pub fn total_staked_by_id_per_block(460		staker: impl EncodeLike<T::AccountId>,461	) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {462		let mut staked = Staked::<T>::iter_prefix((staker,))463			.into_iter()464			.map(|(block, amount)| (block, amount))465			.collect::<Vec<_>>();466		staked.sort_by_key(|(block, _)| *block);467		if !staked.is_empty() {468			Some(staked)469		} else {470			None471		}472	}473474	pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {475		staker.map_or(Some(<TotalStaked<T>>::get()), |s| {476			Self::total_staked_by_id(s.as_sub())477		})478		// Self::total_staked_by_id(staker.as_sub())479	}480481	pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {482		Self::get_locked_balance(staker.as_sub())483			.map(|l| l.amount)484			.unwrap_or_default()485	}486487	pub fn cross_id_total_staked_per_block(488		staker: T::CrossAccountId,489	) -> Vec<(T::BlockNumber, BalanceOf<T>)> {490		Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()491	}492493	fn recalculate_stake(494		staker: &T::AccountId,495		block: T::BlockNumber,496		base: BalanceOf<T>,497		income_acc: &mut BalanceOf<T>,498	) {499		let income = Self::calculate_income(base);500		base.checked_add(&income).map(|res| {501			<Staked<T>>::insert((staker, block), res);502			*income_acc += income;503			<T::Currency as Currency<T::AccountId>>::transfer(504				&T::TreasuryAccountId::get(),505				staker,506				income,507				ExistenceRequirement::KeepAlive,508			)509			.and_then(|_| Self::add_lock_balance(staker, income));510		});511	}512513	fn calculate_income<I>(base: I) -> I514	where515		I: EncodeLike<BalanceOf<T>> + Balance,516	{517		let day_rate = Perbill::from_rational(5u32, 1_0000);518		day_rate * base519	}520}521522impl<T: Config> Pallet<T>523where524	<<T as pallet::Config>::Currency as Currency<T::AccountId>>::Balance: Sum,525{526	pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {527		staker.map_or(PendingUnstake::<T>::iter_values().sum(), |s| {528			PendingUnstake::<T>::iter_prefix_values((s.as_sub(),)).sum()529		})530	}531532	pub fn cross_id_pending_unstake_per_block(533		staker: T::CrossAccountId,534	) -> Vec<(T::BlockNumber, BalanceOf<T>)> {535		let mut unsorted_res = PendingUnstake::<T>::iter_prefix((staker.as_sub(),))536			.into_iter()537			.collect::<Vec<_>>();538		unsorted_res.sort_by_key(|(block, _)| *block);539		unsorted_res540	}541}