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
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Makefile.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ ARTIFACTS_DIR_PATH = "target/wasm32-unknown-unknown/release"
RUST_OPTIMIZER_VERSION = "0.16.0"
# Use rust version from rust-optimizer Dockerfile (see https://github.com/CosmWasm/optimizer/blob/v0.16.0/Dockerfile#L1)
# to be sure that we compile / test against the same version
RUST_VERSION = "1.78.0"
RUST_VERSION = "1.81.0"

[tasks.install-stable]
script = '''
Expand Down
1 change: 1 addition & 0 deletions contracts/account-nft/tests/tests/helpers/mock_env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use mars_types::{

use super::MockEnvBuilder;

#[allow(dead_code)]
pub struct MockEnv {
pub app: BasicApp,
pub minter: Addr,
Expand Down
2 changes: 1 addition & 1 deletion contracts/credit-manager/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mars-credit-manager"
version = { workspace = true }
version = "2.2.0"
authors = { workspace = true }
license = { workspace = true }
edition = { workspace = true }
Expand Down
7 changes: 4 additions & 3 deletions contracts/credit-manager/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ use crate::{
query::{
query_accounts, query_all_coin_balances, query_all_debt_shares,
query_all_total_debt_shares, query_all_vault_positions, query_all_vault_utilizations,
query_config, query_positions, query_total_debt_shares, query_vault_bindings,
query_vault_position_value, query_vault_utilization,
query_config, query_positions, query_swap_fee, query_total_debt_shares,
query_vault_bindings, query_vault_position_value, query_vault_utilization,
},
repay::repay_from_wallet,
update_config::{update_config, update_nft_config, update_owner},
Expand Down Expand Up @@ -132,11 +132,12 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult<Binary> {
start_after,
limit,
} => to_json_binary(&query_vault_bindings(deps, start_after, limit)?),
QueryMsg::SwapFeeRate {} => to_json_binary(&query_swap_fee(deps)?),
};
res.map_err(Into::into)
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(deps: DepsMut, _env: Env, _msg: Empty) -> Result<Response, ContractError> {
migrations::v2_1_0::migrate(deps)
migrations::v2_2_0::migrate(deps)
}
6 changes: 4 additions & 2 deletions contracts/credit-manager/src/instantiate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ use crate::{
error::ContractResult,
state::{
HEALTH_CONTRACT, INCENTIVES, MAX_SLIPPAGE, MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, PARAMS,
RED_BANK, SWAPPER, ZAPPER,
RED_BANK, SWAPPER, SWAP_FEE, ZAPPER,
},
utils::assert_max_slippage,
utils::{assert_max_slippage, assert_swap_fee},
};

pub fn store_config(deps: DepsMut, env: Env, msg: &InstantiateMsg) -> ContractResult<()> {
Expand All @@ -32,6 +32,8 @@ pub fn store_config(deps: DepsMut, env: Env, msg: &InstantiateMsg) -> ContractRe
HEALTH_CONTRACT.save(deps.storage, &msg.health_contract.check(deps.api)?)?;
PARAMS.save(deps.storage, &msg.params.check(deps.api)?)?;
INCENTIVES.save(deps.storage, &msg.incentives.check(deps.api, env.contract.address)?)?;
assert_swap_fee(msg.swap_fee)?;
SWAP_FEE.save(deps.storage, &msg.swap_fee)?;

Ok(())
}
1 change: 1 addition & 0 deletions contracts/credit-manager/src/liquidate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::{
/// - Exceeds liquidatee's total debt for denom
/// - Not enough liquidatee request coin balance to match
/// - The value of the debt repaid exceeds the Maximum Debt Repayable (MDR)
///
/// Returns -> (Debt Coin, Liquidator Request Coin, Liquidatee Request Coin)
/// Difference between Liquidator Request Coin and Liquidatee Request Coin goes to rewards-collector account as protocol fee.
pub fn calculate_liquidation(
Expand Down
1 change: 1 addition & 0 deletions contracts/credit-manager/src/migrations/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod v2_1_0;
pub mod v2_2_0;
10 changes: 4 additions & 6 deletions contracts/credit-manager/src/migrations/v2_1_0.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
use cosmwasm_std::{DepsMut, Response};
use cw2::{assert_contract_version, set_contract_version};

use crate::{
contract::{CONTRACT_NAME, CONTRACT_VERSION},
error::ContractError,
};
use crate::{contract::CONTRACT_NAME, error::ContractError};

const FROM_VERSION: &str = "2.0.3";
const TO_VERSION: &str = "2.1.0";

pub fn migrate(deps: DepsMut) -> Result<Response, ContractError> {
// make sure we're migrating the correct contract and from the correct version
assert_contract_version(deps.storage, &format!("crates.io:{CONTRACT_NAME}"), FROM_VERSION)?;

set_contract_version(deps.storage, format!("crates.io:{CONTRACT_NAME}"), CONTRACT_VERSION)?;
set_contract_version(deps.storage, format!("crates.io:{CONTRACT_NAME}"), TO_VERSION)?;

Ok(Response::new()
.add_attribute("action", "migrate")
.add_attribute("from_version", FROM_VERSION)
.add_attribute("to_version", CONTRACT_VERSION))
.add_attribute("to_version", TO_VERSION))
}
43 changes: 43 additions & 0 deletions contracts/credit-manager/src/migrations/v2_2_0.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use cosmwasm_std::{Decimal, DepsMut, Response};
use cw2::{assert_contract_version, get_contract_version, set_contract_version, VersionError};

use crate::{
contract::{CONTRACT_NAME, CONTRACT_VERSION},
error::ContractError,
state::SWAP_FEE,
};

const FROM_VERSION: &str = "2.1.0";

pub fn migrate(deps: DepsMut) -> Result<Response, ContractError> {
let contract = format!("crates.io:{CONTRACT_NAME}");
let version = get_contract_version(deps.storage)?;
let from_version = version.version;

if version.contract != contract {
return Err(ContractError::Version(VersionError::WrongContract {
expected: contract,
found: version.contract,
}));
}

if from_version != FROM_VERSION {
return Err(ContractError::Version(VersionError::WrongVersion {
expected: FROM_VERSION.to_string(),
found: from_version,
}));
}

assert_contract_version(deps.storage, &contract, FROM_VERSION)?;

if SWAP_FEE.may_load(deps.storage)?.is_none() {
SWAP_FEE.save(deps.storage, &Decimal::zero())?;
}

set_contract_version(deps.storage, format!("crates.io:{CONTRACT_NAME}"), CONTRACT_VERSION)?;

Ok(Response::new()
.add_attribute("action", "migrate")
.add_attribute("from_version", from_version)
.add_attribute("to_version", CONTRACT_VERSION))
}
8 changes: 6 additions & 2 deletions contracts/credit-manager/src/query.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use cosmwasm_std::{Coin, Deps, Env, Order, StdResult};
use cosmwasm_std::{Coin, Decimal, Deps, Env, Order, StdResult};
use cw_paginate::{paginate_map, paginate_map_query, PaginationResponse, DEFAULT_LIMIT, MAX_LIMIT};
use cw_storage_plus::Bound;
use mars_types::{
Expand All @@ -16,7 +16,7 @@ use crate::{
state::{
ACCOUNT_KINDS, ACCOUNT_NFT, COIN_BALANCES, DEBT_SHARES, HEALTH_CONTRACT, INCENTIVES,
MAX_SLIPPAGE, MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, PARAMS, RED_BANK, REWARDS_COLLECTOR,
SWAPPER, TOTAL_DEBT_SHARES, VAULTS, VAULT_POSITIONS, ZAPPER,
SWAPPER, SWAP_FEE, TOTAL_DEBT_SHARES, VAULTS, VAULT_POSITIONS, ZAPPER,
},
utils::debt_shares_to_amount,
vault::vault_utilization_in_deposit_cap_denom,
Expand Down Expand Up @@ -62,6 +62,10 @@ pub fn query_config(deps: Deps) -> ContractResult<ConfigResponse> {
})
}

pub fn query_swap_fee(deps: Deps) -> ContractResult<Decimal> {
Ok(SWAP_FEE.load(deps.storage)?)
}

pub fn query_positions(deps: Deps, account_id: &str) -> ContractResult<Positions> {
Ok(Positions {
account_id: account_id.to_string(),
Expand Down
3 changes: 3 additions & 0 deletions contracts/credit-manager/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,6 @@ pub const REWARDS_COLLECTOR: Item<RewardsCollector> = Item::new("rewards_collect

// (account id, vault addr) bindings between account and vault
pub const VAULTS: Map<&str, Addr> = Map::new("vaults");

// Swap fee
pub const SWAP_FEE: Item<Decimal> = Item::new("swap_fee");
19 changes: 16 additions & 3 deletions contracts/credit-manager/src/swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use mars_types::{

use crate::{
error::{ContractError, ContractResult},
state::{COIN_BALANCES, SWAPPER},
utils::{decrement_coin_balance, update_balance_msg},
state::{COIN_BALANCES, REWARDS_COLLECTOR, SWAPPER, SWAP_FEE},
utils::{decrement_coin_balance, increment_coin_balance, update_balance_msg},
};

pub fn swap_exact_in(
Expand All @@ -19,7 +19,7 @@ pub fn swap_exact_in(
min_receive: Uint128,
route: Option<SwapperRoute>,
) -> ContractResult<Response> {
let coin_in_to_trade = Coin {
let mut coin_in_to_trade = Coin {
denom: coin_in.denom.clone(),
amount: match coin_in.amount {
ActionAmount::Exact(a) => a,
Expand All @@ -35,6 +35,19 @@ pub fn swap_exact_in(

decrement_coin_balance(deps.storage, account_id, &coin_in_to_trade)?;

// Deduct the swap fee
let swap_fee = SWAP_FEE.load(deps.storage)?;
let swap_fee_amount = coin_in_to_trade.amount.checked_mul_floor(swap_fee)?;
coin_in_to_trade.amount = coin_in_to_trade.amount.checked_sub(swap_fee_amount)?;

// Send to Rewards collector
let rc_coin = Coin {
denom: coin_in.denom.clone(),
amount: swap_fee_amount,
};
let rewards_collector_account = REWARDS_COLLECTOR.load(deps.storage)?.account_id;
increment_coin_balance(deps.storage, &rewards_collector_account, &rc_coin)?;

// Updates coin balances for account after the swap has taken place
let update_coin_balance_msg = update_balance_msg(
&deps.querier,
Expand Down
11 changes: 9 additions & 2 deletions contracts/credit-manager/src/update_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ use crate::{
execute::create_credit_account,
state::{
ACCOUNT_NFT, HEALTH_CONTRACT, INCENTIVES, MAX_SLIPPAGE, MAX_UNLOCKING_POSITIONS, ORACLE,
OWNER, RED_BANK, REWARDS_COLLECTOR, SWAPPER, ZAPPER,
OWNER, RED_BANK, REWARDS_COLLECTOR, SWAPPER, SWAP_FEE, ZAPPER,
},
utils::assert_max_slippage,
utils::{assert_max_slippage, assert_swap_fee},
};

pub fn update_config(
Expand Down Expand Up @@ -83,6 +83,13 @@ pub fn update_config(
response.add_attribute("key", "max_slippage").add_attribute("value", num.to_string());
}

if let Some(num) = updates.swap_fee {
assert_swap_fee(num)?;
SWAP_FEE.save(deps.storage, &num)?;
response =
response.add_attribute("key", "swap_fee").add_attribute("value", num.to_string());
}

if let Some(unchecked) = updates.health_contract {
HEALTH_CONTRACT.save(deps.storage, &unchecked.check(deps.api)?)?;
response = response
Expand Down
9 changes: 9 additions & 0 deletions contracts/credit-manager/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ pub fn assert_slippage(storage: &dyn Storage, slippage: Decimal) -> ContractResu
Ok(())
}

pub fn assert_swap_fee(swap_fee: Decimal) -> ContractResult<()> {
if swap_fee >= Decimal::one() {
return Err(ContractError::InvalidConfig {
reason: "Swap fee must be less than 1".to_string(),
});
}
Ok(())
}

pub fn query_nft_token_owner(deps: Deps, account_id: &str) -> ContractResult<String> {
Ok(ACCOUNT_NFT.load(deps.storage)?.query_nft_token_owner(&deps.querier, account_id)?)
}
Expand Down
3 changes: 2 additions & 1 deletion contracts/credit-manager/tests/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,15 @@ mod test_liquidate_lend;
mod test_liquidate_staked_astro_lp;
mod test_liquidate_vault;
mod test_liquidation_pricing;
mod test_migration_v2;
mod test_migration_v2_2_0;
mod test_no_health_check;
mod test_reclaim;
mod test_reentrancy_guard;
mod test_refund_balances;
mod test_repay;
mod test_repay_for_recipient;
mod test_repay_from_wallet;
mod test_rewards_collector_whitelist;
mod test_stake_astro_lp;
mod test_swap;
mod test_unstake_astro_lp;
Expand Down
8 changes: 8 additions & 0 deletions contracts/credit-manager/tests/tests/test_instantiate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,11 @@ fn params_set_on_instantiate() {
fn raises_on_invalid_params_addr() {
MockEnv::new().params("%%%INVALID%%%").build().unwrap();
}

#[test]
#[should_panic]
fn raises_on_invalid_swap_fee() {
use cosmwasm_std::Decimal;

MockEnv::new().swap_fee(Decimal::percent(100)).build().unwrap();
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
use cosmwasm_std::{attr, testing::mock_env, Empty, Event};
use cosmwasm_std::{attr, testing::mock_env, Decimal, Empty, Event};
use cw2::{ContractVersion, VersionError};
use mars_credit_manager::{contract::migrate, error::ContractError};
use mars_credit_manager::{contract::migrate, error::ContractError, state::SWAP_FEE};
use mars_testing::mock_dependencies;

#[test]
fn wrong_contract_name() {
let mut deps = mock_dependencies(&[]);
cw2::set_contract_version(deps.as_mut().storage, "contract_xyz", "2.0.3").unwrap();
cw2::set_contract_version(deps.as_mut().storage, "contract_xyz", "2.1.0").unwrap();

let err = migrate(deps.as_mut(), mock_env(), Empty {}).unwrap_err();

assert_eq!(
err,
match err {
ContractError::Version(VersionError::WrongContract {
expected: "crates.io:mars-credit-manager".to_string(),
found: "contract_xyz".to_string()
})
);
expected,
found,
}) => {
assert_eq!(expected, "crates.io:mars-credit-manager".to_string());
assert_eq!(found, "contract_xyz".to_string());
}
other => panic!("unexpected error: {other:?}"),
}
}

#[test]
Expand All @@ -27,19 +30,21 @@ fn wrong_contract_version() {

let err = migrate(deps.as_mut(), mock_env(), Empty {}).unwrap_err();

assert_eq!(
err,
match err {
ContractError::Version(VersionError::WrongVersion {
expected: "2.0.3".to_string(),
found: "4.1.0".to_string()
})
);
found,
..
}) => {
assert_eq!(found, "4.1.0".to_string());
}
other => panic!("unexpected error: {other:?}"),
}
}

#[test]
fn successful_migration() {
fn successful_migration_from_2_1_0() {
let mut deps = mock_dependencies(&[]);
cw2::set_contract_version(deps.as_mut().storage, "crates.io:mars-credit-manager", "2.0.3")
cw2::set_contract_version(deps.as_mut().storage, "crates.io:mars-credit-manager", "2.1.0")
.unwrap();

let res = migrate(deps.as_mut(), mock_env(), Empty {}).unwrap();
Expand All @@ -49,12 +54,16 @@ fn successful_migration() {
assert!(res.data.is_none());
assert_eq!(
res.attributes,
vec![attr("action", "migrate"), attr("from_version", "2.0.3"), attr("to_version", "2.1.0")]
vec![attr("action", "migrate"), attr("from_version", "2.1.0"), attr("to_version", "2.2.0")]
);

let new_contract_version = ContractVersion {
contract: "crates.io:mars-credit-manager".to_string(),
version: "2.1.0".to_string(),
version: "2.2.0".to_string(),
};
assert_eq!(cw2::get_contract_version(deps.as_ref().storage).unwrap(), new_contract_version);

// Ensure swap fee exists post-migration (zero by default if absent)
let swap_fee = SWAP_FEE.may_load(deps.as_ref().storage).unwrap().unwrap_or_else(Decimal::zero);
assert!(swap_fee <= Decimal::one());
}
Loading
Loading