From 0c8cc199a0fc245df61f9bc07358eb7e0721eb80 Mon Sep 17 00:00:00 2001 From: piobab Date: Tue, 29 Aug 2023 14:33:51 +0200 Subject: [PATCH 1/5] Add pagination with metadata. --- contracts/red-bank/src/contract.rs | 16 ++++++++++++ contracts/red-bank/src/query.rs | 37 ++++++++++++++++++++++++---- packages/types/src/lib.rs | 14 +++++++++++ packages/types/src/red_bank/msg.rs | 9 +++++++ packages/types/src/red_bank/types.rs | 4 +++ 5 files changed, 75 insertions(+), 5 deletions(-) diff --git a/contracts/red-bank/src/contract.rs b/contracts/red-bank/src/contract.rs index 532868c1e..061eae5cf 100644 --- a/contracts/red-bank/src/contract.rs +++ b/contracts/red-bank/src/contract.rs @@ -187,6 +187,22 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result { + 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, diff --git a/contracts/red-bank/src/query.rs b/contracts/red-bank/src/query.rs index 31e92a16b..688749f3e 100644 --- a/contracts/red-bank/src/query.rs +++ b/contracts/red-bank/src/query.rs @@ -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::{ @@ -179,6 +181,18 @@ pub fn query_user_collaterals( start_after: Option, limit: Option, ) -> Result, 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, + start_after: Option, + limit: Option, +) -> Result { let block_time = block.time.seconds(); let start = start_after.map(|denom| Bound::ExclusiveRaw(denom.into_bytes())); @@ -186,10 +200,10 @@ pub fn query_user_collaterals( let acc_id = account_id.unwrap_or("".to_string()); - COLLATERALS + let user_collaterals_res: Result, 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?; @@ -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; + user_collaterals.pop(); // Remove the extra item used for checking if there are more items + let next_start_after = user_collaterals.last().map(|collateral| collateral.denom.clone()); + + Ok(PaginatedUserCollateralResponse { + data: user_collaterals, + metadata: Metadata { + has_more, + next_start_after, + }, + }) } pub fn query_scaled_liquidity_amount( diff --git a/packages/types/src/lib.rs b/packages/types/src/lib.rs index ee8a737d6..ff3b5ea05 100644 --- a/packages/types/src/lib.rs +++ b/packages/types/src/lib.rs @@ -5,3 +5,17 @@ pub mod oracle; pub mod red_bank; pub mod rewards_collector; pub mod swapper; + +use cosmwasm_schema::cw_serde; + +#[cw_serde] +pub struct PaginationResponse { + pub data: Vec, + pub metadata: Metadata, +} + +#[cw_serde] +pub struct Metadata { + pub has_more: bool, + pub next_start_after: Option, +} diff --git a/packages/types/src/red_bank/msg.rs b/packages/types/src/red_bank/msg.rs index f4cc862ab..df61f4718 100644 --- a/packages/types/src/red_bank/msg.rs +++ b/packages/types/src/red_bank/msg.rs @@ -195,6 +195,15 @@ pub enum QueryMsg { limit: Option, }, + /// Get all collateral positions for a user + #[returns(Vec)] + UserCollateralsV2 { + user: String, + account_id: Option, + start_after: Option, + limit: Option, + }, + /// Get user position #[returns(crate::red_bank::UserPositionResponse)] UserPosition { diff --git a/packages/types/src/red_bank/types.rs b/packages/types/src/red_bank/types.rs index 6495d3b99..d6b140449 100644 --- a/packages/types/src/red_bank/types.rs +++ b/packages/types/src/red_bank/types.rs @@ -1,6 +1,8 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Decimal, Uint128}; +use crate::PaginationResponse; + /// Global configuration #[cw_serde] pub struct Config { @@ -96,6 +98,8 @@ pub struct UserCollateralResponse { pub enabled: bool, } +pub type PaginatedUserCollateralResponse = PaginationResponse; + #[cw_serde] pub struct UserPositionResponse { /// Total value of all enabled collateral assets. From 17bd4c7c9ac44a32fb26ce1c5e2ff064c7366196 Mon Sep 17 00:00:00 2001 From: piobab Date: Tue, 29 Aug 2023 16:07:12 +0200 Subject: [PATCH 2/5] Fix return type. --- packages/types/src/red_bank/msg.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/types/src/red_bank/msg.rs b/packages/types/src/red_bank/msg.rs index df61f4718..b80f7ec8c 100644 --- a/packages/types/src/red_bank/msg.rs +++ b/packages/types/src/red_bank/msg.rs @@ -196,7 +196,7 @@ pub enum QueryMsg { }, /// Get all collateral positions for a user - #[returns(Vec)] + #[returns(crate::red_bank::PaginatedUserCollateralResponse)] UserCollateralsV2 { user: String, account_id: Option, From 7d3bd1ee9d50c78af1a1ba60ad9ffacd1afac1c7 Mon Sep 17 00:00:00 2001 From: piobab Date: Wed, 30 Aug 2023 17:42:38 +0200 Subject: [PATCH 3/5] Address comments. --- contracts/red-bank/src/query.rs | 6 +++--- packages/types/src/lib.rs | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/contracts/red-bank/src/query.rs b/contracts/red-bank/src/query.rs index 688749f3e..99fed6031 100644 --- a/contracts/red-bank/src/query.rs +++ b/contracts/red-bank/src/query.rs @@ -223,14 +223,14 @@ pub fn query_user_collaterals_v2( let mut user_collaterals = user_collaterals_res?; let has_more = user_collaterals.len() > limit; - user_collaterals.pop(); // Remove the extra item used for checking if there are more items - let next_start_after = user_collaterals.last().map(|collateral| collateral.denom.clone()); + 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, - next_start_after, }, }) } diff --git a/packages/types/src/lib.rs b/packages/types/src/lib.rs index ff3b5ea05..5750ce359 100644 --- a/packages/types/src/lib.rs +++ b/packages/types/src/lib.rs @@ -17,5 +17,4 @@ pub struct PaginationResponse { #[cw_serde] pub struct Metadata { pub has_more: bool, - pub next_start_after: Option, } From 656fe0bba332c6d744fc697e82ce8e39aed50c8c Mon Sep 17 00:00:00 2001 From: piobab Date: Wed, 30 Aug 2023 18:15:58 +0200 Subject: [PATCH 4/5] Add test case for v2 query. --- contracts/red-bank/src/query.rs | 4 +- contracts/red-bank/tests/tests/test_query.rs | 75 +++++++++++++++++++- 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/contracts/red-bank/src/query.rs b/contracts/red-bank/src/query.rs index 99fed6031..68c89dab3 100644 --- a/contracts/red-bank/src/query.rs +++ b/contracts/red-bank/src/query.rs @@ -20,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 { let owner_state = OWNER.query(deps.storage)?; diff --git a/contracts/red-bank/tests/tests/test_query.rs b/contracts/red-bank/tests/tests/test_query.rs index 1aa27173c..92a213bd4 100644 --- a/contracts/red-bank/tests/tests/test_query.rs +++ b/contracts/red-bank/tests/tests/test_query.rs @@ -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}; @@ -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(&[]); From 05cd2225b1df320628c14d5a099c9337c60da1d8 Mon Sep 17 00:00:00 2001 From: piobab Date: Wed, 30 Aug 2023 18:18:23 +0200 Subject: [PATCH 5/5] Update schema. --- schemas/mars-red-bank/mars-red-bank.json | 117 ++++++++++++++++++ .../mars-red-bank/MarsRedBank.client.ts | 34 +++++ .../mars-red-bank/MarsRedBank.react-query.ts | 32 +++++ .../mars-red-bank/MarsRedBank.types.ts | 15 +++ 4 files changed, 198 insertions(+) diff --git a/schemas/mars-red-bank/mars-red-bank.json b/schemas/mars-red-bank/mars-red-bank.json index 569158af9..6bafea4e5 100644 --- a/schemas/mars-red-bank/mars-red-bank.json +++ b/schemas/mars-red-bank/mars-red-bank.json @@ -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", @@ -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", diff --git a/scripts/types/generated/mars-red-bank/MarsRedBank.client.ts b/scripts/types/generated/mars-red-bank/MarsRedBank.client.ts index 3c34abe51..a48ed7d52 100644 --- a/scripts/types/generated/mars-red-bank/MarsRedBank.client.ts +++ b/scripts/types/generated/mars-red-bank/MarsRedBank.client.ts @@ -24,6 +24,8 @@ import { ArrayOfUncollateralizedLoanLimitResponse, UserCollateralResponse, ArrayOfUserCollateralResponse, + PaginationResponseForUserCollateralResponse, + Metadata, UserDebtResponse, ArrayOfUserDebtResponse, UserHealthStatus, @@ -86,6 +88,17 @@ export interface MarsRedBankReadOnlyInterface { startAfter?: string user: string }) => Promise + userCollateralsV2: ({ + accountId, + limit, + startAfter, + user, + }: { + accountId?: string + limit?: number + startAfter?: string + user: string + }) => Promise userPosition: ({ accountId, user, @@ -133,6 +146,7 @@ export class MarsRedBankQueryClient implements MarsRedBankReadOnlyInterface { this.userDebts = this.userDebts.bind(this) this.userCollateral = this.userCollateral.bind(this) this.userCollaterals = this.userCollaterals.bind(this) + this.userCollateralsV2 = this.userCollateralsV2.bind(this) this.userPosition = this.userPosition.bind(this) this.userPositionLiquidationPricing = this.userPositionLiquidationPricing.bind(this) this.scaledLiquidityAmount = this.scaledLiquidityAmount.bind(this) @@ -266,6 +280,26 @@ export class MarsRedBankQueryClient implements MarsRedBankReadOnlyInterface { }, }) } + userCollateralsV2 = async ({ + accountId, + limit, + startAfter, + user, + }: { + accountId?: string + limit?: number + startAfter?: string + user: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + user_collaterals_v2: { + account_id: accountId, + limit, + start_after: startAfter, + user, + }, + }) + } userPosition = async ({ accountId, user, diff --git a/scripts/types/generated/mars-red-bank/MarsRedBank.react-query.ts b/scripts/types/generated/mars-red-bank/MarsRedBank.react-query.ts index fa1f1ccae..b7502eb79 100644 --- a/scripts/types/generated/mars-red-bank/MarsRedBank.react-query.ts +++ b/scripts/types/generated/mars-red-bank/MarsRedBank.react-query.ts @@ -25,6 +25,8 @@ import { ArrayOfUncollateralizedLoanLimitResponse, UserCollateralResponse, ArrayOfUserCollateralResponse, + PaginationResponseForUserCollateralResponse, + Metadata, UserDebtResponse, ArrayOfUserDebtResponse, UserHealthStatus, @@ -79,6 +81,10 @@ export const marsRedBankQueryKeys = { [ { ...marsRedBankQueryKeys.address(contractAddress)[0], method: 'user_collaterals', args }, ] as const, + userCollateralsV2: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsRedBankQueryKeys.address(contractAddress)[0], method: 'user_collaterals_v2', args }, + ] as const, userPosition: (contractAddress: string | undefined, args?: Record) => [ { ...marsRedBankQueryKeys.address(contractAddress)[0], method: 'user_position', args }, @@ -279,6 +285,32 @@ export function useMarsRedBankUserPositionQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } +export interface MarsRedBankUserCollateralsV2Query + extends MarsRedBankReactQuery { + args: { + accountId?: string + limit?: number + startAfter?: string + user: string + } +} +export function useMarsRedBankUserCollateralsV2Query< + TData = PaginationResponseForUserCollateralResponse, +>({ client, args, options }: MarsRedBankUserCollateralsV2Query) { + return useQuery( + marsRedBankQueryKeys.userCollateralsV2(client?.contractAddress, args), + () => + client + ? client.userCollateralsV2({ + accountId: args.accountId, + limit: args.limit, + startAfter: args.startAfter, + user: args.user, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} export interface MarsRedBankUserCollateralsQuery extends MarsRedBankReactQuery { args: { diff --git a/scripts/types/generated/mars-red-bank/MarsRedBank.types.ts b/scripts/types/generated/mars-red-bank/MarsRedBank.types.ts index b19096d67..9552b2fdf 100644 --- a/scripts/types/generated/mars-red-bank/MarsRedBank.types.ts +++ b/scripts/types/generated/mars-red-bank/MarsRedBank.types.ts @@ -162,6 +162,14 @@ export type QueryMsg = user: string } } + | { + user_collaterals_v2: { + account_id?: string | null + limit?: number | null + start_after?: string | null + user: string + } + } | { user_position: { account_id?: string | null @@ -228,6 +236,13 @@ export interface UserCollateralResponse { enabled: boolean } export type ArrayOfUserCollateralResponse = UserCollateralResponse[] +export interface PaginationResponseForUserCollateralResponse { + data: UserCollateralResponse[] + metadata: Metadata +} +export interface Metadata { + has_more: boolean +} export interface UserDebtResponse { amount: Uint128 amount_scaled: Uint128