diff --git a/Cargo.lock b/Cargo.lock index 4af1e947f..c2106f3ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2147,7 +2147,7 @@ dependencies = [ [[package]] name = "mars-credit-manager" -version = "2.1.0" +version = "2.2.0" dependencies = [ "anyhow", "cosmwasm-schema", @@ -2530,7 +2530,7 @@ dependencies = [ [[package]] name = "mars-rewards-collector-osmosis" -version = "2.1.1" +version = "2.2.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", diff --git a/Makefile.toml b/Makefile.toml index d9758408a..456931601 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -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 = ''' diff --git a/contracts/account-nft/tests/tests/helpers/mock_env.rs b/contracts/account-nft/tests/tests/helpers/mock_env.rs index 882963125..d0e06bfdf 100644 --- a/contracts/account-nft/tests/tests/helpers/mock_env.rs +++ b/contracts/account-nft/tests/tests/helpers/mock_env.rs @@ -18,6 +18,7 @@ use mars_types::{ use super::MockEnvBuilder; +#[allow(dead_code)] pub struct MockEnv { pub app: BasicApp, pub minter: Addr, diff --git a/contracts/account-nft/tests/tests/helpers/mock_env_builder.rs b/contracts/account-nft/tests/tests/helpers/mock_env_builder.rs index ac752309d..86aa7752e 100644 --- a/contracts/account-nft/tests/tests/helpers/mock_env_builder.rs +++ b/contracts/account-nft/tests/tests/helpers/mock_env_builder.rs @@ -153,7 +153,6 @@ impl MockEnvBuilder { zapper: "n/a".to_string(), health_contract: "n/a".to_string(), rewards_collector: None, - swap_fee: Decimal::percent(1), }, }, &[], diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 6180d1add..e7dd9efc4 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -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 } diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index 4a8d74c17..e74d395df 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -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}, @@ -139,5 +139,5 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { #[cfg_attr(not(feature = "library"), entry_point)] pub fn migrate(deps: DepsMut, _env: Env, _msg: Empty) -> Result { - migrations::v2_1_0::migrate(deps) + migrations::v2_2_0::migrate(deps) } diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index 1fdd27ed4..3697e1ee5 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -8,7 +8,7 @@ use crate::{ HEALTH_CONTRACT, INCENTIVES, MAX_SLIPPAGE, MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, PARAMS, 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<()> { @@ -32,6 +32,7 @@ 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(()) diff --git a/contracts/credit-manager/src/liquidate.rs b/contracts/credit-manager/src/liquidate.rs index 9c3c20b2c..d809facb8 100644 --- a/contracts/credit-manager/src/liquidate.rs +++ b/contracts/credit-manager/src/liquidate.rs @@ -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( diff --git a/contracts/credit-manager/src/migrations/mod.rs b/contracts/credit-manager/src/migrations/mod.rs index 78e0d72f0..569d5a2f0 100644 --- a/contracts/credit-manager/src/migrations/mod.rs +++ b/contracts/credit-manager/src/migrations/mod.rs @@ -1 +1,2 @@ pub mod v2_1_0; +pub mod v2_2_0; diff --git a/contracts/credit-manager/src/migrations/v2_1_0.rs b/contracts/credit-manager/src/migrations/v2_1_0.rs index 67cc959f8..73ac80af0 100644 --- a/contracts/credit-manager/src/migrations/v2_1_0.rs +++ b/contracts/credit-manager/src/migrations/v2_1_0.rs @@ -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 { // 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)) } diff --git a/contracts/credit-manager/src/migrations/v2_2_0.rs b/contracts/credit-manager/src/migrations/v2_2_0.rs new file mode 100644 index 000000000..7b2206cd1 --- /dev/null +++ b/contracts/credit-manager/src/migrations/v2_2_0.rs @@ -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 { + 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)) +} diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index dac680eaa..09d353a57 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -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::{ @@ -59,10 +59,13 @@ pub fn query_config(deps: Deps) -> ContractResult { zapper: ZAPPER.load(deps.storage)?.address().into(), health_contract: HEALTH_CONTRACT.load(deps.storage)?.address().into(), rewards_collector: REWARDS_COLLECTOR.may_load(deps.storage)?, - swap_fee: SWAP_FEE.load(deps.storage)?, }) } +pub fn query_swap_fee(deps: Deps) -> ContractResult { + Ok(SWAP_FEE.load(deps.storage)?) +} + pub fn query_positions(deps: Deps, account_id: &str) -> ContractResult { Ok(Positions { account_id: account_id.to_string(), diff --git a/contracts/credit-manager/tests/tests/mod.rs b/contracts/credit-manager/tests/tests/mod.rs index 72ac5ff12..defcc4614 100644 --- a/contracts/credit-manager/tests/tests/mod.rs +++ b/contracts/credit-manager/tests/tests/mod.rs @@ -24,7 +24,7 @@ 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; @@ -32,6 +32,7 @@ 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; diff --git a/contracts/credit-manager/tests/tests/test_instantiate.rs b/contracts/credit-manager/tests/tests/test_instantiate.rs index 242858a57..816a1f2dd 100644 --- a/contracts/credit-manager/tests/tests/test_instantiate.rs +++ b/contracts/credit-manager/tests/tests/test_instantiate.rs @@ -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(); +} diff --git a/contracts/credit-manager/tests/tests/test_migration_v2.rs b/contracts/credit-manager/tests/tests/test_migration_v2_2_0.rs similarity index 59% rename from contracts/credit-manager/tests/tests/test_migration_v2.rs rename to contracts/credit-manager/tests/tests/test_migration_v2_2_0.rs index 28db67d18..21742f2ae 100644 --- a/contracts/credit-manager/tests/tests/test_migration_v2.rs +++ b/contracts/credit-manager/tests/tests/test_migration_v2_2_0.rs @@ -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] @@ -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(); @@ -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()); } diff --git a/contracts/credit-manager/tests/tests/test_rewards_collector_whitelist.rs b/contracts/credit-manager/tests/tests/test_rewards_collector_whitelist.rs new file mode 100644 index 000000000..a0839f942 --- /dev/null +++ b/contracts/credit-manager/tests/tests/test_rewards_collector_whitelist.rs @@ -0,0 +1,64 @@ +use cosmwasm_std::{coin, Addr}; +use cw_multi_test::{BankSudo, Executor, SudoMsg}; +use mars_testing::multitest::helpers; +use mars_types::rewards_collector::{ + ExecuteMsg as RcExecuteMsg, UpdateConfig as RcUpdateConfig, WhitelistAction, +}; + +#[test] +fn rewards_collector_whitelist_enforced() { + let mut mock = helpers::MockEnv::new().build().unwrap(); + let config = mock.query_config(); + let rewards_collector_info = config.rewards_collector.expect("rewards collector configured"); + let rewards_collector_addr = Addr::unchecked(rewards_collector_info.address.clone()); + + // fund the rewards collector with uusdc so distribution can execute + mock.app + .sudo(SudoMsg::Bank(BankSudo::Mint { + to_address: rewards_collector_addr.to_string(), + amount: vec![coin(1_000, "uusdc")], + })) + .unwrap(); + + // non-whitelisted address cannot distribute rewards + assert!(mock + .app + .execute_contract( + Addr::unchecked("not_whitelisted"), + rewards_collector_addr.clone(), + &RcExecuteMsg::DistributeRewards { + denom: "uusdc".to_string(), + }, + &[], + ) + .is_err()); + + // whitelist alice + mock.app + .execute_contract( + Addr::unchecked("owner"), + rewards_collector_addr.clone(), + &RcExecuteMsg::UpdateConfig { + new_cfg: RcUpdateConfig { + whitelist_actions: Some(vec![WhitelistAction::AddAddress { + address: "alice".to_string(), + }]), + ..Default::default() + }, + }, + &[], + ) + .unwrap(); + + // whitelisted address succeeds + mock.app + .execute_contract( + Addr::unchecked("alice"), + rewards_collector_addr, + &RcExecuteMsg::DistributeRewards { + denom: "uusdc".to_string(), + }, + &[], + ) + .unwrap(); +} diff --git a/contracts/credit-manager/tests/tests/test_update_config.rs b/contracts/credit-manager/tests/tests/test_update_config.rs index b519fa40a..d1da7b736 100644 --- a/contracts/credit-manager/tests/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/tests/test_update_config.rs @@ -143,9 +143,6 @@ fn update_config_works_with_full_config() { assert_eq!(&new_config.health_contract, new_health_contract.address()); assert_ne!(new_config.health_contract, original_config.health_contract); - assert_eq!(new_config.swap_fee, new_swap_fee); - assert_ne!(new_config.swap_fee, original_config.swap_fee); - let rc_accounts = mock.query_accounts(&new_rewards_collector, None, None); let rc_account = rc_accounts.first().unwrap(); assert_eq!(rc_account.kind, AccountKind::Default); diff --git a/contracts/health/tests/tests/helpers/mock_env_builder.rs b/contracts/health/tests/tests/helpers/mock_env_builder.rs index fdc46262a..3a0cac15c 100644 --- a/contracts/health/tests/tests/helpers/mock_env_builder.rs +++ b/contracts/health/tests/tests/helpers/mock_env_builder.rs @@ -137,7 +137,6 @@ impl MockEnvBuilder { zapper: "n/a".to_string(), health_contract: "n/a".to_string(), rewards_collector: None, - swap_fee: Decimal::permille(1), }, }, &[], diff --git a/contracts/incentives/src/helpers.rs b/contracts/incentives/src/helpers.rs index 3171d5b25..ed278b768 100644 --- a/contracts/incentives/src/helpers.rs +++ b/contracts/incentives/src/helpers.rs @@ -59,7 +59,7 @@ impl MaybeMutStorage<'_> { /// - duration is a multiple of epoch duration /// - enough tokens are sent to cover the entire duration /// - start_time is a multiple of epoch duration away from any other existing incentive -/// for the same collateral denom and incentive denom tuple +/// for the same collateral denom and incentive denom tuple pub fn validate_incentive_schedule( storage: &dyn Storage, info: &MessageInfo, diff --git a/contracts/params/tests/tests/helpers/mock_env.rs b/contracts/params/tests/tests/helpers/mock_env.rs index 938a23f47..c85186f91 100644 --- a/contracts/params/tests/tests/helpers/mock_env.rs +++ b/contracts/params/tests/tests/helpers/mock_env.rs @@ -243,164 +243,6 @@ impl MockEnvBuilder { }) } - fn deploy_address_provider(&mut self) -> Addr { - let contract = mock_address_provider_contract(); - let code_id = self.app.store_code(contract); - - self.app - .instantiate_contract( - code_id, - self.deployer.clone(), - &address_provider::InstantiateMsg { - owner: self.deployer.clone().to_string(), - prefix: "".to_string(), - }, - &[], - "mock-address-provider", - None, - ) - .unwrap() - } - - fn deploy_oracle(&mut self) -> Addr { - let code_id = self.app.store_code(mock_oracle_contract()); - - let addr = self - .app - .instantiate_contract( - code_id, - self.deployer.clone(), - &oracle::InstantiateMsg:: { - owner: self.deployer.to_string(), - base_denom: "uusd".to_string(), - custom_init: None, - }, - &[], - "oracle", - None, - ) - .unwrap(); - - self.set_address(MarsAddressType::Oracle, addr.clone()); - - addr - } - - fn deploy_red_bank(&mut self, address_provider: &str) -> Addr { - let code_id = self.app.store_code(mock_red_bank_contract()); - - let addr = self - .app - .instantiate_contract( - code_id, - self.deployer.clone(), - &red_bank::InstantiateMsg { - owner: self.deployer.to_string(), - config: red_bank::CreateOrUpdateConfig { - address_provider: Some(address_provider.to_string()), - }, - }, - &[], - "red-bank", - None, - ) - .unwrap(); - - self.set_address(MarsAddressType::RedBank, addr.clone()); - - addr - } - - fn deploy_incentives(&mut self, address_provider_addr: &Addr) -> Addr { - let code_id = self.app.store_code(mock_incentives_contract()); - - let addr = self - .app - .instantiate_contract( - code_id, - self.deployer.clone(), - &incentives::InstantiateMsg { - owner: self.deployer.to_string(), - address_provider: address_provider_addr.to_string(), - epoch_duration: 604800, - max_whitelisted_denoms: 10, - }, - &[], - "incentives", - None, - ) - .unwrap(); - - self.set_address(MarsAddressType::Incentives, addr.clone()); - - addr - } - - fn deploy_rewards_collector_osmosis(&mut self, address_provider_addr: &Addr) -> Addr { - let code_id = self.app.store_code(mock_rewards_collector_osmosis_contract()); - - let addr = self - .app - .instantiate_contract( - code_id, - self.deployer.clone(), - &rewards_collector::InstantiateMsg { - owner: self.deployer.to_string(), - address_provider: address_provider_addr.to_string(), - safety_tax_rate: Decimal::percent(50), - revenue_share_tax_rate: Decimal::percent(50), - safety_fund_config: RewardConfig { - target_denom: "uusdc".to_string(), - transfer_type: TransferType::Bank, - }, - revenue_share_config: RewardConfig { - target_denom: "uusdc".to_string(), - transfer_type: TransferType::Bank, - }, - fee_collector_config: RewardConfig { - target_denom: "umars".to_string(), - transfer_type: TransferType::Bank, - }, - channel_id: "0".to_string(), - timeout_seconds: 900, - whitelisted_distributors: vec![], - }, - &[], - "rewards-collector", - None, - ) - .unwrap(); - - self.set_address(MarsAddressType::RewardsCollector, addr.clone()); - - addr - } - - fn set_address(&mut self, address_type: MarsAddressType, address: Addr) { - let address_provider_addr = self.get_address_provider(); - - self.app - .execute_contract( - self.deployer.clone(), - address_provider_addr, - &address_provider::ExecuteMsg::SetAddress { - address_type, - address: address.into(), - }, - &[], - ) - .unwrap(); - } - - fn get_address_provider(&mut self) -> Addr { - if self.address_provider.is_none() { - let addr = self.deploy_address_provider(); - - self.address_provider = Some(addr); - } - self.address_provider.clone().unwrap() - } - fn set_emergency_owner(&mut self, params_contract: &Addr, eo: &str) { self.app .execute_contract( diff --git a/contracts/red-bank/src/interest_rates.rs b/contracts/red-bank/src/interest_rates.rs index 3d65c6774..1a0b39b31 100644 --- a/contracts/red-bank/src/interest_rates.rs +++ b/contracts/red-bank/src/interest_rates.rs @@ -15,6 +15,7 @@ use crate::{error::ContractError, user::User}; /// 1. Updates market borrow and liquidity indices. /// 2. If there are any protocol rewards, builds a mint to the rewards collector and adds it /// to the returned response +/// /// NOTE: it does not save the market to store /// WARNING: For a given block, this function should be called before updating interest rates /// as it would apply the new interest rates instead of the ones that were valid during diff --git a/contracts/rewards-collector/base/src/contract.rs b/contracts/rewards-collector/base/src/contract.rs index 5683a28fa..9e177c0d5 100644 --- a/contracts/rewards-collector/base/src/contract.rs +++ b/contracts/rewards-collector/base/src/contract.rs @@ -7,9 +7,7 @@ use mars_owner::{Owner, OwnerInit::SetInitialOwner, OwnerUpdate}; use mars_types::{ address_provider::{self, AddressResponseItem, MarsAddressType}, credit_manager::{self, Action}, - incentives::{self, IncentiveKind}, - oracle::ActionKind, - red_bank, + incentives, red_bank, rewards_collector::{ Config, ConfigResponse, ExecuteMsg, InstantiateMsg, QueryMsg, UpdateConfig, }, diff --git a/contracts/rewards-collector/osmosis/Cargo.toml b/contracts/rewards-collector/osmosis/Cargo.toml index 38c1a92b4..fd7c049e3 100644 --- a/contracts/rewards-collector/osmosis/Cargo.toml +++ b/contracts/rewards-collector/osmosis/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mars-rewards-collector-osmosis" -version = "2.1.1" +version = "2.2.0" authors = { workspace = true } edition = { workspace = true } license = { workspace = true } diff --git a/contracts/rewards-collector/osmosis/src/lib.rs b/contracts/rewards-collector/osmosis/src/lib.rs index f580b36d1..5f22dc500 100644 --- a/contracts/rewards-collector/osmosis/src/lib.rs +++ b/contracts/rewards-collector/osmosis/src/lib.rs @@ -78,6 +78,6 @@ pub mod entry { #[entry_point] pub fn migrate(deps: DepsMut, _env: Env, _msg: Empty) -> Result { - migrations::v2_1_1::migrate(deps) + migrations::v2_2_0::migrate(deps) } } diff --git a/contracts/rewards-collector/osmosis/src/migrations/mod.rs b/contracts/rewards-collector/osmosis/src/migrations/mod.rs index dfd90f0b6..d82c4436e 100644 --- a/contracts/rewards-collector/osmosis/src/migrations/mod.rs +++ b/contracts/rewards-collector/osmosis/src/migrations/mod.rs @@ -1,2 +1,3 @@ pub mod v2_1_0; pub mod v2_1_1; +pub mod v2_2_0; diff --git a/contracts/rewards-collector/osmosis/src/migrations/v2_1_0.rs b/contracts/rewards-collector/osmosis/src/migrations/v2_1_0.rs index 0e6cedc46..0f89338c5 100644 --- a/contracts/rewards-collector/osmosis/src/migrations/v2_1_0.rs +++ b/contracts/rewards-collector/osmosis/src/migrations/v2_1_0.rs @@ -2,18 +2,19 @@ use cosmwasm_std::{DepsMut, Response}; use cw2::{assert_contract_version, set_contract_version}; use mars_rewards_collector_base::ContractError; -use crate::entry::{CONTRACT_NAME, CONTRACT_VERSION}; +use crate::entry::CONTRACT_NAME; const FROM_VERSION: &str = "2.0.1"; +const TO_VERSION: &str = "2.1.0"; pub fn migrate(deps: DepsMut) -> Result { // 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)) } diff --git a/contracts/rewards-collector/osmosis/src/migrations/v2_1_1.rs b/contracts/rewards-collector/osmosis/src/migrations/v2_1_1.rs index f0f8fc7c3..28c2dd0c3 100644 --- a/contracts/rewards-collector/osmosis/src/migrations/v2_1_1.rs +++ b/contracts/rewards-collector/osmosis/src/migrations/v2_1_1.rs @@ -3,10 +3,7 @@ use cw2::{assert_contract_version, set_contract_version}; use mars_rewards_collector_base::ContractError; use mars_types::rewards_collector::{Config, RewardConfig, TransferType}; -use crate::{ - entry::{CONTRACT_NAME, CONTRACT_VERSION}, - OsmosisCollector, -}; +use crate::{entry::CONTRACT_NAME, OsmosisCollector}; pub mod previous_state { use cosmwasm_schema::cw_serde; @@ -44,6 +41,7 @@ pub mod previous_state { } const FROM_VERSION: &str = "2.1.0"; +const TO_VERSION: &str = "2.1.1"; pub fn migrate(deps: DepsMut) -> Result { let storage: &mut dyn Storage = deps.storage; @@ -99,10 +97,10 @@ pub fn migrate(deps: DepsMut) -> Result { let collector = OsmosisCollector::default(); collector.config.save(storage, &new_config)?; - 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)) } diff --git a/contracts/rewards-collector/osmosis/src/migrations/v2_2_0.rs b/contracts/rewards-collector/osmosis/src/migrations/v2_2_0.rs new file mode 100644 index 000000000..51da486d3 --- /dev/null +++ b/contracts/rewards-collector/osmosis/src/migrations/v2_2_0.rs @@ -0,0 +1,81 @@ +use cosmwasm_std::{DepsMut, Response, Storage}; +use cw2::{assert_contract_version, get_contract_version, set_contract_version, VersionError}; +use mars_rewards_collector_base::ContractError; +use mars_types::rewards_collector::Config; + +use crate::{ + entry::{CONTRACT_NAME, CONTRACT_VERSION}, + OsmosisCollector, +}; + +mod previous_state { + use cosmwasm_schema::cw_serde; + use cosmwasm_std::{Addr, Decimal}; + use cw_storage_plus::Item; + use mars_types::rewards_collector::RewardConfig; + + #[cw_serde] + pub struct Config { + pub address_provider: Addr, + pub safety_tax_rate: Decimal, + pub revenue_share_tax_rate: Decimal, + pub slippage_tolerance: Decimal, + pub safety_fund_config: RewardConfig, + pub revenue_share_config: RewardConfig, + pub fee_collector_config: RewardConfig, + pub channel_id: String, + pub timeout_seconds: u64, + } + + pub const CONFIG: Item = Item::new("config"); +} + +const FROM_VERSION: &str = "2.1.1"; + +pub fn migrate(deps: DepsMut) -> Result { + let contract = format!("crates.io:{CONTRACT_NAME}"); + let version = get_contract_version(deps.storage)?; + + if version.contract != contract { + return Err(ContractError::Version(VersionError::WrongContract { + expected: contract, + found: version.contract, + })); + } + + if version.version != FROM_VERSION { + return Err(ContractError::Version(VersionError::WrongVersion { + expected: FROM_VERSION.to_string(), + found: version.version, + })); + } + + assert_contract_version(deps.storage, &contract, FROM_VERSION)?; + + let storage: &mut dyn Storage = deps.storage; + let collector = OsmosisCollector::default(); + + let old_config = previous_state::CONFIG.load(storage)?; + + let new_config = Config { + address_provider: old_config.address_provider, + safety_tax_rate: old_config.safety_tax_rate, + revenue_share_tax_rate: old_config.revenue_share_tax_rate, + safety_fund_config: old_config.safety_fund_config, + revenue_share_config: old_config.revenue_share_config, + fee_collector_config: old_config.fee_collector_config, + channel_id: old_config.channel_id, + timeout_seconds: old_config.timeout_seconds, + whitelisted_distributors: vec![], + }; + + new_config.validate()?; + collector.config.save(storage, &new_config)?; + + set_contract_version(deps.storage, contract, CONTRACT_VERSION)?; + + Ok(Response::new() + .add_attribute("action", "migrate") + .add_attribute("from_version", FROM_VERSION) + .add_attribute("to_version", CONTRACT_VERSION)) +} diff --git a/contracts/rewards-collector/osmosis/tests/tests/mod.rs b/contracts/rewards-collector/osmosis/tests/tests/mod.rs index e2174ea2e..3d17ad33c 100644 --- a/contracts/rewards-collector/osmosis/tests/tests/mod.rs +++ b/contracts/rewards-collector/osmosis/tests/tests/mod.rs @@ -2,7 +2,7 @@ mod helpers; mod test_admin; mod test_distribute_rewards; -mod test_migration_v2_1_0_to_v2_1_1; +mod test_migration_v2_2_0; mod test_swap; mod test_update_owner; mod test_whitelist_distributors; diff --git a/contracts/rewards-collector/osmosis/tests/tests/test_migration_v2_1_0_to_v2_1_1.rs b/contracts/rewards-collector/osmosis/tests/tests/test_migration_v2_1_0_to_v2_1_1.rs deleted file mode 100644 index f4f1fcb5c..000000000 --- a/contracts/rewards-collector/osmosis/tests/tests/test_migration_v2_1_0_to_v2_1_1.rs +++ /dev/null @@ -1,100 +0,0 @@ -use cosmwasm_std::{attr, testing::mock_env, Addr, Decimal, Empty, Event}; -use cw2::{ContractVersion, VersionError}; -use mars_rewards_collector_base::ContractError; -use mars_rewards_collector_osmosis::{ - entry::migrate, migrations::v2_1_1::previous_state, OsmosisCollector, -}; -use mars_testing::mock_dependencies; -use mars_types::rewards_collector::TransferType; - -#[test] -fn wrong_contract_name() { - let mut deps = mock_dependencies(&[]); - 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, - ContractError::Version(VersionError::WrongContract { - expected: "crates.io:mars-rewards-collector-osmosis".to_string(), - found: "contract_xyz".to_string() - }) - ); -} - -#[test] -fn wrong_contract_version() { - let mut deps = mock_dependencies(&[]); - cw2::set_contract_version( - deps.as_mut().storage, - "crates.io:mars-rewards-collector-osmosis", - "4.1.0", - ) - .unwrap(); - - let err = migrate(deps.as_mut(), mock_env(), Empty {}).unwrap_err(); - - assert_eq!( - err, - ContractError::Version(VersionError::WrongVersion { - expected: "2.1.0".to_string(), - found: "4.1.0".to_string() - }) - ); -} - -#[test] -fn successful_migration_to_v2_1_1() { - let mut deps = mock_dependencies(&[]); - cw2::set_contract_version( - deps.as_mut().storage, - "crates.io:mars-rewards-collector-osmosis", - "2.1.0", - ) - .unwrap(); - - let v1_config = previous_state::Config { - address_provider: Addr::unchecked("address_provider"), - safety_tax_rate: Decimal::percent(50), - safety_fund_denom: "ibc/6F34E1BD664C36CE49ACC28E60D62559A5F96C4F9A6CCE4FC5A67B2852E24CFE" - .to_string(), - fee_collector_denom: "ibc/2E7368A14AC9AB7870F32CFEA687551C5064FA861868EDF7437BC877358A81F9" - .to_string(), - channel_id: "channel-2083".to_string(), - timeout_seconds: 600, - slippage_tolerance: Decimal::percent(1), - neutron_ibc_config: None, - }; - previous_state::CONFIG.save(deps.as_mut().storage, &v1_config).unwrap(); - - let res = migrate(deps.as_mut(), mock_env(), Empty {}).unwrap(); - - assert_eq!(res.messages, vec![]); - assert_eq!(res.events, vec![] as Vec); - assert!(res.data.is_none()); - assert_eq!( - res.attributes, - vec![attr("action", "migrate"), attr("from_version", "2.1.0"), attr("to_version", "2.1.1")] - ); - - let new_contract_version = ContractVersion { - contract: "crates.io:mars-rewards-collector-osmosis".to_string(), - version: "2.1.1".to_string(), - }; - assert_eq!(cw2::get_contract_version(deps.as_ref().storage).unwrap(), new_contract_version); - - // ensure state is correct - let collector = OsmosisCollector::default(); - let updated_config = collector.config.load(deps.as_ref().storage).unwrap(); - - assert_eq!(updated_config.channel_id, "channel-874".to_string()); - assert_eq!(updated_config.safety_tax_rate, Decimal::percent(45)); - assert_eq!(updated_config.revenue_share_tax_rate, Decimal::percent(10)); - assert_eq!(updated_config.safety_fund_config.target_denom, v1_config.safety_fund_denom); - assert_eq!(updated_config.safety_fund_config.transfer_type, TransferType::Bank); - assert_eq!(updated_config.revenue_share_config.target_denom, v1_config.safety_fund_denom); - assert_eq!(updated_config.revenue_share_config.transfer_type, TransferType::Bank); - assert_eq!(updated_config.fee_collector_config.target_denom, v1_config.fee_collector_denom); - assert_eq!(updated_config.fee_collector_config.transfer_type, TransferType::Ibc); -} diff --git a/contracts/rewards-collector/osmosis/tests/tests/test_migration_v2_2_0.rs b/contracts/rewards-collector/osmosis/tests/tests/test_migration_v2_2_0.rs new file mode 100644 index 000000000..3e4db6d29 --- /dev/null +++ b/contracts/rewards-collector/osmosis/tests/tests/test_migration_v2_2_0.rs @@ -0,0 +1,127 @@ +use cosmwasm_std::{attr, testing::mock_env, Decimal, Empty, Event}; +use cw2::{ContractVersion, VersionError}; +use mars_rewards_collector_base::ContractError; +use mars_rewards_collector_osmosis::{entry::migrate, OsmosisCollector}; +use mars_testing::mock_dependencies; +use mars_types::rewards_collector::{Config, RewardConfig, TransferType}; + +const CONTRACT: &str = "crates.io:mars-rewards-collector-osmosis"; + +mod previous_state { + use cosmwasm_schema::cw_serde; + use cosmwasm_std::{Addr, Decimal}; + use cw_storage_plus::Item; + use mars_types::rewards_collector::RewardConfig; + + #[cw_serde] + pub struct Config { + pub address_provider: Addr, + pub safety_tax_rate: Decimal, + pub revenue_share_tax_rate: Decimal, + pub slippage_tolerance: Decimal, + pub safety_fund_config: RewardConfig, + pub revenue_share_config: RewardConfig, + pub fee_collector_config: RewardConfig, + pub channel_id: String, + pub timeout_seconds: u64, + } + + pub const CONFIG: Item = Item::new("config"); +} + +#[test] +fn wrong_contract_name() { + let mut deps = mock_dependencies(&[]); + cw2::set_contract_version(deps.as_mut().storage, "contract_xyz", "2.1.1").unwrap(); + + let err = migrate(deps.as_mut(), mock_env(), Empty {}).unwrap_err(); + + match err { + ContractError::Version(VersionError::WrongContract { + expected, + found, + }) => { + assert_eq!(expected, CONTRACT.to_string()); + assert_eq!(found, "contract_xyz".to_string()); + } + other => panic!("unexpected error: {other:?}"), + } +} + +#[test] +fn wrong_contract_version() { + let mut deps = mock_dependencies(&[]); + cw2::set_contract_version(deps.as_mut().storage, CONTRACT, "4.1.0").unwrap(); + + let err = migrate(deps.as_mut(), mock_env(), Empty {}).unwrap_err(); + + match err { + ContractError::Version(VersionError::WrongVersion { + found, + .. + }) => { + assert_eq!(found, "4.1.0".to_string()); + } + other => panic!("unexpected error: {other:?}"), + } +} + +#[test] +fn successful_migration_from_2_1_1() { + let mut deps = mock_dependencies(&[]); + cw2::set_contract_version(deps.as_mut().storage, CONTRACT, "2.1.1").unwrap(); + + let reward_cfg = |denom: &str| RewardConfig { + target_denom: denom.to_string(), + transfer_type: TransferType::Bank, + }; + + let addr_provider = deps.as_ref().api.addr_validate("addr_provider").unwrap(); + let old_config = previous_state::Config { + address_provider: addr_provider.clone(), + safety_tax_rate: Decimal::percent(5), + revenue_share_tax_rate: Decimal::percent(10), + slippage_tolerance: Decimal::percent(1), + safety_fund_config: reward_cfg("usdc"), + revenue_share_config: reward_cfg("usdc"), + fee_collector_config: reward_cfg("mars"), + channel_id: "channel-1".to_string(), + timeout_seconds: 600, + }; + + previous_state::CONFIG.save(deps.as_mut().storage, &old_config).unwrap(); + + let res = migrate(deps.as_mut(), mock_env(), Empty {}).unwrap(); + + assert_eq!(res.messages, vec![]); + assert_eq!(res.events, vec![] as Vec); + assert!(res.data.is_none()); + assert_eq!( + res.attributes, + vec![attr("action", "migrate"), attr("from_version", "2.1.1"), attr("to_version", "2.2.0")] + ); + + let new_contract_version = ContractVersion { + contract: CONTRACT.to_string(), + version: "2.2.0".to_string(), + }; + assert_eq!(cw2::get_contract_version(deps.as_ref().storage).unwrap(), new_contract_version); + + let collector = OsmosisCollector::default(); + let stored_config = collector.config.load(deps.as_ref().storage).unwrap(); + + assert_eq!( + stored_config, + Config { + address_provider: addr_provider, + safety_tax_rate: Decimal::percent(5), + revenue_share_tax_rate: Decimal::percent(10), + safety_fund_config: reward_cfg("usdc"), + revenue_share_config: reward_cfg("usdc"), + fee_collector_config: reward_cfg("mars"), + channel_id: "channel-1".to_string(), + timeout_seconds: 600, + whitelisted_distributors: vec![], + } + ); +} diff --git a/contracts/vault/tests/tests/test_redeem.rs b/contracts/vault/tests/tests/test_redeem.rs index 1fc2aa2e9..316dad3c2 100644 --- a/contracts/vault/tests/tests/test_redeem.rs +++ b/contracts/vault/tests/tests/test_redeem.rs @@ -70,7 +70,7 @@ fn redeem_invalid_funds() { ); assert_vault_err( res, - ContractError::Payment(PaymentError::MissingDenom("factory/contract11/vault".to_string())), + ContractError::Payment(PaymentError::MissingDenom("factory/contract12/vault".to_string())), ); } diff --git a/coverage_grcov.Makefile.toml b/coverage_grcov.Makefile.toml index 883872a13..5fdf7714f 100644 --- a/coverage_grcov.Makefile.toml +++ b/coverage_grcov.Makefile.toml @@ -29,7 +29,7 @@ LLVM_PROFILE_FILE = "${COVERAGE_PROF_OUTPUT}/coverage-%p-%m.profraw" condition = { env_not_set = ["SKIP_INSTALL_GRCOV"] } private = true command = "cargo" -args = ["install", "grcov", "--locked"] +args = ["install", "grcov", "--version=0.9.1", "--locked"] # NOTE: ignore coverage for swapper and zapper contracts because their tests are based on `osmosis-testing` which don't work for grcov [tasks.coverage-grcov] diff --git a/packages/testing/src/multitest/helpers/mock_env.rs b/packages/testing/src/multitest/helpers/mock_env.rs index 0a4cad07a..b3c562a2a 100644 --- a/packages/testing/src/multitest/helpers/mock_env.rs +++ b/packages/testing/src/multitest/helpers/mock_env.rs @@ -64,6 +64,7 @@ use mars_types::{ QueryMsg::{UserCollateral, UserDebt}, UserCollateralResponse, UserDebtResponse, }, + rewards_collector::{self, RewardConfig, TransferType}, swapper::{ EstimateExactInSwapResponse, InstantiateMsg as SwapperInstantiateMsg, QueryMsg::EstimateExactInSwap, SwapperRoute, @@ -81,7 +82,10 @@ use super::{ mock_red_bank_contract, mock_rover_contract, mock_swapper_contract, mock_v2_zapper_contract, mock_vault_contract, AccountToFund, CoinInfo, VaultTestInfo, ASTRO_LP_DENOM, }; -use crate::multitest::modules::token_factory::{CustomApp, TokenFactory}; +use crate::{ + integration::mock_contracts::mock_rewards_collector_osmosis_contract, + multitest::modules::token_factory::{CustomApp, TokenFactory}, +}; pub const DEFAULT_RED_BANK_COIN_BALANCE: Uint128 = Uint128::new(1_000_000); @@ -114,6 +118,7 @@ pub struct MockEnvBuilder { pub health_contract: Option, pub evil_vault: Option, pub swap_fee: Option, + pub rewards_collector: Option, } #[allow(clippy::new_ret_no_self)] @@ -142,6 +147,7 @@ impl MockEnv { health_contract: None, evil_vault: None, swap_fee: None, + rewards_collector: None, } } @@ -939,21 +945,22 @@ impl MockEnvBuilder { self.update_health_contract_config(&rover); self.deploy_nft_contract(&rover); + self.fund_users(); + + self.deploy_vaults(); if self.deploy_nft_contract && self.set_nft_contract_minter { + let rewards_collector_addr = self.deploy_rewards_collector(); + self.rewards_collector = Some(rewards_collector_addr.clone()); self.update_config( &rover, ConfigUpdates { - rewards_collector: Some("rewards_collector_contract".to_string()), + rewards_collector: Some(rewards_collector_addr.to_string()), ..Default::default() }, ); } - self.fund_users(); - - self.deploy_vaults(); - Ok(MockEnv { app: self.app, rover, @@ -1421,6 +1428,10 @@ impl MockEnvBuilder { } fn deploy_rewards_collector(&mut self) -> Addr { + if let Some(addr) = &self.rewards_collector { + return addr.clone(); + } + let code_id = self.app.store_code(mock_rewards_collector_osmosis_contract()); let owner = self.get_owner(); let address_provider = self.get_address_provider(); @@ -1458,7 +1469,11 @@ impl MockEnvBuilder { .unwrap(); self.set_address(MarsAddressType::RewardsCollector, addr.clone()); + self.set_address(MarsAddressType::SafetyFund, Addr::unchecked("safety_fund")); + self.set_address(MarsAddressType::RevenueShare, Addr::unchecked("revenue_share")); + self.set_address(MarsAddressType::FeeCollector, Addr::unchecked("fee_collector")); + self.rewards_collector = Some(addr.clone()); addr } diff --git a/packages/types/src/credit_manager/query.rs b/packages/types/src/credit_manager/query.rs index 3510d8511..efea793d0 100644 --- a/packages/types/src/credit_manager/query.rs +++ b/packages/types/src/credit_manager/query.rs @@ -189,7 +189,6 @@ pub struct ConfigResponse { pub zapper: String, pub health_contract: String, pub rewards_collector: Option, - pub swap_fee: Decimal, } #[cw_serde] diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index 51f4297e6..daba3146d 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -1,6 +1,6 @@ { "contract_name": "mars-credit-manager", - "contract_version": "2.1.0", + "contract_version": "2.2.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index 096df6d5b..2a2ae9eef 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -556,6 +556,9 @@ export type QueryMsg = start_after?: string | null } } + | { + swap_fee_rate: {} + } export type VaultPositionAmount = | { unlocked: VaultAmount