This repository has been archived by the owner on May 15, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* * First version * * General refactor * * Refactor 2 * * Make pallet Instantiable * * Readability improvements * * Changed rule id and value to parametrized types
- Loading branch information
Showing
14 changed files
with
978 additions
and
30 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
[package] | ||
description = "Ajuna Network pallet used to handle account affiliates" | ||
name = "pallet-ajuna-affiliate" | ||
|
||
authors.workspace = true | ||
edition.workspace = true | ||
homepage.workspace = true | ||
repository.workspace = true | ||
version.workspace = true | ||
|
||
[package.metadata.docs.rs] | ||
targets = [ "x86_64-unknown-linux-gnu" ] | ||
|
||
[dependencies] | ||
# Substrate (wasm) | ||
frame-support = { workspace = true } | ||
frame-system = { workspace = true } | ||
parity-scale-codec = { workspace = true, features = [ "derive", "max-encoded-len" ] } | ||
scale-info = { workspace = true, features = [ "derive" ] } | ||
sp-runtime = { workspace = true } | ||
sp-std = { workspace = true } | ||
|
||
[dev-dependencies] | ||
pallet-balances = { workspace = true } | ||
sp-io = { workspace = true } | ||
|
||
[features] | ||
default = [ "std" ] | ||
std = [ | ||
"parity-scale-codec/std", | ||
"scale-info/std", | ||
"frame-support/std", | ||
"frame-system/std", | ||
"pallet-balances/std", | ||
"sp-runtime/std", | ||
] | ||
try-runtime = [ "frame-support/try-runtime" ] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
# Ajuna Affiliate Pallet | ||
|
||
This pallet is used to link specific calls from other pallets to an affiliate | ||
program in which one account can refer others. This referral then helps the referrer by | ||
receiving a part of the fees that would go to the treasury, thereby | ||
incentivizing people to invite other people. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,269 @@ | ||
// Ajuna Node | ||
// Copyright (C) 2022 BlogaTech AG | ||
|
||
// This program is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU Affero General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
|
||
// This program is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU Affero General Public License for more details. | ||
|
||
// You should have received a copy of the GNU Affero General Public License | ||
// along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
#![cfg_attr(not(feature = "std"), no_std)] | ||
|
||
pub use pallet::*; | ||
|
||
#[cfg(test)] | ||
mod mock; | ||
|
||
#[cfg(test)] | ||
mod tests; | ||
|
||
pub mod traits; | ||
|
||
use frame_support::pallet_prelude::*; | ||
|
||
use traits::*; | ||
|
||
#[frame_support::pallet] | ||
pub mod pallet { | ||
use super::*; | ||
use sp_runtime::ArithmeticError; | ||
|
||
pub type AffiliatedAccountsOf<T, I> = | ||
BoundedVec<<T as frame_system::Config>::AccountId, <T as Config<I>>::AffiliateMaxLevel>; | ||
|
||
pub type AccountIdFor<T> = <T as frame_system::Config>::AccountId; | ||
|
||
/// The current storage version. | ||
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); | ||
|
||
#[pallet::pallet] | ||
#[pallet::storage_version(STORAGE_VERSION)] | ||
pub struct Pallet<T, I = ()>(PhantomData<(T, I)>); | ||
|
||
#[pallet::config] | ||
pub trait Config<I: 'static = ()>: frame_system::Config { | ||
/// The overarching event type. | ||
type RuntimeEvent: From<Event<Self, I>> | ||
+ IsType<<Self as frame_system::Config>::RuntimeEvent>; | ||
|
||
/// The rule identifier type at runtime. | ||
type RuleIdentifier: Parameter + MaxEncodedLen; | ||
|
||
/// The rule type at runtime. | ||
type RuntimeRule: Parameter + MaxEncodedLen; | ||
|
||
/// The maximum depth of the affiliate relation chain, | ||
#[pallet::constant] | ||
type AffiliateMaxLevel: Get<u32>; | ||
} | ||
|
||
/// Stores the affiliated accounts from the perspectives of the affiliatee | ||
#[pallet::storage] | ||
#[pallet::getter(fn affiliatees)] | ||
pub type Affiliatees<T: Config<I>, I: 'static = ()> = | ||
StorageMap<_, Identity, T::AccountId, AffiliatedAccountsOf<T, I>, OptionQuery>; | ||
|
||
/// Store affiliators aka accounts that have affilatees and earn rewards from them. | ||
/// Such accounts can't be affiliatees anymore. | ||
#[pallet::storage] | ||
#[pallet::getter(fn affiliators)] | ||
pub type Affiliators<T: Config<I>, I: 'static = ()> = | ||
StorageMap<_, Identity, T::AccountId, AffiliatorState, ValueQuery>; | ||
|
||
/// Stores the affiliate logic rules | ||
#[pallet::storage] | ||
pub type AffiliateRules<T: Config<I>, I: 'static = ()> = | ||
StorageMap<_, Blake2_128Concat, T::RuleIdentifier, T::RuntimeRule, OptionQuery>; | ||
|
||
#[pallet::event] | ||
#[pallet::generate_deposit(pub(super) fn deposit_event)] | ||
pub enum Event<T: Config<I>, I: 'static = ()> { | ||
/// An organizer has been set. | ||
OrganizerSet { | ||
organizer: T::AccountId, | ||
}, | ||
AccountAffiliated { | ||
account: T::AccountId, | ||
to: T::AccountId, | ||
}, | ||
RuleAdded { | ||
rule_id: T::RuleIdentifier, | ||
}, | ||
RuleCleared { | ||
rule_id: T::RuleIdentifier, | ||
}, | ||
} | ||
|
||
#[pallet::error] | ||
pub enum Error<T, I = ()> { | ||
/// There is no account set as the organizer | ||
OrganizerNotSet, | ||
/// An account cannot affiliate itself | ||
CannotAffiliateSelf, | ||
/// The account is not allowed to receive affiliates | ||
TargetAccountIsNotAffiliatable, | ||
/// This account has reached the affiliate limit | ||
CannotAffiliateMoreAccounts, | ||
/// This account has already been affiliated by another affiliator | ||
CannotAffiliateAlreadyAffiliatedAccount, | ||
/// This account is already an affiliator, so it cannot affiliate to another account | ||
CannotAffiliateToExistingAffiliator, | ||
/// The account is blocked, so it cannot be affiliated to | ||
CannotAffiliateBlocked, | ||
/// The given extrinsic identifier is already paired with an affiliate rule | ||
ExtrinsicAlreadyHasRule, | ||
} | ||
|
||
impl<T: Config<I>, I: 'static> Pallet<T, I> { | ||
fn add_new_affiliate_to( | ||
affiliator: T::AccountId, | ||
affiliatee: T::AccountId, | ||
) -> DispatchResult { | ||
let mut accounts = Affiliatees::<T, I>::take(&affiliator).unwrap_or_default(); | ||
|
||
Self::try_add_account_to(&mut accounts, affiliator.clone())?; | ||
|
||
Affiliatees::<T, I>::insert(affiliatee, accounts); | ||
Affiliators::<T, I>::try_mutate(&affiliator, |state| { | ||
state.affiliates = state | ||
.affiliates | ||
.checked_add(1) | ||
.ok_or(DispatchError::Arithmetic(ArithmeticError::Overflow))?; | ||
|
||
Ok(()) | ||
}) | ||
} | ||
|
||
fn try_add_account_to( | ||
accounts: &mut AffiliatedAccountsOf<T, I>, | ||
account: T::AccountId, | ||
) -> DispatchResult { | ||
if accounts.len() == T::AffiliateMaxLevel::get() as usize { | ||
accounts.pop(); | ||
} | ||
accounts | ||
.try_insert(0, account) | ||
.map_err(|_| Error::<T, I>::CannotAffiliateMoreAccounts.into()) | ||
} | ||
} | ||
|
||
impl<T: Config<I>, I: 'static> AffiliateInspector<AccountIdFor<T>> for Pallet<T, I> { | ||
fn get_affiliator_chain_for(account: &AccountIdFor<T>) -> Option<Vec<AccountIdFor<T>>> { | ||
Affiliatees::<T, I>::get(account).map(|accounts| accounts.into_inner()) | ||
} | ||
|
||
fn get_affiliate_count_for(account: &AccountIdFor<T>) -> u32 { | ||
Affiliators::<T, I>::get(account).affiliates | ||
} | ||
} | ||
|
||
impl<T: Config<I>, I: 'static> AffiliateMutator<AccountIdFor<T>> for Pallet<T, I> { | ||
fn try_mark_account_as_affiliatable(account: &AccountIdFor<T>) -> DispatchResult { | ||
Affiliators::<T, I>::try_mutate(account, |state| { | ||
ensure!( | ||
state.status != AffiliatableStatus::Blocked, | ||
Error::<T, I>::CannotAffiliateBlocked | ||
); | ||
|
||
state.status = AffiliatableStatus::Affiliatable; | ||
|
||
Ok(()) | ||
}) | ||
} | ||
|
||
fn force_mark_account_as_affiliatable(account: &AccountIdFor<T>) { | ||
Affiliators::<T, I>::mutate(account, |state| { | ||
state.status = AffiliatableStatus::Affiliatable; | ||
}); | ||
} | ||
|
||
fn mark_account_as_blocked(account: &AccountIdFor<T>) { | ||
Affiliators::<T, I>::mutate(account, |state| { | ||
state.status = AffiliatableStatus::Blocked; | ||
}); | ||
} | ||
|
||
fn try_add_affiliate_to( | ||
account: &AccountIdFor<T>, | ||
affiliate: &AccountIdFor<T>, | ||
) -> DispatchResult { | ||
ensure!(account != affiliate, Error::<T, I>::CannotAffiliateSelf); | ||
|
||
let affiliate_state = Affiliators::<T, I>::get(affiliate); | ||
ensure!( | ||
affiliate_state.affiliates == 0, | ||
Error::<T, I>::CannotAffiliateToExistingAffiliator | ||
); | ||
|
||
ensure!( | ||
!Affiliatees::<T, I>::contains_key(affiliate), | ||
Error::<T, I>::CannotAffiliateAlreadyAffiliatedAccount | ||
); | ||
|
||
let affiliator_state = Affiliators::<T, I>::get(account); | ||
ensure!( | ||
affiliator_state.status == AffiliatableStatus::Affiliatable, | ||
Error::<T, I>::TargetAccountIsNotAffiliatable | ||
); | ||
|
||
Self::add_new_affiliate_to(account.clone(), affiliate.clone())?; | ||
|
||
Self::deposit_event(Event::AccountAffiliated { | ||
account: affiliate.clone(), | ||
to: account.clone(), | ||
}); | ||
|
||
Ok(()) | ||
} | ||
|
||
fn try_clear_affiliation_for(account: &AccountIdFor<T>) -> DispatchResult { | ||
Affiliatees::<T, I>::take(account) | ||
.and_then(|mut affiliate_chain| affiliate_chain.pop()) | ||
.map_or_else( | ||
|| Ok(()), | ||
|affiliator| { | ||
Affiliators::<T, I>::try_mutate(&affiliator, |state| { | ||
state.affiliates = state | ||
.affiliates | ||
.checked_sub(1) | ||
.ok_or(DispatchError::Arithmetic(ArithmeticError::Underflow))?; | ||
|
||
Ok(()) | ||
}) | ||
}, | ||
) | ||
} | ||
} | ||
|
||
impl<T: Config<I>, I: 'static> RuleInspector<T::RuleIdentifier, T::RuntimeRule> for Pallet<T, I> { | ||
fn get_rule_for(rule_id: T::RuleIdentifier) -> Option<T::RuntimeRule> { | ||
AffiliateRules::<T, I>::get(rule_id) | ||
} | ||
} | ||
|
||
impl<T: Config<I>, I: 'static> RuleMutator<T::RuleIdentifier, T::RuntimeRule> for Pallet<T, I> { | ||
fn try_add_rule_for(rule_id: T::RuleIdentifier, rule: T::RuntimeRule) -> DispatchResult { | ||
ensure!( | ||
!AffiliateRules::<T, I>::contains_key(rule_id.clone()), | ||
Error::<T, I>::ExtrinsicAlreadyHasRule | ||
); | ||
AffiliateRules::<T, I>::insert(rule_id.clone(), rule); | ||
Self::deposit_event(Event::RuleAdded { rule_id }); | ||
|
||
Ok(()) | ||
} | ||
|
||
fn clear_rule_for(rule_id: T::RuleIdentifier) { | ||
AffiliateRules::<T, I>::remove(rule_id.clone()); | ||
|
||
Self::deposit_event(Event::RuleCleared { rule_id }); | ||
} | ||
} | ||
} |
Oops, something went wrong.