use super::*;
use crate::types::*;
use frame_support::{pallet_prelude::*, traits::UnixTime};
use frame_system::pallet_prelude::*;
use pallet_fruniques::types::{Attributes, CollectionDescription, FruniqueRole, ParentInfo};
use pallet_gated_marketplace::types::MarketplaceRole;
use core::convert::TryInto;
use frame_support::sp_io::hashing::blake2_256;
use frame_support::traits::Time;
use pallet_mapped_assets::DebitFlags;
use pallet_rbac::types::IdOrVec;
use pallet_rbac::types::RoleBasedAccessControl;
use pallet_rbac::types::RoleId;
use scale_info::prelude::vec;
use sp_runtime::sp_std::str;
use sp_runtime::sp_std::vec::Vec;
use sp_runtime::traits::Zero;
impl<T: Config> Pallet<T> {
pub fn do_initial_setup(creator: T::AccountId, admin: T::AccountId) -> DispatchResult {
Self::initialize_rbac()?;
let creator_user: User<T> = User {
cid: ShortString::try_from(b"afloat".to_vec()).unwrap(),
cid_creator: ShortString::try_from(b"HCD:afloat".to_vec()).unwrap(),
group: ShortString::try_from(b"afloat".to_vec()).unwrap(),
created_by: Some(creator.clone()),
created_date: Some(T::TimeProvider::now().as_secs()),
last_modified_by: Some(creator.clone()),
last_modified_date: Some(T::TimeProvider::now().as_secs()),
};
<UserInfo<T>>::insert(creator.clone(), creator_user);
Self::give_role_to_user(creator.clone(), AfloatRole::Owner)?;
if admin != creator {
let admin_user: User<T> = User {
cid: ShortString::try_from(b"afloat".to_vec()).unwrap(),
cid_creator: ShortString::try_from(b"afloat".to_vec()).unwrap(),
group: ShortString::try_from(b"afloat".to_vec()).unwrap(),
created_by: Some(admin.clone()),
created_date: Some(T::TimeProvider::now().as_secs()),
last_modified_by: Some(admin.clone()),
last_modified_date: Some(T::TimeProvider::now().as_secs()),
};
<UserInfo<T>>::insert(admin.clone(), admin_user);
Self::give_role_to_user(admin, AfloatRole::Admin)?;
}
Ok(())
}
pub fn do_create_user(
actor: T::AccountId,
user_address: T::AccountId,
args: SignUpArgs,
) -> DispatchResult {
ensure!(!<UserInfo<T>>::contains_key(user_address.clone()), Error::<T>::UserAlreadyExists);
match args {
SignUpArgs::BuyerOrSeller { cid, cid_creator, group } => {
let user: User<T> = User {
cid,
cid_creator,
group,
created_by: Some(actor.clone()),
created_date: Some(T::TimeProvider::now().as_secs()),
last_modified_by: Some(actor.clone()),
last_modified_date: Some(T::TimeProvider::now().as_secs()),
};
<UserInfo<T>>::insert(user_address.clone(), user);
Self::give_role_to_user(user_address.clone(), AfloatRole::BuyerOrSeller)?;
Self::deposit_event(Event::NewUser(user_address.clone()));
},
SignUpArgs::CPA { cid, cid_creator, group } => {
let user: User<T> = User {
cid,
cid_creator,
group,
created_by: Some(actor.clone()),
created_date: Some(T::TimeProvider::now().as_secs()),
last_modified_by: Some(actor.clone()),
last_modified_date: Some(T::TimeProvider::now().as_secs()),
};
<UserInfo<T>>::insert(user_address.clone(), user);
Self::give_role_to_user(user_address.clone(), AfloatRole::CPA)?;
Self::deposit_event(Event::NewUser(user_address.clone()));
},
}
let marketplace_id =
AfloatMarketPlaceId::<T>::get().ok_or(Error::<T>::MarketPlaceIdNotFound)?;
Self::add_to_afloat_collection(user_address.clone(), FruniqueRole::Collaborator)?;
pallet_gated_marketplace::Pallet::<T>::self_enroll(user_address, marketplace_id)?;
Ok(())
}
pub fn do_edit_user(
actor: T::AccountId,
user_address: T::AccountId,
cid: ShortString,
cid_creator: ShortString,
) -> DispatchResult {
<UserInfo<T>>::try_mutate::<_, _, DispatchError, _>(user_address.clone(), |user| {
let user = user.as_mut().ok_or(Error::<T>::FailedToEditUserAccount)?;
user.last_modified_date = Some(T::TimeProvider::now().as_secs());
user.last_modified_by = Some(actor.clone());
user.cid = cid;
user.cid_creator = cid_creator;
Ok(())
})?;
Ok(())
}
pub fn do_admin_edit_user(
actor: T::AccountId,
user_address: T::AccountId,
cid: ShortString,
cid_creator: ShortString,
group: ShortString,
) -> DispatchResult {
<UserInfo<T>>::try_mutate::<_, _, DispatchError, _>(user_address.clone(), |user| {
let user = user.as_mut().ok_or(Error::<T>::FailedToEditUserAccount)?;
user.last_modified_date = Some(T::TimeProvider::now().as_secs());
user.last_modified_by = Some(actor.clone());
user.cid = cid;
user.cid_creator = cid_creator;
user.group = group;
Ok(())
})?;
Ok(())
}
pub fn do_delete_user(_actor: T::AccountId, user_address: T::AccountId) -> DispatchResult {
Self::remove_from_afloat_collection(user_address.clone(), FruniqueRole::Collaborator)?;
Self::remove_from_afloat_marketplace(user_address.clone())?;
let user_roles = Self::get_all_roles_for_user(user_address.clone())?;
if !user_roles.is_empty() {
for role in user_roles {
Self::remove_role_from_user(user_address.clone(), role)?;
}
}
<UserInfo<T>>::remove(user_address.clone());
Self::deposit_event(Event::UserDeleted(user_address.clone()));
Ok(())
}
pub fn do_set_afloat_balance(
origin: OriginFor<T>,
user_address: T::AccountId,
amount: T::Balance,
) -> DispatchResult {
let authority = ensure_signed(origin.clone())?;
let asset_id = AfloatAssetId::<T>::get().expect("AfloatAssetId should be set");
let debit_flags = DebitFlags { keep_alive: false, best_effort: true };
ensure!(UserInfo::<T>::contains_key(user_address.clone()), Error::<T>::UserNotFound);
let current_balance = Self::do_get_afloat_balance(user_address.clone());
if current_balance > amount {
let diff = current_balance - amount;
pallet_mapped_assets::Pallet::<T>::afloat_do_burn(
asset_id.into(),
&user_address.clone(),
diff,
Some(authority.clone()),
debit_flags,
)?;
} else if current_balance < amount {
let diff = amount - current_balance;
pallet_mapped_assets::Pallet::<T>::afloat_do_mint(
asset_id.into(),
&user_address.clone(),
diff,
Some(authority.clone()),
)?;
}
Self::deposit_event(Event::AfloatBalanceSet(authority, user_address, amount));
Ok(())
}
pub fn do_get_afloat_balance(user_address: T::AccountId) -> T::Balance {
let asset_id = AfloatAssetId::<T>::get().expect("AfloatAssetId should be set");
pallet_mapped_assets::Pallet::<T>::balance(asset_id.into(), user_address)
}
pub fn do_create_sell_order(
authority: T::AccountId,
item_id: <T as pallet_uniques::Config>::ItemId,
price: T::Balance,
tax_credit_amount: u32,
expiration_date: Date,
) -> DispatchResult {
let maybe_roles = Self::get_all_roles_for_user(authority.clone())?;
ensure!(!maybe_roles.is_empty(), Error::<T>::Unauthorized);
let transactions = TransactionBoundedVec::default();
let offer: Offer<T> = Offer {
tax_credit_amount,
tax_credit_amount_remaining: tax_credit_amount.into(),
price_per_credit: price,
creation_date: T::TimeProvider::now().as_secs(),
expiration_date,
tax_credit_id: item_id,
creator_id: authority.clone(),
status: OfferStatus::default(),
offer_type: OfferType::Sell,
cancellation_date: None,
transactions,
};
let offer_id = offer.using_encoded(blake2_256);
<AfloatOffers<T>>::insert(offer_id, offer);
Self::deposit_event(Event::SellOrderCreated(authority));
Ok(())
}
pub fn do_create_buy_order(
authority: T::AccountId,
item_id: <T as pallet_uniques::Config>::ItemId,
price: T::Balance,
tax_credit_amount: u32,
expiration_date: Date,
) -> DispatchResult {
let maybe_roles = Self::get_all_roles_for_user(authority.clone())?;
ensure!(!maybe_roles.is_empty(), Error::<T>::Unauthorized);
let transactions = TransactionBoundedVec::default();
let offer: Offer<T> = Offer {
tax_credit_amount,
tax_credit_amount_remaining: tax_credit_amount.into(),
price_per_credit: price,
creation_date: T::TimeProvider::now().as_secs(),
expiration_date,
tax_credit_id: item_id,
creator_id: authority.clone(),
status: OfferStatus::default(),
offer_type: OfferType::Buy,
cancellation_date: None,
transactions,
};
let offer_id = offer.using_encoded(blake2_256);
<AfloatOffers<T>>::insert(offer_id, offer);
Self::deposit_event(Event::BuyOrderCreated(authority));
Ok(())
}
pub fn do_start_take_sell_order(
authority: OriginFor<T>,
order_id: [u8; 32],
tax_credit_amount: T::Balance,
) -> DispatchResult
where
<T as pallet_uniques::Config>::ItemId: From<u32>,
{
let who = ensure_signed(authority.clone())?;
let maybe_roles = Self::get_all_roles_for_user(who.clone())?;
ensure!(!maybe_roles.is_empty(), Error::<T>::Unauthorized);
ensure!(<AfloatOffers<T>>::contains_key(order_id), Error::<T>::OfferNotFound);
let offer = <AfloatOffers<T>>::get(order_id).unwrap();
ensure!(offer.offer_type == OfferType::Sell, Error::<T>::WrongOfferType);
ensure!(offer.expiration_date > T::TimeProvider::now().as_secs(), Error::<T>::OfferExpired);
ensure!(offer.cancellation_date.is_none(), Error::<T>::OfferCancelled);
ensure!(offer.status == OfferStatus::default(), Error::<T>::OfferTaken);
ensure!(
offer.tax_credit_amount_remaining >= tax_credit_amount,
Error::<T>::NotEnoughTaxCreditsAvailable
);
ensure!(
Self::do_get_afloat_balance(who.clone()) >= offer.price_per_credit * tax_credit_amount.into(),
Error::<T>::NotEnoughAfloatBalanceAvailable
);
let zero_balance: T::Balance = Zero::zero();
ensure!(tax_credit_amount > zero_balance, Error::<T>::Underflow);
let creation_date: u64 = T::Timestamp::now().into();
let price_per_credit: T::Balance = offer.price_per_credit.into();
let total_price: T::Balance = price_per_credit * tax_credit_amount;
let fee: Option<T::Balance> = None;
let tax_credit_id: <T as pallet_uniques::Config>::ItemId = offer.tax_credit_id;
let seller_id: T::AccountId = offer.creator_id;
let buyer_id: T::AccountId = who.clone();
let offer_id: StorageId = order_id;
let seller_confirmation_date: Option<Date> = None;
let buyer_confirmation_date: Option<Date> = Some(creation_date);
let confirmed: bool = false;
let completed: bool = false;
let child_offer_id: Option<StorageId> = None;
let transaction = Transaction {
tax_credit_amount,
price_per_credit,
total_price,
fee,
creation_date,
cancellation_date: None,
tax_credit_id,
seller_id,
buyer_id,
offer_id,
child_offer_id,
seller_confirmation_date,
buyer_confirmation_date,
confirmed,
completed,
};
let transaction_id = transaction.clone().using_encoded(blake2_256);
<AfloatOffers<T>>::try_mutate(order_id, |offer| -> DispatchResult {
let offer = offer.as_mut().ok_or(Error::<T>::OfferNotFound)?;
offer
.transactions
.try_push(transaction_id.clone())
.map_err(|_| Error::<T>::MaxTransactionsReached)?;
Ok(())
})?;
<AfloatTransactions<T>>::insert(transaction_id, transaction);
Ok(())
}
pub fn do_confirm_sell_transaction(
authority: OriginFor<T>,
transaction_id: [u8; 32],
) -> DispatchResult
where
<T as pallet_uniques::Config>::ItemId: From<u32>,
{
let who = ensure_signed(authority.clone())?;
let maybe_roles = Self::get_all_roles_for_user(who.clone())?;
ensure!(!maybe_roles.is_empty(), Error::<T>::Unauthorized);
ensure!(<AfloatTransactions<T>>::contains_key(transaction_id), Error::<T>::TransactionNotFound);
let transaction =
<AfloatTransactions<T>>::get(transaction_id).ok_or(Error::<T>::TransactionNotFound)?;
ensure!(transaction.seller_id == who.clone(), Error::<T>::Unauthorized);
ensure!(transaction.cancellation_date.is_none(), Error::<T>::TransactionCancelled);
ensure!(
transaction.seller_confirmation_date.is_none(),
Error::<T>::TransactionAlreadyConfirmedBySeller
);
ensure!(
transaction.buyer_confirmation_date.is_some(),
Error::<T>::TransactionNotConfirmedByBuyer
);
let confirmation_date: u64 = T::Timestamp::now().into();
let confirmed: bool = true;
let marketplace_id =
<AfloatMarketPlaceId<T>>::get().ok_or(Error::<T>::MarketPlaceIdNotFound)?;
let collection_id = <AfloatCollectionId<T>>::get().ok_or(Error::<T>::CollectionIdNotFound)?;
let tax_credit_amount_u32 = if let Ok(amount) = transaction.tax_credit_amount.try_into() {
amount
} else {
return Err(Error::<T>::TaxCreditAmountOverflow.into());
};
let child_offer_id = pallet_gated_marketplace::Pallet::<T>::do_enlist_sell_offer(
who,
marketplace_id,
collection_id,
transaction.tax_credit_id,
transaction.total_price,
tax_credit_amount_u32,
)?;
<AfloatTransactions<T>>::try_mutate(transaction_id, |transaction| -> DispatchResult {
let mut transaction = transaction.as_mut().ok_or(Error::<T>::TransactionNotFound)?;
transaction.seller_confirmation_date = Some(confirmation_date);
transaction.confirmed = confirmed;
transaction.child_offer_id = Some(child_offer_id);
Ok(())
})?;
Ok(())
}
pub fn do_finish_take_sell_transaction(
authority: OriginFor<T>,
transaction_id: [u8; 32],
) -> DispatchResult
where
<T as pallet_uniques::Config>::ItemId: From<u32>,
{
let who = ensure_signed(authority.clone())?;
let maybe_roles = Self::get_all_roles_for_user(who.clone())?;
ensure!(!maybe_roles.is_empty(), Error::<T>::Unauthorized);
ensure!(<AfloatTransactions<T>>::contains_key(transaction_id), Error::<T>::TransactionNotFound);
let transaction =
<AfloatTransactions<T>>::get(transaction_id).ok_or(Error::<T>::TransactionNotFound)?;
ensure!(transaction.cancellation_date.is_none(), Error::<T>::TransactionCancelled);
ensure!(transaction.confirmed, Error::<T>::TransactionNotConfirmed);
let child_offer_id = transaction.child_offer_id.ok_or(Error::<T>::ChildOfferIdNotFound)?;
let offer_id = transaction.offer_id;
pallet_gated_marketplace::Pallet::<T>::do_take_sell_offer(authority.clone(), child_offer_id)?;
<AfloatOffers<T>>::try_mutate(offer_id, |offer| -> DispatchResult {
let offer = offer.as_mut().ok_or(Error::<T>::OfferNotFound)?;
if transaction.tax_credit_amount > offer.tax_credit_amount_remaining {
return Err(Error::<T>::Underflow.into());
}
offer.tax_credit_amount_remaining =
offer.tax_credit_amount_remaining - transaction.tax_credit_amount;
Ok(())
})?;
<AfloatTransactions<T>>::try_mutate(transaction_id, |transaction| -> DispatchResult {
let mut transaction = transaction.as_mut().ok_or(Error::<T>::TransactionNotFound)?;
transaction.completed = true;
Ok(())
})?;
Self::deposit_event(Event::SellOrderTaken(who));
Ok(())
}
pub fn do_take_buy_order(authority: T::AccountId, order_id: [u8; 32]) -> DispatchResult
where
<T as pallet_uniques::Config>::ItemId: From<u32>,
{
let maybe_roles = Self::get_all_roles_for_user(authority.clone())?;
ensure!(!maybe_roles.is_empty(), Error::<T>::Unauthorized);
pallet_gated_marketplace::Pallet::<T>::do_take_buy_offer(authority.clone(), order_id)?;
Self::deposit_event(Event::BuyOrderTaken(authority));
Ok(())
}
pub fn do_create_tax_credit(
owner: T::AccountId,
metadata: CollectionDescription<T>,
attributes: Option<Attributes<T>>,
parent_info: Option<ParentInfo<T>>,
) -> DispatchResult
where
<T as pallet_uniques::Config>::ItemId: From<u32>,
<T as pallet_uniques::Config>::CollectionId: From<u32>,
{
let maybe_roles = Self::get_all_roles_for_user(owner.clone())?;
ensure!(!maybe_roles.is_empty(), Error::<T>::Unauthorized);
let collection = AfloatCollectionId::<T>::get().ok_or(Error::<T>::CollectionIdNotFound)?;
pallet_fruniques::Pallet::<T>::do_spawn(collection, owner, metadata, attributes, parent_info)
}
pub fn create_afloat_collection(
origin: OriginFor<T>,
metadata: CollectionDescription<T>,
admin: T::AccountId,
) -> DispatchResult
where
<T as pallet_uniques::Config>::CollectionId: From<u32>,
{
let collection_id =
pallet_fruniques::Pallet::<T>::do_create_collection(origin.clone(), metadata, admin.clone());
if let Ok(collection_id) = collection_id {
AfloatCollectionId::<T>::put(collection_id);
Ok(())
} else {
return Err(Error::<T>::FailedToCreateFruniquesCollection.into());
}
}
pub fn add_to_afloat_collection(invitee: T::AccountId, role: FruniqueRole) -> DispatchResult {
let collection_id = AfloatCollectionId::<T>::get().ok_or(Error::<T>::CollectionIdNotFound)?;
pallet_fruniques::Pallet::<T>::insert_auth_in_frunique_collection(invitee, collection_id, role)
}
pub fn remove_from_afloat_collection(
invitee: T::AccountId,
role: FruniqueRole,
) -> DispatchResult {
let collection_id = AfloatCollectionId::<T>::get().ok_or(Error::<T>::CollectionIdNotFound)?;
pallet_fruniques::Pallet::<T>::remove_auth_from_frunique_collection(
invitee,
collection_id,
role,
)
}
pub fn remove_from_afloat_marketplace(invitee: T::AccountId) -> DispatchResult {
let marketplace_id =
AfloatMarketPlaceId::<T>::get().ok_or(Error::<T>::MarketPlaceIdNotFound)?;
pallet_gated_marketplace::Pallet::<T>::remove_from_market_lists(
invitee,
MarketplaceRole::Participant,
marketplace_id,
)
}
pub fn is_admin_or_owner(account: T::AccountId) -> Result<bool, DispatchError> {
let maybe_super_role = <T as pallet::Config>::Rbac::has_role(
account.clone(),
Self::pallet_id(),
&Self::scope_id(),
[AfloatRole::Admin.id(), AfloatRole::Owner.id()].to_vec(),
);
Ok(maybe_super_role.is_ok())
}
pub fn is_owner(account: T::AccountId) -> Result<bool, DispatchError> {
let maybe_owner = <T as pallet::Config>::Rbac::has_role(
account.clone(),
Self::pallet_id(),
&Self::scope_id(),
[AfloatRole::Owner.id()].to_vec(),
);
Ok(maybe_owner.is_ok())
}
pub fn is_cpa(account: T::AccountId) -> Result<bool, DispatchError> {
let maybe_cpa = <T as pallet::Config>::Rbac::has_role(
account.clone(),
Self::pallet_id(),
&Self::scope_id(),
[AfloatRole::CPA.id()].to_vec(),
);
Ok(maybe_cpa.is_ok())
}
pub fn give_role_to_user(authority: T::AccountId, role: AfloatRole) -> DispatchResult {
<T as pallet::Config>::Rbac::assign_role_to_user(
authority,
Self::pallet_id(),
&Self::scope_id(),
role.id(),
)?;
Ok(())
}
pub fn do_add_afloat_admin(
authority: T::AccountId,
user_address: T::AccountId,
) -> DispatchResult {
ensure!(UserInfo::<T>::contains_key(user_address.clone()), Error::<T>::UserNotFound);
Self::give_role_to_user(user_address.clone(), AfloatRole::Admin)?;
Self::deposit_event(Event::AdminAdded(authority, user_address));
Ok(())
}
pub fn remove_role_from_user(authority: T::AccountId, role: AfloatRole) -> DispatchResult {
<T as pallet::Config>::Rbac::remove_role_from_user(
authority,
Self::pallet_id(),
&Self::scope_id(),
role.id(),
)?;
Ok(())
}
fn scope_id() -> [u8; 32] {
"AfloatScope".as_bytes().using_encoded(blake2_256)
}
fn pallet_id() -> IdOrVec {
IdOrVec::Vec("AfloatPallet".as_bytes().to_vec())
}
pub fn remove_rbac_permissions() -> DispatchResult {
<T as pallet::Config>::Rbac::remove_pallet_storage(Self::pallet_id())?;
Ok(())
}
pub fn initialize_rbac() -> DispatchResult {
let pallet_id = Self::pallet_id();
let scope_id = Self::scope_id();
<T as pallet::Config>::Rbac::create_scope(Self::pallet_id(), scope_id)?;
let super_roles = vec![AfloatRole::Owner.to_vec(), AfloatRole::Admin.to_vec()];
let super_role_ids =
<T as pallet::Config>::Rbac::create_and_set_roles(pallet_id.clone(), super_roles)?;
let super_permissions = Permission::admin_permissions();
for super_role in super_role_ids {
<T as pallet::Config>::Rbac::create_and_set_permissions(
pallet_id.clone(),
super_role,
super_permissions.clone(),
)?;
}
let participant_roles = vec![AfloatRole::BuyerOrSeller.to_vec(), AfloatRole::CPA.to_vec()];
<T as pallet::Config>::Rbac::create_and_set_roles(pallet_id.clone(), participant_roles)?;
Ok(())
}
fn role_id_to_afloat_role(role_id: RoleId) -> Option<AfloatRole> {
AfloatRole::enum_to_vec()
.iter()
.find(|role_bytes| role_bytes.using_encoded(blake2_256) == role_id)
.map(|role_bytes| {
let role_str = str::from_utf8(role_bytes).expect("Role bytes should be valid UTF-8");
match role_str {
"Owner" => AfloatRole::Owner,
"Admin" => AfloatRole::Admin,
"BuyerOrSeller" => AfloatRole::BuyerOrSeller,
"CPA" => AfloatRole::CPA,
_ => panic!("Unexpected role string"),
}
})
}
fn get_all_roles_for_user(account_id: T::AccountId) -> Result<Vec<AfloatRole>, DispatchError> {
let roles_storage = <T as pallet::Config>::Rbac::get_roles_by_user(
account_id.clone(),
Self::pallet_id(),
&Self::scope_id(),
);
Ok(roles_storage.into_iter().filter_map(Self::role_id_to_afloat_role).collect())
}
pub fn do_delete_all_users() -> DispatchResult {
UserInfo::<T>::iter_keys().try_for_each(|account_id| {
let is_admin_or_owner = Self::is_admin_or_owner(account_id.clone())?;
if !is_admin_or_owner {
let user_roles = Self::get_all_roles_for_user(account_id.clone())?;
if !user_roles.is_empty() {
for role in user_roles {
Self::remove_role_from_user(account_id.clone(), role)?;
}
}
Self::remove_from_afloat_collection(account_id.clone(), FruniqueRole::Collaborator)?;
Self::remove_from_afloat_marketplace(account_id.clone())?;
UserInfo::<T>::remove(account_id);
}
Ok::<(), DispatchError>(())
})?;
Ok(())
}
}