Skip to content

Commit

Permalink
Feature/market cap (#674)
Browse files Browse the repository at this point in the history
* Add cap field

* Remove generic field for `Config`

* Check capacity while minting new token

* Fix test issue

* Add unit test

* Use underlying asset

* Unit test for update market capacity

* Move test to market.rs

* Fix compile issue

* Enable overflow check unit test

* Unit test for ensure capacity

* Upgrade `update_market`

* Rename variable

* Fix test case issue

* Fix benchmark issue

Co-authored-by: Alan WANG <[email protected]>
  • Loading branch information
alannotnerd and Alan WANG authored Oct 11, 2021
1 parent 641260f commit 92bf590
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 69 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Editor
.idea/
.vscode/
.vim/

# will have compiled files and executables
**/target/
Expand Down
80 changes: 45 additions & 35 deletions pallets/loans/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,34 @@ const DOT: CurrencyId = 0;
const KSM: CurrencyId = 1;
const UNKNOWN: CurrencyId = 5;

const MARKET_MOCK: Market = Market {
close_factor: Ratio::from_percent(50),
collateral_factor: Ratio::from_percent(50),
liquidate_incentive: Rate::from_inner(Rate::DIV / 100 * 110),
state: MarketState::Active,
rate_model: InterestRateModel::Jump(JumpModel {
base_rate: Rate::from_inner(Rate::DIV / 100 * 2),
jump_rate: Rate::from_inner(Rate::DIV / 100 * 10),
full_rate: Rate::from_inner(Rate::DIV / 100 * 32),
jump_utilization: Ratio::from_percent(80),
}),
reserve_factor: Ratio::from_percent(15),
};
const PENDING_MARKET_MOCK: Market = {
let mut market = MARKET_MOCK;
fn market_mock<T: Config>() -> Market<BalanceOf<T>>
where
BalanceOf<T>: From<u128>,
{
Market {
close_factor: Ratio::from_percent(50),
collateral_factor: Ratio::from_percent(50),
liquidate_incentive: Rate::from_inner(Rate::DIV / 100 * 110),
state: MarketState::Active,
rate_model: InterestRateModel::Jump(JumpModel {
base_rate: Rate::from_inner(Rate::DIV / 100 * 2),
jump_rate: Rate::from_inner(Rate::DIV / 100 * 10),
full_rate: Rate::from_inner(Rate::DIV / 100 * 32),
jump_utilization: Ratio::from_percent(80),
}),
reserve_factor: Ratio::from_percent(15),
cap: 1_000_000_000_000_000_000_000u128.into(), // set to $1B
}
}

fn pending_market_mock<T: Config>() -> Market<BalanceOf<T>>
where
BalanceOf<T>: From<u128>,
{
let mut market = market_mock::<T>();
market.state = MarketState::Pending;
market
};
}

const INITIAL_AMOUNT: u32 = 500_000_000;

Expand Down Expand Up @@ -93,35 +103,35 @@ fn assert_last_event<T: Config>(generic_event: <T as Config>::Event) {
benchmarks! {
where_clause {
where
BalanceOf<T>: FixedPointOperand,
BalanceOf<T>: FixedPointOperand + From<u128>,
AssetIdOf<T>: AtLeast32BitUnsigned,
T: pallet_assets::Config<AssetId = CurrencyId, Balance = Balance> + pallet_prices::Config
}

add_market {
}: _(SystemOrigin::Root, UNKNOWN.into(),PENDING_MARKET_MOCK)
}: _(SystemOrigin::Root, UNKNOWN.into(),pending_market_mock::<T>())
verify {
assert_last_event::<T>(Event::<T>::NewMarket(PENDING_MARKET_MOCK).into());
assert_last_event::<T>(Event::<T>::NewMarket(pending_market_mock::<T>()).into());
}

active_market {
assert_ok!(Loans::<T>::add_market(SystemOrigin::Root.into(), UNKNOWN.into(), PENDING_MARKET_MOCK));
assert_ok!(Loans::<T>::add_market(SystemOrigin::Root.into(), UNKNOWN.into(), pending_market_mock::<T>()));
}: _(SystemOrigin::Root,UNKNOWN.into())
verify {
assert_last_event::<T>(Event::<T>::ActivatedMarket(UNKNOWN.into()).into());
}

update_market {
assert_ok!(Loans::<T>::add_market(SystemOrigin::Root.into(), DOT.into(), PENDING_MARKET_MOCK));
}: _(SystemOrigin::Root,DOT.into(), PENDING_MARKET_MOCK)
assert_ok!(Loans::<T>::add_market(SystemOrigin::Root.into(), DOT.into(), pending_market_mock::<T>()));
}: _(SystemOrigin::Root,DOT.into(), pending_market_mock::<T>())
verify {
assert_last_event::<T>(Event::<T>::UpdatedMarket(PENDING_MARKET_MOCK).into());
assert_last_event::<T>(Event::<T>::UpdatedMarket(pending_market_mock::<T>()).into());
}

mint {
let caller: T::AccountId = whitelisted_caller();
transfer_initial_balance::<T>(caller.clone());
assert_ok!(Loans::<T>::add_market(SystemOrigin::Root.into(), DOT.into(), PENDING_MARKET_MOCK));
assert_ok!(Loans::<T>::add_market(SystemOrigin::Root.into(), DOT.into(), pending_market_mock::<T>()));
assert_ok!(Loans::<T>::active_market(SystemOrigin::Root.into(), DOT.into()));
let amount: u32 = 100_000;
}: _(SystemOrigin::Signed(caller.clone()), DOT.into(), amount.into())
Expand All @@ -134,7 +144,7 @@ benchmarks! {
transfer_initial_balance::<T>(caller.clone());
let deposit_amount: u32 = 200_000_000;
let borrowed_amount: u32 = 100_000_000;
assert_ok!(Loans::<T>::add_market(SystemOrigin::Root.into(), DOT.into(), PENDING_MARKET_MOCK));
assert_ok!(Loans::<T>::add_market(SystemOrigin::Root.into(), DOT.into(), pending_market_mock::<T>()));
assert_ok!(Loans::<T>::active_market(SystemOrigin::Root.into(), DOT.into()));
assert_ok!(Loans::<T>::mint(SystemOrigin::Signed(caller.clone()).into(), DOT.into(), deposit_amount.into()));
assert_ok!(Loans::<T>::collateral_asset(SystemOrigin::Signed(caller.clone()).into(), DOT.into(), true));
Expand All @@ -148,7 +158,7 @@ benchmarks! {
transfer_initial_balance::<T>(caller.clone());
let deposit_amount: u32 = 100_000_000;
let redeem_amount: u32 = 100_000;
assert_ok!(Loans::<T>::add_market(SystemOrigin::Root.into(), DOT.into(), PENDING_MARKET_MOCK));
assert_ok!(Loans::<T>::add_market(SystemOrigin::Root.into(), DOT.into(), pending_market_mock::<T>()));
assert_ok!(Loans::<T>::active_market(SystemOrigin::Root.into(), DOT.into()));
assert_ok!(Loans::<T>::mint(SystemOrigin::Signed(caller.clone()).into(), DOT.into(), deposit_amount.into()));
}: _(SystemOrigin::Signed(caller.clone()), DOT.into(), redeem_amount.into())
Expand All @@ -160,7 +170,7 @@ benchmarks! {
let caller: T::AccountId = whitelisted_caller();
transfer_initial_balance::<T>(caller.clone());
let deposit_amount: u32 = 100_000_000;
assert_ok!(Loans::<T>::add_market(SystemOrigin::Root.into(), DOT.into(), PENDING_MARKET_MOCK));
assert_ok!(Loans::<T>::add_market(SystemOrigin::Root.into(), DOT.into(), pending_market_mock::<T>()));
assert_ok!(Loans::<T>::active_market(SystemOrigin::Root.into(), DOT.into()));
assert_ok!(Loans::<T>::mint(SystemOrigin::Signed(caller.clone()).into(), DOT.into(), deposit_amount.into()));
}: _(SystemOrigin::Signed(caller.clone()), DOT.into())
Expand All @@ -174,7 +184,7 @@ benchmarks! {
let deposit_amount: u32 = 200_000_000;
let borrowed_amount: u32 = 100_000_000;
let repay_amount: u32 = 100;
assert_ok!(Loans::<T>::add_market(SystemOrigin::Root.into(), DOT.into(), PENDING_MARKET_MOCK));
assert_ok!(Loans::<T>::add_market(SystemOrigin::Root.into(), DOT.into(), pending_market_mock::<T>()));
assert_ok!(Loans::<T>::active_market(SystemOrigin::Root.into(), DOT.into()));
assert_ok!(Loans::<T>::mint(SystemOrigin::Signed(caller.clone()).into(), DOT.into(), deposit_amount.into()));
assert_ok!(Loans::<T>::collateral_asset(SystemOrigin::Signed(caller.clone()).into(), DOT.into(), true));
Expand All @@ -189,7 +199,7 @@ benchmarks! {
transfer_initial_balance::<T>(caller.clone());
let deposit_amount: u32 = 200_000_000;
let borrowed_amount: u32 = 100_000_000;
assert_ok!(Loans::<T>::add_market(SystemOrigin::Root.into(), DOT.into(), PENDING_MARKET_MOCK));
assert_ok!(Loans::<T>::add_market(SystemOrigin::Root.into(), DOT.into(), pending_market_mock::<T>()));
assert_ok!(Loans::<T>::active_market(SystemOrigin::Root.into(), DOT.into()));
assert_ok!(Loans::<T>::mint(SystemOrigin::Signed(caller.clone()).into(), DOT.into(), deposit_amount.into()));
assert_ok!(Loans::<T>::collateral_asset(SystemOrigin::Signed(caller.clone()).into(), DOT.into(), true));
Expand All @@ -203,7 +213,7 @@ benchmarks! {
let caller: T::AccountId = whitelisted_caller();
transfer_initial_balance::<T>(caller.clone());
let deposit_amount: u32 = 200_000_000;
assert_ok!(Loans::<T>::add_market(SystemOrigin::Root.into(), DOT.into(), PENDING_MARKET_MOCK));
assert_ok!(Loans::<T>::add_market(SystemOrigin::Root.into(), DOT.into(), pending_market_mock::<T>()));
assert_ok!(Loans::<T>::active_market(SystemOrigin::Root.into(), DOT.into()));
assert_ok!(Loans::<T>::mint(SystemOrigin::Signed(caller.clone()).into(), DOT.into(), deposit_amount.into()));
}: _(SystemOrigin::Signed(caller.clone()), DOT.into(), true)
Expand All @@ -220,9 +230,9 @@ benchmarks! {
let borrowed_amount: u32 = 200_000_000;
let liquidate_amount: u32 = 100_000_000;
let incentive_amount: u32 = 110_000_000;
assert_ok!(Loans::<T>::add_market(SystemOrigin::Root.into(), DOT.into(), PENDING_MARKET_MOCK));
assert_ok!(Loans::<T>::add_market(SystemOrigin::Root.into(), DOT.into(), pending_market_mock::<T>()));
assert_ok!(Loans::<T>::active_market(SystemOrigin::Root.into(), DOT.into()));
assert_ok!(Loans::<T>::add_market(SystemOrigin::Root.into(), KSM.into(), PENDING_MARKET_MOCK));
assert_ok!(Loans::<T>::add_market(SystemOrigin::Root.into(), KSM.into(), pending_market_mock::<T>()));
assert_ok!(Loans::<T>::active_market(SystemOrigin::Root.into(), KSM.into()));
assert_ok!(Loans::<T>::mint(SystemOrigin::Signed(bob.clone()).into(), KSM.into(), deposit_amount.into()));
assert_ok!(Loans::<T>::mint(SystemOrigin::Signed(alice.clone()).into(), DOT.into(), deposit_amount.into()));
Expand All @@ -238,7 +248,7 @@ benchmarks! {
let payer = T::Lookup::unlookup(caller.clone());
transfer_initial_balance::<T>(caller.clone());
let amount: u32 = 2000;
assert_ok!(Loans::<T>::add_market(SystemOrigin::Root.into(), DOT.into(), PENDING_MARKET_MOCK));
assert_ok!(Loans::<T>::add_market(SystemOrigin::Root.into(), DOT.into(), pending_market_mock::<T>()));
assert_ok!(Loans::<T>::active_market(SystemOrigin::Root.into(), DOT.into()));
}: _(SystemOrigin::Root, payer, DOT.into(), amount.into())
verify {
Expand All @@ -251,7 +261,7 @@ benchmarks! {
transfer_initial_balance::<T>(caller.clone());
let add_amount: u32 = 2000;
let reduce_amount: u32 = 1000;
assert_ok!(Loans::<T>::add_market(SystemOrigin::Root.into(), DOT.into(), PENDING_MARKET_MOCK));
assert_ok!(Loans::<T>::add_market(SystemOrigin::Root.into(), DOT.into(), pending_market_mock::<T>()));
assert_ok!(Loans::<T>::active_market(SystemOrigin::Root.into(), DOT.into()));
assert_ok!(Loans::<T>::add_reserves(SystemOrigin::Root.into(), payer.clone(), DOT.into(), add_amount.into()));
}: _(SystemOrigin::Root, payer, DOT.into(), reduce_amount.into())
Expand All @@ -264,7 +274,7 @@ benchmarks! {
transfer_initial_balance::<T>(alice.clone());
let deposit_amount: u32 = 200_000_000;
let borrow_amount: u32 = 100_000_000;
assert_ok!(Loans::<T>::add_market(SystemOrigin::Root.into(), DOT.into(), PENDING_MARKET_MOCK));
assert_ok!(Loans::<T>::add_market(SystemOrigin::Root.into(), DOT.into(), pending_market_mock::<T>()));
assert_ok!(Loans::<T>::active_market(SystemOrigin::Root.into(), DOT.into()));
assert_ok!(Loans::<T>::mint(SystemOrigin::Signed(alice.clone()).into(), DOT.into(), deposit_amount.into()));
assert_ok!(Loans::<T>::collateral_asset(SystemOrigin::Signed(alice.clone()).into(), DOT.into(), true));
Expand Down
2 changes: 1 addition & 1 deletion pallets/loans/src/interest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ where
pub(crate) fn update_borrow_index(
borrow_rate: Rate,
asset_id: AssetIdOf<T>,
market: &Market,
market: &Market<BalanceOf<T>>,
delta_time: u64,
) -> DispatchResult {
let borrows_prior = Self::total_borrows(asset_id);
Expand Down
57 changes: 33 additions & 24 deletions pallets/loans/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ pub mod pallet {
MarketAlredyExists,
/// New markets must have a pending state
NewMarketMustHavePendingState,
/// Market reached its upper limitation
ExceededMarketCapacity,
}

#[pallet::event]
Expand Down Expand Up @@ -180,13 +182,13 @@ pub mod pallet {
ReservesAdded(T::AccountId, AssetIdOf<T>, BalanceOf<T>, BalanceOf<T>),
/// New interest rate model is set
/// [new_interest_rate_model]
NewMarket(Market),
NewMarket(Market<BalanceOf<T>>),
/// Event emitted when a market is activated
/// [admin, asset_id]
ActivatedMarket(AssetIdOf<T>),
/// Event emitted when a market is activated
/// [admin, asset_id]
UpdatedMarket(Market),
UpdatedMarket(Market<BalanceOf<T>>),
}

/// The timestamp of the last calculation of accrued interest
Expand Down Expand Up @@ -290,7 +292,8 @@ pub mod pallet {

/// Mapping of asset id to its market
#[pallet::storage]
pub type Markets<T: Config> = StorageMap<_, Blake2_128Concat, AssetIdOf<T>, Market>;
pub type Markets<T: Config> =
StorageMap<_, Blake2_128Concat, AssetIdOf<T>, Market<BalanceOf<T>>>;

#[pallet::pallet]
pub struct Pallet<T>(PhantomData<T>);
Expand Down Expand Up @@ -381,7 +384,7 @@ pub mod pallet {
pub fn add_market(
origin: OriginFor<T>,
asset_id: AssetIdOf<T>,
market: Market,
market: Market<BalanceOf<T>>,
) -> DispatchResultWithPostInfo {
T::UpdateOrigin::ensure_origin(origin)?;
ensure!(
Expand Down Expand Up @@ -417,27 +420,18 @@ pub mod pallet {
pub fn update_market(
origin: OriginFor<T>,
asset_id: AssetIdOf<T>,
market: Market,
market: Market<BalanceOf<T>>,
) -> DispatchResultWithPostInfo {
T::UpdateOrigin::ensure_origin(origin)?;
ensure!(
market.rate_model.check_model(),
Error::<T>::InvalidRateModelParam
);
Self::mutate_market(asset_id, |stored_market| {
let Market {
collateral_factor,
reserve_factor,
close_factor,
liquidate_incentive,
rate_model,
state: _,
} = stored_market;
*collateral_factor = market.collateral_factor;
*reserve_factor = market.reserve_factor;
*close_factor = market.close_factor;
*liquidate_incentive = market.liquidate_incentive;
*rate_model = market.rate_model;
*stored_market = Market {
state: stored_market.state,
..market
};
})?;

Self::deposit_event(Event::<T>::UpdatedMarket(market));
Expand All @@ -457,6 +451,7 @@ pub mod pallet {
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
Self::ensure_currency(asset_id)?;
Self::ensure_capacity(asset_id, mint_amount)?;

T::Assets::transfer(asset_id, &who, &Self::account_id(), mint_amount, false)?;
Self::update_earned_stored(&who, asset_id)?;
Expand Down Expand Up @@ -817,7 +812,7 @@ where
fn collateral_asset_value(
borrower: &T::AccountId,
asset_id: AssetIdOf<T>,
market: &Market,
market: &Market<BalanceOf<T>>,
) -> Result<FixedU128, DispatchError> {
if !AccountDeposits::<T>::contains_key(asset_id, borrower) {
return Ok(FixedU128::zero());
Expand Down Expand Up @@ -856,7 +851,7 @@ where
asset_id: AssetIdOf<T>,
redeemer: &T::AccountId,
voucher_amount: BalanceOf<T>,
market: &Market,
market: &Market<BalanceOf<T>>,
) -> DispatchResult {
let deposit = Self::account_deposits(asset_id, redeemer);
if deposit.voucher_balance < voucher_amount {
Expand Down Expand Up @@ -1031,7 +1026,7 @@ where
borrower: &T::AccountId,
liquidate_asset_id: AssetIdOf<T>,
repay_amount: BalanceOf<T>,
market: &Market,
market: &Market<BalanceOf<T>>,
) -> DispatchResult {
let (_, shortfall) = Self::get_account_liquidity(borrower)?;
if shortfall.is_zero() {
Expand Down Expand Up @@ -1213,6 +1208,20 @@ where
}
}

/// Ensure market is enough to supply `amount` asset.
fn ensure_capacity(asset_id: AssetIdOf<T>, amount: BalanceOf<T>) -> DispatchResult {
let market = Self::market(asset_id)?;

// Assets holded by market currently.
let current_cash = T::Assets::balance(asset_id, &Self::account_id());

let total_cash = current_cash
.checked_add(&amount)
.ok_or(ArithmeticError::Overflow)?;
ensure!(total_cash <= market.cap, Error::<T>::ExceededMarketCapacity);
Ok(())
}

pub fn calc_underlying_amount(
voucher_amount: BalanceOf<T>,
exchange_rate: Rate,
Expand Down Expand Up @@ -1248,7 +1257,7 @@ where
// Returns a stored Market.
//
// Returns `Err` if market does not exist.
pub fn market(asset_id: AssetIdOf<T>) -> Result<Market, DispatchError> {
pub fn market(asset_id: AssetIdOf<T>) -> Result<Market<BalanceOf<T>>, DispatchError> {
Markets::<T>::try_get(asset_id).map_err(|_err| Error::<T>::MarketDoesNotExist.into())
}

Expand All @@ -1257,7 +1266,7 @@ where
// Returns `Err` if market does not exist.
pub(crate) fn mutate_market<F>(asset_id: AssetIdOf<T>, cb: F) -> Result<(), DispatchError>
where
F: FnOnce(&mut Market),
F: FnOnce(&mut Market<BalanceOf<T>>),
{
Markets::<T>::try_mutate(asset_id, |opt| {
if let Some(market) = opt {
Expand All @@ -1269,7 +1278,7 @@ where
}

// All markets that are `MarketStatus::Active`.
fn active_markets() -> impl Iterator<Item = (AssetIdOf<T>, Market)> {
fn active_markets() -> impl Iterator<Item = (AssetIdOf<T>, Market<BalanceOf<T>>)> {
Markets::<T>::iter().filter(|(_, market)| market.state == MarketState::Active)
}
}
3 changes: 2 additions & 1 deletion pallets/loans/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ pub fn million_dollar(d: u128) -> u128 {
dollar(d) * 10_u128.pow(6)
}

pub const MARKET_MOCK: Market = Market {
pub const MARKET_MOCK: Market<Balance> = Market {
close_factor: Ratio::from_percent(50),
collateral_factor: Ratio::from_percent(50),
liquidate_incentive: Rate::from_inner(Rate::DIV / 100 * 110),
Expand All @@ -326,4 +326,5 @@ pub const MARKET_MOCK: Market = Market {
jump_utilization: Ratio::from_percent(80),
}),
reserve_factor: Ratio::from_percent(15),
cap: 1_000_000_000_000_000_000_000u128, // set to $1B
};
9 changes: 9 additions & 0 deletions pallets/loans/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,15 @@ fn mint_works() {
#[test]
fn mint_must_return_err_when_overflows_occur() {
new_test_ext().execute_with(|| {
Loans::update_market(
Origin::root(),
DOT,
Market {
cap: u128::MAX,
..MARKET_MOCK
},
)
.unwrap();
// MAX_DEPOSIT = u128::MAX * exchangeRate
const OVERFLOW_DEPOSIT: u128 = u128::MAX / 50 + 1;

Expand Down
Loading

0 comments on commit 92bf590

Please sign in to comment.