Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions contracts/red-bank/src/asset.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
use cosmwasm_std::{Decimal, DepsMut, Env, MessageInfo, Response, Uint128};
use mars_red_bank_types::{
address_provider,
address_provider::MarsAddressType,
error::MarsError,
red_bank::{InitOrUpdateAssetParams, Market},
};
use mars_utils::helpers::validate_native_denom;

use crate::{
error::ContractError,
interest_rates::{apply_accumulated_interests, update_interest_rates},
state::{CONFIG, MARKETS, OWNER},
};

/// Initialize asset if not exist.
/// Initialization requires that all params are provided and there is no asset in state.
pub fn init_asset(
deps: DepsMut,
env: Env,
info: MessageInfo,
denom: String,
params: InitOrUpdateAssetParams,
) -> Result<Response, ContractError> {
OWNER.assert_owner(deps.storage, &info.sender)?;

validate_native_denom(&denom)?;

if MARKETS.may_load(deps.storage, &denom)?.is_some() {
return Err(ContractError::AssetAlreadyInitialized {});
}

let new_market = create_market(env.block.time.seconds(), &denom, params)?;
MARKETS.save(deps.storage, &denom, &new_market)?;

Ok(Response::new().add_attribute("action", "init_asset").add_attribute("denom", denom))
}

/// Initialize new market
pub fn create_market(
block_time: u64,
denom: &str,
params: InitOrUpdateAssetParams,
) -> Result<Market, ContractError> {
// Destructuring a struct’s fields into separate variables in order to force
// compile error if we add more params
let InitOrUpdateAssetParams {
reserve_factor,
interest_rate_model,
} = params;

// All fields should be available
let available = reserve_factor.is_some() && interest_rate_model.is_some();

if !available {
return Err(MarsError::InstantiateParamsUnavailable {}.into());
}

let new_market = Market {
denom: denom.to_string(),
borrow_index: Decimal::one(),
liquidity_index: Decimal::one(),
borrow_rate: Decimal::zero(),
liquidity_rate: Decimal::zero(),
reserve_factor: reserve_factor.unwrap(),
indexes_last_updated: block_time,
collateral_total_scaled: Uint128::zero(),
debt_total_scaled: Uint128::zero(),
interest_rate_model: interest_rate_model.unwrap(),
};

new_market.validate()?;

Ok(new_market)
}

/// Update asset with new params.
pub fn update_asset(
deps: DepsMut,
env: Env,
info: MessageInfo,
denom: String,
params: InitOrUpdateAssetParams,
) -> Result<Response, ContractError> {
OWNER.assert_owner(deps.storage, &info.sender)?;

let market_option = MARKETS.may_load(deps.storage, &denom)?;
match market_option {
None => Err(ContractError::AssetNotInitialized {}),
Some(mut market) => {
// Destructuring a struct’s fields into separate variables in order to force
// compile error if we add more params
let InitOrUpdateAssetParams {
reserve_factor,
interest_rate_model,
} = params;

// If reserve factor or interest rates are updated we update indexes with
// current values before applying the change to prevent applying this
// new params to a period where they were not valid yet. Interests rates are
// recalculated after changes are applied.
let should_update_interest_rates = (reserve_factor.is_some()
&& reserve_factor.unwrap() != market.reserve_factor)
|| interest_rate_model.is_some();

let mut response = Response::new();

if should_update_interest_rates {
let config = CONFIG.load(deps.storage)?;
let addresses = address_provider::helpers::query_contract_addrs(
deps.as_ref(),
&config.address_provider,
vec![MarsAddressType::Incentives, MarsAddressType::RewardsCollector],
)?;
let rewards_collector_addr = &addresses[&MarsAddressType::RewardsCollector];
let incentives_addr = &addresses[&MarsAddressType::Incentives];

response = apply_accumulated_interests(
deps.storage,
&env,
&mut market,
rewards_collector_addr,
incentives_addr,
response,
)?;
}

let mut updated_market = Market {
reserve_factor: reserve_factor.unwrap_or(market.reserve_factor),
interest_rate_model: interest_rate_model.unwrap_or(market.interest_rate_model),
..market
};

updated_market.validate()?;

if should_update_interest_rates {
response = update_interest_rates(&env, &mut updated_market, response)?;
}
MARKETS.save(deps.storage, &denom, &updated_market)?;

Ok(response.add_attribute("action", "update_asset").add_attribute("denom", denom))
}
}
}
140 changes: 140 additions & 0 deletions contracts/red-bank/src/borrow.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
use cosmwasm_std::{DepsMut, Env, MessageInfo, Response, Uint128};
use mars_red_bank_types::{address_provider, address_provider::MarsAddressType};
use mars_utils::helpers::build_send_asset_msg;

