From 5a1eb056345f4e9ad3aa0125565930155d152ec5 Mon Sep 17 00:00:00 2001
From: cdamian <17934949+cdamian@users.noreply.github.com>
Date: Wed, 8 Nov 2023 12:43:54 +0100
Subject: [PATCH] integration-tests: Add missing LP foreign investment test

---
 .../liquidity_pools/foreign_investments.rs    | 389 +++++++++++++++++-
 .../tests/liquidity_pools/transfers.rs        |   9 -
 2 files changed, 388 insertions(+), 10 deletions(-)

diff --git a/runtime/integration-tests/src/liquidity_pools/pallet/development/tests/liquidity_pools/foreign_investments.rs b/runtime/integration-tests/src/liquidity_pools/pallet/development/tests/liquidity_pools/foreign_investments.rs
index c784aefb8a..e0b27cdfa5 100644
--- a/runtime/integration-tests/src/liquidity_pools/pallet/development/tests/liquidity_pools/foreign_investments.rs
+++ b/runtime/integration-tests/src/liquidity_pools/pallet/development/tests/liquidity_pools/foreign_investments.rs
@@ -3182,10 +3182,396 @@ mod mismatching_currencies {
 		});
 	}
 
+	/// 1. increase initial redeem
+	/// 2. process partial redemption
+	/// 3. collect
+	/// 4. process redemption
+	/// 5. fulfill swap order should implicitly collect
+	#[tokio::test]
+	async fn fulfill_redeem_swap_order_requires_collect() {
+		let mut env = {
+			let mut genesis = Storage::default();
+			genesis::default_balances::<DevelopmentRuntime>(&mut genesis);
+			env::test_env_with_centrifuge_storage(Handle::current(), genesis)
+		};
+
+		setup_test_env(&mut env);
+
+		env.with_mut_state(Chain::Para(PARA_ID), || {
+			let pool_id = DEFAULT_POOL_ID;
+			let investor: AccountId =
+				AccountConverter::<DevelopmentRuntime, LocationToAccountId>::convert((
+					DOMAIN_MOONBEAM,
+					Keyring::Bob.into(),
+				));
+			let trader: AccountId = Keyring::Alice.into();
+			let pool_currency: CurrencyId = AUSD_CURRENCY_ID;
+			let foreign_currency: CurrencyId = USDT_CURRENCY_ID;
+			let pool_currency_decimals = currency_decimals::AUSD;
+			let invest_amount_pool_denominated: u128 = 10 * dollar(18);
+			let swap_order_id = 1;
+			create_currency_pool(pool_id, pool_currency, pool_currency_decimals.into());
+			let invest_amount_foreign_denominated: u128 = enable_usdt_trading(
+				pool_currency,
+				invest_amount_pool_denominated,
+				true,
+				true,
+				true,
+				|| {},
+			);
+			// invest in pool currency to reach `InvestmentOngoing` quickly
+			do_initial_increase_investment(
+				pool_id,
+				invest_amount_pool_denominated,
+				investor.clone(),
+				pool_currency,
+				true,
+			);
+			// Manually set payment currency since we removed it in the above shortcut setup
+			InvestmentPaymentCurrency::<DevelopmentRuntime>::insert(
+				&investor,
+				default_investment_id(),
+				foreign_currency,
+			);
+			assert_ok!(Tokens::mint_into(
+				foreign_currency,
+				&trader,
+				invest_amount_foreign_denominated * 2
+			));
+
+			// Decrease invest setup to have invest order swapping into foreign currency
+			let msg = LiquidityPoolMessage::DecreaseInvestOrder {
+				pool_id,
+				tranche_id: default_tranche_id(pool_id),
+				investor: investor.clone().into(),
+				currency: general_currency_index(foreign_currency),
+				amount: invest_amount_foreign_denominated,
+			};
+			assert_ok!(LiquidityPools::submit(
+				DEFAULT_DOMAIN_ADDRESS_MOONBEAM,
+				msg.clone()
+			));
+
+			// Redeem setup: Increase and process
+			assert_ok!(Tokens::mint_into(
+				default_investment_id().into(),
+				&Domain::convert(DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain()),
+				invest_amount_pool_denominated
+			));
+			let msg = LiquidityPoolMessage::IncreaseRedeemOrder {
+				pool_id,
+				tranche_id: default_tranche_id(pool_id),
+				investor: investor.clone().into(),
+				currency: general_currency_index(foreign_currency),
+				amount: invest_amount_pool_denominated,
+			};
+			assert_ok!(LiquidityPools::submit(
+				DEFAULT_DOMAIN_ADDRESS_MOONBEAM,
+				msg.clone()
+			));
+			let pool_account =
+				pallet_pool_system::pool_types::PoolLocator { pool_id }.into_account_truncating();
+			assert_ok!(Tokens::mint_into(
+				pool_currency,
+				&pool_account,
+				invest_amount_pool_denominated
+			));
+			assert_ok!(Investments::process_redeem_orders(default_investment_id()));
+			// Process 50% of redemption at 25% rate, i.e. 1 pool currency = 4 tranche
+			// tokens
+			assert_ok!(Investments::redeem_fulfillment(
+				default_investment_id(),
+				FulfillmentWithPrice {
+					of_amount: Perquintill::from_percent(50),
+					price: Ratio::checked_from_rational(1, 4).unwrap(),
+				}
+			));
+			assert_eq!(
+				ForeignInvestments::foreign_investment_info(swap_order_id)
+					.unwrap()
+					.last_swap_reason
+					.unwrap(),
+				TokenSwapReason::Investment
+			);
+			assert_ok!(Investments::collect_redemptions_for(
+				RuntimeOrigin::signed(Keyring::Charlie.into()),
+				investor.clone(),
+				default_investment_id()
+			));
+			assert_eq!(
+				ForeignInvestments::foreign_investment_info(swap_order_id)
+					.unwrap()
+					.last_swap_reason
+					.unwrap(),
+				TokenSwapReason::InvestmentAndRedemption
+			);
+			assert_eq!(
+				InvestmentState::<DevelopmentRuntime>::get(&investor, default_investment_id()),
+				InvestState::ActiveSwapIntoForeignCurrency {
+					swap: Swap {
+						amount: invest_amount_foreign_denominated,
+						currency_in: foreign_currency,
+						currency_out: pool_currency
+					}
+				}
+			);
+			assert_eq!(
+				RedemptionState::<DevelopmentRuntime>::get(&investor, default_investment_id()),
+				RedeemState::RedeemingAndActiveSwapIntoForeignCurrency {
+					redeem_amount: invest_amount_pool_denominated / 2,
+					swap: Swap {
+						amount: invest_amount_foreign_denominated / 8,
+						currency_in: foreign_currency,
+						currency_out: pool_currency
+					}
+				}
+			);
+			assert_eq!(
+				RedemptionPayoutCurrency::<DevelopmentRuntime>::get(
+					&investor,
+					default_investment_id()
+				)
+				.unwrap(),
+				foreign_currency
+			);
+			let swap_amount =
+				invest_amount_foreign_denominated + invest_amount_foreign_denominated / 8;
+			assert!(System::events().iter().any(|e| {
+				e.event
+					== pallet_order_book::Event::<DevelopmentRuntime>::OrderUpdated {
+						order_id: swap_order_id,
+						account: investor.clone(),
+						buy_amount: swap_amount,
+						sell_rate_limit: Ratio::one(),
+						min_fulfillment_amount: min_fulfillment_amount(foreign_currency),
+					}
+					.into()
+			}));
+			ensure_executed_collect_redeem_not_dispatched();
+
+			// Process remaining redemption at 25% rate, i.e. 1 pool currency =
+			// 4 tranche tokens
+			assert_ok!(Investments::process_redeem_orders(default_investment_id()));
+			assert_ok!(Investments::redeem_fulfillment(
+				default_investment_id(),
+				FulfillmentWithPrice {
+					of_amount: Perquintill::from_percent(100),
+					price: Ratio::checked_from_rational(1, 4).unwrap(),
+				}
+			));
+			assert_ok!(Investments::collect_redemptions_for(
+				RuntimeOrigin::signed(Keyring::Charlie.into()),
+				investor.clone(),
+				default_investment_id()
+			));
+			assert_eq!(
+				InvestmentState::<DevelopmentRuntime>::get(&investor, default_investment_id()),
+				InvestState::ActiveSwapIntoForeignCurrency {
+					swap: Swap {
+						amount: invest_amount_foreign_denominated,
+						currency_in: foreign_currency,
+						currency_out: pool_currency
+					}
+				}
+			);
+			assert_eq!(
+				RedemptionState::<DevelopmentRuntime>::get(&investor, default_investment_id()),
+				RedeemState::ActiveSwapIntoForeignCurrency {
+					swap: Swap {
+						amount: invest_amount_foreign_denominated / 4,
+						currency_in: foreign_currency,
+						currency_out: pool_currency
+					}
+				}
+			);
+			let swap_amount =
+				invest_amount_foreign_denominated + invest_amount_foreign_denominated / 4;
+			assert!(System::events().iter().any(|e| {
+				e.event
+					== pallet_order_book::Event::<DevelopmentRuntime>::OrderUpdated {
+						order_id: swap_order_id,
+						account: investor.clone(),
+						buy_amount: swap_amount,
+						sell_rate_limit: Ratio::one(),
+						min_fulfillment_amount: min_fulfillment_amount(foreign_currency),
+					}
+					.into()
+			}));
+
+			// Partially fulfilling the swap order below the invest swapping amount should
+			// still have both states swapping into foreign
+			assert_ok!(OrderBook::fill_order_partial(
+				RuntimeOrigin::signed(trader.clone()),
+				swap_order_id,
+				invest_amount_foreign_denominated / 2
+			));
+			assert!(System::events().iter().any(|e| {
+				e.event
+					== pallet_order_book::Event::<DevelopmentRuntime>::OrderFulfillment {
+						order_id: swap_order_id,
+						placing_account: investor.clone(),
+						fulfilling_account: trader.clone(),
+						partial_fulfillment: true,
+						fulfillment_amount: invest_amount_foreign_denominated / 2,
+						currency_in: foreign_currency,
+						currency_out: pool_currency,
+						sell_rate_limit: Ratio::one(),
+					}
+					.into()
+			}));
+			assert_eq!(
+				InvestmentState::<DevelopmentRuntime>::get(&investor, default_investment_id()),
+				InvestState::ActiveSwapIntoForeignCurrencyAndSwapIntoForeignDone {
+					swap: Swap {
+						amount: invest_amount_foreign_denominated / 2,
+						currency_in: foreign_currency,
+						currency_out: pool_currency
+					},
+					done_amount: invest_amount_foreign_denominated / 2
+				}
+			);
+			assert_eq!(
+				RedemptionState::<DevelopmentRuntime>::get(&investor, default_investment_id()),
+				RedeemState::ActiveSwapIntoForeignCurrency {
+					swap: Swap {
+						amount: invest_amount_foreign_denominated / 4,
+						currency_in: foreign_currency,
+						currency_out: pool_currency
+					},
+				}
+			);
+			assert!(
+				RedemptionPayoutCurrency::<DevelopmentRuntime>::contains_key(
+					&investor,
+					default_investment_id()
+				)
+			);
+			assert!(ForeignInvestments::foreign_investment_info(swap_order_id).is_some());
+			assert!(
+				ForeignInvestments::token_swap_order_ids(&investor, default_investment_id())
+					.is_some()
+			);
+			ensure_executed_collect_redeem_not_dispatched();
+
+			// Partially fulfilling the swap order for the remaining invest swap amount
+			// should still clear the investment state
+			assert_ok!(OrderBook::fill_order_partial(
+				RuntimeOrigin::signed(trader.clone()),
+				swap_order_id,
+				invest_amount_foreign_denominated / 2
+			));
+			assert!(!InvestmentState::<DevelopmentRuntime>::contains_key(
+				&investor,
+				default_investment_id()
+			),);
+			assert_eq!(
+				RedemptionState::<DevelopmentRuntime>::get(&investor, default_investment_id()),
+				RedeemState::ActiveSwapIntoForeignCurrency {
+					swap: Swap {
+						amount: invest_amount_foreign_denominated / 4,
+						currency_in: foreign_currency,
+						currency_out: pool_currency
+					},
+				}
+			);
+			assert!(
+				RedemptionPayoutCurrency::<DevelopmentRuntime>::contains_key(
+					&investor,
+					default_investment_id()
+				)
+			);
+			assert!(ForeignInvestments::foreign_investment_info(swap_order_id).is_some());
+			assert!(
+				ForeignInvestments::token_swap_order_ids(&investor, default_investment_id())
+					.is_some()
+			);
+			ensure_executed_collect_redeem_not_dispatched();
+
+			// Partially fulfilling the swap order below the redeem swap amount should still
+			// clear the investment state
+			assert_ok!(OrderBook::fill_order_partial(
+				RuntimeOrigin::signed(trader.clone()),
+				swap_order_id,
+				invest_amount_foreign_denominated / 8
+			));
+			assert!(!InvestmentState::<DevelopmentRuntime>::contains_key(
+				&investor,
+				default_investment_id()
+			),);
+			assert_eq!(
+				RedemptionState::<DevelopmentRuntime>::get(&investor, default_investment_id()),
+				RedeemState::ActiveSwapIntoForeignCurrencyAndSwapIntoForeignDone {
+					swap: Swap {
+						amount: invest_amount_foreign_denominated / 8,
+						currency_in: foreign_currency,
+						currency_out: pool_currency
+					},
+					done_amount: invest_amount_foreign_denominated / 8
+				}
+			);
+			assert!(
+				RedemptionPayoutCurrency::<DevelopmentRuntime>::contains_key(
+					&investor,
+					default_investment_id()
+				)
+			);
+			assert!(ForeignInvestments::foreign_investment_info(swap_order_id).is_some());
+			assert!(
+				ForeignInvestments::token_swap_order_ids(&investor, default_investment_id())
+					.is_some()
+			);
+			ensure_executed_collect_redeem_not_dispatched();
+
+			// Partially fulfilling the swap order below the redeem swap amount should still
+			// clear the investment state
+			assert_ok!(OrderBook::fill_order_partial(
+				RuntimeOrigin::signed(trader.clone()),
+				swap_order_id,
+				invest_amount_foreign_denominated / 8
+			));
+			assert!(!InvestmentState::<DevelopmentRuntime>::contains_key(
+				&investor,
+				default_investment_id()
+			),);
+			assert!(!RedemptionState::<DevelopmentRuntime>::contains_key(
+				&investor,
+				default_investment_id()
+			),);
+			assert!(
+				!RedemptionPayoutCurrency::<DevelopmentRuntime>::contains_key(
+					&investor,
+					default_investment_id()
+				)
+			);
+			assert!(ForeignInvestments::foreign_investment_info(swap_order_id).is_none());
+			assert!(
+				ForeignInvestments::token_swap_order_ids(&investor, default_investment_id())
+					.is_none()
+			);
+			assert!(System::events().iter().any(|e| {
+				e.event
+					== pallet_liquidity_pools_gateway::Event::OutboundMessageSubmitted {
+						sender: TreasuryAccount::get(),
+						domain: DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain(),
+						message: pallet_liquidity_pools::Message::ExecutedCollectRedeem {
+							pool_id,
+							tranche_id: default_tranche_id(pool_id),
+							investor: investor.clone().into(),
+							currency: general_currency_index(foreign_currency),
+							currency_payout: invest_amount_foreign_denominated / 4,
+							tranche_tokens_payout: invest_amount_pool_denominated,
+							remaining_redeem_amount: 0,
+						},
+					}
+					.into()
+			}));
+		});
+	}
+
 	/// Similar to [concurrent_swap_orders_same_direction] but with partial
 	/// fulfillment
 	#[tokio::test]
