#![cfg_attr(not(feature = "std"), no_std)]
/// Edit this file to define custom logic or remove it if it is not needed.
/// Learn more about FRAME and the core library of Substrate FRAME pallets:
/// <https://docs.substrate.io/reference/frame-pallets/>
pub use pallet::*;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
use frame_support::{
pallet_prelude::*,
sp_runtime::traits::Zero,
traits::{ fungibles, fungibles::* },
};
use traits::subaccounts::{ SubAccounts, AccountOrigin };
use frame_system::pallet_prelude::*;
#[frame_support::pallet]
pub mod pallet {
use super::*;
// Type alias used for interaction with fungibles (assets).
// Balance type alias.
pub type AssetBalanceOf<T> =
<<T as Config>::Fungibles as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
/// Asset id type alias.
pub type AssetIdOf<T> =
<<T as Config>::Fungibles as Inspect<<T as frame_system::Config>::AccountId>>::AssetId;
// Account id type alias.
pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
// Subscription Id
pub type SubscriptionId = u64;
// Structure that represents a Subscription when one is created
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct Subscription<T: Config> {
pub owner: AccountIdOf<T>,
pub asset_id: AssetIdOf<T>,
pub price: AssetBalanceOf<T>,
pub title: BoundedVec<u8, T::TitleMaxLength>,
pub text: BoundedVec<u8, T::TextMaxLength>,
pub access_link: BoundedVec<u8, T::AccessLinkMaxLength>,
pub subscriptions_stopped: bool,
}
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config: frame_system::Config {
/// Because this pallet emits events, it depends on the runtime's definition of an event.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// Type to access the Assets Pallet.
type Fungibles: fungibles::Inspect<Self::AccountId> +
fungibles::Mutate<Self::AccountId> +
fungibles::metadata::Inspect<Self::AccountId> +
fungibles::Create<Self::AccountId>;
/// Type to get the maximum title's length.
type TitleMaxLength: Get<u32>;
/// Type to get the maximum text's length.
type TextMaxLength: Get<u32>;
/// Type to get the maximum access link's length.
type AccessLinkMaxLength: Get<u32>;
/// Type to access the sub account pallet.
type SubAccounts: SubAccounts<Self::AccountId, AccountOrigin>;
}
// Stores the Subscription struct: asset_id -> subscription_id -> subscription struct
#[pallet::storage]
#[pallet::getter(fn get_subscriptions)]
pub type Subscriptions<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
AssetIdOf<T>,
Blake2_128Concat,
SubscriptionId,
Subscription<T>,
OptionQuery
>;
// Stores the subscribers of the Subscription: asset_id -> subscription_id -> account_id -> bool
#[pallet::storage]
#[pallet::getter(fn get_subscribers)]
pub type Subscribers<T: Config> = StorageNMap<
_,
(
NMapKey<Blake2_128Concat, AssetIdOf<T>>,
NMapKey<Blake2_128Concat, SubscriptionId>,
NMapKey<Blake2_128Concat, AccountIdOf<T>>,
),
bool,
OptionQuery
>;
// Stores the next subscription_id: asset_id -> subscription_id
#[pallet::storage]
#[pallet::getter(fn get_next_subscription_id)]
pub type NextSubscriptionId<T: Config> = StorageMap<
_,
Blake2_128Concat,
AssetIdOf<T>,
SubscriptionId,
ValueQuery
>;
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
SubscriptionCreated {
owner: AccountIdOf<T>,
asset_id: AssetIdOf<T>,
subscription_id: SubscriptionId,
price: AssetBalanceOf<T>,
title: BoundedVec<u8, T::TitleMaxLength>,
},
SubscriptionUpdated {
owner: AccountIdOf<T>,
asset_id: AssetIdOf<T>,
subscription_id: SubscriptionId,
title: BoundedVec<u8, T::TitleMaxLength>,
text: BoundedVec<u8, T::TextMaxLength>,
},
SubscriptionStopped {
owner: AccountIdOf<T>,
asset_id: AssetIdOf<T>,
subscription_id: SubscriptionId,
},
SubscriptionRestarted {
owner: AccountIdOf<T>,
asset_id: AssetIdOf<T>,
subscription_id: SubscriptionId,
},
UserSubscribed {
who: AccountIdOf<T>,
asset_id: AssetIdOf<T>,
subscription_id: SubscriptionId,
},
UserUnsubscribed {
who: AccountIdOf<T>,
asset_id: AssetIdOf<T>,
subscription_id: SubscriptionId,
},
}
// Errors inform users that something went wrong.
#[pallet::error]
pub enum Error<T> {
// Asset doesn't exist.
AssetDoesNotExist,
// Subscription price must be more than zero.
PriceMustNotBeZero,
// Subscription doesn't exist.
SubscriptionDoesNotExist,
// User doens't have permission to change something in the subscription.
DontHavePermission,
// Subscription is already stopped.
SubscriptionAlreadyStopped,
// Subscription is not stopped.
SubscriptionIsNotStopped,
// When the user try to be a subscriber but the subscription is stopped.
SubscriptionIsStopped,
// User is already a subscriber.
AlreadySubscriber,
// User isn't a subscriber.
NotSubscriber,
// User doesn't have enough balance to pay the subscription
InsuffientBalance,
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight({ 0 })]
pub fn create_subscription(
origin: OriginFor<T>,
asset_id: AssetIdOf<T>,
price: AssetBalanceOf<T>,
title: BoundedVec<u8, T::TitleMaxLength>,
text: BoundedVec<u8, T::TextMaxLength>,
access_link: BoundedVec<u8, T::AccessLinkMaxLength>
) -> DispatchResult {
let mut who = ensure_signed(origin)?;
// Mutate the origin to transfer funds from the main account
who = T::SubAccounts::get_main_account(who)?;
ensure!(T::Fungibles::asset_exists(asset_id.clone()), Error::<T>::AssetDoesNotExist);
ensure!(!price.is_zero(), Error::<T>::PriceMustNotBeZero);
let new_subscription = Subscription::<T> {
owner: who.clone(),
asset_id: asset_id.clone(),
price: price.clone(),
title: title.clone(),
text: text.clone(),
access_link: access_link.clone(),
subscriptions_stopped: false,
};
let subscription_id = NextSubscriptionId::<T>::get(&asset_id);
Subscriptions::<T>::insert(&asset_id, &subscription_id, new_subscription);
let new_subscription_id = subscription_id + 1;
NextSubscriptionId::<T>::insert(&asset_id, new_subscription_id);
Self::deposit_event(Event::SubscriptionCreated {
owner: who,
asset_id,
subscription_id,
price,
title,
});
Ok(())
}
#[pallet::call_index(1)]
#[pallet::weight({ 0 })]
pub fn update_subscription(
origin: OriginFor<T>,
asset_id: AssetIdOf<T>,
subscription_id: SubscriptionId,
title: BoundedVec<u8, T::TitleMaxLength>,
text: BoundedVec<u8, T::TextMaxLength>,
access_link: BoundedVec<u8, T::AccessLinkMaxLength>
) -> DispatchResult {
let mut who = ensure_signed(origin)?;
// Mutate the origin to transfer funds from the main account
who = T::SubAccounts::get_main_account(who)?;
ensure!(T::Fungibles::asset_exists(asset_id.clone()), Error::<T>::AssetDoesNotExist);
Subscriptions::<T>::mutate(
&asset_id,
subscription_id,
|mut subscription| -> DispatchResult {
if let Some(subscription) = &mut subscription {
ensure!(subscription.owner == who, Error::<T>::DontHavePermission);
subscription.title = title.clone();
subscription.text = text.clone();
subscription.access_link = access_link.clone();
Ok(())
} else {
Err(Error::<T>::SubscriptionDoesNotExist.into())
}
}
)?;
Self::deposit_event(Event::SubscriptionUpdated {
owner: who,
asset_id,
subscription_id,
title,
text,
});
Ok(())
}
#[pallet::call_index(2)]
#[pallet::weight({ 0 })]
pub fn stop_subscription(
origin: OriginFor<T>,
asset_id: AssetIdOf<T>,
subscription_id: SubscriptionId
) -> DispatchResult {
let mut who = ensure_signed(origin)?;
// Mutate the origin to transfer funds from the main account
who = T::SubAccounts::get_main_account(who)?;
ensure!(T::Fungibles::asset_exists(asset_id.clone()), Error::<T>::AssetDoesNotExist);
Subscriptions::<T>::mutate(
&asset_id,
subscription_id,
|mut subscription| -> DispatchResult {
if let Some(subscription) = &mut subscription {
ensure!(subscription.owner == who, Error::<T>::DontHavePermission);
ensure!(
subscription.subscriptions_stopped == false,
Error::<T>::SubscriptionAlreadyStopped
);
subscription.subscriptions_stopped = true;
Ok(())
} else {
Err(Error::<T>::SubscriptionDoesNotExist.into())
}
}
)?;
Self::deposit_event(Event::SubscriptionStopped {
owner: who,
asset_id,
subscription_id,
});
Ok(())
}
#[pallet::call_index(3)]
#[pallet::weight({ 0 })]
pub fn restart_subscription(
origin: OriginFor<T>,
asset_id: AssetIdOf<T>,
subscription_id: SubscriptionId
) -> DispatchResult {
let mut who = ensure_signed(origin)?;
// Mutate the origin to transfer funds from the main account
who = T::SubAccounts::get_main_account(who)?;
ensure!(T::Fungibles::asset_exists(asset_id.clone()), Error::<T>::AssetDoesNotExist);
Subscriptions::<T>::mutate(
&asset_id,
subscription_id,
|mut subscription| -> DispatchResult {
if let Some(subscription) = &mut subscription {
ensure!(subscription.owner == who, Error::<T>::DontHavePermission);
ensure!(
subscription.subscriptions_stopped == true,
Error::<T>::SubscriptionIsNotStopped
);
subscription.subscriptions_stopped = false;
Ok(())
} else {
Err(Error::<T>::SubscriptionDoesNotExist.into())
}
}
)?;
Self::deposit_event(Event::SubscriptionRestarted {
owner: who,
asset_id,
subscription_id,
});
Ok(())
}
#[pallet::call_index(4)]
#[pallet::weight({ 0 })]
pub fn subscribe(
origin: OriginFor<T>,
asset_id: AssetIdOf<T>,
subscription_id: SubscriptionId
) -> DispatchResult {
let mut who = ensure_signed(origin)?;
// Mutate the origin to transfer funds from the main account
who = T::SubAccounts::get_main_account(who)?;
ensure!(T::Fungibles::asset_exists(asset_id.clone()), Error::<T>::AssetDoesNotExist);
Subscriptions::<T>::mutate(
&asset_id,
&subscription_id,
|subscription| -> DispatchResult {
if let Some(subscription) = subscription {
ensure!(
!subscription.subscriptions_stopped,
Error::<T>::SubscriptionIsStopped
);
let who_balance =
<<T as Config>::Fungibles as fungibles::Inspect<<T as frame_system::Config>::AccountId>>::balance(
asset_id.clone(),
&who
);
ensure!(who_balance >= subscription.price, Error::<T>::InsuffientBalance);
Ok(())
} else {
Err(Error::<T>::SubscriptionDoesNotExist.into())
}
}
)?;
Subscribers::<T>::mutate(
(&asset_id, &subscription_id, &who),
|is_subscriber| -> DispatchResult {
if is_subscriber.is_none() {
*is_subscriber = Some(true);
Ok(())
} else {
Err(Error::<T>::AlreadySubscriber.into())
}
}
)?;
Self::deposit_event(Event::UserSubscribed {
who,
asset_id,
subscription_id,
});
Ok(())
}
#[pallet::call_index(5)]
#[pallet::weight({ 0 })]
pub fn unsubscribe(
origin: OriginFor<T>,
asset_id: AssetIdOf<T>,
subscription_id: SubscriptionId
) -> DispatchResult {
let mut who = ensure_signed(origin)?;
// Mutate the origin to transfer funds from the main account
who = T::SubAccounts::get_main_account(who)?;
ensure!(T::Fungibles::asset_exists(asset_id.clone()), Error::<T>::AssetDoesNotExist);
Subscribers::<T>::mutate(
(&asset_id, &subscription_id, &who),
|is_subscriber| -> DispatchResult {
if is_subscriber.is_some() {
*is_subscriber = None;
Ok(())
} else {
Err(Error::<T>::NotSubscriber.into())
}
}
)?;
Self::deposit_event(Event::UserUnsubscribed {
who,
asset_id,
subscription_id,
});
Ok(())
}
}
}