diff --git a/libs/traits/src/investments.rs b/libs/traits/src/investments.rs index 7cc5eae6ac..4741668eda 100644 --- a/libs/traits/src/investments.rs +++ b/libs/traits/src/investments.rs @@ -91,7 +91,8 @@ pub trait Investment { pub trait InvestmentCollector { type Error: Debug; type InvestmentId; - type Result: Debug; + type InvestResult: Debug; + type RedeemResult: Debug; /// Collect the results of a user's invest orders for the given /// investment. If any amounts are not fulfilled they are directly @@ -99,7 +100,7 @@ pub trait InvestmentCollector { fn collect_investment( who: AccountId, investment_id: Self::InvestmentId, - ) -> Result; + ) -> Result; /// Collect the results of a users redeem orders for the given /// investment. If any amounts are not fulfilled they are directly @@ -107,7 +108,7 @@ pub trait InvestmentCollector { fn collect_redemption( who: AccountId, investment_id: Self::InvestmentId, - ) -> Result; + ) -> Result; } /// A trait, when implemented must take care of diff --git a/libs/types/src/investments.rs b/libs/types/src/investments.rs index 891dbb3264..afc057e890 100644 --- a/libs/types/src/investments.rs +++ b/libs/types/src/investments.rs @@ -136,21 +136,32 @@ impl RedeemCollection { } } -/// The collected investment/redemption amount for an account +/// The collected investment amounts for an account #[derive(Encode, Default, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] -pub struct CollectedAmount { - /// The amount which was was collected - /// * If investment: Tranche tokens - /// * If redemption: Payment currency +pub struct CollectedInvestment { + /// The amount of tranche tokens which was was collected pub amount_collected: Balance, - /// The amount which invested and converted during processing based on the - /// fulfillment price(s) - /// * If investment: Payment currency - /// * If redemption: Tranche tokens + /// The previously invested amount of payment currency which was converted + /// during processing based on the fulfillment price(s) pub amount_payment: Balance, } +/// The collected redemption amounts for an account +#[derive(Encode, Default, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct CollectedRedemption { + /// The amount of payment currency which was was collected + pub amount_collected: Balance, + + /// The previously invested amount of tranche tokens which was converted + /// during processing based on the fulfillment price(s) + pub amount_payment: Balance, + + /// The remaining collectable amount which was already processed during + /// epoch execution + pub amount_remaining_collectable: Balance, +} + /// A representation of an investment identifier and the corresponding owner. /// /// NOTE: Trimmed version of `InvestmentInfo` required for foreign investments. diff --git a/pallets/foreign-investments/src/impls/mod.rs b/pallets/foreign-investments/src/impls/mod.rs index 3000c88317..d2f023105a 100644 --- a/pallets/foreign-investments/src/impls/mod.rs +++ b/pallets/foreign-investments/src/impls/mod.rs @@ -16,7 +16,7 @@ use cfg_traits::{ StatusNotificationHook, TokenSwaps, }; use cfg_types::investments::{ - CollectedAmount, ExecutedForeignCollectInvest, ExecutedForeignCollectRedeem, + CollectedInvestment, ExecutedForeignCollectInvest, ExecutedForeignCollectRedeem, ExecutedForeignDecrease, Swap, }; use frame_support::{traits::Get, transactional}; @@ -30,7 +30,7 @@ use crate::{ InnerRedeemState, InvestState, InvestTransition, RedeemState, RedeemTransition, TokenSwapReason, }, - CollectedRedemption, Config, Error, Event, ForeignInvestmentInfo, ForeignInvestmentInfoOf, + CollectedRedemptions, Config, Error, Event, ForeignInvestmentInfo, ForeignInvestmentInfoOf, InvestmentState, Pallet, RedemptionState, SwapOf, TokenSwapOrderIds, TokenSwapReasons, }; @@ -153,7 +153,7 @@ impl ForeignInvestment for Pallet { ) -> Result, DispatchError> { // No need to transition or update state as collection of tranche tokens is // independent of the current `InvestState` - let CollectedAmount:: { + let CollectedInvestment:: { amount_collected, amount_payment, } = T::Investment::collect_investment(who.clone(), investment_id)?; @@ -173,10 +173,11 @@ impl ForeignInvestment for Pallet { pool_currency: T::CurrencyId, ) -> Result<(), DispatchError> { let collected = T::Investment::collect_redemption(who.clone(), investment_id)?; - CollectedRedemption::::try_mutate(who, investment_id, |collected_redemption| { + CollectedRedemptions::::try_mutate(who, investment_id, |collected_redemption| { collected_redemption .amount_collected .ensure_add_assign(collected.amount_collected)?; + // TODO: We only need this at a later stage collected_redemption .amount_payment .ensure_add_assign(collected.amount_payment)?; @@ -186,11 +187,14 @@ impl ForeignInvestment for Pallet { // Transition state to initiate swap from pool to return currency let pre_state = RedemptionState::::get(who, investment_id.clone()).unwrap_or_default(); - let post_state = pre_state.transition(RedeemTransition::Collect(SwapOf:: { - amount: collected.amount_collected, - currency_in: return_currency, - currency_out: pool_currency, - }))?; + let post_state = pre_state.transition(RedeemTransition::Collect( + SwapOf:: { + amount: collected.amount_collected, + currency_in: return_currency, + currency_out: pool_currency, + }, + collected.amount_remaining_collectable, + ))?; Pallet::::apply_redeem_state_transition(who, investment_id, post_state)?; @@ -537,9 +541,9 @@ impl Pallet { inner_redeem_state: InnerRedeemState, ) -> Result>, DispatchError> { // TODO: Should just be amount and maybe factor in the remaining amount as well - let collected_redemption = CollectedRedemption::::get(who, investment_id); + let collected_redemption = CollectedRedemptions::::get(who, investment_id); - // Send notification and kill `CollectedRedemption` iff the state includes + // Send notification and kill `CollectedRedemptions` iff the state includes // `SwapIntoReturnDone` without `ActiveSwapIntoReturnCurrency` match inner_redeem_state { InnerRedeemState::SwapIntoReturnDone { done_swap, .. } @@ -553,12 +557,12 @@ impl Pallet { who, investment_id, done_swap.currency_in, - CollectedAmount { + CollectedInvestment { amount_collected: done_swap.amount, amount_payment: collected_redemption.amount_payment, }, )?; - CollectedRedemption::::remove(who, investment_id); + CollectedRedemptions::::remove(who, investment_id); Ok(()) } _ => Ok(()), @@ -949,7 +953,7 @@ impl Pallet { who: &T::AccountId, investment_id: T::InvestmentId, currency: T::CurrencyId, - collected: CollectedAmount, + collected: CollectedInvestment, ) -> DispatchResult { T::ExecutedCollectRedeemHook::notify_status_change( ForeignInvestmentInfoOf:: { diff --git a/pallets/foreign-investments/src/impls/redeem.rs b/pallets/foreign-investments/src/impls/redeem.rs index 69ec67827f..8f2d742b70 100644 --- a/pallets/foreign-investments/src/impls/redeem.rs +++ b/pallets/foreign-investments/src/impls/redeem.rs @@ -56,7 +56,9 @@ where RedeemTransition::FulfillSwapOrder(swap) => { Self::handle_fulfilled_swap_order(&self, swap) } - RedeemTransition::Collect(swap) => Self::handle_collect(&self, swap), + RedeemTransition::Collect(swap, remaining_collectable) => { + Self::handle_collect(&self, swap, remaining_collectable) + } } } @@ -573,6 +575,7 @@ where fn transition_collect( &self, collected_swap: Swap, + remaining_collectable: Balance, ) -> Result { ensure!( self.get_active_swap() @@ -590,66 +593,141 @@ where RedeemingAndActiveSwapIntoReturnCurrency { .. } | RedeemingAndSwapIntoReturnDone { .. } | RedeemingAndActiveSwapIntoReturnCurrencyAndSwapIntoReturnDone { .. } => Ok(*self), - // TODO: Probably just ignore `collect_amount` - CollectableRedemption { collect_amount } => Ok(Self::ActiveSwapIntoReturnCurrency { - swap: collected_swap, - }), - RedeemingAndCollectableRedemption { redeem_amount, collect_amount } => Ok(Self::RedeemingAndActiveSwapIntoReturnCurrency { - redeem_amount, - swap: collected_swap, - }), - RedeemingAndCollectableRedemptionAndActiveSwapIntoReturnCurrency { redeem_amount, collect_amount, swap } => Ok(Self::RedeemingAndActiveSwapIntoReturnCurrency { - redeem_amount, - swap: Swap { + CollectableRedemption { .. } => { + if remaining_collectable.is_zero() { + Ok(Self::ActiveSwapIntoReturnCurrency { + swap: collected_swap, + }) + } else { + Ok(Self::CollectableRedemptionAndActiveSwapIntoReturnCurrency { + collect_amount: remaining_collectable, + swap: collected_swap, + }) + } + }, + RedeemingAndCollectableRedemption { redeem_amount, .. } => { + if remaining_collectable.is_zero() { + Ok(Self::RedeemingAndActiveSwapIntoReturnCurrency { + redeem_amount, + swap: collected_swap, + }) + } else { + Ok(Self::RedeemingAndCollectableRedemptionAndActiveSwapIntoReturnCurrency { + redeem_amount, + swap: collected_swap, + collect_amount: remaining_collectable, + }) + } + }, + RedeemingAndCollectableRedemptionAndActiveSwapIntoReturnCurrency { redeem_amount, swap, .. } => { + let new_swap = Swap { amount: swap.amount.ensure_add(collected_swap.amount)?, ..collected_swap + }; + if remaining_collectable.is_zero() { + Ok(Self::RedeemingAndActiveSwapIntoReturnCurrency { + redeem_amount, + swap: new_swap + }) + } else { + Ok(Self::RedeemingAndCollectableRedemptionAndActiveSwapIntoReturnCurrency { + redeem_amount, + swap: new_swap, + collect_amount: remaining_collectable + }) } - }), - RedeemingAndCollectableRedemptionAndSwapIntoReturnDone { redeem_amount, collect_amount, done_swap } => { - Ok(Self::RedeemingAndActiveSwapIntoReturnCurrencyAndSwapIntoReturnDone { - redeem_amount, - swap: Swap { - amount: collected_swap.amount, - ..collected_swap - }, - done_amount: done_swap.amount - }) }, - RedeemingAndCollectableRedemptionAndActiveSwapIntoReturnCurrencyAndSwapIntoReturnDone { redeem_amount, collect_amount, swap, done_amount } => { - Ok(Self::RedeemingAndActiveSwapIntoReturnCurrencyAndSwapIntoReturnDone { - redeem_amount, - swap: Swap { - amount: swap.amount.ensure_add(collected_swap.amount)?, - ..collected_swap - }, - done_amount - }) + RedeemingAndCollectableRedemptionAndSwapIntoReturnDone { redeem_amount, done_swap, .. } => { + let new_swap = Swap { + amount: collected_swap.amount, + ..collected_swap + }; + if remaining_collectable.is_zero() { + Ok(Self::RedeemingAndActiveSwapIntoReturnCurrencyAndSwapIntoReturnDone { + redeem_amount, + swap: new_swap, + done_amount: done_swap.amount + }) + } else { + Ok(Self::RedeemingAndCollectableRedemptionAndActiveSwapIntoReturnCurrencyAndSwapIntoReturnDone { + redeem_amount, + swap: new_swap, + done_amount: done_swap.amount, + collect_amount: remaining_collectable + }) + } }, - CollectableRedemptionAndActiveSwapIntoReturnCurrency { collect_amount, swap } => { - Ok(Self::ActiveSwapIntoReturnCurrency { - swap: Swap { - amount: swap.amount.ensure_add(collected_swap.amount)?, - ..collected_swap - } - }) + RedeemingAndCollectableRedemptionAndActiveSwapIntoReturnCurrencyAndSwapIntoReturnDone { redeem_amount, swap, done_amount, .. } => { + let new_swap = Swap { + amount: swap.amount.ensure_add(collected_swap.amount)?, + ..collected_swap + }; + if remaining_collectable.is_zero() { + Ok(Self::RedeemingAndActiveSwapIntoReturnCurrencyAndSwapIntoReturnDone { + redeem_amount, + swap: new_swap, + done_amount + }) + } else { + Ok(Self::RedeemingAndCollectableRedemptionAndActiveSwapIntoReturnCurrencyAndSwapIntoReturnDone { + redeem_amount, + swap: new_swap, + done_amount, + collect_amount: remaining_collectable + }) + } + }, + CollectableRedemptionAndActiveSwapIntoReturnCurrency { swap, .. } => { + let new_swap = Swap { + amount: swap.amount.ensure_add(collected_swap.amount)?, + ..collected_swap + }; + if remaining_collectable.is_zero() { + Ok(Self::ActiveSwapIntoReturnCurrency { + swap: new_swap + }) + } else { + Ok(Self::CollectableRedemptionAndActiveSwapIntoReturnCurrency { + swap: new_swap, + collect_amount: remaining_collectable + }) + } }, CollectableRedemptionAndSwapIntoReturnDone { collect_amount, done_swap } => { - Ok(Self::ActiveSwapIntoReturnCurrencyAndSwapIntoReturnDone { - swap: Swap { - amount: collected_swap.amount, - ..collected_swap - }, - done_amount: done_swap.amount, - }) + let new_swap = Swap { + amount: collected_swap.amount, + ..collected_swap + }; + if remaining_collectable.is_zero() { + Ok(Self::ActiveSwapIntoReturnCurrencyAndSwapIntoReturnDone { + swap: new_swap, + done_amount: done_swap.amount, + }) + } else { + Ok(Self::CollectableRedemptionAndActiveSwapIntoReturnCurrencyAndSwapIntoReturnDone { + swap: new_swap, + done_amount: done_swap.amount, + collect_amount: remaining_collectable + }) + } }, - CollectableRedemptionAndActiveSwapIntoReturnCurrencyAndSwapIntoReturnDone { collect_amount, swap, done_amount } => { - Ok(Self::ActiveSwapIntoReturnCurrencyAndSwapIntoReturnDone { - swap: Swap { - amount: swap.amount.ensure_add(collected_swap.amount)?, - ..collected_swap - }, - done_amount - }) + CollectableRedemptionAndActiveSwapIntoReturnCurrencyAndSwapIntoReturnDone { swap, done_amount , .. } => { + let new_swap = Swap { + amount: swap.amount.ensure_add(collected_swap.amount)?, + ..collected_swap + }; + if remaining_collectable.is_zero() { + Ok(Self::ActiveSwapIntoReturnCurrencyAndSwapIntoReturnDone { + swap: new_swap, + done_amount + }) + } else { + Ok(Self::CollectableRedemptionAndActiveSwapIntoReturnCurrencyAndSwapIntoReturnDone { + swap: new_swap, + done_amount, + collect_amount: remaining_collectable + }) + } }, } } @@ -802,14 +880,21 @@ where /// Throws if the state does not allow for collection or the the inner state /// includes an active/done swap with mismatching currencies to the provided /// ones. - fn handle_collect(&self, swap: Swap) -> Result { + fn handle_collect( + &self, + swap: Swap, + remaining_collectable: Balance, + ) -> Result { match self { RedeemState::NoState | RedeemState::Invested { .. } => Err(DispatchError::Other( "Invalid redeem state when transitioning collect", )), - RedeemState::NotInvestedAnd { inner } | RedeemState::InvestedAnd { inner, .. } => Ok( - Self::swap_inner_state(&self, inner.transition_collect(swap)?), - ), + RedeemState::NotInvestedAnd { inner } | RedeemState::InvestedAnd { inner, .. } => { + Ok(Self::swap_inner_state( + &self, + inner.transition_collect(swap, remaining_collectable)?, + )) + } } } } diff --git a/pallets/foreign-investments/src/lib.rs b/pallets/foreign-investments/src/lib.rs index 1c24ef8c5b..42a227b28b 100644 --- a/pallets/foreign-investments/src/lib.rs +++ b/pallets/foreign-investments/src/lib.rs @@ -45,7 +45,8 @@ pub mod pallet { StatusNotificationHook, TokenSwaps, }; use cfg_types::investments::{ - CollectedAmount, ExecutedForeignCollectRedeem, ExecutedForeignDecrease, + CollectedInvestment, CollectedRedemption, ExecutedForeignCollectRedeem, + ExecutedForeignDecrease, }; use frame_support::{dispatch::HasCompact, pallet_prelude::*}; use frame_system::pallet_prelude::*; @@ -124,7 +125,8 @@ pub mod pallet { Self::AccountId, Error = DispatchError, InvestmentId = Self::InvestmentId, - Result = CollectedAmount, + InvestResult = CollectedInvestment, + RedeemResult = CollectedRedemption, >; /// The default sell price limit for token swaps which defines the @@ -256,13 +258,13 @@ pub mod pallet { /// in pool currency and ends with having swapped the entire amount to /// return currency. #[pallet::storage] - pub(super) type CollectedRedemption = StorageDoubleMap< + pub(super) type CollectedRedemptions = StorageDoubleMap< _, Blake2_128Concat, T::AccountId, Blake2_128Concat, T::InvestmentId, - CollectedAmount, + CollectedInvestment, ValueQuery, >; diff --git a/pallets/foreign-investments/src/types.rs b/pallets/foreign-investments/src/types.rs index 2e05db82a6..d8b27efeb1 100644 --- a/pallets/foreign-investments/src/types.rs +++ b/pallets/foreign-investments/src/types.rs @@ -325,5 +325,5 @@ pub enum RedeemTransition< IncreaseRedeemOrder(Balance), DecreaseRedeemOrder(Balance), FulfillSwapOrder(Swap), - Collect(Swap), + Collect(Swap, Balance), } diff --git a/pallets/investments/src/lib.rs b/pallets/investments/src/lib.rs index 4f9d767866..407b2dc577 100644 --- a/pallets/investments/src/lib.rs +++ b/pallets/investments/src/lib.rs @@ -23,7 +23,10 @@ use cfg_traits::{ }; use cfg_types::{ fixed_point::FixedPointNumberExtension, - investments::{CollectedAmount, InvestCollection, InvestmentAccount, RedeemCollection}, + investments::{ + CollectedInvestment, CollectedRedemption, InvestCollection, InvestmentAccount, + RedeemCollection, + }, orders::{FulfillmentWithPrice, Order, TotalOrder}, }; use frame_support::{ @@ -634,7 +637,7 @@ where pub(crate) fn do_collect_invest( who: T::AccountId, investment_id: T::InvestmentId, - ) -> Result<(CollectedAmount, PostDispatchInfo), DispatchErrorWithPostInfo> { + ) -> Result<(CollectedInvestment, PostDispatchInfo), DispatchErrorWithPostInfo> { let info = T::Accountant::info(investment_id).map_err(|_| Error::::UnknownInvestment)?; InvestOrders::::try_mutate(&who, investment_id, |maybe_order| { // Exit early if order does not exist @@ -713,7 +716,7 @@ where }, ); - let collected_investment = CollectedAmount { + let collected_investment = CollectedInvestment { amount_collected: collection.payout_investment_invest, amount_payment, }; @@ -739,7 +742,7 @@ where pub(crate) fn do_collect_redeem( who: T::AccountId, investment_id: T::InvestmentId, - ) -> Result<(CollectedAmount, PostDispatchInfo), DispatchErrorWithPostInfo> { + ) -> Result<(CollectedRedemption, PostDispatchInfo), DispatchErrorWithPostInfo> { let info = T::Accountant::info(investment_id).map_err(|_| Error::::UnknownInvestment)?; RedeemOrders::::try_mutate(&who, investment_id, |maybe_order| { // Exit early if order does not exist @@ -774,6 +777,7 @@ where }); // TODO: Return correct weight // - Accountant::info() + 2 * Storage::read() + Storage::write() + // TODO: Check in foreign if its okay to return 0 remaining return Ok((Default::default(), ().into())); } @@ -826,9 +830,10 @@ where }, ); - let collected_redemption = CollectedAmount { + let collected_redemption = CollectedRedemption { amount_collected: collection.payout_investment_redeem, amount_payment, + amount_remaining_collectable: collection.remaining_investment_redeem, }; Self::deposit_event(Event::RedeemOrdersCollected { @@ -1436,14 +1441,15 @@ where InvestmentProperties>, { type Error = DispatchError; + type InvestResult = CollectedInvestment; type InvestmentId = T::InvestmentId; - type Result = CollectedAmount; + type RedeemResult = CollectedRedemption; // TODO: Write unit test in investments to test this fn collect_investment( who: T::AccountId, investment_id: T::InvestmentId, - ) -> Result, DispatchError> { + ) -> Result, DispatchError> { Pallet::::do_collect_invest(who, investment_id) .map_err(|e| e.error) .map(|(collected_amount, _)| collected_amount) @@ -1453,7 +1459,7 @@ where fn collect_redemption( who: T::AccountId, investment_id: T::InvestmentId, - ) -> Result, DispatchError> { + ) -> Result, DispatchError> { Pallet::::do_collect_redeem(who, investment_id) .map_err(|e| e.error) .map(|(collected_amount, _)| collected_amount)