#![allow(clippy::string_lit_as_bytes)]
#![allow(clippy::redundant_closure_call)]
#![allow(clippy::type_complexity)]
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
use util::{
share::{AtomicShareProfile, SimpleShareGenesis},
traits::{
AccessGenesis, ChainSudoPermissions, ChangeGroupMembership, GenerateUniqueID, GetGroupSize,
GroupMembership, IDIsAvailable, LockableProfile, OrganizationSupervisorPermissions,
ReservableProfile, SeededGenerateUniqueID, ShareBank, SubGroupSupervisorPermissions,
VerifyShape, WeightedShareGroup,
},
uuid::UUID2,
};
use codec::Codec;
use frame_support::{
decl_error, decl_event, decl_module, decl_storage, ensure, storage::IterableStorageDoubleMap,
traits::Get, Parameter,
};
use frame_system::{self as system, ensure_signed};
use sp_runtime::{
traits::{
AtLeast32Bit, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Member, Saturating, Zero,
},
DispatchError, DispatchResult,
};
use sp_std::{fmt::Debug, prelude::*};
pub type OrgId = u32;
pub type ShareId = u32;
pub trait Trait: system::Trait {
type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
type OrgData: GetGroupSize<GroupId = u32>
+ GroupMembership<Self::AccountId>
+ IDIsAvailable<OrgId>
+ GenerateUniqueID<OrgId>
+ ChainSudoPermissions<Self::AccountId>
+ OrganizationSupervisorPermissions<u32, Self::AccountId>
+ ChangeGroupMembership<Self::AccountId>;
type Shares: Parameter
+ Member
+ AtLeast32Bit
+ Codec
+ Default
+ Copy
+ MaybeSerializeDeserialize
+ Debug
+ PartialOrd
+ CheckedSub
+ Zero;
type ReservationLimit: Get<u32>;
}
decl_event!(
pub enum Event<T>
where
<T as frame_system::Trait>::AccountId,
<T as Trait>::Shares,
{
SharesReserved(OrgId, ShareId, AccountId, u32),
SharesUnReserved(OrgId, ShareId, AccountId, u32),
SharesLocked(OrgId, ShareId, AccountId),
SharesUnlocked(OrgId, ShareId, AccountId),
SharesIssued(OrgId, ShareId, AccountId, Shares),
SharesBurned(OrgId, ShareId, AccountId, Shares),
SharesBatchIssued(OrgId, ShareId, Shares),
SharesBatchBurned(OrgId, ShareId, Shares),
TotalSharesIssued(OrgId, ShareId, Shares),
}
);
decl_error! {
pub enum Error for Module<T: Trait> {
LogicBugShouldBeCaughtInTests,
UnAuthorizedRequestToSwapSupervisor,
ReservationWouldExceedHardLimit,
CannotUnreserveWithZeroReservations,
ShareHolderMembershipUninitialized,
ProfileNotInstantiated,
CanOnlyBurnReservedShares,
IssuanceCannotGoNegative,
CannotIssueToLockedProfile,
InitialIssuanceShapeIsInvalid,
CantReserveMoreThanShareTotal,
CannotBurnIfIssuanceDNE,
OrganizationMustBeRegisteredToIssueShares,
OrganizationMustBeRegisteredToBurnShares,
OrganizationMustBeRegisteredToLockShares,
OrganizationMustBeRegisteredToUnLockShares,
OrganizationMustBeRegisteredToReserveShares,
OrganizationMustBeRegisteredToUnReserveShares,
NotAuthorizedToRegisterShares,
NotAuthorizedToLockShares,
NotAuthorizedToUnLockShares,
NotAuthorizedToReserveShares,
NotAuthorizedToUnReserveShares,
NotAuthorizedToIssueShares,
NotAuthorizedToBurnShares,
CantBurnSharesIfReferenceCountIsNone,
GenesisTotalMustEqualSumToUseBatchOps,
IssuanceWouldOverflowShares,
}
}
decl_storage! {
trait Store for Module<T: Trait> as Shares {
ShareGroupSupervisor get(fn share_group_supervisor): double_map
hasher(opaque_blake2_256) OrgId,
hasher(opaque_blake2_256) ShareId => Option<T::AccountId>;
ShareIdCounter get(fn share_id_counter):
map hasher(opaque_blake2_256) OrgId => ShareId;
ClaimedShareIdentity get(fn claimed_share_identity): double_map
hasher(opaque_blake2_256) OrgId,
hasher(opaque_blake2_256) ShareId => bool;
MembershipReferenceCounter get(fn membership_reference_counter): double_map
hasher(opaque_blake2_256) OrgId,
hasher(opaque_blake2_256) T::AccountId => u32;
TotalIssuance get(fn total_issuance): double_map
hasher(opaque_blake2_256) OrgId,
hasher(opaque_blake2_256) ShareId => Option<T::Shares>;
Profile get(fn profile): double_map
hasher(blake2_128_concat) UUID2,
hasher(blake2_128_concat) T::AccountId => Option<AtomicShareProfile<T::Shares>>;
ShareGroupSize get(fn share_group_size): double_map
hasher(opaque_blake2_256) OrgId,
hasher(opaque_blake2_256) ShareId => u32;
}
add_extra_genesis {
config(share_supervisors): Option<Vec<(OrgId, ShareId, T::AccountId)>>;
config(shareholder_membership): Option<Vec<(OrgId, ShareId, T::AccountId, T::Shares)>>;
build(|config: &GenesisConfig<T>| {
if let Some(sup) = &config.share_supervisors {
sup.iter().for_each(|(org, sid, acc)| {
ShareGroupSupervisor::<T>::insert(org, sid, acc);
});
}
if let Some(mem) = &config.shareholder_membership {
mem.iter().for_each(|(org_id, share_id, account, shares)| {
let _ = ShareGroupSupervisor::<T>::get(org_id, share_id).expect("share supervisor must exist in order to add members at genesis");
<Module<T>>::issue(
*org_id,
*share_id,
account.clone(),
*shares,
false
).expect("genesis member could not be added to the organization");
});
}
})
}
}
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
type Error = Error<T>;
fn deposit_event() = default;
const ReservationLimit: u32 = T::ReservationLimit::get();
#[weight = 0]
fn issue_shares(origin, organization: OrgId, share_id: ShareId, who: T::AccountId, shares: T::Shares) -> DispatchResult {
let issuer = ensure_signed(origin)?;
ensure!(Self::check_organization_existence(organization), Error::<T>::OrganizationMustBeRegisteredToIssueShares);
let authentication: bool = Self::check_if_sudo_account(&issuer)
|| Self::check_if_organization_supervisor_account(organization, &issuer)
|| Self::is_sub_group_supervisor(organization, share_id, &issuer);
ensure!(authentication, Error::<T>::NotAuthorizedToIssueShares);
Self::issue(organization, share_id, who.clone(), shares, false)?;
Self::deposit_event(RawEvent::SharesIssued(organization, share_id, who, shares));
Ok(())
}
#[weight = 0]
fn burn_shares(origin, organization: OrgId, share_id: ShareId, who: T::AccountId, shares: T::Shares) -> DispatchResult {
let burner = ensure_signed(origin)?;
ensure!(Self::check_organization_existence(organization), Error::<T>::OrganizationMustBeRegisteredToBurnShares);
let authentication: bool = Self::check_if_sudo_account(&burner)
|| Self::check_if_organization_supervisor_account(organization, &burner);
ensure!(authentication, Error::<T>::NotAuthorizedToBurnShares);
Self::burn(organization, share_id, who.clone(), shares, false)?;
Self::deposit_event(RawEvent::SharesBurned(organization, share_id, who, shares));
Ok(())
}
#[weight = 0]
fn batch_issue_shares(origin, organization: OrgId, share_id: ShareId, new_accounts: Vec<(T::AccountId, T::Shares)>) -> DispatchResult {
let issuer = ensure_signed(origin)?;
ensure!(Self::check_organization_existence(organization), Error::<T>::OrganizationMustBeRegisteredToIssueShares);
let authentication: bool = Self::check_if_sudo_account(&issuer)
|| Self::check_if_organization_supervisor_account(organization, &issuer)
|| Self::is_sub_group_supervisor(organization, share_id, &issuer);
ensure!(authentication, Error::<T>::NotAuthorizedToIssueShares);
let genesis: SimpleShareGenesis<T::AccountId, T::Shares> = new_accounts.into();
let total_new_shares_minted = genesis.total();
Self::batch_issue(organization, share_id, genesis)?;
Self::deposit_event(RawEvent::SharesBatchIssued(organization, share_id, total_new_shares_minted));
Ok(())
}
#[weight = 0]
fn batch_burn_shares(origin, organization: OrgId, share_id: ShareId, old_accounts: Vec<(T::AccountId, T::Shares)>) -> DispatchResult {
let issuer = ensure_signed(origin)?;
ensure!(Self::check_organization_existence(organization), Error::<T>::OrganizationMustBeRegisteredToIssueShares);
let authentication: bool = Self::check_if_sudo_account(&issuer)
|| Self::check_if_organization_supervisor_account(organization, &issuer)
|| Self::is_sub_group_supervisor(organization, share_id, &issuer);
ensure!(authentication, Error::<T>::NotAuthorizedToBurnShares);
let genesis: SimpleShareGenesis<T::AccountId, T::Shares> = old_accounts.into();
let total_new_shares_burned = genesis.total();
Self::batch_burn(organization, share_id, genesis)?;
Self::deposit_event(RawEvent::SharesBatchBurned(organization, share_id, total_new_shares_burned));
Ok(())
}
#[weight = 0]
fn lock_shares(origin, organization: OrgId, share_id: ShareId, who: T::AccountId) -> DispatchResult {
let locker = ensure_signed(origin)?;
ensure!(Self::check_organization_existence(organization), Error::<T>::OrganizationMustBeRegisteredToLockShares);
let authentication: bool = Self::check_if_sudo_account(&locker)
|| Self::check_if_organization_supervisor_account(organization, &locker)
|| Self::is_sub_group_supervisor(organization, share_id, &locker)
|| locker == who;
ensure!(authentication, Error::<T>::NotAuthorizedToLockShares);
Self::lock_profile(organization, share_id, &who)?;
Self::deposit_event(RawEvent::SharesLocked(organization, share_id, who));
Ok(())
}
#[weight = 0]
fn unlock_shares(origin, organization: OrgId, share_id: ShareId, who: T::AccountId) -> DispatchResult {
let unlocker = ensure_signed(origin)?;
ensure!(Self::check_organization_existence(organization), Error::<T>::OrganizationMustBeRegisteredToUnLockShares);
let authentication: bool = Self::check_if_sudo_account(&unlocker)
|| Self::check_if_organization_supervisor_account(organization, &unlocker)
|| Self::is_sub_group_supervisor(organization, share_id, &unlocker)
|| unlocker == who;
ensure!(authentication, Error::<T>::NotAuthorizedToUnLockShares);
Self::unlock_profile(organization, share_id, &who)?;
Self::deposit_event(RawEvent::SharesUnlocked(organization, share_id, who));
Ok(())
}
#[weight = 0]
fn reserve_shares(origin, organization: OrgId, share_id: ShareId, who: T::AccountId) -> DispatchResult {
let reserver = ensure_signed(origin)?;
ensure!(Self::check_organization_existence(organization), Error::<T>::OrganizationMustBeRegisteredToReserveShares);
let authentication: bool = Self::check_if_sudo_account(&reserver)
|| Self::check_if_organization_supervisor_account(organization, &reserver)
|| Self::is_sub_group_supervisor(organization, share_id, &reserver)
|| reserver == who;
ensure!(authentication, Error::<T>::NotAuthorizedToReserveShares);
let reservation_context = Self::reserve(organization, share_id, &who, None)?;
let times_reserved = reservation_context.0;
Self::deposit_event(RawEvent::SharesReserved(organization, share_id, who, times_reserved));
Ok(())
}
#[weight = 0]
fn unreserve_shares(origin, organization: OrgId, share_id: ShareId, who: T::AccountId) -> DispatchResult {
let unreserver = ensure_signed(origin)?;
ensure!(Self::check_organization_existence(organization), Error::<T>::OrganizationMustBeRegisteredToUnReserveShares);
let authentication: bool = Self::check_if_sudo_account(&unreserver)
|| Self::check_if_organization_supervisor_account(organization, &unreserver)
|| Self::is_sub_group_supervisor(organization, share_id, &unreserver)
|| unreserver == who;
ensure!(authentication, Error::<T>::NotAuthorizedToUnReserveShares);
let reservation_context = Self::unreserve(organization, share_id, &who, None)?;
let times_reserved = reservation_context.0;
Self::deposit_event(RawEvent::SharesUnReserved(organization, share_id, who, times_reserved));
Ok(())
}
}
}
impl<T: Trait> Module<T> {
fn set_profile(
prefix_key: UUID2,
who: &T::AccountId,
new: &AtomicShareProfile<T::Shares>,
) -> DispatchResult {
Profile::<T>::insert(prefix_key, who, new);
Ok(())
}
fn check_if_account_is_member_in_organization(
organization: OrgId,
account: &T::AccountId,
) -> bool {
<<T as Trait>::OrgData as GroupMembership<<T as frame_system::Trait>::AccountId>>::is_member_of_group(organization, account)
}
fn check_organization_existence(organization: OrgId) -> bool {
!<<T as Trait>::OrgData as IDIsAvailable<OrgId>>::id_is_available(organization)
}
fn check_if_sudo_account(who: &T::AccountId) -> bool {
<<T as Trait>::OrgData as ChainSudoPermissions<<T as frame_system::Trait>::AccountId>>::is_sudo_key(who)
}
fn check_if_organization_supervisor_account(organization: OrgId, who: &T::AccountId) -> bool {
<<T as Trait>::OrgData as OrganizationSupervisorPermissions<
u32,
<T as frame_system::Trait>::AccountId,
>>::is_organization_supervisor(organization, who)
}
fn add_new_member(organization: OrgId, new_member: T::AccountId) {
<<T as Trait>::OrgData as ChangeGroupMembership<
<T as frame_system::Trait>::AccountId,
>>::add_group_member(organization, new_member, false)
}
fn remove_old_member(organization: OrgId, old_member: T::AccountId) {
<<T as Trait>::OrgData as ChangeGroupMembership<
<T as frame_system::Trait>::AccountId,
>>::remove_group_member(organization, old_member, false);
}
}
impl<T: Trait> IDIsAvailable<UUID2> for Module<T> {
fn id_is_available(id: UUID2) -> bool {
!ClaimedShareIdentity::get(id.one(), id.two())
}
}
impl<T: Trait> SeededGenerateUniqueID<OrgId, ShareId> for Module<T> {
fn seeded_generate_unique_id(seed: OrgId) -> ShareId {
let mut id_counter = ShareIdCounter::get(seed) + 1;
while ClaimedShareIdentity::get(seed, id_counter) {
id_counter += 1u32;
}
ShareIdCounter::insert(seed, id_counter);
id_counter
}
}
impl<T: Trait> SubGroupSupervisorPermissions<u32, u32, T::AccountId> for Module<T> {
fn is_sub_group_supervisor(org: u32, sub_group: u32, who: &T::AccountId) -> bool {
if let Some(supervisor) = Self::share_group_supervisor(org, sub_group) {
return who == &supervisor;
}
false
}
fn put_sub_group_supervisor(org: u32, sub_group: u32, supervisor: T::AccountId) {
<ShareGroupSupervisor<T>>::insert(org, sub_group, supervisor)
}
fn set_sub_group_supervisor(
org: u32,
sub_group: u32,
old_supervisor: &T::AccountId,
new_supervisor: T::AccountId,
) -> DispatchResult {
let authentication: bool = Self::check_if_sudo_account(&old_supervisor)
|| Self::check_if_organization_supervisor_account(org, &old_supervisor)
|| Self::is_sub_group_supervisor(org, sub_group, &old_supervisor);
if authentication {
<ShareGroupSupervisor<T>>::insert(org, sub_group, new_supervisor);
return Ok(());
}
Err(Error::<T>::UnAuthorizedRequestToSwapSupervisor.into())
}
}
impl<T: Trait> GetGroupSize for Module<T> {
type GroupId = UUID2;
fn get_size_of_group(group_id: Self::GroupId) -> u32 {
ShareGroupSize::get(group_id.one(), group_id.two())
}
}
impl<T: Trait> GroupMembership<T::AccountId> for Module<T> {
fn is_member_of_group(group_id: Self::GroupId, who: &T::AccountId) -> bool {
<Profile<T>>::get(group_id, who).is_some()
}
}
impl<T: Trait> ReservableProfile<T::AccountId> for Module<T> {
type ReservationContext = (u32, T::Shares);
fn reserve(
organization: OrgId,
share_id: ShareId,
who: &T::AccountId,
amount: Option<Self::ReservationContext>,
) -> Result<Self::ReservationContext, DispatchError> {
let prefix_key = UUID2::new(organization, share_id);
let old_profile =
Profile::<T>::get(prefix_key, who).ok_or(Error::<T>::ProfileNotInstantiated)?;
let max_amount_is_share_total = old_profile.total();
let amount_or_default = if let Some(amt) = amount {
amt.1
} else {
max_amount_is_share_total
};
ensure!(
max_amount_is_share_total >= amount_or_default,
Error::<T>::CantReserveMoreThanShareTotal
);
let times_reserved_increment = if let Some(amt) = amount { amt.0 } else { 1u32 };
let times_reserved = old_profile.times_reserved() + times_reserved_increment;
ensure!(
times_reserved < T::ReservationLimit::get(),
Error::<T>::ReservationWouldExceedHardLimit
);
let new_share_profile = old_profile.iterate_times_reserved(times_reserved_increment);
let new_times_reserved = new_share_profile.times_reserved();
Self::set_profile(prefix_key, who, &new_share_profile)?;
Ok((new_times_reserved, amount_or_default))
}
fn unreserve(
organization: OrgId,
share_id: ShareId,
who: &T::AccountId,
amount: Option<Self::ReservationContext>,
) -> Result<Self::ReservationContext, DispatchError> {
let prefix_key = UUID2::new(organization, share_id);
let old_profile =
Profile::<T>::get(prefix_key, who).ok_or(Error::<T>::ProfileNotInstantiated)?;
let max_amount_is_share_total = old_profile.total();
let amount_or_default = if let Some(amt) = amount {
amt.1
} else {
max_amount_is_share_total
};
ensure!(
max_amount_is_share_total >= amount_or_default,
Error::<T>::CantReserveMoreThanShareTotal
);
let times_reserved_decrement = if let Some(amt) = amount {
amt.0
} else {
1u32
};
let current_times_reserved = old_profile.times_reserved();
let new_times_reserved = current_times_reserved
.checked_sub(times_reserved_decrement)
.ok_or(Error::<T>::CannotUnreserveWithZeroReservations)?;
let new_share_profile = old_profile.decrement_times_reserved(times_reserved_decrement);
ensure!(
new_times_reserved == new_share_profile.times_reserved(),
Error::<T>::LogicBugShouldBeCaughtInTests
);
Self::set_profile(prefix_key, who, &new_share_profile)?;
Ok((new_times_reserved, amount_or_default))
}
}
impl<T: Trait> LockableProfile<T::AccountId> for Module<T> {
fn lock_profile(organization: OrgId, share_id: ShareId, who: &T::AccountId) -> DispatchResult {
let prefix_key = UUID2::new(organization, share_id);
let locked_profile = if let Some(to_be_locked) = Profile::<T>::get(prefix_key, who) {
to_be_locked.lock()
} else {
return Err(Error::<T>::ProfileNotInstantiated.into());
};
Profile::<T>::insert(prefix_key, who, locked_profile);
Ok(())
}
fn unlock_profile(
organization: OrgId,
share_id: ShareId,
who: &T::AccountId,
) -> DispatchResult {
let prefix_key = UUID2::new(organization, share_id);
let locked_profile = if let Some(to_be_locked) = Profile::<T>::get(prefix_key, who) {
to_be_locked.unlock()
} else {
return Err(Error::<T>::ProfileNotInstantiated.into());
};
Profile::<T>::insert(prefix_key, who, locked_profile);
Ok(())
}
}
impl<T: Trait> WeightedShareGroup<T::AccountId> for Module<T> {
type Shares = T::Shares;
type Profile = AtomicShareProfile<T::Shares>;
type Genesis = SimpleShareGenesis<T::AccountId, T::Shares>;
fn outstanding_shares(organization: OrgId, share_id: ShareId) -> Option<T::Shares> {
<TotalIssuance<T>>::get(organization, share_id)
}
fn get_share_profile(
organization: OrgId,
share_id: ShareId,
who: &T::AccountId,
) -> Option<Self::Profile> {
let prefix_key = UUID2::new(organization, share_id);
Profile::<T>::get(prefix_key, who)
}
fn shareholder_membership(organization: OrgId, share_id: ShareId) -> Option<Self::Genesis> {
let prefix = UUID2::new(organization, share_id);
if Self::id_is_available(prefix) {
None
} else {
Some(
<Profile<T>>::iter()
.filter(|(uuidtwo, _, _)| uuidtwo == &prefix)
.map(|(_, account, profile)| (account, profile.total()))
.collect::<Vec<(T::AccountId, T::Shares)>>()
.into(),
)
}
}
}
impl<T: Trait> ShareBank<T::AccountId> for Module<T> {
fn issue(
organization: OrgId,
share_id: ShareId,
new_owner: T::AccountId,
amount: T::Shares,
batch: bool,
) -> DispatchResult {
if !ClaimedShareIdentity::get(organization, share_id) {
ClaimedShareIdentity::insert(organization, share_id, true);
}
if !Self::check_if_account_is_member_in_organization(organization, &new_owner) {
Self::add_new_member(organization, new_owner.clone());
}
let prefix_key = UUID2::new(organization, share_id);
let old_share_profile = Profile::<T>::get(prefix_key, &new_owner);
let new_share_profile = if let Some(old_profile) = old_share_profile {
ensure!(
old_profile.is_unlocked(),
Error::<T>::CannotIssueToLockedProfile
);
old_profile.add_shares(amount)
} else {
let new_share_group_count_for_account =
<MembershipReferenceCounter<T>>::get(organization, &new_owner) + 1u32;
<MembershipReferenceCounter<T>>::insert(
organization,
&new_owner,
new_share_group_count_for_account,
);
AtomicShareProfile::new_shares(amount)
};
if !batch {
let current_issuance =
<TotalIssuance<T>>::get(organization, share_id).unwrap_or_else(T::Shares::zero);
let new_amount = current_issuance.saturating_add(amount);
<TotalIssuance<T>>::insert(organization, share_id, new_amount);
}
Profile::<T>::insert(prefix_key, &new_owner, new_share_profile);
Ok(())
}
fn burn(
organization: OrgId,
share_id: ShareId,
old_owner: T::AccountId,
amount: T::Shares,
batch: bool,
) -> DispatchResult {
let current_issuance = <TotalIssuance<T>>::get(organization, share_id)
.ok_or(Error::<T>::CannotBurnIfIssuanceDNE)?;
let prefix_key = UUID2::new(organization, share_id);
let profile =
Profile::<T>::get(prefix_key, &old_owner).ok_or(Error::<T>::ProfileNotInstantiated)?;
let total_shares = &profile.total();
ensure!(
total_shares >= &amount,
Error::<T>::CanOnlyBurnReservedShares
);
if !batch {
ensure!(
current_issuance >= amount,
Error::<T>::IssuanceCannotGoNegative
);
let new_amount = current_issuance - amount;
<TotalIssuance<T>>::insert(organization, share_id, new_amount);
}
let new_profile = profile.subtract_shares(amount);
if new_profile.is_zero() {
let membership_rc = <MembershipReferenceCounter<T>>::get(organization, &old_owner)
.checked_sub(1u32)
.ok_or(Error::<T>::CantBurnSharesIfReferenceCountIsNone)?;
if membership_rc == 0 {
Self::remove_old_member(organization, old_owner.clone());
}
<MembershipReferenceCounter<T>>::insert(organization, &old_owner, membership_rc);
Profile::<T>::remove(prefix_key, &old_owner);
} else {
Profile::<T>::insert(prefix_key, &old_owner, new_profile);
}
Ok(())
}
fn batch_issue(organization: u32, share_id: u32, genesis: Self::Genesis) -> DispatchResult {
ensure!(
genesis.verify_shape(),
Error::<T>::GenesisTotalMustEqualSumToUseBatchOps
);
let old_issuance =
<TotalIssuance<T>>::get(organization, share_id).unwrap_or_else(T::Shares::zero);
let new_issuance = old_issuance
.checked_add(&genesis.total())
.ok_or(Error::<T>::IssuanceWouldOverflowShares)?;
<TotalIssuance<T>>::insert(organization, share_id, new_issuance);
genesis
.account_ownership()
.into_iter()
.map(|(member, shares)| -> DispatchResult {
Self::issue(organization, share_id, member, shares, true)
})
.collect::<DispatchResult>()?;
Ok(())
}
fn batch_burn(organization: u32, share_id: u32, genesis: Self::Genesis) -> DispatchResult {
ensure!(
genesis.verify_shape(),
Error::<T>::GenesisTotalMustEqualSumToUseBatchOps
);
let old_issuance =
<TotalIssuance<T>>::get(organization, share_id).unwrap_or_else(T::Shares::zero);
let new_issuance = old_issuance
.checked_sub(&genesis.total())
.ok_or(Error::<T>::IssuanceCannotGoNegative)?;
<TotalIssuance<T>>::insert(organization, share_id, new_issuance);
genesis
.account_ownership()
.into_iter()
.map(|(member, shares)| -> DispatchResult {
Self::burn(organization, share_id, member, shares, true)
})
.collect::<DispatchResult>()?;
Ok(())
}
}