-	async fn fulfill_redeem_swap_order_requires_collect() {
+	async fn partial_fulfillment_concurrent_swap_orders_same_direction() {
 		let mut env = {
 			let mut genesis = Storage::default();
 			genesis::default_balances::<DevelopmentRuntime>(&mut genesis);
@@ -3195,6 +3581,7 @@ mod mismatching_currencies {
 		setup_test_env(&mut env);
 
 		env.with_mut_state(Chain::Para(PARA_ID), || {
+			// Increase invest setup
 			let pool_id = DEFAULT_POOL_ID;
 			let investor: AccountId =
 				AccountConverter::<DevelopmentRuntime, LocationToAccountId>::convert((
diff --git a/runtime/integration-tests/src/liquidity_pools/pallet/development/tests/liquidity_pools/transfers.rs b/runtime/integration-tests/src/liquidity_pools/pallet/development/tests/liquidity_pools/transfers.rs
index 17c81ded9b..d95e21ce2b 100644
--- a/runtime/integration-tests/src/liquidity_pools/pallet/development/tests/liquidity_pools/transfers.rs
+++ b/runtime/integration-tests/src/liquidity_pools/pallet/development/tests/liquidity_pools/transfers.rs
@@ -200,7 +200,6 @@ async fn transfer_non_tranche_tokens_to_local() {
 			amount,
 		};
 
-		// assert_eq!(OrmlTokens::total_issuance(currency_id), AUSD_ED * 2);
 		assert_eq!(OrmlTokens::total_issuance(currency_id), 0);
 
 		// Finally, verify that we can now transfer the tranche to the destination
@@ -208,15 +207,7 @@ async fn transfer_non_tranche_tokens_to_local() {
 		assert_ok!(LiquidityPools::submit(DEFAULT_DOMAIN_ADDRESS_MOONBEAM, msg));
 
 		// Verify that the correct amount was minted
-		// assert_eq!(
-		// 	OrmlTokens::total_issuance(currency_id),
-		// 	amount + AUSD_ED * 2
-		// );
 		assert_eq!(OrmlTokens::total_issuance(currency_id), amount);
-		// assert_eq!(
-		// 	OrmlTokens::free_balance(currency_id, &receiver),
-		// 	amount + AUSD_ED
-		// );
 		assert_eq!(OrmlTokens::free_balance(currency_id, &receiver), amount);
 
 		// Verify empty transfers throw