diff --git a/contracts/vault/src/msg.rs b/contracts/vault/src/msg.rs index 03f30d54..72669abe 100644 --- a/contracts/vault/src/msg.rs +++ b/contracts/vault/src/msg.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::Uint128; +use cosmwasm_std::{Decimal, Uint128}; use cw_vault_standard::{VaultStandardExecuteMsg, VaultStandardQueryMsg}; use crate::performance_fee::PerformanceFeeConfig; @@ -98,6 +98,20 @@ pub struct VaultInfoResponseExt { /// Performance fee configuration pub performance_fee_config: PerformanceFeeConfig, + + /// Total base tokens in the vault + pub total_base_tokens: Uint128, + + /// Total vault tokens minted + pub total_vault_tokens: Uint128, + + /// Current share price, representing the value of one vault token in terms of the base token. + /// It is calculated as the ratio of total base tokens in the vault to the total supply of vault tokens. + /// Denominated as a decimal value: + /// `share_price = total_base_tokens / total_vault_tokens` + /// This share price allows users to determine how many base tokens can be redeemed per vault token: + /// `base_tokens = vault_share * vault_tokens` + pub share_price: Option, } /// Unlock state for a single user diff --git a/contracts/vault/src/query.rs b/contracts/vault/src/query.rs index 9d124336..8639fd2ae 100644 --- a/contracts/vault/src/query.rs +++ b/contracts/vault/src/query.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Addr, Deps, Order, Uint128}; +use cosmwasm_std::{Addr, Decimal, Deps, Order, Uint128}; use cw_paginate::{paginate_map_query, PaginationResponse, DEFAULT_LIMIT, MAX_LIMIT}; use cw_storage_plus::Bound; @@ -14,16 +14,35 @@ use crate::{ }; pub fn query_vault_info(deps: Deps) -> ContractResult { + let vault_token = VAULT_TOKEN.load(deps.storage)?; + let total_vault_tokens = vault_token.query_total_supply(deps)?; + + // If vault account is not set, we don't calculate share price. + // It means that the vault is not binded to any account yet. + let vault_account_id_opt = VAULT_ACC_ID.may_load(deps.storage)?; + let mut total_base_tokens = Uint128::zero(); + let mut share_price = None; + if vault_account_id_opt.is_some() { + total_base_tokens = total_base_tokens_in_account(deps)?; + share_price = if total_vault_tokens.is_zero() { + None + } else { + Some(Decimal::checked_from_ratio(total_base_tokens, total_vault_tokens)?) + }; + } Ok(VaultInfoResponseExt { base_token: BASE_TOKEN.load(deps.storage)?, - vault_token: VAULT_TOKEN.load(deps.storage)?.to_string(), + vault_token: vault_token.to_string(), title: TITLE.may_load(deps.storage)?, subtitle: SUBTITLE.may_load(deps.storage)?, description: DESCRIPTION.may_load(deps.storage)?, credit_manager: CREDIT_MANAGER.load(deps.storage)?, - vault_account_id: VAULT_ACC_ID.may_load(deps.storage)?, + vault_account_id: vault_account_id_opt, cooldown_period: COOLDOWN_PERIOD.load(deps.storage)?, performance_fee_config: PERFORMANCE_FEE_CONFIG.load(deps.storage)?, + total_base_tokens, + total_vault_tokens, + share_price, }) } diff --git a/contracts/vault/tests/tests/test_binding.rs b/contracts/vault/tests/tests/test_binding.rs index 7c5bcd00..cae605d7 100644 --- a/contracts/vault/tests/tests/test_binding.rs +++ b/contracts/vault/tests/tests/test_binding.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{coin, Addr, Decimal}; +use cosmwasm_std::{coin, Addr, Decimal, Uint128}; use mars_types::health::AccountKind; use mars_vault::{ error::ContractError, msg::VaultInfoResponseExt, performance_fee::PerformanceFeeConfig, @@ -56,7 +56,10 @@ fn only_credit_manager_can_bind_account() { performance_fee_config: PerformanceFeeConfig { fee_rate: Decimal::zero(), withdrawal_interval: 0 - } + }, + total_base_tokens: Uint128::zero(), + total_vault_tokens: Uint128::zero(), + share_price: None, } ) } diff --git a/contracts/vault/tests/tests/test_deposit.rs b/contracts/vault/tests/tests/test_deposit.rs index 6a53b9b3..d6b1fc63 100644 --- a/contracts/vault/tests/tests/test_deposit.rs +++ b/contracts/vault/tests/tests/test_deposit.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{coin, Addr, Uint128}; +use cosmwasm_std::{coin, Addr, Decimal, Uint128}; use cw_utils::PaymentError; use mars_types::health::AccountKind; use mars_vault::error::ContractError; @@ -7,7 +7,11 @@ use super::{ helpers::{AccountToFund, MockEnv}, vault_helpers::{assert_vault_err, execute_deposit}, }; -use crate::tests::{helpers::deploy_managed_vault, vault_helpers::query_vault_info}; +use crate::tests::{ + helpers::deploy_managed_vault, + test_redeem::uusdc_info, + vault_helpers::{query_total_assets, query_total_vault_token_supply, query_vault_info}, +}; #[test] fn deposit_invalid_funds() { @@ -105,6 +109,7 @@ fn deposit_succeded() { let user = Addr::unchecked("user"); let user_funded_amt = Uint128::new(1_000_000_000); let mut mock = MockEnv::new() + .set_params(&[uusdc_info()]) .fund_account(AccountToFund { addr: fund_manager.clone(), funds: vec![coin(1_000_000_000, "untrn")], @@ -165,4 +170,17 @@ fn deposit_succeded() { let assets_res = res.deposits.first().unwrap(); assert_eq!(assets_res.amount, deposited_amt); assert_eq!(assets_res.denom, "uusdc".to_string()); + + // check total base/vault tokens and share price + let vault_info_res = query_vault_info(&mock, &managed_vault_addr); + let total_base_tokens = query_total_assets(&mock, &managed_vault_addr); + let total_vault_tokens = query_total_vault_token_supply(&mock, &managed_vault_addr); + assert_eq!(total_base_tokens, deposited_amt); + assert_eq!(total_vault_tokens, user_vault_token_balance); + assert_eq!(vault_info_res.total_base_tokens, total_base_tokens); + assert_eq!(vault_info_res.total_vault_tokens, total_vault_tokens); + assert_eq!( + vault_info_res.share_price, + Some(Decimal::from_ratio(total_base_tokens, total_vault_tokens)) + ); } diff --git a/contracts/vault/tests/tests/test_instantiate.rs b/contracts/vault/tests/tests/test_instantiate.rs index 13dca05e..5c323ecf 100644 --- a/contracts/vault/tests/tests/test_instantiate.rs +++ b/contracts/vault/tests/tests/test_instantiate.rs @@ -1,7 +1,7 @@ use std::str::FromStr; use anyhow::Result as AnyResult; -use cosmwasm_std::{coin, Addr, Decimal}; +use cosmwasm_std::{coin, Addr, Decimal, Uint128}; use cw_multi_test::Executor; use mars_utils::error::ValidationError; use mars_vault::{ @@ -42,7 +42,10 @@ fn instantiate_with_empty_metadata() { performance_fee_config: PerformanceFeeConfig { fee_rate: Decimal::zero(), withdrawal_interval: 0 - } + }, + total_base_tokens: Uint128::zero(), + total_vault_tokens: Uint128::zero(), + share_price: None, } ) } @@ -99,7 +102,10 @@ fn instantiate_with_metadata() { performance_fee_config: PerformanceFeeConfig { fee_rate: Decimal::from_str("0.000046287042457349").unwrap(), withdrawal_interval: 1563, - } + }, + total_base_tokens: Uint128::zero(), + total_vault_tokens: Uint128::zero(), + share_price: None, } ) } diff --git a/contracts/vault/tests/tests/vault_helpers.rs b/contracts/vault/tests/tests/vault_helpers.rs index 4130a38f..c2190395 100644 --- a/contracts/vault/tests/tests/vault_helpers.rs +++ b/contracts/vault/tests/tests/vault_helpers.rs @@ -110,6 +110,10 @@ pub fn query_vault_info(mock_env: &MockEnv, vault: &Addr) -> VaultInfoResponseEx .unwrap() } +pub fn query_total_assets(mock_env: &MockEnv, vault: &Addr) -> Uint128 { + mock_env.app.wrap().query_wasm_smart(vault.to_string(), &QueryMsg::TotalAssets {}).unwrap() +} + pub fn query_total_vault_token_supply(mock_env: &MockEnv, vault: &Addr) -> Uint128 { mock_env .app