use crate::{
error::ContractError,
health::assert_below_max_ltv_after_borrow,
helpers::query_asset_params,
interest_rates::{
apply_accumulated_interests, get_scaled_debt_amount, get_underlying_debt_amount,
get_underlying_liquidity_amount, update_interest_rates,
},
state::{CONFIG, MARKETS},
user::User,
};

/// Add debt for the borrower and send the borrowed funds
pub fn borrow(
deps: DepsMut,
env: Env,
info: MessageInfo,
denom: String,
borrow_amount: Uint128,
recipient: Option<String>,
) -> Result<Response, ContractError> {
let borrower = User(&info.sender);

let config = CONFIG.load(deps.storage)?;

let addresses = address_provider::helpers::query_contract_addrs(
deps.as_ref(),
&config.address_provider,
vec![
MarsAddressType::Oracle,
MarsAddressType::Incentives,
MarsAddressType::RewardsCollector,
MarsAddressType::Params,
],
)?;
let rewards_collector_addr = &addresses[&MarsAddressType::RewardsCollector];
let incentives_addr = &addresses[&MarsAddressType::Incentives];
let oracle_addr = &addresses[&MarsAddressType::Oracle];
let params_addr = &addresses[&MarsAddressType::Params];

let asset_params = query_asset_params(&deps.querier, params_addr, &denom)?;

if !asset_params.red_bank.borrow_enabled {
return Err(ContractError::BorrowNotEnabled {
denom,
});
}

// Load market and user state
let mut borrow_market = MARKETS.load(deps.storage, &denom)?;

let collateral_balance_before = get_underlying_liquidity_amount(
borrow_market.collateral_total_scaled,
&borrow_market,
env.block.time.seconds(),
)?;

// Cannot borrow zero amount or more than available collateral
if borrow_amount.is_zero() || borrow_amount > collateral_balance_before {
return Err(ContractError::InvalidBorrowAmount {
denom,
});
}

let uncollateralized_loan_limit = borrower.uncollateralized_loan_limit(deps.storage, &denom)?;

// Check if user can borrow specified amount
let mut uncollateralized_debt = false;
if uncollateralized_loan_limit.is_zero() {
if !assert_below_max_ltv_after_borrow(
&deps.as_ref(),
&env,
borrower.address(),
oracle_addr,
params_addr,
&denom,
borrow_amount,
)? {
return Err(ContractError::BorrowAmountExceedsGivenCollateral {});
}
} else {
// Uncollateralized loan: check borrow amount plus debt does not exceed uncollateralized loan limit
uncollateralized_debt = true;

let debt_amount_scaled = borrower.debt_amount_scaled(deps.storage, &denom)?;

let asset_market = MARKETS.load(deps.storage, &denom)?;
let debt_amount = get_underlying_debt_amount(
debt_amount_scaled,
&asset_market,
env.block.time.seconds(),
)?;

let debt_after_borrow = debt_amount.checked_add(borrow_amount)?;
if debt_after_borrow > uncollateralized_loan_limit {
return Err(ContractError::BorrowAmountExceedsUncollateralizedLoanLimit {});
}
}

let mut response = Response::new();

response = apply_accumulated_interests(
deps.storage,
&env,
&mut borrow_market,
rewards_collector_addr,
incentives_addr,
response,
)?;

// Set new debt
let borrow_amount_scaled =
get_scaled_debt_amount(borrow_amount, &borrow_market, env.block.time.seconds())?;

borrow_market.increase_debt(borrow_amount_scaled)?;
borrower.increase_debt(deps.storage, &denom, borrow_amount_scaled, uncollateralized_debt)?;

response = update_interest_rates(&env, &mut borrow_market, response)?;
MARKETS.save(deps.storage, &denom, &borrow_market)?;

// Send borrow amount to borrower or another recipient
let recipient_addr = if let Some(recipient) = recipient {
deps.api.addr_validate(&recipient)?
} else {
borrower.address().clone()
};

Ok(response
.add_message(build_send_asset_msg(&recipient_addr, &denom, borrow_amount))
.add_attribute("action", "borrow")
.add_attribute("sender", borrower)
.add_attribute("recipient", recipient_addr)
.add_attribute("denom", denom)
.add_attribute("amount", borrow_amount)
.add_attribute("amount_scaled", borrow_amount_scaled))
}
68 changes: 68 additions & 0 deletions contracts/red-bank/src/collateral.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use cosmwasm_std::{DepsMut, Env, MessageInfo, Response};
use mars_red_bank_types::{
self,
address_provider::{self, MarsAddressType},
};

