#![cfg_attr(not(feature = "std"), no_std)]
use frame_support::pallet_prelude::*;
/// 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::*;
use scale_info::prelude::vec::Vec;
use frame_support::{
traits::{
tokens::fungibles::{ Balanced, Inspect },
Currency,
LockableCurrency,
ReservableCurrency,
},
};
use frame_system::pallet_prelude::BlockNumberFor;
use frame_support::traits::fungibles::roles::Inspect as Inspector;
use parity_scale_codec::{ Encode, Decode, HasCompact };
use sp_runtime::{ traits::{ AtLeast32BitUnsigned, Saturating, Zero } };
use traits::{ newsfeed::NewsfeedInterface, newsfeed::StatusType};
use sp_std::vec;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod weights;
//pub use weights::WeightInfo;
/// Balance type alias.
pub(crate) type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
/// Asset id type alias.
pub(crate) type AssetIdOf<T> = <T as Config>::AssetId;
//<<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;
/// Status details type alias.
pub(crate) type StatusDetailsA<T> = StatusDetails<
<T as frame_system::Config>::AccountId,
AssetIdOf<T>,
BlockNumberFor<T>,
<T as Config>::StatusIndex,
BalanceOf<T>,
>;
/// Status alias. An status is a vec<u8>.
pub(crate) type Status = Vec<u8>;
/// Details of an Statuss.
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo)]
pub struct StatusDetails<AccountId, AssetId, BlockNumber, StatusIndex, Balance> {
/// Status Id,
pub status_id: StatusIndex,
/// Status type.
pub status_type: StatusType,
/// Account who created this status.
pub created_by: AccountId,
/// Status to be published.
pub status: Vec<u8>,
/// Block number when the status was set.
pub block: BlockNumber,
/// Token id.
pub token_id: AssetId,
/// Is it a reply to another status.
pub is_reply: bool,
/// Optional account Id for follow up.
pub optional_account: Option<AccountId>,
/// Optional amount value
pub optional_amount: Option<Balance>,
/// Second optional amount value
pub optional_amount2: Option<Balance>,
}
// #[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
// pub enum StatusType {
// Post,
// BondReward,
// Buy,
// Sell,
// Stake,
// UnStake,
// Follow,
// }
impl<AccountId: Clone + Eq, AssetId, BlockNumber, StatusIndex, Balance> StatusDetails<
AccountId,
AssetId,
BlockNumber,
StatusIndex,
Balance,
> {
/// Creates a new StatusDetails object.
pub fn new(
status_id: StatusIndex,
status_type: StatusType,
created_by: AccountId,
status: Vec<u8>,
block: BlockNumber,
token_id: AssetId,
is_reply: bool,
optional_account: Option<AccountId>,
optional_amount: Option<Balance>,
optional_amount2: Option<Balance>,
) -> Self {
Self { status_id,
status_type,
created_by,
status,
block,
token_id,
is_reply,
optional_account,
optional_amount,
optional_amount2}
}
}
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_system::pallet_prelude::*;
#[pallet::pallet]
#[pallet::without_storage_info]
pub struct Pallet<T>(_);
/// Configure the pallet by specifying the parameters and types on which it depends.
#[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>;
/// The fungibles instance used for transfers in assets.
/// The Balance type should be the same as in balances pallet.
type Fungibles: Inspect<Self::AccountId, Balance = BalanceOf<Self>, AssetId = Self::AssetId> +
Balanced<Self::AccountId> +
Inspector<Self::AccountId>;
// Currency type for this pallet.
// The Balance type should be the same as in assets pallet.
type Currency: ReservableCurrency<Self::AccountId> +
LockableCurrency<Self::AccountId, Moment = BlockNumberFor<Self>>;
type AssetId: Member
+ Parameter
+ Copy
+ MaybeSerializeDeserialize
+ MaxEncodedLen
+ Default
+ Zero
+ From<u32>;
/// Identifier and index for an status.
type StatusIndex: Member +
Parameter +
AtLeast32BitUnsigned +
Default +
Copy +
HasCompact +
MaybeSerializeDeserialize +
MaxEncodedLen +
TypeInfo;
/// Overarching type of all pallets origins.
type PalletsOrigin: From<frame_system::RawOrigin<Self::AccountId>>;
}
/// The number of status that have been posted so far.
#[pallet::storage]
#[pallet::getter(fn status_count)]
pub type StatusCount<T: Config> = StorageValue<_, T::StatusIndex, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn token_status_count)]
pub type TokenStatusCount<T: Config> = StorageValue<_, T::StatusIndex, ValueQuery>;
/// Main storage.
#[pallet::storage]
#[pallet::getter(fn status_details_of)]
pub(super) type StatusDetailsOf<T: Config> = StorageNMap<
_,
(
NMapKey<Blake2_128Concat, AssetIdOf<T>>,
NMapKey<Blake2_128Concat, T::AccountId>,
NMapKey<Blake2_128Concat, T::StatusIndex>,
),
StatusDetails<T::AccountId, AssetIdOf<T>, BlockNumberFor<T>, T::StatusIndex, BalanceOf<T>>
>;
// double storage map for token statuses
#[pallet::storage]
#[pallet::getter(fn status_of)]
pub(super) type TokenStatusOf<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
AssetIdOf<T>,
Blake2_128Concat,
T::StatusIndex,
StatusDetails<T::AccountId, AssetIdOf<T>, BlockNumberFor<T>, T::StatusIndex, BalanceOf<T>>
>;
// Storage map for status replies
#[pallet::storage]
#[pallet::getter(fn status_replies)]
pub(super) type StatusReplies<T: Config> = StorageMap<
_,
Blake2_128Concat,
T::StatusIndex,
Vec<T::StatusIndex> // try with value query
>;
// Pallets use events to inform users when important changes are made.
// https://docs.substrate.io/main-docs/build/events-errors/
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// An status was created.
StatusCreated {
token_id: AssetIdOf<T>,
creator: T::AccountId,
status_id: T::StatusIndex,
},
/// An status was cancelled.
StatusCancelled {
token_id: AssetIdOf<T>,
creator: T::AccountId,
status_id: T::StatusIndex,
},
/// A token status was created.
TokenStatusCreated {
token_id: AssetIdOf<T>,
creator: T::AccountId,
status_id: T::StatusIndex,
},
/// A token status was cancelled.
TokenStatusCancelled {
token_id: AssetIdOf<T>,
creator: T::AccountId,
status_id: T::StatusIndex,
},
}
// Errors inform users that something went wrong.
#[pallet::error]
pub enum Error<T> {
/// The status index was not found.
StatusNotFound,
/// Only token owner create a token status.
NotTokenOwner,
/// An status should be associated to an existing token.
TokenDoesNotExist,
/// To create an status, should be a token holder.
NotTokenHolder,
/// Reply already added.
ReplyAlreadyAdded,
}
// Dispatchable functions allows users to interact with the pallet and invoke state changes.
// These functions materialize as "extrinsics", which are often compared to transactions.
// Dispatchable functions must be annotated with a weight and must return a DispatchResult.
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Post an Status.
///
/// The dispatch origin for this call must be _Signed_.
///
/// - `status`: The status to be posted.
/// - `token_id`: The token id to be associated with the status.
#[pallet::call_index(2)]
#[pallet::weight({ 0 })]
pub fn post_status(
origin: OriginFor<T>,
status: Vec<u8>,
token_id: AssetIdOf<T>
) -> DispatchResult {
let who = ensure_signed(origin)?;
// Get the blocknumber
let block = <frame_system::Pallet<T>>::block_number();
// Get next status_id from storage.
let mut status_id = StatusCount::<T>::get();
// Increment self by one, saturating.
status_id.saturating_inc();
// Create status details struct.
let status = StatusDetails::new(
status_id,
StatusType::Post,
who.clone(),
status.clone(),
block,
token_id,
false,
None,
None,
None,
);
// Call inner function.
Self::try_create_status(status)?;
// Updates status count.
StatusCount::<T>::put(status_id);
// Emit an event.
Self::deposit_event(Event::StatusCreated { token_id, creator: who, status_id });
Ok(())
}
/// Cancel an Status.
///
/// The dispatch origin of this must be _Signed_.
///
/// - `token_id`: The token id associated with the status.
/// - `status_id`: The status id to be cancelled.
#[pallet::call_index(3)]
#[pallet::weight({ 0 })]
pub fn delete_status(
origin: OriginFor<T>,
token_id: AssetIdOf<T>,
status_id: T::StatusIndex
) -> DispatchResult {
let who = ensure_signed(origin)?;
// Call internal function
Self::try_erase_status(who.clone(), token_id, status_id)?;
// Emit an event.
Self::deposit_event(Event::StatusCancelled { token_id, creator: who, status_id });
Ok(())
}
/// Post token status.
///
/// The dispatch origin of this must be _Signed_.
/// Can only be called by the token owner.
///
/// - `status`: The status to be posted.
/// - `token_id`: The token id to be associated with the status.
#[pallet::call_index(4)]
#[pallet::weight({ 0 })]
pub fn post_token_status(
origin: OriginFor<T>,
status: Vec<u8>,
token_id: AssetIdOf<T>
) -> DispatchResult {
let who = ensure_signed(origin)?;
// Get the blocknumber
let block = <frame_system::Pallet<T>>::block_number();
// Get next status_id from storage.
let mut token_status_id = TokenStatusCount::<T>::get();
// Increment self by one, saturating.
token_status_id.saturating_inc();
// Create status details struct.
let status = StatusDetails::new(
token_status_id,
StatusType::Post,
who.clone(),
status.clone(),
block,
token_id,
false,
None,
None,
None,
);
// Call inner function.
Self::try_create_token_status(status)?;
// Updates status count.
TokenStatusCount::<T>::put(token_status_id);
// Emit an event.
Self::deposit_event(Event::TokenStatusCreated {
token_id,
creator: who,
status_id: token_status_id,
});
Ok(())
}
/// Cancel token status.
///
/// The dispatch origin of this must be _Signed_.
/// Can only be called by the token owner.
///
/// - `token_id`: The token id associated with the status.
/// - `status_id`: The status id to be cancelled.
#[pallet::call_index(5)]
#[pallet::weight({ 0 })]
pub fn delete_token_status(
origin: OriginFor<T>,
token_id: AssetIdOf<T>,
status_id: T::StatusIndex
) -> DispatchResult {
let who = ensure_signed(origin)?;
// Call internal function
Self::try_erase_token_status(who.clone(), token_id, status_id)?;
// Emit an event.
Self::deposit_event(Event::TokenStatusCancelled { token_id, creator: who, status_id });
Ok(())
}
// Reply to an status. The same as posting an status. But adding the status id the
// statusReplies vector of the parent status.
#[pallet::call_index(6)]
#[pallet::weight({ 0 })]
pub fn reply_status(
origin: OriginFor<T>,
status: Vec<u8>,
token_id: AssetIdOf<T>,
parent_status_id: T::StatusIndex
) -> DispatchResult {
let who = ensure_signed(origin)?;
// Get the blocknumber
let block = <frame_system::Pallet<T>>::block_number();
// Get next status_id from storage.
let mut status_id = StatusCount::<T>::get();
// Increment self by one, saturating.
status_id.saturating_inc();
// Create status details struct.
let status = StatusDetails::new(
status_id,
StatusType::Post,
who.clone(),
status.clone(),
block,
token_id,
true,
None,
None,
None,
);
// Call inner function.
Self::try_create_status(status)?;
let replies = StatusReplies::<T>::get(parent_status_id);
// If attendee is none, create a new vector and add attendee. If attendee is some, add
// attendee to vector.
match replies {
None => {
// let mut replies = Vec::new();
// replies.push(status_id);
let replies = vec![status_id];
StatusReplies::<T>::insert(parent_status_id, replies);
}
Some(mut replies) => {
// Ensure attende was not already added
ensure!(!replies.contains(&status_id), Error::<T>::ReplyAlreadyAdded);
replies.push(status_id);
StatusReplies::<T>::insert(parent_status_id, replies);
}
}
// Updates status count.
StatusCount::<T>::put(status_id);
// Emit an event.
Self::deposit_event(Event::StatusCreated { token_id, creator: who, status_id });
Ok(())
}
}
}
impl<T: Config> Pallet<T> {
/// Check if the account is the owner of the token.
fn is_owner(who: T::AccountId, asset_id: AssetIdOf<T>) -> bool {
<T::Fungibles as Inspector<T::AccountId>>
::owner(asset_id)
.map(|owner| owner == who)
.unwrap_or_default()
}
/// Actually create the status.
fn try_create_status(status: StatusDetailsA<T>) -> DispatchResult {
// Getting the token id.
let token_id = status.token_id;
// Getting the status id.
let status_id = status.status_id;
// Ensure currency asset exists.
let total_issuance = <T::Fungibles as Inspect<T::AccountId>>::total_issuance(
token_id
);
ensure!(total_issuance > BalanceOf::<T>::zero(), Error::<T>::TokenDoesNotExist);
// ensure caller is holder of the token.
ensure!(Self::check_balance(&status.created_by, token_id), Error::<T>::NotTokenHolder);
StatusDetailsOf::<T>::insert(
(&status.token_id, &status.created_by, status_id),
status.clone()
);
Ok(())
}
/// Actually erase status.
fn try_erase_status(
who: T::AccountId,
token_id: AssetIdOf<T>,
status_id: T::StatusIndex
) -> DispatchResult {
// Ensure status exists
ensure!(
StatusDetailsOf::<T>::contains_key((&token_id, who.clone(), status_id)),
Error::<T>::StatusNotFound
);
// Remove status. It also ensures the caller is the creator.
<StatusDetailsOf<T>>::remove((token_id, who.clone(), status_id));
Ok(())
}
/// Actually create token status.
fn try_create_token_status(status: StatusDetailsA<T>) -> DispatchResult {
// Getting the token id.
let token_id = status.token_id;
// Getting the status id.
let token_status_id = status.status_id;
// Ensure currency asset exists.
let total_issuance = <T::Fungibles as Inspect<T::AccountId>>::total_issuance(token_id);
ensure!(total_issuance > BalanceOf::<T>::zero(), Error::<T>::TokenDoesNotExist);
// Ensure the caller is the owner of the token.
ensure!(Self::is_owner(status.created_by.clone(), token_id), Error::<T>::NotTokenOwner);
TokenStatusOf::<T>::insert(status.token_id, token_status_id, status);
Ok(())
}
/// Actually elimitate token status.
// TODO: check if this function should return a result.
fn try_erase_token_status(
who: T::AccountId,
token_id: AssetIdOf<T>,
token_status_id: T::StatusIndex
) -> DispatchResult {
// Ensure status exists
ensure!(
TokenStatusOf::<T>::contains_key(token_id, token_status_id),
Error::<T>::StatusNotFound
);
// Ensure currency asset exists.
let total_issuance = <T::Fungibles as Inspect<T::AccountId>>::total_issuance(token_id);
ensure!(total_issuance > BalanceOf::<T>::zero(), Error::<T>::TokenDoesNotExist);
// Ensure the caller is the owner of the token.
ensure!(Self::is_owner(who.clone(), token_id), Error::<T>::NotTokenOwner);
// Remove status.
<TokenStatusOf<T>>::remove(token_id, token_status_id);
Ok(())
}
/// Checks if the user is holding the token.
fn check_balance(who: &T::AccountId, asset_id: AssetIdOf<T>) -> bool {
<T::Fungibles as Inspect<T::AccountId>>::balance(asset_id, who) > BalanceOf::<T>::zero()
}
}
impl<T: Config> NewsfeedInterface<AccountIdOf<T>, StatusType, AssetIdOf<T>, BalanceOf<T>> for Pallet<T> {
/// Post a new status.
fn post_status(who: AccountIdOf<T>, status_type: StatusType, status: Status, token_id: AssetIdOf<T>, optional_account: Option<AccountIdOf<T>>, optional_amount: Option<BalanceOf<T>>, optional_amount2: Option<BalanceOf<T>>) {
// Get the blocknumber
let block = <frame_system::Pallet<T>>::block_number();
// Get next status_id from storage.
let mut status_id = StatusCount::<T>::get();
// Increment self by one, saturating.
status_id.saturating_inc();
// Create status details struct.
let status = StatusDetails::new(
status_id,
status_type,
who.clone(),
status.clone(),
block,
token_id,
false,
optional_account,
optional_amount,
optional_amount2,
);
StatusDetailsOf::<T>::insert(
(status.token_id, status.clone().created_by, status_id),
status
);
// Updates status count.
StatusCount::<T>::put(status_id);
}
}