From 34c7045e56b452c0771fc7f73bd95a29f9b294cb Mon Sep 17 00:00:00 2001 From: mark Date: Mon, 10 Nov 2025 09:34:02 +0800 Subject: [PATCH 1/9] add migration --- Cargo.lock | 4 +- Makefile.toml | 2 +- checklist.md | 63 +++++ contracts/credit-manager/Cargo.toml | 2 +- contracts/credit-manager/src/contract.rs | 6 +- contracts/credit-manager/src/instantiate.rs | 3 +- .../credit-manager/src/migrations/mod.rs | 1 + .../credit-manager/src/migrations/v2_1_0.rs | 10 +- .../credit-manager/src/migrations/v2_2_0.rs | 43 ++++ contracts/credit-manager/src/query.rs | 6 +- contracts/credit-manager/tests/tests/mod.rs | 3 +- .../tests/tests/test_instantiate.rs | 8 + ...gration_v2.rs => test_migration_v2_2_0.rs} | 47 ++-- .../tests/test_rewards_collector_whitelist.rs | 64 +++++ .../params/tests/tests/helpers/mock_env.rs | 25 +- .../rewards-collector/base/src/contract.rs | 4 +- .../rewards-collector/osmosis/Cargo.toml | 2 +- .../rewards-collector/osmosis/src/lib.rs | 2 +- .../osmosis/src/migrations/mod.rs | 1 + .../osmosis/src/migrations/v2_1_0.rs | 7 +- .../osmosis/src/migrations/v2_1_1.rs | 10 +- .../osmosis/src/migrations/v2_2_0.rs | 35 +++ .../osmosis/tests/tests/mod.rs | 2 +- .../tests/test_migration_v2_1_0_to_v2_1_1.rs | 100 -------- .../tests/tests/test_migration_v2_2_0.rs | 66 ++++++ contracts/vault/tests/tests/test_redeem.rs | 2 +- .../testing/src/multitest/helpers/mock_env.rs | 27 ++- scripts/deploy/osmosis/testnet-config.ts | 2 +- scripts/package.json | 1 + scripts/tests/v2_2_0_basic.ts | 224 ++++++++++++++++++ scripts/utils/environment.ts | 10 +- 31 files changed, 615 insertions(+), 167 deletions(-) create mode 100644 checklist.md create mode 100644 contracts/credit-manager/src/migrations/v2_2_0.rs rename contracts/credit-manager/tests/tests/{test_migration_v2.rs => test_migration_v2_2_0.rs} (59%) create mode 100644 contracts/credit-manager/tests/tests/test_rewards_collector_whitelist.rs create mode 100644 contracts/rewards-collector/osmosis/src/migrations/v2_2_0.rs delete mode 100644 contracts/rewards-collector/osmosis/tests/tests/test_migration_v2_1_0_to_v2_1_1.rs create mode 100644 contracts/rewards-collector/osmosis/tests/tests/test_migration_v2_2_0.rs create mode 100644 scripts/tests/v2_2_0_basic.ts 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..ca911bcb8 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -8,7 +8,7 @@ default_to_workspace = false [env] # Directory with wasm files used by integration tests (another directory can be used instead, for example 'artifacts' from rust-optimizer) -ARTIFACTS_DIR_PATH = "target/wasm32-unknown-unknown/release" +ARTIFACTS_DIR_PATH = "artifacts" # If you bump this version, verify RUST_VERSION correctness 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) diff --git a/checklist.md b/checklist.md new file mode 100644 index 000000000..6f01f4819 --- /dev/null +++ b/checklist.md @@ -0,0 +1,63 @@ +# Update Contracts - Cherry-pick from core-contracts + +## Overview +Pulling latest changes from core-contracts by cherry-picking specific commits. + +## Commits to Cherry-pick (in reverse chronological order) +- [x] `27e8a393232b3f4af2cb7fd89f32fb81dc025881` (oldest - start here) ✅ Conflicts resolved +- [x] `db65c00a347d13d81af1cf4b19776ab4b72eeb07` ✅ Conflicts resolved +- [x] `cb3ef67df47a7ef75d0c9fa1d0577bfc54c59e5d` ✅ Cherry-picked cleanly (2025-11-03) +- [ ] `b508ec06e5b90ed5200fecf4be06a95574c0f1c4` (newest - finish here) + - ⏭️ Cherry-pick skipped (files removed locally); needs manual decision + +## Progress Checklist +- [x] Check current git status and branch + - Current branch: `latest-core-contracts` + - Uncommitted changes in: Makefile.toml +- [x] Add core-contracts as remote (if not already present) + - ✅ Already configured: `git@github.com:mars-protocol/core-contracts.git` +- [x] Fetch latest changes from core-contracts + - ✅ Fetched successfully, found all target commits +- [ ] Cherry-pick commits in order + - ✅ First commit (27e8a393...) conflicts resolved + - ✅ Second commit (db65c00a...) conflicts resolved + - ✅ Third commit (cb3ef67d...) conflicts resolved + - ⚠️ Final commit (b508ec06...) skipped: neutron migration files absent in workspace +- [ ] Test and verify changes +- [ ] Document any conflicts or issues + +## Conflicts to Resolve + +**✅ Commit 27e8a393... (Add Spot trading fees) - RESOLVED** + +**✅ Commit db65c00a... (Add whitelist for rewards distributors) - RESOLVED** + +**✅ Commit cb3ef67d... (Add spot swap fee query) - RESOLVED** + +**Previous conflicts (resolved):** +- `contracts/account-nft/tests/tests/helpers/mock_env_builder.rs` +- `contracts/credit-manager/src/instantiate.rs` +- `contracts/credit-manager/src/query.rs` +- `contracts/credit-manager/src/state.rs` +- `contracts/credit-manager/src/swap.rs` +- `contracts/credit-manager/src/update_config.rs` +- `contracts/credit-manager/src/utils.rs` +- `contracts/credit-manager/tests/tests/test_update_config.rs` +- `contracts/health/tests/tests/helpers/mock_env_builder.rs` +- `packages/testing/src/multitest/helpers/mock_env.rs` +- `packages/types/src/credit_manager/instantiate.rs` +- `packages/types/src/credit_manager/query.rs` +- `scripts/deploy/base/deployer.ts` +- `scripts/deploy/neutron/devnet-config.ts` +- `scripts/deploy/neutron/mainnet-config.ts` +- `scripts/deploy/neutron/testnet-config.ts` +- `scripts/deploy/osmosis/mainnet-config.ts` +- `scripts/deploy/osmosis/testnet-config.ts` +- `scripts/types/config.ts` + +## Notes +- Started: 2025-10-01T14:59:29+08:00 +- ✅ First commit (27e8a393...) completed with conflicts resolved +- ✅ Second commit (db65c00a...) completed with conflicts resolved +- ✅ Third commit (cb3ef67d...) completed with conflicts resolved (2025-11-03) +- ⚠️ Cherry-pick b508ec06... skipped (neutron migrations deleted upstream here) 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/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..17426e934 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::{ @@ -63,6 +63,10 @@ pub fn query_config(deps: Deps) -> ContractResult { }) } +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/params/tests/tests/helpers/mock_env.rs b/contracts/params/tests/tests/helpers/mock_env.rs index 938a23f47..cc9bd6218 100644 --- a/contracts/params/tests/tests/helpers/mock_env.rs +++ b/contracts/params/tests/tests/helpers/mock_env.rs @@ -1,13 +1,26 @@ use std::{mem::take, str::FromStr}; use anyhow::Result as AnyResult; -use cosmwasm_std::{Addr, Decimal}; +use cosmwasm_std::{Addr, Decimal, Empty}; use cw_multi_test::{App, AppResponse, BasicApp, Executor}; use cw_paginate::PaginationResponse; use mars_owner::{OwnerResponse, OwnerUpdate}; -use mars_types::params::{ - AssetParams, AssetParamsUpdate, ConfigResponse, EmergencyUpdate, ExecuteMsg, InstantiateMsg, - QueryMsg, VaultConfig, VaultConfigUpdate, +use mars_testing::{ + integration::mock_contracts::mock_rewards_collector_osmosis_contract, + multitest::helpers::{ + mock_address_provider_contract, mock_incentives_contract, mock_oracle_contract, + mock_red_bank_contract, + }, +}; +use mars_types::{ + address_provider::{self, MarsAddressType}, + incentives, oracle, + params::{ + AssetParams, AssetParamsUpdate, ConfigResponse, EmergencyUpdate, ExecuteMsg, + InstantiateMsg, QueryMsg, VaultConfig, VaultConfigUpdate, + }, + red_bank, + rewards_collector::{self, RewardConfig, TransferType}, }; use super::contracts::mock_params_contract; @@ -19,8 +32,10 @@ pub struct MockEnv { pub struct MockEnvBuilder { pub app: BasicApp, + pub deployer: Addr, pub target_health_factor: Option, pub emergency_owner: Option, + pub address_provider: Option, } #[allow(clippy::new_ret_no_self)] @@ -28,8 +43,10 @@ impl MockEnv { pub fn new() -> MockEnvBuilder { MockEnvBuilder { app: App::default(), + deployer: Addr::unchecked("deployer"), target_health_factor: None, emergency_owner: None, + address_provider: None, } } 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..fa8c2e5ff --- /dev/null +++ b/contracts/rewards-collector/osmosis/src/migrations/v2_2_0.rs @@ -0,0 +1,35 @@ +use cosmwasm_std::{DepsMut, Response}; +use cw2::{assert_contract_version, get_contract_version, set_contract_version, VersionError}; +use mars_rewards_collector_base::ContractError; + +use crate::entry::{CONTRACT_NAME, CONTRACT_VERSION}; + +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)?; + + 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..d7bebab02 --- /dev/null +++ b/contracts/rewards-collector/osmosis/tests/tests/test_migration_v2_2_0.rs @@ -0,0 +1,66 @@ +use cosmwasm_std::{attr, testing::mock_env, Empty, Event}; +use cw2::{ContractVersion, VersionError}; +use mars_rewards_collector_base::ContractError; +use mars_rewards_collector_osmosis::entry::migrate; +use mars_testing::mock_dependencies; + +const CONTRACT: &str = "crates.io:mars-rewards-collector-osmosis"; + +#[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 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); +} 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/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/scripts/deploy/osmosis/testnet-config.ts b/scripts/deploy/osmosis/testnet-config.ts index 9391a4921..6f19b6215 100644 --- a/scripts/deploy/osmosis/testnet-config.ts +++ b/scripts/deploy/osmosis/testnet-config.ts @@ -236,7 +236,7 @@ const testActions = { export const osmosisTestnetConfig: DeploymentConfig = { mainnet: false, - deployerMnemonic: 'TO BE INSERTED AT TIME OF DEPLOYMENT', + deployerMnemonic: 'helmet gloom borrow nurse position child lion about grunt column habit forest', marsDenom: mars, atomDenom: atom, safetyFundAddr: safetyFundAddr, diff --git a/scripts/package.json b/scripts/package.json index a3eea427b..8641dd70f 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -10,6 +10,7 @@ "deploy:neutron-testnet": "yarn build && node build/deploy/neutron/testnet-index.js", "deploy:neutron-testnet-multisig": "yarn build && node build/deploy/neutron/testnet-multisig-index.js", "deploy:neutron-mainnet": "yarn build && node build/deploy/neutron/mainnet-index.js", + "test:v2.2.0-basic": "yarn build && node build/tests/v2_2_0_basic.js", "generate-types": "yarn rust-schema && tsc --project codegen-tsconfig.json && rm -rf types/generated && node build/codegen && node build/codegen/insertIgnores.js && yarn format", "rust-schema": "cd ../ && cargo make generate-all-schemas && cd scripts", "compile-wasm": "cd ../ && cargo make rust-optimizer && cd scripts", diff --git a/scripts/tests/v2_2_0_basic.ts b/scripts/tests/v2_2_0_basic.ts new file mode 100644 index 000000000..b94ad54c2 --- /dev/null +++ b/scripts/tests/v2_2_0_basic.ts @@ -0,0 +1,224 @@ +import path from 'path' +import { readFile } from 'fs/promises' +import { osmosisTestnetConfig } from '../deploy/osmosis/testnet-config' +import { DeploymentConfig } from '../types/config' +import { getWallet, getAddress, setupClient } from '../deploy/base/setup-deployer' +import { Storage } from '../deploy/base/storage' +import { printBlue, printGreen, printRed, printYellow } from '../utils/chalk' +import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate' +import { MarsCreditManagerQueryClient } from '../types/generated/mars-credit-manager/MarsCreditManager.client' +import { Rover } from '../deploy/base/test-actions-credit-manager' +import { Deployer as RedBankHarness } from '../deploy/base/test-actions-red-bank' + +const TARGET_VERSION = '2.2.0' +const ARTIFACT_FILE = 'mars_credit_manager.wasm' +const DEFAULT_LABEL = 'deployer-owner' + +type SupportedEnv = 'osmosis-testnet' + +const supportedConfigs: Record = { + 'osmosis-testnet': osmosisTestnetConfig, +} + +interface ScriptOptions { + label: string + config: DeploymentConfig +} + +const expectDefined = (value: T | undefined | null, message: string): T => { + if (value === undefined || value === null) { + throw new Error(message) + } + return value +} + +const resolveFromRepoRoot = (...segments: string[]) => + path.resolve(__dirname, '../../../', ...segments) + +const loadOptions = (): ScriptOptions => { + const env = (process.env.MARS_ENV ?? 'osmosis-testnet') as SupportedEnv + const baseConfig = supportedConfigs[env] + if (!baseConfig) { + throw new Error(`Unsupported MARS_ENV value: ${env}`) + } + + const mnemonic = process.env.MARS_MNEMONIC ?? baseConfig.deployerMnemonic + if (!mnemonic || mnemonic.includes('TO BE INSERTED')) { + throw new Error('Set MARS_MNEMONIC with the deployer mnemonic for this environment.') + } + + const label = process.env.MARS_LABEL ?? DEFAULT_LABEL + + return { + label, + config: { ...baseConfig, deployerMnemonic: mnemonic }, + } +} + +const uploadCreditManager = async ( + client: SigningCosmWasmClient, + deployer: string, + storage: Storage, +) => { + const wasmPath = resolveFromRepoRoot('artifacts', ARTIFACT_FILE) + const wasm = await readFile(wasmPath) + printBlue(`Uploading ${ARTIFACT_FILE} from ${wasmPath}`) + const { codeId, checksum } = await client.upload(deployer, wasm, 'auto') + storage.codeIds.creditManager = codeId + printGreen(`Uploaded credit manager wasm :: code id ${codeId} :: checksum ${checksum}`) + return codeId +} + +const migrateCreditManager = async ( + client: SigningCosmWasmClient, + deployer: string, + storage: Storage, + expectedVersion: string, +) => { + const creditManagerAddr = expectDefined( + storage.addresses.creditManager, + 'credit manager address missing from storage; deploy or record it first.', + ) + + const info: { contract: string; version: string } = await client.queryContractSmart( + creditManagerAddr, + { contract_version: {} }, + ) + + if (info.version === expectedVersion) { + printYellow(`Credit manager already at target version ${expectedVersion}, skipping migrate.`) + return + } + + printBlue( + `Migrating credit manager at ${creditManagerAddr} from version ${info.version} to ${expectedVersion}`, + ) + const codeId = await uploadCreditManager(client, deployer, storage) + await client.migrate(deployer, creditManagerAddr, codeId, {}, "auto") + + const after: { contract: string; version: string } = await client.queryContractSmart( + creditManagerAddr, + { contract_version: {} }, + ) + + if (after.version !== expectedVersion) { + throw new Error(`Migration failed: expected ${expectedVersion}, found ${after.version}`) + } + printGreen(`Migration complete :: contract ${after.contract} :: version ${after.version}`) +} + +const ensureSwapFeeInitialized = async ( + client: SigningCosmWasmClient, + storage: Storage, +) => { + const creditManagerAddr = expectDefined( + storage.addresses.creditManager, + 'credit manager address missing from storage.', + ) + const queryClient = new MarsCreditManagerQueryClient(client, creditManagerAddr) + const fee = await queryClient.swapFeeRate() + printGreen(`Swap fee rate query succeeded :: rate ${fee}`) +} + +const runCreditAccountFlow = async ( + client: SigningCosmWasmClient, + deployerAddr: string, + storage: Storage, + config: DeploymentConfig, +) => { + if (!config.testActions) { + throw new Error('testActions not configured for deployment; cannot run credit account flow.') + } + + const rover = new Rover(deployerAddr, storage, config, client, config.testActions) + await rover.createCreditAccount() + await rover.deposit() + await rover.swap() + await rover.withdraw() + await rover.refundAllBalances() + printGreen('Credit account flow complete') +} + +const distributeRewards = async ( + client: SigningCosmWasmClient, + deployerAddr: string, + storage: Storage, + config: DeploymentConfig, +) => { + const rewardsAddr = storage.addresses.rewardsCollector + if (!rewardsAddr) { + printYellow('Rewards collector address missing; skipping reward distribution checks.') + return + } + const contractAddr = rewardsAddr + + const denoms = new Set([ + config.rewardsCollector.safetyFundConfig.target_denom, + config.rewardsCollector.revenueShareConfig.target_denom, + config.rewardsCollector.feeCollectorConfig.target_denom, + ]) + + for (const denom of denoms) { + printBlue(`Triggering distribute_rewards for denom ${denom}`) + try { + await client.execute( + deployerAddr, + contractAddr, + { distribute_rewards: { denom } }, + 'auto', + ) + printGreen(`distribute_rewards executed for ${denom}`) + } catch (err) { + printRed(`distribute_rewards failed for ${denom}: ${String(err)}`) + throw err + } + } +} + +const runRewardsSwap = async ( + client: SigningCosmWasmClient, + deployerAddr: string, + storage: Storage, + config: DeploymentConfig, +) => { + const harness = new RedBankHarness(config, client, deployerAddr, storage) + await harness.executeRewardsSwap() +} + +const ensureGasBalance = async ( + client: SigningCosmWasmClient, + address: string, + denom: string, + minAmount: number, +) => { + const balance = await client.getBalance(address, denom) + if (Number(balance.amount) < minAmount) { + throw new Error(`Insufficient ${denom} balance (${balance.amount}) to run tests.`) + } +} + +const main = async () => { + const { config, label } = loadOptions() + const wallet = await getWallet(config.deployerMnemonic, config.chain.prefix) + const client = await setupClient(config, wallet) + const deployerAddr = await getAddress(wallet) + + printYellow(`Running tests with deployer ${deployerAddr} on ${config.chain.id}`) + + const storage = await Storage.load(config.chain.id, label) + + await ensureGasBalance(client, deployerAddr, config.chain.baseDenom, 100_000) + await migrateCreditManager(client, deployerAddr, storage, TARGET_VERSION) + await ensureSwapFeeInitialized(client, storage) + await runCreditAccountFlow(client, deployerAddr, storage, config) + await distributeRewards(client, deployerAddr, storage, config) + await runRewardsSwap(client, deployerAddr, storage, config) + + await storage.save() + printGreen('v2.2.0 basic functionality script completed successfully') +} + +void main().catch((err) => { + printRed(`v2.2.0 basic functionality script failed: ${String(err)}`) + process.exitCode = 1 +}) diff --git a/scripts/utils/environment.ts b/scripts/utils/environment.ts index 8baf38452..a2fe41ff1 100644 --- a/scripts/utils/environment.ts +++ b/scripts/utils/environment.ts @@ -1,12 +1,12 @@ -import os from 'os' +// import os from 'os' // for m1 macs, the binaries should look like: rover-aarch64.wasm vs rover.wasm export const wasmFile = (contractName: string) => { let fileStr = contractName - const env = os.arch() - if (env === 'arm64') { - fileStr += '-aarch64' - } + // const env = os.arch() + // if (env === 'arm64') { + // fileStr += '' + // } fileStr += '.wasm' return fileStr } From b33c55cc461247204cf117cba734c5216735adb8 Mon Sep 17 00:00:00 2001 From: mark Date: Wed, 12 Nov 2025 14:12:20 +0100 Subject: [PATCH 2/9] fix rc migration --- .../osmosis/src/migrations/v2_2_0.rs | 47 +++++++++++++- .../tests/tests/test_migration_v2_2_0.rs | 65 ++++++++++++++++++- 2 files changed, 108 insertions(+), 4 deletions(-) diff --git a/contracts/rewards-collector/osmosis/src/migrations/v2_2_0.rs b/contracts/rewards-collector/osmosis/src/migrations/v2_2_0.rs index fa8c2e5ff..1c6f116df 100644 --- a/contracts/rewards-collector/osmosis/src/migrations/v2_2_0.rs +++ b/contracts/rewards-collector/osmosis/src/migrations/v2_2_0.rs @@ -1,8 +1,31 @@ -use cosmwasm_std::{DepsMut, Response}; +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}; +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"; @@ -26,6 +49,26 @@ pub fn migrate(deps: DepsMut) -> Result { 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() 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 index d7bebab02..3e4db6d29 100644 --- 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 @@ -1,11 +1,34 @@ -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_rewards_collector_base::ContractError; -use mars_rewards_collector_osmosis::entry::migrate; +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(&[]); @@ -48,6 +71,26 @@ 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![]); @@ -63,4 +106,22 @@ fn successful_migration_from_2_1_1() { 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![], + } + ); } From 7ab915fbbf77a1dffde8b1666ee9fc89eebef463 Mon Sep 17 00:00:00 2001 From: mark Date: Mon, 17 Nov 2025 09:17:03 +0100 Subject: [PATCH 3/9] remove swap_fee from config --- contracts/account-nft/tests/tests/helpers/mock_env_builder.rs | 3 +-- contracts/credit-manager/src/query.rs | 3 +-- contracts/credit-manager/tests/tests/test_update_config.rs | 3 --- contracts/health/tests/tests/helpers/mock_env_builder.rs | 3 +-- packages/types/src/credit_manager/query.rs | 1 - 5 files changed, 3 insertions(+), 10 deletions(-) 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..ab8c7bc8b 100644 --- a/contracts/account-nft/tests/tests/helpers/mock_env_builder.rs +++ b/contracts/account-nft/tests/tests/helpers/mock_env_builder.rs @@ -152,8 +152,7 @@ impl MockEnvBuilder { swapper: "n/a".to_string(), zapper: "n/a".to_string(), health_contract: "n/a".to_string(), - rewards_collector: None, - swap_fee: Decimal::percent(1), + rewards_collector: None }, }, &[], diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index 17426e934..e063665e5 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -58,8 +58,7 @@ pub fn query_config(deps: Deps) -> ContractResult { swapper: SWAPPER.load(deps.storage)?.address().into(), 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)?, + rewards_collector: REWARDS_COLLECTOR.may_load(deps.storage)? }) } 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..8aa410158 100644 --- a/contracts/health/tests/tests/helpers/mock_env_builder.rs +++ b/contracts/health/tests/tests/helpers/mock_env_builder.rs @@ -136,8 +136,7 @@ impl MockEnvBuilder { swapper: "n/a".to_string(), zapper: "n/a".to_string(), health_contract: "n/a".to_string(), - rewards_collector: None, - swap_fee: Decimal::permille(1), + rewards_collector: None }, }, &[], 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] From 78aaa53e8842e66560d206fe7553be8dff39aa21 Mon Sep 17 00:00:00 2001 From: mark Date: Mon, 17 Nov 2025 15:09:18 +0100 Subject: [PATCH 4/9] update types --- .../mars-credit-manager/mars-credit-manager.json | 2 +- scripts/tests/v2_2_0_basic.ts | 14 +++----------- .../mars-credit-manager/MarsCreditManager.types.ts | 3 +++ 3 files changed, 7 insertions(+), 12 deletions(-) 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/tests/v2_2_0_basic.ts b/scripts/tests/v2_2_0_basic.ts index b94ad54c2..00c92fc74 100644 --- a/scripts/tests/v2_2_0_basic.ts +++ b/scripts/tests/v2_2_0_basic.ts @@ -94,7 +94,7 @@ const migrateCreditManager = async ( `Migrating credit manager at ${creditManagerAddr} from version ${info.version} to ${expectedVersion}`, ) const codeId = await uploadCreditManager(client, deployer, storage) - await client.migrate(deployer, creditManagerAddr, codeId, {}, "auto") + await client.migrate(deployer, creditManagerAddr, codeId, {}, 'auto') const after: { contract: string; version: string } = await client.queryContractSmart( creditManagerAddr, @@ -107,10 +107,7 @@ const migrateCreditManager = async ( printGreen(`Migration complete :: contract ${after.contract} :: version ${after.version}`) } -const ensureSwapFeeInitialized = async ( - client: SigningCosmWasmClient, - storage: Storage, -) => { +const ensureSwapFeeInitialized = async (client: SigningCosmWasmClient, storage: Storage) => { const creditManagerAddr = expectDefined( storage.addresses.creditManager, 'credit manager address missing from storage.', @@ -161,12 +158,7 @@ const distributeRewards = async ( for (const denom of denoms) { printBlue(`Triggering distribute_rewards for denom ${denom}`) try { - await client.execute( - deployerAddr, - contractAddr, - { distribute_rewards: { denom } }, - 'auto', - ) + await client.execute(deployerAddr, contractAddr, { distribute_rewards: { denom } }, 'auto') printGreen(`distribute_rewards executed for ${denom}`) } catch (err) { printRed(`distribute_rewards failed for ${denom}: ${String(err)}`) 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 From 839228b41d36231efc7f334a06b60bd9d52ad001 Mon Sep 17 00:00:00 2001 From: mark Date: Mon, 17 Nov 2025 15:23:06 +0100 Subject: [PATCH 5/9] clippy + fmt --- .../tests/tests/helpers/mock_env_builder.rs | 2 +- contracts/credit-manager/src/query.rs | 2 +- .../tests/tests/helpers/mock_env_builder.rs | 2 +- .../params/tests/tests/helpers/mock_env.rs | 179 +----------------- .../osmosis/src/migrations/v2_2_0.rs | 5 +- 5 files changed, 11 insertions(+), 179 deletions(-) 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 ab8c7bc8b..86aa7752e 100644 --- a/contracts/account-nft/tests/tests/helpers/mock_env_builder.rs +++ b/contracts/account-nft/tests/tests/helpers/mock_env_builder.rs @@ -152,7 +152,7 @@ impl MockEnvBuilder { swapper: "n/a".to_string(), zapper: "n/a".to_string(), health_contract: "n/a".to_string(), - rewards_collector: None + rewards_collector: None, }, }, &[], diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index e063665e5..09d353a57 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -58,7 +58,7 @@ pub fn query_config(deps: Deps) -> ContractResult { swapper: SWAPPER.load(deps.storage)?.address().into(), zapper: ZAPPER.load(deps.storage)?.address().into(), health_contract: HEALTH_CONTRACT.load(deps.storage)?.address().into(), - rewards_collector: REWARDS_COLLECTOR.may_load(deps.storage)? + rewards_collector: REWARDS_COLLECTOR.may_load(deps.storage)?, }) } diff --git a/contracts/health/tests/tests/helpers/mock_env_builder.rs b/contracts/health/tests/tests/helpers/mock_env_builder.rs index 8aa410158..3a0cac15c 100644 --- a/contracts/health/tests/tests/helpers/mock_env_builder.rs +++ b/contracts/health/tests/tests/helpers/mock_env_builder.rs @@ -136,7 +136,7 @@ impl MockEnvBuilder { swapper: "n/a".to_string(), zapper: "n/a".to_string(), health_contract: "n/a".to_string(), - rewards_collector: None + rewards_collector: None, }, }, &[], diff --git a/contracts/params/tests/tests/helpers/mock_env.rs b/contracts/params/tests/tests/helpers/mock_env.rs index cc9bd6218..d15c520b9 100644 --- a/contracts/params/tests/tests/helpers/mock_env.rs +++ b/contracts/params/tests/tests/helpers/mock_env.rs @@ -1,26 +1,13 @@ use std::{mem::take, str::FromStr}; use anyhow::Result as AnyResult; -use cosmwasm_std::{Addr, Decimal, Empty}; +use cosmwasm_std::{Addr, Decimal}; use cw_multi_test::{App, AppResponse, BasicApp, Executor}; use cw_paginate::PaginationResponse; use mars_owner::{OwnerResponse, OwnerUpdate}; -use mars_testing::{ - integration::mock_contracts::mock_rewards_collector_osmosis_contract, - multitest::helpers::{ - mock_address_provider_contract, mock_incentives_contract, mock_oracle_contract, - mock_red_bank_contract, - }, -}; -use mars_types::{ - address_provider::{self, MarsAddressType}, - incentives, oracle, - params::{ - AssetParams, AssetParamsUpdate, ConfigResponse, EmergencyUpdate, ExecuteMsg, - InstantiateMsg, QueryMsg, VaultConfig, VaultConfigUpdate, - }, - red_bank, - rewards_collector::{self, RewardConfig, TransferType}, +use mars_types::params::{ + AssetParams, AssetParamsUpdate, ConfigResponse, EmergencyUpdate, ExecuteMsg, InstantiateMsg, + QueryMsg, VaultConfig, VaultConfigUpdate, }; use super::contracts::mock_params_contract; @@ -260,164 +247,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/rewards-collector/osmosis/src/migrations/v2_2_0.rs b/contracts/rewards-collector/osmosis/src/migrations/v2_2_0.rs index 1c6f116df..51da486d3 100644 --- a/contracts/rewards-collector/osmosis/src/migrations/v2_2_0.rs +++ b/contracts/rewards-collector/osmosis/src/migrations/v2_2_0.rs @@ -3,7 +3,10 @@ use cw2::{assert_contract_version, get_contract_version, set_contract_version, V use mars_rewards_collector_base::ContractError; use mars_types::rewards_collector::Config; -use crate::{entry::{CONTRACT_NAME, CONTRACT_VERSION}, OsmosisCollector}; +use crate::{ + entry::{CONTRACT_NAME, CONTRACT_VERSION}, + OsmosisCollector, +}; mod previous_state { use cosmwasm_schema::cw_serde; From df6ac08c935d3d66033b781ed2a76d31ac9bc760 Mon Sep 17 00:00:00 2001 From: mark Date: Mon, 17 Nov 2025 15:45:24 +0100 Subject: [PATCH 6/9] tidy --- Makefile.toml | 2 +- checklist.md | 63 ------- scripts/deploy/osmosis/testnet-config.ts | 2 +- scripts/package.json | 1 - scripts/tests/v2_2_0_basic.ts | 216 ----------------------- scripts/utils/environment.ts | 6 - 6 files changed, 2 insertions(+), 288 deletions(-) delete mode 100644 checklist.md delete mode 100644 scripts/tests/v2_2_0_basic.ts diff --git a/Makefile.toml b/Makefile.toml index ca911bcb8..d9758408a 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -8,7 +8,7 @@ default_to_workspace = false [env] # Directory with wasm files used by integration tests (another directory can be used instead, for example 'artifacts' from rust-optimizer) -ARTIFACTS_DIR_PATH = "artifacts" +ARTIFACTS_DIR_PATH = "target/wasm32-unknown-unknown/release" # If you bump this version, verify RUST_VERSION correctness 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) diff --git a/checklist.md b/checklist.md deleted file mode 100644 index 6f01f4819..000000000 --- a/checklist.md +++ /dev/null @@ -1,63 +0,0 @@ -# Update Contracts - Cherry-pick from core-contracts - -## Overview -Pulling latest changes from core-contracts by cherry-picking specific commits. - -## Commits to Cherry-pick (in reverse chronological order) -- [x] `27e8a393232b3f4af2cb7fd89f32fb81dc025881` (oldest - start here) ✅ Conflicts resolved -- [x] `db65c00a347d13d81af1cf4b19776ab4b72eeb07` ✅ Conflicts resolved -- [x] `cb3ef67df47a7ef75d0c9fa1d0577bfc54c59e5d` ✅ Cherry-picked cleanly (2025-11-03) -- [ ] `b508ec06e5b90ed5200fecf4be06a95574c0f1c4` (newest - finish here) - - ⏭️ Cherry-pick skipped (files removed locally); needs manual decision - -## Progress Checklist -- [x] Check current git status and branch - - Current branch: `latest-core-contracts` - - Uncommitted changes in: Makefile.toml -- [x] Add core-contracts as remote (if not already present) - - ✅ Already configured: `git@github.com:mars-protocol/core-contracts.git` -- [x] Fetch latest changes from core-contracts - - ✅ Fetched successfully, found all target commits -- [ ] Cherry-pick commits in order - - ✅ First commit (27e8a393...) conflicts resolved - - ✅ Second commit (db65c00a...) conflicts resolved - - ✅ Third commit (cb3ef67d...) conflicts resolved - - ⚠️ Final commit (b508ec06...) skipped: neutron migration files absent in workspace -- [ ] Test and verify changes -- [ ] Document any conflicts or issues - -## Conflicts to Resolve - -**✅ Commit 27e8a393... (Add Spot trading fees) - RESOLVED** - -**✅ Commit db65c00a... (Add whitelist for rewards distributors) - RESOLVED** - -**✅ Commit cb3ef67d... (Add spot swap fee query) - RESOLVED** - -**Previous conflicts (resolved):** -- `contracts/account-nft/tests/tests/helpers/mock_env_builder.rs` -- `contracts/credit-manager/src/instantiate.rs` -- `contracts/credit-manager/src/query.rs` -- `contracts/credit-manager/src/state.rs` -- `contracts/credit-manager/src/swap.rs` -- `contracts/credit-manager/src/update_config.rs` -- `contracts/credit-manager/src/utils.rs` -- `contracts/credit-manager/tests/tests/test_update_config.rs` -- `contracts/health/tests/tests/helpers/mock_env_builder.rs` -- `packages/testing/src/multitest/helpers/mock_env.rs` -- `packages/types/src/credit_manager/instantiate.rs` -- `packages/types/src/credit_manager/query.rs` -- `scripts/deploy/base/deployer.ts` -- `scripts/deploy/neutron/devnet-config.ts` -- `scripts/deploy/neutron/mainnet-config.ts` -- `scripts/deploy/neutron/testnet-config.ts` -- `scripts/deploy/osmosis/mainnet-config.ts` -- `scripts/deploy/osmosis/testnet-config.ts` -- `scripts/types/config.ts` - -## Notes -- Started: 2025-10-01T14:59:29+08:00 -- ✅ First commit (27e8a393...) completed with conflicts resolved -- ✅ Second commit (db65c00a...) completed with conflicts resolved -- ✅ Third commit (cb3ef67d...) completed with conflicts resolved (2025-11-03) -- ⚠️ Cherry-pick b508ec06... skipped (neutron migrations deleted upstream here) diff --git a/scripts/deploy/osmosis/testnet-config.ts b/scripts/deploy/osmosis/testnet-config.ts index 6f19b6215..9391a4921 100644 --- a/scripts/deploy/osmosis/testnet-config.ts +++ b/scripts/deploy/osmosis/testnet-config.ts @@ -236,7 +236,7 @@ const testActions = { export const osmosisTestnetConfig: DeploymentConfig = { mainnet: false, - deployerMnemonic: 'helmet gloom borrow nurse position child lion about grunt column habit forest', + deployerMnemonic: 'TO BE INSERTED AT TIME OF DEPLOYMENT', marsDenom: mars, atomDenom: atom, safetyFundAddr: safetyFundAddr, diff --git a/scripts/package.json b/scripts/package.json index 8641dd70f..a3eea427b 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -10,7 +10,6 @@ "deploy:neutron-testnet": "yarn build && node build/deploy/neutron/testnet-index.js", "deploy:neutron-testnet-multisig": "yarn build && node build/deploy/neutron/testnet-multisig-index.js", "deploy:neutron-mainnet": "yarn build && node build/deploy/neutron/mainnet-index.js", - "test:v2.2.0-basic": "yarn build && node build/tests/v2_2_0_basic.js", "generate-types": "yarn rust-schema && tsc --project codegen-tsconfig.json && rm -rf types/generated && node build/codegen && node build/codegen/insertIgnores.js && yarn format", "rust-schema": "cd ../ && cargo make generate-all-schemas && cd scripts", "compile-wasm": "cd ../ && cargo make rust-optimizer && cd scripts", diff --git a/scripts/tests/v2_2_0_basic.ts b/scripts/tests/v2_2_0_basic.ts deleted file mode 100644 index 00c92fc74..000000000 --- a/scripts/tests/v2_2_0_basic.ts +++ /dev/null @@ -1,216 +0,0 @@ -import path from 'path' -import { readFile } from 'fs/promises' -import { osmosisTestnetConfig } from '../deploy/osmosis/testnet-config' -import { DeploymentConfig } from '../types/config' -import { getWallet, getAddress, setupClient } from '../deploy/base/setup-deployer' -import { Storage } from '../deploy/base/storage' -import { printBlue, printGreen, printRed, printYellow } from '../utils/chalk' -import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate' -import { MarsCreditManagerQueryClient } from '../types/generated/mars-credit-manager/MarsCreditManager.client' -import { Rover } from '../deploy/base/test-actions-credit-manager' -import { Deployer as RedBankHarness } from '../deploy/base/test-actions-red-bank' - -const TARGET_VERSION = '2.2.0' -const ARTIFACT_FILE = 'mars_credit_manager.wasm' -const DEFAULT_LABEL = 'deployer-owner' - -type SupportedEnv = 'osmosis-testnet' - -const supportedConfigs: Record = { - 'osmosis-testnet': osmosisTestnetConfig, -} - -interface ScriptOptions { - label: string - config: DeploymentConfig -} - -const expectDefined = (value: T | undefined | null, message: string): T => { - if (value === undefined || value === null) { - throw new Error(message) - } - return value -} - -const resolveFromRepoRoot = (...segments: string[]) => - path.resolve(__dirname, '../../../', ...segments) - -const loadOptions = (): ScriptOptions => { - const env = (process.env.MARS_ENV ?? 'osmosis-testnet') as SupportedEnv - const baseConfig = supportedConfigs[env] - if (!baseConfig) { - throw new Error(`Unsupported MARS_ENV value: ${env}`) - } - - const mnemonic = process.env.MARS_MNEMONIC ?? baseConfig.deployerMnemonic - if (!mnemonic || mnemonic.includes('TO BE INSERTED')) { - throw new Error('Set MARS_MNEMONIC with the deployer mnemonic for this environment.') - } - - const label = process.env.MARS_LABEL ?? DEFAULT_LABEL - - return { - label, - config: { ...baseConfig, deployerMnemonic: mnemonic }, - } -} - -const uploadCreditManager = async ( - client: SigningCosmWasmClient, - deployer: string, - storage: Storage, -) => { - const wasmPath = resolveFromRepoRoot('artifacts', ARTIFACT_FILE) - const wasm = await readFile(wasmPath) - printBlue(`Uploading ${ARTIFACT_FILE} from ${wasmPath}`) - const { codeId, checksum } = await client.upload(deployer, wasm, 'auto') - storage.codeIds.creditManager = codeId - printGreen(`Uploaded credit manager wasm :: code id ${codeId} :: checksum ${checksum}`) - return codeId -} - -const migrateCreditManager = async ( - client: SigningCosmWasmClient, - deployer: string, - storage: Storage, - expectedVersion: string, -) => { - const creditManagerAddr = expectDefined( - storage.addresses.creditManager, - 'credit manager address missing from storage; deploy or record it first.', - ) - - const info: { contract: string; version: string } = await client.queryContractSmart( - creditManagerAddr, - { contract_version: {} }, - ) - - if (info.version === expectedVersion) { - printYellow(`Credit manager already at target version ${expectedVersion}, skipping migrate.`) - return - } - - printBlue( - `Migrating credit manager at ${creditManagerAddr} from version ${info.version} to ${expectedVersion}`, - ) - const codeId = await uploadCreditManager(client, deployer, storage) - await client.migrate(deployer, creditManagerAddr, codeId, {}, 'auto') - - const after: { contract: string; version: string } = await client.queryContractSmart( - creditManagerAddr, - { contract_version: {} }, - ) - - if (after.version !== expectedVersion) { - throw new Error(`Migration failed: expected ${expectedVersion}, found ${after.version}`) - } - printGreen(`Migration complete :: contract ${after.contract} :: version ${after.version}`) -} - -const ensureSwapFeeInitialized = async (client: SigningCosmWasmClient, storage: Storage) => { - const creditManagerAddr = expectDefined( - storage.addresses.creditManager, - 'credit manager address missing from storage.', - ) - const queryClient = new MarsCreditManagerQueryClient(client, creditManagerAddr) - const fee = await queryClient.swapFeeRate() - printGreen(`Swap fee rate query succeeded :: rate ${fee}`) -} - -const runCreditAccountFlow = async ( - client: SigningCosmWasmClient, - deployerAddr: string, - storage: Storage, - config: DeploymentConfig, -) => { - if (!config.testActions) { - throw new Error('testActions not configured for deployment; cannot run credit account flow.') - } - - const rover = new Rover(deployerAddr, storage, config, client, config.testActions) - await rover.createCreditAccount() - await rover.deposit() - await rover.swap() - await rover.withdraw() - await rover.refundAllBalances() - printGreen('Credit account flow complete') -} - -const distributeRewards = async ( - client: SigningCosmWasmClient, - deployerAddr: string, - storage: Storage, - config: DeploymentConfig, -) => { - const rewardsAddr = storage.addresses.rewardsCollector - if (!rewardsAddr) { - printYellow('Rewards collector address missing; skipping reward distribution checks.') - return - } - const contractAddr = rewardsAddr - - const denoms = new Set([ - config.rewardsCollector.safetyFundConfig.target_denom, - config.rewardsCollector.revenueShareConfig.target_denom, - config.rewardsCollector.feeCollectorConfig.target_denom, - ]) - - for (const denom of denoms) { - printBlue(`Triggering distribute_rewards for denom ${denom}`) - try { - await client.execute(deployerAddr, contractAddr, { distribute_rewards: { denom } }, 'auto') - printGreen(`distribute_rewards executed for ${denom}`) - } catch (err) { - printRed(`distribute_rewards failed for ${denom}: ${String(err)}`) - throw err - } - } -} - -const runRewardsSwap = async ( - client: SigningCosmWasmClient, - deployerAddr: string, - storage: Storage, - config: DeploymentConfig, -) => { - const harness = new RedBankHarness(config, client, deployerAddr, storage) - await harness.executeRewardsSwap() -} - -const ensureGasBalance = async ( - client: SigningCosmWasmClient, - address: string, - denom: string, - minAmount: number, -) => { - const balance = await client.getBalance(address, denom) - if (Number(balance.amount) < minAmount) { - throw new Error(`Insufficient ${denom} balance (${balance.amount}) to run tests.`) - } -} - -const main = async () => { - const { config, label } = loadOptions() - const wallet = await getWallet(config.deployerMnemonic, config.chain.prefix) - const client = await setupClient(config, wallet) - const deployerAddr = await getAddress(wallet) - - printYellow(`Running tests with deployer ${deployerAddr} on ${config.chain.id}`) - - const storage = await Storage.load(config.chain.id, label) - - await ensureGasBalance(client, deployerAddr, config.chain.baseDenom, 100_000) - await migrateCreditManager(client, deployerAddr, storage, TARGET_VERSION) - await ensureSwapFeeInitialized(client, storage) - await runCreditAccountFlow(client, deployerAddr, storage, config) - await distributeRewards(client, deployerAddr, storage, config) - await runRewardsSwap(client, deployerAddr, storage, config) - - await storage.save() - printGreen('v2.2.0 basic functionality script completed successfully') -} - -void main().catch((err) => { - printRed(`v2.2.0 basic functionality script failed: ${String(err)}`) - process.exitCode = 1 -}) diff --git a/scripts/utils/environment.ts b/scripts/utils/environment.ts index a2fe41ff1..56a225ced 100644 --- a/scripts/utils/environment.ts +++ b/scripts/utils/environment.ts @@ -1,12 +1,6 @@ -// import os from 'os' -// for m1 macs, the binaries should look like: rover-aarch64.wasm vs rover.wasm export const wasmFile = (contractName: string) => { let fileStr = contractName - // const env = os.arch() - // if (env === 'arm64') { - // fileStr += '' - // } fileStr += '.wasm' return fileStr } From 854fe095e656afb802cb7bf44b12ca4d1b151a83 Mon Sep 17 00:00:00 2001 From: mark Date: Mon, 17 Nov 2025 16:51:13 +0100 Subject: [PATCH 7/9] undo environment.ts file changes --- scripts/utils/environment.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/utils/environment.ts b/scripts/utils/environment.ts index 56a225ced..8baf38452 100644 --- a/scripts/utils/environment.ts +++ b/scripts/utils/environment.ts @@ -1,6 +1,12 @@ +import os from 'os' +// for m1 macs, the binaries should look like: rover-aarch64.wasm vs rover.wasm export const wasmFile = (contractName: string) => { let fileStr = contractName + const env = os.arch() + if (env === 'arm64') { + fileStr += '-aarch64' + } fileStr += '.wasm' return fileStr } From 8c4c4989510ea4e6b3be775880afa66d68e51cdf Mon Sep 17 00:00:00 2001 From: mark Date: Wed, 19 Nov 2025 01:53:06 +0300 Subject: [PATCH 8/9] fix build --- Makefile.toml | 2 +- coverage_grcov.Makefile.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/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] From 683252f91254834d4e1a3e3818243d8a71c05355 Mon Sep 17 00:00:00 2001 From: mark Date: Wed, 19 Nov 2025 05:22:12 +0300 Subject: [PATCH 9/9] clippy --- contracts/account-nft/tests/tests/helpers/mock_env.rs | 1 + contracts/credit-manager/src/liquidate.rs | 1 + contracts/incentives/src/helpers.rs | 2 +- contracts/params/tests/tests/helpers/mock_env.rs | 4 ---- contracts/red-bank/src/interest_rates.rs | 1 + 5 files changed, 4 insertions(+), 5 deletions(-) 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/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/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 d15c520b9..c85186f91 100644 --- a/contracts/params/tests/tests/helpers/mock_env.rs +++ b/contracts/params/tests/tests/helpers/mock_env.rs @@ -19,10 +19,8 @@ pub struct MockEnv { pub struct MockEnvBuilder { pub app: BasicApp, - pub deployer: Addr, pub target_health_factor: Option, pub emergency_owner: Option, - pub address_provider: Option, } #[allow(clippy::new_ret_no_self)] @@ -30,10 +28,8 @@ impl MockEnv { pub fn new() -> MockEnvBuilder { MockEnvBuilder { app: App::default(), - deployer: Addr::unchecked("deployer"), target_health_factor: None, emergency_owner: None, - address_provider: None, } } 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