-
Notifications
You must be signed in to change notification settings - Fork 72
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
Loans: Add multiple triggers for write-off #1314
Changes from 20 commits
bc2b958
5a26036
b40990a
1d10d1c
3413645
4e1947e
8cc79e6
ef115b2
8651a32
b8513bb
2a76255
611b811
cf7fd86
8ba6c7b
4cb9e8d
02fae81
bcb0979
48ac41f
ad045b9
841f12a
cd848b7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -36,9 +36,14 @@ | |||||
//! The whole pallet is optimized for the more expensive extrinsic that is | ||||||
//! [`Pallet::update_portfolio_valuation()`] that should go through all active loans. | ||||||
|
||||||
pub mod migrations; | ||||||
pub mod migrations { | ||||||
pub mod nuke; | ||||||
pub mod v1; | ||||||
} | ||||||
|
||||||
pub mod types; | ||||||
pub mod valuation; | ||||||
pub mod write_off; | ||||||
|
||||||
#[cfg(test)] | ||||||
mod mock; | ||||||
|
@@ -82,10 +87,12 @@ pub mod pallet { | |||||
traits::{BadOrigin, One, Zero}, | ||||||
ArithmeticError, FixedPointOperand, | ||||||
}; | ||||||
use sp_std::vec::Vec; | ||||||
use types::{ | ||||||
self, ActiveLoan, AssetOf, BorrowLoanError, CloseLoanError, CreateLoanError, LoanInfoOf, | ||||||
PortfolioValuationUpdateType, WriteOffState, WriteOffStatus, WrittenOffError, | ||||||
PortfolioValuationUpdateType, WrittenOffError, | ||||||
}; | ||||||
use write_off::{WriteOffRule, WriteOffStatus}; | ||||||
|
||||||
use super::*; | ||||||
|
||||||
|
@@ -94,7 +101,7 @@ pub mod pallet { | |||||
<T as Config>::CurrencyId, | ||||||
>>::PoolId; | ||||||
|
||||||
const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); | ||||||
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); | ||||||
|
||||||
#[pallet::pallet] | ||||||
#[pallet::generate_store(pub(super) trait Store)] | ||||||
|
@@ -237,7 +244,7 @@ pub mod pallet { | |||||
_, | ||||||
Blake2_128Concat, | ||||||
PoolIdOf<T>, | ||||||
BoundedVec<WriteOffState<T::Rate>, T::MaxWriteOffPolicySize>, | ||||||
BoundedVec<WriteOffRule<T::Rate>, T::MaxWriteOffPolicySize>, | ||||||
ValueQuery, | ||||||
>; | ||||||
|
||||||
|
@@ -293,7 +300,7 @@ pub mod pallet { | |||||
}, | ||||||
WriteOffPolicyUpdated { | ||||||
pool_id: PoolIdOf<T>, | ||||||
policy: BoundedVec<WriteOffState<T::Rate>, T::MaxWriteOffPolicySize>, | ||||||
policy: BoundedVec<WriteOffRule<T::Rate>, T::MaxWriteOffPolicySize>, | ||||||
}, | ||||||
} | ||||||
|
||||||
|
@@ -305,9 +312,9 @@ pub mod pallet { | |||||
LoanNotFound, | ||||||
/// Emits when a loan exist but it's not active | ||||||
LoanNotActive, | ||||||
/// Emits when a write-off state is not found in a policy for a specific loan. | ||||||
/// Emits when a write-off rule is not found in a policy for a specific loan. | ||||||
/// It happens when there is no policy or the loan is not overdue. | ||||||
NoValidWriteOffState, | ||||||
NoValidWriteOffRule, | ||||||
/// Emits when the NFT owner is not found | ||||||
NFTOwnerNotFound, | ||||||
/// Emits when NFT owner doesn't match the expected owner | ||||||
|
@@ -473,7 +480,7 @@ pub mod pallet { | |||||
/// - Write off by admin with percentage 0.5 and penalty 0.2 | ||||||
/// - Time passes and the policy can be applied. | ||||||
/// - Write of with a policy that says: percentage 0.3, penaly 0.4 | ||||||
/// - The loan is written off with the maximum between the policy and the current state: | ||||||
/// - The loan is written off with the maximum between the policy and the current rule: | ||||||
/// percentage 0.5, penaly 0.4 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Realizing that only means you did a deep review, thanks!! |
||||||
/// | ||||||
/// No special permisions are required to this call. | ||||||
|
@@ -488,8 +495,9 @@ pub mod pallet { | |||||
ensure_signed(origin)?; | ||||||
|
||||||
let (status, _count) = Self::update_active_loan(pool_id, loan_id, |loan| { | ||||||
let state = Self::find_write_off_state(pool_id, loan.maturity_date())?; | ||||||
let limit = state.status().max(loan.write_off_status()); | ||||||
let rule = Self::find_write_off_rule(pool_id, loan)? | ||||||
.ok_or(Error::<T>::NoValidWriteOffRule)?; | ||||||
let limit = rule.status.compose_max(loan.write_off_status()); | ||||||
|
||||||
loan.write_off(&limit, &limit)?; | ||||||
|
||||||
|
@@ -532,10 +540,11 @@ pub mod pallet { | |||||
}; | ||||||
|
||||||
let _count = Self::update_active_loan(pool_id, loan_id, |loan| { | ||||||
let state = Self::find_write_off_state(pool_id, loan.maturity_date()); | ||||||
let limit = state.map(|s| s.status()).unwrap_or_else(|_| status.clone()); | ||||||
let rule = Self::find_write_off_rule(pool_id, loan)?; | ||||||
let limit = rule.map(|r| r.status).unwrap_or_else(|| status.clone()); | ||||||
|
||||||
loan.write_off(&limit, &status) | ||||||
loan.write_off(&limit, &status)?; | ||||||
Ok(limit) | ||||||
lemunozm marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
})?; | ||||||
|
||||||
Self::deposit_event(Event::<T>::WrittenOff { | ||||||
|
@@ -584,7 +593,7 @@ pub mod pallet { | |||||
Ok(()) | ||||||
} | ||||||
|
||||||
/// Updates the write off policy. | ||||||
/// Updates the write off policy with write off rules. | ||||||
/// | ||||||
/// The write off policy is used to automatically set a write off minimum value to the | ||||||
/// loan. | ||||||
|
@@ -593,7 +602,7 @@ pub mod pallet { | |||||
pub fn update_write_off_policy( | ||||||
origin: OriginFor<T>, | ||||||
pool_id: PoolIdOf<T>, | ||||||
policy: BoundedVec<WriteOffState<T::Rate>, T::MaxWriteOffPolicySize>, | ||||||
policy: BoundedVec<WriteOffRule<T::Rate>, T::MaxWriteOffPolicySize>, | ||||||
) -> DispatchResult { | ||||||
let who = ensure_signed(origin)?; | ||||||
Self::ensure_role(pool_id, &who, PoolRole::PoolAdmin)?; | ||||||
|
@@ -679,16 +688,38 @@ pub mod pallet { | |||||
}) | ||||||
} | ||||||
|
||||||
fn find_write_off_state( | ||||||
/// From all overdue write off rules, it returns the one has highest percentage | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit
Suggested change
|
||||||
/// (or highest penalty, if same percentage) that can be applied. | ||||||
/// | ||||||
/// Suppose a policy with the following rules: | ||||||
/// - overdue_days: 5, percentage 10% | ||||||
/// - overdue_days: 10, percentage 30% | ||||||
/// - overdue_days: 15, percentage 20% | ||||||
/// | ||||||
/// If the loan is not overdue, it will not return any rule. | ||||||
/// If the loan overdue by 4 days, it will not return any rule. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit
Suggested change
|
||||||
/// If the loan is overdue by 9 days, it will return the first rule. | ||||||
/// If the loan is overdue by 60 days, it will return the second rule | ||||||
/// (because it has a higher percetage). | ||||||
fn find_write_off_rule( | ||||||
pool_id: PoolIdOf<T>, | ||||||
maturity_date: Moment, | ||||||
) -> Result<WriteOffState<T::Rate>, DispatchError> { | ||||||
WriteOffState::find_best( | ||||||
WriteOffPolicy::<T>::get(pool_id).into_iter(), | ||||||
maturity_date, | ||||||
T::Time::now().as_secs(), | ||||||
) | ||||||
.ok_or_else(|| Error::<T>::NoValidWriteOffState.into()) | ||||||
loan: &ActiveLoan<T>, | ||||||
) -> Result<Option<WriteOffRule<T::Rate>>, DispatchError> { | ||||||
Ok(WriteOffPolicy::<T>::get(pool_id) | ||||||
.into_iter() | ||||||
.filter_map(|rule| { | ||||||
rule.triggers | ||||||
.iter() | ||||||
.map(|trigger| loan.check_write_off_trigger(&trigger.0)) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Maybe it would be better to destructure into the 0th tuple element directly to make it more explicit?
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It has only one element, the |
||||||
.find(|e| match e { | ||||||
Ok(value) => *value, | ||||||
Err(_) => true, | ||||||
}) | ||||||
.map(|result| result.map(|_| rule)) | ||||||
}) | ||||||
.collect::<Result<Vec<_>, _>>()? // This exits if error before getting the maximum | ||||||
.into_iter() | ||||||
.max_by(|r1, r2| r1.status.cmp(&r2.status))) | ||||||
} | ||||||
|
||||||
fn update_portfolio_valuation_with_pv( | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should not keep them around forever or hide them behind a feature flag to not bloat the wasm. But rather minor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once the migration is not used, the unused code should be removed from the wasm. Until I know, Substrate passes
wasm-opt
that removes unused symbols/code and things like those.