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
16 changes: 16 additions & 0 deletions contracts/red-bank/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,22 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result<Binary, ContractErro
limit,
)?)
}
QueryMsg::UserCollateralsV2 {
user,
account_id,
start_after,
limit,
} => {
let user_addr = deps.api.addr_validate(&user)?;
to_binary(&query::query_user_collaterals_v2(
deps,
&env.block,
user_addr,
account_id,
start_after,
limit,
)?)
}
QueryMsg::UserPosition {
user,
account_id,
Expand Down
41 changes: 34 additions & 7 deletions contracts/red-bank/src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ use mars_interest_rate::{
use mars_red_bank_types::{
address_provider::{self, MarsAddressType},
red_bank::{
Collateral, ConfigResponse, Debt, Market, UncollateralizedLoanLimitResponse,
UserCollateralResponse, UserDebtResponse, UserHealthStatus, UserPositionResponse,
Collateral, ConfigResponse, Debt, Market, PaginatedUserCollateralResponse,
UncollateralizedLoanLimitResponse, UserCollateralResponse, UserDebtResponse,
UserHealthStatus, UserPositionResponse,
},
Metadata,
};

use crate::{
Expand All @@ -18,8 +20,8 @@ use crate::{
state::{COLLATERALS, CONFIG, DEBTS, MARKETS, OWNER, UNCOLLATERALIZED_LOAN_LIMITS},
};

const DEFAULT_LIMIT: u32 = 5;
const MAX_LIMIT: u32 = 10;
const DEFAULT_LIMIT: u32 = 10;
const MAX_LIMIT: u32 = 30;

pub fn query_config(deps: Deps) -> StdResult<ConfigResponse> {
let owner_state = OWNER.query(deps.storage)?;
Expand Down Expand Up @@ -179,17 +181,29 @@ pub fn query_user_collaterals(
start_after: Option<String>,
limit: Option<u32>,
) -> Result<Vec<UserCollateralResponse>, ContractError> {
let res_v2 = query_user_collaterals_v2(deps, block, user_addr, account_id, start_after, limit)?;
Ok(res_v2.data)
}

pub fn query_user_collaterals_v2(
deps: Deps,
block: &BlockInfo,
user_addr: Addr,
account_id: Option<String>,
start_after: Option<String>,
limit: Option<u32>,
) -> Result<PaginatedUserCollateralResponse, ContractError> {
let block_time = block.time.seconds();

let start = start_after.map(|denom| Bound::ExclusiveRaw(denom.into_bytes()));
let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize;

let acc_id = account_id.unwrap_or("".to_string());

COLLATERALS
let user_collaterals_res: Result<Vec<_>, ContractError> = COLLATERALS
.prefix((&user_addr, &acc_id))
.range(deps.storage, start, None, Order::Ascending)
.take(limit)
.take(limit + 1) // Fetch one extra item to determine if there are more
.map(|item| {
let (denom, collateral) = item?;

Expand All @@ -205,7 +219,20 @@ pub fn query_user_collaterals(
enabled: collateral.enabled,
})
})
.collect()
.collect();

let mut user_collaterals = user_collaterals_res?;
let has_more = user_collaterals.len() > limit;
if has_more {
user_collaterals.pop(); // Remove the extra item used for checking if there are more items
}

Ok(PaginatedUserCollateralResponse {
data: user_collaterals,
metadata: Metadata {
has_more,
},
})
}

pub fn query_scaled_liquidity_amount(
Expand Down
75 changes: 74 additions & 1 deletion contracts/red-bank/tests/tests/test_query.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use cosmwasm_std::{testing::mock_env, Addr, Decimal, Uint128};
use mars_interest_rate::{get_scaled_debt_amount, get_underlying_debt_amount, SCALING_FACTOR};
use mars_red_bank::{
query::{query_user_collaterals, query_user_debt, query_user_debts},
query::{query_user_collaterals, query_user_collaterals_v2, query_user_debt, query_user_debts},
state::DEBTS,
};
use mars_red_bank_types::red_bank::{Debt, Market, UserCollateralResponse, UserDebtResponse};
Expand Down Expand Up @@ -67,6 +67,79 @@ fn query_collateral() {
);
}

#[test]
fn paginate_user_collaterals_v2() {
let mut deps = th_setup(&[]);
let env = mock_env();

let user_addr = Addr::unchecked("user");

let market_1 = th_init_market(deps.as_mut(), "uosmo", &Default::default());
let market_2 = th_init_market(deps.as_mut(), "uatom", &Default::default());
let market_3 = th_init_market(deps.as_mut(), "untrn", &Default::default());
let market_4 = th_init_market(deps.as_mut(), "ujuno", &Default::default());
let market_5 = th_init_market(deps.as_mut(), "uusdc", &Default::default());
let market_6 = th_init_market(deps.as_mut(), "ujake", &Default::default());

set_collateral(deps.as_mut(), &user_addr, &market_1.denom, Uint128::one(), true);
set_collateral(deps.as_mut(), &user_addr, &market_2.denom, Uint128::one(), true);
set_collateral(deps.as_mut(), &user_addr, &market_3.denom, Uint128::one(), true);
set_collateral(deps.as_mut(), &user_addr, &market_4.denom, Uint128::one(), true);
set_collateral(deps.as_mut(), &user_addr, &market_5.denom, Uint128::one(), true);
set_collateral(deps.as_mut(), &user_addr, &market_6.denom, Uint128::one(), false);

// Check pagination with default params
let collaterals =
query_user_collaterals_v2(deps.as_ref(), &env.block, user_addr.clone(), None, None, None)
.unwrap();
assert_eq!(
to_denoms(&collaterals.data),
vec!["uatom", "ujake", "ujuno", "untrn", "uosmo", "uusdc"]
);
assert!(!collaterals.metadata.has_more);

// Paginate all collaterals
let collaterals = query_user_collaterals_v2(
deps.as_ref(),
&env.block,
user_addr.clone(),
None,
Some("uatom".to_string()),
Some(2),
)
.unwrap();
assert_eq!(to_denoms(&collaterals.data), vec!["ujake", "ujuno"]);
assert!(collaterals.metadata.has_more);

let collaterals = query_user_collaterals_v2(
deps.as_ref(),
&env.block,
user_addr.clone(),
None,
Some("ujuno".to_string()),
Some(2),
)
.unwrap();
assert_eq!(to_denoms(&collaterals.data), vec!["untrn", "uosmo"]);
assert!(collaterals.metadata.has_more);

let collaterals = query_user_collaterals_v2(
deps.as_ref(),
&env.block,
user_addr,
None,
Some("uosmo".to_string()),
Some(2),
)
.unwrap();
assert_eq!(to_denoms(&collaterals.data), vec!["uusdc"]);
assert!(!collaterals.metadata.has_more);
}

fn to_denoms(res: &[UserCollateralResponse]) -> Vec<&str> {
res.iter().map(|item| item.denom.as_str()).collect()
}

#[test]
fn test_query_user_debt() {
let mut deps = th_setup(&[]);
Expand Down
13 changes: 13 additions & 0 deletions packages/types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,16 @@ pub mod oracle;
pub mod red_bank;
pub mod rewards_collector;
pub mod swapper;

use cosmwasm_schema::cw_serde;

#[cw_serde]
pub struct PaginationResponse<T> {
pub data: Vec<T>,
pub metadata: Metadata,
}

#[cw_serde]
pub struct Metadata {
pub has_more: bool,
}
9 changes: 9 additions & 0 deletions packages/types/src/red_bank/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,15 @@ pub enum QueryMsg {
limit: Option<u32>,
},

/// Get all collateral positions for a user
#[returns(crate::red_bank::PaginatedUserCollateralResponse)]
UserCollateralsV2 {
user: String,
account_id: Option<String>,
start_after: Option<String>,
limit: Option<u32>,
},

/// Get user position
#[returns(crate::red_bank::UserPositionResponse)]
UserPosition {
Expand Down
4 changes: 4 additions & 0 deletions packages/types/src/red_bank/types.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Decimal, Uint128};

use crate::PaginationResponse;

/// Global configuration
#[cw_serde]
pub struct Config<T> {
Expand Down Expand Up @@ -96,6 +98,8 @@ pub struct UserCollateralResponse {
pub enabled: bool,
}

pub type PaginatedUserCollateralResponse = PaginationResponse<UserCollateralResponse>;

#[cw_serde]
pub struct UserPositionResponse {
/// Total value of all enabled collateral assets.
Expand Down
117 changes: 117 additions & 0 deletions schemas/mars-red-bank/mars-red-bank.json
Original file line number Diff line number Diff line change
Expand Up @@ -822,6 +822,48 @@
},
"additionalProperties": false
},
{
"description": "Get all collateral positions for a user",
"type": "object",
"required": [
"user_collaterals_v2"
],
"properties": {
"user_collaterals_v2": {
"type": "object",
"required": [
"user"
],
"properties": {
"account_id": {
"type": [
"string",
"null"
]
},
"limit": {
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 0.0
},
"start_after": {
"type": [
"string",
"null"
]
},
"user": {
"type": "string"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"description": "Get user position",
"type": "object",
Expand Down Expand Up @@ -1508,6 +1550,81 @@
}
}
},
"user_collaterals_v2": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "PaginationResponse_for_UserCollateralResponse",
"type": "object",
"required": [
"data",
"metadata"
],
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/UserCollateralResponse"
}
},
"metadata": {
"$ref": "#/definitions/Metadata"
}
},
"additionalProperties": false,
"definitions": {
"Metadata": {
"type": "object",
"required": [
"has_more"
],
"properties": {
"has_more": {
"type": "boolean"
}
},
"additionalProperties": false
},
"Uint128": {
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
"type": "string"
},
"UserCollateralResponse": {
"type": "object",
"required": [
"amount",
"amount_scaled",
"denom",
"enabled"
],
"properties": {
"amount": {
"description": "Underlying asset amount that is actually deposited at the current block",
"allOf": [
{
"$ref": "#/definitions/Uint128"
}
]
},
"amount_scaled": {
"description": "Scaled collateral amount stored in contract state",
"allOf": [
{
"$ref": "#/definitions/Uint128"
}
]
},
"denom": {
"description": "Asset denom",
"type": "string"
},
"enabled": {
"description": "Wether the user is using asset as collateral or not",
"type": "boolean"
}
},
"additionalProperties": false
}
}
},
"user_debt": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "UserDebtResponse",
Expand Down
Loading