use crate::{
error::ContractError,
health::get_health_and_positions,
state::{COLLATERALS, CONFIG},
user::User,
};

/// Update (enable / disable) collateral asset for specific user
pub fn update_asset_collateral_status(
deps: DepsMut,
env: Env,
info: MessageInfo,
denom: String,
enable: bool,
) -> Result<Response, ContractError> {
let user = User(&info.sender);

let mut collateral =
COLLATERALS.may_load(deps.storage, (user.address(), &denom))?.ok_or_else(|| {
ContractError::UserNoCollateralBalance {
user: user.into(),
denom: denom.clone(),
}
})?;

let previously_enabled = collateral.enabled;

collateral.enabled = enable;
COLLATERALS.save(deps.storage, (user.address(), &denom), &collateral)?;

// if the collateral was previously enabled, but is not disabled, it is necessary to ensure the
// user is not liquidatable after disabling
if previously_enabled && !enable {
let config = CONFIG.load(deps.storage)?;

let addresses = address_provider::helpers::query_contract_addrs(
deps.as_ref(),
&config.address_provider,
vec![MarsAddressType::Oracle, MarsAddressType::Params],
)?;
let oracle_addr = &addresses[&MarsAddressType::Oracle];
let params_addr = &addresses[&MarsAddressType::Params];

let (health, _) = get_health_and_positions(
&deps.as_ref(),
&env,
user.address(),
oracle_addr,
params_addr,
)?;

if health.is_liquidatable() {
return Err(ContractError::InvalidHealthFactorAfterDisablingCollateral {});
}
}

Ok(Response::new()
.add_attribute("action", "update_asset_collateral_status")
.add_attribute("user", user)
.add_attribute("denom", denom)
.add_attribute("enable", enable.to_string()))
}
42 changes: 42 additions & 0 deletions contracts/red-bank/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use cosmwasm_std::{DepsMut, MessageInfo, Response};
use mars_owner::OwnerUpdate;
use mars_red_bank_types::red_bank::CreateOrUpdateConfig;
use mars_utils::helpers::option_string_to_addr;

use crate::{
error::ContractError,
state::{CONFIG, OWNER},
};

pub fn update_owner(
deps: DepsMut,
info: MessageInfo,
update: OwnerUpdate,
) -> Result<Response, ContractError> {
Ok(OWNER.update(deps, info, update)?)
}

/// Update config
pub fn update_config(
deps: DepsMut,
info: MessageInfo,
new_config: CreateOrUpdateConfig,
) -> Result<Response, ContractError> {
OWNER.assert_owner(deps.storage, &info.sender)?;

let mut config = CONFIG.load(deps.storage)?;

// Destructuring a struct’s fields into separate variables in order to force
// compile error if we add more params
let CreateOrUpdateConfig {
address_provider,
} = new_config;

// Update config
config.address_provider =
option_string_to_addr(deps.api, address_provider, config.address_provider)?;

CONFIG.save(deps.storage, &config)?;

Ok(Response::new().add_attribute("action", "update_config"))
}
Loading