//! # Unit Gift Pallet
//! <!-- Original author of paragraph: @faiz
//!
//! ## Overview
//!
//! Pallet that allows a user to create Giftbox with a message , users can open that giftbox to get assets as reward.
//!
//! ### Goals
//!
//! The pallet is designed to make the following possible:
//!
//! * create giftbox.
//! * open gitbox.
//!
//! ## Interface
//!
//! ### Permissionless Functions
//!
//! - `create_giftbox`: create a gitbox.
//! - `open_giftbox` : open a giftbox.
//!
//!//! Please refer to the [`Call`] enum and its associated variants for documentation on each
//! function.
// Ensure we're `no_std` when compiling for Wasm.
#![cfg_attr(not(feature = "std"), no_std)]
// Re-export pallet items so that they can be accessed from the crate namespace.
pub use pallet::*;
mod types;
pub use types::*;
use frame_support::traits::{ Currency, ReservableCurrency };
use traits::profile::{ ProfileInspect, ProfileInterface };
use traits::{ subaccounts::{ SubAccounts, AccountOrigin }, asset::{AssetInterface, TokenType} };
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
// Definition of the pallet logic, to be aggregated at runtime definition through
// `construct_runtime`.
#[frame_support::pallet]
pub mod pallet {
use frame_support::{
pallet_prelude::*,
traits::{ fungibles::{ self, Inspect, Mutate }, tokens::Preservation::Expendable },
};
use frame_system::pallet_prelude::*;
// Import various types used to declare pallet in scope
use super::*;
use frame_support::sp_runtime::traits::AccountIdConversion;
use frame_support::PalletId;
use frame_support::dispatch::Vec;
pub type Decimals = u8;
// Balance type alias.
pub type BalanceOf<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;
/// A type alias for the balance type from this pallet's point of view.
pub type DepositBalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
#[pallet::config]
pub trait Config: frame_system::Config + pallet_assets::Config + pallet_profile::Config {
/// The overarching event type.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// The currency mechanism, used for paying for reserves.
type Currency: ReservableCurrency<Self::AccountId>;
/// The pallet's id
#[pallet::constant]
type GiftPalletId: Get<PalletId>;
/// Type to access the profile pallet
type Profile: ProfileInspect<
Self::AccountId,
<Self as pallet::Config>::ProfileStringLimit
> +
ProfileInterface<Self::AccountId, <Self as pallet::Config>::ProfileStringLimit>;
#[pallet::constant]
type ProfileStringLimit: Get<u32>;
/// The maximum length of a name or symbol stored on-chain.
#[pallet::constant]
type StringLimit: Get<u32>;
/// The maximum length of a name or symbol stored on-chain.
#[pallet::constant]
type MessageLimit: Get<u32>;
/// Type to access the sub account pallet
type SubAccounts: SubAccounts<Self::AccountId, AccountOrigin>;
/// Type to access the Assets Pallet.
type Fungibles: fungibles::Inspect<Self::AccountId> + //, AssetId = u32> // Hash this
fungibles::Mutate<Self::AccountId> +
fungibles::metadata::Inspect<Self::AccountId> +
fungibles::Create<Self::AccountId> +
fungibles::roles::Inspect<Self::AccountId> +
AssetInterface<AssetIdOf<Self>, BalanceOf<Self>, Self::AccountId, Decimals, TokenType>;
}
// Simple declaration of the `Pallet` type. It is placeholder we use to implement traits and
// method.
#[pallet::pallet]
#[pallet::without_storage_info]
pub struct Pallet<T>(_);
#[pallet::storage]
#[pallet::getter(fn community_gifts)]
/// all community gift boxes
pub type CommunityGifts<T: Config> = StorageValue<
_,
Vec<
CommunityGiftBoxes<
T::AccountId,
BalanceOf<T>,
AssetIdOf<T>,
BoundedVec<u8, <T as pallet::Config>::StringLimit>,
BoundedVec<u8, T::MessageLimit>
>
>
>;
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// A `Gift` is Added.
AddedGiftItem {
gift_box: CommunityGiftBoxes<
T::AccountId,
BalanceOf<T>,
AssetIdOf<T>,
BoundedVec<u8, <T as pallet::Config>::StringLimit>,
BoundedVec<u8, T::MessageLimit>
>,
},
/// A `Gift` is Opened.
OpenedGiftBox {
gift_box: CommunityGiftBoxes<
T::AccountId,
BalanceOf<T>,
AssetIdOf<T>,
BoundedVec<u8, <T as pallet::Config>::StringLimit>,
BoundedVec<u8, T::MessageLimit>
>,
},
}
#[pallet::error]
pub enum Error<T> {
/// User Not Found
UserNotFound,
/// Not Enough Assets
NotEnoughAssets,
/// Gift Item Not Found
GiftItemNotFound,
/// Not Enough Balance In Account
NotEnoughBalance,
/// No Gifts Created Yet
NoGiftsYet,
/// Profile Not Found
ProfileNotFound,
}
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Issue a new Giftboxes from a public origin.
///
/// This is a GiftBox having amount of assets given by creator of giftbox.
///
/// Origin must be Signed.
///
/// Parameters:
/// - `gift_token_amount`: The Amount of Tokens Inside a Giftbox.
/// - `gift_code_hash`: The unique identifier hash for that giftbox
/// - `_token_id`: The asset Id for which the Gift is created
/// - `message`: The Message Given by Giftbox Creator
///
/// Emits `AddedGiftItem` event when successful.
#[pallet::call_index(0)]
#[pallet::weight(10_000 + T::DbWeight::get().writes(1).ref_time())]
pub fn create_giftbox(
origin: OriginFor<T>,
gift_token_amount: BalanceOf<T>,
gift_code_hash: BoundedVec<u8, <T as pallet::Config>::StringLimit>,
_token_id: AssetIdOf<T>,
message: BoundedVec<u8, T::MessageLimit>
) -> DispatchResult {
let mut who = ensure_signed(origin.clone())?;
// Mutate the origin to transfer funds from the main account
who = <T as pallet::Config>::SubAccounts::get_main_account(who)?;
// getting current user balance
let balance = T::Fungibles::balance(_token_id.clone(), &who.clone());
// balance should be greater than gift amount
ensure!(balance > gift_token_amount, Error::<T>::NotEnoughBalance);
// locking assets
Self::lock_assets(who.clone(), _token_id.clone(), gift_token_amount)?;
// making sure the storage is not null
if CommunityGifts::<T>::get().is_none() {
// initializing an empty vec
let empty_vec: Vec<
CommunityGiftBoxes<
T::AccountId,
BalanceOf<T>,
AssetIdOf<T>,
BoundedVec<u8, <T as pallet::Config>::StringLimit>,
BoundedVec<u8, T::MessageLimit>
>
> = Vec::new();
CommunityGifts::<T>::put(empty_vec);
}
// getting all gifts
let mut gifts = CommunityGifts::<T>::get().ok_or(Error::<T>::NoGiftsYet)?;
let gift = CommunityGiftBoxes {
id: gifts.len() as u32,
user_address: who.clone(),
gift_box_fund_transfer_id: 0,
gift_box_collect_transfer_id: 0,
gift_code: gift_code_hash,
amount: gift_token_amount,
token_id: _token_id,
message,
collector_user_id: who,
};
gifts.push(gift.clone());
// updating the storage
CommunityGifts::<T>::put(gifts);
Self::deposit_event(Event::AddedGiftItem {
gift_box: gift,
});
Ok(())
}
/// Opens a Giftbox from a public origin.
///
/// The Origin calling this function will receive the assets inside the giftbox.
///
/// Origin must be Signed.
///
/// Parameters:
/// - `gift_code_hash`: The unique identifier hash for that giftbox
///
/// Emits `OpenedGiftBox` event when successful.
#[pallet::call_index(1)]
#[pallet::weight(10_000 + T::DbWeight::get().writes(1).ref_time())]
pub fn open_giftbox(
origin: OriginFor<T>,
gift_code: BoundedVec<u8, <T as pallet::Config>::StringLimit>
) -> DispatchResult {
let mut _who = ensure_signed(origin.clone())?;
// Mutate the origin to transfer funds from the main account
_who = <T as pallet::Config>::SubAccounts::get_main_account(_who)?;
// making sure storage value is not null
if CommunityGifts::<T>::get().is_none() {
let empty_vec: Vec<
CommunityGiftBoxes<
T::AccountId,
BalanceOf<T>,
AssetIdOf<T>,
BoundedVec<u8, <T as pallet::Config>::StringLimit>,
BoundedVec<u8, T::MessageLimit>
>
> = Vec::new();
CommunityGifts::<T>::put(empty_vec);
}
// getting al gifts
let mut gifts = CommunityGifts::<T>::get().ok_or(Error::<T>::NoGiftsYet)?;
// getting if the gift exists
let item = gifts
.clone()
.into_iter()
.find(|x| x.gift_code == gift_code);
// gift should exists
let item = item.ok_or(Error::<T>::GiftItemNotFound)?;
if item.gift_box_collect_transfer_id == 0 {
T::Fungibles::transfer(
item.token_id.clone(),
&Self::account_id().clone(),
&_who.clone(),
item.amount,
Expendable
)?;
// updating the gift object
let gift_item = CommunityGiftBoxes {
id: item.id,
user_address: item.user_address.clone(), // owner of giftbox will be the same
gift_box_fund_transfer_id: 1u32,
gift_box_collect_transfer_id: 1_u32,
gift_code,
amount: item.amount,
token_id: item.token_id.clone(),
message: item.message.clone(),
collector_user_id: _who.clone(),
};
// removing old gift box
gifts.retain(|x| *x != item);
// pushing new one
gifts.push(gift_item.clone());
// updating the storage
CommunityGifts::<T>::put(gifts);
Self::deposit_event(Event::OpenedGiftBox {
gift_box: gift_item,
});
}
Ok(())
}
}
impl<T: Config> Pallet<T> {
/// returns the acco8unt id for the pallet
pub fn account_id() -> T::AccountId {
T::GiftPalletId::get().into_account_truncating()
}
/// locks assets of user
pub fn lock_assets(
account: T::AccountId,
asset_id: AssetIdOf<T>,
amount: BalanceOf<T>
) -> DispatchResult {
let transfer = T::Fungibles::transfer(
asset_id,
&account.clone(),
&Self::account_id(),
amount,
Expendable
);
ensure!(transfer.is_ok(), Error::<T>::NotEnoughAssets);
Ok(())
}
/// unlocks assets of user
pub fn unlock_assets(
account: T::AccountId,
asset_id: AssetIdOf<T>,
amount: BalanceOf<T>
) -> DispatchResult {
let transfer = T::Fungibles::transfer(
asset_id,
&Self::account_id(),
&account,
amount,
Expendable
);
ensure!(transfer.is_ok(), Error::<T>::NotEnoughAssets);
Ok(())
}
}
}