//! # Unit Meets Pallet
//! <!-- Original author of paragraph: @matthieu
//!
//! ## Overview
//!
//! Pallet that allows a user to choose who he wants to meet.
//!
//! ### Goals
//!
//! The pallet is designed to make the following possible:
//!
//! * Choose who a user want to meet.
//! * Choose if he wants to be part of this app.
//!
//! ## Interface
//!
//! ### Permissionless Functions
//!
//! - `respond_to_user`: "yes" or "no" to meet another person.
//! - `set_pref_all_users` : accept to share the profile to other - yes by default.
//!
//!//! 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::*;
use sp_std::{ vec, vec::Vec };
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
// #[cfg(feature = "runtime-benchmarks")]
// mod benchmarking;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::{ pallet_prelude::*, traits::{ fungibles, fungibles::*, UnixTime } };
use frame_system::pallet_prelude::*;
use pallet_pool::PoolManager;
use traits::asset::{AssetInterface, TokenType};
// use traits::asset::AssetInterface;
/// Type used to represent the token decimals.
pub type Decimals = u8;
#[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>;
/// Type to access the Assets Pallet.
type Fungibles: fungibles::Inspect<Self::AccountId> +
fungibles::Mutate<Self::AccountId> +
fungibles::metadata::Inspect<Self::AccountId> +
AssetInterface<
<Self::Fungibles as Inspect<Self::AccountId>>::AssetId,
<Self::Fungibles as Inspect<Self::AccountId>>::Balance,
Self::AccountId,
Decimals,
TokenType
>;
// Type to access the Pool Pallet.
type Pool: PoolManager<Self::AccountId, AssetId<Self>, BalanceOf<Self>>;
/// Tradeing fee rate
#[pallet::constant]
type GetExchangeFee: Get<(u32, u32)>;
/// Provides an interface to get the actual time in Unix Time.
type TimeProvider: UnixTime;
}
// Balance type
pub type BalanceOf<T> =
<<T as Config>::Fungibles as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
//Asset Id type
pub type AssetId<T> =
<<T as Config>::Fungibles as Inspect<<T as frame_system::Config>::AccountId>>::AssetId;
// The response of a user to another user, by tokenId
#[pallet::storage]
#[pallet::getter(fn user_responses)]
pub type UserResponses<T: Config> = StorageMap<
_,
Twox64Concat,
(T::AccountId, T::AccountId, AssetId<T>),
bool,
ValueQuery
>;
// For a user, by Asset Id, the list of all users he said yes to meet
#[pallet::storage]
#[pallet::getter(fn all_user_responses)]
pub type AllUserResponses<T: Config> = StorageMap<
_,
Twox64Concat,
(T::AccountId, AssetId<T>),
Vec<T::AccountId>,
ValueQuery
>;
// For a user, the list of all matches and their corresponding times
#[pallet::storage]
#[pallet::getter(fn user_matches)]
pub type UserMatches<T: Config> = StorageMap<
_,
Twox64Concat,
T::AccountId,
(Vec<T::AccountId>, Vec<u64>),
ValueQuery
>;
// By Asset Id, if there is a match between two users
#[pallet::storage]
#[pallet::getter(fn matches)]
pub type Matches<T: Config> = StorageMap<
_,
Twox64Concat,
(T::AccountId, T::AccountId, AssetId<T>),
bool,
ValueQuery
>;
// If a user want to display its profile
#[pallet::storage]
#[pallet::getter(fn user_pref_hide)]
pub type UserPrefHide<T: Config> = StorageDoubleMap<
_,
Twox64Concat,
AssetId<T>,
Twox64Concat,
T::AccountId,
bool,
ValueQuery
>;
// 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> {
// Event if there is a match between two users
MatchFound(T::AccountId, T::AccountId),
}
// Errors inform users that something went wrong.
#[pallet::error]
pub enum Error<T> {
// Can not meet my self
CannotRespondToSelf,
//If already responsed
AlreadyResponded,
// If there is already a match
MatchAlreadyExists,
// User is hidden
UserUnavailable
}
// Dispatchable functions allows users to interact with the pallet and invoke state changes.
#[pallet::call]
impl<T: Config> Pallet<T> {
/// The origin can select a response for each user.
///
/// The origin must be Signed.
///
/// Parameters:
/// - `user`: the response of the origin to meet this accountId.
/// - `token_id`: The asset id of the grant request.
/// - `response`: The response - true or false.
/// - `time` : Time of the transaction,
/// handled by the frond end. Modify it to use Timestamp.
#[pallet::call_index(1)]
#[pallet::weight(
10_000 +
T::DbWeight::get().writes(7).ref_time() +
T::DbWeight::get().reads(12).ref_time()
)]
pub fn respond_to_user(
origin: OriginFor<T>,
user: T::AccountId,
tokenid: AssetId<T>,
response: bool
) -> DispatchResult {
let who = ensure_signed(origin)?;
<<T as pallet::Config>::Fungibles as traits::asset::AssetInterface<
AssetId<T>,
BalanceOf<T>,
T::AccountId,
Decimals,
TokenType
>>::asset_exists(tokenid.clone())?;
// Ensure users have active pref
ensure!(!UserPrefHide::<T>::get(tokenid.clone(), who.clone()) && !UserPrefHide::<T>::get(tokenid.clone(), user.clone()), Error::<T>::UserUnavailable);
// Ensure user is not responding to self
ensure!(who != user, Error::<T>::CannotRespondToSelf);
// Ensure that a user cannot respond more than once to the same user for a given tokenid
ensure!(
!UserResponses::<T>::contains_key((who.clone(), user.clone(), tokenid.clone())),
Error::<T>::AlreadyResponded
);
// Store user's response for the other user
UserResponses::<T>::insert((who.clone(), user.clone(), tokenid.clone()), response);
AllUserResponses::<T>::mutate((who.clone(), tokenid.clone()), |users| {
users.push(user.clone())
});
// Check if there's a match
let match_response = UserResponses::<T>::get((
user.clone(),
who.clone(),
tokenid.clone(),
));
if match_response && response {
// Both users said 'yes', they can discuss!
Matches::<T>::insert((who.clone(), user.clone(), tokenid.clone()), true);
Matches::<T>::insert((user.clone(), who.clone(), tokenid.clone()), true);
let time = T::TimeProvider::now().as_secs();
// Add each user to the other's match list
UserMatches::<T>::mutate(who.clone(), |(user_ids, times)| {
user_ids.push(user.clone());
times.push(time);
});
UserMatches::<T>::mutate(user.clone(), |(user_ids, times)| {
user_ids.push(who.clone());
times.push(time);
});
// Emit match event
Self::deposit_event(Event::MatchFound(who.clone(), user.clone()));
}
Ok(())
}
/// The origin can select if if wants to be displayed in this app.
///
/// The origin must be Signed.
///
/// Parameters:
/// - `pref`: Its choice.
#[pallet::call_index(2)]
#[pallet::weight(10_000 + T::DbWeight::get().writes(1).ref_time())]
pub fn set_pref_all_users(
origin: OriginFor<T>,
tokenid: AssetId<T>,
pref: bool
) -> DispatchResult {
let who = ensure_signed(origin)?;
<<T as pallet::Config>::Fungibles as traits::asset::AssetInterface<
AssetId<T>,
BalanceOf<T>,
T::AccountId,
Decimals,
TokenType
>>::asset_exists(tokenid.clone())?;
// Store the user's preference
UserPrefHide::<T>::insert(tokenid, who, pref);
Ok(())
}
}
}