//! # Unit stores Pallet
//! <!-- Original author of paragraph: @faiz
//!
//! ## Overview
//!
//! Pallet that allows a users to create a store, add products and others users can purchase products
//! using the tokens from the store.
//!
//! ### Goals
//!
//! The pallet is designed to make the following possible:
//!
//! * create a store.
//! * delete a store.
//! * edit a store.
//! * create a product.
//! * delete a product.
//! * edit a product.
//! * purchase products.
//!
//! ## Interface
//!
//! ### Permissionless Functions
//!
//! - `create_store`: create a store.
//! - `buy_product`: create a order.
//!
//! ### Privileged Functions
//!
//! - `edit_store`: edit store.
//! - `add_product`: add product.
//! - `edit_product`: edit product.
//! - `delete_store`: delete store
//! - `delete_product`: delete product
//!
//!//! 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)]
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod weights;
pub use weights::WeightInfo;
#[cfg(test)]
pub mod mock;
#[cfg(test)]
mod tests;
// Re-export pallet items so that they can be accessed from the crate namespace.
pub use pallet::*;
mod types;
pub use types::*;
use traits::subaccounts::{ SubAccounts, AccountOrigin };
#[frame_support::pallet]
pub mod pallet {
use frame_support::pallet_prelude::*;
use frame_system::{ pallet_prelude::*, Origin };
// Import various types used to declare pallet in scope
use super::*;
/// A type alias for the balance type from this pallet's point of view.
pub type BalanceOf<T> = <T as pallet_assets::Config>::Balance;
// 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>(_);
/// The module configuration trait.
#[pallet::config]
pub trait Config: frame_system::Config + pallet_assets::Config {
/// The overarching event type.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
#[pallet::constant]
type StringLimit: Get<u32>;
/// The maximum length of data stored on-chain.
#[pallet::constant]
type DataLimit: Get<u32>;
/// Type to access the sub account pallet
type SubAccounts: SubAccounts<Self::AccountId, AccountOrigin>;
// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;
// Helper trait for benchmarks.
#[cfg(feature = "runtime-benchmarks")]
type ProfileBenchmark;
}
#[pallet::storage]
#[pallet::getter(fn order_id)]
/// Counter for the buy orders.
pub type OrderId<T> = StorageValue<_, u32, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn store_id)]
/// Counter for stores.
pub type StoreId<T> = StorageValue<_, u32, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn product_id)]
/// Counter for products.
pub type ProductId<T> = StorageValue<_, u32, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn store_item)]
/// Store item by store id
pub(super) type StoreItem<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
T::AssetId,
Blake2_128Concat,
u32,
StoreDetails<T::AccountId, T::AssetId, BoundedVec<u8, T::DataLimit>>
>;
#[pallet::storage]
#[pallet::getter(fn order_item)]
/// Order item by order id
pub(super) type OrderItem<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
u32, // store id
Blake2_128Concat,
u32, // order id
OrderDetails<T::AccountId, T::AssetId, BalanceOf<T>, BoundedVec<u8, T::DataLimit>>
>;
#[pallet::storage]
#[pallet::getter(fn store_products)]
/// Store products by store id and product id
pub(super) type StoreProducts<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
u32,
Blake2_128Concat,
u32,
ProductDetails<T::AccountId, BalanceOf<T>, BoundedVec<u8, T::DataLimit>, T::AssetId>
>;
#[pallet::storage]
#[pallet::getter(fn stores_per_user)]
/// Store if the user has created a store for an asset
pub(super) type StoresPerUser<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
T::AccountId,
Blake2_128Concat,
T::AssetId,
bool,
ValueQuery
>;
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// A store was created. (Owner, store id, asset id)
StoreCreated(T::AccountId, u32, T::AssetId),
/// Store info was edited. (Asset id, store id)
StoreModified(T::AssetId, u32),
/// A product was created. (Product id, store id, asset id)
ProductAdded(u32, u32, T::AssetId),
/// Product info was edited. (Product id, store id, asset id)
ProductModified(u32, u32, T::AssetId),
/// A user bought a product. (Order id)
OrderAdded(u32),
/// Store and all it's products has been deleted. (Asset id, store id)
RemovedStore(T::AssetId, u32),
/// A product was deleted. (Product id, store id, asset id)
RemovedProduct(u32, u32, T::AssetId),
}
#[pallet::error]
pub enum Error<T> {
/// Each user can only have one store per asset
StoreAlreadyCreated,
/// Store Not Found
StoreNotFound,
/// Product Not Found
ProductNotFound,
/// You Are Not The Store Owner
YouAreNotTheStoreOwner,
/// Order Not Found
OrderNotFound,
/// Overflow
Overflow,
}
#[pallet::call(weight(<T as Config>::WeightInfo))]
impl<T: Config> Pallet<T> {
/// Creates store from a public origin.
///
/// This creates an store and its owner is the origin.
///
/// A user can have only one store per asset.
///
/// Parameters:
/// - `title`: The title of the store.
/// - `description`: The description of the store.
/// - `asset_id`: The asset for which the store will be created.
///
/// Emits `StoreCreated` event when successful.
#[pallet::call_index(0)]
pub fn create_store(
origin: OriginFor<T>,
title: BoundedVec<u8, T::DataLimit>,
description: BoundedVec<u8, T::DataLimit>,
asset_id: T::AssetId
) -> 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)?;
ensure!(
!StoresPerUser::<T>::contains_key(who.clone(), asset_id),
Error::<T>::StoreAlreadyCreated
);
// current store id
let current_store_id = StoreId::<T>::get();
let new_id = current_store_id.checked_add(1).ok_or(Error::<T>::Overflow)?;
let store = StoreDetails {
title,
description,
owner: who.clone(),
asset_id,
store_id: current_store_id,
};
// add to store item
StoreItem::<T>::insert(asset_id, current_store_id, store);
// store that the user has created an store for this asset
StoresPerUser::<T>::insert(who.clone(), asset_id, true);
// update the store id
StoreId::<T>::put(new_id);
Self::deposit_event(Event::StoreCreated(who, current_store_id, asset_id));
Ok(())
}
/// Adds a product to a store.
///
/// Only the owner of the store can call this function.
///
/// Parameters:
/// - `title`: The title of the product.
/// - `description`: The description of the product.
/// - `store_id`: The store id.
/// - `product_price`: The product price.
///
/// Emits `ProductAdded` event when successful.
#[pallet::call_index(1)]
pub fn add_product(
origin: OriginFor<T>,
title: BoundedVec<u8, T::DataLimit>,
description: BoundedVec<u8, T::DataLimit>,
store_id: u32,
product_price: BalanceOf<T>,
asset_id: T::AssetId
) -> 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 the store item or error if don't exists
let store = StoreItem::<T>::get(asset_id, store_id).ok_or(Error::<T>::StoreNotFound)?;
ensure!(store.owner == who.clone(), Error::<T>::YouAreNotTheStoreOwner);
// current product id
let product_id = ProductId::<T>::get();
let new_product_id = product_id.checked_add(1).ok_or(Error::<T>::Overflow)?;
let product = ProductDetails {
title,
description,
store_id,
product_price,
owner: who,
product_id,
asset_id,
};
// save product for this store
StoreProducts::<T>::insert(store_id, product_id, product);
// updating the product id
ProductId::<T>::put(new_product_id);
Self::deposit_event(Event::ProductAdded(product_id, store_id, asset_id));
Ok(())
}
/// Edit the info from a store.
///
/// The origin must be the owner of the store.
///
/// Parameters:
/// - `title`: The new title of the store.
/// - `description`: The new description of the store.
/// - `store_id`: The store id to modify.
/// - `asset_id`: The id of the asset where the store was created.
///
/// Emits `StoreModified` event when successful.
#[pallet::call_index(4)]
pub fn edit_store(
origin: OriginFor<T>,
title: BoundedVec<u8, T::DataLimit>,
description: BoundedVec<u8, T::DataLimit>,
store_id: u32,
asset_id: T::AssetId
) -> DispatchResult {
let mut who = ensure_signed(origin.clone())?;
// Mutate the origin to store info for the main account
who = <T as pallet::Config>::SubAccounts::get_main_account(who)?;
// Get store if exists
let store_details = StoreItem::<T>
::get(asset_id, store_id)
.ok_or(Error::<T>::StoreNotFound)?;
// only owner can edit it
ensure!(store_details.owner == who.clone(), Error::<T>::YouAreNotTheStoreOwner);
let store = StoreDetails {
title,
description,
owner: who.clone(),
asset_id,
store_id,
};
// add to store item
StoreItem::<T>::insert(asset_id, store_id, store);
Self::deposit_event(Event::StoreModified(asset_id, store_id));
Ok(())
}
/// Edit the info of a product.
///
/// The origin must be the owner of the store.
///
/// Parameters:
/// - `title`: The new title of the product.
/// - `description`: The new description of the product.
/// - `store_id`: The id of the store of which the product belongs.
/// - `product_id`: The id of product.
/// - `product_price`: The new price of the product.
///
/// Emits `ProductModified` event when successful.
#[pallet::call_index(5)]
pub fn edit_product(
origin: OriginFor<T>,
title: BoundedVec<u8, T::DataLimit>,
description: BoundedVec<u8, T::DataLimit>,
store_id: u32,
product_id: u32,
product_price: BalanceOf<T>,
asset_id: T::AssetId
) -> 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)?;
let store_details = StoreItem::<T>
::get(asset_id, store_id)
.ok_or(Error::<T>::StoreNotFound)?;
// only owner should edit
ensure!(store_details.owner == who.clone(), Error::<T>::YouAreNotTheStoreOwner);
// ensure the product exists
ensure!(
StoreProducts::<T>::contains_key(store_id, product_id),
Error::<T>::ProductNotFound
);
let product = ProductDetails {
title,
description,
store_id,
product_price,
owner: who,
product_id,
asset_id,
};
StoreProducts::<T>::insert(store_id, product_id, product);
Self::deposit_event(Event::ProductModified(store_id, product_id, asset_id));
Ok(())
}
/// Deletes a store and all the products that were created for it.
///
/// The origin must be the owner of the store.
///
/// This function will delete up to 100 products.
///
/// Parameters:
/// - `store_id`: The id of the store to delete.
/// - `asset_id`: The asset id where the store was created.
///
/// Emits `RemovedStore` event when successful.
#[pallet::call_index(6)]
pub fn delete_store(
origin: OriginFor<T>,
store_id: u32,
asset_id: T::AssetId
) -> 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 the store or error if doesn't exists
let store_details = StoreItem::<T>
::get(asset_id, store_id)
.ok_or(Error::<T>::StoreNotFound)?;
ensure!(store_details.owner == who.clone(), Error::<T>::YouAreNotTheStoreOwner);
// Delete all products from this store
let _ = StoreProducts::<T>::clear_prefix(store_id, 100, None);
// remove store item
StoreItem::<T>::remove(asset_id, store_id);
// The user won't have a store for this asset anymore
StoresPerUser::<T>::insert(who, asset_id, false);
Self::deposit_event(Event::RemovedStore(asset_id, store_id));
Ok(())
}
/// Removes a product from a store.
///
/// The origin must be the owner of the store.
///
/// Parameters:
/// - `store_id`: The id of the store.
/// - `product_id`: The id of the product.
/// - `asset_id`: The asset id where the store was created.
///
/// Emits `RemovedProduct` event when successful.
#[pallet::call_index(7)]
pub fn delete_product(
origin: OriginFor<T>,
store_id: u32,
product_id: u32,
asset_id: T::AssetId
) -> 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)?;
// product should exists
ensure!(
StoreProducts::<T>::contains_key(store_id, product_id),
Error::<T>::ProductNotFound
);
let store_details = StoreItem::<T>
::get(asset_id, store_id)
.ok_or(Error::<T>::StoreNotFound)?;
// only owner can delete it
ensure!(store_details.owner == who.clone(), Error::<T>::YouAreNotTheStoreOwner);
// Remove product
StoreProducts::<T>::remove(store_id, product_id);
Self::deposit_event(Event::RemovedProduct(store_id, product_id, asset_id));
Ok(())
}
/// Buy a product from a store.
///
/// Anyone can call this function. The sender should have enough balance to pay for the product.
///
/// Parameters:
/// - `store_id`: The id of the store where the product was created.
/// - `product_id`: The id of the product to buy.
///
/// Emits `OrderAdded` event when successful.
#[pallet::call_index(8)]
pub fn buy_product(origin: OriginFor<T>, store_id: u32, product_id: u32) -> 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.clone())?;
// getting the product or error if doens't exists
let product = StoreProducts::<T>
::get(store_id, product_id)
.ok_or(Error::<T>::ProductNotFound)?;
// current order id
let order_id = OrderId::<T>::get();
let new_order_id = order_id.checked_add(1).ok_or(Error::<T>::Overflow)?;
let order = OrderDetails {
title: product.title,
description: product.description,
owner: product.owner.clone(),
store_id,
product_id,
order_id,
product_price: product.product_price,
asset_id: product.asset_id,
buyer: who.clone(),
};
// add to order item
OrderItem::<T>::insert(store_id, order_id, order.clone());
// updating the order id
OrderId::<T>::put(new_order_id);
// transfering the amount to product owner
<pallet_assets::Pallet<T>>::transfer(
Origin::<T>::Signed(who).into(),
product.asset_id,
product.owner.clone(),
product.product_price
)?;
Self::deposit_event(Event::OrderAdded(order_id));
Ok(())
}
}
}