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

difftreelog

source

pallets/app-promotion/src/lib.rs14.0 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;34pub mod types;3536#[cfg(test)]37mod tests;3839use sp_std::vec::Vec;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};51pub use pallet::*;52use pallet_evm::account::CrossAccountId;53use sp_runtime::{54	Perbill,55	traits::{BlockNumberProvider, CheckedAdd, CheckedSub},56	ArithmeticError,57};5859type BalanceOf<T> =60	<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;6162const SECONDS_TO_BLOCK: u32 = 6;63const DAY: u32 = 60 * 60 * 24 / SECONDS_TO_BLOCK;64const WEEK: u32 = 7 * DAY;65const TWO_WEEK: u32 = 2 * WEEK;66const YEAR: u32 = DAY * 365;6768pub const LOCK_IDENTIFIER: [u8; 8] = *b"appstake";6970#[frame_support::pallet]71pub mod pallet {72	use super::*;73	use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};74	use frame_system::pallet_prelude::*;7576	#[pallet::config]77	pub trait Config: frame_system::Config + pallet_evm::account::Config {78		type Currency: ExtendedLockableCurrency<Self::AccountId>;7980		type TreasuryAccountId: Get<Self::AccountId>;8182		// The block number provider83		type BlockNumberProvider: BlockNumberProvider<BlockNumber = Self::BlockNumber>;8485		// /// Number of blocks that pass between treasury balance updates due to inflation86		// #[pallet::constant]87		// type InterestBlockInterval: Get<Self::BlockNumber>;8889		// // Weight information for functions of this pallet.90		// type WeightInfo: WeightInfo;91	}9293	#[pallet::pallet]94	#[pallet::generate_store(pub(super) trait Store)]95	pub struct Pallet<T>(_);9697	#[pallet::storage]98	pub type TotalStaked<T: Config> = StorageValue<Value = BalanceOf<T>, QueryKind = ValueQuery>;99100	#[pallet::storage]101	pub type Admin<T: Config> = StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;102103	/// Amount of tokens staked by account in the blocknumber.104	#[pallet::storage]105	pub type Staked<T: Config> = StorageNMap<106		Key = (107			Key<Blake2_128Concat, T::AccountId>,108			Key<Twox64Concat, T::BlockNumber>,109		),110		Value = BalanceOf<T>,111		QueryKind = ValueQuery,112	>;113114	#[pallet::storage]115	pub type PendingUnstake<T: Config> = StorageNMap<116		Key = (117			Key<Blake2_128Concat, T::AccountId>,118			Key<Twox64Concat, T::BlockNumber>,119		),120		Value = BalanceOf<T>,121		QueryKind = ValueQuery,122	>;123124	/// A block when app-promotion has started125	#[pallet::storage]126	pub type StartBlock<T: Config> = StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;127128	/// Next target block when interest is recalculated129	#[pallet::storage]130	#[pallet::getter(fn get_interest_block)]131	pub type NextInterestBlock<T: Config> =132		StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;133134	#[pallet::hooks]135	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {136		fn on_initialize(current_block: T::BlockNumber) -> Weight137		where138			<T as frame_system::Config>::BlockNumber: From<u32>,139		{140			PendingUnstake::<T>::iter()141				.filter_map(|((staker, block), amount)| {142					if block <= current_block {143						Some((staker, block, amount))144					} else {145						None146					}147				})148				.for_each(|(staker, block, amount)| {149					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 treasuries150					<PendingUnstake<T>>::remove((staker, block));151				});152153			let next_interest_block = Self::get_interest_block();154155			if next_interest_block != 0.into() && current_block >= next_interest_block {156				let mut acc = <BalanceOf<T>>::default();157				let mut weight: Weight = 0;158				NextInterestBlock::<T>::set(current_block + DAY.into());159				Staked::<T>::iter()160					.filter(|((_, block), _)| *block + DAY.into() <= current_block)161					.for_each(|((staker, block), amount)| {162						Self::recalculate_stake(&staker, block, amount, &mut acc);163						// weight += recalculate_stake();164					});165				<TotalStaked<T>>::get()166					.checked_add(&acc)167					.map(|res| <TotalStaked<T>>::set(res));168			};169			0170		}171	}172173	#[pallet::call]174	impl<T: Config> Pallet<T> {175		#[pallet::weight(0)]176		pub fn set_admin_address(origin: OriginFor<T>, admin: T::AccountId) -> DispatchResult {177			ensure_root(origin)?;178			<Admin<T>>::set(Some(admin));179180			Ok(())181		}182183		#[pallet::weight(0)]184		pub fn start_app_promotion(185			origin: OriginFor<T>,186			promotion_start_relay_block: T::BlockNumber,187		) -> DispatchResult188		where189			<T as frame_system::Config>::BlockNumber: From<u32>,190		{191			ensure_root(origin)?;192193			// Start app-promotion mechanics if it has not been yet initialized194			if <StartBlock<T>>::get() == 0u32.into() {195				// Set promotion global start block196				<StartBlock<T>>::set(promotion_start_relay_block);197198				<NextInterestBlock<T>>::set(promotion_start_relay_block + DAY.into());199			}200201			Ok(())202		}203204		#[pallet::weight(0)]205		pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {206			let staker_id = ensure_signed(staker)?;207208			let balance =209				<<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);210211			ensure!(balance >= amount, ArithmeticError::Underflow);212213			Self::set_lock_unchecked(&staker_id, amount);214215			let block_number =216				<T::BlockNumberProvider as BlockNumberProvider>::current_block_number();217218			<Staked<T>>::insert(219				(&staker_id, block_number),220				<Staked<T>>::get((&staker_id, block_number))221					.checked_add(&amount)222					.ok_or(ArithmeticError::Overflow)?,223			);224225			<TotalStaked<T>>::set(226				<TotalStaked<T>>::get()227					.checked_add(&amount)228					.ok_or(ArithmeticError::Overflow)?,229			);230231			Ok(())232		}233234		#[pallet::weight(0)]235		pub fn unstake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {236			let staker_id = ensure_signed(staker)?;237238			let mut stakes = Staked::<T>::iter_prefix((&staker_id,)).collect::<Vec<_>>();239240			let total_staked = stakes241				.iter()242				.fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + *amount);243244			ensure!(total_staked >= amount, ArithmeticError::Underflow);245246			<TotalStaked<T>>::set(247				<TotalStaked<T>>::get()248					.checked_sub(&amount)249					.ok_or(ArithmeticError::Underflow)?,250			);251252			let block = <T::BlockNumberProvider>::current_block_number() + WEEK.into();253			<PendingUnstake<T>>::insert(254				(&staker_id, block),255				<PendingUnstake<T>>::get((&staker_id, block))256					.checked_add(&amount)257					.ok_or(ArithmeticError::Overflow)?,258			);259260			stakes.sort_by_key(|(block, _)| *block);261262			let mut acc_amount = amount;263			let new_state = stakes264				.into_iter()265				.map_while(|(block, balance_per_block)| {266					if acc_amount == <BalanceOf<T>>::default() {267						return None;268					}269					if acc_amount <= balance_per_block {270						let res = (block, balance_per_block - acc_amount, acc_amount);271						acc_amount = <BalanceOf<T>>::default();272						return Some(res);273					} else {274						acc_amount -= balance_per_block;275						return Some((block, <BalanceOf<T>>::default(), acc_amount));276					}277				})278				.collect::<Vec<_>>();279280			new_state281				.into_iter()282				.for_each(|(block, to_staked, _to_pending)| {283					if to_staked == <BalanceOf<T>>::default() {284						<Staked<T>>::remove((&staker_id, block));285					} else {286						<Staked<T>>::insert((&staker_id, block), to_staked);287					}288				});289290			Ok(())291		}292	}293}294295impl<T: Config> Pallet<T> {296	// pub fn stake(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {297	// 	let balance = <<T as Config>::Currency as Currency<T::AccountId>>::free_balance(staker);298299	// 	ensure!(balance >= amount, ArithmeticError::Underflow);300301	// 	Self::set_lock_unchecked(staker, amount);302303	// 	let block_number = <T::BlockNumberProvider as BlockNumberProvider>::current_block_number();304305	// 	<Staked<T>>::insert(306	// 		(staker, block_number),307	// 		<Staked<T>>::get((staker, block_number))308	// 			.checked_add(&amount)309	// 			.ok_or(ArithmeticError::Overflow)?,310	// 	);311312	// 	<TotalStaked<T>>::set(313	// 		<TotalStaked<T>>::get()314	// 			.checked_add(&amount)315	// 			.ok_or(ArithmeticError::Overflow)?,316	// 	);317318	// 	Ok(())319	// }320321	// pub fn unstake(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {322	// 	let mut stakes = Staked::<T>::iter_prefix((staker,)).collect::<Vec<_>>();323324	// 	let total_staked = stakes325	// 		.iter()326	// 		.fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + *amount);327328	// 	ensure!(total_staked >= amount, ArithmeticError::Underflow);329330	// 	<TotalStaked<T>>::set(331	// 		<TotalStaked<T>>::get()332	// 			.checked_sub(&amount)333	// 			.ok_or(ArithmeticError::Underflow)?,334	// 	);335336	// 	let block = <T::BlockNumberProvider>::current_block_number() + WEEK.into();337	// 	<PendingUnstake<T>>::insert(338	// 		(staker, block),339	// 		<PendingUnstake<T>>::get((staker, block))340	// 			.checked_add(&amount)341	// 			.ok_or(ArithmeticError::Overflow)?,342	// 	);343344	// 	stakes.sort_by_key(|(block, _)| *block);345346	// 	let mut acc_amount = amount;347	// 	let new_state = stakes348	// 		.into_iter()349	// 		.map_while(|(block, balance_per_block)| {350	// 			if acc_amount == <BalanceOf<T>>::default() {351	// 				return None;352	// 			}353	// 			if acc_amount <= balance_per_block {354	// 				let res = (block, balance_per_block - acc_amount, acc_amount);355	// 				acc_amount = <BalanceOf<T>>::default();356	// 				return Some(res);357	// 			} else {358	// 				acc_amount -= balance_per_block;359	// 				return Some((block, <BalanceOf<T>>::default(), acc_amount));360	// 			}361	// 		})362	// 		.collect::<Vec<_>>();363364	// 	new_state365	// 		.into_iter()366	// 		.for_each(|(block, to_staked, _to_pending)| {367	// 			if to_staked == <BalanceOf<T>>::default() {368	// 				<Staked<T>>::remove((staker, block));369	// 			} else {370	// 				<Staked<T>>::insert((staker, block), to_staked);371	// 			}372	// 		});373374	// 	Ok(())375	// }376377	pub fn sponsor_collection(admin: T::AccountId, collection_id: u32) -> DispatchResult {378		Ok(())379	}380381	pub fn stop_sponsorign_collection(admin: T::AccountId, collection_id: u32) -> DispatchResult {382		Ok(())383	}384385	pub fn sponsor_conract(admin: T::AccountId, app_id: u32) -> DispatchResult {386		Ok(())387	}388389	pub fn stop_sponsorign_contract(admin: T::AccountId, app_id: u32) -> DispatchResult {390		Ok(())391	}392}393394impl<T: Config> Pallet<T> {395	fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {396		let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();397		locked_balance -= amount;398		Self::set_lock_unchecked(staker, locked_balance);399	}400401	fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {402		Self::get_locked_balance(staker)403			.map(|l| l.amount)404			.and_then(|b| b.checked_add(&amount))405			.map(|new_lock| Self::set_lock_unchecked(staker, new_lock))406			.ok_or(ArithmeticError::Overflow.into())407	}408409	fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {410		<T::Currency as LockableCurrency<T::AccountId>>::set_lock(411			LOCK_IDENTIFIER,412			staker,413			amount,414			WithdrawReasons::all(),415		)416	}417418	pub fn get_locked_balance(419		staker: impl EncodeLike<T::AccountId>,420	) -> Option<BalanceLock<BalanceOf<T>>> {421		<T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)422			.into_iter()423			.find(|l| l.id == LOCK_IDENTIFIER)424	}425426	pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {427		let staked = Staked::<T>::iter_prefix((staker,))428			.into_iter()429			.fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + amount);430		if staked != <BalanceOf<T>>::default() {431			Some(staked)432		} else {433			None434		}435	}436437	pub fn total_staked_by_id_per_block(438		staker: impl EncodeLike<T::AccountId>,439	) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {440		let staked = Staked::<T>::iter_prefix((staker,))441			.into_iter()442			.map(|(block, amount)| (block, amount))443			.collect::<Vec<_>>();444		if !staked.is_empty() {445			Some(staked)446		} else {447			None448		}449	}450451	pub fn cross_id_total_staked(staker: T::CrossAccountId) -> Option<BalanceOf<T>> {452		Self::total_staked_by_id(staker.as_sub())453	}454455	pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {456		Self::get_locked_balance(staker.as_sub())457			.map(|l| l.amount)458			.unwrap_or_default()459	}460461	pub fn cross_id_total_staked_per_block(462		staker: T::CrossAccountId,463	) -> Vec<(T::BlockNumber, BalanceOf<T>)> {464		Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()465	}466467	fn recalculate_stake(468		staker: &T::AccountId,469		block: T::BlockNumber,470		base: BalanceOf<T>,471		income_acc: &mut BalanceOf<T>,472	) {473		let income = Self::calculate_income(base);474		base.checked_add(&income).map(|res| {475			<Staked<T>>::insert((staker, block), res);476			*income_acc += income;477			<T::Currency as Currency<T::AccountId>>::transfer(478				&T::TreasuryAccountId::get(),479				staker,480				income,481				ExistenceRequirement::KeepAlive,482			)483			.and_then(|_| Self::add_lock_balance(staker, income));484		});485	}486487	fn calculate_income<I>(base: I) -> I488	where489		I: EncodeLike<BalanceOf<T>> + Balance,490	{491		let day_rate = Perbill::from_rational(5u32, 1_0000);492		day_rate * base493	}494}