Skip to content

Commit

Permalink
Ep/be 154 controller rafactoring exchange rate borrow stored (#32)
Browse files Browse the repository at this point in the history
* [BE-154] Replace fns get_exchange_rate/calculate_exchange_rate/get_wrap_by_underlying/get_underlying_by_wrap/convert_from_wrapped/convert_to_wrapped from controller to pool. Clean up Errors in controller pallet.

* [BE-154] Refactor controller && minterest-protocol according to changes

* [BE-154] Fix mock files for each pallet. Replace tests for fns convert_to_wrapped/convert_from_wrapped/calculate_exchange_rate/get_exchange_rate/get_wrapped_by_underlying/get_underlying_by_wrapped. Fix protocol tests && integration tests according to changes in main code.

* [BE-154] Remove current_exchange_rate from pool struct && setter && test for this parameter.

* [BE-154] Fix some tests according to current_exchange_rate removing
  • Loading branch information
Apollo50 committed Jan 21, 2021
1 parent 5b2b9fb commit 5bcc549
Show file tree
Hide file tree
Showing 12 changed files with 294 additions and 334 deletions.
4 changes: 0 additions & 4 deletions node/src/chain_spec.rs
Expand Up @@ -235,7 +235,6 @@ fn testnet_genesis(
Pool {
total_borrowed: Balance::zero(),
borrow_index: FixedU128::one(),
current_exchange_rate: FixedU128::one(),
total_insurance: Balance::zero(),
},
),
Expand All @@ -244,7 +243,6 @@ fn testnet_genesis(
Pool {
total_borrowed: Balance::zero(),
borrow_index: FixedU128::one(),
current_exchange_rate: FixedU128::one(),
total_insurance: Balance::zero(),
},
),
Expand All @@ -253,7 +251,6 @@ fn testnet_genesis(
Pool {
total_borrowed: Balance::zero(),
borrow_index: FixedU128::one(),
current_exchange_rate: FixedU128::one(),
total_insurance: Balance::zero(),
},
),
Expand All @@ -262,7 +259,6 @@ fn testnet_genesis(
Pool {
total_borrowed: Balance::zero(),
borrow_index: FixedU128::one(),
current_exchange_rate: FixedU128::one(),
total_insurance: Balance::zero(),
},
),
Expand Down
116 changes: 2 additions & 114 deletions pallets/controller/src/lib.rs
Expand Up @@ -55,9 +55,6 @@ pub trait Trait: liquidity_pools::Trait + system::Trait + oracle::Trait + accoun
/// The overarching event type.
type Event: From<Event> + Into<<Self as system::Trait>::Event>;

/// Start exchange rate
type InitialExchangeRate: Get<Rate>;

/// The approximate number of blocks per year
type BlocksPerYear: Get<u128>;

Expand Down Expand Up @@ -114,12 +111,6 @@ decl_error! {
/// Borrow rate is absurdly high.
BorrowRateIsTooHight,

/// The currency is not enabled in protocol.
NotValidUnderlyingAssetId,

/// The currency is not enabled in wrapped protocol.
NotValidWrappedTokenId,

/// Oracle unavailable or price equal 0ю
OraclePriceError,

Expand Down Expand Up @@ -335,7 +326,6 @@ decl_module! {
type RateResult = result::Result<Rate, DispatchError>;
type BalanceResult = result::Result<Balance, DispatchError>;
type LiquidityResult = result::Result<(Balance, Balance), DispatchError>;
type CurrencyIdResult = result::Result<CurrencyId, DispatchError>;

impl<T: Trait> Module<T> {
/// Applies accrued interest to total borrows and insurances.
Expand Down Expand Up @@ -412,41 +402,6 @@ impl<T: Trait> Module<T> {
Ok(())
}

/// Converts a specified number of underlying assets into wrapped tokens.
/// The calculation is based on the exchange rate.
///
/// - `underlying_asset_id`: CurrencyId of underlying assets to be converted to wrapped tokens.
/// - `underlying_amount`: The amount of underlying assets to be converted to wrapped tokens.
/// Returns `wrapped_amount = underlying_amount / exchange_rate`
pub fn convert_to_wrapped(underlying_asset_id: CurrencyId, underlying_amount: Balance) -> BalanceResult {
let exchange_rate = Self::get_exchange_rate(underlying_asset_id)?;

let wrapped_amount = Rate::from_inner(underlying_amount)
.checked_div(&exchange_rate)
.map(|x| x.into_inner())
.ok_or(Error::<T>::NumOverflow)?;

Ok(wrapped_amount)
}

/// Converts a specified number of wrapped tokens into underlying assets.
/// The calculation is based on the exchange rate.
///
/// - `wrapped_id`: CurrencyId of the wrapped tokens to be converted to underlying assets.
/// - `wrapped_amount`: The amount of wrapped tokens to be converted to underlying assets.
/// Returns `underlying_amount = wrapped_amount * exchange_rate`
pub fn convert_from_wrapped(wrapped_id: CurrencyId, wrapped_amount: Balance) -> BalanceResult {
let underlying_asset_id = Self::get_underlying_asset_id_by_wrapped_id(&wrapped_id)?;
let exchange_rate = Self::get_exchange_rate(underlying_asset_id)?;

let underlying_amount = Rate::from_inner(wrapped_amount)
.checked_mul(&exchange_rate)
.map(|x| x.into_inner())
.ok_or(Error::<T>::NumOverflow)?;

Ok(underlying_amount)
}

/// Return the borrow balance of account based on stored data.
///
/// - `who`: the address whose balance should be calculated.
Expand Down Expand Up @@ -501,11 +456,11 @@ impl<T: Trait> Module<T> {

// For each tokens the account is in
for asset in m_tokens_ids.into_iter() {
let underlying_asset = Self::get_underlying_asset_id_by_wrapped_id(&asset)?;
let underlying_asset = <LiquidityPools<T>>::get_underlying_asset_id_by_wrapped_id(&asset)?;

// Read the balances and exchange rate from the cToken
let borrow_balance = Self::borrow_balance_stored(account, underlying_asset)?;
let exchange_rate = Self::get_exchange_rate(underlying_asset)?;
let exchange_rate = <LiquidityPools<T>>::get_exchange_rate(underlying_asset)?;
let collateral_factor = Self::get_collateral_factor(underlying_asset);

// Get the normalized price of the asset.
Expand Down Expand Up @@ -631,53 +586,6 @@ impl<T: Trait> Module<T> {

// Private methods
impl<T: Trait> Module<T> {
/// Calculates the exchange rate from the underlying to the mToken.
/// This function does not accrue interest before calculating the exchange rate.
pub fn get_exchange_rate(underlying_asset_id: CurrencyId) -> RateResult {
let wrapped_asset_id = Self::get_wrapped_id_by_underlying_asset_id(&underlying_asset_id)?;
// The total amount of cash the market has
let total_cash = <LiquidityPools<T>>::get_pool_available_liquidity(underlying_asset_id);

// Total number of tokens in circulation
let total_supply = T::MultiCurrency::total_issuance(wrapped_asset_id);

let total_insurance = <LiquidityPools<T>>::get_pool_total_insurance(underlying_asset_id);

let total_borrowed = <LiquidityPools<T>>::get_pool_total_borrowed(underlying_asset_id);

let current_exchange_rate =
Self::calculate_exchange_rate(total_cash, total_supply, total_insurance, total_borrowed)?;
// FIXME: can be removed.
<LiquidityPools<T>>::set_current_exchange_rate(underlying_asset_id, current_exchange_rate)?;

Ok(current_exchange_rate)
}

/// Calculates the exchange rate from the underlying to the mToken.
fn calculate_exchange_rate(
total_cash: Balance,
total_supply: Balance,
total_insurance: Balance,
total_borrowed: Balance,
) -> RateResult {
let rate = match total_supply.cmp(&Balance::zero()) {
// If there are no tokens minted: exchangeRate = InitialExchangeRate.
Ordering::Equal => T::InitialExchangeRate::get(),
// Otherwise: exchange_rate = (total_cash - total_insurance + total_borrowed) / total_supply
_ => {
let cash_plus_borrows = total_cash.checked_add(total_borrowed).ok_or(Error::<T>::NumOverflow)?;

let cash_plus_borrows_minus_insurance = cash_plus_borrows
.checked_sub(total_insurance)
.ok_or(Error::<T>::NumOverflow)?;

Rate::saturating_from_rational(cash_plus_borrows_minus_insurance, total_supply)
}
};

Ok(rate)
}

/// Calculates the current borrow rate per block.
fn calculate_borrow_interest_rate(underlying_asset_id: CurrencyId, utilization_rate: Rate) -> RateResult {
let kink = Self::controller_dates(underlying_asset_id).kink;
Expand Down Expand Up @@ -841,26 +749,6 @@ impl<T: Trait> Module<T> {
.ok_or(Error::<T>::NumOverflow)?;
Ok(result)
}

pub fn get_wrapped_id_by_underlying_asset_id(asset_id: &CurrencyId) -> CurrencyIdResult {
match asset_id {
CurrencyId::DOT => Ok(CurrencyId::MDOT),
CurrencyId::KSM => Ok(CurrencyId::MKSM),
CurrencyId::BTC => Ok(CurrencyId::MBTC),
CurrencyId::ETH => Ok(CurrencyId::METH),
_ => Err(Error::<T>::NotValidUnderlyingAssetId.into()),
}
}

pub fn get_underlying_asset_id_by_wrapped_id(wrapped_id: &CurrencyId) -> CurrencyIdResult {
match wrapped_id {
CurrencyId::MDOT => Ok(CurrencyId::DOT),
CurrencyId::MKSM => Ok(CurrencyId::KSM),
CurrencyId::MBTC => Ok(CurrencyId::BTC),
CurrencyId::METH => Ok(CurrencyId::ETH),
_ => Err(Error::<T>::NotValidWrappedTokenId.into()),
}
}
}

// Getters for Controller Data
Expand Down
8 changes: 3 additions & 5 deletions pallets/controller/src/mock.rs
Expand Up @@ -106,12 +106,15 @@ impl orml_currencies::Trait for Runtime {

parameter_types! {
pub const LiquidityPoolsModuleId: ModuleId = ModuleId(*b"min/pool");
pub const InitialExchangeRate: Rate = Rate::from_inner(1_000_000_000_000_000_000);

}

impl liquidity_pools::Trait for Runtime {
type Event = TestEvent;
type MultiCurrency = orml_tokens::Module<Runtime>;
type ModuleId = LiquidityPoolsModuleId;
type InitialExchangeRate = InitialExchangeRate;
}

impl oracle::Trait for Runtime {
Expand All @@ -128,7 +131,6 @@ impl accounts::Trait for Runtime {
}

parameter_types! {
pub const InitialExchangeRate: Rate = Rate::from_inner(1_000_000_000_000_000_000);
pub const BlocksPerYear: u128 = 5256000u128;
pub MTokensId: Vec<CurrencyId> = vec![
CurrencyId::MDOT,
Expand All @@ -146,7 +148,6 @@ parameter_types! {

impl Trait for Runtime {
type Event = TestEvent;
type InitialExchangeRate = InitialExchangeRate;
type MTokensId = MTokensId;
type BlocksPerYear = BlocksPerYear;
type UnderlyingAssetId = UnderlyingAssetId;
Expand Down Expand Up @@ -209,7 +210,6 @@ impl ExtBuilder {
Pool {
total_borrowed,
borrow_index: Rate::saturating_from_rational(1, 1),
current_exchange_rate: Rate::from_inner(1),
total_insurance: Balance::zero(),
},
));
Expand All @@ -224,7 +224,6 @@ impl ExtBuilder {
Pool {
total_borrowed: Balance::zero(),
borrow_index: Rate::saturating_from_rational(1, 1),
current_exchange_rate: Rate::from_inner(1),
total_insurance,
},
));
Expand All @@ -237,7 +236,6 @@ impl ExtBuilder {
Pool {
total_borrowed: Balance::zero(),
borrow_index: Rate::saturating_from_rational(2, 1),
current_exchange_rate: Rate::saturating_from_rational(1, 1),
total_insurance: Balance::zero(),
},
));
Expand Down
113 changes: 0 additions & 113 deletions pallets/controller/src/tests.rs
Expand Up @@ -100,91 +100,6 @@ fn accrue_interest_should_not_work() {
});
}

#[test]
fn convert_to_wrapped_should_work() {
ExtBuilder::default()
.user_balance(ALICE, CurrencyId::DOT, ONE_HUNDRED)
.user_balance(ALICE, CurrencyId::MDOT, ONE_HUNDRED)
.pool_total_borrowed(CurrencyId::DOT, 40)
.build()
.execute_with(|| {
// exchange_rate = 40 / 100 = 0.4
assert_eq!(Controller::convert_to_wrapped(CurrencyId::DOT, 10), Ok(25));

// Overflow in calculation: wrapped_amount = max_value() / exchange_rate,
// when exchange_rate < 1
assert_err!(
Controller::convert_to_wrapped(CurrencyId::DOT, Balance::max_value()),
Error::<Runtime>::NumOverflow
);
});
}

#[test]
fn convert_from_wrapped_should_work() {
ExtBuilder::default()
.user_balance(ALICE, CurrencyId::DOT, ONE_HUNDRED)
.user_balance(ALICE, CurrencyId::MDOT, ONE_HUNDRED)
.user_balance(ALICE, CurrencyId::MBTC, 1)
.pool_balance(CurrencyId::BTC, 100)
.pool_total_borrowed(CurrencyId::DOT, 40)
.build()
.execute_with(|| {
// underlying_amount = 10 * 0.4 = 4
assert_eq!(Controller::convert_from_wrapped(CurrencyId::MDOT, 10), Ok(4));

// Overflow in calculation: underlying_amount = max_value() * exchange_rate
assert_err!(
Controller::convert_from_wrapped(CurrencyId::MBTC, Balance::max_value()),
Error::<Runtime>::NumOverflow
);
});
}

#[test]
fn calculate_exchange_rate_should_work() {
ExtBuilder::default().build().execute_with(|| {
// exchange_rate = (102 - 2 + 20) / 100 = 1.2
assert_eq!(
Controller::calculate_exchange_rate(102, 100, 2, 20),
Ok(Rate::saturating_from_rational(12, 10))
);
// If there are no tokens minted: exchangeRate = InitialExchangeRate = 1.0
assert_eq!(
Controller::calculate_exchange_rate(102, 0, 2, 0),
Ok(Rate::saturating_from_rational(1, 1))
);

// Overflow in calculation: total_cash + total_borrowed
assert_noop!(
Controller::calculate_exchange_rate(Balance::max_value(), 100, 100, 100),
Error::<Runtime>::NumOverflow
);

// Overflow in calculation: cash_plus_borrows - total_insurance
assert_noop!(
Controller::calculate_exchange_rate(100, 100, Balance::max_value(), 100),
Error::<Runtime>::NumOverflow
);
});
}

#[test]
fn get_exchange_rate_should_work() {
ExtBuilder::default()
.pool_balance(CurrencyId::DOT, dollars(100_u128))
.user_balance(ALICE, CurrencyId::MDOT, dollars(125_u128))
.pool_total_borrowed(CurrencyId::DOT, dollars(300_u128))
.build()
.execute_with(|| {
// exchange_rate = (100 - 0 + 300) / 125 = 3.2
assert_eq!(
Controller::get_exchange_rate(CurrencyId::DOT),
Ok(Rate::saturating_from_rational(32, 10))
);
});
}

#[test]
fn calculate_borrow_interest_rate_should_work() {
ExtBuilder::default().build().execute_with(|| {
Expand Down Expand Up @@ -324,34 +239,6 @@ fn calculate_new_total_insurance_should_work() {
});
}

#[test]
fn get_wrapped_id_by_underlying_asset_id_should_work() {
ExtBuilder::default().build().execute_with(|| {
assert_eq!(
Controller::get_wrapped_id_by_underlying_asset_id(&CurrencyId::DOT),
Ok(CurrencyId::MDOT)
);
assert_noop!(
Controller::get_wrapped_id_by_underlying_asset_id(&CurrencyId::MDOT),
Error::<Runtime>::NotValidUnderlyingAssetId
);
});
}

#[test]
fn get_underlying_asset_id_by_wrapped_id_should_work() {
ExtBuilder::default().build().execute_with(|| {
assert_eq!(
Controller::get_underlying_asset_id_by_wrapped_id(&CurrencyId::MDOT),
Ok(CurrencyId::DOT)
);
assert_noop!(
Controller::get_underlying_asset_id_by_wrapped_id(&CurrencyId::DOT),
Error::<Runtime>::NotValidWrappedTokenId
);
});
}

#[test]
fn borrow_balance_stored_with_zero_balance_should_work() {
ExtBuilder::default()
Expand Down

0 comments on commit 5bcc549

Please sign in to comment.