diff --git a/programs/uxd/src/utils/calculate_depositories_mint_collateral_amount.rs b/programs/uxd/src/utils/calculate_depositories_mint_collateral_amount.rs index 2d3a86304..bcafb2cd2 100644 --- a/programs/uxd/src/utils/calculate_depositories_mint_collateral_amount.rs +++ b/programs/uxd/src/utils/calculate_depositories_mint_collateral_amount.rs @@ -11,11 +11,12 @@ use super::compute_amount_less_fraction_floor; pub struct DepositoryInfoForMintCollateralAmount { pub directly_mintable: bool, pub target_redeemable_amount: u64, - pub redeemable_amount_under_management: u128, + pub redeemable_amount_under_management: u64, + pub redeemable_amount_under_management_cap: u64, } pub fn calculate_depositories_mint_collateral_amount( - requested_mint_collateral_amount: u64, + requested_collateral_amount: u64, depositories_info: &Vec, ) -> Result> { require!( @@ -25,10 +26,11 @@ pub fn calculate_depositories_mint_collateral_amount( // --------------------------------------------------------------------- // -- Phase 1 - // -- Calculate the maximum mintable collateral amount for each depository + // -- Calculate the under_target redeemable amount for each depository + // -- This amount is what we can mint into first, so that all depositories remain balanced // --------------------------------------------------------------------- - let depositories_maximum_mintable_collateral_amount = depositories_info + let depositories_under_target_redeemable_amount = depositories_info .iter() .map(|depository| { if !depository.directly_mintable { @@ -36,50 +38,115 @@ pub fn calculate_depositories_mint_collateral_amount( } let depository_redeemable_amount_under_management = checked_as_u64(depository.redeemable_amount_under_management)?; - if depository.target_redeemable_amount <= depository_redeemable_amount_under_management + Ok(std::cmp::min( + depository_redeemable_amount_under_management, + depository.target_redeemable_amount, + )) + }) + .collect::>>()?; + + let total_under_target_redeemable_amount = + calculate_depositories_sum_value(&depositories_under_target_redeemable_amount)?; + + // --------------------------------------------------------------------- + // -- Phase 2 + // -- Calculate the under cap redeemable amount for each depository + // -- This is the amount of redeemable past the target and under the hard-cap of the depository + // -- This amount will be used as a last-ditch effort to fullfull the mint + // -- In case all mintable depositories are filled up to their target + // --------------------------------------------------------------------- + + let depositories_under_cap_redeemable_amount = depositories_info + .iter() + .map(|depository| { + if !depository.directly_mintable { + return Ok(0); + } + if depository.redeemable_amount_under_management_cap + <= depository.target_redeemable_amount { return Ok(0); } Ok(depository - .target_redeemable_amount - .checked_sub(depository_redeemable_amount_under_management) + .redeemable_amount_under_management_cap + .checked_sub(depository.target_redeemable_amount) .ok_or(UxdError::MathOverflow)?) }) .collect::>>()?; + let total_under_cap_redeemable_amount = + calculate_depositories_sum_value(&depositories_under_cap_redeemable_amount)?; + // --------------------------------------------------------------------- - // -- Phase 2 - // -- Calculate the total amount we could possibly mint - // -- If this total is not enough, we abort + // -- Phase 3 + // -- Check that we have enough available space in our depositories + // -- To be able to fullfill the user's mint requested amount // --------------------------------------------------------------------- - let total_maximum_mintable_collateral_amount = - calculate_depositories_sum_value(&depositories_maximum_mintable_collateral_amount)?; + let total_overall_mintable_amount = total_under_target_redeemable_amount + .checked_add(total_under_cap_redeemable_amount) + .ok_or(UxdError::MathOverflow)?; require!( - total_maximum_mintable_collateral_amount >= requested_mint_collateral_amount, + total_overall_mintable_amount >= requested_collateral_amount, UxdError::DepositoriesTargerRedeemableAmountReached ); // --------------------------------------------------------------------- - // -- Phase 3 - // -- Calculate the actual minted amount per depository for the requested mint amount, - // -- it is a weighted slice of the total mintable amount, scaled by the requested mint amount + // -- Phase 4 + // -- Compute the final amounts by: + // -- trying as a best-effort to keep the balance (step1) + // -- And when keeping the perfect balance is not possible, + // -- try to consume the under_cap amount fairly (step2) // --------------------------------------------------------------------- - let depositories_mint_collateral_amount = depositories_maximum_mintable_collateral_amount - .iter() - .map(|depository_maximum_mintable_collateral_amount| { - let other_depositories_maximum_mintable_collateral_amount = - total_maximum_mintable_collateral_amount - .checked_sub(*depository_maximum_mintable_collateral_amount) - .ok_or(UxdError::MathOverflow)?; - compute_amount_less_fraction_floor( - requested_mint_collateral_amount, - other_depositories_maximum_mintable_collateral_amount, - total_maximum_mintable_collateral_amount, - ) - }) - .collect::>>()?; + let depositories_mint_collateral_amount = std::iter::zip( + depositories_under_target_redeemable_amount.iter(), + depositories_under_cap_redeemable_amount.iter(), + ) + .map( + |(depository_under_target_redeemable_amount, depository_under_cap_redeemable_amount)| { + // Step 1, try to mint any under_target amount to fill the depository to its target + let requested_primary_collateral_amount = std::cmp::min( + requested_collateral_amount, + total_under_target_redeemable_amount, + ); + let depository_primary_collateral_amount = if total_under_target_redeemable_amount > 0 { + let other_depositories_under_target_redeemable_amount = + total_under_target_redeemable_amount + .checked_sub(*depository_under_target_redeemable_amount) + .ok_or(UxdError::MathOverflow)?; + compute_amount_less_fraction_floor( + requested_primary_collateral_amount, + other_depositories_under_target_redeemable_amount, + total_under_target_redeemable_amount, + )? + } else { + 0 + }; + // Step 2, when all depositories are to their targets, try to fill up to their caps fairly + let requested_backup_collateral_amount = requested_collateral_amount + .checked_sub(requested_primary_collateral_amount) + .ok_or(UxdError::MathOverflow)?; + let depository_backup_collateral_amount = if total_under_cap_redeemable_amount > 0 { + let other_depositories_under_cap_redeemable_amount = + total_under_cap_redeemable_amount + .checked_sub(*depository_under_cap_redeemable_amount) + .ok_or(UxdError::MathOverflow)?; + compute_amount_less_fraction_floor( + requested_backup_collateral_amount, + other_depositories_under_cap_redeemable_amount, + total_under_cap_redeemable_amount, + )? + } else { + 0 + }; + // The combo of the two gives our depository amount + Ok(requested_primary_collateral_amount + .checked_add(requested_backup_collateral_amount) + .ok_or(UxdError::MathOverflow)?) + }, + ) + .collect::>>()?; // Done Ok(depositories_mint_collateral_amount) diff --git a/programs/uxd/src/utils/calculate_depositories_redeemable_amount.rs b/programs/uxd/src/utils/calculate_depositories_redeemable_amount.rs index c54e1ac32..f1a288594 100644 --- a/programs/uxd/src/utils/calculate_depositories_redeemable_amount.rs +++ b/programs/uxd/src/utils/calculate_depositories_redeemable_amount.rs @@ -55,7 +55,7 @@ pub fn calculate_depositories_redeemable_amount( // -- Phase 2 // -- Calculate the under_target redeemable amount for each depository // -- This amount is what remains redeemable after we redeemed the over_target amount - // -- This amount will be used as a last-ditch effort to fullfull the redeem when needed + // -- This amount will be used as a last-ditch effort to fullfill the redeem when needed // --------------------------------------------------------------------- let depositories_under_target_redeemable_amount = depositories_info @@ -104,36 +104,35 @@ pub fn calculate_depositories_redeemable_amount( ) .map( |(depository_over_target_redeemable_amount, depository_under_target_redeemable_amount)| { - // Total possible redeemable amounts for both steps - let requested_first_redeemable_amount = std::cmp::min( + // Step 1, try to use the over_target amounts, weighted for each depository + let requested_primary_redeemable_amount = std::cmp::min( requested_redeemable_amount, total_over_target_redeemable_amount, ); - let requested_second_redeemable_amount = requested_redeemable_amount - .checked_sub(requested_first_redeemable_amount) - .ok_or(UxdError::MathOverflow)?; - // First step, try to use the over_target amounts, weighted for each depository - let depository_first_redeemable_amount = if total_over_target_redeemable_amount > 0 { + let depository_primary_redeemable_amount = if total_over_target_redeemable_amount > 0 { let other_depositories_over_target_redeemable_amount = total_over_target_redeemable_amount .checked_sub(*depository_over_target_redeemable_amount) .ok_or(UxdError::MathOverflow)?; compute_amount_less_fraction_floor( - requested_first_redeemable_amount, + requested_primary_redeemable_amount, other_depositories_over_target_redeemable_amount, total_over_target_redeemable_amount, )? } else { 0 }; - // Second step, anything under_target must be taken as backup - let depository_second_redeemable_amount = if total_under_target_redeemable_amount > 0 { + // Step 2, anything under_target must be used as backup + let requested_backup_redeemable_amount = requested_redeemable_amount + .checked_sub(requested_primary_redeemable_amount) + .ok_or(UxdError::MathOverflow)?; + let depository_backup_redeemable_amount = if total_under_target_redeemable_amount > 0 { let other_depositories_under_target_redeemable_amount = total_under_target_redeemable_amount .checked_sub(*depository_under_target_redeemable_amount) .ok_or(UxdError::MathOverflow)?; compute_amount_less_fraction_floor( - requested_second_redeemable_amount, + requested_backup_redeemable_amount, other_depositories_under_target_redeemable_amount, total_under_target_redeemable_amount, )? @@ -141,8 +140,8 @@ pub fn calculate_depositories_redeemable_amount( 0 }; // The combo of the two gives our depository amount - Ok(depository_first_redeemable_amount - .checked_add(depository_second_redeemable_amount) + Ok(depository_primary_redeemable_amount + .checked_add(depository_backup_redeemable_amount) .ok_or(UxdError::MathOverflow)?) }, ) diff --git a/programs/uxd/tests/integration_tests/api/program_uxd/procedures/process_setup_router_depositories_fields.rs b/programs/uxd/tests/integration_tests/api/program_uxd/procedures/process_setup_router_depositories_fields.rs index 6dab2be02..60d642990 100644 --- a/programs/uxd/tests/integration_tests/api/program_uxd/procedures/process_setup_router_depositories_fields.rs +++ b/programs/uxd/tests/integration_tests/api/program_uxd/procedures/process_setup_router_depositories_fields.rs @@ -1,10 +1,10 @@ use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::Keypair; +use uxd::instructions::EditAlloyxVaultDepositoryFields; use uxd::instructions::EditCredixLpDepositoryFields; use uxd::instructions::EditIdentityDepositoryFields; use uxd::instructions::EditMercurialVaultDepositoryFields; -use uxd::instructions::EditAlloyxVaultDepositoryFields; use crate::integration_tests::api::program_context; use crate::integration_tests::api::program_uxd; diff --git a/programs/uxd/tests/integration_tests/suites/mod.rs b/programs/uxd/tests/integration_tests/suites/mod.rs index 88b870aeb..0607423f6 100644 --- a/programs/uxd/tests/integration_tests/suites/mod.rs +++ b/programs/uxd/tests/integration_tests/suites/mod.rs @@ -6,7 +6,7 @@ pub mod test_credix_lp_depository_rebalance_illiquid; pub mod test_credix_lp_depository_rebalance_liquid; pub mod test_credix_lp_depository_rebalance_no_overflow; pub mod test_credix_lp_depository_rebalance_under_requested; -pub mod test_ensure_devnet; +//pub mod test_ensure_devnet; pub mod test_identity_depository_edit; pub mod test_identity_depository_mint_and_redeem; pub mod test_mercurial_vault_depository_edit;