Skip to content
This repository has been archived by the owner on Sep 22, 2022. It is now read-only.

Commit

Permalink
EligibleAccountsStrategy
Browse files Browse the repository at this point in the history
  • Loading branch information
TarekkMA committed Apr 5, 2022
1 parent 6589fef commit 6020670
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 36 deletions.
9 changes: 7 additions & 2 deletions pallets/free-calls/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,17 @@ pub mod pallet {
type WeightInfo: WeightInfo;

/// A calculation strategy to convert locked tokens info to a max quota per largest window.
type MaxQuotaCalculationStrategy: MaxQuotaCalculationStrategy<Self::BlockNumber, BalanceOf<Self>>;
type MaxQuotaCalculationStrategy: MaxQuotaCalculationStrategy<Self::AccountId, Self::BlockNumber, BalanceOf<Self>>;

/// Maximum number of accounts that can be added as eligible at a time.
//TODO: remove this after we integrate locking tokens
#[pallet::constant]
type AccountsSetLimit: Get<u32>;

/// Amount of free quota granted to eligible accounts.
//TODO: remove this after we integrate locking tokens
#[pallet::constant]
type FreeQuotaPerEligibleAccount: Get<NumberOfCalls>;
}

/// Keeps track of each windows usage for each consumer.
Expand Down Expand Up @@ -233,7 +238,7 @@ pub mod pallet {
}

let locked_info = <LockedInfoByAccount<T>>::get(consumer.clone());
let max_quota = match T::MaxQuotaCalculationStrategy::calculate(current_block, locked_info) {
let max_quota = match T::MaxQuotaCalculationStrategy::calculate(consumer.clone(), current_block, locked_info) {
Some(max_quota) if max_quota > 0 => max_quota,
_ => return None,
};
Expand Down
17 changes: 13 additions & 4 deletions pallets/free-calls/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,8 @@ impl test_pallet::Config for Test {
type CallFilterFn = fn(&Call) -> bool;
static DEFAULT_CALL_FILTER_FN: CallFilterFn = |_| true;

type QuotaCalculationFn<T> = fn(<T as frame_system::Config>::BlockNumber, Option<LockedInfoOf<T>>) -> Option<NumberOfCalls>;
static DEFAULT_QUOTA_CALCULATION_FN: QuotaCalculationFn<Test> = |current_block, locked_info| {
type QuotaCalculationFn<T> = fn(<T as frame_system::Config>::AccountId, <T as frame_system::Config>::BlockNumber, Option<LockedInfoOf<T>>) -> Option<NumberOfCalls>;
static DEFAULT_QUOTA_CALCULATION_FN: QuotaCalculationFn<Test> = |consumer, current_block, locked_info| {
return Some(10);
};

Expand Down Expand Up @@ -155,15 +155,23 @@ impl Contains<Call> for TestCallFilter {
}

pub struct TestQuotaCalculation;
impl pallet_free_calls::quota_strategy::MaxQuotaCalculationStrategy<<Test as frame_system::Config>::BlockNumber, BalanceOf<Test>> for TestQuotaCalculation {
impl pallet_free_calls::quota_strategy::MaxQuotaCalculationStrategy<
<Test as frame_system::Config>::AccountId,
<Test as frame_system::Config>::BlockNumber,
BalanceOf<Test>> for TestQuotaCalculation {
fn calculate(
consumer: <Test as frame_system::Config>::AccountId,
current_block: <Test as frame_system::Config>::BlockNumber,
locked_info: Option<LockedInfoOf<Test>>
) -> Option<NumberOfCalls> {
QUOTA_CALCULATION.with(|strategy| strategy.borrow()(current_block, locked_info))
QUOTA_CALCULATION.with(|strategy| strategy.borrow()(consumer, current_block, locked_info))
}
}

parameter_types! {
pub const FreeQuotaPerEligibleAccount: NumberOfCalls = 100;
}

impl pallet_free_calls::Config for Test {
type Event = Event;
type Call = Call;
Expand All @@ -172,6 +180,7 @@ impl pallet_free_calls::Config for Test {
type WeightInfo = ();
type MaxQuotaCalculationStrategy = TestQuotaCalculation;
type AccountsSetLimit = AccountsSetLimit;
type FreeQuotaPerEligibleAccount = FreeQuotaPerEligibleAccount;
}

pub struct ExtBuilder {
Expand Down
76 changes: 58 additions & 18 deletions pallets/free-calls/src/quota_strategy.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,46 @@
use frame_support::pallet_prelude::Get;
use crate::quota::NumberOfCalls;
use pallet_locker_mirror::LockedInfo;
use pallet_locker_mirror::{BalanceOf, LockedInfo, LockedInfoOf};
use sp_std::cmp::min;
use sp_std::convert::TryInto;
use sp_std::marker::PhantomData;
use subsocial_primitives as primitives;
use subsocial_primitives::currency;
use subsocial_primitives::{AccountId, Balance, BlockNumber, currency};
use subsocial_primitives::time::*;
use crate::{Config, EligibleAccounts};

/// A strategy used to calculate the MaxQuota of a consumer.
pub trait MaxQuotaCalculationStrategy<BlockNumber, Balance> {
fn calculate(current_block: BlockNumber, locked_info: Option<LockedInfo<BlockNumber, Balance>>) -> Option<NumberOfCalls>;
pub trait MaxQuotaCalculationStrategy<AccountId, BlockNumber, Balance> {
fn calculate(
consumer: AccountId,
current_block: BlockNumber,
locked_info: Option<LockedInfo<BlockNumber, Balance>>,
) -> Option<NumberOfCalls>;
}


/// An implementation of the MaxQuotaCalculationStrategy that grants accounts in the eligible accounts
/// storage a fixed amount of free calls as max quota.
pub struct EligibleAccountsStrategy<T: Config>(PhantomData<T>);

impl<T: Config> Default for EligibleAccountsStrategy<T> {
fn default() -> Self {
Self(PhantomData)
}
}

impl<T: Config> MaxQuotaCalculationStrategy<T::AccountId, T::BlockNumber, BalanceOf<T>> for EligibleAccountsStrategy<T> {
fn calculate(
consumer: T::AccountId,
_current_block: T::BlockNumber,
_locked_info: Option<LockedInfoOf<T>>,
) -> Option<NumberOfCalls> {
if EligibleAccounts::<T>::get(consumer) {
Some(T::FreeQuotaPerEligibleAccount::get())
} else {
None
}
}
}

// TODO: try to find a better way to calculate it based on the circulating supply
Expand Down Expand Up @@ -44,21 +75,28 @@ const FREE_CALLS_PER_SUB: NumberOfCalls = 10;
/// | 12 months | 360 | 100% |
/// +-------------+------+---------+
/// ```
pub struct FreeCallsCalculationStrategy<BlockNumber, Balance>(
PhantomData<BlockNumber>,
PhantomData<Balance>,
pub struct FreeCallsCalculationStrategy<AccountId, BlockNumber, Balance>(
PhantomData<(AccountId, BlockNumber, Balance)>,
);

impl<BlockNumber, Balance> Default for FreeCallsCalculationStrategy<BlockNumber, Balance> {
impl<AccountId, BlockNumber, Balance> Default for FreeCallsCalculationStrategy<AccountId, BlockNumber, Balance> {
fn default() -> Self {
Self(PhantomData, PhantomData)
Self(PhantomData)
}
}

impl MaxQuotaCalculationStrategy<primitives::BlockNumber, primitives::Balance>
for FreeCallsCalculationStrategy<primitives::BlockNumber, primitives::Balance>
impl MaxQuotaCalculationStrategy<
primitives::AccountId,
primitives::BlockNumber,
primitives::Balance,
> for FreeCallsCalculationStrategy<
primitives::AccountId,
primitives::BlockNumber,
primitives::Balance,
>
{
fn calculate(
_consumer: primitives::AccountId,
current_block: primitives::BlockNumber,
locked_info: Option<LockedInfo<primitives::BlockNumber, primitives::Balance>>,
) -> Option<NumberOfCalls> {
Expand Down Expand Up @@ -114,6 +152,7 @@ fn get_utilization_percent(time_locked: primitives::BlockNumber) -> u64 {

#[cfg(test)]
mod tests {
use frame_benchmarking::account;
use pallet_locker_mirror::{LockedInfo, LockedInfoOf};
use subsocial_primitives::{*, currency::*, time::*};
use crate::quota::NumberOfCalls;
Expand Down Expand Up @@ -208,37 +247,38 @@ mod tests {
};

///////////////////////////////////////
let consumer = || account("Dummy Consumer", 0, 0);

// Expect none if no locked_info provided
assert_eq!(
FreeCallsCalculationStrategy::calculate(current_block, None),
FreeCallsCalculationStrategy::calculate(consumer(), current_block, None),
None,
);
assert_eq!(
FreeCallsCalculationStrategy::calculate(before_current_block, None),
FreeCallsCalculationStrategy::calculate(consumer(),before_current_block, None),
None,
);
assert_eq!(
FreeCallsCalculationStrategy::calculate(after_current_block, None),
FreeCallsCalculationStrategy::calculate(consumer(),after_current_block, None),
None,
);

assert_eq!(
FreeCallsCalculationStrategy::calculate(current_block, Some(locked_info)),
FreeCallsCalculationStrategy::calculate(consumer(),current_block, Some(locked_info)),
expected_quota,
);

// test expiration
assert_eq!(
FreeCallsCalculationStrategy::calculate(current_block, Some(locked_info_just_expired)),
FreeCallsCalculationStrategy::calculate(consumer(),current_block, Some(locked_info_just_expired)),
None,
);
assert_eq!(
FreeCallsCalculationStrategy::calculate(current_block, Some(locked_info_expired)),
FreeCallsCalculationStrategy::calculate(consumer(),current_block, Some(locked_info_expired)),
None,
);
assert_eq!(
FreeCallsCalculationStrategy::calculate(current_block, Some(locked_info_not_yet_expired)),
FreeCallsCalculationStrategy::calculate(consumer(),current_block, Some(locked_info_not_yet_expired)),
expected_quota,
);

Expand Down
27 changes: 17 additions & 10 deletions pallets/free-calls/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ fn dummy() {
.windows_config(vec![
WindowConfig::new(1, max_quota_percentage!(100)),
])
.quota_calculation(|_, _| 100.into())
.quota_calculation(|_, _, _| 100.into())
.build().execute_with(|| {
let consumer: AccountId = account("Consumer", 2, 1);

Expand All @@ -331,7 +331,7 @@ fn dummy() {
.windows_config(vec![
WindowConfig::new(1, max_quota_percentage!(100)),
])
.quota_calculation(|_, _| None)
.quota_calculation(|_, _, _| None)
.build().execute_with(|| {
let consumer: AccountId = account("Consumer", 2, 1);

Expand All @@ -346,16 +346,19 @@ fn dummy() {
#[test]
fn locked_token_info_and_current_block_number_will_be_passed_to_the_calculation_strategy() {
thread_local! {
static CAPTURED_CONSUMER: RefCell<Option<AccountId>> = RefCell::new(None);
static CAPTURED_LOCKED_TOKENS: RefCell<Option<LockedInfoOf<Test>>> = RefCell::new(None);
static CAPTURED_CURRENT_BLOCK: RefCell<Option<BlockNumber>> = RefCell::new(None);
}

let get_captured_consumer = || CAPTURED_CONSUMER.with(|x| x.borrow().clone());
let get_captured_locked_tokens = || CAPTURED_LOCKED_TOKENS.with(|x| x.borrow().clone());
let get_captured_current_block = || CAPTURED_CURRENT_BLOCK.with(|x| x.borrow().clone());

ExtBuilder::default()
.windows_config(vec![WindowConfig::new(1, max_quota_percentage!(100))])
.quota_calculation(|current_block, locked_tokens| {
.quota_calculation(|consumer, current_block, locked_tokens| {
CAPTURED_CONSUMER.with(|x| *x.borrow_mut() = Some(consumer));
CAPTURED_LOCKED_TOKENS.with(|x| *x.borrow_mut() = locked_tokens.clone());
CAPTURED_CURRENT_BLOCK.with(|x| *x.borrow_mut() = Some(current_block));

Expand All @@ -365,13 +368,15 @@ fn locked_token_info_and_current_block_number_will_be_passed_to_the_calculation_
.execute_with(|| {
let consumer: AccountId = account("Consumer", 0, 0);

assert_eq!(get_captured_consumer(), None);
assert_eq!(get_captured_locked_tokens(), None);
assert_eq!(get_captured_current_block(), None);

TestUtils::set_block_number(11);

TestUtils::assert_try_free_call_works(consumer.clone(), Declined(OutOfQuota));

assert_eq!(get_captured_consumer(), Some(consumer.clone()));
assert_eq!(get_captured_locked_tokens(), None);
assert_eq!(get_captured_current_block(), Some(11));

Expand All @@ -385,18 +390,20 @@ fn locked_token_info_and_current_block_number_will_be_passed_to_the_calculation_

TestUtils::assert_try_free_call_works(consumer.clone(), Granted(Succeeded));

assert_eq!(get_captured_consumer(), Some(consumer.clone()));
assert_eq!(get_captured_locked_tokens(), Some(locked_info.clone()));
assert_eq!(get_captured_current_block(), Some(55));


//// change locked info and try again
//// change locked info and try again, and change consumer

let new_locked_info = TestUtils::random_locked_info();
<LockedInfoByAccount<Test>>::insert(consumer.clone(), new_locked_info.clone());

// Block number is still 55 and quota is 1
TestUtils::assert_try_free_call_works(consumer.clone(), Declined(OutOfQuota));

assert_eq!(get_captured_consumer(), Some(consumer.clone()));
assert_eq!(get_captured_locked_tokens(), Some(new_locked_info));
assert_ne!(get_captured_locked_tokens(), Some(locked_info));
assert_eq!(get_captured_current_block(), Some(55));
Expand All @@ -414,7 +421,7 @@ fn boxed_call_will_be_passed_to_the_call_filter() {

ExtBuilder::default()
.windows_config(vec![WindowConfig::new(1, max_quota_percentage!(100))])
.quota_calculation(|_, _| Some(10))
.quota_calculation(|_, _, _| Some(10))
.call_filter(|call| {
CAPTURED_CALL.with(|x| *x.borrow_mut() = call.clone().into());
true
Expand Down Expand Up @@ -473,7 +480,7 @@ fn denied_if_call_filter_returns_false() {
ExtBuilder::default()
.windows_config(vec![WindowConfig::new(1, max_quota_percentage!(100))])
.call_filter(|_| ALLOW_CALLS.with(|b| b.borrow().clone()))
.quota_calculation(|_,_| Some(1000))
.quota_calculation(|_, _,_| Some(1000))
.build()
.execute_with(|| {
let consumer: AccountId = account("Consumer", 0, 0);
Expand Down Expand Up @@ -590,7 +597,7 @@ fn donot_exceed_the_allowed_quota_with_one_window() {
.windows_config(vec![
WindowConfig::new(20, max_quota_percentage!(100)),
])
.quota_calculation(|_, _| 5.into())
.quota_calculation(|_, _, _| 5.into())
.build()
.execute_with(|| {
let storage = TestUtils::capture_stats_storage();
Expand Down Expand Up @@ -631,7 +638,7 @@ fn donot_exceed_the_allowed_quota_with_one_window() {
fn consumer_with_quota_but_no_previous_usages() {
ExtBuilder::default()
.windows_config(vec![ WindowConfig::new(100, max_quota_percentage!(100)) ])
.quota_calculation(|_, _| Some(100))
.quota_calculation(|_, _, _| Some(100))
.build()
.execute_with(|| {
TestUtils::set_block_number(315);
Expand Down Expand Up @@ -677,7 +684,7 @@ fn consumer_with_quota_but_no_previous_usages() {
fn consumer_with_quota_and_have_previous_usages() {
ExtBuilder::default()
.windows_config(vec![ WindowConfig::new(50, max_quota_percentage!(100)) ])
.quota_calculation(|_, _| Some(34))
.quota_calculation(|_, _, _| Some(34))
.build()
.execute_with(|| {
let consumer: AccountId = account("Consumer", 0, 0);
Expand Down Expand Up @@ -738,7 +745,7 @@ fn consumer_with_quota_and_have_previous_usages() {
#[test]
fn testing_scenario_1() {
ExtBuilder::default()
.quota_calculation(|_,_| Some(55))
.quota_calculation(|_, _,_| Some(55))
.windows_config(vec![
WindowConfig::new(100, max_quota_percentage!(100)),
WindowConfig::new(20, max_quota_percentage!(33.33333)),
Expand Down
7 changes: 5 additions & 2 deletions runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ mod free_calls;

use subsocial_primitives::{currency::*, time::*};
use pallet_free_calls::config::{RateLimiterConfig, WindowConfig};
use pallet_free_calls::quota_strategy::FreeCallsCalculationStrategy;
use pallet_free_calls::quota_strategy::{EligibleAccountsStrategy, FreeCallsCalculationStrategy};
use pallet_free_calls::quota::NumberOfCalls;

/// Opaque types. These are used by the CLI to instantiate machinery that don't need to know
/// the specifics of the runtime. They can then be made to be agnostic over specific formats
Expand Down Expand Up @@ -448,6 +449,7 @@ parameter_types! {
FREE_CALLS_WINDOWS_CONFIGS.to_vec(),
FREE_CALLS_CONFIG_HASH,
);
pub const FreeQuotaPerEligibleAccount: NumberOfCalls = 100;
}

impl pallet_free_calls::Config for Runtime {
Expand All @@ -456,8 +458,9 @@ impl pallet_free_calls::Config for Runtime {
type RateLimiterConfig = RateLimiterConfigPram;
type CallFilter = FreeCallsFilter;
type WeightInfo = ();
type MaxQuotaCalculationStrategy = FreeCallsCalculationStrategy<BlockNumber, Balance>;
type MaxQuotaCalculationStrategy = EligibleAccountsStrategy<Self>;
type AccountsSetLimit = AccountsSetLimit;
type FreeQuotaPerEligibleAccount = FreeQuotaPerEligibleAccount;
}

impl pallet_locker_mirror::Config for Runtime {
Expand Down

0 comments on commit 6020670

Please sign in to comment.