#![cfg_attr(not(feature = "std"), no_std)]
use cfg_primitives::OrderId;
use cfg_traits::{
investments::{Investment, InvestmentAccountant, InvestmentCollector, OrderManager},
PreConditions, StatusNotificationHook,
};
use cfg_types::{
fixed_point::FixedPointNumberExtension,
investments::{CollectedAmount, InvestCollection, InvestmentAccount, RedeemCollection},
orders::{FulfillmentWithPrice, Order, TotalOrder},
};
use frame_support::{
dispatch::{DispatchErrorWithPostInfo, PostDispatchInfo},
pallet_prelude::*,
traits::tokens::{
fungibles::{Inspect, Mutate},
Preservation,
},
};
use frame_system::pallet_prelude::*;
pub use pallet::*;
use sp_runtime::{
traits::{AccountIdConversion, CheckedAdd, CheckedSub, EnsureAddAssign, One, Zero},
ArithmeticError, FixedPointNumber,
};
use sp_std::{
cmp::{min, Ordering},
convert::TryInto,
vec::Vec,
};
pub mod weights;
pub use weights::WeightInfo;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
type CurrencyOf<T> =
<<T as Config>::Tokens as Inspect<<T as frame_system::Config>::AccountId>>::AssetId;
#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo)]
pub enum OrderType<AccountId, InvestmentId, Amount> {
Investment {
who: AccountId,
investment_id: InvestmentId,
amount: Amount,
},
Redemption {
who: AccountId,
investment_id: InvestmentId,
amount: Amount,
},
}
#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo)]
pub enum CollectOutcome {
FullyCollected,
PartiallyCollected,
}
pub type OrderOf<T> = Order<<T as Config>::Amount, OrderId>;
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo)]
pub enum CollectType {
Closing,
Overflowing,
}
#[frame_support::pallet]
pub mod pallet {
use cfg_types::investments::InvestmentInfo;
use sp_runtime::{traits::AtLeast32BitUnsigned, FixedPointNumber, FixedPointOperand};
use super::*;
#[pallet::config]
pub trait Config: frame_system::Config {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type InvestmentId: Member + Parameter + Copy + MaxEncodedLen + Into<CurrencyOf<Self>>;
type Accountant: InvestmentAccountant<
Self::AccountId,
Error = DispatchError,
InvestmentId = Self::InvestmentId,
Amount = Self::Amount,
InvestmentInfo = InvestmentInfo<Self::AccountId, CurrencyOf<Self>, Self::InvestmentId>,
>;
type Amount: Member
+ Parameter
+ AtLeast32BitUnsigned
+ Default
+ Copy
+ MaxEncodedLen
+ FixedPointOperand
+ From<u64>
+ From<u128>
+ TryInto<u64>;
type BalanceRatio: Member
+ Parameter
+ Default
+ Copy
+ FixedPointNumber<Inner = Self::Amount>
+ FixedPointNumberExtension
+ MaxEncodedLen;
#[pallet::constant]
type MaxOutstandingCollects: Get<u32>;
type Tokens: Mutate<Self::AccountId> + Inspect<Self::AccountId, Balance = Self::Amount>;
type PreConditions: PreConditions<
OrderType<Self::AccountId, Self::InvestmentId, Self::Amount>,
Result = DispatchResult,
>;
type CollectedInvestmentHook: StatusNotificationHook<
Error = DispatchError,
Id = (Self::AccountId, Self::InvestmentId),
Status = CollectedAmount<Self::Amount, Self::Amount>,
>;
type CollectedRedemptionHook: StatusNotificationHook<
Error = DispatchError,
Id = (Self::AccountId, Self::InvestmentId),
Status = CollectedAmount<Self::Amount, Self::Amount>,
>;
type WeightInfo: weights::WeightInfo;
}
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
pub struct Pallet<T>(_);
#[pallet::storage]
#[pallet::getter(fn invest_order_id)]
pub(crate) type InvestOrderId<T: Config> =
StorageMap<_, Blake2_128Concat, T::InvestmentId, OrderId, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn redeem_order_id)]
pub(crate) type RedeemOrderId<T: Config> =
StorageMap<_, Blake2_128Concat, T::InvestmentId, OrderId, ValueQuery>;
#[pallet::storage]
pub type InvestOrders<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
T::AccountId,
Blake2_128Concat,
T::InvestmentId,
Order<T::Amount, OrderId>,
>;
#[pallet::storage]
pub type RedeemOrders<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
T::AccountId,
Blake2_128Concat,
T::InvestmentId,
Order<T::Amount, OrderId>,
>;
#[pallet::storage]
#[pallet::getter(fn acc_active_invest_order)]
pub type ActiveInvestOrders<T: Config> =
StorageMap<_, Blake2_128Concat, T::InvestmentId, TotalOrder<T::Amount>, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn acc_active_redeem_order)]
pub type ActiveRedeemOrders<T: Config> =
StorageMap<_, Blake2_128Concat, T::InvestmentId, TotalOrder<T::Amount>, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn acc_in_processing_invest_order)]
pub(crate) type InProcessingInvestOrders<T: Config> =
StorageMap<_, Blake2_128Concat, T::InvestmentId, TotalOrder<T::Amount>>;
#[pallet::storage]
#[pallet::getter(fn acc_in_processing_redeem_order)]
pub(crate) type InProcessingRedeemOrders<T: Config> =
StorageMap<_, Blake2_128Concat, T::InvestmentId, TotalOrder<T::Amount>>;
#[pallet::storage]
#[pallet::getter(fn cleared_invest_order)]
pub(crate) type ClearedInvestOrders<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
T::InvestmentId,
Twox64Concat,
OrderId,
FulfillmentWithPrice<T::BalanceRatio>,
>;
#[pallet::storage]
#[pallet::getter(fn cleared_redeem_order)]
pub(crate) type ClearedRedeemOrders<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
T::InvestmentId,
Twox64Concat,
OrderId,
FulfillmentWithPrice<T::BalanceRatio>,
>;
#[pallet::event]
#[pallet::generate_deposit(pub (super) fn deposit_event)]
pub enum Event<T: Config> {
InvestOrdersCollected {
investment_id: T::InvestmentId,
who: T::AccountId,
processed_orders: Vec<OrderId>,
collection: InvestCollection<T::Amount>,
outcome: CollectOutcome,
},
RedeemOrdersCollected {
investment_id: T::InvestmentId,
who: T::AccountId,
processed_orders: Vec<OrderId>,
collection: RedeemCollection<T::Amount>,
outcome: CollectOutcome,
},
InvestOrderUpdated {
investment_id: T::InvestmentId,
submitted_at: OrderId,
who: T::AccountId,
amount: T::Amount,
},
RedeemOrderUpdated {
investment_id: T::InvestmentId,
submitted_at: OrderId,
who: T::AccountId,
amount: T::Amount,
},
InvestOrdersCleared {
investment_id: T::InvestmentId,
order_id: OrderId,
fulfillment: FulfillmentWithPrice<T::BalanceRatio>,
},
RedeemOrdersCleared {
investment_id: T::InvestmentId,
order_id: OrderId,
fulfillment: FulfillmentWithPrice<T::BalanceRatio>,
},
InvestOrdersInProcessing {
investment_id: T::InvestmentId,
order_id: OrderId,
total_order: TotalOrder<T::Amount>,
},
RedeemOrdersInProcessing {
investment_id: T::InvestmentId,
order_id: OrderId,
total_order: TotalOrder<T::Amount>,
},
InvestCollectedWithoutActivePosition {
who: T::AccountId,
investment_id: T::InvestmentId,
},
RedeemCollectedWithoutActivePosition {
who: T::AccountId,
investment_id: T::InvestmentId,
},
InvestCollectedForNonClearedOrderId {
who: T::AccountId,
investment_id: T::InvestmentId,
},
RedeemCollectedForNonClearedOrderId {
who: T::AccountId,
investment_id: T::InvestmentId,
},
}
#[pallet::error]
pub enum Error<T> {
OrderNotCleared,
OrderStillActive,
UnknownInvestment,
CollectRequired,
ZeroPricedInvestment,
OrderNotInProcessing,
OrderInProcessing,
NoNewOrder,
NoActiveInvestOrder,
NoActiveRedeemOrder,
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::weight(T::WeightInfo::update_invest_order())]
#[pallet::call_index(0)]
pub fn update_invest_order(
origin: OriginFor<T>,
investment_id: T::InvestmentId,
amount: T::Amount,
) -> DispatchResult {
let who = ensure_signed(origin)?;
Pallet::<T>::do_update_investment(who, investment_id, amount)
}
#[pallet::weight(T::WeightInfo::update_redeem_order())]
#[pallet::call_index(1)]
pub fn update_redeem_order(
origin: OriginFor<T>,
investment_id: T::InvestmentId,
amount: T::Amount,
) -> DispatchResult {
let who = ensure_signed(origin)?;
Pallet::<T>::do_update_redemption(who, investment_id, amount)
}
#[pallet::weight(T::WeightInfo::collect_investments(T::MaxOutstandingCollects::get()))]
#[pallet::call_index(2)]
pub fn collect_investments(
origin: OriginFor<T>,
investment_id: T::InvestmentId,
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
Self::do_collect_invest(who, investment_id)
}
#[pallet::weight(T::WeightInfo::collect_redemptions(T::MaxOutstandingCollects::get()))]
#[pallet::call_index(3)]
pub fn collect_redemptions(
origin: OriginFor<T>,
investment_id: T::InvestmentId,
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
Self::do_collect_redeem(who, investment_id)
}
#[pallet::weight(T::WeightInfo::collect_investments(T::MaxOutstandingCollects::get()))]
#[pallet::call_index(4)]
pub fn collect_investments_for(
origin: OriginFor<T>,
who: T::AccountId,
investment_id: T::InvestmentId,
) -> DispatchResultWithPostInfo {
ensure_signed(origin)?;
Self::do_collect_invest(who, investment_id)
}
#[pallet::weight(T::WeightInfo::collect_redemptions(T::MaxOutstandingCollects::get()))]
#[pallet::call_index(5)]
pub fn collect_redemptions_for(
origin: OriginFor<T>,
who: T::AccountId,
investment_id: T::InvestmentId,
) -> DispatchResultWithPostInfo {
ensure_signed(origin)?;
Self::do_collect_redeem(who, investment_id)
}
}
}
impl<T: Config> Pallet<T> {
pub(crate) fn do_update_investment(
who: T::AccountId,
investment_id: T::InvestmentId,
amount: T::Amount,
) -> DispatchResult {
T::PreConditions::check(OrderType::Investment {
who: who.clone(),
investment_id,
amount,
})?;
let info = T::Accountant::info(investment_id).map_err(|_| Error::<T>::UnknownInvestment)?;
let cur_order_id = ActiveInvestOrders::<T>::try_mutate(
investment_id,
|total_order| -> Result<OrderId, DispatchError> {
InvestOrders::<T>::try_mutate(
&who,
investment_id,
|maybe_order| -> Result<OrderId, DispatchError> {
let order =
Pallet::<T>::invest_order_or_default(investment_id, maybe_order);
let cur_order_id = InvestOrderId::<T>::get(investment_id);
ensure!(
order.submitted_at() == cur_order_id,
Error::<T>::CollectRequired
);
Self::do_update_invest_order(
total_order,
&who,
investment_id,
info.payment_currency,
order,
amount,
)?;
if amount == T::Amount::zero() {
*maybe_order = None;
}
Ok(cur_order_id)
},
)
},
)?;
Self::deposit_event(Event::InvestOrderUpdated {
investment_id,
submitted_at: cur_order_id,
who,
amount,
});
Ok(())
}
pub(crate) fn do_update_redemption(
who: T::AccountId,
investment_id: T::InvestmentId,
amount: T::Amount,
) -> DispatchResult {
T::PreConditions::check(OrderType::Redemption {
who: who.clone(),
investment_id,
amount,
})?;
let _ = T::Accountant::info(investment_id).map_err(|_| Error::<T>::UnknownInvestment)?;
let cur_order_id = ActiveRedeemOrders::<T>::try_mutate(
investment_id,
|total_order| -> Result<OrderId, DispatchError> {
RedeemOrders::<T>::try_mutate(
&who,
investment_id,
|maybe_order| -> Result<OrderId, DispatchError> {
let order =
Pallet::<T>::redeem_order_or_default(investment_id, maybe_order);
let cur_order_id = RedeemOrderId::<T>::get(investment_id);
ensure!(
order.submitted_at() == cur_order_id,
Error::<T>::CollectRequired
);
Self::do_update_redeem_order(
total_order,
&who,
investment_id,
order,
amount,
)?;
if amount.is_zero() {
*maybe_order = None;
}
Ok(cur_order_id)
},
)
},
)?;
Self::deposit_event(Event::RedeemOrderUpdated {
investment_id,
submitted_at: cur_order_id,
who,
amount,
});
Ok(())
}
fn rm_empty(amount: T::Amount, storage_order: &mut Option<OrderOf<T>>, on_not_empty: Event<T>) {
if !amount.is_zero() {
Self::deposit_event(on_not_empty);
} else {
*storage_order = None;
}
}
#[allow(clippy::type_complexity)]
pub(crate) fn do_collect_invest(
who: T::AccountId,
investment_id: T::InvestmentId,
) -> DispatchResultWithPostInfo {
T::PreConditions::check(OrderType::Investment {
who: who.clone(),
investment_id,
amount: Default::default(),
})?;
let _ = T::Accountant::info(investment_id).map_err(|_| Error::<T>::UnknownInvestment)?;
let (collected_investment, post_dispatch_info) = InvestOrders::<T>::try_mutate(
&who,
investment_id,
|maybe_order| -> Result<
(CollectedAmount<T::Amount, T::Amount>, PostDispatchInfo),
DispatchErrorWithPostInfo,
> {
let order = if let Some(order) = maybe_order.as_mut() {
order
} else {
Self::deposit_event(Event::<T>::InvestCollectedWithoutActivePosition {
who: who.clone(),
investment_id,
});
return Ok((Default::default(), ().into()));
};
let mut collection = InvestCollection::<T::Amount>::from_order(order);
let mut collected_ids = Vec::new();
let cur_order_id = InvestOrderId::<T>::get(investment_id);
let last_processed_order_id = min(
order
.submitted_at()
.saturating_add(T::MaxOutstandingCollects::get().into()),
cur_order_id,
);
if order.submitted_at() == cur_order_id {
Self::deposit_event(Event::<T>::InvestCollectedForNonClearedOrderId {
who: who.clone(),
investment_id,
});
return Ok((Default::default(), ().into()));
}
let mut amount_payment = T::Amount::zero();
for order_id in order.submitted_at()..last_processed_order_id {
let fulfillment = ClearedInvestOrders::<T>::try_get(investment_id, order_id)
.map_err(|_| Error::<T>::OrderNotCleared)?;
let currency_payout =
Pallet::<T>::acc_payout_invest(&mut collection, &fulfillment)?;
Pallet::<T>::acc_remaining_invest(&mut collection, &fulfillment)?;
collected_ids.push(order_id);
amount_payment.ensure_add_assign(
fulfillment
.price
.checked_mul_int_floor(currency_payout)
.ok_or(ArithmeticError::Overflow)?,
)?;
}
order.update_after_collect(
collection.remaining_investment_invest,
last_processed_order_id,
);
T::Accountant::transfer(
investment_id,
&InvestmentAccount { investment_id }.into_account_truncating(),
&who,
collection.payout_investment_invest,
)?;
let amount = order.amount();
Self::rm_empty(
amount,
maybe_order,
Event::InvestOrderUpdated {
investment_id,
submitted_at: last_processed_order_id,
who: who.clone(),
amount,
},
);
let collected_investment = CollectedAmount {
amount_collected: collection.payout_investment_invest,
amount_payment,
};
Self::deposit_event(Event::InvestOrdersCollected {
investment_id,
who: who.clone(),
processed_orders: collected_ids,
collection,
outcome: if last_processed_order_id == cur_order_id {
CollectOutcome::FullyCollected
} else {
CollectOutcome::PartiallyCollected
},
});
Ok((collected_investment, ().into()))
},
)?;
if collected_investment != Default::default() {
T::CollectedInvestmentHook::notify_status_change(
(who, investment_id),
collected_investment,
)?;
}
Ok(post_dispatch_info)
}
#[allow(clippy::type_complexity)]
pub(crate) fn do_collect_redeem(
who: T::AccountId,
investment_id: T::InvestmentId,
) -> DispatchResultWithPostInfo {
let info = T::Accountant::info(investment_id).map_err(|_| Error::<T>::UnknownInvestment)?;
let (collected_redemption, post_dispatch_info) = RedeemOrders::<T>::try_mutate(
&who,
investment_id,
|maybe_order| -> Result<
(CollectedAmount<T::Amount, T::Amount>, PostDispatchInfo),
DispatchErrorWithPostInfo,
> {
let order = if let Some(order) = maybe_order.as_mut() {
order
} else {
Self::deposit_event(Event::<T>::RedeemCollectedWithoutActivePosition {
who: who.clone(),
investment_id,
});
return Ok((Default::default(), ().into()));
};
let mut collection = RedeemCollection::<T::Amount>::from_order(order);
let mut collected_ids = Vec::new();
let cur_order_id = RedeemOrderId::<T>::get(investment_id);
let last_processed_order_id = min(
order
.submitted_at()
.saturating_add(T::MaxOutstandingCollects::get().into()),
cur_order_id,
);
if order.submitted_at() == cur_order_id {
Self::deposit_event(Event::<T>::RedeemCollectedForNonClearedOrderId {
who: who.clone(),
investment_id,
});
return Ok((Default::default(), ().into()));
}
let mut amount_payment = T::Amount::zero();
for order_id in order.submitted_at()..last_processed_order_id {
let fulfillment = ClearedRedeemOrders::<T>::try_get(investment_id, order_id)
.map_err(|_| Error::<T>::OrderNotCleared)?;
let payout_tranche_tokens =
Pallet::<T>::acc_payout_redeem(&mut collection, &fulfillment)?;
Pallet::<T>::acc_remaining_redeem(&mut collection, &fulfillment)?;
collected_ids.push(order_id);
amount_payment.ensure_add_assign(
fulfillment
.price
.reciprocal_floor()
.ok_or(Error::<T>::ZeroPricedInvestment)?
.checked_mul_int_floor(payout_tranche_tokens)
.ok_or(ArithmeticError::Overflow)?,
)?;
}
order.update_after_collect(
collection.remaining_investment_redeem,
last_processed_order_id,
);
let investment_account =
InvestmentAccount { investment_id }.into_account_truncating();
T::Tokens::transfer(
info.payment_currency,
&investment_account,
&who,
collection.payout_investment_redeem,
Preservation::Expendable,
)?;
let amount = order.amount();
Self::rm_empty(
amount,
maybe_order,
Event::RedeemOrderUpdated {
investment_id,
submitted_at: last_processed_order_id,
who: who.clone(),
amount,
},
);
let collected_redemption = CollectedAmount {
amount_collected: collection.payout_investment_redeem,
amount_payment,
};
Self::deposit_event(Event::RedeemOrdersCollected {
investment_id,
who: who.clone(),
processed_orders: collected_ids,
collection,
outcome: if last_processed_order_id == cur_order_id {
CollectOutcome::FullyCollected
} else {
CollectOutcome::PartiallyCollected
},
});
Ok((collected_redemption, ().into()))
},
)?;
if collected_redemption != Default::default() {
T::CollectedRedemptionHook::notify_status_change(
(who, investment_id),
collected_redemption,
)?;
}
Ok(post_dispatch_info)
}
pub(crate) fn do_update_invest_order(
total_order: &mut TotalOrder<T::Amount>,
who: &T::AccountId,
investment_id: T::InvestmentId,
payment_currency: CurrencyOf<T>,
order: &mut OrderOf<T>,
amount: T::Amount,
) -> DispatchResult {
let investment_account = InvestmentAccount { investment_id }.into_account_truncating();
let (send, recv, transfer_amount) = Self::update_order_amount(
who,
&investment_account,
order.updatable_amount(),
amount,
&mut total_order.amount,
)?;
T::Tokens::transfer(
payment_currency,
send,
recv,
transfer_amount,
Preservation::Expendable,
)
.map(|_| ())
}
pub(crate) fn do_update_redeem_order(
total_order: &mut TotalOrder<T::Amount>,
who: &T::AccountId,
investment_id: T::InvestmentId,
order: &mut OrderOf<T>,
amount: T::Amount,
) -> DispatchResult {
let investment_account = InvestmentAccount { investment_id }.into_account_truncating();
let (send, recv, transfer_amount) = Self::update_order_amount(
who,
&investment_account,
order.updatable_amount(),
amount,
&mut total_order.amount,
)?;
T::Accountant::transfer(investment_id, send, recv, transfer_amount)
}
#[allow(clippy::type_complexity)]
fn update_order_amount<'a>(
who: &'a T::AccountId,
pool: &'a T::AccountId,
old_order: &mut T::Amount,
new_order: T::Amount,
total_orders: &mut T::Amount,
) -> Result<(&'a T::AccountId, &'a T::AccountId, T::Amount), DispatchError> {
match new_order.cmp(old_order) {
Ordering::Greater => {
let transfer_amount = new_order
.checked_sub(old_order)
.expect("New order larger than old order. qed.");
*total_orders = total_orders
.checked_add(&transfer_amount)
.ok_or(ArithmeticError::Overflow)?;
*old_order = new_order;
Ok((who, pool, transfer_amount))
}
Ordering::Less => {
let transfer_amount = old_order
.checked_sub(&new_order)
.expect("Old order larger than new order. qed.");
*total_orders = total_orders
.checked_sub(&transfer_amount)
.ok_or(ArithmeticError::Underflow)?;
*old_order = new_order;
Ok((pool, who, transfer_amount))
}
Ordering::Equal => Err(Error::<T>::NoNewOrder.into()),
}
}
pub fn acc_payout_invest(
collection: &mut InvestCollection<T::Amount>,
fulfillment: &FulfillmentWithPrice<T::BalanceRatio>,
) -> Result<T::Amount, DispatchError> {
let remaining = collection.remaining_investment_invest;
let payout_investment_invest = &fulfillment
.price
.reciprocal_floor()
.ok_or(Error::<T>::ZeroPricedInvestment)?
.checked_mul_int_floor(fulfillment.of_amount.mul_floor(remaining))
.ok_or(ArithmeticError::Overflow)?;
collection.payout_investment_invest = collection
.payout_investment_invest
.checked_add(payout_investment_invest)
.ok_or(ArithmeticError::Overflow)?;
Ok(*payout_investment_invest)
}
pub fn acc_payout_redeem(
collection: &mut RedeemCollection<T::Amount>,
fulfillment: &FulfillmentWithPrice<T::BalanceRatio>,
) -> Result<T::Amount, DispatchError> {
let remaining = collection.remaining_investment_redeem;
let payout_investment_redeem = &fulfillment
.price
.checked_mul_int_floor(fulfillment.of_amount.mul_floor(remaining))
.ok_or(ArithmeticError::Overflow)?;
collection.payout_investment_redeem = collection
.payout_investment_redeem
.checked_add(payout_investment_redeem)
.ok_or(ArithmeticError::Overflow)?;
Ok(*payout_investment_redeem)
}
pub fn acc_remaining_redeem(
collection: &mut RedeemCollection<T::Amount>,
fulfillment: &FulfillmentWithPrice<T::BalanceRatio>,
) -> DispatchResult {
let remaining = collection.remaining_investment_redeem;
collection.remaining_investment_redeem = collection
.remaining_investment_redeem
.checked_sub(&fulfillment.of_amount.mul_floor(remaining))
.ok_or(ArithmeticError::Underflow)?;
Ok(())
}
pub fn acc_remaining_invest(
collection: &mut InvestCollection<T::Amount>,
fulfillment: &FulfillmentWithPrice<T::BalanceRatio>,
) -> DispatchResult {
let remaining = collection.remaining_investment_invest;
collection.remaining_investment_invest = collection
.remaining_investment_invest
.checked_sub(&fulfillment.of_amount.mul_floor(remaining))
.ok_or(ArithmeticError::Underflow)?;
Ok(())
}
fn invest_order_or_default(
investment_id: T::InvestmentId,
order: &mut Option<Order<T::Amount, OrderId>>,
) -> &mut Order<T::Amount, OrderId> {
if order.is_none() {
let mut new_order = Some(Order::new(
T::Amount::zero(),
InvestOrderId::<T>::get(investment_id),
));
sp_std::mem::swap(order, &mut new_order);
}
order.as_mut().expect("Order is Some(). qed.")
}
fn redeem_order_or_default(
investment_id: T::InvestmentId,
order: &mut Option<Order<T::Amount, OrderId>>,
) -> &mut Order<T::Amount, OrderId> {
if order.is_none() {
let mut new_order = Some(Order::new(
T::Amount::zero(),
RedeemOrderId::<T>::get(investment_id),
));
sp_std::mem::swap(order, &mut new_order);
}
order.as_mut().expect("Order is Some(). qed.")
}
}
impl<T: Config> Investment<T::AccountId> for Pallet<T> {
type Amount = T::Amount;
type CurrencyId = CurrencyOf<T>;
type Error = DispatchError;
type InvestmentId = T::InvestmentId;
type TrancheAmount = T::Amount;
fn update_investment(
who: &T::AccountId,
investment_id: Self::InvestmentId,
amount: Self::Amount,
) -> Result<(), Self::Error> {
Pallet::<T>::do_update_investment(who.clone(), investment_id, amount)
}
fn investment(
who: &T::AccountId,
investment_id: Self::InvestmentId,
) -> Result<Self::Amount, Self::Error> {
Ok(InvestOrders::<T>::get(who, investment_id)
.map_or_else(Zero::zero, |order| order.amount()))
}
fn update_redemption(
who: &T::AccountId,
investment_id: Self::InvestmentId,
amount: Self::TrancheAmount,
) -> Result<(), Self::Error> {
Pallet::<T>::do_update_redemption(who.clone(), investment_id, amount)
}
fn redemption(
who: &T::AccountId,
investment_id: Self::InvestmentId,
) -> Result<Self::TrancheAmount, Self::Error> {
Ok(RedeemOrders::<T>::get(who, investment_id)
.map_or_else(Zero::zero, |order| order.amount()))
}
}
impl<T: Config> OrderManager for Pallet<T> {
type Error = DispatchError;
type Fulfillment = FulfillmentWithPrice<T::BalanceRatio>;
type InvestmentId = T::InvestmentId;
type Orders = TotalOrder<T::Amount>;
fn invest_orders(investment_id: Self::InvestmentId) -> Self::Orders {
ActiveInvestOrders::<T>::get(investment_id)
}
fn redeem_orders(investment_id: Self::InvestmentId) -> Self::Orders {
ActiveRedeemOrders::<T>::get(investment_id)
}
fn process_invest_orders(
investment_id: Self::InvestmentId,
) -> Result<Self::Orders, Self::Error> {
let total_orders = ActiveInvestOrders::<T>::try_mutate(
investment_id,
|orders| -> Result<TotalOrder<T::Amount>, DispatchError> {
InProcessingInvestOrders::<T>::try_mutate(
investment_id,
|in_processing_orders| -> DispatchResult {
ensure!(
in_processing_orders.is_none(),
Error::<T>::OrderInProcessing
);
*in_processing_orders = Some(orders.clone());
Ok(())
},
)?;
let mut total_orders = TotalOrder::default();
sp_std::mem::swap(orders, &mut total_orders);
Ok(total_orders)
},
)?;
let order_id = InvestOrderId::<T>::try_mutate(
investment_id,
|order_id| -> Result<OrderId, DispatchError> {
let cur_order_id = *order_id;
*order_id = order_id
.checked_add(One::one())
.ok_or(ArithmeticError::Overflow)?;
Ok(cur_order_id)
},
)?;
Self::deposit_event(Event::InvestOrdersInProcessing {
investment_id,
order_id,
total_order: total_orders.clone(),
});
Ok(total_orders)
}
fn process_redeem_orders(
investment_id: Self::InvestmentId,
) -> Result<Self::Orders, Self::Error> {
let total_orders = ActiveRedeemOrders::<T>::try_mutate(
investment_id,
|orders| -> Result<TotalOrder<T::Amount>, DispatchError> {
InProcessingRedeemOrders::<T>::try_mutate(
investment_id,
|in_processing_orders| -> DispatchResult {
ensure!(
in_processing_orders.is_none(),
Error::<T>::OrderInProcessing
);
*in_processing_orders = Some(orders.clone());
Ok(())
},
)?;
let mut total_orders = TotalOrder::default();
sp_std::mem::swap(orders, &mut total_orders);
Ok(total_orders)
},
)?;
let order_id = RedeemOrderId::<T>::try_mutate(
investment_id,
|order_id| -> Result<OrderId, DispatchError> {
let cur_order_id = *order_id;
*order_id = order_id
.checked_add(One::one())
.ok_or(ArithmeticError::Overflow)?;
Ok(cur_order_id)
},
)?;
Self::deposit_event(Event::RedeemOrdersInProcessing {
investment_id,
order_id,
total_order: total_orders.clone(),
});
Ok(total_orders)
}
fn invest_fulfillment(
investment_id: Self::InvestmentId,
fulfillment: Self::Fulfillment,
) -> Result<(), DispatchError> {
let order_id = InProcessingInvestOrders::<T>::try_mutate(
investment_id,
|maybe_orders| -> Result<OrderId, DispatchError> {
let orders = maybe_orders
.as_ref()
.ok_or(Error::<T>::OrderNotInProcessing)?;
let invest_amount = fulfillment.of_amount.mul_floor(orders.amount);
let remaining_invest_amount = orders
.amount
.checked_sub(&invest_amount)
.ok_or(ArithmeticError::Underflow)?;
let investment_account =
InvestmentAccount { investment_id }.into_account_truncating();
let info = T::Accountant::info(investment_id)?;
T::Tokens::transfer(
info.payment_currency,
&investment_account,
&info.owner,
invest_amount,
Preservation::Expendable,
)?;
let amount_of_investment_units = fulfillment
.price
.reciprocal()
.ok_or(Error::<T>::ZeroPricedInvestment)?
.checked_mul_int(invest_amount)
.ok_or(ArithmeticError::Overflow)?;
T::Accountant::deposit(&investment_account, info.id, amount_of_investment_units)?;
let order_id = InvestOrderId::<T>::get(investment_id)
.checked_sub(1)
.ok_or(ArithmeticError::Underflow)?;
ClearedInvestOrders::<T>::insert(investment_id, order_id, fulfillment);
ActiveInvestOrders::<T>::try_mutate(
investment_id,
|total_orders| -> DispatchResult {
total_orders.amount = total_orders
.amount
.checked_add(&remaining_invest_amount)
.ok_or(ArithmeticError::Overflow)?;
Ok(())
},
)?;
*maybe_orders = None;
Ok(order_id)
},
)?;
Self::deposit_event(Event::InvestOrdersCleared {
investment_id,
order_id,
fulfillment,
});
Ok(())
}
fn redeem_fulfillment(
investment_id: Self::InvestmentId,
fulfillment: Self::Fulfillment,
) -> Result<(), DispatchError> {
let order_id = InProcessingRedeemOrders::<T>::try_mutate(
investment_id,
|maybe_orders| -> Result<OrderId, DispatchError> {
let orders = maybe_orders
.as_ref()
.ok_or(Error::<T>::OrderNotInProcessing)?;
let redeem_amount = fulfillment.of_amount.mul_floor(orders.amount);
let redeem_amount_payment = fulfillment
.price
.checked_mul_int(redeem_amount)
.ok_or(ArithmeticError::Overflow)?;
let remaining_redeem_amount = orders
.amount
.checked_sub(&redeem_amount)
.ok_or(ArithmeticError::Underflow)?;
let investment_account =
InvestmentAccount { investment_id }.into_account_truncating();
let info = T::Accountant::info(investment_id)?;
T::Tokens::transfer(
info.payment_currency,
&info.owner,
&investment_account,
redeem_amount_payment,
Preservation::Expendable,
)?;
T::Accountant::withdraw(&investment_account, info.id, redeem_amount)?;
let order_id = RedeemOrderId::<T>::get(investment_id)
.checked_sub(1)
.ok_or(ArithmeticError::Underflow)?;
ClearedRedeemOrders::<T>::insert(investment_id, order_id, fulfillment);
ActiveRedeemOrders::<T>::try_mutate(
investment_id,
|total_orders| -> DispatchResult {
total_orders.amount = total_orders
.amount
.checked_add(&remaining_redeem_amount)
.ok_or(ArithmeticError::Overflow)?;
Ok(())
},
)?;
*maybe_orders = None;
Ok(order_id)
},
)?;
Self::deposit_event(Event::RedeemOrdersCleared {
investment_id,
order_id,
fulfillment,
});
Ok(())
}
}
impl<T: Config> InvestmentCollector<T::AccountId> for Pallet<T> {
type Error = DispatchError;
type InvestmentId = T::InvestmentId;
type Result = ();
fn collect_investment(who: T::AccountId, investment_id: T::InvestmentId) -> DispatchResult {
Pallet::<T>::do_collect_invest(who, investment_id)
.map_err(|e| e.error)
.map(|_| ())
}
fn collect_redemption(who: T::AccountId, investment_id: T::InvestmentId) -> DispatchResult {
Pallet::<T>::do_collect_redeem(who, investment_id)
.map_err(|e| e.error)
.map(|_| ())
}
}