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

difftreelog

fix stake and unstake extrinsics

PraetorP2022-08-30parent: #daf5420.patch.diff
in: master

1 file changed

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::{vec::Vec, iter::Sum, borrow::ToOwned};40use sp_core::H160;41use codec::EncodeLike;42use pallet_balances::BalanceLock;43pub use types::*;4445// use up_common::constants::{DAYS, UNIQUE};46use up_data_structs::CollectionId;4748use frame_support::{49	dispatch::{DispatchResult},50	traits::{51		Currency, Get, LockableCurrency, WithdrawReasons, tokens::Balance, ExistenceRequirement,52	},53	ensure,54};5556use weights::WeightInfo;5758pub use pallet::*;59use pallet_evm::account::CrossAccountId;60use sp_runtime::{61	Perbill,62	traits::{BlockNumberProvider, CheckedAdd, CheckedSub, AccountIdConversion},63	ArithmeticError,64};6566type BalanceOf<T> =67	<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;6869// const SECONDS_TO_BLOCK: u32 = 6;70// const DAY: u32 = 60 * 60 * 24 / SECONDS_TO_BLOCK;71// const WEEK: u32 = 7 * DAY;72// const TWO_WEEK: u32 = 2 * WEEK;73// const YEAR: u32 = DAY * 365;7475pub const LOCK_IDENTIFIER: [u8; 8] = *b"appstake";7677#[frame_support::pallet]78pub mod pallet {79	use super::*;80	use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, PalletId};81	use frame_system::pallet_prelude::*;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 ContractHandler: ContractHandler<AccountId = Self::CrossAccountId, ContractId = H160>;9394		type TreasuryAccountId: Get<Self::AccountId>;9596		/// The app's pallet id, used for deriving its sovereign account ID.97		#[pallet::constant]98		type PalletId: Get<PalletId>;99100		/// In relay blocks.101		#[pallet::constant]102		type RecalculationInterval: Get<Self::BlockNumber>;103		/// In relay blocks.104		#[pallet::constant]105		type PendingInterval: Get<Self::BlockNumber>;106107		/// In chain blocks.108		#[pallet::constant]109		type Day: Get<Self::BlockNumber>; // useless110111		#[pallet::constant]112		type Nominal: Get<BalanceOf<Self>>;113114		#[pallet::constant]115		type IntervalIncome: Get<Perbill>;116117		/// Weight information for extrinsics in this pallet.118		type WeightInfo: WeightInfo;119120		// The relay block number provider121		type RelayBlockNumberProvider: BlockNumberProvider<BlockNumber = Self::BlockNumber>;122123		/// Events compatible with [`frame_system::Config::Event`].124		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;125	}126127	#[pallet::pallet]128	#[pallet::generate_store(pub(super) trait Store)]129	pub struct Pallet<T>(_);130131	#[pallet::event]132	#[pallet::generate_deposit(fn deposit_event)]133	pub enum Event<T: Config> {134		StakingRecalculation(135			/// An recalculated staker136			T::AccountId,137			/// Base on which interest is calculated138			BalanceOf<T>,139			/// Amount of accrued interest140			BalanceOf<T>,141		),142	}143144	#[pallet::error]145	pub enum Error<T> {146		/// Error due to action requiring admin to be set147		AdminNotSet,148		/// No permission to perform an action149		NoPermission,150		/// Insufficient funds to perform an action151		NotSufficientFounds,152		/// An error related to the fact that an invalid argument was passed to perform an action153		InvalidArgument,154	}155156	#[pallet::storage]157	pub type TotalStaked<T: Config> = StorageValue<Value = BalanceOf<T>, QueryKind = ValueQuery>;158159	#[pallet::storage]160	pub type Admin<T: Config> = StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;161162	/// Amount of tokens staked by account in the blocknumber.163	#[pallet::storage]164	pub type Staked<T: Config> = StorageNMap<165		Key = (166			Key<Blake2_128Concat, T::AccountId>,167			Key<Twox64Concat, T::BlockNumber>,168		),169		Value = (BalanceOf<T>, T::BlockNumber),170		QueryKind = ValueQuery,171	>;172173	/// Amount of tokens pending unstake per user per block.174	#[pallet::storage]175	pub type PendingUnstake<T: Config> = StorageNMap<176		Key = (177			Key<Blake2_128Concat, T::AccountId>,178			Key<Twox64Concat, T::BlockNumber>,179		),180		Value = BalanceOf<T>,181		QueryKind = ValueQuery,182	>;183184	/// A block when app-promotion has started .I think this is redundant, because we only need `NextInterestBlock`.185	#[pallet::storage]186	pub type StartBlock<T: Config> = StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;187188	/// Next target block when interest is recalculated189	#[pallet::storage]190	#[pallet::getter(fn get_interest_block)]191	pub type NextInterestBlock<T: Config> =192		StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;193194	/// Stores the address of the staker for which the last revenue recalculation was performed.195	/// If `None`, then recalculation has not yet been performed or calculations have been completed for all stakers.196	#[pallet::storage]197	#[pallet::getter(fn get_last_calculated_staker)]198	pub type LastCalcucaltedStaker<T: Config> =199		StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;200201	#[pallet::hooks]202	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {203		fn on_initialize(current_block: T::BlockNumber) -> Weight204		where205			<T as frame_system::Config>::BlockNumber: From<u32>,206		{207			let mut consumed_weight = 0;208			// let mut add_weight = |reads, writes, weight| {209			// 	consumed_weight += T::DbWeight::get().reads_writes(reads, writes);210			// 	consumed_weight += weight;211			// };212213			PendingUnstake::<T>::iter()214				.filter_map(|((staker, block), amount)| {215					if block <= current_block {216						Some((staker, block, amount))217					} else {218						None219					}220				})221				.for_each(|(staker, block, amount)| {222					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 treasuries223					<PendingUnstake<T>>::remove((staker, block));224				});225226			// let next_interest_block = Self::get_interest_block();227			// let current_relay_block = T::RelayBlockNumberProvider::current_block_number();228			// if next_interest_block != 0.into() && current_relay_block >= next_interest_block {229			// 	let mut acc = <BalanceOf<T>>::default();230			// 	let mut base_acc = <BalanceOf<T>>::default();231232			// 	NextInterestBlock::<T>::set(233			// 		NextInterestBlock::<T>::get() + T::RecalculationInterval::get(),234			// 	);235			// 	add_weight(0, 1, 0);236237			// 	Staked::<T>::iter()238			// 		.filter(|((_, block), _)| {239			// 			*block + T::RecalculationInterval::get() <= current_relay_block240			// 		})241			// 		.for_each(|((staker, block), amount)| {242			// 			Self::recalculate_stake(&staker, block, amount, &mut acc);243			// 			add_weight(0, 0, T::WeightInfo::recalculate_stake());244			// 			base_acc += amount;245			// 		});246			// 	<TotalStaked<T>>::get()247			// 		.checked_add(&acc)248			// 		.map(|res| <TotalStaked<T>>::set(res));249250			// 	Self::deposit_event(Event::StakingRecalculation(base_acc, acc));251			// 	add_weight(0, 1, 0);252			// } else {253			// 	add_weight(1, 0, 0)254			// };255			consumed_weight256		}257	}258259	#[pallet::call]260	impl<T: Config> Pallet<T>261	where262		T::BlockNumber: From<u32>,263	{264		#[pallet::weight(T::WeightInfo::set_admin_address())]265		pub fn set_admin_address(origin: OriginFor<T>, admin: T::CrossAccountId) -> DispatchResult {266			ensure_root(origin)?;267			<Admin<T>>::set(Some(admin.as_sub().to_owned()));268269			Ok(())270		}271272		#[pallet::weight(T::WeightInfo::start_app_promotion())]273		pub fn start_app_promotion(274			origin: OriginFor<T>,275			promotion_start_relay_block: Option<T::BlockNumber>,276		) -> DispatchResult277		where278			<T as frame_system::Config>::BlockNumber: From<u32>,279		{280			ensure_root(origin)?;281282			// Start app-promotion mechanics if it has not been yet initialized283			if <StartBlock<T>>::get() == 0u32.into() {284				let start_block = promotion_start_relay_block285					.unwrap_or(T::RelayBlockNumberProvider::current_block_number());286287				// Set promotion global start block288				<StartBlock<T>>::set(start_block);289290				<NextInterestBlock<T>>::set(start_block + T::RecalculationInterval::get());291			}292293			Ok(())294		}295296		#[pallet::weight(T::WeightInfo::stop_app_promotion())]297		pub fn stop_app_promotion(origin: OriginFor<T>) -> DispatchResult298		where299			<T as frame_system::Config>::BlockNumber: From<u32>,300		{301			ensure_root(origin)?;302303			if <StartBlock<T>>::get() != 0u32.into() {304				<StartBlock<T>>::set(T::BlockNumber::default());305				<NextInterestBlock<T>>::set(T::BlockNumber::default());306			}307308			Ok(())309		}310311		#[pallet::weight(T::WeightInfo::stake())]312		pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {313			let staker_id = ensure_signed(staker)?;314315			ensure!(amount >= T::Nominal::get(), ArithmeticError::Underflow);316317			let balance =318				<<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);319320			ensure!(balance >= amount, ArithmeticError::Underflow);321322			<<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(323				&staker_id,324				amount,325				WithdrawReasons::all(),326				balance - amount,327			)?;328329			Self::add_lock_balance(&staker_id, amount)?;330331			let block_number = T::RelayBlockNumberProvider::current_block_number();332			let recalc_block = (block_number / T::RecalculationInterval::get() + 2u32.into())333				* T::RecalculationInterval::get();334335			<Staked<T>>::insert((&staker_id, block_number), {336				let mut balance_and_recalc_block = <Staked<T>>::get((&staker_id, block_number));337				balance_and_recalc_block.0 = balance_and_recalc_block338					.0339					.checked_add(&amount)340					.ok_or(ArithmeticError::Overflow)?;341				balance_and_recalc_block.1 = recalc_block;342				balance_and_recalc_block343			});344345			// <TotalStaked<T>>::set(346			// 	<TotalStaked<T>>::get()347			// 		.checked_add(&amount)348			// 		.ok_or(ArithmeticError::Overflow)?,349			// );350351			Ok(())352		}353354		#[pallet::weight(T::WeightInfo::unstake())]355		pub fn unstake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {356			let staker_id = ensure_signed(staker)?;357358			let mut stakes = Staked::<T>::drain_prefix((&staker_id,));359360			// let total_staked = stakes361			// 	.iter()362			// 	.fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + *amount);363364			// ensure!(total_staked >= amount, ArithmeticError::Underflow);365366			// <TotalStaked<T>>::set(367			// 	<TotalStaked<T>>::get()368			// 		.checked_sub(&amount)369			// 		.ok_or(ArithmeticError::Underflow)?,370			// );371372			// let block =373			// 	T::RelayBlockNumberProvider::current_block_number() + T::PendingInterval::get();374			// <PendingUnstake<T>>::insert(375			// 	(&staker_id, block),376			// 	<PendingUnstake<T>>::get((&staker_id, block))377			// 		.checked_add(&amount)378			// 		.ok_or(ArithmeticError::Overflow)?,379			// );380381			// stakes.sort_by_key(|(block, _)| *block);382383			// let mut acc_amount = amount;384			// let new_state = stakes385			// 	.into_iter()386			// 	.map_while(|(block, balance_per_block)| {387			// 		if acc_amount == <BalanceOf<T>>::default() {388			// 			return None;389			// 		}390			// 		if acc_amount <= balance_per_block {391			// 			let res = (block, balance_per_block - acc_amount, acc_amount);392			// 			acc_amount = <BalanceOf<T>>::default();393			// 			return Some(res);394			// 		} else {395			// 			acc_amount -= balance_per_block;396			// 			return Some((block, <BalanceOf<T>>::default(), acc_amount));397			// 		}398			// 	})399			// 	.collect::<Vec<_>>();400401			// new_state402			// 	.into_iter()403			// 	.for_each(|(block, to_staked, _to_pending)| {404			// 		if to_staked == <BalanceOf<T>>::default() {405			// 			<Staked<T>>::remove((&staker_id, block));406			// 		} else {407			// 			<Staked<T>>::insert((&staker_id, block), to_staked);408			// 		}409			// 	});410411			Ok(())412413			// let staker_id = ensure_signed(staker)?;414415			// let mut stakes = Staked::<T>::iter_prefix((&staker_id,)).collect::<Vec<_>>();416417			// let total_staked = stakes418			// 	.iter()419			// 	.fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + *amount);420421			// ensure!(total_staked >= amount, ArithmeticError::Underflow);422423			// <TotalStaked<T>>::set(424			// 	<TotalStaked<T>>::get()425			// 		.checked_sub(&amount)426			// 		.ok_or(ArithmeticError::Underflow)?,427			// );428429			// let block =430			// 	T::RelayBlockNumberProvider::current_block_number() + T::PendingInterval::get();431			// <PendingUnstake<T>>::insert(432			// 	(&staker_id, block),433			// 	<PendingUnstake<T>>::get((&staker_id, block))434			// 		.checked_add(&amount)435			// 		.ok_or(ArithmeticError::Overflow)?,436			// );437438			// stakes.sort_by_key(|(block, _)| *block);439440			// let mut acc_amount = amount;441			// let new_state = stakes442			// 	.into_iter()443			// 	.map_while(|(block, balance_per_block)| {444			// 		if acc_amount == <BalanceOf<T>>::default() {445			// 			return None;446			// 		}447			// 		if acc_amount <= balance_per_block {448			// 			let res = (block, balance_per_block - acc_amount, acc_amount);449			// 			acc_amount = <BalanceOf<T>>::default();450			// 			return Some(res);451			// 		} else {452			// 			acc_amount -= balance_per_block;453			// 			return Some((block, <BalanceOf<T>>::default(), acc_amount));454			// 		}455			// 	})456			// 	.collect::<Vec<_>>();457458			// new_state459			// 	.into_iter()460			// 	.for_each(|(block, to_staked, _to_pending)| {461			// 		if to_staked == <BalanceOf<T>>::default() {462			// 			<Staked<T>>::remove((&staker_id, block));463			// 		} else {464			// 			<Staked<T>>::insert((&staker_id, block), to_staked);465			// 		}466			// 	});467468			// Ok(())469		}470471		#[pallet::weight(T::WeightInfo::sponsor_collection())]472		pub fn sponsor_collection(473			admin: OriginFor<T>,474			collection_id: CollectionId,475		) -> DispatchResult {476			let admin_id = ensure_signed(admin)?;477			ensure!(478				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,479				Error::<T>::NoPermission480			);481482			T::CollectionHandler::set_sponsor(Self::account_id(), collection_id)483		}484		#[pallet::weight(T::WeightInfo::stop_sponsoring_collection())]485		pub fn stop_sponsoring_collection(486			admin: OriginFor<T>,487			collection_id: CollectionId,488		) -> DispatchResult {489			let admin_id = ensure_signed(admin)?;490491			ensure!(492				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,493				Error::<T>::NoPermission494			);495496			ensure!(497				T::CollectionHandler::get_sponsor(collection_id)?498					.ok_or(<Error<T>>::InvalidArgument)?499					== Self::account_id(),500				<Error<T>>::NoPermission501			);502			T::CollectionHandler::remove_collection_sponsor(collection_id)503		}504505		#[pallet::weight(T::WeightInfo::sponsor_contract())]506		pub fn sponsor_conract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {507			let admin_id = ensure_signed(admin)?;508509			ensure!(510				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,511				Error::<T>::NoPermission512			);513514			T::ContractHandler::set_sponsor(515				T::CrossAccountId::from_sub(Self::account_id()),516				contract_id,517			)518		}519520		#[pallet::weight(T::WeightInfo::stop_sponsoring_contract())]521		pub fn stop_sponsoring_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {522			let admin_id = ensure_signed(admin)?;523524			ensure!(525				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,526				Error::<T>::NoPermission527			);528529			ensure!(530				T::ContractHandler::get_sponsor(contract_id)?.ok_or(<Error<T>>::InvalidArgument)?531					== T::CrossAccountId::from_sub(Self::account_id()),532				<Error<T>>::NoPermission533			);534			T::ContractHandler::remove_contract_sponsor(contract_id)535		}536537		#[pallet::weight(0)]538		pub fn payout_stakers(admin: OriginFor<T>, stakers_number: Option<u8>) -> DispatchResult {539			let admin_id = ensure_signed(admin)?;540541			ensure!(542				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,543				Error::<T>::NoPermission544			);545546			Ok(())547		}548	}549}550551impl<T: Config> Pallet<T> {552	pub fn account_id() -> T::AccountId {553		T::PalletId::get().into_account_truncating()554	}555556	fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {557		let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();558		locked_balance -= amount;559		Self::set_lock_unchecked(staker, locked_balance);560	}561562	fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {563		Self::get_locked_balance(staker)564			.map_or(<BalanceOf<T>>::default(), |l| l.amount)565			.checked_add(&amount)566			.map(|new_lock| Self::set_lock_unchecked(staker, new_lock))567			.ok_or(ArithmeticError::Overflow.into())568	}569570	fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {571		<T::Currency as LockableCurrency<T::AccountId>>::set_lock(572			LOCK_IDENTIFIER,573			staker,574			amount,575			WithdrawReasons::all(),576		)577	}578579	pub fn get_locked_balance(580		staker: impl EncodeLike<T::AccountId>,581	) -> Option<BalanceLock<BalanceOf<T>>> {582		<T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)583			.into_iter()584			.find(|l| l.id == LOCK_IDENTIFIER)585	}586587	pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {588		let staked = Staked::<T>::iter_prefix((staker,))589			.into_iter()590			.fold(<BalanceOf<T>>::default(), |acc, (_, (amount, _))| {591				acc + amount592			});593		if staked != <BalanceOf<T>>::default() {594			Some(staked)595		} else {596			None597		}598	}599600	pub fn total_staked_by_id_per_block(601		staker: impl EncodeLike<T::AccountId>,602	) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {603		let mut staked = Staked::<T>::iter_prefix((staker,))604			.into_iter()605			.map(|(block, (amount, _))| (block, amount))606			.collect::<Vec<_>>();607		staked.sort_by_key(|(block, _)| *block);608		if !staked.is_empty() {609			Some(staked)610		} else {611			None612		}613	}614615	pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {616		staker.map_or(Some(<TotalStaked<T>>::get()), |s| {617			Self::total_staked_by_id(s.as_sub())618		})619		// Self::total_staked_by_id(staker.as_sub())620	}621622	pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {623		Self::get_locked_balance(staker.as_sub())624			.map(|l| l.amount)625			.unwrap_or_default()626	}627628	pub fn cross_id_total_staked_per_block(629		staker: T::CrossAccountId,630	) -> Vec<(T::BlockNumber, BalanceOf<T>)> {631		Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()632	}633634	fn recalculate_stake(635		staker: &T::AccountId,636		block: T::BlockNumber,637		base: BalanceOf<T>,638		income_acc: &mut BalanceOf<T>,639	) {640		let income = Self::calculate_income(base);641		// base.checked_add(&income).map(|res| {642		// 	<Staked<T>>::insert((staker, block), res);643		// 	*income_acc += income;644		// 	<T::Currency as Currency<T::AccountId>>::transfer(645		// 		&T::TreasuryAccountId::get(),646		// 		staker,647		// 		income,648		// 		ExistenceRequirement::KeepAlive,649		// 	)650		// 	.and_then(|_| Self::add_lock_balance(staker, income));651		// });652	}653654	fn calculate_income<I>(base: I) -> I655	where656		I: EncodeLike<BalanceOf<T>> + Balance,657	{658		T::IntervalIncome::get() * base659	}660}661662impl<T: Config> Pallet<T>663where664	<<T as pallet::Config>::Currency as Currency<T::AccountId>>::Balance: Sum,665{666	pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {667		staker.map_or(PendingUnstake::<T>::iter_values().sum(), |s| {668			PendingUnstake::<T>::iter_prefix_values((s.as_sub(),)).sum()669		})670	}671672	pub fn cross_id_pending_unstake_per_block(673		staker: T::CrossAccountId,674	) -> Vec<(T::BlockNumber, BalanceOf<T>)> {675		let mut unsorted_res = PendingUnstake::<T>::iter_prefix((staker.as_sub(),))676			.into_iter()677			.collect::<Vec<_>>();678		unsorted_res.sort_by_key(|(block, _)| *block);679		unsorted_res680	}681}