Conversation
| } | ||
|
|
||
| pub fn assert_health(deps: DepsMut, env: Env, token_id: NftTokenId) -> ContractResult<Response> { | ||
| let position = query_position(deps.as_ref(), &env, token_id)?; |
There was a problem hiding this comment.
I'm directly calling query_position here. Not sure if that is more normal or rather doing a query hitting that position msg endpoint.
c07984e to
0fc8452
Compare
| &QueryMsg::Market { | ||
| asset: asset_info.into(), | ||
| }, | ||
| )?; |
There was a problem hiding this comment.
Some optimization could be to use QueryMsg::MarketsList to query markets in one call.
There was a problem hiding this comment.
Ah, will investigate
There was a problem hiding this comment.
In my case, MarketsList won't help me because it returns Vec<MarketInfo> which doesn't have max_tvl and liquidation_threshold fields. Will need to still call QueryMsg::Market individually.
There was a problem hiding this comment.
We can fix that
I will work on red bank after finishing rewards collector. I will see what i can do
There was a problem hiding this comment.
We could query by list of denoms but for now I don't know if it is worth
| let margin = HEALTH_FACTOR_MARGIN.load(deps.storage)?; | ||
| let hf_str = position | ||
| .health_factor | ||
| .map_or("null".to_string(), |dec| dec.to_string()); |
There was a problem hiding this comment.
To be honest I am not sure if this null is good. Maybe if it is null then we don't add it to attributes. Maybe we could add something more descriptive.
There was a problem hiding this comment.
Would n/a be better? I wonder if it would be more difficult for the consumers of these attributes if the field sometimes wasn't present 🤔
| } | ||
| } | ||
|
|
||
| // TODO: After mars-core updates oracle contract to CosmWasm 1.0 should update this mock to return MarsDecimal |
There was a problem hiding this comment.
mars-core oracle already updated
There was a problem hiding this comment.
I would need a version bump here: https://crates.io/crates/mars-core to import 🙏
There was a problem hiding this comment.
Could you please use local deps for now ('lib = { path = "../lib" }')? Maybe it is better to update crates.io if we have everything ready (after testing and audit).
There was a problem hiding this comment.
This type lives in mars-core so I don't have access to the file to add as a local dependency here. Maybe I'm missing something? Or are you suggesting copying over Mars' Decimal struct file to Rover (temporarily)?
There was a problem hiding this comment.
New outpost doesn't use MarsDecimal (old one https://github.com/mars-protocol/mars-core/blob/master/packages/mars-core/src/red_bank/mod.rs#L13, new one https://github.com/mars-protocol/outposts/blob/master/packages/mars-outpost/src/red_bank/mod.rs#L4).
Anyway, for now you can leave it as it is but then we can bump on crates.io and use with correct https://github.com/mars-protocol/outposts/blob/master/packages/mars-outpost/src/red_bank/mod.rs#L43
There was a problem hiding this comment.
oh! In that case, this code comment is invalid because there is not special Mars Decimal struct anyway. Happy to see it gone! Will remove this.
contracts/mock-red-bank/src/msg.rs
Outdated
| } | ||
|
|
||
| // Schema reference: https://github.com/mars-protocol/mars-core/blob/master/packages/mars-core/src/red_bank/mod.rs#L47 | ||
| // TODO: After mars-core updates oracle contract to CosmWasm 1.0 should update this mock to return MarsDecimal |
grod220
left a comment
There was a problem hiding this comment.
Lots of changes from borrow PR relating to cw20s. Going to do a bit rebase with this.
| let margin = HEALTH_FACTOR_MARGIN.load(deps.storage)?; | ||
| let hf_str = position | ||
| .health_factor | ||
| .map_or("null".to_string(), |dec| dec.to_string()); |
There was a problem hiding this comment.
Would n/a be better? I wonder if it would be more difficult for the consumers of these attributes if the field sometimes wasn't present 🤔
| } | ||
| } | ||
|
|
||
| // TODO: After mars-core updates oracle contract to CosmWasm 1.0 should update this mock to return MarsDecimal |
There was a problem hiding this comment.
I would need a version bump here: https://crates.io/crates/mars-core to import 🙏
| &QueryMsg::Market { | ||
| asset: asset_info.into(), | ||
| }, | ||
| )?; |
There was a problem hiding this comment.
Ah, will investigate
| }; | ||
|
|
||
| let liquidatable = lqdt_health_factor.map_or_else(|| false, |hf| hf <= Decimal::one()); | ||
| let above_max_ltv = max_ltv_health_factor.map_or_else(|| false, |hf| hf <= Decimal::one()); |
There was a problem hiding this comment.
This is different than fields. See: https://marslend.slack.com/archives/C03QSLT0LPJ/p1659677771072499. Having debt shares that are zero value is not a liquidatable event.
| ORACLE.save(deps.storage, &unchecked.check(deps.api)?)?; | ||
| response = response | ||
| .add_attribute("key", "oracle") | ||
| .add_attribute("value", unchecked.0); |
There was a problem hiding this comment.
not a fan of unchecked.0, because people will need to guess what "0" standards for. we know it stands for the contract's address, but it can well be something else.
instead, OracleBase can implement an address method that return the address:
struct OracleBase<T>(T); // T should be private
impl<T> OracleBase<T> {
pub fn address(&self) -> &T {
return &self.0;
}
}
response = response.add_attribute("value", unchecked.address());There was a problem hiding this comment.
Ah, this is much better. Actually think a getter would be even better, but those don't exist in rust. This will also require a new method since you can't use the constructor directly now.
| let liquidatable = lqdt_health_factor.map_or_else(|| false, |hf| hf <= Decimal::one()); | ||
| let above_max_ltv = max_ltv_health_factor.map_or_else(|| false, |hf| hf <= Decimal::one()); |
There was a problem hiding this comment.
since false is a constant, you can use map_or instead of map_or_else
*_or_else methods are for non-constant values that need to be evaluated at runtime
| let liquidatable = lqdt_health_factor.map_or_else(|| false, |hf| hf <= Decimal::one()); | |
| let above_max_ltv = max_ltv_health_factor.map_or_else(|| false, |hf| hf <= Decimal::one()); | |
| let liquidatable = lqdt_health_factor.map_or(false, |hf| hf <= Decimal::one()); | |
| let above_max_ltv = max_ltv_health_factor.map_or(false, |hf| hf <= Decimal::one()); |
There was a problem hiding this comment.
Good to know that distinction
| pub fn assert_below_max_ltv( | ||
| deps: DepsMut, | ||
| env: Env, | ||
| token_id: NftTokenId, | ||
| ) -> ContractResult<Response> { | ||
| let position = compute_health(deps.as_ref(), &env, token_id)?; |
There was a problem hiding this comment.
| pub fn assert_below_max_ltv( | |
| deps: DepsMut, | |
| env: Env, | |
| token_id: NftTokenId, | |
| ) -> ContractResult<Response> { | |
| let position = compute_health(deps.as_ref(), &env, token_id)?; | |
| pub fn assert_below_max_ltv( | |
| deps: Deps, | |
| env: Env, | |
| token_id: NftTokenId, | |
| ) -> ContractResult<Response> { | |
| let position = compute_health(deps, &env, token_id)?; |
| token_id: token_id.to_string(), | ||
| assets: get_assets(deps, token_id)?, | ||
| debt_shares: get_debt_shares(deps, token_id)?, | ||
| coin_assets: coin_asset_values, |
There was a problem hiding this comment.
why renaming assets to coin_assets? feels redundant. imo, it can either be coins or assets, but no coin_assets
There was a problem hiding this comment.
I'm trying to distinguish the difference between:
- Assets -> coins or vaults that are a positive contributor to your health factor
- Debts -> coins you owe
But maybe renaming the state from ASSETS to COIN_BALANCES and this field to coins communicates this enough.
| denom: denom.clone(), | ||
| amount: *amount, | ||
| price, | ||
| total_value, |
There was a problem hiding this comment.
imo this field should be called value instead of total_value. "total" means the sum of all coins' values. this is the value of just one coin.
| red_bank: RedBankBase("redbankaddr".to_string()), | ||
| oracle: OracleBase("oracle_contract".to_string()), |
There was a problem hiding this comment.
can you make the format of the mock contract addresses consistent
| CoinInfo { | ||
| denom: "coin_1".to_string(), | ||
| max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), | ||
| liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), | ||
| price: Decimal::from_atomics(10u128, 0).unwrap(), | ||
| }, | ||
| CoinInfo { | ||
| denom: "coin_2".to_string(), | ||
| max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), | ||
| liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), | ||
| price: Decimal::from_atomics(10u128, 0).unwrap(), | ||
| }, | ||
| CoinInfo { | ||
| denom: "coin_3".to_string(), | ||
| max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), | ||
| liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), | ||
| price: Decimal::from_atomics(10u128, 0).unwrap(), | ||
| }, | ||
| CoinInfo { | ||
| denom: "coin_4".to_string(), | ||
| max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), | ||
| liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), | ||
| price: Decimal::from_atomics(10u128, 0).unwrap(), | ||
| }, | ||
| CoinInfo { | ||
| denom: "coin_5".to_string(), | ||
| max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), | ||
| liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), | ||
| price: Decimal::from_atomics(10u128, 0).unwrap(), | ||
| }, | ||
| CoinInfo { | ||
| denom: "coin_6".to_string(), | ||
| max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), | ||
| liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), | ||
| price: Decimal::from_atomics(10u128, 0).unwrap(), | ||
| }, | ||
| CoinInfo { | ||
| denom: "coin_7".to_string(), | ||
| max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), | ||
| liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), | ||
| price: Decimal::from_atomics(10u128, 0).unwrap(), | ||
| }, | ||
| CoinInfo { | ||
| denom: "coin_8".to_string(), | ||
| max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), | ||
| liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), | ||
| price: Decimal::from_atomics(10u128, 0).unwrap(), | ||
| }, | ||
| CoinInfo { | ||
| denom: "coin_9".to_string(), | ||
| max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), | ||
| liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), | ||
| price: Decimal::from_atomics(10u128, 0).unwrap(), | ||
| }, | ||
| CoinInfo { | ||
| denom: "coin_10".to_string(), | ||
| max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), | ||
| liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), | ||
| price: Decimal::from_atomics(10u128, 0).unwrap(), | ||
| }, | ||
| CoinInfo { | ||
| denom: "coin_11".to_string(), | ||
| max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), | ||
| liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), | ||
| price: Decimal::from_atomics(10u128, 0).unwrap(), | ||
| }, | ||
| CoinInfo { | ||
| denom: "coin_12".to_string(), | ||
| max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), | ||
| liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), | ||
| price: Decimal::from_atomics(10u128, 0).unwrap(), | ||
| }, | ||
| CoinInfo { | ||
| denom: "coin_13".to_string(), | ||
| max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), | ||
| liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), | ||
| price: Decimal::from_atomics(10u128, 0).unwrap(), | ||
| }, | ||
| CoinInfo { | ||
| denom: "coin_14".to_string(), | ||
| max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), | ||
| liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), | ||
| price: Decimal::from_atomics(10u128, 0).unwrap(), | ||
| }, |
There was a problem hiding this comment.
may be a good idea to use a helper function to build this vector instead of typing it out, e.g.
let build_mock_coin_infos = |count: usize| -> Vec<CoinInfo> {
(1..count)
.into_iter()
.map(|i| CoinInfo {
denom: format!("coin_{}", i),
max_ltv: Decimal::from_atomics(7u128, 1).unwrap(),
liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(),
price: Decimal::from_atomics(10u128, 0).unwrap(),
})
.collect()
};
let coin_infos = build_mock_coin_infos(50);| let response: Decimal = querier.query(&QueryRequest::Wasm(WasmQuery::Smart { | ||
| contract_addr: self.0.to_string(), | ||
| msg: to_binary(&QueryMsg::AssetPrice { | ||
| denom: denom.to_string(), | ||
| })?, | ||
| }))?; | ||
| Ok(response) |
There was a problem hiding this comment.
a more succinct implementation. maybe works, haven't tried
| let response: Decimal = querier.query(&QueryRequest::Wasm(WasmQuery::Smart { | |
| contract_addr: self.0.to_string(), | |
| msg: to_binary(&QueryMsg::AssetPrice { | |
| denom: denom.to_string(), | |
| })?, | |
| }))?; | |
| Ok(response) | |
| querier.query_wasm_smart( | |
| self.0.to_string(), | |
| &QueryMsg::AssetPrice { | |
| denom: denom.to_string(), | |
| }, | |
| ) |
| pub assets_value: Decimal, | ||
| /// Total value of debts | ||
| pub debts_value: Decimal, | ||
| /// The sum of the value of all assets (multiplied by their liquidation threshold) over the |
There was a problem hiding this comment.
i feel the use of the term "liquidation threshold" is very confusing here.
imo "liquidation threshold" is a single constant number. if my health factor is below it, i'm liquidatable. otherwise i'm not liquidatable.
the term to be used here should be something like "collateral factor" or "collateral power"
There was a problem hiding this comment.
Liquidation thresholds will always be different for different assets though.
Max LTV -- the most we'll allow a position to be taken out
Liquidation threshold -- the LTV where you get liquidated
There will be a wide variety of values for both of these.
packages/rover/src/health.rs
Outdated
| /// Total value of assets | ||
| pub assets_value: Decimal, |
There was a problem hiding this comment.
imo if it's the total value of assets, it should be named total_asset_value
if it's the value of a single asset, it should be asset_value
at least this is how i name my variables it in Fields
There was a problem hiding this comment.
That makes sense. However, would call it: total_assets_value
There was a problem hiding this comment.
Uh i don’t think that’s correct english grammar
There was a problem hiding this comment.
How so? Assets is the plural of asset. So the combined value of all assets could be: total_value_of_assets or total_assets_value.
| pub denom: String, | ||
| pub shares: Uint128, | ||
| pub total_value: Decimal, | ||
| } |
There was a problem hiding this comment.
@larry0x so should we expose the shares or not here? I'm not sure it really communicates anything to the frontend. I wonder if instead we should do the math to calculate the amount underlying this and send that back instead.
There was a problem hiding this comment.
i like returning the underlying amount instead. if someone wants to know the shares they can use raw query.
another benefit of returning the underlying amount is, instead of having two structs CoinValue and DebtShareValue, we can have only one (CoinValue) and use it for both assets and debts.
There was a problem hiding this comment.
Yes, think this makes sense. But I want to create a follow up PR for this (given this one is nearly three weeks old).
The main feature is the health check assertion callback ensuring only healthy state changes are allowed. However, lots of little things are included here: