difftreelog
feautres: Pending interval is now tied to relay blocks, contract sponsors and `stopAppPromotion` added. Preparing to integrate the `app-promotion` palette to integrate with Unique and Quartz.
in: master
13 files changed
pallets/app-promotion/src/lib.rsdiffbeforeafterboth1// 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}pallets/app-promotion/src/types.rsdiffbeforeafterboth--- a/pallets/app-promotion/src/types.rs
+++ b/pallets/app-promotion/src/types.rs
@@ -9,7 +9,7 @@
use sp_runtime::DispatchError;
use up_data_structs::{CollectionId, SponsorshipState};
use sp_std::borrow::ToOwned;
-
+use pallet_evm_contract_helpers::{Pallet as EvmHelpersPallet, Config as EvmHelpersConfig, Sponsoring};
pub trait ExtendedLockableCurrency<AccountId: Parameter>: LockableCurrency<AccountId> {
fn locks<KArg>(who: KArg) -> WeakBoundedVec<BalanceLock<Self::Balance>, Self::MaxLocks>
@@ -94,3 +94,40 @@
.map(|acc| acc.to_owned()))
}
}
+
+pub trait ContractHandler {
+ type ContractId;
+ type AccountId;
+
+ fn set_sponsor(sponsor_id: Self::AccountId, contract_id: Self::ContractId) -> DispatchResult;
+
+ fn remove_contract_sponsor(collection_id: Self::ContractId) -> DispatchResult;
+
+ fn get_sponsor(contract_id: Self::ContractId)
+ -> Result<Option<Self::AccountId>, DispatchError>;
+}
+
+impl<T: EvmHelpersConfig> ContractHandler for EvmHelpersPallet<T> {
+ type ContractId = sp_core::H160;
+
+ type AccountId = T::CrossAccountId;
+
+ fn set_sponsor(sponsor_id: Self::AccountId, contract_id: Self::ContractId) -> DispatchResult {
+ Sponsoring::<T>::insert(
+ contract_id,
+ SponsorshipState::<T::CrossAccountId>::Confirmed(sponsor_id),
+ );
+ Ok(())
+ }
+
+ fn remove_contract_sponsor(contract_id: Self::ContractId) -> DispatchResult {
+ Sponsoring::<T>::remove(contract_id);
+ Ok(())
+ }
+
+ fn get_sponsor(
+ contract_id: Self::ContractId,
+ ) -> Result<Option<Self::AccountId>, DispatchError> {
+ Ok(Self::get_sponsor(contract_id))
+ }
+}
pallets/evm-contract-helpers/src/lib.rsdiffbeforeafterboth--- a/pallets/evm-contract-helpers/src/lib.rs
+++ b/pallets/evm-contract-helpers/src/lib.rs
@@ -75,7 +75,7 @@
/// * **Key** - contract address.
/// * **Value** - sponsorship state.
#[pallet::storage]
- pub(super) type Sponsoring<T: Config> = StorageMap<
+ pub type Sponsoring<T: Config> = StorageMap<
Hasher = Twox64Concat,
Key = H160,
Value = SponsorshipState<T::CrossAccountId>,
primitives/common/src/constants.rsdiffbeforeafterboth--- a/primitives/common/src/constants.rs
+++ b/primitives/common/src/constants.rs
@@ -22,6 +22,7 @@
use crate::types::{BlockNumber, Balance};
pub const MILLISECS_PER_BLOCK: u64 = 12000;
+pub const MILLISECS_PER_RELAY_BLOCK: u64 = 6000;
pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK;
@@ -30,6 +31,11 @@
pub const HOURS: BlockNumber = MINUTES * 60;
pub const DAYS: BlockNumber = HOURS * 24;
+// These time units are defined in number of relay blocks.
+pub const RELAY_MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_RELAY_BLOCK as BlockNumber);
+pub const RELAY_HOURS: BlockNumber = RELAY_MINUTES * 60;
+pub const RELAY_DAYS: BlockNumber = RELAY_HOURS * 24;
+
pub const MICROUNIQUE: Balance = 1_000_000_000_000;
pub const MILLIUNIQUE: Balance = 1_000 * MICROUNIQUE;
pub const CENTIUNIQUE: Balance = 10 * MILLIUNIQUE;
runtime/common/config/pallets/app_promotion.rsdiffbeforeafterboth--- a/runtime/common/config/pallets/app_promotion.rs
+++ b/runtime/common/config/pallets/app_promotion.rs
@@ -16,28 +16,40 @@
use crate::{
runtime_common::config::pallets::{TreasuryAccountId, RelayChainBlockNumberProvider},
- Runtime, Balances, BlockNumber, Unique, Event,
+ Runtime, Balances, BlockNumber, Unique, Event, EvmContractHelpers,
};
use frame_support::{parameter_types, PalletId};
use sp_arithmetic::Perbill;
use up_common::{
- constants::{DAYS, UNIQUE},
+ constants::{DAYS, UNIQUE, RELAY_DAYS},
types::Balance,
};
+#[cfg(all(not(feature = "unique-runtime"), not(feature = "quartz-runtime")))]
parameter_types! {
pub const AppPromotionId: PalletId = PalletId(*b"appstake");
pub const RecalculationInterval: BlockNumber = 20;
- pub const PendingInterval: BlockNumber = 10;
+ pub const PendingInterval: BlockNumber = 20;
pub const Nominal: Balance = UNIQUE;
pub const Day: BlockNumber = DAYS;
- pub IntervalIncome: Perbill = Perbill::from_rational(RecalculationInterval::get(), 2 * DAYS) * Perbill::from_rational(5u32, 10_000);
+ pub IntervalIncome: Perbill = Perbill::from_rational(RecalculationInterval::get(), RELAY_DAYS) * Perbill::from_rational(5u32, 10_000);
+}
+
+#[cfg(any(feature = "unique-runtime", feature = "quartz-runtime"))]
+parameter_types! {
+ pub const AppPromotionId: PalletId = PalletId(*b"appstake");
+ pub const RecalculationInterval: BlockNumber = RELAY_DAYS;
+ pub const PendingInterval: BlockNumber = 7 * RELAY_DAYS;
+ pub const Nominal: Balance = UNIQUE;
+ pub const Day: BlockNumber = RELAY_DAYS;
+ pub IntervalIncome: Perbill = Perbill::from_rational(5u32, 10_000);
}
impl pallet_app_promotion::Config for Runtime {
type PalletId = AppPromotionId;
type CollectionHandler = Unique;
+ type ContractHandler = EvmContractHelpers;
type Currency = Balances;
type WeightInfo = pallet_app_promotion::weights::SubstrateWeight<Self>;
type TreasuryAccountId = TreasuryAccountId;
tests/src/app-promotion.test.tsdiffbeforeafterboth--- a/tests/src/app-promotion.test.ts
+++ b/tests/src/app-promotion.test.ts
@@ -706,6 +706,12 @@
nominal = helper.balance.getOneTokenNominal();
});
});
+
+ after(async function () {
+ await usingPlaygrounds(async (helper) => {
+ await helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.stopAppPromotion()));
+ });
+ });
it('will credit 0.05% for staking period', async () => {
// arrange: bob.stake(10000);
@@ -770,39 +776,24 @@
const staker = await createUser(40n * nominal);
await waitForRecalculationBlock(helper.api!);
- // const foo = await helper.api!.registry.getChainProperties().
+
await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(10n * nominal))).to.be.eventually.fulfilled;
- // await waitNewBlocks(helper.api!, 1);
+
await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(10n * nominal))).to.be.eventually.fulfilled;
- // await waitNewBlocks(helper.api!, 1);
+
await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(10n * nominal))).to.be.eventually.fulfilled;
- // console.log(await helper.balance.getSubstrate(staker.address));
- // await waitNewBlocks(helper.api!, 17);
+
await waitForRelayBlock(helper.api!, 34);
expect((await helper.api!.rpc.unique.totalStakedPerBlock(normalizeAccountId(staker)))
.map(([_, amount]) => amount.toBigInt()))
.to.be.deep.equal([calculateIncome(10n * nominal, 10n), calculateIncome(10n * nominal, 10n), calculateIncome(10n * nominal, 10n)]);
- // console.log(await getBlockNumber(helper.api!));
- // console.log((await helper.api!.rpc.unique.totalStakedPerBlock(normalizeAccountId(staker))).map(([block, amount]) => [block.toBigInt(), amount.toBigInt()]));
- // console.log(`${calculateIncome(10n * nominal, 10n)} || ${calculateIncome(10n * nominal, 10n, 2)}`);
- // await waitNewBlocks(helper.api!, 10);
await waitForRelayBlock(helper.api!, 20);
- // console.log((await helper.api!.rpc.unique.totalStakedPerBlock(normalizeAccountId(staker))).map(([_, amount]) => amount.toBigInt()));
- // console.log(await helper.balance.getSubstrate(staker.address));
await expect(helper.signTransaction(staker, helper.api!.tx.promotion.unstake(calculateIncome(10n * nominal, 10n, 2) - 10n * nominal))).to.be.eventually.fulfilled;
- // console.log(calculateIncome(10n * nominal, 10n, 2));
- // console.log(calculateIncome(10n * nominal, 10n, 3));
- // console.log(calculateIncome(10n * nominal, 10n, 4));
- // console.log(calculateIncome(10n * nominal, 10n, 5));
expect((await helper.api!.rpc.unique.totalStakedPerBlock(normalizeAccountId(staker)))
.map(([_, amount]) => amount.toBigInt()))
.to.be.deep.equal([10n * nominal, calculateIncome(10n * nominal, 10n, 2), calculateIncome(10n * nominal, 10n, 2)]);
-
- // console.log((await helper.api!.rpc.unique.totalStakedPerBlock(normalizeAccountId(staker))).map(([_, amount]) => amount.toBigInt()));
-
- // console.log(await helper.balance.getSubstrate(staker.address));
});
});
tests/src/interfaces/augment-api-consts.tsdiffbeforeafterboth--- a/tests/src/interfaces/augment-api-consts.ts
+++ b/tests/src/interfaces/augment-api-consts.ts
@@ -78,7 +78,7 @@
**/
palletId: FrameSupportPalletId & AugmentedConst<ApiType>;
/**
- * In chain blocks.
+ * In relay blocks.
**/
pendingInterval: u32 & AugmentedConst<ApiType>;
/**
tests/src/interfaces/augment-api-errors.tsdiffbeforeafterboth--- a/tests/src/interfaces/augment-api-errors.ts
+++ b/tests/src/interfaces/augment-api-errors.ts
@@ -430,11 +430,16 @@
[key: string]: AugmentedError<ApiType>;
};
promotion: {
+ /**
+ * Error due to action requiring admin to be set
+ **/
AdminNotSet: AugmentedError<ApiType>;
- AlreadySponsored: AugmentedError<ApiType>;
+ /**
+ * An error related to the fact that an invalid argument was passed to perform an action
+ **/
InvalidArgument: AugmentedError<ApiType>;
/**
- * No permission to perform action
+ * No permission to perform an action
**/
NoPermission: AugmentedError<ApiType>;
/**
tests/src/interfaces/augment-api-query.tsdiffbeforeafterboth--- a/tests/src/interfaces/augment-api-query.ts
+++ b/tests/src/interfaces/augment-api-query.ts
@@ -525,7 +525,7 @@
**/
staked: AugmentedQuery<ApiType, (arg1: AccountId32 | string | Uint8Array, arg2: u32 | AnyNumber | Uint8Array) => Observable<u128>, [AccountId32, u32]> & QueryableStorageEntry<ApiType, [AccountId32, u32]>;
/**
- * A block when app-promotion has started
+ * A block when app-promotion has started .I think this is redundant, because we only need `NextInterestBlock`.
**/
startBlock: AugmentedQuery<ApiType, () => Observable<u32>, []> & QueryableStorageEntry<ApiType, []>;
totalStaked: AugmentedQuery<ApiType, () => Observable<u128>, []> & QueryableStorageEntry<ApiType, []>;
tests/src/interfaces/augment-api-tx.tsdiffbeforeafterboth--- a/tests/src/interfaces/augment-api-tx.ts
+++ b/tests/src/interfaces/augment-api-tx.ts
@@ -365,9 +365,12 @@
promotion: {
setAdminAddress: AugmentedSubmittable<(admin: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [PalletEvmAccountBasicCrossAccountIdRepr]>;
sponsorCollection: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32]>;
+ sponsorConract: AugmentedSubmittable<(contractId: H160 | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [H160]>;
stake: AugmentedSubmittable<(amount: u128 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [u128]>;
startAppPromotion: AugmentedSubmittable<(promotionStartRelayBlock: Option<u32> | null | Uint8Array | u32 | AnyNumber) => SubmittableExtrinsic<ApiType>, [Option<u32>]>;
+ stopAppPromotion: AugmentedSubmittable<() => SubmittableExtrinsic<ApiType>, []>;
stopSponsorignCollection: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32]>;
+ stopSponsorignContract: AugmentedSubmittable<(contractId: H160 | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [H160]>;
unstake: AugmentedSubmittable<(amount: u128 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [u128]>;
/**
* Generic tx
tests/src/interfaces/default/types.tsdiffbeforeafterboth--- a/tests/src/interfaces/default/types.ts
+++ b/tests/src/interfaces/default/types.ts
@@ -816,6 +816,7 @@
readonly asStartAppPromotion: {
readonly promotionStartRelayBlock: Option<u32>;
} & Struct;
+ readonly isStopAppPromotion: boolean;
readonly isStake: boolean;
readonly asStake: {
readonly amount: u128;
@@ -832,7 +833,15 @@
readonly asStopSponsorignCollection: {
readonly collectionId: u32;
} & Struct;
- readonly type: 'SetAdminAddress' | 'StartAppPromotion' | 'Stake' | 'Unstake' | 'SponsorCollection' | 'StopSponsorignCollection';
+ readonly isSponsorConract: boolean;
+ readonly asSponsorConract: {
+ readonly contractId: H160;
+ } & Struct;
+ readonly isStopSponsorignContract: boolean;
+ readonly asStopSponsorignContract: {
+ readonly contractId: H160;
+ } & Struct;
+ readonly type: 'SetAdminAddress' | 'StartAppPromotion' | 'StopAppPromotion' | 'Stake' | 'Unstake' | 'SponsorCollection' | 'StopSponsorignCollection' | 'SponsorConract' | 'StopSponsorignContract';
}
/** @name PalletAppPromotionError */
@@ -841,8 +850,7 @@
readonly isNoPermission: boolean;
readonly isNotSufficientFounds: boolean;
readonly isInvalidArgument: boolean;
- readonly isAlreadySponsored: boolean;
- readonly type: 'AdminNotSet' | 'NoPermission' | 'NotSufficientFounds' | 'InvalidArgument' | 'AlreadySponsored';
+ readonly type: 'AdminNotSet' | 'NoPermission' | 'NotSufficientFounds' | 'InvalidArgument';
}
/** @name PalletAppPromotionEvent */
tests/src/interfaces/lookup.tsdiffbeforeafterboth--- a/tests/src/interfaces/lookup.ts
+++ b/tests/src/interfaces/lookup.ts
@@ -2463,6 +2463,7 @@
start_app_promotion: {
promotionStartRelayBlock: 'Option<u32>',
},
+ stop_app_promotion: 'Null',
stake: {
amount: 'u128',
},
@@ -2473,7 +2474,13 @@
collectionId: 'u32',
},
stop_sponsorign_collection: {
- collectionId: 'u32'
+ collectionId: 'u32',
+ },
+ sponsor_conract: {
+ contractId: 'H160',
+ },
+ stop_sponsorign_contract: {
+ contractId: 'H160'
}
}
},
@@ -3098,7 +3105,7 @@
* Lookup410: pallet_app_promotion::pallet::Error<T>
**/
PalletAppPromotionError: {
- _enum: ['AdminNotSet', 'NoPermission', 'NotSufficientFounds', 'InvalidArgument', 'AlreadySponsored']
+ _enum: ['AdminNotSet', 'NoPermission', 'NotSufficientFounds', 'InvalidArgument']
},
/**
* Lookup413: pallet_evm::pallet::Error<T>
tests/src/interfaces/types-lookup.tsdiffbeforeafterboth--- a/tests/src/interfaces/types-lookup.ts
+++ b/tests/src/interfaces/types-lookup.ts
@@ -2669,6 +2669,7 @@
readonly asStartAppPromotion: {
readonly promotionStartRelayBlock: Option<u32>;
} & Struct;
+ readonly isStopAppPromotion: boolean;
readonly isStake: boolean;
readonly asStake: {
readonly amount: u128;
@@ -2685,7 +2686,15 @@
readonly asStopSponsorignCollection: {
readonly collectionId: u32;
} & Struct;
- readonly type: 'SetAdminAddress' | 'StartAppPromotion' | 'Stake' | 'Unstake' | 'SponsorCollection' | 'StopSponsorignCollection';
+ readonly isSponsorConract: boolean;
+ readonly asSponsorConract: {
+ readonly contractId: H160;
+ } & Struct;
+ readonly isStopSponsorignContract: boolean;
+ readonly asStopSponsorignContract: {
+ readonly contractId: H160;
+ } & Struct;
+ readonly type: 'SetAdminAddress' | 'StartAppPromotion' | 'StopAppPromotion' | 'Stake' | 'Unstake' | 'SponsorCollection' | 'StopSponsorignCollection' | 'SponsorConract' | 'StopSponsorignContract';
}
/** @name PalletEvmCall (305) */
@@ -3288,8 +3297,7 @@
readonly isNoPermission: boolean;
readonly isNotSufficientFounds: boolean;
readonly isInvalidArgument: boolean;
- readonly isAlreadySponsored: boolean;
- readonly type: 'AdminNotSet' | 'NoPermission' | 'NotSufficientFounds' | 'InvalidArgument' | 'AlreadySponsored';
+ readonly type: 'AdminNotSet' | 'NoPermission' | 'NotSufficientFounds' | 'InvalidArgument';
}
/** @name PalletEvmError (413) */