Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: pallet-pool-system - Ratio Calculation #1856

Merged
merged 10 commits into from
Jun 6, 2024
43 changes: 34 additions & 9 deletions pallets/pool-system/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ pub mod pallet {
traits::{tokens::Preservation, Contains, EnsureOriginWithArg},
PalletId,
};
use rev_slice::SliceExt;
use sp_runtime::{traits::BadOrigin, ArithmeticError};

use super::*;
Expand Down Expand Up @@ -1239,15 +1240,39 @@ pub mod pallet {

let executed_amounts = epoch.tranches.fulfillment_cash_flows(solution)?;
let total_assets = epoch.nav.total(pool.reserve.total)?;
let tranche_ratios = epoch.tranches.combine_with_residual_top(
&executed_amounts,
|tranche, &(invest, redeem)| {
Ok(Perquintill::from_rational(
tranche.supply.ensure_add(invest)?.ensure_sub(redeem)?,
total_assets,
))
},
)?;

let tranche_ratios = {
let mut sum_non_residual_tranche_ratios = Perquintill::zero();
let num_tranches = pool.tranches.num_tranches();
let mut current_tranche = 1;
let mut ratios = epoch
.tranches
// NOTE: Reversing amounts, as residual amount is on top.
.combine_with_non_residual_top(
executed_amounts.rev(),
|tranche, &(invest, redeem)| {
let ratio = if current_tranche < num_tranches {
lemunozm marked this conversation as resolved.
Show resolved Hide resolved
Perquintill::from_rational(
tranche.supply.ensure_add(invest)?.ensure_sub(redeem)?,
total_assets,
)
} else {
Perquintill::one().ensure_sub(sum_non_residual_tranche_ratios)?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sum of tranche ratios can never be higher than one, right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this line is annulated by line 1263. No matter which value sum_non_residual_tranche_ratios has, at the end of this iteration sum_non_residual_tranche_ratios will always have the value one(). Is this the expected behavior?

};

sum_non_residual_tranche_ratios.ensure_add_assign(ratio)?;
current_tranche.ensure_add_assign(1)?;

Ok(ratio)
},
)?;

// NOTE: We need to reverse the ratios here, as the residual tranche is on top
// all the time
ratios.reverse();

ratios
};

pool.tranches.rebalance_tranches(
T::Time::now(),
Expand Down
6 changes: 3 additions & 3 deletions pallets/pool-system/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ use cfg_types::{
time::TimeProvider,
tokens::{CurrencyId, CustomMetadata, TrancheCurrency},
};
#[cfg(feature = "runtime-benchmarks")]
use frame_support::dispatch::RawOrigin;
use frame_support::{
assert_ok, derive_impl,
dispatch::RawOrigin,
parameter_types,
assert_ok, derive_impl, parameter_types,
traits::{Contains, EnsureOriginWithArg, Hooks, PalletInfoAccess, SortedMembers},
Blake2_128, PalletId, StorageHasher,
};
Expand Down
8 changes: 5 additions & 3 deletions pallets/pool-system/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ use crate::{
UnhealthyState,
};

mod ratios;

const AUSD_CURRENCY_ID: CurrencyId = CurrencyId::ForeignAsset(1);

pub mod util {
Expand Down Expand Up @@ -73,7 +75,7 @@ pub mod util {
TrancheInput {
tranche_type: TrancheType::NonResidual {
interest_rate_per_sec: Rate::default(),
min_risk_buffer: Perquintill::default(),
min_risk_buffer: Perquintill::from_percent(50),
},
seniority: None,
metadata: TrancheMetadata {
Expand All @@ -83,7 +85,7 @@ pub mod util {
},
],
AUSD_CURRENCY_ID,
0,
10_000 * CURRENCY,
vec![],
)
.unwrap();
Expand All @@ -95,7 +97,7 @@ pub mod util {
// forcing to call `execute_epoch()` later.
Investments::update_invest_order(
RuntimeOrigin::signed(0),
TrancheCurrency::generate(0, JuniorTrancheId::get()),
TrancheCurrency::generate(0, SeniorTrancheId::get()),
500 * CURRENCY,
)
.unwrap();
Expand Down
130 changes: 130 additions & 0 deletions pallets/pool-system/src/tests/ratios.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Copyright 2021 Centrifuge Foundation (centrifuge.io).
//
// This file is part of the Centrifuge chain project.
// Centrifuge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version (see http://www.gnu.org/licenses).
// Centrifuge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
use sp_arithmetic::traits::EnsureSub;

use super::*;

#[test]
fn ensure_ratios_are_distributed_correctly() {
new_test_ext().execute_with(|| {
let pool_owner = DEFAULT_POOL_OWNER;
let pool_owner_origin = RuntimeOrigin::signed(pool_owner);

util::default_pool::create();

// Assert ratios are all zero
crate::Pool::<Runtime>::get(0)
.unwrap()
.tranches
.residual_top_slice()
.iter()
.for_each(|tranche| {
assert_eq!(tranche.ratio, Perquintill::from_percent(0));
});

// Force min_epoch_time to 0 without using update
// as this breaks the runtime-defined pool
// parameter bounds and update will not allow this.
Pool::<Runtime>::try_mutate(0, |maybe_pool| -> Result<(), ()> {
maybe_pool.as_mut().unwrap().parameters.min_epoch_time = 0;
maybe_pool.as_mut().unwrap().parameters.max_nav_age = u64::MAX;
Ok(())
})
.unwrap();

invest_close_and_collect(
0,
vec![
(0, JuniorTrancheId::get(), 500 * CURRENCY),
(0, SeniorTrancheId::get(), 500 * CURRENCY),
],
);

// Ensure ratios are 50/50
Pool::<Runtime>::get(0)
.unwrap()
.tranches
.residual_top_slice()
.iter()
.for_each(|tranche| {
assert_eq!(tranche.ratio, Perquintill::from_percent(50));
});

// Attempt to redeem 40%
assert_ok!(Investments::update_redeem_order(
RuntimeOrigin::signed(0),
TrancheCurrency::generate(0, SeniorTrancheId::get()),
200 * CURRENCY
));
assert_ok!(PoolSystem::close_epoch(pool_owner_origin.clone(), 0));
assert_ok!(Investments::collect_redemptions(
RuntimeOrigin::signed(0),
TrancheCurrency::generate(0, SeniorTrancheId::get()),
));

let new_residual_ratio = Perquintill::from_rational(5u64, 8u64);
let mut next_ratio = new_residual_ratio;

// Ensure ratios are 500/800 and 300/800
Pool::<Runtime>::get(0)
.unwrap()
.tranches
.residual_top_slice()
.iter()
.for_each(|tranche| {
assert_eq!(tranche.ratio, next_ratio);
next_ratio = Perquintill::one().ensure_sub(next_ratio).unwrap();
});

// Attempt to redeem everything
assert_ok!(Investments::update_redeem_order(
RuntimeOrigin::signed(0),
TrancheCurrency::generate(0, SeniorTrancheId::get()),
300 * CURRENCY
));
assert_ok!(PoolSystem::close_epoch(pool_owner_origin.clone(), 0));
assert_ok!(Investments::collect_redemptions(
RuntimeOrigin::signed(0),
TrancheCurrency::generate(0, SeniorTrancheId::get()),
));

let new_residual_ratio = Perquintill::one();
let mut next_ratio = new_residual_ratio;

// Ensure ratios are 100/0
lemunozm marked this conversation as resolved.
Show resolved Hide resolved
Pool::<Runtime>::get(0)
.unwrap()
.tranches
.residual_top_slice()
.iter()
.for_each(|tranche| {
assert_eq!(tranche.ratio, next_ratio);
next_ratio = Perquintill::one().ensure_sub(next_ratio).unwrap();
});

// Ensure ratio goes up again
invest_close_and_collect(0, vec![(0, SeniorTrancheId::get(), 300 * CURRENCY)]);
let new_residual_ratio = Perquintill::from_rational(5u64, 8u64);
let mut next_ratio = new_residual_ratio;

// Ensure ratios are 500/800 and 300/800
Pool::<Runtime>::get(0)
.unwrap()
.tranches
.residual_top_slice()
.iter()
.for_each(|tranche| {
assert_eq!(tranche.ratio, next_ratio);
next_ratio = Perquintill::one().ensure_sub(next_ratio).unwrap();
});
});
}
Loading