Skip to content

Commit

Permalink
Fix Apollo liquidation logic (#1046)
Browse files Browse the repository at this point in the history
* Changing logic for Liquidate extrinsic in order to ensure total liquidity does not get changed, and writing additional test along with adjusting previous one tied to it

* New extrinsic for editing pool info

* Fixing previous benchmarks, adding new benchmarks and adding new tests for edit_pool_info extrinsic

* Fixing clippy errors

* minor format fix

* Optimizing number of reads in liquidate extrinsic, fixing typos, and fixing the event in repay function
  • Loading branch information
CvijanCBS authored May 17, 2024
1 parent 2fbc5bc commit 1975edb
Show file tree
Hide file tree
Showing 5 changed files with 595 additions and 71 deletions.
59 changes: 42 additions & 17 deletions pallets/apollo-platform/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ fn setup_benchmark<T: Config>() -> Result<(), &'static str> {
let dai_owner: T::AccountId = assets::AssetOwners::<T>::get::<T::AssetId>(DAI.into()).unwrap();
let ceres_owner: T::AccountId =
assets::AssetOwners::<T>::get::<T::AssetId>(CERES_ASSET_ID.into()).unwrap();
let apollo_owner: T::AccountId =
assets::AssetOwners::<T>::get::<T::AssetId>(APOLLO_ASSET_ID.into()).unwrap();

// Register assets
Assets::<T>::register_asset_id(
Expand All @@ -77,19 +79,6 @@ fn setup_benchmark<T: Config>() -> Result<(), &'static str> {
)
.unwrap();

Assets::<T>::register_asset_id(
owner.clone(),
APOLLO_ASSET_ID.into(),
AssetSymbol(b"APOLLO".to_vec()),
AssetName(b"Apollo".to_vec()),
DEFAULT_BALANCE_PRECISION,
Balance::from(0u32),
true,
None,
None,
)
.unwrap();

// Mint assets to Alice
Assets::<T>::mint(
RawOrigin::Signed(xor_owner).into(),
Expand All @@ -116,15 +105,15 @@ fn setup_benchmark<T: Config>() -> Result<(), &'static str> {
.unwrap();

Assets::<T>::mint(
owner_origin.clone(),
RawOrigin::Signed(apollo_owner.clone()).into(),
APOLLO_ASSET_ID.into(),
owner.clone(),
balance!(500),
)
.unwrap();

Assets::<T>::mint(
owner_origin.clone(),
RawOrigin::Signed(apollo_owner).into(),
APOLLO_ASSET_ID.into(),
pallet_account,
balance!(100000),
Expand Down Expand Up @@ -268,6 +257,8 @@ benchmarks! {

let lending_amount = balance!(100);

setup_benchmark::<T>()?;

ApolloPlatform::<T>::add_pool(
RawOrigin::Signed(caller).into(),
asset_id.into(),
Expand All @@ -286,7 +277,7 @@ benchmarks! {
lending_amount
).unwrap()
} verify {
assert_last_event::<T>(Event::Lended(alice, asset_id.into(), lending_amount).into());
assert_last_event::<T>(Event::Lent(alice, asset_id.into(), lending_amount).into());
}

borrow {
Expand All @@ -306,7 +297,7 @@ benchmarks! {
let lending_amount_alice = balance!(300);
let lending_amount_bob = balance!(200000);

let collateral_amount = balance!(101.00000000000000001);
let collateral_amount = balance!(99.009900990099009999);
let borrow_amount = balance!(100);

setup_benchmark::<T>()?;
Expand Down Expand Up @@ -720,4 +711,38 @@ benchmarks! {
verify {
assert_last_event::<T>(Event::PoolRemoved(caller, asset_id.into()).into());
}

edit_pool_info {
let caller = pallet::AuthorityAccount::<T>::get();
let asset_id = XOR;
let initial_parameter_value = balance!(1);
let edit_parameter_value = balance!(0.8);

ApolloPlatform::<T>::add_pool(
RawOrigin::Signed(caller.clone()).into(),
asset_id.into(),
initial_parameter_value,
initial_parameter_value,
initial_parameter_value,
initial_parameter_value,
initial_parameter_value,
initial_parameter_value,
initial_parameter_value,
).unwrap();
}: {
ApolloPlatform::<T>::edit_pool_info(
RawOrigin::Signed(caller.clone()).into(),
asset_id.into(),
edit_parameter_value,
edit_parameter_value,
edit_parameter_value,
edit_parameter_value,
edit_parameter_value,
edit_parameter_value,
edit_parameter_value,
).unwrap()
}
verify {
assert_last_event::<T>(Event::PoolInfoEdited(caller, asset_id.into()).into());
}
}
155 changes: 131 additions & 24 deletions pallets/apollo-platform/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ pub mod pallet {
#[pallet::without_storage_info]
pub struct Pallet<T>(PhantomData<T>);

/// Lended asset -> AccountId -> LendingPosition
/// Lent asset -> AccountId -> LendingPosition
#[pallet::storage]
#[pallet::getter(fn user_lending_info)]
pub type UserLendingInfo<T: Config> = StorageDoubleMap<
Expand Down Expand Up @@ -216,8 +216,8 @@ pub mod pallet {
pub enum Event<T: Config> {
/// Pool added [who, asset_id]
PoolAdded(AccountIdOf<T>, AssetIdOf<T>),
/// Lended [who, asset_id, amount]
Lended(AccountIdOf<T>, AssetIdOf<T>, Balance),
/// Lent [who, asset_id, amount]
Lent(AccountIdOf<T>, AssetIdOf<T>, Balance),
/// Borrowed [who, collateral_asset, collateral_amount, borrow_asset, borrow_amount]
Borrowed(AccountIdOf<T>, AssetIdOf<T>, Balance, AssetIdOf<T>, Balance),
/// ClaimedLendingRewards [who, asset_id, amount]
Expand All @@ -236,6 +236,8 @@ pub mod pallet {
Liquidated(AccountIdOf<T>, AssetIdOf<T>),
/// Pool removed [who, asset_id]
PoolRemoved(AccountIdOf<T>, AssetIdOf<T>),
/// Pool info edited [who, asset_id]
PoolInfoEdited(AccountIdOf<T>, AssetIdOf<T>),
}

#[pallet::error]
Expand All @@ -248,7 +250,7 @@ pub mod pallet {
InvalidPoolParameters,
/// Pool does not exist
PoolDoesNotExist,
/// The amount that is being lended is invalid
/// The amount that is being lent is invalid
InvalidLendingAmount,
/// Collateral token does not exists
CollateralTokenDoesNotExist,
Expand All @@ -258,8 +260,8 @@ pub mod pallet {
SameCollateralAndBorrowingAssets,
/// No liquidity for borrowing asset
NoLiquidityForBorrowingAsset,
/// Nothing lended
NothingLended,
/// Nothing lent
NothingLent,
/// Invalid collateral amount
InvalidCollateralAmount,
/// Can not transfer borrowing amount
Expand Down Expand Up @@ -458,7 +460,7 @@ pub mod pallet {
pool_info.total_liquidity += lending_amount;
<PoolData<T>>::insert(lending_asset, pool_info);

Self::deposit_event(Event::Lended(user, lending_asset, lending_amount));
Self::deposit_event(Event::Lent(user, lending_asset, lending_amount));
Ok(().into())
}

Expand Down Expand Up @@ -501,7 +503,7 @@ pub mod pallet {
<PoolData<T>>::get(collateral_asset).ok_or(Error::<T>::PoolDoesNotExist)?;
ensure!(!collateral_pool_info.is_removed, Error::<T>::PoolIsRemoved);
let mut user_lending_info = <UserLendingInfo<T>>::get(collateral_asset, user.clone())
.ok_or(Error::<T>::NothingLended)?;
.ok_or(Error::<T>::NothingLent)?;
let collateral_asset_price = Self::get_price(collateral_asset);

// Calculate required collateral asset in dollars
Expand Down Expand Up @@ -609,10 +611,10 @@ pub mod pallet {
let block_number = <frame_system::Pallet<T>>::block_number();
let pool_info = PoolData::<T>::get(asset_id).ok_or(Error::<T>::PoolDoesNotExist)?;

// Check if user has lended or borrowed rewards
// Check if user has lent or borrowed rewards
if is_lending {
let mut lend_user_info = <UserLendingInfo<T>>::get(asset_id, user.clone())
.ok_or(Error::<T>::NothingLended)?;
.ok_or(Error::<T>::NothingLent)?;
let interests =
Self::calculate_lending_earnings(&lend_user_info, &pool_info, block_number);
lend_user_info.lending_interest += interests.0 + interests.1;
Expand Down Expand Up @@ -693,7 +695,7 @@ pub mod pallet {
let mut pool_info =
<PoolData<T>>::get(withdrawn_asset).ok_or(Error::<T>::PoolDoesNotExist)?;
let mut user_info = <UserLendingInfo<T>>::get(withdrawn_asset, user.clone())
.ok_or(Error::<T>::NothingLended)?;
.ok_or(Error::<T>::NothingLent)?;

ensure!(
withdrawn_amount <= user_info.lending_amount,
Expand Down Expand Up @@ -776,6 +778,9 @@ pub mod pallet {
user_info.borrowing_rewards += interest_and_reward.1;
user_info.last_borrowing_block = block_number;

// Total repaid
let mut total_repaid: Balance = amount_to_repay;

if amount_to_repay <= user_info.borrowing_interest {
// If user is repaying only part or whole interest
user_info.borrowing_interest =
Expand Down Expand Up @@ -878,9 +883,12 @@ pub mod pallet {
user_info.borrowing_interest,
borrowing_asset,
)?;

// Updating total repaid
total_repaid = total_borrowed_amount;
}

Self::deposit_event(Event::Repaid(user, borrowing_asset, amount_to_repay));
Self::deposit_event(Event::Repaid(user, borrowing_asset, total_repaid));
Ok(().into())
}

Expand Down Expand Up @@ -971,27 +979,76 @@ pub mod pallet {
return Err(Error::<T>::InvalidLiquidation.into());
}

// Distribute liquidated collaterals to users and reserves
// Calculate total borrow and total collateral in dollars
let mut total_borrowed: Balance = 0;

// Distributing and calculating total borrwed
for (collateral_asset, user_info) in user_infos.iter() {
// Calculate collateral in dollars
let collateral_asset_price = Self::get_price(*collateral_asset);
let collateral_amount_in_dollars =
(FixedWrapper::from(user_info.collateral_amount)
* FixedWrapper::from(collateral_asset_price))
.try_into_balance()
.unwrap_or(0);

// Calculate user's borrowed amount
let borrow_asset_price = Self::get_price(asset_id);
let user_borrowed_in_dollars = (FixedWrapper::from(user_info.borrowing_amount)
* FixedWrapper::from(borrow_asset_price))
.try_into_balance()
.unwrap_or(0);

// Calculating amount to distribute in dollars and converting to collateral token amount
let amount_to_distribute_in_dollars =
collateral_amount_in_dollars.saturating_sub(user_borrowed_in_dollars);
let amount_to_distribute = (FixedWrapper::from(amount_to_distribute_in_dollars)
/ FixedWrapper::from(collateral_asset_price))
.try_into_balance()
.unwrap_or(0);

// Distributing the amount calculated as sufficit of collateral asset in dollars over borrowed amount in dollars
let _ = Self::distribute_protocol_interest(
*collateral_asset,
user_info.collateral_amount,
amount_to_distribute,
asset_id,
);

// Amount to exchange
let amount_to_exchange = (FixedWrapper::from(user_borrowed_in_dollars)
/ FixedWrapper::from(collateral_asset_price))
.try_into_balance()
.unwrap_or(0);

// Exchange collateral asset into borrowed asset on Pallet
T::LiquidityProxyPallet::exchange(
DEXId::Polkaswap.into(),
&Self::account_id(),
&Self::account_id(),
collateral_asset,
&asset_id,
SwapAmount::with_desired_input(amount_to_exchange, Balance::zero()),
LiquiditySourceFilter::empty(DEXId::Polkaswap.into()),
)?;

// Updating collateral pool total_collateral amount and total_liquidity
let mut collateral_pool_info =
PoolData::<T>::get(*collateral_asset).unwrap_or_default();
collateral_pool_info.total_collateral = collateral_pool_info
.total_collateral
.saturating_sub(user_info.collateral_amount);
total_borrowed += user_info.borrowing_amount;

<PoolData<T>>::insert(*collateral_asset, collateral_pool_info);
// Add user's borrowed amount tied with this asset to total_borrowed in given asset
total_borrowed += user_info.borrowing_amount;
}

// Updating total_borrowed and total_liquidity for given asset
let mut borrow_pool_info = PoolData::<T>::get(asset_id).unwrap_or_default();
borrow_pool_info.total_borrowed = borrow_pool_info
.total_borrowed
.saturating_sub(total_borrowed);
borrow_pool_info.total_liquidity += total_borrowed;

<PoolData<T>>::insert(asset_id, borrow_pool_info);
<UserBorrowingInfo<T>>::remove(asset_id, user.clone());
Expand Down Expand Up @@ -1047,6 +1104,56 @@ pub mod pallet {
Self::deposit_event(Event::PoolRemoved(user, asset_id_to_remove));
Ok(())
}

/// Edit pool info
#[pallet::call_index(10)]
#[pallet::weight(<T as Config>::WeightInfo::edit_pool_info())]
pub fn edit_pool_info(
origin: OriginFor<T>,
asset_id: AssetIdOf<T>,
new_loan_to_value: Balance,
new_liquidation_threshold: Balance,
new_optimal_utilization_rate: Balance,
new_base_rate: Balance,
new_slope_rate_1: Balance,
new_slope_rate_2: Balance,
new_reserve_factor: Balance,
) -> DispatchResult {
let user = ensure_signed(origin)?;

if user != AuthorityAccount::<T>::get() {
return Err(Error::<T>::Unauthorized.into());
}

// Check parameters
if new_loan_to_value > balance!(1)
|| new_liquidation_threshold > balance!(1)
|| new_optimal_utilization_rate > balance!(1)
|| new_reserve_factor > balance!(1)
{
return Err(Error::<T>::InvalidPoolParameters.into());
}

let mut pool_info = PoolData::<T>::get(asset_id).ok_or(Error::<T>::PoolDoesNotExist)?;

// Check if pool is removed
ensure!(!pool_info.is_removed, Error::<T>::PoolIsRemoved);

// Update pool info
pool_info.loan_to_value = new_loan_to_value;
pool_info.liquidation_threshold = new_liquidation_threshold;
pool_info.optimal_utilization_rate = new_optimal_utilization_rate;
pool_info.base_rate = new_base_rate;
pool_info.slope_rate_1 = new_slope_rate_1;
pool_info.slope_rate_2 = new_slope_rate_2;
pool_info.reserve_factor = new_reserve_factor;

// Saving new pool info
<PoolData<T>>::insert(asset_id, pool_info);

Self::deposit_event(Event::PoolInfoEdited(user, asset_id));
Ok(())
}
}

/// Validate unsigned call to this pallet.
Expand Down Expand Up @@ -1300,6 +1407,15 @@ pub mod pallet {
.try_into_balance()
.unwrap_or(0);

// Transfer amount to developer fund
Assets::<T>::transfer_from(
&asset_id,
&Self::account_id(),
&AuthorityAccount::<T>::get(),
developer_amount,
)
.map_err(|_| Error::<T>::CanNotTransferAmountToDevelopers)?;

// Transfer APOLLO to treasury
T::LiquidityProxyPallet::exchange(
DEXId::Polkaswap.into(),
Expand Down Expand Up @@ -1329,15 +1445,6 @@ pub mod pallet {
outcome.amount,
)?;

// Transfer amount to developer fund
Assets::<T>::transfer_from(
&asset_id,
&Self::account_id(),
&AuthorityAccount::<T>::get(),
developer_amount,
)
.map_err(|_| Error::<T>::CanNotTransferAmountToDevelopers)?;

Ok(().into())
}

Expand Down
Loading

0 comments on commit 1975edb

Please sign in to comment.