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

Ep/be 154 controller rafactoring exchange rate borrow stored #32

Merged
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