#![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 },
LockableCurrency,
ReservableCurrency,
UnixTime,
};
use frame_support::traits::fungibles::roles::Inspect as Inspector;
use codec::{ Decode, Encode, HasCompact };
use sp_runtime::traits::{ AtLeast32BitUnsigned, Saturating, Zero };
use scale_info::prelude::vec;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
/// Asset id type alias.
pub(crate) type AssetIdOf<T> =
<<T as Config>::Fungibles as Inspect<<T as frame_system::Config>::AccountId>>::AssetId;
/// StringLimit type alias.
pub(crate) type StringLimitOf<T> = <T as Config>::StringLimit;
/// Event type alias.
pub(crate) type EventTypeOf<T> = EventDetails<
BalanceOf<T>,
<T as frame_system::Config>::AccountId,
AssetIdOf<T>,
<T as Config>::EventIndex,
StringLimitOf<T>
>;
/// Time provider type alias.
pub(crate) type TimeProviderOf<T> = <T as pallet::Config>::TimeProvider;
/// Details of an Event.
#[derive(CloneNoBound, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo)]
#[scale_info(skip_type_params(StringLimit))]
pub struct EventDetails<
Balance: Clone,
AccountId: Clone,
AssetId: Clone,
EventIndex: Clone,
StringLimit: Get<u32>
> {
/// Account who created this Event.
pub created_by: AccountId,
/// Event id.
pub event_id: EventIndex,
/// Is unconference or not.
pub is_unconference: u32, // TODO: change this to a enum 0, 1, 2 (event, unconference, session)
/// Event name.
pub event_name: BoundedVec<u8, StringLimit>,
/// Event location.
pub event_location: BoundedVec<u8, StringLimit>,
/// Host user address.
pub host_user_address: BoundedVec<u8, StringLimit>,
/// Event description.
pub event_description: BoundedVec<u8, StringLimit>,
/// Token id.
pub token_id: AssetId,
/// Conference id.
pub conference_id: u32,
/// Time slot.
pub timeslot: u32,
/// Room number.
pub room: u32,
/// Event start time.
pub start_time: u64,
/// Event end time.
pub end_time: u64,
/// Session length.
pub session_length: Option<u64>,
/// A session is approved or not. This Only apply to sessions.
pub approved: bool,
/// Time zone for the event.
pub timezone: BoundedVec<u8, StringLimit>,
/// Number of rooms.
pub room_count: u32,
/// Number of time slots.
pub timeslot_count: u32,
/// Video link.
pub video_link: BoundedVec<u8, StringLimit>,
/// Min balance to be able attend an event.
pub min_balance: Balance,
}
impl<
Balance: AtLeast32BitUnsigned + Copy,
AccountId: Clone + Eq,
AssetId: Clone,
EventIndex: Clone,
StringLimit: Get<u32>
> EventDetails<Balance, AccountId, AssetId, EventIndex, StringLimit> {
/// Creates a new EventDetails with Ongoing status.
pub fn new(
created_by: AccountId,
event_id: EventIndex,
is_unconference: u32,
event_name: BoundedVec<u8, StringLimit>,
event_location: BoundedVec<u8, StringLimit>,
host_user_address: BoundedVec<u8, StringLimit>,
event_description: BoundedVec<u8, StringLimit>,
start_time: u64,
end_time: u64,
token_id: AssetId,
conference_id: u32,
timeslot: u32,
room: u32,
session_length: Option<u64>,
approved: bool, // TODO: consider making it an option
timezone: BoundedVec<u8, StringLimit>,
room_count: u32,
timeslot_count: u32,
video_link: BoundedVec<u8, StringLimit>,
min_balance: Balance
) -> Self {
Self {
created_by,
event_id,
is_unconference,
event_name,
event_location,
host_user_address,
event_description,
token_id,
conference_id,
timeslot,
room,
start_time,
end_time,
session_length,
approved,
timezone,
room_count,
timeslot_count,
video_link,
min_balance,
}
}
}
/// Score of an Account for an Event.
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo)]
pub struct EventScore<Balance> {
/// Number of attendees the account invited.
pub invited: u32,
/// Sum of Unit Holding of all attendees the account invited.
pub holding: Balance,
}
impl<Balance> EventScore<Balance> {
/// Creates a new EventScore.
pub fn new(invited: u32, holding: Balance) -> Self {
Self { invited, holding }
}
}
// type for slot in a conference
pub type Slot = u32;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_system::pallet_prelude::*;
pub type BalanceOf<T> = <T as pallet_assets::Config>::Balance;
#[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 + pallet_profile::Config + pallet_assets::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>> +
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>>;
/// Identifier and index for an event.
type EventIndex: Member +
Parameter +
AtLeast32BitUnsigned +
Default +
Copy +
HasCompact +
MaybeSerializeDeserialize +
MaxEncodedLen +
TypeInfo;
/// Overarching type of all pallets origins.
type PalletsOrigin: From<frame_system::RawOrigin<Self::AccountId>>;
/// Time provider.
type TimeProvider: UnixTime;
/// The maximum length of Strings, namely event name, location, user address, timezone, videolink and event description.
#[pallet::constant]
type StringLimit: Get<u32>;
}
/// The number of events that have been made so far.
#[pallet::storage]
#[pallet::getter(fn event_count)]
pub type EventCount<T: Config> = StorageValue<_, T::EventIndex, ValueQuery>;
/// Details of events.
#[pallet::storage]
#[pallet::getter(fn event_details_of)]
pub(super) type EventDetailsOf<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
AssetIdOf<T>,
Blake2_128Concat,
T::EventIndex,
EventDetails<BalanceOf<T>, T::AccountId, AssetIdOf<T>, T::EventIndex, StringLimitOf<T>>
>;
// Ateendees of an event.
#[pallet::storage]
#[pallet::getter(fn attendees_of)]
pub type AttendeesOf<T: Config> = StorageMap<
_,
Blake2_128Concat,
(AssetIdOf<T>, T::EventIndex),
Vec<T::AccountId>
>;
// Speakers of an event.
#[pallet::storage]
#[pallet::getter(fn speakers_of)]
pub type SpeakersOf<T: Config> = StorageMap<
_,
Blake2_128Concat,
(AssetIdOf<T>, T::EventIndex),
Vec<(Slot, T::AccountId)>
>;
// Ateendees of an event scores.
#[pallet::storage]
#[pallet::getter(fn score_of_event)]
pub type ScoreOf<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
(AssetIdOf<T>, T::EventIndex),
Blake2_128Concat,
T::AccountId,
EventScore<BalanceOf<T>>
>;
// Attendees balance when they were added to an event.
#[pallet::storage]
#[pallet::getter(fn balance_of)]
pub type BalanceOfAttendee<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
(AssetIdOf<T>, T::EventIndex),
Blake2_128Concat,
T::AccountId,
BalanceOf<T>
>;
// Sessions for an Unconference Event.
#[pallet::storage]
#[pallet::getter(fn sessions_of)]
pub(super) type SessionsOf<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
T::EventIndex,
Blake2_128Concat,
Slot,
T::EventIndex
>;
// 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 event was created.
EventCreated {
token_id: AssetIdOf<T>,
event_name: BoundedVec<u8, StringLimitOf<T>>,
event_id: T::EventIndex,
creator: T::AccountId,
},
/// An atendee was added.
AtendeeAdded {
asset_id: AssetIdOf<T>,
event_id: T::EventIndex,
attendee: T::AccountId,
},
/// An atendee was removed.
AtendeeRemoved {
inviter: T::AccountId,
token_id: AssetIdOf<T>,
event_id: T::EventIndex,
attendee: T::AccountId,
},
/// An event was updated.
EventUpdated {
token_id: AssetIdOf<T>,
event_id: T::EventIndex,
creator: T::AccountId,
},
/// An event was cancelled.
EventCancelled {
token_id: AssetIdOf<T>,
event_id: T::EventIndex,
},
/// An event status was changed.
EventStatusChanged {
token_id: AssetIdOf<T>,
event_id: T::EventIndex,
},
/// A session was added.
SessionAdded {
event_id: T::EventIndex,
slot: u32,
session_name: BoundedVec<u8, StringLimitOf<T>>,
session_id: T::EventIndex,
creator: T::AccountId,
},
/// An events has been removed.
EventRemoved {
asset_id: AssetIdOf<T>,
event_id: T::EventIndex,
creator: T::AccountId,
},
}
// Errors inform users that something went wrong.
#[pallet::error]
pub enum Error<T> {
/// The event index was not found.
EventNotFound,
/// Invalid event details given.
InvalidEventDetails,
/// Invalid event period given.
InvalidEventPeriod,
/// Only the creator can modify the event.
NotEventCreator,
/// Only token owner create an event.
NotTokenOwner,
/// Event only can be change to the next status.
InvalidEventStatus,
/// An event should be associated to a token with Total Issuance greater than Zero.
ZeroTotalIssuance,
/// To add an atendee, should be a token holder.
NotTokenHolder,
/// One account can be added only once.
AlreadyAdded,
/// Can only add atendees to an event when is open.
EventIsNotOpen,
/// Invalid user id, attende has no profile.
InvalidUserId,
/// An event can´t have an empty name.
EmptyEventName,
/// An event can´t be updated if it has already started.
EventAlreadyOngoing,
/// An event can´t be updated if it start time has passed.
EventIsNoLongerEditable,
/// Some scores were not cleared. // TODO: Check what happen in this case.
ScoreOfNotCleared,
/// Some balances were not cleared.
BalanceOfAttendeeNotCleared,
/// Some sessions were not cleared.
SessionsOfNotCleared,
/// To remove an event, remove all sessions first.
CantRemoveEventWithSessions,
/// Can't apply to an slot that is already taken.
TimeSlotAlreadyTaken,
}
// 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> {
/// Create a Event.
///
/// The dispatch origin of this must be _Signed_.
///
/// - `event_name`: The name of the event.
/// - `event_location`: The location of the event.
/// - `host_user_address`: The host user address.
/// - `event_description`: The description of the event.
/// - `event_end`: The end of the event, as blocknumber.
/// - `token_id`: The token id of the event.
/// - `conference_id`: The conference id of the event.
/// - `timeslot`: The timeslot of the event.
/// - `room`: The room of the event.
/// - `timezone`: The timezone of the event.
/// - `room_count`: The room count of the event.
/// - `timeslot_count`: The timeslot count of the event.
/// - `video_link`: The video link of the event.
#[pallet::call_index(2)]
#[pallet::weight({ 0 })]
pub fn create_event(
origin: OriginFor<T>,
event_name: BoundedVec<u8, StringLimitOf<T>>,
is_unconference: u32,
event_location: BoundedVec<u8, StringLimitOf<T>>,
host_user_address: BoundedVec<u8, StringLimitOf<T>>,
event_description: BoundedVec<u8, StringLimitOf<T>>,
start_time: u64,
end_time: u64,
token_id: AssetIdOf<T>,
conference_id: u32,
timeslot: u32,
room: u32,
session_length: Option<u64>,
approved: bool,
timezone: BoundedVec<u8, StringLimitOf<T>>,
room_count: u32,
timeslot_count: u32,
video_link: BoundedVec<u8, StringLimitOf<T>>,
minimun_balance: BalanceOf<T>
) -> DispatchResult {
let who = ensure_signed(origin)?;
// Get next event_id from storage.
let mut event_id = EventCount::<T>::get();
// Increment self by one, saturating.
event_id.saturating_inc();
// Create event details struct.
let event = EventDetails::new(
who.clone(),
event_id,
is_unconference,
event_name.clone(),
event_location,
host_user_address,
event_description,
start_time,
end_time,
token_id.clone(),
conference_id,
timeslot,
room,
session_length,
approved,
timezone,
room_count,
timeslot_count,
video_link,
minimun_balance
);
// Call inner function.
Self::try_create_event(event, event_id)?;
// Updates event count.
EventCount::<T>::put(event_id);
// Emit an event.
Self::deposit_event(Event::EventCreated {
token_id,
event_name,
event_id,
creator: who,
});
Ok(())
}
// /// Add an atendee to an event.
///
/// The dispatch origin of this must be _Signed_.
///
/// - `asset_id`: The asset id of the event.
/// - `event_id`: The event id of the event.
#[pallet::call_index(3)]
#[pallet::weight({ 0 })]
pub fn add_attendee(
origin: OriginFor<T>,
asset_id: AssetIdOf<T>,
event_id: T::EventIndex
) -> DispatchResult {
let who = ensure_signed(origin)?;
// Call inner function.
Self::try_add_attendee(asset_id.clone(), event_id, who.clone())?;
// Emit an event.
Self::deposit_event(Event::<T>::AtendeeAdded {
asset_id,
event_id,
attendee: who,
});
Ok(())
}
/// Remove an atendee from an event.
///
/// The dispatch origin of this must be _Signed_.
/// Can only be called by the owner of the event.
///
/// - `token_id`: The token id of the event.
/// - `event_id`: The event id of the event.
/// - `attendee`: The attendee account id to be removed.
#[pallet::call_index(4)]
#[pallet::weight({ 0 })]
pub fn remove_attendee(
origin: OriginFor<T>,
token_id: AssetIdOf<T>,
event_id: T::EventIndex,
attendee: T::AccountId
) -> DispatchResult {
let who = ensure_signed(origin)?;
// Call inner function.
Self::try_remove_attendee(
who.clone(),
token_id.clone(),
event_id,
attendee.clone()
)?;
// Emit an event.
Self::deposit_event(Event::<T>::AtendeeRemoved {
inviter: who,
token_id,
event_id,
attendee,
});
Ok(())
}
/// Update an event.
///
/// The dispatch origin of this must be _Signed_.
/// Can only be called by the owner of the event.
///
/// - `event_id`: The event id of the event.
/// - `event_name`: The name of the event.
/// - `event_location`: The location of the event.
/// - `host_user_address`: The host user address.
/// - `event_description`: The description of the event.
/// - `token_id`: The token id of the event.
/// - `conference_id`: The conference id of the event.
/// - `timeslot`: The timeslot of the event.
/// - `room`: The room of the event.
/// - `timezone`: The timezone of the event.
/// - `room_count`: The room count of the event.
/// - `timeslot_count`: The timeslot count of the event.
/// - `video_link`: The video link of the event.
#[pallet::call_index(5)]
#[pallet::weight({ 0 })]
pub fn update_event(
origin: OriginFor<T>,
event_id: T::EventIndex,
event_name: BoundedVec<u8, StringLimitOf<T>>,
event_location: BoundedVec<u8, StringLimitOf<T>>,
host_user_address: BoundedVec<u8, StringLimitOf<T>>,
event_description: BoundedVec<u8, StringLimitOf<T>>,
start_time: u64,
end_time: u64,
token_id: AssetIdOf<T>,
conference_id: u32,
timeslot: u32,
room: u32,
timezone: BoundedVec<u8, StringLimitOf<T>>,
room_count: u32,
timeslot_count: u32,
video_link: BoundedVec<u8, StringLimitOf<T>>,
min_balance: BalanceOf<T>
) -> DispatchResult {
let who = ensure_signed(origin)?;
// Call inner function.
Self::try_update_event(
&who,
event_id,
event_name.clone(),
event_location,
host_user_address,
event_description,
start_time,
end_time,
token_id.clone(),
conference_id,
timeslot,
room,
timezone,
room_count,
timeslot_count,
video_link,
min_balance
)?;
// Emit an event.
Self::deposit_event(Event::EventUpdated { token_id, event_id, creator: who });
Ok(())
}
/// Add a session to an event/conference.
///
// TODO: Verify a session can only have one speaker. so a session can´t call this function
#[pallet::call_index(7)]
#[pallet::weight({ 0 })]
pub fn add_session(
origin: OriginFor<T>,
event_id: T::EventIndex,
slot: u32,
timeslot: u32,
room: u32,
session_name: BoundedVec<u8, StringLimitOf<T>>,
session_location: BoundedVec<u8, StringLimitOf<T>>,
host_user_address: BoundedVec<u8, StringLimitOf<T>>,
session_description: BoundedVec<u8, StringLimitOf<T>>,
start_time: u64,
end_time: u64,
token_id: AssetIdOf<T>,
conference_id: u32,
timezone: BoundedVec<u8, StringLimitOf<T>>,
video_link: BoundedVec<u8, StringLimitOf<T>>,
minimun_balance: BalanceOf<T>
) -> DispatchResult {
let who = ensure_signed(origin)?;
// Get the event details.
let event = EventDetailsOf::<T>
::get(&token_id, event_id)
.ok_or(Error::<T>::EventNotFound)?;
// Ensure slots are more than zero
// Slots are calculated multiplying number of rooms by number of timeslots
let slots = event.room_count * event.timeslot_count;
ensure!(slots > 0, Error::<T>::InvalidEventDetails);
// Ensure there is not a session already in the same slot by callin sessionsOf and checking if the vector has a value in that index
let session_in_slot = SessionsOf::<T>::get(event_id, slot);
// Ensure session is none
ensure!(session_in_slot.is_none(), Error::<T>::TimeSlotAlreadyTaken);
// Get next event_id from storage.
let mut session_id = EventCount::<T>::get();
// Increment self by one, saturating.
session_id.saturating_inc();
let is_unconference = 2; // TODO: change this to a enum 0, 1, 2 (event, unconference, session)
// Create event details struct.
let event = EventDetails::new(
who.clone(),
session_id, // id of the session
is_unconference,
session_name.clone(), //event_name
session_location, //event_location
host_user_address,
session_description, //event_description
start_time,
end_time,
token_id.clone(),
conference_id,
timeslot,
room,
event.session_length,
false, //approved
timezone,
0, //room_count TODO: consider making this optional
0, //timeslot_count
video_link,
minimun_balance
);
// Try create event
Self::try_create_event(event, session_id)?;
// Add Session to storage
SessionsOf::<T>::insert(event_id, slot, session_id);
// Set new event count
EventCount::<T>::put(session_id);
// Emit an event.
Self::deposit_event(Event::SessionAdded {
event_id,
slot,
session_name,
session_id: event_id,
creator: who,
});
Ok(())
}
// TODO: handle the case when the session is already approved
// Aprove a session.
#[pallet::call_index(8)]
#[pallet::weight({ 0 })]
pub fn approve_session(
origin: OriginFor<T>,
token_id: AssetIdOf<T>,
event_id: T::EventIndex,
slot: u32
) -> DispatchResult {
let who = ensure_signed(origin)?;
// Get the session id.
let session_id = SessionsOf::<T>
::get(event_id, slot)
.ok_or(Error::<T>::EventNotFound)?;
// Get event details.
let event = EventDetailsOf::<T>
::get(&token_id, event_id)
.ok_or(Error::<T>::EventNotFound)?;
// Get session details
let mut session_details = EventDetailsOf::<T>
::get(&token_id, session_id)
.ok_or(Error::<T>::EventNotFound)?;
// Ensure caller is the creator of the event
ensure!(event.created_by == who, Error::<T>::NotEventCreator);
// Update session details.
session_details.approved = true;
// add session to storage of events
EventDetailsOf::<T>::insert(&token_id, session_id, session_details.clone());
// get session info
//let session = SessionsOf::<T>::get(event_id, slot).ok_or(Error::<T>::EventNotFound)?; // get in it twice?
let speaker = session_details.created_by;
let token_id = session_details.token_id;
// add speaker
let speakers = SpeakersOf::<T>::get((&token_id, &event_id));
// If speakers is none, create a new vector and add speaker. If speakers is some, add
// speaker to vector.
match speakers {
None => {
let speakers = vec![(slot, speaker.clone())];
SpeakersOf::<T>::insert((&token_id, &event_id), speakers);
}
Some(mut speakers) => {
// Ensure speaker was not already added to the same slot
ensure!(!speakers.contains(&(slot, speaker.clone())), Error::<T>::AlreadyAdded);
speakers.push((slot, speaker));
SpeakersOf::<T>::insert((token_id, event_id), speakers);
}
}
Ok(())
}
// reject a session and remove it from storage.
// TODO: a rejection should reduce the index
#[pallet::call_index(9)]
#[pallet::weight({ 0 })]
pub fn reject_session(
origin: OriginFor<T>,
token_id: AssetIdOf<T>,
event_id: T::EventIndex,
slot: u32
) -> DispatchResult {
let who = ensure_signed(origin)?;
// Get event details
let event = EventDetailsOf::<T>
::get(token_id.clone(), event_id)
.ok_or(Error::<T>::EventNotFound)?;
// Ensure caller is the creator of the event
ensure!(event.created_by == who, Error::<T>::NotEventCreator);
// Get the session details id.
let session_details_id = SessionsOf::<T>
::get(event_id, slot)
.ok_or(Error::<T>::EventNotFound)?;
// Remove session details from storage.
EventDetailsOf::<T>::remove(&token_id, session_details_id);
// Remove session from storage.
SessionsOf::<T>::remove(event_id, slot);
// Remove from speakers list
let mut speakers = SpeakersOf::<T>
::get((&token_id, &event_id))
.ok_or(Error::<T>::EventNotFound)?;
// Remove speaker from vector
speakers.retain(|(s, _)| *s != slot);
// Re insert speakers
SpeakersOf::<T>::insert((token_id, event_id), speakers);
Ok(())
}
/// Remove and event.
/// - `asset_id`: The asset id of the event.
/// - `event_id`: The event id of the event.
// TODO: First the user has to remove all sessions, then the event can be removed
#[pallet::call_index(10)]
#[pallet::weight({ 0 })]
pub fn remove_event(
origin: OriginFor<T>,
asset_id: AssetIdOf<T>,
event_id: T::EventIndex
) -> DispatchResult {
let who = ensure_signed(origin)?;
// Ensure caller is the owner of the event.
ensure!(Self::is_owner(who.clone(), asset_id.clone()), Error::<T>::NotEventCreator);
// Ensure event exists.
ensure!(
EventDetailsOf::<T>::contains_key(&asset_id, event_id),
Error::<T>::EventNotFound
);
// Ensure event has not sessions.
let sessions = SessionsOf::<T>::iter_prefix(event_id).count();
ensure!(sessions == 0, Error::<T>::CantRemoveEventWithSessions);
// Remove event from storage.
EventDetailsOf::<T>::remove(&asset_id, event_id);
// Remove all attendees of the event.
AttendeesOf::<T>::remove((&asset_id, &event_id));
// Remove all speakers of the event.
SpeakersOf::<T>::remove((&asset_id, &event_id));
// Remove all Scores related to the event.
let res_score = ScoreOf::<T>::clear_prefix((&asset_id, &event_id), 100, None); // TODO: replace 100 with max parameters
ensure!(res_score.maybe_cursor.is_none(), Error::<T>::ScoreOfNotCleared);
// Remove all attendees balances related to the event.
let res_balance = BalanceOfAttendee::<T>::clear_prefix((&asset_id, &event_id), 100, None);
ensure!(res_balance.maybe_cursor.is_none(), Error::<T>::BalanceOfAttendeeNotCleared);
// Remove all sessions for to the event.
let res_sesssion = SessionsOf::<T>::clear_prefix(event_id, 100, None);
ensure!(res_sesssion.maybe_cursor.is_none(), Error::<T>::SessionsOfNotCleared);
// Emit an event.
Self::deposit_event(Event::EventRemoved {
asset_id,
event_id,
creator: who,
});
Ok(())
}
}
impl<T: Config> Pallet<T> {
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 an event.
fn try_create_event(event: EventTypeOf<T>, event_id: T::EventIndex) -> DispatchResult {
let token_id = event.token_id.clone();
Self::ensure_asset_exist(token_id.clone())?;
let creator = event.created_by.clone();
Self::ensure_is_owner(creator, token_id.clone())?;
// if event has is_unconference = 2, don´t check for period validation
if event.is_unconference != 2 {
// Ensure start and end are valid.
let start = event.start_time;
let end = event.end_time;
Self::validate_event_period(start, end)?;
}
// Ensure event name is not empty.
//ensure!(!event.event_name.is_empty(), Error::<T>::EmptyEventName);
EventDetailsOf::<T>::insert(&event.token_id, event_id, event.clone());
Ok(())
}
/// Update details of a event only it is not ongoing yet. Can only be done by the creator
// TODO: only allow the creator to update the event if it is open
fn try_update_event(
who: &T::AccountId,
event_id: T::EventIndex,
event_name: BoundedVec<u8, StringLimitOf<T>>,
event_location: BoundedVec<u8, StringLimitOf<T>>,
host_user_address: BoundedVec<u8, StringLimitOf<T>>,
event_description: BoundedVec<u8, StringLimitOf<T>>,
start_time: u64,
end_time: u64,
token_id: AssetIdOf<T>,
conference_id: u32,
timeslot: u32,
room: u32,
timezone: BoundedVec<u8, StringLimitOf<T>>,
room_count: u32,
timeslot_count: u32,
video_link: BoundedVec<u8, StringLimitOf<T>>,
min_balance: BalanceOf<T>
) -> DispatchResult {
Self::validate_event_period(start_time, end_time)?;
// Get event details.
let mut event = EventDetailsOf::<T>
::get(&token_id, event_id)
.ok_or(Error::<T>::EventNotFound)?;
// Ensure event creator is the same as who is calling.
ensure!(event.created_by == *who, Error::<T>::NotEventCreator);
let start = event.start_time;
let now = TimeProviderOf::<T>::now().as_secs();
// Ensure event has not started yet.
ensure!(now < start, Error::<T>::EventIsNoLongerEditable);
// Ensure event name is not empty.
ensure!(!event_name.is_empty(), Error::<T>::EmptyEventName);
// Update event details.
event.event_name = event_name;
event.event_location = event_location;
event.host_user_address = host_user_address;
event.event_description = event_description;
event.token_id = token_id.clone();
event.conference_id = conference_id;
event.timeslot = timeslot;
event.room = room;
event.start_time = start_time;
event.end_time = end_time;
event.timezone = timezone;
event.room_count = room_count;
event.timeslot_count = timeslot_count;
event.video_link = video_link;
event.min_balance = min_balance;
// Update event details in storage.
EventDetailsOf::<T>::insert(&token_id, event_id, event.clone());
// Emit an event.
Self::deposit_event(Event::EventUpdated { token_id, event_id, creator: who.clone() });
Ok(())
}
fn check_balance_above_minimum(
who: &T::AccountId,
asset_id: AssetIdOf<T>,
minimum_balance: BalanceOf<T>
) -> bool {
<T::Fungibles as Inspect<T::AccountId>>::balance(asset_id, who) >= minimum_balance
}
fn try_add_attendee(
token_id: AssetIdOf<T>,
event_id: T::EventIndex,
attendee: T::AccountId
) -> DispatchResult {
// Get event details.
let event = EventDetailsOf::<T>
::get(&token_id, event_id)
.ok_or(Error::<T>::EventNotFound)?;
// Check account has enough balance
ensure!(
Self::check_balance_above_minimum(&attendee, token_id.clone(), event.min_balance),
Error::<T>::NotTokenHolder
);
let start = event.start_time;
let now = TimeProviderOf::<T>::now().as_secs();
// Ensure event has not started yet.
ensure!(now < start, Error::<T>::EventIsNoLongerEditable);
let attendess = AttendeesOf::<T>::get((&token_id, &event_id));
// If attendee is none, create a new vector and add attendee. If attendee is some, add
// attendee to vector.
match attendess {
None => {
let attendees = vec![attendee.clone()];
AttendeesOf::<T>::insert((&token_id, &event_id), attendees);
}
Some(mut attendees) => {
// Ensure attende was not already added
ensure!(!attendees.contains(&attendee), Error::<T>::AlreadyAdded);
attendees.push(attendee.clone());
AttendeesOf::<T>::insert((&token_id, &event_id), attendees);
}
}
// When an attendee is added, we need to update the score of the inviter
// Who is the inviter of the current attendee?
// Check in the profile of the attendee who is the inviter
let attendee_profile = pallet_profile::UserItem::<T>
::get(attendee.clone())
.ok_or(Error::<T>::InvalidUserId)?;
let attendee_inviter = attendee_profile.invited_by_user_id;
// how much Unit is the attendee holding?
let attendee_holding = <T::Fungibles as Inspect<T::AccountId>>::balance(
token_id.clone(),
&attendee
);
// Update score of the inviter
//let mut score_vector = ScoreOf::<T>::get((token_id, event_id, attendee_inviter.clone())).ok_or(Error::<T>::EventNotFound)?;
let score_vector = ScoreOf::<T>::get((&token_id, &event_id), attendee_inviter.clone());
match score_vector {
None => {
let score_vector = EventScore::new(1, attendee_holding);
ScoreOf::<T>::insert(
(&token_id, &event_id),
attendee_inviter.clone(),
score_vector
);
}
Some(mut score_vector) => {
// Add one to the number of attendees
score_vector.invited += 1;
// Add the amount of unit holding
score_vector.holding += attendee_holding;
// Update the score
ScoreOf::<T>::insert(
(&token_id, &event_id),
attendee_inviter.clone(),
score_vector
);
}
}
// Update balance of the attendee
BalanceOfAttendee::<T>::insert(
(token_id, event_id),
attendee.clone(),
attendee_holding
);
Ok(())
}
// Function to remove attendee from event
fn try_remove_attendee(
who: T::AccountId,
token_id: AssetIdOf<T>,
event_id: T::EventIndex,
attendee: T::AccountId
) -> DispatchResult {
// ensure caller is the owner of the token
ensure!(Self::is_owner(who, token_id.clone()), Error::<T>::NotTokenOwner);
// Get attendees
let attendess = AttendeesOf::<T>::get((&token_id, &event_id));
// If attendee is none, create a new vector and add attendee. If attendee is some, add
// attendee to vector.
match attendess {
None => {
return Err(Error::<T>::EventNotFound.into());
}
Some(mut attendees) => {
// Remove attendee from vector
attendees.retain(|x| *x != attendee);
AttendeesOf::<T>::insert((&token_id, &event_id), attendees);
}
}
// When an attendee is removed, we need to update the score of the inviter
// Who is the inviter of the current attendee?
// Check in the profile of the attendee who is the inviter
let attendee_profile = pallet_profile::UserItem::<T>
::get(attendee.clone())
.ok_or(Error::<T>::InvalidUserId)?;
let attendee_inviter = attendee_profile.invited_by_user_id;
// how much Unit is the attendee was holding?
let attendee_holding = BalanceOfAttendee::<T>
::get((&token_id, &event_id), attendee.clone())
.ok_or(Error::<T>::InvalidUserId)?;
// Update score of the inviter
let mut score_vector = ScoreOf::<T>
::get((&token_id, &event_id), attendee_inviter.clone())
.ok_or(Error::<T>::EventNotFound)?;
// Remove one to the number of attendees
score_vector.invited -= 1;
// Remove the amount of unit holding
score_vector.holding -= attendee_holding;
// Update the score
ScoreOf::<T>::insert((&token_id, &event_id), attendee_inviter.clone(), score_vector);
// Remove balance of the attendee
BalanceOfAttendee::<T>::remove((token_id, event_id), attendee.clone());
Ok(())
}
// Function to ensure it is a valid event period
fn validate_event_period(start: u64, end: u64) -> DispatchResult {
let now = TimeProviderOf::<T>::now().as_secs();
ensure!(start >= now && end > now && end > start, Error::<T>::InvalidEventPeriod);
Ok(())
}
// Function to ensure the asset exist
fn ensure_asset_exist(token_id: AssetIdOf<T>) -> DispatchResult {
// Ensure total issuance is not zero.
let total_issuance = <T::Fungibles as Inspect<T::AccountId>>::total_issuance(token_id);
ensure!(total_issuance > Zero::zero(), Error::<T>::ZeroTotalIssuance);
Ok(())
}
// Function to ensure the caller is the owner of the token
fn ensure_is_owner(who: T::AccountId, token_id: AssetIdOf<T>) -> DispatchResult {
// ensure caller is the owner of the token
ensure!(Self::is_owner(who, token_id), Error::<T>::NotTokenOwner);
Ok(())
}
}
}