Skip to content

Commit

Permalink
Feature/total collateral (#1031)
Browse files Browse the repository at this point in the history
* feature(kensetsu): adds field `total_collateral` that aggregates collateral locked for cdp type

* feature(kensetsu): migration
  • Loading branch information
Alexey-N-Chernyshov committed May 8, 2024
1 parent 484aa12 commit fd4d7e3
Show file tree
Hide file tree
Showing 6 changed files with 407 additions and 234 deletions.
1 change: 1 addition & 0 deletions pallets/kensetsu/benchmarking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ fn set_xor_as_collateral_type<T: Config>() {
stability_fee_rate: FixedU128::from_perbill(Perbill::from_percent(10)),
minimal_collateral_deposit: balance!(0),
},
total_collateral: balance!(0),
kusd_supply: balance!(0),
last_fee_update_time: Default::default(),
interest_coefficient: FixedU128::one(),
Expand Down
188 changes: 97 additions & 91 deletions pallets/kensetsu/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ pub struct CollateralInfo<Moment> {
/// Collateral Risk parameters set by risk management
pub risk_parameters: CollateralRiskParameters,

/// Total collateral locked in all CDPs
pub total_collateral: Balance,

/// Amount of KUSD issued for the collateral
pub kusd_supply: Balance,

Expand Down Expand Up @@ -788,7 +791,13 @@ pub mod pallet {
#[pallet::weight(<T as Config>::WeightInfo::donate())]
pub fn donate(origin: OriginFor<T>, kusd_amount: Balance) -> DispatchResult {
let who = ensure_signed(origin)?;
Self::cover_bad_debt(&who, kusd_amount)?;
technical::Pallet::<T>::transfer_in(
&T::KusdAssetId::get(),
&who,
&T::TreasuryTechAccount::get(),
kusd_amount,
)?;
Self::cover_bad_debt(kusd_amount)?;
Self::deposit_event(Event::Donation {
debt_asset_id: T::KusdAssetId::get(),
amount: kusd_amount,
Expand Down Expand Up @@ -1037,19 +1046,11 @@ pub mod pallet {
// stablecoin minted is taxed by `borrow_tax` to buy back and burn KEN, the tax
// increases debt
Self::incentivize_ken_token(borrow_tax)?;
let new_debt = cdp
.debt
.checked_add(borrow_amount_with_tax)
.ok_or(Error::<T>::ArithmeticError)?;

Self::ensure_collateral_cap(cdp.collateral_asset_id, borrow_amount_with_tax)?;
Self::ensure_protocol_cap(borrow_amount_with_tax)?;
Self::mint_to(who, borrow_amount)?;
Self::update_cdp_debt(cdp_id, new_debt)?;
Self::increase_collateral_kusd_supply(
&cdp.collateral_asset_id,
borrow_amount_with_tax,
)?;
Self::increase_cdp_debt(cdp_id, borrow_amount_with_tax)?;
Self::deposit_event(Event::DebtIncreased {
cdp_id,
owner: who.clone(),
Expand All @@ -1072,13 +1073,7 @@ pub mod pallet {
// if repaying amount exceeds debt, leftover is not burned
let to_cover_debt = amount.min(cdp.debt);
Self::burn_from(&cdp.owner, to_cover_debt)?;
Self::update_cdp_debt(
cdp_id,
cdp.debt
.checked_sub(to_cover_debt)
.ok_or(Error::<T>::ArithmeticError)?,
)?;
Self::decrease_collateral_kusd_supply(&cdp.collateral_asset_id, to_cover_debt)?;
Self::decrease_cdp_debt(cdp_id, to_cover_debt)?;
Self::deposit_event(Event::DebtPayment {
cdp_id,
owner: cdp.owner,
Expand All @@ -1095,27 +1090,14 @@ pub mod pallet {
///
/// ## Parameters
///
/// - `from`: The account from which the stablecoin will be used to cover bad debt.
/// - `kusd_amount`: The amount of stablecoin to cover bad debt.
fn cover_bad_debt(from: &AccountIdOf<T>, kusd_amount: Balance) -> DispatchResult {
fn cover_bad_debt(kusd_amount: Balance) -> DispatchResult {
let bad_debt = BadDebt::<T>::get();
let to_cover_debt = if kusd_amount <= bad_debt {
kusd_amount
} else {
technical::Pallet::<T>::transfer_in(
&T::KusdAssetId::get(),
from,
&T::TreasuryTechAccount::get(),
kusd_amount
.checked_sub(bad_debt)
.ok_or(Error::<T>::ArithmeticError)?,
)?;
bad_debt
};
Self::burn_from(from, to_cover_debt)?;
let bad_debt_change = bad_debt.min(kusd_amount);
Self::burn_treasury(bad_debt_change)?;
BadDebt::<T>::try_mutate(|bad_debt| {
*bad_debt = bad_debt
.checked_sub(to_cover_debt)
.checked_sub(bad_debt_change)
.ok_or(Error::<T>::ArithmeticError)?;
DispatchResult::Ok(())
})?;
Expand Down Expand Up @@ -1240,7 +1222,7 @@ pub mod pallet {
DispatchResult::Ok(())
})?;
}
Self::increase_collateral_kusd_supply(&cdp.collateral_asset_id, stability_fee)?;
Self::increase_collateral_stablecoin_supply(&cdp.collateral_asset_id, stability_fee)?;
Self::mint_treasury(&T::KusdAssetId::get(), stability_fee)?;

Ok(cdp)
Expand Down Expand Up @@ -1376,40 +1358,35 @@ pub mod pallet {

// penalty is a protocol profit which stays on treasury tech account
let penalty = Self::liquidation_penalty() * kusd_swapped.min(cdp.debt);
Self::cover_bad_debt(penalty)?;
let proceeds = kusd_swapped - penalty;
Self::update_cdp_collateral(
cdp_id,
cdp.collateral_amount
.checked_sub(collateral_liquidated)
.ok_or(Error::<T>::ArithmeticError)?,
)?;
// stablecoin supply change for collateral.
let kusd_supply_change: Balance;
if cdp.debt > proceeds {
Self::burn_treasury(proceeds)?;
let shortage = cdp
.debt
.checked_sub(proceeds)
.ok_or(Error::<T>::ArithmeticError)?;
if cdp.collateral_amount <= collateral_liquidated {
// no collateral, total default
// CDP debt is not covered with liquidation, now it is a protocol bad debt
let profit_burnt = Self::cover_with_protocol(shortage)?;
let shortage = cdp
.debt
.checked_sub(proceeds)
.ok_or(Error::<T>::ArithmeticError)?;
Self::cover_with_protocol(shortage)?;
// close empty CDP, debt == 0, collateral == 0
Self::decrease_cdp_debt(cdp_id, cdp.debt)?;
Self::delete_cdp(cdp_id)?;
kusd_supply_change = proceeds
.checked_add(profit_burnt)
.ok_or(Error::<T>::ArithmeticError)?;
} else {
// partly covered
Self::update_cdp_debt(cdp_id, shortage)?;
kusd_supply_change = proceeds;
Self::decrease_cdp_debt(cdp_id, proceeds)?;
}
} else {
Self::burn_treasury(cdp.debt)?;
// CDP debt is covered
Self::update_cdp_debt(cdp_id, 0)?;
kusd_supply_change = cdp.debt;
Self::decrease_cdp_debt(cdp_id, cdp.debt)?;
// There is more stablecoins than to cover debt and penalty, leftover goes to cdp.owner
let leftover = proceeds
.checked_sub(cdp.debt)
Expand All @@ -1421,7 +1398,6 @@ pub mod pallet {
leftover,
)?;
};
Self::decrease_collateral_kusd_supply(&cdp.collateral_asset_id, kusd_supply_change)?;
LiquidatedThisBlock::<T>::put(true);

Ok((collateral_liquidated, proceeds, penalty))
Expand Down Expand Up @@ -1462,7 +1438,7 @@ pub mod pallet {

/// Cover CDP debt with protocol balance
/// If protocol balance is less than amount to cover, it is a bad debt
fn cover_with_protocol(amount: Balance) -> Result<Balance, DispatchError> {
fn cover_with_protocol(amount: Balance) -> DispatchResult {
let treasury_account_id = technical::Pallet::<T>::tech_account_id_to_account_id(
&T::TreasuryTechAccount::get(),
)?;
Expand All @@ -1485,7 +1461,7 @@ pub mod pallet {
};
Self::burn_treasury(to_burn)?;

Ok(to_burn)
Ok(())
}

/// Increments CDP Id counter, changes storage state.
Expand All @@ -1512,17 +1488,54 @@ pub mod pallet {
fn update_cdp_collateral(cdp_id: CdpId, collateral_amount: Balance) -> DispatchResult {
CDPDepository::<T>::try_mutate(cdp_id, |cdp| {
let cdp = cdp.as_mut().ok_or(Error::<T>::CDPNotFound)?;
let old_collateral = cdp.collateral_amount;
CollateralInfos::<T>::try_mutate(cdp.collateral_asset_id, |collateral_info| {
let collateral_info = collateral_info
.as_mut()
.ok_or(Error::<T>::CollateralInfoNotFound)?;
collateral_info.total_collateral = collateral_info
.total_collateral
.checked_sub(old_collateral)
.ok_or(Error::<T>::ArithmeticError)?
.checked_add(collateral_amount)
.ok_or(Error::<T>::ArithmeticError)?;
Ok::<(), Error<T>>(())
})?;
cdp.collateral_amount = collateral_amount;
Ok(())
})
}

/// Updates CDP debt balance
fn update_cdp_debt(cdp_id: CdpId, debt: Balance) -> DispatchResult {
/// Updates CDP debt by increasing the value.
fn increase_cdp_debt(cdp_id: CdpId, debt_change: Balance) -> DispatchResult {
CDPDepository::<T>::try_mutate(cdp_id, |cdp| {
let cdp = cdp.as_mut().ok_or(Error::<T>::CDPNotFound)?;
cdp.debt = debt;
Ok(())
cdp.debt = cdp
.debt
.checked_add(debt_change)
.ok_or(Error::<T>::ArithmeticError)?;
Self::increase_collateral_stablecoin_supply(&cdp.collateral_asset_id, debt_change)
})
}

/// Updates CDP debt by decreasing the value.
fn decrease_cdp_debt(cdp_id: CdpId, debt_change: Balance) -> DispatchResult {
CDPDepository::<T>::try_mutate(cdp_id, |cdp| {
let cdp = cdp.as_mut().ok_or(Error::<T>::CDPNotFound)?;
cdp.debt = cdp
.debt
.checked_sub(debt_change)
.ok_or(Error::<T>::ArithmeticError)?;
CollateralInfos::<T>::try_mutate(cdp.collateral_asset_id, |collateral_info| {
let collateral_info = collateral_info
.as_mut()
.ok_or(Error::<T>::CollateralInfoNotFound)?;
collateral_info.kusd_supply = collateral_info
.kusd_supply
.checked_sub(debt_change)
.ok_or(Error::<T>::ArithmeticError)?;
Ok(())
})
})
}

Expand All @@ -1536,6 +1549,16 @@ pub mod pallet {
&cdp.owner,
transfer_out,
)?;
CollateralInfos::<T>::try_mutate(cdp.collateral_asset_id, |collateral_info| {
let collateral_info = collateral_info
.as_mut()
.ok_or(Error::<T>::CollateralInfoNotFound)?;
collateral_info.total_collateral = collateral_info
.total_collateral
.checked_sub(transfer_out)
.ok_or(Error::<T>::ArithmeticError)?;
Ok::<(), Error<T>>(())
})?;
if let Some(mut cdp_ids) = CdpOwnerIndex::<T>::take(&cdp.owner) {
cdp_ids.retain(|&x| x != cdp_id);
if !cdp_ids.is_empty() {
Expand All @@ -1551,40 +1574,6 @@ pub mod pallet {
Ok(())
}

/// Increases tracker of KUSD supply for collateral asset
fn increase_collateral_kusd_supply(
collateral_asset_id: &AssetIdOf<T>,
additional_kusd_supply: Balance,
) -> DispatchResult {
CollateralInfos::<T>::try_mutate(collateral_asset_id, |collateral_info| {
let collateral_info = collateral_info
.as_mut()
.ok_or(Error::<T>::CollateralInfoNotFound)?;
collateral_info.kusd_supply = collateral_info
.kusd_supply
.checked_add(additional_kusd_supply)
.ok_or(Error::<T>::ArithmeticError)?;
Ok(())
})
}

/// Decreases tracker of KUSD supply for collateral asset
fn decrease_collateral_kusd_supply(
collateral_asset_id: &AssetIdOf<T>,
seized_kusd_supply: Balance,
) -> DispatchResult {
CollateralInfos::<T>::try_mutate(collateral_asset_id, |collateral_info| {
let collateral_info = collateral_info
.as_mut()
.ok_or(Error::<T>::CollateralInfoNotFound)?;
collateral_info.kusd_supply = collateral_info
.kusd_supply
.checked_sub(seized_kusd_supply)
.ok_or(Error::<T>::ArithmeticError)?;
Ok(())
})
}

/// Inserts or updates `CollateralRiskParameters` for collateral asset id.
/// If `CollateralRiskParameters` exists for asset id, then updates them.
/// Else if `CollateralRiskParameters` does not exist, inserts a new value.
Expand All @@ -1611,7 +1600,8 @@ pub mod pallet {
None => {
let _ = option_collateral_info.insert(CollateralInfo {
risk_parameters: new_risk_parameters,
kusd_supply: balance!(0),
total_collateral: Balance::zero(),
kusd_supply: Balance::zero(),
last_fee_update_time: Timestamp::<T>::get(),
interest_coefficient: FixedU128::one(),
});
Expand All @@ -1621,6 +1611,22 @@ pub mod pallet {
})
}

fn increase_collateral_stablecoin_supply(
collateral_asset_id: &T::AssetId,
supply_change: Balance,
) -> DispatchResult {
CollateralInfos::<T>::try_mutate(collateral_asset_id, |collateral_info| {
let collateral_info = collateral_info
.as_mut()
.ok_or(Error::<T>::CollateralInfoNotFound)?;
collateral_info.kusd_supply = collateral_info
.kusd_supply
.checked_add(supply_change)
.ok_or(Error::<T>::ArithmeticError)?;
Ok(())
})
}

/// Returns CDP ids where the account id is owner
pub fn get_account_cdp_ids(
account_id: &AccountIdOf<T>,
Expand Down
Loading

0 comments on commit fd4d7e3

Please sign in to comment.