git.delta.rocks / unique-network / refs/commits / 9d0f344e127b

difftreelog

source

pallets/app-promotion/src/lib.rs18.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, borrow::ToOwned};40use codec::EncodeLike;41use pallet_balances::BalanceLock;42pub use types::ExtendedLockableCurrency;4344// use up_common::constants::{DAYS, UNIQUE};45use up_data_structs::CollectionId;4647use frame_support::{48	dispatch::{DispatchResult},49	traits::{50		Currency, Get, LockableCurrency, WithdrawReasons, tokens::Balance, ExistenceRequirement,51	},52	ensure,53};5455use weights::WeightInfo;5657pub use pallet::*;58use pallet_evm::account::CrossAccountId;59use sp_runtime::{60	Perbill,61	traits::{BlockNumberProvider, CheckedAdd, CheckedSub, AccountIdConversion},62	ArithmeticError,63};6465type BalanceOf<T> =66	<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;6768// const SECONDS_TO_BLOCK: u32 = 6;69// const DAY: u32 = 60 * 60 * 24 / SECONDS_TO_BLOCK;70// const WEEK: u32 = 7 * DAY;71// const TWO_WEEK: u32 = 2 * WEEK;72// const YEAR: u32 = DAY * 365;7374pub const LOCK_IDENTIFIER: [u8; 8] = *b"appstake";7576#[frame_support::pallet]77pub mod pallet {78	use super::*;79	use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, PalletId};80	use frame_system::pallet_prelude::*;81	use types::CollectionHandler;8283	#[pallet::config]84	pub trait Config: frame_system::Config + pallet_evm::account::Config {85		type Currency: ExtendedLockableCurrency<Self::AccountId>;8687		type CollectionHandler: CollectionHandler<88			AccountId = Self::AccountId,89			CollectionId = CollectionId,90		>;9192		type TreasuryAccountId: Get<Self::AccountId>;9394		/// The app's pallet id, used for deriving its sovereign account ID.95		#[pallet::constant]96		type PalletId: Get<PalletId>;9798		/// In relay blocks.99		#[pallet::constant]100		type RecalculationInterval: Get<Self::BlockNumber>;101		/// In chain blocks.102		#[pallet::constant]103		type PendingInterval: Get<Self::BlockNumber>;104105		/// In chain blocks.106		#[pallet::constant]107		type Day: Get<Self::BlockNumber>; // useless108109		#[pallet::constant]110		type Nominal: Get<BalanceOf<Self>>;111112		#[pallet::constant]113		type IntervalIncome: Get<Perbill>;114115		/// Weight information for extrinsics in this pallet.116		type WeightInfo: WeightInfo;117118		// The relay block number provider119		type RelayBlockNumberProvider: BlockNumberProvider<BlockNumber = Self::BlockNumber>;120121		/// Events compatible with [`frame_system::Config::Event`].122		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;123124		// /// Number of blocks that pass between treasury balance updates due to inflation125		// #[pallet::constant]126		// type InterestBlockInterval: Get<Self::BlockNumber>;127128		// // Weight information for functions of this pallet.129		// type WeightInfo: WeightInfo;130	}131132	#[pallet::pallet]133	#[pallet::generate_store(pub(super) trait Store)]134	pub struct Pallet<T>(_);135136	#[pallet::event]137	#[pallet::generate_deposit(fn deposit_event)]138	pub enum Event<T: Config> {139		StakingRecalculation(140			/// Base on which interest is calculated141			BalanceOf<T>,142			/// Amount of accrued interest143			BalanceOf<T>,144		),145	}146147	#[pallet::error]148	pub enum Error<T> {149		AdminNotSet,150		/// No permission to perform action151		NoPermission,152		/// Insufficient funds to perform an action153		NotSufficientFounds,154		InvalidArgument,155		AlreadySponsored,156	}157158	#[pallet::storage]159	pub type TotalStaked<T: Config> = StorageValue<Value = BalanceOf<T>, QueryKind = ValueQuery>;160161	#[pallet::storage]162	pub type Admin<T: Config> = StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;163164	/// Amount of tokens staked by account in the blocknumber.165	#[pallet::storage]166	pub type Staked<T: Config> = StorageNMap<167		Key = (168			Key<Blake2_128Concat, T::AccountId>,169			Key<Twox64Concat, T::BlockNumber>,170		),171		Value = BalanceOf<T>,172		QueryKind = ValueQuery,173	>;174175	/// Amount of tokens pending unstake per user per block.176	#[pallet::storage]177	pub type PendingUnstake<T: Config> = StorageNMap<178		Key = (179			Key<Blake2_128Concat, T::AccountId>,180			Key<Twox64Concat, T::BlockNumber>,181		),182		Value = BalanceOf<T>,183		QueryKind = ValueQuery,184	>;185186	/// A block when app-promotion has started187	#[pallet::storage]188	pub type StartBlock<T: Config> = StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;189190	/// Next target block when interest is recalculated191	#[pallet::storage]192	#[pallet::getter(fn get_interest_block)]193	pub type NextInterestBlock<T: Config> =194		StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;195196	#[pallet::hooks]197	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {198		fn on_initialize(current_block: T::BlockNumber) -> Weight199		where200			<T as frame_system::Config>::BlockNumber: From<u32>,201		{202			let mut consumed_weight = 0;203			let mut add_weight = |reads, writes, weight| {204				consumed_weight += T::DbWeight::get().reads_writes(reads, writes);205				consumed_weight += weight;206			};207208			PendingUnstake::<T>::iter()209				.filter_map(|((staker, block), amount)| {210					if block <= current_block {211						Some((staker, block, amount))212					} else {213						None214					}215				})216				.for_each(|(staker, block, amount)| {217					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 treasuries218					<PendingUnstake<T>>::remove((staker, block));219				});220221			let next_interest_block = Self::get_interest_block();222			let current_relay_block = T::RelayBlockNumberProvider::current_block_number();223			if next_interest_block != 0.into() && current_relay_block >= next_interest_block {224				let mut acc = <BalanceOf<T>>::default();225				let mut base_acc = <BalanceOf<T>>::default();226227				NextInterestBlock::<T>::set(228					NextInterestBlock::<T>::get() + T::RecalculationInterval::get(),229				);230				add_weight(0, 1, 0);231232				Staked::<T>::iter()233					.filter(|((_, block), _)| {234						*block + T::RecalculationInterval::get() <= current_relay_block235					})236					.for_each(|((staker, block), amount)| {237						Self::recalculate_stake(&staker, block, amount, &mut acc);238						add_weight(0, 0, T::WeightInfo::recalculate_stake());239						base_acc += amount;240					});241				<TotalStaked<T>>::get()242					.checked_add(&acc)243					.map(|res| <TotalStaked<T>>::set(res));244245				Self::deposit_event(Event::StakingRecalculation(base_acc, acc));246				add_weight(0, 1, 0);247			} else {248				add_weight(1, 0, 0)249			};250			consumed_weight251		}252	}253254	#[pallet::call]255	impl<T: Config> Pallet<T> {256		#[pallet::weight(T::WeightInfo::set_admin_address())]257		pub fn set_admin_address(origin: OriginFor<T>, admin: T::CrossAccountId) -> DispatchResult {258			ensure_root(origin)?;259			<Admin<T>>::set(Some(admin.as_sub().to_owned()));260261			Ok(())262		}263264		#[pallet::weight(T::WeightInfo::start_app_promotion())]265		pub fn start_app_promotion(266			origin: OriginFor<T>,267			promotion_start_relay_block: Option<T::BlockNumber>,268		) -> DispatchResult269		where270			<T as frame_system::Config>::BlockNumber: From<u32>,271		{272			ensure_root(origin)?;273274			// Start app-promotion mechanics if it has not been yet initialized275			if <StartBlock<T>>::get() == 0u32.into() {276				let start_block = promotion_start_relay_block277					.unwrap_or(T::RelayBlockNumberProvider::current_block_number());278279				// Set promotion global start block280				<StartBlock<T>>::set(start_block);281282				<NextInterestBlock<T>>::set(start_block + T::RecalculationInterval::get());283			}284285			Ok(())286		}287288		#[pallet::weight(T::WeightInfo::stake())]289		pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {290			let staker_id = ensure_signed(staker)?;291292			ensure!(amount >= T::Nominal::get(), ArithmeticError::Underflow);293294			let balance =295				<<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);296297			ensure!(balance >= amount, ArithmeticError::Underflow);298299			<<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(300				&staker_id,301				amount,302				WithdrawReasons::all(),303				balance - amount,304			)?;305306			Self::add_lock_balance(&staker_id, amount)?;307308			let block_number = T::RelayBlockNumberProvider::current_block_number();309310			<Staked<T>>::insert(311				(&staker_id, block_number),312				<Staked<T>>::get((&staker_id, block_number))313					.checked_add(&amount)314					.ok_or(ArithmeticError::Overflow)?,315			);316317			<TotalStaked<T>>::set(318				<TotalStaked<T>>::get()319					.checked_add(&amount)320					.ok_or(ArithmeticError::Overflow)?,321			);322323			Ok(())324		}325326		#[pallet::weight(T::WeightInfo::unstake())]327		pub fn unstake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {328			let staker_id = ensure_signed(staker)?;329330			let mut stakes = Staked::<T>::iter_prefix((&staker_id,)).collect::<Vec<_>>();331332			let total_staked = stakes333				.iter()334				.fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + *amount);335336			ensure!(total_staked >= amount, ArithmeticError::Underflow);337338			<TotalStaked<T>>::set(339				<TotalStaked<T>>::get()340					.checked_sub(&amount)341					.ok_or(ArithmeticError::Underflow)?,342			);343344			let block = frame_system::Pallet::<T>::block_number() + T::PendingInterval::get();345			<PendingUnstake<T>>::insert(346				(&staker_id, block),347				<PendingUnstake<T>>::get((&staker_id, block))348					.checked_add(&amount)349					.ok_or(ArithmeticError::Overflow)?,350			);351352			stakes.sort_by_key(|(block, _)| *block);353354			let mut acc_amount = amount;355			let new_state = stakes356				.into_iter()357				.map_while(|(block, balance_per_block)| {358					if acc_amount == <BalanceOf<T>>::default() {359						return None;360					}361					if acc_amount <= balance_per_block {362						let res = (block, balance_per_block - acc_amount, acc_amount);363						acc_amount = <BalanceOf<T>>::default();364						return Some(res);365					} else {366						acc_amount -= balance_per_block;367						return Some((block, <BalanceOf<T>>::default(), acc_amount));368					}369				})370				.collect::<Vec<_>>();371372			new_state373				.into_iter()374				.for_each(|(block, to_staked, _to_pending)| {375					if to_staked == <BalanceOf<T>>::default() {376						<Staked<T>>::remove((&staker_id, block));377					} else {378						<Staked<T>>::insert((&staker_id, block), to_staked);379					}380				});381382			Ok(())383		}384385		#[pallet::weight(0)]386		pub fn sponsor_collection(387			admin: OriginFor<T>,388			collection_id: CollectionId,389		) -> DispatchResult {390			let admin_id = ensure_signed(admin)?;391			ensure!(392				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,393				Error::<T>::NoPermission394			);395396			T::CollectionHandler::set_sponsor(Self::account_id(), collection_id)397		}398		#[pallet::weight(0)]399		pub fn stop_sponsorign_collection(400			admin: OriginFor<T>,401			collection_id: CollectionId,402		) -> DispatchResult {403			let admin_id = ensure_signed(admin)?;404405			ensure!(406				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,407				Error::<T>::NoPermission408			);409410			ensure!(411				T::CollectionHandler::get_sponsor(collection_id)?412					.ok_or(<Error<T>>::InvalidArgument)?413					== Self::account_id(),414				<Error<T>>::NoPermission415			);416			T::CollectionHandler::remove_collection_sponsor(collection_id)417		}418	}419}420421impl<T: Config> Pallet<T> {422	// pub fn stake(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {423	// 	let balance = <<T as Config>::Currency as Currency<T::AccountId>>::free_balance(staker);424425	// 	ensure!(balance >= amount, ArithmeticError::Underflow);426427	// 	Self::set_lock_unchecked(staker, amount);428429	// 	let block_number = <T::BlockNumberProvider as BlockNumberProvider>::current_block_number();430431	// 	<Staked<T>>::insert(432	// 		(staker, block_number),433	// 		<Staked<T>>::get((staker, block_number))434	// 			.checked_add(&amount)435	// 			.ok_or(ArithmeticError::Overflow)?,436	// 	);437438	// 	<TotalStaked<T>>::set(439	// 		<TotalStaked<T>>::get()440	// 			.checked_add(&amount)441	// 			.ok_or(ArithmeticError::Overflow)?,442	// 	);443444	// 	Ok(())445	// }446447	// pub fn unstake(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {448	// 	let mut stakes = Staked::<T>::iter_prefix((staker,)).collect::<Vec<_>>();449450	// 	let total_staked = stakes451	// 		.iter()452	// 		.fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + *amount);453454	// 	ensure!(total_staked >= amount, ArithmeticError::Underflow);455456	// 	<TotalStaked<T>>::set(457	// 		<TotalStaked<T>>::get()458	// 			.checked_sub(&amount)459	// 			.ok_or(ArithmeticError::Underflow)?,460	// 	);461462	// 	let block = <T::BlockNumberProvider>::current_block_number() + WEEK.into();463	// 	<PendingUnstake<T>>::insert(464	// 		(staker, block),465	// 		<PendingUnstake<T>>::get((staker, block))466	// 			.checked_add(&amount)467	// 			.ok_or(ArithmeticError::Overflow)?,468	// 	);469470	// 	stakes.sort_by_key(|(block, _)| *block);471472	// 	let mut acc_amount = amount;473	// 	let new_state = stakes474	// 		.into_iter()475	// 		.map_while(|(block, balance_per_block)| {476	// 			if acc_amount == <BalanceOf<T>>::default() {477	// 				return None;478	// 			}479	// 			if acc_amount <= balance_per_block {480	// 				let res = (block, balance_per_block - acc_amount, acc_amount);481	// 				acc_amount = <BalanceOf<T>>::default();482	// 				return Some(res);483	// 			} else {484	// 				acc_amount -= balance_per_block;485	// 				return Some((block, <BalanceOf<T>>::default(), acc_amount));486	// 			}487	// 		})488	// 		.collect::<Vec<_>>();489490	// 	new_state491	// 		.into_iter()492	// 		.for_each(|(block, to_staked, _to_pending)| {493	// 			if to_staked == <BalanceOf<T>>::default() {494	// 				<Staked<T>>::remove((staker, block));495	// 			} else {496	// 				<Staked<T>>::insert((staker, block), to_staked);497	// 			}498	// 		});499500	// 	Ok(())501	// }502503	// pub fn sponsor_collection(admin: T::AccountId, collection_id: u32) -> DispatchResult {504	// 	Ok(())505	// }506507	// pub fn stop_sponsorign_collection(admin: T::AccountId, collection_id: u32) -> DispatchResult {508	// 	Ok(())509	// }510511	pub fn sponsor_conract(admin: T::AccountId, app_id: u32) -> DispatchResult {512		Ok(())513	}514515	pub fn stop_sponsorign_contract(admin: T::AccountId, app_id: u32) -> DispatchResult {516		Ok(())517	}518519	pub fn account_id() -> T::AccountId {520		T::PalletId::get().into_account_truncating()521	}522}523524impl<T: Config> Pallet<T> {525	fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {526		let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();527		locked_balance -= amount;528		Self::set_lock_unchecked(staker, locked_balance);529	}530531	fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {532		Self::get_locked_balance(staker)533			.map_or(<BalanceOf<T>>::default(), |l| l.amount)534			.checked_add(&amount)535			.map(|new_lock| Self::set_lock_unchecked(staker, new_lock))536			.ok_or(ArithmeticError::Overflow.into())537	}538539	fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {540		<T::Currency as LockableCurrency<T::AccountId>>::set_lock(541			LOCK_IDENTIFIER,542			staker,543			amount,544			WithdrawReasons::all(),545		)546	}547548	pub fn get_locked_balance(549		staker: impl EncodeLike<T::AccountId>,550	) -> Option<BalanceLock<BalanceOf<T>>> {551		<T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)552			.into_iter()553			.find(|l| l.id == LOCK_IDENTIFIER)554	}555556	pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {557		let staked = Staked::<T>::iter_prefix((staker,))558			.into_iter()559			.fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + amount);560		if staked != <BalanceOf<T>>::default() {561			Some(staked)562		} else {563			None564		}565	}566567	pub fn total_staked_by_id_per_block(568		staker: impl EncodeLike<T::AccountId>,569	) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {570		let mut staked = Staked::<T>::iter_prefix((staker,))571			.into_iter()572			.map(|(block, amount)| (block, amount))573			.collect::<Vec<_>>();574		staked.sort_by_key(|(block, _)| *block);575		if !staked.is_empty() {576			Some(staked)577		} else {578			None579		}580	}581582	pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {583		staker.map_or(Some(<TotalStaked<T>>::get()), |s| {584			Self::total_staked_by_id(s.as_sub())585		})586		// Self::total_staked_by_id(staker.as_sub())587	}588589	pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {590		Self::get_locked_balance(staker.as_sub())591			.map(|l| l.amount)592			.unwrap_or_default()593	}594595	pub fn cross_id_total_staked_per_block(596		staker: T::CrossAccountId,597	) -> Vec<(T::BlockNumber, BalanceOf<T>)> {598		Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()599	}600601	fn recalculate_stake(602		staker: &T::AccountId,603		block: T::BlockNumber,604		base: BalanceOf<T>,605		income_acc: &mut BalanceOf<T>,606	) {607		let income = Self::calculate_income(base);608		base.checked_add(&income).map(|res| {609			<Staked<T>>::insert((staker, block), res);610			*income_acc += income;611			<T::Currency as Currency<T::AccountId>>::transfer(612				&T::TreasuryAccountId::get(),613				staker,614				income,615				ExistenceRequirement::KeepAlive,616			)617			.and_then(|_| Self::add_lock_balance(staker, income));618		});619	}620621	fn calculate_income<I>(base: I) -> I622	where623		I: EncodeLike<BalanceOf<T>> + Balance,624	{625		T::IntervalIncome::get() * base626	}627}628629impl<T: Config> Pallet<T>630where631	<<T as pallet::Config>::Currency as Currency<T::AccountId>>::Balance: Sum,632{633	pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {634		staker.map_or(PendingUnstake::<T>::iter_values().sum(), |s| {635			PendingUnstake::<T>::iter_prefix_values((s.as_sub(),)).sum()636		})637	}638639	pub fn cross_id_pending_unstake_per_block(640		staker: T::CrossAccountId,641	) -> Vec<(T::BlockNumber, BalanceOf<T>)> {642		let mut unsorted_res = PendingUnstake::<T>::iter_prefix((staker.as_sub(),))643			.into_iter()644			.collect::<Vec<_>>();645		unsorted_res.sort_by_key(|(block, _)| *block);646		unsorted_res647	}648}