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.rsdiffbeforeafterboth--- a/pallets/app-promotion/src/lib.rs
+++ b/pallets/app-promotion/src/lib.rs
@@ -37,9 +37,10 @@
pub mod weights;
use sp_std::{vec::Vec, iter::Sum, borrow::ToOwned};
+use sp_core::H160;
use codec::EncodeLike;
use pallet_balances::BalanceLock;
-pub use types::ExtendedLockableCurrency;
+pub use types::*;
// use up_common::constants::{DAYS, UNIQUE};
use up_data_structs::CollectionId;
@@ -78,7 +79,6 @@
use super::*;
use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, PalletId};
use frame_system::pallet_prelude::*;
- use types::CollectionHandler;
#[pallet::config]
pub trait Config: frame_system::Config + pallet_evm::account::Config {
@@ -89,6 +89,8 @@
CollectionId = CollectionId,
>;
+ type ContractHandler: ContractHandler<AccountId = Self::CrossAccountId, ContractId = H160>;
+
type TreasuryAccountId: Get<Self::AccountId>;
/// The app's pallet id, used for deriving its sovereign account ID.
@@ -98,7 +100,7 @@
/// In relay blocks.
#[pallet::constant]
type RecalculationInterval: Get<Self::BlockNumber>;
- /// In chain blocks.
+ /// In relay blocks.
#[pallet::constant]
type PendingInterval: Get<Self::BlockNumber>;
@@ -120,13 +122,6 @@
/// Events compatible with [`frame_system::Config::Event`].
type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;
-
- // /// Number of blocks that pass between treasury balance updates due to inflation
- // #[pallet::constant]
- // type InterestBlockInterval: Get<Self::BlockNumber>;
-
- // // Weight information for functions of this pallet.
- // type WeightInfo: WeightInfo;
}
#[pallet::pallet]
@@ -146,13 +141,14 @@
#[pallet::error]
pub enum Error<T> {
+ /// Error due to action requiring admin to be set
AdminNotSet,
- /// No permission to perform action
+ /// No permission to perform an action
NoPermission,
/// Insufficient funds to perform an action
NotSufficientFounds,
+ /// An error related to the fact that an invalid argument was passed to perform an action
InvalidArgument,
- AlreadySponsored,
}
#[pallet::storage]
@@ -183,7 +179,7 @@
QueryKind = ValueQuery,
>;
- /// A block when app-promotion has started
+ /// A block when app-promotion has started .I think this is redundant, because we only need `NextInterestBlock`.
#[pallet::storage]
pub type StartBlock<T: Config> = StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;
@@ -285,6 +281,21 @@
Ok(())
}
+ #[pallet::weight(0)]
+ pub fn stop_app_promotion(origin: OriginFor<T>) -> DispatchResult
+ where
+ <T as frame_system::Config>::BlockNumber: From<u32>,
+ {
+ ensure_root(origin)?;
+
+ if <StartBlock<T>>::get() != 0u32.into() {
+ <StartBlock<T>>::set(T::BlockNumber::default());
+ <NextInterestBlock<T>>::set(T::BlockNumber::default());
+ }
+
+ Ok(())
+ }
+
#[pallet::weight(T::WeightInfo::stake())]
pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {
let staker_id = ensure_signed(staker)?;
@@ -341,7 +352,8 @@
.ok_or(ArithmeticError::Underflow)?,
);
- let block = frame_system::Pallet::<T>::block_number() + T::PendingInterval::get();
+ let block =
+ T::RelayBlockNumberProvider::current_block_number() + T::PendingInterval::get();
<PendingUnstake<T>>::insert(
(&staker_id, block),
<PendingUnstake<T>>::get((&staker_id, block))
@@ -415,113 +427,46 @@
);
T::CollectionHandler::remove_collection_sponsor(collection_id)
}
- }
-}
-
-impl<T: Config> Pallet<T> {
- // pub fn stake(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {
- // let balance = <<T as Config>::Currency as Currency<T::AccountId>>::free_balance(staker);
-
- // ensure!(balance >= amount, ArithmeticError::Underflow);
-
- // Self::set_lock_unchecked(staker, amount);
-
- // let block_number = <T::BlockNumberProvider as BlockNumberProvider>::current_block_number();
- // <Staked<T>>::insert(
- // (staker, block_number),
- // <Staked<T>>::get((staker, block_number))
- // .checked_add(&amount)
- // .ok_or(ArithmeticError::Overflow)?,
- // );
-
- // <TotalStaked<T>>::set(
- // <TotalStaked<T>>::get()
- // .checked_add(&amount)
- // .ok_or(ArithmeticError::Overflow)?,
- // );
-
- // Ok(())
- // }
-
- // pub fn unstake(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {
- // let mut stakes = Staked::<T>::iter_prefix((staker,)).collect::<Vec<_>>();
-
- // let total_staked = stakes
- // .iter()
- // .fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + *amount);
-
- // ensure!(total_staked >= amount, ArithmeticError::Underflow);
-
- // <TotalStaked<T>>::set(
- // <TotalStaked<T>>::get()
- // .checked_sub(&amount)
- // .ok_or(ArithmeticError::Underflow)?,
- // );
-
- // let block = <T::BlockNumberProvider>::current_block_number() + WEEK.into();
- // <PendingUnstake<T>>::insert(
- // (staker, block),
- // <PendingUnstake<T>>::get((staker, block))
- // .checked_add(&amount)
- // .ok_or(ArithmeticError::Overflow)?,
- // );
-
- // stakes.sort_by_key(|(block, _)| *block);
-
- // let mut acc_amount = amount;
- // let new_state = stakes
- // .into_iter()
- // .map_while(|(block, balance_per_block)| {
- // if acc_amount == <BalanceOf<T>>::default() {
- // return None;
- // }
- // if acc_amount <= balance_per_block {
- // let res = (block, balance_per_block - acc_amount, acc_amount);
- // acc_amount = <BalanceOf<T>>::default();
- // return Some(res);
- // } else {
- // acc_amount -= balance_per_block;
- // return Some((block, <BalanceOf<T>>::default(), acc_amount));
- // }
- // })
- // .collect::<Vec<_>>();
-
- // new_state
- // .into_iter()
- // .for_each(|(block, to_staked, _to_pending)| {
- // if to_staked == <BalanceOf<T>>::default() {
- // <Staked<T>>::remove((staker, block));
- // } else {
- // <Staked<T>>::insert((staker, block), to_staked);
- // }
- // });
+ #[pallet::weight(0)]
+ pub fn sponsor_conract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {
+ let admin_id = ensure_signed(admin)?;
- // Ok(())
- // }
+ ensure!(
+ admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,
+ Error::<T>::NoPermission
+ );
- // pub fn sponsor_collection(admin: T::AccountId, collection_id: u32) -> DispatchResult {
- // Ok(())
- // }
+ T::ContractHandler::set_sponsor(
+ T::CrossAccountId::from_sub(Self::account_id()),
+ contract_id,
+ )
+ }
- // pub fn stop_sponsorign_collection(admin: T::AccountId, collection_id: u32) -> DispatchResult {
- // Ok(())
- // }
+ #[pallet::weight(0)]
+ pub fn stop_sponsorign_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {
+ let admin_id = ensure_signed(admin)?;
- pub fn sponsor_conract(admin: T::AccountId, app_id: u32) -> DispatchResult {
- Ok(())
- }
+ ensure!(
+ admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,
+ Error::<T>::NoPermission
+ );
- pub fn stop_sponsorign_contract(admin: T::AccountId, app_id: u32) -> DispatchResult {
- Ok(())
+ ensure!(
+ T::ContractHandler::get_sponsor(contract_id)?.ok_or(<Error<T>>::InvalidArgument)?
+ == T::CrossAccountId::from_sub(Self::account_id()),
+ <Error<T>>::NoPermission
+ );
+ T::ContractHandler::remove_contract_sponsor(contract_id)
+ }
}
+}
+impl<T: Config> Pallet<T> {
pub fn account_id() -> T::AccountId {
T::PalletId::get().into_account_truncating()
}
-}
-impl<T: Config> Pallet<T> {
fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {
let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();
locked_balance -= amount;
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.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#![doc = include_str!("../README.md")]18#![cfg_attr(not(feature = "std"), no_std)]19#![deny(missing_docs)]2021use codec::{Decode, Encode, MaxEncodedLen};22pub use pallet::*;23pub use eth::*;24use scale_info::TypeInfo;25pub mod eth;2627#[frame_support::pallet]28pub mod pallet {29 pub use super::*;30 use frame_support::pallet_prelude::*;31 use pallet_evm_coder_substrate::DispatchResult;32 use sp_core::H160;33 use pallet_evm::account::CrossAccountId;34 use up_data_structs::SponsorshipState;3536 #[pallet::config]37 pub trait Config:38 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::account::Config39 {40 /// Address, under which magic contract will be available41 type ContractAddress: Get<H160>;42 /// In case of enabled sponsoring, but no sponsoring rate limit set,43 /// this value will be used implicitly44 type DefaultSponsoringRateLimit: Get<Self::BlockNumber>;45 }4647 #[pallet::error]48 pub enum Error<T> {49 /// This method is only executable by contract owner50 NoPermission,5152 /// No pending sponsor for contract.53 NoPendingSponsor,54 }5556 #[pallet::pallet]57 #[pallet::generate_store(pub(super) trait Store)]58 pub struct Pallet<T>(_);5960 /// Store owner for contract.61 ///62 /// * **Key** - contract address.63 /// * **Value** - owner for contract.64 #[pallet::storage]65 pub(super) type Owner<T: Config> =66 StorageMap<Hasher = Twox128, Key = H160, Value = H160, QueryKind = ValueQuery>;6768 #[pallet::storage]69 #[deprecated]70 pub(super) type SelfSponsoring<T: Config> =71 StorageMap<Hasher = Twox128, Key = H160, Value = bool, QueryKind = ValueQuery>;7273 /// Store for contract sponsorship state.74 ///75 /// * **Key** - contract address.76 /// * **Value** - sponsorship state.77 #[pallet::storage]78 pub(super) type Sponsoring<T: Config> = StorageMap<79 Hasher = Twox64Concat,80 Key = H160,81 Value = SponsorshipState<T::CrossAccountId>,82 QueryKind = ValueQuery,83 >;8485 /// Store for sponsoring mode.86 ///87 /// ### Usage88 /// Prefer to delete collection from storage if mode chaged to [`Disabled`](SponsoringModeT::Disabled).89 ///90 /// * **Key** - contract address.91 /// * **Value** - [`sponsoring mode`](SponsoringModeT).92 #[pallet::storage]93 pub(super) type SponsoringMode<T: Config> =94 StorageMap<Hasher = Twox128, Key = H160, Value = SponsoringModeT, QueryKind = OptionQuery>;9596 /// Storage for sponsoring rate limit in blocks.97 ///98 /// * **Key** - contract address.99 /// * **Value** - amount of sponsored blocks.100 #[pallet::storage]101 pub(super) type SponsoringRateLimit<T: Config> = StorageMap<102 Hasher = Twox128,103 Key = H160,104 Value = T::BlockNumber,105 QueryKind = ValueQuery,106 OnEmpty = T::DefaultSponsoringRateLimit,107 >;108109 /// Storage for last sponsored block.110 ///111 /// * **Key1** - contract address.112 /// * **Key2** - sponsored user address.113 /// * **Value** - last sponsored block number.114 #[pallet::storage]115 pub(super) type SponsorBasket<T: Config> = StorageDoubleMap<116 Hasher1 = Twox128,117 Key1 = H160,118 Hasher2 = Twox128,119 Key2 = H160,120 Value = T::BlockNumber,121 QueryKind = OptionQuery,122 >;123124 /// Storege for contracts with [`Allowlisted`](SponsoringModeT::Allowlisted) sponsoring mode.125 ///126 /// ### Usage127 /// Prefer to delete collection from storage if mode chaged to non `Allowlisted`, than set **Value** to **false**.128 ///129 /// * **Key** - contract address.130 /// * **Value** - is contract in [`Allowlisted`](SponsoringModeT::Allowlisted) mode.131 #[pallet::storage]132 pub(super) type AllowlistEnabled<T: Config> =133 StorageMap<Hasher = Twox128, Key = H160, Value = bool, QueryKind = ValueQuery>;134135 /// Storage for users that allowed for sponsorship.136 ///137 /// ### Usage138 /// Prefer to delete record from storage if user no more allowed for sponsorship.139 ///140 /// * **Key1** - contract address.141 /// * **Key2** - user that allowed for sponsorship.142 /// * **Value** - allowance for sponsorship.143 #[pallet::storage]144 pub(super) type Allowlist<T: Config> = StorageDoubleMap<145 Hasher1 = Twox128,146 Key1 = H160,147 Hasher2 = Twox128,148 Key2 = H160,149 Value = bool,150 QueryKind = ValueQuery,151 >;152153 impl<T: Config> Pallet<T> {154 /// Get contract owner.155 pub fn contract_owner(contract: H160) -> H160 {156 <Owner<T>>::get(contract)157 }158159 /// Set `sponsor` for `contract`.160 ///161 /// `sender` must be owner of contract.162 pub fn set_sponsor(163 sender: &T::CrossAccountId,164 contract: H160,165 sponsor: &T::CrossAccountId,166 ) -> DispatchResult {167 Pallet::<T>::ensure_owner(contract, *sender.as_eth())?;168 Sponsoring::<T>::insert(169 contract,170 SponsorshipState::<T::CrossAccountId>::Unconfirmed(sponsor.clone()),171 );172 Ok(())173 }174175 /// Set `contract` as self sponsored.176 ///177 /// `sender` must be owner of contract.178 pub fn self_sponsored_enable(sender: &T::CrossAccountId, contract: H160) -> DispatchResult {179 Pallet::<T>::ensure_owner(contract, *sender.as_eth())?;180 Sponsoring::<T>::insert(181 contract,182 SponsorshipState::<T::CrossAccountId>::Confirmed(T::CrossAccountId::from_eth(183 contract,184 )),185 );186 Ok(())187 }188189 /// Remove sponsor for `contract`.190 ///191 /// `sender` must be owner of contract.192 pub fn remove_sponsor(sender: &T::CrossAccountId, contract: H160) -> DispatchResult {193 Pallet::<T>::ensure_owner(contract, *sender.as_eth())?;194 Sponsoring::<T>::remove(contract);195 Ok(())196 }197198 /// Confirm sponsorship.199 ///200 /// `sender` must be same that set via [`set_sponsor`].201 pub fn confirm_sponsorship(sender: &T::CrossAccountId, contract: H160) -> DispatchResult {202 match Sponsoring::<T>::get(contract) {203 SponsorshipState::Unconfirmed(sponsor) => {204 ensure!(sponsor == *sender, Error::<T>::NoPermission);205 Sponsoring::<T>::insert(206 contract,207 SponsorshipState::<T::CrossAccountId>::Confirmed(sponsor),208 );209 Ok(())210 }211 SponsorshipState::Disabled | SponsorshipState::Confirmed(_) => {212 Err(Error::<T>::NoPendingSponsor.into())213 }214 }215 }216217 /// Get sponsor.218 pub fn get_sponsor(contract: H160) -> Option<T::CrossAccountId> {219 match Sponsoring::<T>::get(contract) {220 SponsorshipState::Disabled | SponsorshipState::Unconfirmed(_) => None,221 SponsorshipState::Confirmed(sponsor) => Some(sponsor),222 }223 }224225 /// Get current sponsoring mode, performing lazy migration from legacy storage226 pub fn sponsoring_mode(contract: H160) -> SponsoringModeT {227 <SponsoringMode<T>>::get(contract)228 .or_else(|| {229 <SelfSponsoring<T>>::get(contract).then(|| SponsoringModeT::Allowlisted)230 })231 .unwrap_or_default()232 }233234 /// Reconfigure contract sponsoring mode235 pub fn set_sponsoring_mode(contract: H160, mode: SponsoringModeT) {236 if mode == SponsoringModeT::Disabled {237 <SponsoringMode<T>>::remove(contract);238 } else {239 <SponsoringMode<T>>::insert(contract, mode);240 }241 <SelfSponsoring<T>>::remove(contract)242 }243244 /// Set duration between two sponsored contract calls245 pub fn set_sponsoring_rate_limit(contract: H160, rate_limit: T::BlockNumber) {246 <SponsoringRateLimit<T>>::insert(contract, rate_limit);247 }248249 /// Is user added to allowlist, or he is owner of specified contract250 pub fn allowed(contract: H160, user: H160) -> bool {251 <Allowlist<T>>::get(&contract, &user) || <Owner<T>>::get(&contract) == user252 }253254 /// Toggle contract allowlist access255 pub fn toggle_allowlist(contract: H160, enabled: bool) {256 <AllowlistEnabled<T>>::insert(contract, enabled)257 }258259 /// Toggle user presence in contract's allowlist260 pub fn toggle_allowed(contract: H160, user: H160, allowed: bool) {261 <Allowlist<T>>::insert(contract, user, allowed);262 }263264 /// Throw error if user is not allowed to reconfigure target contract265 pub fn ensure_owner(contract: H160, user: H160) -> DispatchResult {266 ensure!(<Owner<T>>::get(&contract) == user, Error::<T>::NoPermission);267 Ok(())268 }269 }270}271272/// Available contract sponsoring modes273#[derive(Encode, Decode, PartialEq, TypeInfo, MaxEncodedLen, Default)]274pub enum SponsoringModeT {275 /// Sponsoring is disabled276 #[default]277 Disabled,278 /// Only users from allowlist will be sponsored279 Allowlisted,280 /// All users will be sponsored281 Generous,282}283284impl SponsoringModeT {285 fn from_eth(v: u8) -> Option<Self> {286 Some(match v {287 0 => Self::Disabled,288 1 => Self::Allowlisted,289 2 => Self::Generous,290 _ => return None,291 })292 }293 fn to_eth(self) -> u8 {294 match self {295 SponsoringModeT::Disabled => 0,296 SponsoringModeT::Allowlisted => 1,297 SponsoringModeT::Generous => 2,298 }299 }300}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#![doc = include_str!("../README.md")]18#![cfg_attr(not(feature = "std"), no_std)]19#![deny(missing_docs)]2021use codec::{Decode, Encode, MaxEncodedLen};22pub use pallet::*;23pub use eth::*;24use scale_info::TypeInfo;25pub mod eth;2627#[frame_support::pallet]28pub mod pallet {29 pub use super::*;30 use frame_support::pallet_prelude::*;31 use pallet_evm_coder_substrate::DispatchResult;32 use sp_core::H160;33 use pallet_evm::account::CrossAccountId;34 use up_data_structs::SponsorshipState;3536 #[pallet::config]37 pub trait Config:38 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::account::Config39 {40 /// Address, under which magic contract will be available41 type ContractAddress: Get<H160>;42 /// In case of enabled sponsoring, but no sponsoring rate limit set,43 /// this value will be used implicitly44 type DefaultSponsoringRateLimit: Get<Self::BlockNumber>;45 }4647 #[pallet::error]48 pub enum Error<T> {49 /// This method is only executable by contract owner50 NoPermission,5152 /// No pending sponsor for contract.53 NoPendingSponsor,54 }5556 #[pallet::pallet]57 #[pallet::generate_store(pub(super) trait Store)]58 pub struct Pallet<T>(_);5960 /// Store owner for contract.61 ///62 /// * **Key** - contract address.63 /// * **Value** - owner for contract.64 #[pallet::storage]65 pub(super) type Owner<T: Config> =66 StorageMap<Hasher = Twox128, Key = H160, Value = H160, QueryKind = ValueQuery>;6768 #[pallet::storage]69 #[deprecated]70 pub(super) type SelfSponsoring<T: Config> =71 StorageMap<Hasher = Twox128, Key = H160, Value = bool, QueryKind = ValueQuery>;7273 /// Store for contract sponsorship state.74 ///75 /// * **Key** - contract address.76 /// * **Value** - sponsorship state.77 #[pallet::storage]78 pub type Sponsoring<T: Config> = StorageMap<79 Hasher = Twox64Concat,80 Key = H160,81 Value = SponsorshipState<T::CrossAccountId>,82 QueryKind = ValueQuery,83 >;8485 /// Store for sponsoring mode.86 ///87 /// ### Usage88 /// Prefer to delete collection from storage if mode chaged to [`Disabled`](SponsoringModeT::Disabled).89 ///90 /// * **Key** - contract address.91 /// * **Value** - [`sponsoring mode`](SponsoringModeT).92 #[pallet::storage]93 pub(super) type SponsoringMode<T: Config> =94 StorageMap<Hasher = Twox128, Key = H160, Value = SponsoringModeT, QueryKind = OptionQuery>;9596 /// Storage for sponsoring rate limit in blocks.97 ///98 /// * **Key** - contract address.99 /// * **Value** - amount of sponsored blocks.100 #[pallet::storage]101 pub(super) type SponsoringRateLimit<T: Config> = StorageMap<102 Hasher = Twox128,103 Key = H160,104 Value = T::BlockNumber,105 QueryKind = ValueQuery,106 OnEmpty = T::DefaultSponsoringRateLimit,107 >;108109 /// Storage for last sponsored block.110 ///111 /// * **Key1** - contract address.112 /// * **Key2** - sponsored user address.113 /// * **Value** - last sponsored block number.114 #[pallet::storage]115 pub(super) type SponsorBasket<T: Config> = StorageDoubleMap<116 Hasher1 = Twox128,117 Key1 = H160,118 Hasher2 = Twox128,119 Key2 = H160,120 Value = T::BlockNumber,121 QueryKind = OptionQuery,122 >;123124 /// Storege for contracts with [`Allowlisted`](SponsoringModeT::Allowlisted) sponsoring mode.125 ///126 /// ### Usage127 /// Prefer to delete collection from storage if mode chaged to non `Allowlisted`, than set **Value** to **false**.128 ///129 /// * **Key** - contract address.130 /// * **Value** - is contract in [`Allowlisted`](SponsoringModeT::Allowlisted) mode.131 #[pallet::storage]132 pub(super) type AllowlistEnabled<T: Config> =133 StorageMap<Hasher = Twox128, Key = H160, Value = bool, QueryKind = ValueQuery>;134135 /// Storage for users that allowed for sponsorship.136 ///137 /// ### Usage138 /// Prefer to delete record from storage if user no more allowed for sponsorship.139 ///140 /// * **Key1** - contract address.141 /// * **Key2** - user that allowed for sponsorship.142 /// * **Value** - allowance for sponsorship.143 #[pallet::storage]144 pub(super) type Allowlist<T: Config> = StorageDoubleMap<145 Hasher1 = Twox128,146 Key1 = H160,147 Hasher2 = Twox128,148 Key2 = H160,149 Value = bool,150 QueryKind = ValueQuery,151 >;152153 impl<T: Config> Pallet<T> {154 /// Get contract owner.155 pub fn contract_owner(contract: H160) -> H160 {156 <Owner<T>>::get(contract)157 }158159 /// Set `sponsor` for `contract`.160 ///161 /// `sender` must be owner of contract.162 pub fn set_sponsor(163 sender: &T::CrossAccountId,164 contract: H160,165 sponsor: &T::CrossAccountId,166 ) -> DispatchResult {167 Pallet::<T>::ensure_owner(contract, *sender.as_eth())?;168 Sponsoring::<T>::insert(169 contract,170 SponsorshipState::<T::CrossAccountId>::Unconfirmed(sponsor.clone()),171 );172 Ok(())173 }174175 /// Set `contract` as self sponsored.176 ///177 /// `sender` must be owner of contract.178 pub fn self_sponsored_enable(sender: &T::CrossAccountId, contract: H160) -> DispatchResult {179 Pallet::<T>::ensure_owner(contract, *sender.as_eth())?;180 Sponsoring::<T>::insert(181 contract,182 SponsorshipState::<T::CrossAccountId>::Confirmed(T::CrossAccountId::from_eth(183 contract,184 )),185 );186 Ok(())187 }188189 /// Remove sponsor for `contract`.190 ///191 /// `sender` must be owner of contract.192 pub fn remove_sponsor(sender: &T::CrossAccountId, contract: H160) -> DispatchResult {193 Pallet::<T>::ensure_owner(contract, *sender.as_eth())?;194 Sponsoring::<T>::remove(contract);195 Ok(())196 }197198 /// Confirm sponsorship.199 ///200 /// `sender` must be same that set via [`set_sponsor`].201 pub fn confirm_sponsorship(sender: &T::CrossAccountId, contract: H160) -> DispatchResult {202 match Sponsoring::<T>::get(contract) {203 SponsorshipState::Unconfirmed(sponsor) => {204 ensure!(sponsor == *sender, Error::<T>::NoPermission);205 Sponsoring::<T>::insert(206 contract,207 SponsorshipState::<T::CrossAccountId>::Confirmed(sponsor),208 );209 Ok(())210 }211 SponsorshipState::Disabled | SponsorshipState::Confirmed(_) => {212 Err(Error::<T>::NoPendingSponsor.into())213 }214 }215 }216217 /// Get sponsor.218 pub fn get_sponsor(contract: H160) -> Option<T::CrossAccountId> {219 match Sponsoring::<T>::get(contract) {220 SponsorshipState::Disabled | SponsorshipState::Unconfirmed(_) => None,221 SponsorshipState::Confirmed(sponsor) => Some(sponsor),222 }223 }224225 /// Get current sponsoring mode, performing lazy migration from legacy storage226 pub fn sponsoring_mode(contract: H160) -> SponsoringModeT {227 <SponsoringMode<T>>::get(contract)228 .or_else(|| {229 <SelfSponsoring<T>>::get(contract).then(|| SponsoringModeT::Allowlisted)230 })231 .unwrap_or_default()232 }233234 /// Reconfigure contract sponsoring mode235 pub fn set_sponsoring_mode(contract: H160, mode: SponsoringModeT) {236 if mode == SponsoringModeT::Disabled {237 <SponsoringMode<T>>::remove(contract);238 } else {239 <SponsoringMode<T>>::insert(contract, mode);240 }241 <SelfSponsoring<T>>::remove(contract)242 }243244 /// Set duration between two sponsored contract calls245 pub fn set_sponsoring_rate_limit(contract: H160, rate_limit: T::BlockNumber) {246 <SponsoringRateLimit<T>>::insert(contract, rate_limit);247 }248249 /// Is user added to allowlist, or he is owner of specified contract250 pub fn allowed(contract: H160, user: H160) -> bool {251 <Allowlist<T>>::get(&contract, &user) || <Owner<T>>::get(&contract) == user252 }253254 /// Toggle contract allowlist access255 pub fn toggle_allowlist(contract: H160, enabled: bool) {256 <AllowlistEnabled<T>>::insert(contract, enabled)257 }258259 /// Toggle user presence in contract's allowlist260 pub fn toggle_allowed(contract: H160, user: H160, allowed: bool) {261 <Allowlist<T>>::insert(contract, user, allowed);262 }263264 /// Throw error if user is not allowed to reconfigure target contract265 pub fn ensure_owner(contract: H160, user: H160) -> DispatchResult {266 ensure!(<Owner<T>>::get(&contract) == user, Error::<T>::NoPermission);267 Ok(())268 }269 }270}271272/// Available contract sponsoring modes273#[derive(Encode, Decode, PartialEq, TypeInfo, MaxEncodedLen, Default)]274pub enum SponsoringModeT {275 /// Sponsoring is disabled276 #[default]277 Disabled,278 /// Only users from allowlist will be sponsored279 Allowlisted,280 /// All users will be sponsored281 Generous,282}283284impl SponsoringModeT {285 fn from_eth(v: u8) -> Option<Self> {286 Some(match v {287 0 => Self::Disabled,288 1 => Self::Allowlisted,289 2 => Self::Generous,290 _ => return None,291 })292 }293 fn to_eth(self) -> u8 {294 match self {295 SponsoringModeT::Disabled => 0,296 SponsoringModeT::Allowlisted => 1,297 SponsoringModeT::Generous => 2,298 }299 }300}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) */