//! # Papers Pallet
//! <!-- Original author of paragraph: @pablolteixeira
//!
//! ## Overview
//!
//! Pallet contests enables users to create and update papers, including subpapers. Each paper may contain zero or more subpapers.
//!
//! ### Goals
//!
//! The pallet is designed to make the following possible:
//!
//! * Create a paper.
//! * Update a paper.
//! * Create a subpaper.
//! * Update a subpaper.
//!
//! ## Interface
//!
//! ### Permissionless Functions
//!
//! - `create_paper`: This function is used to create a paper with a specified title and text content.
//!
//! - `update_paper`: This function is used to update a paper, allowing for changes to its title or text content.
//!
//! - `create_subpaper`: This function is employed to create a subpaper within a paper.
//!
//! - `update_subpaper`: This function is used to update a subpaper within a paper, allowing for changes to its title or
//! text content.
//!
//!//! Please refer to the [`Call`] enum and its associated variants for documentation on each
//! function.
#![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::{
traits::{
fungibles::{ Create, Inspect, Mutate, metadata::Inspect as MetadataInspect },
UnixTime,
},
dispatch::fmt::Debug,
};
use traits::subaccounts::{ SubAccounts, AccountOrigin };
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
#[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(Fungibles trait implemented there).
type Fungibles: Inspect<Self::AccountId> +
MetadataInspect<Self::AccountId> +
Create<Self::AccountId> +
Mutate<Self::AccountId>;
/// The minimum title length.
#[pallet::constant]
type TitleLimit: Get<u32>;
/// The minimum text length.
#[pallet::constant]
type TextLimit: Get<u32>;
/// Provides an interface to get the actual time in Unix Time.
type TimeProvider: UnixTime;
/// Type to access the sub account pallet.
type SubAccounts: SubAccounts<Self::AccountId, AccountOrigin>;
}
/// Account id type alias.
pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
/// Asset Balance id 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;
/// Paper and subpaper id type alias.
pub type PaperId = u64;
/// Paper type alias to struct implementation.
pub type PaperOf<T> = Paper<T, AssetIdOf<T>, AccountIdOf<T>, PaperId>;
/// SubPaper type alias to struct implementation.
pub type SubPaperOf<T> = SubPaper<T, AssetIdOf<T>, AccountIdOf<T>, PaperId, u64>;
/// This struct represents a paper entry and stores information about a user's paper submission, including the associated
/// asset ID, unique paper ID, user's account address, paper title, paper text, and any other relevant data as defined by
/// the generic type parameters.
#[derive(Encode, Decode, TypeInfo, MaxEncodedLen, Debug, PartialEq, Default)]
#[scale_info(skip_type_params(T))]
pub struct Paper<T: Config, AssetId, AccountId, PaperId> {
pub asset_id: AssetId,
pub paper_id: PaperId,
pub user_address: AccountId,
pub title: BoundedVec<u8, T::TitleLimit>,
pub text: BoundedVec<u8, T::TextLimit>,
}
impl<T: Config> Clone for Paper<T, AssetIdOf<T>, AccountIdOf<T>, PaperId> {
fn clone(&self) -> Self {
Self {
asset_id: self.asset_id.clone(),
paper_id: self.paper_id.clone(),
user_address: self.user_address.clone(),
title: self.title.clone(),
text: self.text.clone(),
}
}
}
/// This struct stores information about a user's subpaper submission, including the associated asset ID, the unique IDs
/// of the parent paper and the subpaper, the user's account address, subpaper title, subpaper text, and the timestamp when
/// the subpaper was created.
#[derive(Encode, Decode, TypeInfo, MaxEncodedLen, Debug, PartialEq, Default)]
#[scale_info(skip_type_params(T))]
pub struct SubPaper<T: Config, AssetId, AccountId, PaperId, Time> {
pub asset_id: AssetId,
pub paper_id: PaperId,
pub subpaper_id: PaperId,
pub user_address: AccountId,
pub title: BoundedVec<u8, T::TitleLimit>,
pub text: BoundedVec<u8, T::TextLimit>,
pub created_at: Time,
}
impl<T: Config> Clone for SubPaper<T, AssetIdOf<T>, AccountIdOf<T>, PaperId, u64> {
fn clone(&self) -> Self {
Self {
asset_id: self.asset_id.clone(),
paper_id: self.paper_id.clone(),
subpaper_id: self.subpaper_id.clone(),
user_address: self.user_address.clone(),
title: self.title.clone(),
text: self.text.clone(),
created_at: self.created_at.clone(),
}
}
}
// Store the next paper id within each token.
// papers id - asset_id -> paper_id
#[pallet::storage]
#[pallet::getter(fn get_paper_id_value)]
pub type PaperIdCounter<T: Config> = StorageMap<
_,
Blake2_128Concat,
AssetIdOf<T>,
PaperId,
ValueQuery
>;
/// Store the next subpaper id within each token and paper.
/// asset_id -> paper_id -> subpaper_id
#[pallet::storage]
#[pallet::getter(fn get_subpaper_id_value)]
pub type SubPaperIdCounter<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
AssetIdOf<T>,
Blake2_128Concat,
PaperId,
PaperId,
ValueQuery
>;
/// Store the papers within each token.
/// storage just for papers - asset_id -> paper_id -> paper
#[pallet::storage]
#[pallet::getter(fn get_id_papers)]
pub type PaperIdMap<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
AssetIdOf<T>,
Blake2_128Concat,
PaperId,
PaperOf<T>
>;
/// Store the papers within each token and paper.
/// asset_id -> paper_id -> subpaper_id -> subpaper
#[pallet::storage]
#[pallet::getter(fn get_subpaper)]
pub type SubPaperIdMap<T: Config> = StorageNMap<
_,
(
NMapKey<Blake2_128Concat, AssetIdOf<T>>,
NMapKey<Blake2_128Concat, PaperId>,
NMapKey<Blake2_128Concat, PaperId>,
),
SubPaperOf<T>
>;
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// Create a paper.
PaperCreated {
who: T::AccountId,
asset_id: AssetIdOf<T>,
paper_id: PaperId,
},
/// Create a subpaper.
SubPaperCreated {
who: T::AccountId,
asset_id: AssetIdOf<T>,
paper_id: PaperId,
subpaper_id: PaperId,
},
/// Update a paper.
PaperUpdated {
who: T::AccountId,
asset_id: AssetIdOf<T>,
paper_id: PaperId,
title: BoundedVec<u8, T::TitleLimit>,
text: BoundedVec<u8, T::TextLimit>,
},
/// Update a subpaper.
SubPaperUpdated {
who: T::AccountId,
asset_id: AssetIdOf<T>,
paper_id: PaperId,
subpaper_id: PaperId,
title: BoundedVec<u8, T::TitleLimit>,
text: BoundedVec<u8, T::TextLimit>,
},
}
#[pallet::error]
pub enum Error<T> {
/// Asset id does not exist.
AssetDontExist,
/// Paper id does not exist.
PaperIdDontExist,
/// Subpaper id does not exist.
SubPaperIdDontExist,
/// Only the owner of a paper or subpaper has the permission to create or update subpapers within that paper.
NoPermission,
}
#[pallet::call]
impl<T: Config> Pallet<T> {
/// The origin can create a paper within the token ID 'asset_id', with the title 'title', and the text 'text'.
///
/// Parameters:
/// - `asset_id`: The token ID of the paper.
/// - `title`: The title of the paper.
/// - `text`: The text of the paper.
///
/// Emits `PaperCreated` event when successful.
#[pallet::call_index(0)]
#[pallet::weight({ 0 })]
pub fn create_paper(
origin: OriginFor<T>,
asset_id: AssetIdOf<T>,
title: BoundedVec<u8, T::TitleLimit>,
text: BoundedVec<u8, T::TextLimit>
) -> 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>::AssetDontExist);
let paper_id = PaperIdCounter::<T>::get(asset_id.clone());
let paper = PaperOf::<T> {
asset_id: asset_id.clone(),
paper_id: paper_id.clone(),
user_address: who.clone(),
title: title,
text: text,
};
PaperIdCounter::<T>::insert(asset_id.clone(), paper_id.clone() + 1);
PaperIdMap::<T>::insert(asset_id.clone(), paper_id.clone(), paper.clone());
Self::deposit_event(Event::<T>::PaperCreated { who, asset_id, paper_id });
Ok(())
}
/// The origin can update a paper within the token ID 'asset_id' and paper ID 'paper_id' by modifying the title to
/// 'title' and the text to 'text'.
///
/// Parameters:
/// - `asset_id`: The token ID of the paper.
/// - `paper_id`: The paper ID of the paper.
/// - `title`: The title of the paper.
/// - `text`: The text of the paper.
///
/// Emits `PaperUpdated` event when successful.
#[pallet::call_index(1)]
#[pallet::weight({ 0 })]
pub fn update_paper(
origin: OriginFor<T>,
asset_id: AssetIdOf<T>,
paper_id: PaperId,
title: BoundedVec<u8, T::TitleLimit>,
text: BoundedVec<u8, T::TextLimit>
) -> 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>::AssetDontExist);
ensure!(
PaperIdMap::<T>::contains_key(asset_id.clone(), paper_id.clone()),
Error::<T>::PaperIdDontExist
);
let mut paper_updated = PaperIdMap::<T>
::get(asset_id.clone(), paper_id.clone())
.unwrap();
ensure!(who == paper_updated.user_address, Error::<T>::NoPermission);
paper_updated.title = title.clone();
paper_updated.text = text.clone();
PaperIdMap::<T>::insert(asset_id.clone(), paper_id.clone(), paper_updated);
Self::deposit_event(Event::<T>::PaperUpdated { who, asset_id, paper_id, title, text });
Ok(())
}
/// The origin can create a subpaper within the token ID 'asset_id' and paper ID 'paper_id', with the title 'title',
/// and the text 'text'.
///
/// Parameters:
/// - `asset_id`: The token ID of the subpaper.
/// - `paper_id`: The paper ID of the subpaper.
/// - `title`: The title of the subpaper.
/// - `text`: The text of the subpaper.
///
/// Emits `SubPaperCreated` event when successful.
#[pallet::call_index(2)]
#[pallet::weight({ 0 })]
pub fn create_subpaper(
origin: OriginFor<T>,
asset_id: AssetIdOf<T>,
paper_id: PaperId,
title: BoundedVec<u8, T::TitleLimit>,
text: BoundedVec<u8, T::TextLimit>
) -> 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>::AssetDontExist);
ensure!(
PaperIdMap::<T>::contains_key(&asset_id, &paper_id),
Error::<T>::PaperIdDontExist
);
let paper = PaperIdMap::<T>::get(&asset_id, &paper_id).unwrap();
ensure!(who == paper.user_address, Error::<T>::NoPermission);
let subpaper_id = SubPaperIdCounter::<T>::get(&asset_id, &paper_id);
let subpaper = SubPaperOf::<T> {
asset_id: asset_id.clone(),
paper_id: paper_id.clone(),
subpaper_id: subpaper_id.clone(),
user_address: who.clone(),
title: title,
text: text,
created_at: T::TimeProvider::now().as_secs(),
};
SubPaperIdCounter::<T>::insert(
asset_id.clone(),
paper_id.clone(),
subpaper_id.clone() + 1
);
SubPaperIdMap::<T>::insert(
(asset_id.clone(), paper_id.clone(), subpaper_id.clone()),
subpaper
);
Self::deposit_event(Event::<T>::SubPaperCreated {
who,
asset_id,
paper_id,
subpaper_id,
});
Ok(())
}
/// The origin can update a subpaper within the token ID 'asset_id', paper ID 'paper_id' and subpaper ID 'subpaper_id'
/// by modifying the title to 'title' and the text to 'text'.
///
/// Parameters:
/// - `asset_id`: The token ID of the subpaper.
/// - `paper_id`: The paper ID of the subpaper.
/// - `subpaper_id`: The subpaper ID of the subpaper
/// - `title`: The title of the subpaper.
/// - `text`: The text of the subpaper.
///
/// Emits `SubPaperUpdated` event when successful.
#[pallet::call_index(3)]
#[pallet::weight({ 0 })]
pub fn update_subpaper(
origin: OriginFor<T>,
asset_id: AssetIdOf<T>,
paper_id: PaperId,
subpaper_id: PaperId,
title: BoundedVec<u8, T::TitleLimit>,
text: BoundedVec<u8, T::TextLimit>
) -> 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>::AssetDontExist);
ensure!(
PaperIdMap::<T>::contains_key(&asset_id, &paper_id),
Error::<T>::PaperIdDontExist
);
ensure!(
SubPaperIdMap::<T>::contains_key((&asset_id, &paper_id, &subpaper_id)),
Error::<T>::SubPaperIdDontExist
);
let paper = PaperIdMap::<T>::get(&asset_id, &paper_id).unwrap();
ensure!(who == paper.user_address, Error::<T>::NoPermission);
let mut subpaper_update = SubPaperIdMap::<T>
::get((&asset_id, &paper_id, &subpaper_id))
.unwrap();
subpaper_update.title = title.clone();
subpaper_update.text = text.clone();
SubPaperIdMap::<T>::insert(
(asset_id.clone(), paper_id.clone(), subpaper_id.clone()),
subpaper_update
);
Self::deposit_event(Event::<T>::SubPaperUpdated {
who,
asset_id,
paper_id,
subpaper_id,
title,
text,
});
Ok(())
}
}
}