diff --git a/.cargo/audit.toml b/.cargo/audit.toml index 4c505faee..ffaf6c841 100644 --- a/.cargo/audit.toml +++ b/.cargo/audit.toml @@ -2,6 +2,6 @@ [advisories] # Ignore the following advisory IDs. -# Reported vulnerabilities relate to test-tube which is only used for testing. +# Reported vulnerabilities relate to test-tube and cw-it which is only used for testing. # RUSTSEC-2024-0344 - no newer dependency fixing the issue in cosmwasm -ignore = ["RUSTSEC-2024-0003", "RUSTSEC-2024-0006", "RUSTSEC-2024-0019", "RUSTSEC-2024-0332", "RUSTSEC-2024-0336", "RUSTSEC-2024-0344", "RUSTSEC-2024-0421"] \ No newline at end of file +ignore = ["RUSTSEC-2024-0003", "RUSTSEC-2024-0006", "RUSTSEC-2024-0019", "RUSTSEC-2024-0332", "RUSTSEC-2024-0336", "RUSTSEC-2024-0344", "RUSTSEC-2024-0421", "RUSTSEC-2024-0437", "RUSTSEC-2025-0009"] \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index db4956a89..4af1e947f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2131,7 +2131,7 @@ dependencies = [ [[package]] name = "mars-address-provider" -version = "2.1.0" +version = "2.1.1" dependencies = [ "bech32 0.11.0", "cosmwasm-schema", @@ -2518,7 +2518,9 @@ dependencies = [ name = "mars-rewards-collector-neutron" version = "2.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", + "cw-storage-plus 1.2.0", "cw2 1.1.2", "mars-rewards-collector-base", "mars-testing", @@ -2528,7 +2530,7 @@ dependencies = [ [[package]] name = "mars-rewards-collector-osmosis" -version = "2.1.0" +version = "2.1.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -2542,6 +2544,7 @@ dependencies = [ "mars-utils 2.1.0", "osmosis-std 0.22.0", "serde", + "test-case", ] [[package]] diff --git a/contracts/address-provider/Cargo.toml b/contracts/address-provider/Cargo.toml index 49bd950f3..0aac4c37e 100644 --- a/contracts/address-provider/Cargo.toml +++ b/contracts/address-provider/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "mars-address-provider" description = "A smart contract that holds addresses of Mars Red Bank contracts" -version = { workspace = true } +version = "2.1.1" authors = { workspace = true } edition = { workspace = true } license = { workspace = true } diff --git a/contracts/address-provider/src/contract.rs b/contracts/address-provider/src/contract.rs index 6c2334ac2..59b79caff 100644 --- a/contracts/address-provider/src/contract.rs +++ b/contracts/address-provider/src/contract.rs @@ -171,5 +171,5 @@ fn query_all_addresses( #[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_1_1::migrate(deps) } diff --git a/contracts/address-provider/src/migrations/mod.rs b/contracts/address-provider/src/migrations/mod.rs index 78e0d72f0..dfd90f0b6 100644 --- a/contracts/address-provider/src/migrations/mod.rs +++ b/contracts/address-provider/src/migrations/mod.rs @@ -1 +1,2 @@ pub mod v2_1_0; +pub mod v2_1_1; diff --git a/contracts/address-provider/src/migrations/v2_1_1.rs b/contracts/address-provider/src/migrations/v2_1_1.rs new file mode 100644 index 000000000..9ce2758a9 --- /dev/null +++ b/contracts/address-provider/src/migrations/v2_1_1.rs @@ -0,0 +1,22 @@ +use cosmwasm_std::{DepsMut, Response}; +use cw2::{assert_contract_version, set_contract_version}; + +use crate::{ + contract::{CONTRACT_NAME, CONTRACT_VERSION}, + error::ContractError, +}; + +const FROM_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)?; + + // add new address + 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/address-provider/tests/tests/test_migration_v2.rs b/contracts/address-provider/tests/tests/test_migration_v2.rs index b70f16e45..7d7e0ce84 100644 --- a/contracts/address-provider/tests/tests/test_migration_v2.rs +++ b/contracts/address-provider/tests/tests/test_migration_v2.rs @@ -6,7 +6,7 @@ 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.0").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(); @@ -30,7 +30,7 @@ fn wrong_contract_version() { assert_eq!( err, ContractError::Version(VersionError::WrongVersion { - expected: "2.0.0".to_string(), + expected: "2.1.0".to_string(), found: "4.1.0".to_string() }) ); @@ -39,7 +39,7 @@ fn wrong_contract_version() { #[test] fn successful_migration() { let mut deps = mock_dependencies(&[]); - cw2::set_contract_version(deps.as_mut().storage, "crates.io:mars-address-provider", "2.0.0") + cw2::set_contract_version(deps.as_mut().storage, "crates.io:mars-address-provider", "2.1.0") .unwrap(); let res = migrate(deps.as_mut(), mock_env(), Empty {}).unwrap(); @@ -49,12 +49,12 @@ fn successful_migration() { assert!(res.data.is_none()); assert_eq!( res.attributes, - vec![attr("action", "migrate"), attr("from_version", "2.0.0"), attr("to_version", "2.1.0")] + 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-address-provider".to_string(), - version: "2.1.0".to_string(), + version: "2.1.1".to_string(), }; assert_eq!(cw2::get_contract_version(deps.as_ref().storage).unwrap(), new_contract_version); } diff --git a/contracts/rewards-collector/base/src/contract.rs b/contracts/rewards-collector/base/src/contract.rs index 8d158fcfd..9845be597 100644 --- a/contracts/rewards-collector/base/src/contract.rs +++ b/contracts/rewards-collector/base/src/contract.rs @@ -1,13 +1,15 @@ use cosmwasm_std::{ - coin, to_json_binary, Addr, Binary, Coin, CosmosMsg, CustomMsg, Deps, DepsMut, Empty, Env, - MessageInfo, Response, StdResult, Uint128, WasmMsg, + coin, to_json_binary, Addr, Binary, Coin, CosmosMsg, CustomMsg, Decimal, Deps, DepsMut, Empty, + Env, MessageInfo, Response, StdResult, Uint128, WasmMsg, }; use cw_storage_plus::Item; use mars_owner::{Owner, OwnerInit::SetInitialOwner, OwnerUpdate}; use mars_types::{ address_provider::{self, AddressResponseItem, MarsAddressType}, credit_manager::{self, Action}, - incentives, red_bank, + incentives, + oracle::ActionKind, + red_bank, rewards_collector::{ Config, ConfigResponse, ExecuteMsg, InstantiateMsg, QueryMsg, UpdateConfig, }, @@ -17,9 +19,10 @@ use mars_utils::helpers::option_string_to_addr; use crate::{ helpers::{stringify_option_amount, unwrap_option_amount}, - ContractError, ContractResult, IbcTransferMsg, + ContractError, ContractResult, TransferMsg, }; -pub struct Collector<'a, M: CustomMsg, I: IbcTransferMsg> { + +pub struct Collector<'a, M: CustomMsg, I: TransferMsg> { /// Contract's owner pub owner: Owner<'a>, /// The contract's configurations @@ -30,7 +33,7 @@ pub struct Collector<'a, M: CustomMsg, I: IbcTransferMsg> { pub ibc_transfer_msg: std::marker::PhantomData, } -impl<'a, M: CustomMsg, I: IbcTransferMsg> Default for Collector<'a, M, I> { +impl<'a, M: CustomMsg, I: TransferMsg> Default for Collector<'a, M, I> { fn default() -> Self { Self { owner: Owner::new("owner"), @@ -44,7 +47,7 @@ impl<'a, M: CustomMsg, I: IbcTransferMsg> Default for Collector<'a, M, I> { impl<'a, M, I> Collector<'a, M, I> where M: CustomMsg, - I: IbcTransferMsg, + I: TransferMsg, { pub fn instantiate( &self, @@ -93,8 +96,7 @@ where } => self.withdraw_from_credit_manager(deps, account_id, actions), ExecuteMsg::DistributeRewards { denom, - amount, - } => self.distribute_rewards(deps, env, denom, amount), + } => self.distribute_rewards(deps, &env, &denom), ExecuteMsg::SwapAsset { denom, amount, @@ -105,7 +107,7 @@ where } => self.swap_asset( deps, env, - denom, + &denom, amount, safety_fund_route, fee_collector_route, @@ -153,26 +155,25 @@ where let UpdateConfig { address_provider, safety_tax_rate, - safety_fund_denom, - fee_collector_denom, + revenue_share_tax_rate, + safety_fund_config, + revenue_share_config, + fee_collector_config, channel_id, timeout_seconds, slippage_tolerance, - neutron_ibc_config, } = new_cfg; cfg.address_provider = option_string_to_addr(deps.api, address_provider, cfg.address_provider)?; cfg.safety_tax_rate = safety_tax_rate.unwrap_or(cfg.safety_tax_rate); - cfg.safety_fund_denom = safety_fund_denom.unwrap_or(cfg.safety_fund_denom); - cfg.fee_collector_denom = fee_collector_denom.unwrap_or(cfg.fee_collector_denom); + cfg.revenue_share_tax_rate = revenue_share_tax_rate.unwrap_or(cfg.revenue_share_tax_rate); + cfg.safety_fund_config = safety_fund_config.unwrap_or(cfg.safety_fund_config); + cfg.revenue_share_config = revenue_share_config.unwrap_or(cfg.revenue_share_config); + cfg.fee_collector_config = fee_collector_config.unwrap_or(cfg.fee_collector_config); cfg.channel_id = channel_id.unwrap_or(cfg.channel_id); cfg.timeout_seconds = timeout_seconds.unwrap_or(cfg.timeout_seconds); cfg.slippage_tolerance = slippage_tolerance.unwrap_or(cfg.slippage_tolerance); - if neutron_ibc_config.is_some() { - // override current config, otherwise leave previous one - cfg.neutron_ibc_config = neutron_ibc_config; - } cfg.validate()?; @@ -282,11 +283,12 @@ where .add_attribute("action", "claim_incentive_rewards")) } + #[allow(clippy::too_many_arguments)] pub fn swap_asset( &self, deps: DepsMut, env: Env, - denom: String, + denom: &str, amount: Option, safety_fund_route: Option, fee_collector_route: Option, @@ -295,113 +297,305 @@ where ) -> ContractResult> { let cfg = self.config.load(deps.storage)?; - let swapper_addr = deps - .querier - .query_wasm_smart::( - cfg.address_provider, - &mars_types::address_provider::QueryMsg::Address(MarsAddressType::Swapper), - )? - .address; - // if amount is None, swap the total balance let amount_to_swap = - unwrap_option_amount(&deps.querier, &env.contract.address, &denom, amount)?; + unwrap_option_amount(&deps.querier, &env.contract.address, denom, amount)?; + + // split the amount to swap between the safety fund, fee collector and the revenue share + // we combine revenue fund and safety fund because they are the same denom + let rf_and_sf_combined = amount_to_swap + .checked_mul_floor(cfg.safety_tax_rate.checked_add(cfg.revenue_share_tax_rate)?)?; + let fc_amount = amount_to_swap.checked_sub(rf_and_sf_combined)?; - // split the amount to swap between the safety fund and the fee collector - let amount_safety_fund = amount_to_swap * cfg.safety_tax_rate; - let amount_fee_collector = amount_to_swap.checked_sub(amount_safety_fund)?; let mut messages = vec![]; + let addresses = &deps.querier.query_wasm_smart::>( + cfg.address_provider, + &address_provider::QueryMsg::Addresses(vec![ + MarsAddressType::Swapper, + MarsAddressType::Oracle, + ]), + )?; + + let swapper_addr = &addresses[0].address; + let oracle_addr = &addresses[1].address; + + let asset_in_price = deps + .querier + .query_wasm_smart::( + oracle_addr.to_string(), + &mars_types::oracle::QueryMsg::Price { + denom: denom.to_string(), + kind: Some(ActionKind::Default), + }, + )? + .price; + + // apply slippage to asset in price. Creating this variable means we only need to apply + // slippage tolerance calculation once, instead of for each denom + let slippage_adjusted_asset_in_price = + asset_in_price.checked_mul(Decimal::one().checked_sub(cfg.slippage_tolerance)?)?; // execute the swap to safety fund denom, if the amount to swap is non-zero, // and if the denom is not already the safety fund denom - if !amount_safety_fund.is_zero() && denom != cfg.safety_fund_denom { - let coin_in_safety_fund = coin(amount_safety_fund.u128(), denom.clone()); - messages.push(WasmMsg::Execute { - contract_addr: swapper_addr.clone(), - msg: to_json_binary( - &mars_types::swapper::ExecuteMsg::::SwapExactIn { - coin_in: coin_in_safety_fund.clone(), - denom_out: cfg.safety_fund_denom, - min_receive: safety_fund_min_receive.ok_or(ContractError::InvalidMinReceive {reason: "required to pass 'safety_fund_min_receive' when swapped to safety fund denom".to_string()})?, - route: safety_fund_route, - }, + // Note that revenue share is included in this swap as they are the same denom + if !rf_and_sf_combined.is_zero() && denom != cfg.safety_fund_config.target_denom { + let swap_msg = self.swap_asset_to_reward( + &deps, + oracle_addr, + &denom.to_string(), + rf_and_sf_combined, + slippage_adjusted_asset_in_price, + safety_fund_min_receive.ok_or( + ContractError::InvalidMinReceive { + reason: "required to pass 'safety_fund_min_receive' when swapping safety fund amount".to_string() + } )?, - funds: vec![coin_in_safety_fund], - }); + &cfg.safety_fund_config.target_denom, + safety_fund_route, + swapper_addr, + )?; + + messages.push(swap_msg); } // execute the swap to fee collector denom, if the amount to swap is non-zero, // and if the denom is not already the fee collector denom - if !amount_fee_collector.is_zero() && denom != cfg.fee_collector_denom { - let coin_in_fee_collector = coin(amount_fee_collector.u128(), denom.clone()); - messages.push(WasmMsg::Execute { - contract_addr: swapper_addr, - msg: to_json_binary( - &mars_types::swapper::ExecuteMsg::::SwapExactIn { - coin_in: coin_in_fee_collector.clone(), - denom_out: cfg.fee_collector_denom, - min_receive: fee_collector_min_receive.ok_or(ContractError::InvalidMinReceive {reason: "required to pass 'fee_collector_min_receive' when swapped to fee collector denom".to_string()})?, - route: fee_collector_route, - }, + if !fc_amount.is_zero() && denom != cfg.fee_collector_config.target_denom { + let swap_msg = self.swap_asset_to_reward( + &deps, + oracle_addr, + &denom.to_string(), + fc_amount, + slippage_adjusted_asset_in_price, + fee_collector_min_receive.ok_or( + ContractError::InvalidMinReceive { + reason: "required to pass 'fee_collector_min_receive' when swapping to fee collector".to_string() + } )?, - funds: vec![coin_in_fee_collector], - }); + &cfg.fee_collector_config.target_denom, + fee_collector_route, + swapper_addr, + )?; + + messages.push(swap_msg); } Ok(Response::new() .add_messages(messages) .add_attribute("action", "swap_asset") .add_attribute("denom", denom) - .add_attribute("amount_safety_fund", amount_safety_fund) - .add_attribute("amount_fee_collector", amount_fee_collector)) + .add_attribute("amount_safety_fund", rf_and_sf_combined) + .add_attribute("amount_fee_collector", fc_amount)) + } + + fn swap_asset_to_reward( + &self, + deps: &DepsMut, + oracle_addr: &str, + asset_in_denom: &String, + asset_in_amount: Uint128, + slippage_adjusted_asset_in_price: Decimal, + min_receive: Uint128, + target_reward_denom: &String, + target_route: Option, + swapper_addr: &str, + ) -> Result { + let target_fund_price = deps + .querier + .query_wasm_smart::( + oracle_addr.to_string(), + &mars_types::oracle::QueryMsg::Price { + denom: target_reward_denom.to_string(), + kind: Some(ActionKind::Default), + }, + )? + .price; + + self.ensure_min_receive_within_slippage_tolerance( + asset_in_denom.to_string(), + target_reward_denom.to_string(), + asset_in_amount, + slippage_adjusted_asset_in_price, + target_fund_price, + min_receive, + )?; + + self.generate_swap_msg( + swapper_addr, + asset_in_denom, + asset_in_amount, + target_reward_denom, + min_receive, + target_route, + ) + } + + fn generate_swap_msg( + &self, + swapper_addr: &str, + denom_in: &str, + amount_in: Uint128, + denom_out: &str, + min_receive: Uint128, + route: Option, + ) -> Result { + Ok(WasmMsg::Execute { + contract_addr: swapper_addr.to_string(), + msg: to_json_binary(&mars_types::swapper::ExecuteMsg::::SwapExactIn { + coin_in: coin(amount_in.u128(), denom_in), + denom_out: denom_out.to_string(), + min_receive, + route, + })?, + funds: vec![coin(amount_in.u128(), denom_in)], + }) + } + + /// Ensure the slippage is not greater than what is tolerated in contract config + /// We do this by calculating the minimum_price and applying that to the min receive + /// Calculation is as follows: + /// Safety_denom price in oracle is 2 + /// slippage_adjusted_asset_in_price (calculated via oracle) is 9.5 + /// pair price = 9.5 / 2 = 4.75 + /// minimum_tolerated = 4.75 * amount_in + fn ensure_min_receive_within_slippage_tolerance( + &self, + asset_in_denom: String, + asset_out_denom: String, + amount_in: Uint128, + slippage_adjusted_asset_in_price: Decimal, + asset_out_price: Decimal, + min_receive: Uint128, + ) -> Result<(), ContractError> { + // The price of the asset to be swapped, denominated in the output asset denom + let asset_out_denominated_price = + slippage_adjusted_asset_in_price.checked_div(asset_out_price)?; + let min_receive_lower_limit = amount_in.checked_mul_floor(asset_out_denominated_price)?; + + if min_receive_lower_limit > min_receive { + return Err(ContractError::SlippageLimitExceeded { + denom_in: asset_in_denom, + denom_out: asset_out_denom, + min_receive_minimum: min_receive_lower_limit, + min_receive_given: min_receive, + }); + } + + Ok(()) } pub fn distribute_rewards( &self, deps: DepsMut, - env: Env, - denom: String, - amount: Option, + env: &Env, + denom: &str, ) -> ContractResult> { - let cfg = self.config.load(deps.storage)?; + let mut res = Response::new().add_attribute("action", "distribute_rewards"); + let mut msgs: Vec> = vec![]; + + // Configs + let cfg = &self.config.load(deps.storage)?; + let safety_fund_config = &cfg.safety_fund_config; + let revenue_share_config = &cfg.revenue_share_config; + let fee_collector_config = &cfg.fee_collector_config; + + // Get specified denom balance + let balance = deps.querier.query_balance(env.contract.address.as_str(), denom)?; + if balance.amount == Uint128::zero() { + return Ok(res.add_attribute("denom", denom).add_attribute("amount", "zero")); + } - let to_address = if denom == cfg.safety_fund_denom { - address_provider::helpers::query_module_addr( + if denom == safety_fund_config.target_denom { + // When distributing to the safety fund we need to split by safety fund and revenue share, + // as we enforce that they have the same denom in the configuration + let sf_proportion = if cfg.revenue_share_tax_rate.is_zero() { + Decimal::one() + } else { + cfg.safety_tax_rate + .checked_div(cfg.safety_tax_rate.checked_add(cfg.revenue_share_tax_rate)?)? + }; + + // Amounts to send + let sf_amount = balance.amount.checked_mul_floor(sf_proportion)?; + let rs_amount = balance.amount.checked_sub(sf_amount)?; + + // Fetch our target addresses for distribution + let contracts = vec![MarsAddressType::SafetyFund, MarsAddressType::RevenueShare]; + let addresses = address_provider::helpers::query_contract_addrs( deps.as_ref(), &cfg.address_provider, - MarsAddressType::SafetyFund, - )? - } else if denom == cfg.fee_collector_denom { - address_provider::helpers::query_module_addr( + contracts, + )?; + let sf_address = &addresses[&MarsAddressType::SafetyFund]; + let rs_address = &addresses[&MarsAddressType::RevenueShare]; + + // Generate distribute msg + let sf_distribute_msg = I::transfer_msg( + env, + sf_address.as_str(), + Coin { + denom: denom.to_string(), + amount: sf_amount, + }, + cfg, + &safety_fund_config.transfer_type, + )?; + msgs.push(sf_distribute_msg); + + res = res + .add_attribute("address_type", MarsAddressType::SafetyFund.to_string()) + .add_attribute("to", sf_address) + .add_attribute("amount", sf_amount); + + // if the revenue share amount is non-zero, we need to send that portion also + if !rs_amount.is_zero() { + let revenue_share_distribute_msg = I::transfer_msg( + env, + rs_address.as_str(), + Coin { + denom: denom.to_string(), + amount: rs_amount, + }, + cfg, + &revenue_share_config.transfer_type, + )?; + + msgs.push(revenue_share_distribute_msg); + res = res + .add_attribute("address_type", MarsAddressType::RevenueShare.to_string()) + .add_attribute("to", rs_address) + .add_attribute("amount", rs_amount); + } + } else if denom == fee_collector_config.target_denom { + let fee_collector_address = address_provider::helpers::query_contract_addr( deps.as_ref(), &cfg.address_provider, MarsAddressType::FeeCollector, - )? + )?; + let fee_collector_distribute_msg = I::transfer_msg( + env, + fee_collector_address.as_str(), + Coin { + denom: denom.to_string(), + amount: balance.amount, + }, + cfg, + &fee_collector_config.transfer_type, + )?; + + msgs.push(fee_collector_distribute_msg); + + res = res + .add_attribute("address_type", MarsAddressType::FeeCollector.to_string()) + .add_attribute("to", fee_collector_address) + .add_attribute("amount", balance.amount); } else { return Err(ContractError::AssetNotEnabledForDistribution { - denom, + denom: denom.to_string(), }); - }; - - let amount_to_distribute = - unwrap_option_amount(&deps.querier, &env.contract.address, &denom, amount)?; - - let transfer_msg = I::ibc_transfer_msg( - env, - to_address.clone(), - Coin { - denom: denom.clone(), - amount: amount_to_distribute, - }, - cfg, - )?; + } - Ok(Response::new() - .add_message(transfer_msg) - .add_attribute("action", "distribute_rewards") - .add_attribute("denom", denom) - .add_attribute("amount", amount_to_distribute) - .add_attribute("to", to_address)) + Ok(res.add_messages(msgs)) } pub fn query_config(&self, deps: Deps) -> StdResult { @@ -412,12 +606,13 @@ where proposed_new_owner: owner_state.proposed, address_provider: cfg.address_provider.into(), safety_tax_rate: cfg.safety_tax_rate, - safety_fund_denom: cfg.safety_fund_denom, - fee_collector_denom: cfg.fee_collector_denom, + revenue_share_tax_rate: cfg.revenue_share_tax_rate, + safety_fund_config: cfg.safety_fund_config, + revenue_share_config: cfg.revenue_share_config, + fee_collector_config: cfg.fee_collector_config, channel_id: cfg.channel_id, timeout_seconds: cfg.timeout_seconds, slippage_tolerance: cfg.slippage_tolerance, - neutron_ibc_config: cfg.neutron_ibc_config, }) } } diff --git a/contracts/rewards-collector/base/src/error.rs b/contracts/rewards-collector/base/src/error.rs index 89c7ae6e3..71f9c16f8 100644 --- a/contracts/rewards-collector/base/src/error.rs +++ b/contracts/rewards-collector/base/src/error.rs @@ -1,4 +1,7 @@ -use cosmwasm_std::{CheckedMultiplyRatioError, OverflowError, StdError, Uint128}; +use cosmwasm_std::{ + CheckedFromRatioError, CheckedMultiplyFractionError, CheckedMultiplyRatioError, OverflowError, + StdError, Uint128, +}; use mars_owner::OwnerError; use mars_types::error::MarsError; use mars_utils::error::ValidationError; @@ -24,6 +27,12 @@ pub enum ContractError { #[error("{0}")] CheckedMultiplyRatio(#[from] CheckedMultiplyRatioError), + #[error("{0}")] + CheckedMultiplyFractionError(#[from] CheckedMultiplyFractionError), + + #[error("{0}")] + CheckedFromRatioError(#[from] CheckedFromRatioError), + #[error("Asset is not enabled for distribution: {denom}")] AssetNotEnabledForDistribution { denom: String, @@ -45,11 +54,24 @@ pub enum ContractError { reason: String, }, + #[error("Min receive given for swap: {denom_in} -> {denom_out} is too small. `min_receive` allowed: {min_receive_minimum}, `min_receive` given: {min_receive_given}")] + SlippageLimitExceeded { + denom_in: String, + denom_out: String, + min_receive_minimum: Uint128, + min_receive_given: Uint128, + }, + #[error("Invalid actions. Only Withdraw and WithdrawLiquidity is possible to pass for CreditManager")] InvalidActionsForCreditManager {}, #[error("{0}")] Version(#[from] cw2::VersionError), + + #[error("Unsupported transfer type: {transfer_type}")] + UnsupportedTransferType { + transfer_type: String, + }, } pub type ContractResult = Result; diff --git a/contracts/rewards-collector/base/src/traits.rs b/contracts/rewards-collector/base/src/traits.rs index 824910d96..4cb5c1818 100644 --- a/contracts/rewards-collector/base/src/traits.rs +++ b/contracts/rewards-collector/base/src/traits.rs @@ -1,14 +1,13 @@ use std::fmt::{Debug, Display}; use cosmwasm_std::{ - Coin, CosmosMsg, CustomMsg, CustomQuery, Decimal, Empty, Env, IbcMsg, IbcTimeout, - QuerierWrapper, Uint128, + BankMsg, Coin, CosmosMsg, CustomMsg, CustomQuery, Decimal, Empty, Env, QuerierWrapper, Uint128, }; -use mars_types::rewards_collector::Config; +use mars_types::rewards_collector::{Config, TransferType}; use schemars::JsonSchema; use serde::{de::DeserializeOwned, Serialize}; -use crate::ContractResult; +use crate::{ContractError, ContractResult}; pub trait Route: Serialize + DeserializeOwned + Clone + Debug + Display + PartialEq + JsonSchema @@ -35,27 +34,33 @@ where ) -> ContractResult>; } -pub trait IbcTransferMsg { - fn ibc_transfer_msg( - env: Env, - to_address: String, +pub trait TransferMsg { + fn transfer_msg( + env: &Env, + to_address: &str, amount: Coin, - cfg: Config, + cfg: &Config, + transfer_type: &TransferType, ) -> ContractResult>; } -impl IbcTransferMsg for Empty { - fn ibc_transfer_msg( - env: Env, - to_address: String, +impl TransferMsg for Empty { + fn transfer_msg( + _: &Env, + to_address: &str, amount: Coin, - cfg: Config, + _: &Config, + transfer_type: &TransferType, ) -> ContractResult> { - Ok(CosmosMsg::Ibc(IbcMsg::Transfer { - channel_id: cfg.channel_id, - to_address, - amount, - timeout: IbcTimeout::with_timestamp(env.block.time.plus_seconds(cfg.timeout_seconds)), - })) + // By default, we only support bank transfers + match transfer_type { + TransferType::Bank => Ok(CosmosMsg::Bank(BankMsg::Send { + to_address: to_address.to_string(), + amount: vec![amount], + })), + TransferType::Ibc => Err(ContractError::UnsupportedTransferType { + transfer_type: transfer_type.to_string(), + }), + } } } diff --git a/contracts/rewards-collector/neutron/Cargo.toml b/contracts/rewards-collector/neutron/Cargo.toml index 5f773f797..1860b113c 100644 --- a/contracts/rewards-collector/neutron/Cargo.toml +++ b/contracts/rewards-collector/neutron/Cargo.toml @@ -21,6 +21,8 @@ library = [] [dependencies] cosmwasm-std = { workspace = true, features = ["stargate"] } cw2 = { workspace = true } +cw-storage-plus = { workspace = true } +cosmwasm-schema = { workspace = true } mars-rewards-collector-base = { workspace = true } mars-types = { workspace = true } neutron-sdk = { workspace = true } diff --git a/contracts/rewards-collector/neutron/src/lib.rs b/contracts/rewards-collector/neutron/src/lib.rs index dfc921c4d..ea9e6dee6 100644 --- a/contracts/rewards-collector/neutron/src/lib.rs +++ b/contracts/rewards-collector/neutron/src/lib.rs @@ -1,63 +1,17 @@ -use std::vec; - -use cosmwasm_std::{coin, Coin, CosmosMsg, Env, StdError}; -use mars_rewards_collector_base::{ - contract::Collector, ContractError, ContractResult, IbcTransferMsg, -}; -use neutron_sdk::{ - bindings::msg::{IbcFee, NeutronMsg}, - sudo::msg::RequestPacketTimeoutHeight, -}; - pub mod migrations; -pub struct NeutronIbcMsgFactory {} - -impl IbcTransferMsg for NeutronIbcMsgFactory { - fn ibc_transfer_msg( - env: Env, - to_address: String, - amount: Coin, - cfg: mars_types::rewards_collector::Config, - ) -> ContractResult> { - let neutron_config = cfg.neutron_ibc_config.ok_or(ContractError::Std( - StdError::generic_err("source_port must be provided for neutron"), - ))?; - Ok(NeutronMsg::IbcTransfer { - source_port: neutron_config.source_port, - source_channel: cfg.channel_id, - token: amount, - sender: env.contract.address.to_string(), - receiver: to_address, - timeout_height: RequestPacketTimeoutHeight { - revision_number: None, - revision_height: None, - }, - timeout_timestamp: env.block.time.nanos() + cfg.timeout_seconds * 1_000_000_000, - memo: "".to_string(), - fee: IbcFee { - recv_fee: vec![coin(0u128, "untrn")], - ack_fee: neutron_config.acc_fee, - timeout_fee: neutron_config.timeout_fee, - }, - } - .into()) - } -} - -pub type NeutronCollector<'a> = Collector<'a, NeutronMsg, NeutronIbcMsgFactory>; - #[cfg(not(feature = "library"))] pub mod entry { use cosmwasm_std::{ entry_point, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, }; use cw2::set_contract_version; - use mars_rewards_collector_base::ContractResult; + use mars_rewards_collector_base::{contract::Collector, ContractResult}; use mars_types::rewards_collector::{ExecuteMsg, InstantiateMsg, QueryMsg}; - use neutron_sdk::bindings::msg::NeutronMsg; - use crate::{migrations, NeutronCollector}; + use crate::migrations; + + pub type NeutronCollector<'a> = Collector<'a, Empty, Empty>; pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -80,7 +34,7 @@ pub mod entry { env: Env, info: MessageInfo, msg: ExecuteMsg, - ) -> ContractResult> { + ) -> ContractResult { let collector = NeutronCollector::default(); collector.execute(deps, env, info, msg) } diff --git a/contracts/rewards-collector/osmosis/Cargo.toml b/contracts/rewards-collector/osmosis/Cargo.toml index 07726498d..38c1a92b4 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 = { workspace = true } +version = "2.1.1" authors = { workspace = true } edition = { workspace = true } license = { workspace = true } @@ -34,3 +34,4 @@ mars-testing = { workspace = true } mars-utils = { workspace = true } osmosis-std = { workspace = true } serde = { workspace = true } +test-case = { workspace = true } diff --git a/contracts/rewards-collector/osmosis/src/lib.rs b/contracts/rewards-collector/osmosis/src/lib.rs index 478a57050..f580b36d1 100644 --- a/contracts/rewards-collector/osmosis/src/lib.rs +++ b/contracts/rewards-collector/osmosis/src/lib.rs @@ -1,21 +1,52 @@ +use cosmwasm_std::{Coin, CosmosMsg, Empty, Env, IbcMsg, IbcTimeout}; +use mars_rewards_collector_base::{contract::Collector, ContractResult, TransferMsg}; +use mars_types::rewards_collector::{Config, TransferType}; + pub mod migrations; +pub struct OsmosisMsgFactory {} + +impl TransferMsg for OsmosisMsgFactory { + fn transfer_msg( + env: &Env, + to_address: &str, + amount: Coin, + cfg: &Config, + transfer_type: &TransferType, + ) -> ContractResult> { + match transfer_type { + TransferType::Bank => Ok(CosmosMsg::Bank(cosmwasm_std::BankMsg::Send { + to_address: to_address.to_string(), + amount: vec![amount], + })), + TransferType::Ibc => Ok(CosmosMsg::Ibc(IbcMsg::Transfer { + channel_id: cfg.channel_id.to_string(), + to_address: to_address.to_string(), + amount, + timeout: IbcTimeout::with_timestamp( + env.block.time.plus_seconds(cfg.timeout_seconds), + ), + })), + } + } +} + +pub type OsmosisCollector<'a> = Collector<'a, Empty, OsmosisMsgFactory>; + #[cfg(not(feature = "library"))] pub mod entry { use cosmwasm_std::{ entry_point, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, }; use cw2::set_contract_version; - use mars_rewards_collector_base::{contract::Collector, ContractError, ContractResult}; + use mars_rewards_collector_base::{ContractError, ContractResult}; use mars_types::rewards_collector::{ExecuteMsg, InstantiateMsg, QueryMsg}; - use crate::migrations; + use crate::{migrations, OsmosisCollector}; pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - pub type OsmosisCollector<'a> = Collector<'a, Empty, Empty>; - #[entry_point] pub fn instantiate( deps: DepsMut, @@ -47,6 +78,6 @@ pub mod entry { #[entry_point] pub fn migrate(deps: DepsMut, _env: Env, _msg: Empty) -> Result { - migrations::v2_1_0::migrate(deps) + migrations::v2_1_1::migrate(deps) } } diff --git a/contracts/rewards-collector/osmosis/src/migrations/mod.rs b/contracts/rewards-collector/osmosis/src/migrations/mod.rs index 78e0d72f0..dfd90f0b6 100644 --- a/contracts/rewards-collector/osmosis/src/migrations/mod.rs +++ b/contracts/rewards-collector/osmosis/src/migrations/mod.rs @@ -1 +1,2 @@ pub mod v2_1_0; +pub mod v2_1_1; diff --git a/contracts/rewards-collector/osmosis/src/migrations/v2_1_1.rs b/contracts/rewards-collector/osmosis/src/migrations/v2_1_1.rs new file mode 100644 index 000000000..09538c267 --- /dev/null +++ b/contracts/rewards-collector/osmosis/src/migrations/v2_1_1.rs @@ -0,0 +1,107 @@ +use cosmwasm_std::{Decimal, DepsMut, Response, Storage}; +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, +}; + +pub mod previous_state { + use cosmwasm_schema::cw_serde; + use cosmwasm_std::{Addr, Coin, Decimal}; + use cw_storage_plus::Item; + + pub const CONFIG: Item = Item::new("config"); + + #[cw_serde] + pub struct Config { + /// Address provider returns addresses for all protocol contracts + pub address_provider: Addr, + /// Percentage of fees that are sent to the safety fund + pub safety_tax_rate: Decimal, + /// The asset to which the safety fund share is converted + pub safety_fund_denom: String, + /// The asset to which the fee collector share is converted + pub fee_collector_denom: String, + /// The channel ID of the mars hub + pub channel_id: String, + /// Number of seconds after which an IBC transfer is to be considered failed, if no acknowledgement is received + pub timeout_seconds: u64, + /// Maximum percentage of price movement (minimum amount you accept to receive during swap) + pub slippage_tolerance: Decimal, + /// Neutron IBC config + pub neutron_ibc_config: Option, + } + + #[cw_serde] + pub struct NeutronIbcConfig { + pub source_port: String, + pub acc_fee: Vec, + pub timeout_fee: Vec, + } +} + +const FROM_VERSION: &str = "2.1.0"; + +pub fn migrate(deps: DepsMut) -> Result { + let storage: &mut dyn Storage = deps.storage; + + // make sure we're migrating the correct contract and from the correct version + assert_contract_version(storage, &format!("crates.io:{CONTRACT_NAME}"), FROM_VERSION)?; + + // load the existing config + let old_config = previous_state::CONFIG.load(storage)?; + + previous_state::CONFIG.remove(storage); + + let new_config = Config { + // old, unchanged values + address_provider: old_config.address_provider, + slippage_tolerance: old_config.slippage_tolerance, + timeout_seconds: old_config.timeout_seconds, + + // source channel on osmosis-1 for neutron-1 is channel-874. Proof below + // https://lcd.osmosis.zone/ibc/core/channel/v1/channels/channel-874/ports/transfer = counterparty channel-10, connection-2338 + // https://lcd.osmosis.zone/ibc/core/connection/v1/connections/connection-2338 = client-id = 07-tendermint-19 + // https://lcd.osmosis.zone/ibc/core/client/v1/client_states/07-tendermint-2823 = chain-id = neutron-1 + channel_id: "channel-874".to_string(), + + // updated tax_rate to account for the new revenue share + // breakdown is now 45% safety fund, 10% revenue share, remaining 45% fee collector + safety_tax_rate: Decimal::percent(45), + revenue_share_tax_rate: Decimal::percent(10), + + // safety fund set to same denom as before. Bank transfer, not IBC + safety_fund_config: RewardConfig { + target_denom: old_config.safety_fund_denom.clone(), + transfer_type: TransferType::Bank, + }, + + // revenue share set to same denom as safety fund. Bank transfer, not IBC + revenue_share_config: RewardConfig { + target_denom: old_config.safety_fund_denom, + transfer_type: TransferType::Bank, + }, + + // fee collector set to same denom as before. IBC transfer to neutron + fee_collector_config: RewardConfig { + target_denom: old_config.fee_collector_denom, + transfer_type: TransferType::Ibc, + }, + }; + + // ensure our new config is legal + new_config.validate()?; + + let collector = OsmosisCollector::default(); + collector.config.save(storage, &new_config)?; + + 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/rewards-collector/osmosis/tests/tests/helpers/mod.rs b/contracts/rewards-collector/osmosis/tests/tests/helpers/mod.rs index 13bc1de76..d764ff34f 100644 --- a/contracts/rewards-collector/osmosis/tests/tests/helpers/mod.rs +++ b/contracts/rewards-collector/osmosis/tests/tests/helpers/mod.rs @@ -8,7 +8,7 @@ use cosmwasm_std::{ use mars_osmosis::BalancerPool; use mars_rewards_collector_osmosis::entry; use mars_testing::{mock_info, MarsMockQuerier}; -use mars_types::rewards_collector::{Config, InstantiateMsg, QueryMsg}; +use mars_types::rewards_collector::{Config, InstantiateMsg, QueryMsg, RewardConfig, TransferType}; use osmosis_std::types::osmosis::{gamm::v1beta1::PoolAsset, poolmanager::v1beta1::PoolResponse}; pub fn mock_instantiate_msg() -> InstantiateMsg { @@ -16,12 +16,22 @@ pub fn mock_instantiate_msg() -> InstantiateMsg { owner: "owner".to_string(), address_provider: "address_provider".to_string(), safety_tax_rate: Decimal::percent(25), - safety_fund_denom: "uusdc".to_string(), - fee_collector_denom: "umars".to_string(), + revenue_share_tax_rate: Decimal::percent(10), + 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::Ibc, + }, channel_id: "channel-69".to_string(), timeout_seconds: 300, slippage_tolerance: Decimal::percent(3), - neutron_ibc_config: None, } } @@ -30,15 +40,16 @@ pub fn mock_config(api: MockApi, msg: InstantiateMsg) -> Config { } pub fn setup_test() -> OwnedDeps { - let mut deps = OwnedDeps::<_, _, _> { - storage: MockStorage::default(), - api: MockApi::default(), - querier: MarsMockQuerier::new(MockQuerier::new(&[( - MOCK_CONTRACT_ADDR, - &[coin(88888, "uatom"), coin(1234, "uusdc"), coin(8964, "umars")], - )])), - custom_query_type: Default::default(), - }; + let mut deps: OwnedDeps = + OwnedDeps::<_, _, _> { + storage: MockStorage::default(), + api: MockApi::default(), + querier: MarsMockQuerier::new(MockQuerier::new(&[( + MOCK_CONTRACT_ADDR, + &[coin(88888, "uatom"), coin(1234, "uusdc"), coin(8964, "umars")], + )])), + custom_query_type: Default::default(), + }; // set up pools for the mock osmosis querier deps.querier.set_query_pool_response( diff --git a/contracts/rewards-collector/osmosis/tests/tests/mod.rs b/contracts/rewards-collector/osmosis/tests/tests/mod.rs index da32dc68a..8f39526ac 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; +mod test_migration_v2_1_0_to_v2_1_1; mod test_swap; mod test_update_owner; mod test_withdraw; diff --git a/contracts/rewards-collector/osmosis/tests/tests/test_admin.rs b/contracts/rewards-collector/osmosis/tests/tests/test_admin.rs index 4c08242f8..e7fb8581d 100644 --- a/contracts/rewards-collector/osmosis/tests/tests/test_admin.rs +++ b/contracts/rewards-collector/osmosis/tests/tests/test_admin.rs @@ -27,16 +27,17 @@ fn instantiating() { proposed_new_owner: None, address_provider: config.address_provider.to_string(), safety_tax_rate: config.safety_tax_rate, - safety_fund_denom: config.safety_fund_denom, - fee_collector_denom: config.fee_collector_denom, + revenue_share_tax_rate: config.revenue_share_tax_rate, + safety_fund_config: config.safety_fund_config, + revenue_share_config: config.revenue_share_config, + fee_collector_config: config.fee_collector_config, channel_id: config.channel_id, timeout_seconds: config.timeout_seconds, slippage_tolerance: config.slippage_tolerance, - neutron_ibc_config: config.neutron_ibc_config } ); - // init config with safety_tax_rate greater than 1; should fail + // init config with total_weight greater than 1; should fail init_msg.safety_tax_rate = Decimal::percent(150); let info = mock_info("deployer"); @@ -44,8 +45,8 @@ fn instantiating() { assert_eq!( err, ContractError::Validation(ValidationError::InvalidParam { - param_name: "safety_tax_rate".to_string(), - invalid_value: "1.5".to_string(), + param_name: "total_tax_rate".to_string(), + invalid_value: "1.6".to_string(), predicate: "<= 1".to_string(), }) ); @@ -104,8 +105,8 @@ fn updating_config() { assert_eq!( err, ContractError::Validation(ValidationError::InvalidParam { - param_name: "safety_tax_rate".to_string(), - invalid_value: "1.25".to_string(), + param_name: "total_tax_rate".to_string(), + invalid_value: "1.35".to_string(), predicate: "<= 1".to_string(), }) ); diff --git a/contracts/rewards-collector/osmosis/tests/tests/test_distribute_rewards.rs b/contracts/rewards-collector/osmosis/tests/tests/test_distribute_rewards.rs index b5f52d9a3..b7e30e75e 100644 --- a/contracts/rewards-collector/osmosis/tests/tests/test_distribute_rewards.rs +++ b/contracts/rewards-collector/osmosis/tests/tests/test_distribute_rewards.rs @@ -1,81 +1,118 @@ -use cosmwasm_std::{ - coin, testing::mock_env, CosmosMsg, IbcMsg, IbcTimeout, SubMsg, Timestamp, Uint128, -}; -use mars_rewards_collector_base::ContractError; +use cosmwasm_std::{coin, Coin, CosmosMsg, Decimal, IbcMsg, IbcTimeout, SubMsg, Timestamp}; use mars_rewards_collector_osmosis::entry::execute; use mars_testing::{mock_env as mock_env_at_height_and_time, mock_info, MockEnvParams}; -use mars_types::rewards_collector::ExecuteMsg; +use mars_types::rewards_collector::{ExecuteMsg, UpdateConfig}; +use test_case::test_case; use super::helpers; -#[test] -fn distributing_rewards() { - let mut deps = helpers::setup_test(); +#[test_case( + &[coin(1234, "uusdc")], + "umars".to_string(), + vec![], + None; + "Distribute nothing sends no messages" +)] +#[test_case( + &[coin(1234, "umars")], + "umars".to_string(), + vec![ + SubMsg::new(CosmosMsg::Ibc(IbcMsg::Transfer { + channel_id: "channel-69".to_string(), + to_address: "fee_collector".to_string(), + amount: coin(1234, "umars"), + timeout: IbcTimeout::with_timestamp(Timestamp::from_seconds(17000300)) + })) + ], + None; + "Distribute single denom" +)] +#[test_case( + &[ + coin(1234, "uusdc"), + ], + "uusdc".to_string(), + // uusdc balance in contract = 1234 + // safety fund = 0.25 / (0.1+0.25) = 0.7142857142857143 + // rev share = 0.1 / (0.1+0.25) = 0.28571428571 + // 1234 * 0.7142857142857143 = 881.4 = 881 + // 1234 - 881 = 353 + vec![ + SubMsg::new(CosmosMsg::Bank(cosmwasm_std::BankMsg::Send { + to_address: "safety_fund".to_string(), + amount: vec![coin(881, "uusdc")], + })), + SubMsg::new(CosmosMsg::Bank(cosmwasm_std::BankMsg::Send { + to_address: "revenue_share".to_string(), + amount: vec![coin(353, "uusdc")], + })) + ], + None; + "distribute same denom to safety fund and rev share" +)] +#[test_case( + &[ + coin(1234, "uusdc"), + ], + "uusdc".to_string(), + // uusdc balance in contract = 1234 + // safety fund = 0.25 / (0.25) = 1 + // 1234 * 1 = 1234 + // 1234 - 1234 = 0 + vec![ + SubMsg::new(CosmosMsg::Bank(cosmwasm_std::BankMsg::Send { + to_address: "safety_fund".to_string(), + amount: vec![coin(1234, "uusdc")], + })), + ], + Some(UpdateConfig{ + revenue_share_tax_rate: Some(Decimal::zero()), + ..Default::default() + }); + "distribute when rev share is zero" +)] + +fn assert_rewards_distribution( + initial_balances: &[Coin], + denom_to_distribute: String, + expected_msgs: Vec, + config: Option, +) { + let mut deps: cosmwasm_std::OwnedDeps< + cosmwasm_std::MemoryStorage, + cosmwasm_std::testing::MockApi, + mars_testing::MarsMockQuerier, + > = helpers::setup_test(); + deps.querier.set_contract_balances(initial_balances); let env = mock_env_at_height_and_time(MockEnvParams { block_height: 10000, block_time: Timestamp::from_seconds(17000000), }); - // distribute uusdc to safety fund - let res = execute( - deps.as_mut(), - env.clone(), - mock_info("jake"), - ExecuteMsg::DistributeRewards { - denom: "uusdc".to_string(), - amount: Some(Uint128::new(123)), - }, - ) - .unwrap(); - assert_eq!(res.messages.len(), 1); - assert_eq!( - res.messages[0], - SubMsg::new(CosmosMsg::Ibc(IbcMsg::Transfer { - channel_id: "channel-69".to_string(), - to_address: "safety_fund".to_string(), - amount: coin(123, "uusdc"), - timeout: IbcTimeout::with_timestamp(Timestamp::from_seconds(17000300)) - })) - ); + if let Some(cfg) = config { + execute( + deps.as_mut(), + env.clone(), + mock_info("owner"), + ExecuteMsg::UpdateConfig { + new_cfg: cfg, + }, + ) + .unwrap(); + } - // distribute umars to fee collector + // distribute uusdc to safety fund and rev share let res = execute( deps.as_mut(), - env, + env.clone(), mock_info("jake"), ExecuteMsg::DistributeRewards { - denom: "umars".to_string(), - amount: None, + denom: denom_to_distribute, }, ) .unwrap(); - assert_eq!(res.messages.len(), 1); - assert_eq!( - res.messages[0], - SubMsg::new(CosmosMsg::Ibc(IbcMsg::Transfer { - channel_id: "channel-69".to_string(), - to_address: "fee_collector".to_string(), - amount: coin(8964, "umars"), - timeout: IbcTimeout::with_timestamp(Timestamp::from_seconds(17000300)) - })) - ); + assert_eq!(res.messages.len(), expected_msgs.len()); - // distribute uatom; should fail - let err = execute( - deps.as_mut(), - mock_env(), - mock_info("jake"), - ExecuteMsg::DistributeRewards { - denom: "uatom".to_string(), - amount: Some(Uint128::new(123)), - }, - ) - .unwrap_err(); - assert_eq!( - err, - ContractError::AssetNotEnabledForDistribution { - denom: "uatom".to_string() - } - ); + assert_eq!(res.messages, expected_msgs); } diff --git a/contracts/rewards-collector/osmosis/tests/tests/test_migration_v2.rs b/contracts/rewards-collector/osmosis/tests/tests/test_migration_v2.rs deleted file mode 100644 index edd57a9d5..000000000 --- a/contracts/rewards-collector/osmosis/tests/tests/test_migration_v2.rs +++ /dev/null @@ -1,69 +0,0 @@ -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; - -#[test] -fn wrong_contract_name() { - let mut deps = mock_dependencies(&[]); - cw2::set_contract_version(deps.as_mut().storage, "contract_xyz", "2.0.1").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.0.1".to_string(), - found: "4.1.0".to_string() - }) - ); -} - -#[test] -fn successful_migration() { - let mut deps = mock_dependencies(&[]); - cw2::set_contract_version( - deps.as_mut().storage, - "crates.io:mars-rewards-collector-osmosis", - "2.0.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.0.1"), attr("to_version", "2.1.0")] - ); - - let new_contract_version = ContractVersion { - contract: "crates.io:mars-rewards-collector-osmosis".to_string(), - version: "2.1.0".to_string(), - }; - assert_eq!(cw2::get_contract_version(deps.as_ref().storage).unwrap(), new_contract_version); -} 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 new file mode 100644 index 000000000..f4f1fcb5c --- /dev/null +++ b/contracts/rewards-collector/osmosis/tests/tests/test_migration_v2_1_0_to_v2_1_1.rs @@ -0,0 +1,100 @@ +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_swap.rs b/contracts/rewards-collector/osmosis/tests/tests/test_swap.rs index c6cfe529d..cb402f024 100644 --- a/contracts/rewards-collector/osmosis/tests/tests/test_swap.rs +++ b/contracts/rewards-collector/osmosis/tests/tests/test_swap.rs @@ -1,13 +1,13 @@ use cosmwasm_std::{ coin, testing::mock_env, to_json_binary, CosmosMsg, Decimal, Empty, SubMsg, Uint128, WasmMsg, }; +use mars_rewards_collector_base::ContractError; use mars_rewards_collector_osmosis::entry::execute; use mars_testing::mock_info; use mars_types::{ rewards_collector::{ConfigResponse, ExecuteMsg, QueryMsg}, swapper::{self, OsmoRoute, OsmoSwap, SwapperRoute}, }; -use osmosis_std::types::osmosis::twap::v1beta1::ArithmeticTwapToNowResponse; use super::helpers; @@ -15,38 +15,26 @@ use super::helpers; fn swapping_asset() { let mut deps = helpers::setup_test(); - let uatom_uosmo_price = Decimal::from_ratio(125u128, 10u128); - deps.querier.set_arithmetic_twap_price( - 1, - "uatom", - "uosmo", - ArithmeticTwapToNowResponse { - arithmetic_twap: uatom_uosmo_price.to_string(), - }, - ); - let uosmo_uusdc_price = Decimal::from_ratio(10u128, 1u128); - deps.querier.set_arithmetic_twap_price( - 69, - "uosmo", - "uusdc", - ArithmeticTwapToNowResponse { - arithmetic_twap: uosmo_uusdc_price.to_string(), - }, - ); - let uosmo_umars_price = Decimal::from_ratio(5u128, 10u128); - deps.querier.set_arithmetic_twap_price( - 420, - "uosmo", - "umars", - ArithmeticTwapToNowResponse { - arithmetic_twap: uosmo_umars_price.to_string(), - }, - ); - let cfg: ConfigResponse = helpers::query(deps.as_ref(), QueryMsg::Config {}); - let safety_fund_input = Uint128::new(10517); - let fee_collector_input = Uint128::new(31552); + let usdc_denom = "uusdc".to_string(); + let mars_denom = "umars".to_string(); + let atom_denom = "uatom".to_string(); + + let uusdc_usd_price = Decimal::one(); + let umars_uusdc_price = Decimal::from_ratio(5u128, 10u128); // 0.5 uusdc = 1 umars + let uatom_uusdc_price = Decimal::from_ratio(125u128, 10u128); // 12.5 uusd = 1 uatom + + deps.querier.set_oracle_price(&usdc_denom, uusdc_usd_price); + deps.querier.set_oracle_price(&mars_denom, umars_uusdc_price); + deps.querier.set_oracle_price(&atom_denom, uatom_uusdc_price); + + deps.querier.set_swapper_estimate_price(&mars_denom, umars_uusdc_price); + deps.querier.set_swapper_estimate_price(&atom_denom, uatom_uusdc_price); + deps.querier.set_swapper_estimate_price(&usdc_denom, uusdc_usd_price); + + let safety_fund_input = Uint128::new(14724); + let fee_collector_input = Uint128::new(27345); let res = execute( deps.as_mut(), @@ -58,17 +46,17 @@ fn swapping_asset() { safety_fund_route: Some(SwapperRoute::Osmo(OsmoRoute { swaps: vec![OsmoSwap { pool_id: 12, - to: cfg.safety_fund_denom.to_string(), + to: cfg.safety_fund_config.target_denom.to_string(), }], })), fee_collector_route: Some(SwapperRoute::Osmo(OsmoRoute { swaps: vec![OsmoSwap { pool_id: 69, - to: cfg.fee_collector_denom.to_string(), + to: cfg.fee_collector_config.target_denom.to_string(), }], })), - safety_fund_min_receive: Some(Uint128::new(1822)), - fee_collector_min_receive: Some(Uint128::new(4458)), + safety_fund_min_receive: Some(Uint128::new(178528)), + fee_collector_min_receive: Some(Uint128::new(663140)), }, ) .unwrap(); @@ -79,12 +67,12 @@ fn swapping_asset() { contract_addr: "swapper".to_string(), msg: to_json_binary(&swapper::ExecuteMsg::::SwapExactIn { coin_in: coin(safety_fund_input.u128(), "uatom"), - denom_out: cfg.safety_fund_denom.to_string(), - min_receive: Uint128::new(1822), + denom_out: cfg.safety_fund_config.target_denom.to_string(), + min_receive: Uint128::new(178528), route: Some(SwapperRoute::Osmo(OsmoRoute { swaps: vec![OsmoSwap { pool_id: 12, - to: cfg.safety_fund_denom.to_string(), + to: cfg.safety_fund_config.target_denom.to_string(), }], })), }) @@ -98,12 +86,12 @@ fn swapping_asset() { contract_addr: "swapper".to_string(), msg: to_json_binary(&swapper::ExecuteMsg::::SwapExactIn { coin_in: coin(fee_collector_input.u128(), "uatom"), - denom_out: cfg.fee_collector_denom.to_string(), - min_receive: Uint128::new(4458), + denom_out: cfg.fee_collector_config.target_denom.to_string(), + min_receive: Uint128::new(663140), route: Some(SwapperRoute::Osmo(OsmoRoute { swaps: vec![OsmoSwap { pool_id: 69, - to: cfg.fee_collector_denom, + to: cfg.fee_collector_config.target_denom.to_string(), }], })), }) @@ -119,11 +107,8 @@ fn swapping_asset() { /// For example, for the Osmosis outpost, we plan to set /// /// - fee_collector_denom = MARS -/// - safety_fund_denom = axlUSDC -/// -/// For protocol revenue collected in axlUSDC, we want half to be swapped to -/// MARS and sent to the fee collector, and the other half _not swapped_ and -/// sent to safety fund. +/// - safety_fund_denom = USDC +/// - revenue_share_denom = USDC /// /// In this test, we make sure the safety fund part of the swap is properly /// skipped. @@ -134,42 +119,39 @@ fn swapping_asset() { fn skipping_swap_if_denom_matches() { let mut deps = helpers::setup_test(); - let uusdc_uosmo_price = Decimal::from_ratio(1u128, 10u128); - deps.querier.set_arithmetic_twap_price( - 69, - "uusdc", - "uosmo", - ArithmeticTwapToNowResponse { - arithmetic_twap: uusdc_uosmo_price.to_string(), - }, - ); - let uosmo_umars_price = Decimal::from_ratio(5u128, 10u128); - deps.querier.set_arithmetic_twap_price( - 420, - "uosmo", - "umars", - ArithmeticTwapToNowResponse { - arithmetic_twap: uosmo_umars_price.to_string(), - }, - ); + let usdc_denom = "uusdc".to_string(); + let mars_denom = "umars".to_string(); + let atom_denom = "uatom".to_string(); + + let uusdc_usd_price = Decimal::one(); + let umars_uusdc_price = Decimal::from_ratio(5u128, 10u128); // 0.5 uusdc = 1 umars + let uatom_uusdc_price = Decimal::from_ratio(125u128, 10u128); // 12.5 uusd = 1 uatom + + deps.querier.set_oracle_price(&usdc_denom, uusdc_usd_price); + deps.querier.set_oracle_price(&mars_denom, umars_uusdc_price); + deps.querier.set_oracle_price(&atom_denom, uatom_uusdc_price); + + deps.querier.set_swapper_estimate_price(&mars_denom, umars_uusdc_price); + deps.querier.set_swapper_estimate_price(&atom_denom, uatom_uusdc_price); + deps.querier.set_swapper_estimate_price(&usdc_denom, uusdc_usd_price); let res = execute( deps.as_mut(), mock_env(), mock_info("jake"), ExecuteMsg::SwapAsset { - denom: "uusdc".to_string(), + denom: usdc_denom.to_string(), amount: None, safety_fund_route: Some(SwapperRoute::Osmo(OsmoRoute { swaps: vec![OsmoSwap { pool_id: 12, - to: "uusdc".to_string(), + to: usdc_denom.to_string(), }], })), fee_collector_route: Some(SwapperRoute::Osmo(OsmoRoute { swaps: vec![OsmoSwap { pool_id: 69, - to: "umars".to_string(), + to: mars_denom.to_string(), }], })), safety_fund_min_receive: Some(Uint128::new(1822)), @@ -181,13 +163,14 @@ fn skipping_swap_if_denom_matches() { // the response should only contain one swap message, from USDC to MARS, for // the fee collector. // - // the USDC --> USDC swap for safety fund should be skipped. + // the USDC --> USDC swap for safety fund and revenue share should be skipped. assert_eq!(res.messages.len(), 1); - // amount of USDC the contract held prior to swap: 1234 + // amount of ATOM the contract held prior to swap: 1234 // // amount for safety fund: 1234 * 0.25 = 308 - // amount for fee collector: 1234 - 308 = 926 + // amount for revenue share: 1234 * 0.1 = 123 + // amount for fee collector: 1234 - 308 = 803 // // 1 uusdc = 0.1 uosmo // 1 uosmo = 0.5 umars @@ -196,19 +179,123 @@ fn skipping_swap_if_denom_matches() { let swap_msg: CosmosMsg = WasmMsg::Execute { contract_addr: "swapper".to_string(), msg: to_json_binary(&swapper::ExecuteMsg::::SwapExactIn { - coin_in: coin(926u128, "uusdc"), + coin_in: coin(803u128, "uusdc"), denom_out: "umars".to_string(), min_receive: Uint128::new(4458), route: Some(SwapperRoute::Osmo(OsmoRoute { swaps: vec![OsmoSwap { pool_id: 69, - to: "umars".to_string(), + to: mars_denom.to_string(), }], })), }) .unwrap(), - funds: vec![coin(926u128, "uusdc")], + funds: vec![coin(803u128, usdc_denom)], } .into(); assert_eq!(res.messages[0], SubMsg::new(swap_msg)); } + +#[test] +fn swap_fails_if_slippage_limit_exceeded() { + let mut deps = helpers::setup_test(); + + let usdc_denom = "uusdc".to_string(); + let mars_denom = "umars".to_string(); + let atom_denom = "uatom".to_string(); + + let uusdc_usd_price = Decimal::one(); + let umars_uusdc_price = Decimal::from_ratio(5u128, 10u128); // 0.5 uusdc = 1 umars + let uatom_uusdc_price = Decimal::from_ratio(125u128, 10u128); // 12.5 uusd = 1 uatom + + deps.querier.set_oracle_price(&usdc_denom, uusdc_usd_price); + + deps.querier.set_oracle_price(&mars_denom, umars_uusdc_price); + + deps.querier.set_oracle_price(&atom_denom, uatom_uusdc_price); + + deps.querier.set_swapper_estimate_price(&mars_denom, umars_uusdc_price); + deps.querier.set_swapper_estimate_price(&atom_denom, uatom_uusdc_price); + deps.querier.set_swapper_estimate_price(&usdc_denom, uusdc_usd_price); + + // Here we test the slippage limits for each of the target reward denoms + // + // uatom for revenue share = 88888 * 0.1 = 8888 + // uatom for safety fund = 88888 * 0.25 = 22222 + // uatom for Fee collector = 88888 - 8888 - 22222 = 57778 + // worst price (atom -> mars ) = 12.5 / 0/5 = 25 * (1-0.03) = 24.25 + // worst price (atom -> usdc) = 12.5 = * (1-0.03) = 12.125 + // minimum revenue share (atom -> usdc) = 8888 * 12.125 = 107767 + // minimum safety fund (atom -> usdc) = 31110 * 12.125 = 377208 + // minimum fee collector (atom -> mars) = 57778 * 24.25= 1401116 + + // Safety Fund fail + let res = execute( + deps.as_mut(), + mock_env(), + mock_info("jake"), + ExecuteMsg::SwapAsset { + denom: atom_denom.to_string(), + amount: None, + safety_fund_route: Some(SwapperRoute::Osmo(OsmoRoute { + swaps: vec![OsmoSwap { + pool_id: 12, + to: usdc_denom.to_string(), + }], + })), + fee_collector_route: Some(SwapperRoute::Osmo(OsmoRoute { + swaps: vec![OsmoSwap { + pool_id: 69, + to: mars_denom.to_string(), + }], + })), + safety_fund_min_receive: Some(Uint128::new(377207)), // 377207 < 377208 -> error + fee_collector_min_receive: Some(Uint128::new(1401116)), // pass + }, + ); + + assert_eq!( + res.unwrap_err(), + ContractError::SlippageLimitExceeded { + denom_in: atom_denom.clone(), + denom_out: usdc_denom.clone(), + min_receive_minimum: Uint128::new(377208), + min_receive_given: Uint128::new(377207), + } + ); + + // Fee Collector fail + let res = execute( + deps.as_mut(), + mock_env(), + mock_info("jake"), + ExecuteMsg::SwapAsset { + denom: atom_denom.to_string(), + amount: None, + safety_fund_route: Some(SwapperRoute::Osmo(OsmoRoute { + swaps: vec![OsmoSwap { + pool_id: 12, + to: usdc_denom.to_string(), + }], + })), + fee_collector_route: Some(SwapperRoute::Osmo(OsmoRoute { + swaps: vec![OsmoSwap { + pool_id: 69, + to: mars_denom.to_string(), + }], + })), + safety_fund_min_receive: Some(Uint128::new(377208)), // pass + fee_collector_min_receive: Some(Uint128::new(1401115)), // 1401115 < 1401116 -> error + }, + ); + + assert_eq!( + res.unwrap_err(), + ContractError::SlippageLimitExceeded { + denom_in: atom_denom.clone(), + denom_out: mars_denom.clone(), + min_receive_minimum: Uint128::new(1401116), + min_receive_given: Uint128::new(1401115), + } + ); +} diff --git a/integration-tests/tests/test_oracles.rs b/integration-tests/tests/test_oracles.rs index 6ef3fffc2..60bafc155 100644 --- a/integration-tests/tests/test_oracles.rs +++ b/integration-tests/tests/test_oracles.rs @@ -15,11 +15,11 @@ use mars_types::{ oracle::{ExecuteMsg, InstantiateMsg, PriceResponse, QueryMsg}, params::AssetParamsUpdate, red_bank::{ - CreateOrUpdateConfig, ExecuteMsg as ExecuteRedBank, - ExecuteMsg::{Borrow, Deposit}, + CreateOrUpdateConfig, + ExecuteMsg::{self as ExecuteRedBank, Borrow, Deposit}, InstantiateMsg as InstantiateRedBank, }, - rewards_collector::InstantiateMsg as InstantiateRewards, + rewards_collector::{InstantiateMsg as InstantiateRewards, RewardConfig, TransferType}, }; use osmosis_std::types::osmosis::{ downtimedetector::v1beta1::Downtime, @@ -1279,12 +1279,22 @@ fn setup_redbank(wasm: &Wasm, signer: &SigningAccount) -> (Strin owner: (signer.address()), address_provider: addr_provider_addr.clone(), safety_tax_rate: Decimal::percent(25), - safety_fund_denom: "uosmo".to_string(), - fee_collector_denom: "uosmo".to_string(), + safety_fund_config: RewardConfig { + target_denom: "uusdc".to_string(), + transfer_type: TransferType::Bank, + }, + revenue_share_tax_rate: Decimal::percent(10), + 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::Ibc, + }, channel_id: "channel-1".to_string(), timeout_seconds: 60, slippage_tolerance: Decimal::new(Uint128::from(1u128)), - neutron_ibc_config: None, }, ); diff --git a/integration-tests/tests/test_rewards_collector.rs b/integration-tests/tests/test_rewards_collector.rs index 86a0fbd86..3d5eb321e 100644 --- a/integration-tests/tests/test_rewards_collector.rs +++ b/integration-tests/tests/test_rewards_collector.rs @@ -1,9 +1,13 @@ -use cosmwasm_std::{coin, Decimal, Uint128}; +use cosmwasm_std::{coin, Decimal, Empty, Uint128}; +use mars_oracle_osmosis::OsmosisPriceSourceUnchecked; use mars_types::{ address_provider::{ ExecuteMsg as ExecuteMsgAddr, InstantiateMsg as InstantiateAddr, MarsAddressType, }, - rewards_collector::{ExecuteMsg, InstantiateMsg as InstantiateRewards, UpdateConfig}, + oracle, + rewards_collector::{ + ExecuteMsg, InstantiateMsg as InstantiateRewards, RewardConfig, TransferType, UpdateConfig, + }, swapper::{EstimateExactInSwapResponse, OsmoRoute, OsmoSwap, QueryMsg, SwapperRoute}, }; use osmosis_test_tube::{Account, Gamm, Module, OsmosisTestApp, Wasm}; @@ -22,12 +26,12 @@ mod helpers; const OSMOSIS_ADDR_PROVIDER_CONTRACT_NAME: &str = "mars-address-provider"; const OSMOSIS_REWARDS_CONTRACT_NAME: &str = "mars-rewards-collector-osmosis"; const OSMOSIS_SWAPPER_CONTRACT_NAME: &str = "mars-swapper-osmosis"; +const OSMOSIS_ORACLE_CONTRACT_NAME: &str = "mars-oracle-osmosis"; #[test] fn swapping_rewards() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); - let accs = app .init_accounts( &[ @@ -52,9 +56,13 @@ fn swapping_rewards() { }, ); - let safety_fund_denom = "uusdc"; + let usdc_denom = "uusdc"; + let mars_denom = "umars"; + let safety_fund_denom = usdc_denom; + let revenue_share_denom = usdc_denom; let fee_collector_denom = "umars"; let safety_tax_rate = Decimal::percent(25); + let revenue_share_tax_rate = Decimal::percent(10); let rewards_addr = instantiate_contract( &wasm, signer, @@ -63,12 +71,22 @@ fn swapping_rewards() { owner: signer.address(), address_provider: addr_provider_addr.clone(), safety_tax_rate, - safety_fund_denom: safety_fund_denom.to_string(), - fee_collector_denom: fee_collector_denom.to_string(), + revenue_share_tax_rate, + safety_fund_config: RewardConfig { + target_denom: safety_fund_denom.to_string(), + transfer_type: TransferType::Bank, + }, + revenue_share_config: RewardConfig { + target_denom: revenue_share_denom.to_string(), + transfer_type: TransferType::Bank, + }, + fee_collector_config: RewardConfig { + target_denom: fee_collector_denom.to_string(), + transfer_type: TransferType::Ibc, + }, channel_id: "channel-1".to_string(), timeout_seconds: 60, - slippage_tolerance: Decimal::percent(1), - neutron_ibc_config: None, + slippage_tolerance: Decimal::percent(5), }, ); @@ -82,6 +100,18 @@ fn swapping_rewards() { }, ); + // Instantiate oracle addr + let oracle_addr = instantiate_contract( + &wasm, + signer, + OSMOSIS_ORACLE_CONTRACT_NAME, + &mars_types::oracle::InstantiateMsg:: { + owner: signer.address(), + base_denom: usdc_denom.to_string(), + custom_init: None, + }, + ); + // Set swapper addr in address provider wasm.execute( &addr_provider_addr, @@ -94,6 +124,71 @@ fn swapping_rewards() { ) .unwrap(); + // Set oracle addr in address provider + wasm.execute( + &addr_provider_addr, + &mars_types::address_provider::ExecuteMsg::SetAddress { + address_type: MarsAddressType::Oracle, + address: oracle_addr.clone(), + }, + &[], + signer, + ) + .unwrap(); + + // Set prices in the oracle + wasm.execute( + &oracle_addr, + &oracle::ExecuteMsg::<_, Empty>::SetPriceSource { + denom: usdc_denom.to_string(), + price_source: OsmosisPriceSourceUnchecked::Fixed { + price: Decimal::one(), + }, + }, + &[], + signer, + ) + .unwrap(); + + wasm.execute( + &oracle_addr, + &oracle::ExecuteMsg::<_, Empty>::SetPriceSource { + denom: mars_denom.to_string(), + price_source: OsmosisPriceSourceUnchecked::Fixed { + price: Decimal::from_ratio(30u128, 20u128), + }, + }, + &[], + signer, + ) + .unwrap(); + + wasm.execute( + &oracle_addr, + &oracle::ExecuteMsg::<_, Empty>::SetPriceSource { + denom: "uatom".to_string(), + price_source: OsmosisPriceSourceUnchecked::Fixed { + price: Decimal::from_ratio(50u128, 20u128), + }, + }, + &[], + signer, + ) + .unwrap(); + + wasm.execute( + &oracle_addr, + &oracle::ExecuteMsg::<_, Empty>::SetPriceSource { + denom: "uosmo".to_string(), + price_source: OsmosisPriceSourceUnchecked::Fixed { + price: Decimal::from_ratio(10u128, 20u128), + }, + }, + &[], + signer, + ) + .unwrap(); + let gamm = Gamm::new(&app); let pool_mars_osmo = gamm .create_basic_pool(&[coin(2_000_000, "umars"), coin(6_000_000, "uosmo")], signer) @@ -111,7 +206,6 @@ fn swapping_rewards() { .data .pool_id; - println!("pre swap"); // swap to create historic index for TWAP swap_to_create_twap_records( &app, @@ -138,8 +232,6 @@ fn swapping_rewards() { 600u64, ); - println!("postSwap"); - // fund contract let bank = Bank::new(&app); bank.send(user, &rewards_addr, &[coin(125u128, "uosmo")]).unwrap(); @@ -153,7 +245,8 @@ fn swapping_rewards() { let fee_collector_denom_balance = bank.query_balance(&rewards_addr, fee_collector_denom); assert_eq!(fee_collector_denom_balance, 0u128); - let safety_fund_amt_swap = Uint128::new(osmo_balance) * safety_tax_rate; + let safety_fund_amt_swap = + Uint128::new(osmo_balance) * (safety_tax_rate + revenue_share_tax_rate); let fee_collector_amt_swap = Uint128::new(osmo_balance) - safety_fund_amt_swap; let safety_fund_route = Some(SwapperRoute::Osmo(OsmoRoute { @@ -172,6 +265,7 @@ fn swapping_rewards() { }, ) .unwrap(); + let safety_fund_min_receive = safety_fund_estimate.amount * Decimal::percent(99); let fee_collector_route = Some(SwapperRoute::Osmo(OsmoRoute { @@ -193,7 +287,6 @@ fn swapping_rewards() { let fee_collector_min_receive = fee_collector_estimate.amount * Decimal::percent(99); // swap osmo - println!("swap osmo"); wasm.execute( &rewards_addr, &ExecuteMsg::SwapAsset { @@ -209,7 +302,8 @@ fn swapping_rewards() { ) .unwrap(); - let safety_fund_amt_swap = Uint128::new(atom_balance) * safety_tax_rate; + let safety_fund_amt_swap = + Uint128::new(atom_balance) * (safety_tax_rate + revenue_share_tax_rate); let fee_collector_amt_swap = Uint128::new(atom_balance) - safety_fund_amt_swap; let safety_fund_route = Some(SwapperRoute::Osmo(OsmoRoute { @@ -320,7 +414,7 @@ fn distribute_rewards_if_ibc_channel_invalid() { &addr_provider_addr, &ExecuteMsgAddr::SetAddress { address_type: MarsAddressType::FeeCollector, - address: "mars17xpfvakm2amg962yls6f84z3kell8c5ldy6e7x".to_string(), + address: "osmo17xfxz0axs6cr7jejqpphuhs7yldnp295acmu9a".to_string(), }, &[], signer, @@ -330,7 +424,18 @@ fn distribute_rewards_if_ibc_channel_invalid() { &addr_provider_addr, &ExecuteMsgAddr::SetAddress { address_type: MarsAddressType::SafetyFund, - address: "mars1s4hgh56can3e33e0zqpnjxh0t5wdf7u3pze575".to_string(), + address: "osmo1f2m24wktq0sw3c0lexlg7fv4kngwyttvzws3a3r3al9ld2s2pvds87jqvf".to_string(), + }, + &[], + signer, + ) + .unwrap(); + + wasm.execute( + &addr_provider_addr, + &ExecuteMsgAddr::SetAddress { + address_type: MarsAddressType::RevenueShare, + address: "osmo14qncu5xag9ec26cx09x6pwncn9w74pq3wyr8rj".to_string(), }, &[], signer, @@ -340,6 +445,7 @@ fn distribute_rewards_if_ibc_channel_invalid() { // setup rewards-collector contract let safety_fund_denom = "uusdc"; let fee_collector_denom = "umars"; + let revenue_share_denom = "uusdc"; let rewards_addr = instantiate_contract( &wasm, signer, @@ -348,12 +454,22 @@ fn distribute_rewards_if_ibc_channel_invalid() { owner: signer.address(), address_provider: addr_provider_addr, safety_tax_rate: Decimal::percent(50), - safety_fund_denom: safety_fund_denom.to_string(), - fee_collector_denom: fee_collector_denom.to_string(), + revenue_share_tax_rate: Decimal::percent(10), + safety_fund_config: RewardConfig { + target_denom: safety_fund_denom.to_string(), + transfer_type: TransferType::Bank, + }, + revenue_share_config: RewardConfig { + target_denom: revenue_share_denom.to_string(), + transfer_type: TransferType::Bank, + }, + fee_collector_config: RewardConfig { + target_denom: fee_collector_denom.to_string(), + transfer_type: TransferType::Ibc, + }, channel_id: "".to_string(), timeout_seconds: 60, slippage_tolerance: Decimal::percent(1), - neutron_ibc_config: None, }, ); @@ -368,13 +484,12 @@ fn distribute_rewards_if_ibc_channel_invalid() { let mars_balance = bank.query_balance(&rewards_addr, "umars"); assert_eq!(mars_balance, mars_balance); - // distribute usdc + // distribute umars rewards let res = wasm .execute( &rewards_addr, &ExecuteMsg::DistributeRewards { - denom: "uusdc".to_string(), - amount: None, + denom: "umars".to_string(), }, &[], signer, @@ -389,12 +504,13 @@ fn distribute_rewards_if_ibc_channel_invalid() { new_cfg: UpdateConfig { address_provider: None, safety_tax_rate: None, - safety_fund_denom: None, - fee_collector_denom: None, + revenue_share_tax_rate: None, + safety_fund_config: None, + revenue_share_config: None, + fee_collector_config: None, channel_id: Some("channel-1".to_string()), timeout_seconds: None, slippage_tolerance: None, - neutron_ibc_config: None, }, }, &[], @@ -402,13 +518,12 @@ fn distribute_rewards_if_ibc_channel_invalid() { ) .unwrap(); - // distribute mars + // distribute rewards let res = wasm .execute( &rewards_addr, &ExecuteMsg::DistributeRewards { denom: "umars".to_string(), - amount: None, }, &[], signer, diff --git a/packages/testing/src/integration/mock_env.rs b/packages/testing/src/integration/mock_env.rs index 47b8cae00..31dae64f8 100644 --- a/packages/testing/src/integration/mock_env.rs +++ b/packages/testing/src/integration/mock_env.rs @@ -22,7 +22,7 @@ use mars_types::{ self, CreateOrUpdateConfig, InitOrUpdateAssetParams, Market, MarketV2Response, UserCollateralResponse, UserDebtResponse, UserPositionResponse, }, - rewards_collector, + rewards_collector::{self, RewardConfig}, }; use pyth_sdk_cw::PriceIdentifier; @@ -836,8 +836,10 @@ pub struct MockEnvBuilder { // rewards-collector params safety_tax_rate: Decimal, - safety_fund_denom: String, - fee_collector_denom: String, + revenue_share_tax_rate: Decimal, + safety_fund_config: RewardConfig, + revenue_share_config: RewardConfig, + fee_collector_config: RewardConfig, slippage_tolerance: Decimal, pyth_contract_addr: String, @@ -856,9 +858,20 @@ impl MockEnvBuilder { base_denom: "uosmo".to_string(), base_denom_decimals: 6u8, target_health_factor: Decimal::from_str("1.05").unwrap(), - safety_tax_rate: Decimal::percent(50), - safety_fund_denom: "uusdc".to_string(), - fee_collector_denom: "uusdc".to_string(), + safety_tax_rate: Decimal::percent(45), + revenue_share_tax_rate: Decimal::percent(10), + safety_fund_config: RewardConfig { + target_denom: "uusdc".to_string(), + transfer_type: rewards_collector::TransferType::Bank, + }, + revenue_share_config: RewardConfig { + target_denom: "uusdc".to_string(), + transfer_type: rewards_collector::TransferType::Bank, + }, + fee_collector_config: RewardConfig { + target_denom: "umars".to_string(), + transfer_type: rewards_collector::TransferType::Ibc, + }, slippage_tolerance: Decimal::percent(5), pyth_contract_addr: "osmo1svg55quy7jjee6dn0qx85qxxvx5cafkkw4tmqpcjr9dx99l0zrhs4usft5" .to_string(), // correct bech32 addr to pass validation @@ -887,18 +900,18 @@ impl MockEnvBuilder { self } - pub fn safety_tax_rate(&mut self, percentage: Decimal) -> &mut Self { - self.safety_tax_rate = percentage; + pub fn safety_fund_config(&mut self, config: RewardConfig) -> &mut Self { + self.safety_fund_config = config; self } - pub fn safety_fund_denom(&mut self, denom: &str) -> &mut Self { - self.safety_fund_denom = denom.to_string(); + pub fn revenue_share_config(&mut self, config: RewardConfig) -> &mut Self { + self.revenue_share_config = config; self } - pub fn fee_collector_denom(&mut self, denom: &str) -> &mut Self { - self.fee_collector_denom = denom.to_string(); + pub fn fee_collector_config(&mut self, config: RewardConfig) -> &mut Self { + self.fee_collector_config = config; self } @@ -1068,12 +1081,13 @@ impl MockEnvBuilder { owner: self.owner.to_string(), address_provider: address_provider_addr.to_string(), safety_tax_rate: self.safety_tax_rate, - safety_fund_denom: self.safety_fund_denom.clone(), - fee_collector_denom: self.fee_collector_denom.clone(), + revenue_share_tax_rate: self.revenue_share_tax_rate, + safety_fund_config: self.safety_fund_config.clone(), + revenue_share_config: self.revenue_share_config.clone(), + fee_collector_config: self.fee_collector_config.clone(), channel_id: "0".to_string(), timeout_seconds: 900, slippage_tolerance: self.slippage_tolerance, - neutron_ibc_config: None, }, &[], "rewards-collector", diff --git a/packages/testing/src/lib.rs b/packages/testing/src/lib.rs index 1996c8f5a..80edad581 100644 --- a/packages/testing/src/lib.rs +++ b/packages/testing/src/lib.rs @@ -19,6 +19,7 @@ mod params_querier; mod pyth_querier; mod red_bank_querier; mod redemption_rate_querier; +mod swapper_querier; pub mod test_runner; #[cfg(feature = "astroport")] pub mod wasm_oracle; diff --git a/packages/testing/src/mars_mock_querier.rs b/packages/testing/src/mars_mock_querier.rs index 89890ba43..88938ade9 100644 --- a/packages/testing/src/mars_mock_querier.rs +++ b/packages/testing/src/mars_mock_querier.rs @@ -27,6 +27,7 @@ use crate::{ pyth_querier::PythQuerier, red_bank_querier::RedBankQuerier, redemption_rate_querier::RedemptionRateQuerier, + swapper_querier::SwapperQuerier, }; pub struct MarsMockQuerier { @@ -40,6 +41,7 @@ pub struct MarsMockQuerier { redemption_rate_querier: RedemptionRateQuerier, params_querier: ParamsQuerier, cosmwasm_pool_queries: CosmWasmPoolQuerier, + swapper_querier: SwapperQuerier, } impl Querier for MarsMockQuerier { @@ -71,6 +73,7 @@ impl MarsMockQuerier { redemption_rate_querier: Default::default(), params_querier: ParamsQuerier::default(), cosmwasm_pool_queries: CosmWasmPoolQuerier::default(), + swapper_querier: SwapperQuerier::default(), } } @@ -130,6 +133,10 @@ impl MarsMockQuerier { self.osmosis_querier.pools.insert(pool_id, pool_response); } + pub fn set_swapper_estimate_price(&mut self, denom: &str, price: Decimal) { + self.swapper_querier.swap_prices.insert(denom.to_string(), price); + } + pub fn set_spot_price( &mut self, id: u64, @@ -297,9 +304,13 @@ impl MarsMockQuerier { return self.params_querier.handle_query(params_query); } + // Swapper Queries + if let Ok(swapper_query) = from_json::(msg) { + return self.swapper_querier.handle_query(&contract_addr, swapper_query); + } + // CosmWasm pool Queries if let Ok(cw_pool_query) = from_json::(msg) { - println!("query: {:?}", cw_pool_query); return self.cosmwasm_pool_queries.handle_query(cw_pool_query); } diff --git a/packages/testing/src/swapper_querier.rs b/packages/testing/src/swapper_querier.rs new file mode 100644 index 000000000..df0cf2a14 --- /dev/null +++ b/packages/testing/src/swapper_querier.rs @@ -0,0 +1,35 @@ +use std::collections::HashMap; + +use cosmwasm_std::{to_json_binary, Addr, Decimal, QuerierResult}; +use mars_types::swapper::{EstimateExactInSwapResponse, QueryMsg}; + +#[derive(Default)] +pub struct SwapperQuerier { + pub swap_prices: HashMap, +} + +impl SwapperQuerier { + pub fn handle_query(&self, _contract_addr: &Addr, query: QueryMsg) -> QuerierResult { + let ret = match query { + QueryMsg::EstimateExactInSwap { + coin_in, + denom_out, + route: _, + } => { + let denom_in = coin_in.denom.clone(); + let denom_in_price = self.swap_prices.get(&denom_in).unwrap(); + let denom_out_price = self.swap_prices.get(&denom_out).unwrap(); + + let price = denom_in_price / denom_out_price; + let amount = coin_in.amount * price; + to_json_binary(&EstimateExactInSwapResponse { + amount, + }) + .into() + } + _ => Err("[mock]: Unsupported swapper query").into(), + }; + + Ok(ret).into() + } +} diff --git a/packages/types/src/address_provider.rs b/packages/types/src/address_provider.rs index 5252f07a1..fd47f6d6b 100644 --- a/packages/types/src/address_provider.rs +++ b/packages/types/src/address_provider.rs @@ -37,6 +37,8 @@ pub enum MarsAddressType { Swapper, /// Astroport incentives contract AstroportIncentives, + /// The address that shall receive the revenue share given to neutron (10%) + RevenueShare, } impl fmt::Display for MarsAddressType { @@ -53,6 +55,7 @@ impl fmt::Display for MarsAddressType { MarsAddressType::SafetyFund => "safety_fund", MarsAddressType::Swapper => "swapper", MarsAddressType::AstroportIncentives => "astroport_incentives", + MarsAddressType::RevenueShare => "revenue_share", }; write!(f, "{s}") } @@ -74,6 +77,7 @@ impl FromStr for MarsAddressType { "safety_fund" => Ok(MarsAddressType::SafetyFund), "swapper" => Ok(MarsAddressType::Swapper), "astroport_incentives" => Ok(MarsAddressType::AstroportIncentives), + "revenue_share" => Ok(MarsAddressType::RevenueShare), _ => Err(StdError::parse_err(type_name::(), s)), } } diff --git a/packages/types/src/rewards_collector.rs b/packages/types/src/rewards_collector.rs index 6748da6ac..e05d47540 100644 --- a/packages/types/src/rewards_collector.rs +++ b/packages/types/src/rewards_collector.rs @@ -1,5 +1,7 @@ +use std::fmt; + use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Api, Coin, Decimal, StdResult, Uint128}; +use cosmwasm_std::{Addr, Api, Decimal, StdResult, Uint128}; use mars_owner::OwnerUpdate; use mars_utils::{ error::ValidationError, @@ -18,18 +20,44 @@ pub struct InstantiateMsg { pub address_provider: String, /// Percentage of fees that are sent to the safety fund pub safety_tax_rate: Decimal, - /// The asset to which the safety fund share is converted - pub safety_fund_denom: String, - /// The asset to which the fee collector share is converted - pub fee_collector_denom: String, - /// The channel ID of the mars hub + /// Percentage of fees that are sent to the revenue share + pub revenue_share_tax_rate: Decimal, + /// Configuration for the safety fund reward share + pub safety_fund_config: RewardConfig, + /// Configuration for the revenue share reward share + pub revenue_share_config: RewardConfig, + /// Configuration for the fee collector reward share + pub fee_collector_config: RewardConfig, + /// The channel ID of neutron-1 pub channel_id: String, /// Number of seconds after which an IBC transfer is to be considered failed, if no acknowledgement is received pub timeout_seconds: u64, /// Maximum percentage of price movement (minimum amount you accept to receive during swap) pub slippage_tolerance: Decimal, - /// Neutron Ibc config - pub neutron_ibc_config: Option, +} +#[cw_serde] +pub enum TransferType { + // Use IBC to distribute rewards cross chain + Ibc, + // Use bank send to distribute rewards to a local address + Bank, +} + +impl fmt::Display for TransferType { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + TransferType::Ibc => write!(f, "Ibc"), + TransferType::Bank => write!(f, "Bank"), + } + } +} + +#[cw_serde] +pub struct RewardConfig { + /// The denomination in which rewards will be distributed + pub target_denom: String, + /// The method of reward distribution (IBC or Bank transfer) + pub transfer_type: TransferType, } #[cw_serde] @@ -38,30 +66,26 @@ pub struct Config { pub address_provider: Addr, /// Percentage of fees that are sent to the safety fund pub safety_tax_rate: Decimal, - /// The asset to which the safety fund share is converted - pub safety_fund_denom: String, - /// The asset to which the fee collector share is converted - pub fee_collector_denom: String, - /// The channel ID of the mars hub + /// Percentage of fees that are sent to the revenue share + pub revenue_share_tax_rate: Decimal, + /// Configuration for the safety fund transfer + pub safety_fund_config: RewardConfig, + /// Configuration for the revenue share parameters + pub revenue_share_config: RewardConfig, + /// Configuration for the fee collector parameters + pub fee_collector_config: RewardConfig, + /// The channel ID for osmosis -> neutron pub channel_id: String, /// Number of seconds after which an IBC transfer is to be considered failed, if no acknowledgement is received pub timeout_seconds: u64, /// Maximum percentage of price movement (minimum amount you accept to receive during swap) pub slippage_tolerance: Decimal, - /// Neutron IBC config - pub neutron_ibc_config: Option, -} - -#[cw_serde] -pub struct NeutronIbcConfig { - pub source_port: String, - pub acc_fee: Vec, - pub timeout_fee: Vec, } impl Config { pub fn validate(&self) -> Result<(), ValidationError> { - decimal_param_le_one(self.safety_tax_rate, "safety_tax_rate")?; + let total_tax_rate = self.safety_tax_rate + self.revenue_share_tax_rate; + decimal_param_le_one(total_tax_rate, "total_tax_rate")?; integer_param_gt_zero(self.timeout_seconds, "timeout_seconds")?; @@ -73,8 +97,15 @@ impl Config { }); } - validate_native_denom(&self.safety_fund_denom)?; - validate_native_denom(&self.fee_collector_denom)?; + // There is an assumption that revenue share and safety fund are swapped to the same denom + assert_eq!(self.safety_fund_config.target_denom, self.revenue_share_config.target_denom); + + // Ensure that the fee collector is a different denom than the safety fund and revenue share + assert_ne!(self.fee_collector_config.target_denom, self.safety_fund_config.target_denom); + + validate_native_denom(&self.safety_fund_config.target_denom)?; + validate_native_denom(&self.revenue_share_config.target_denom)?; + validate_native_denom(&self.fee_collector_config.target_denom)?; Ok(()) } @@ -85,12 +116,13 @@ impl Config { Ok(Config { address_provider: api.addr_validate(&msg.address_provider)?, safety_tax_rate: msg.safety_tax_rate, - safety_fund_denom: msg.safety_fund_denom, - fee_collector_denom: msg.fee_collector_denom, + revenue_share_tax_rate: msg.revenue_share_tax_rate, + safety_fund_config: msg.safety_fund_config, + revenue_share_config: msg.revenue_share_config, + fee_collector_config: msg.fee_collector_config, channel_id: msg.channel_id, timeout_seconds: msg.timeout_seconds, slippage_tolerance: msg.slippage_tolerance, - neutron_ibc_config: msg.neutron_ibc_config, }) } } @@ -102,18 +134,20 @@ pub struct UpdateConfig { pub address_provider: Option, /// Percentage of fees that are sent to the safety fund pub safety_tax_rate: Option, - /// The asset to which the safety fund share is converted - pub safety_fund_denom: Option, - /// The asset to which the fee collector share is converted - pub fee_collector_denom: Option, - /// The channel id of the mars hub + /// Percentage of fees that are sent to the revenue share + pub revenue_share_tax_rate: Option, + /// Safety fund configuration + pub safety_fund_config: Option, + /// Revenue share configuration + pub revenue_share_config: Option, + /// Fee collector configuration + pub fee_collector_config: Option, + /// The channel id for osmosis -> neutron pub channel_id: Option, /// Number of seconds after which an IBC transfer is to be considered failed, if no acknowledgement is received pub timeout_seconds: Option, /// Maximum percentage of price movement (minimum amount you accept to receive during swap) pub slippage_tolerance: Option, - /// Neutron Ibc config - pub neutron_ibc_config: Option, } #[cw_serde] @@ -138,12 +172,11 @@ pub enum ExecuteMsg { actions: Vec, }, - /// Distribute the accrued protocol income between the safety fund and the fee modules on mars hub, - /// according to the split set in config. + /// Distribute the accrued protocol income between the safety fund, fee collector and + /// revenue share addresses, according to the split set in config. /// Callable by any address. DistributeRewards { denom: String, - amount: Option, }, /// Swap any asset on the contract @@ -182,18 +215,20 @@ pub struct ConfigResponse { pub address_provider: String, /// Percentage of fees that are sent to the safety fund pub safety_tax_rate: Decimal, - /// The asset to which the safety fund share is converted - pub safety_fund_denom: String, - /// The asset to which the fee collector share is converted - pub fee_collector_denom: String, - /// The channel ID of the mars hub + /// Percentage of fees that are sent to the revenue share + pub revenue_share_tax_rate: Decimal, + /// Configuration for the safety fund parameters + pub safety_fund_config: RewardConfig, + /// Configuration for the revenue share parameters + pub revenue_share_config: RewardConfig, + /// Configuration for the fee collector parameters + pub fee_collector_config: RewardConfig, + /// The channel ID for osmosis -> neutron pub channel_id: String, /// Number of seconds after which an IBC transfer is to be considered failed, if no acknowledgement is received pub timeout_seconds: u64, /// Maximum percentage of price movement (minimum amount you accept to receive during swap) pub slippage_tolerance: Decimal, - /// Neutron Ibc config - pub neutron_ibc_config: Option, } #[cw_serde] @@ -205,7 +240,13 @@ pub enum QueryMsg { } #[cw_serde] -pub enum MigrateMsg { - V1_0_0ToV2_0_0 {}, - V2_0_0ToV2_0_1 {}, +pub enum OsmosisMigrateMsg { + V2_0_0ToV2_1_0 {}, + V2_1_0ToV2_1_1 {}, +} + +#[cw_serde] +pub enum NeutronMigrateMsg { + V2_1_0ToV2_2_0 {}, + V2_2_0ToV2_2_1 {}, } diff --git a/schemas/mars-address-provider/mars-address-provider.json b/schemas/mars-address-provider/mars-address-provider.json index 78f030b4d..239f1089a 100644 --- a/schemas/mars-address-provider/mars-address-provider.json +++ b/schemas/mars-address-provider/mars-address-provider.json @@ -1,6 +1,6 @@ { "contract_name": "mars-address-provider", - "contract_version": "2.1.0", + "contract_version": "2.1.1", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -115,6 +115,13 @@ "enum": [ "astroport_incentives" ] + }, + { + "description": "The address that shall receive the revenue share given to neutron (10%)", + "type": "string", + "enum": [ + "revenue_share" + ] } ] }, @@ -326,6 +333,13 @@ "enum": [ "astroport_incentives" ] + }, + { + "description": "The address that shall receive the revenue share given to neutron (10%)", + "type": "string", + "enum": [ + "revenue_share" + ] } ] } @@ -405,6 +419,13 @@ "enum": [ "astroport_incentives" ] + }, + { + "description": "The address that shall receive the revenue share given to neutron (10%)", + "type": "string", + "enum": [ + "revenue_share" + ] } ] } @@ -487,6 +508,13 @@ "enum": [ "astroport_incentives" ] + }, + { + "description": "The address that shall receive the revenue share given to neutron (10%)", + "type": "string", + "enum": [ + "revenue_share" + ] } ] } @@ -569,6 +597,13 @@ "enum": [ "astroport_incentives" ] + }, + { + "description": "The address that shall receive the revenue share given to neutron (10%)", + "type": "string", + "enum": [ + "revenue_share" + ] } ] } diff --git a/schemas/mars-rewards-collector-base/mars-rewards-collector-base.json b/schemas/mars-rewards-collector-base/mars-rewards-collector-base.json index 0be52f018..0b1bfe28b 100644 --- a/schemas/mars-rewards-collector-base/mars-rewards-collector-base.json +++ b/schemas/mars-rewards-collector-base/mars-rewards-collector-base.json @@ -9,9 +9,11 @@ "required": [ "address_provider", "channel_id", - "fee_collector_denom", + "fee_collector_config", "owner", - "safety_fund_denom", + "revenue_share_config", + "revenue_share_tax_rate", + "safety_fund_config", "safety_tax_rate", "slippage_tolerance", "timeout_seconds" @@ -22,21 +24,14 @@ "type": "string" }, "channel_id": { - "description": "The channel ID of the mars hub", + "description": "The channel ID of neutron-1", "type": "string" }, - "fee_collector_denom": { - "description": "The asset to which the fee collector share is converted", - "type": "string" - }, - "neutron_ibc_config": { - "description": "Neutron Ibc config", - "anyOf": [ - { - "$ref": "#/definitions/NeutronIbcConfig" - }, + "fee_collector_config": { + "description": "Configuration for the fee collector reward share", + "allOf": [ { - "type": "null" + "$ref": "#/definitions/RewardConfig" } ] }, @@ -44,9 +39,29 @@ "description": "The contract's owner", "type": "string" }, - "safety_fund_denom": { - "description": "The asset to which the safety fund share is converted", - "type": "string" + "revenue_share_config": { + "description": "Configuration for the revenue share reward share", + "allOf": [ + { + "$ref": "#/definitions/RewardConfig" + } + ] + }, + "revenue_share_tax_rate": { + "description": "Percentage of fees that are sent to the revenue share", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "safety_fund_config": { + "description": "Configuration for the safety fund reward share", + "allOf": [ + { + "$ref": "#/definitions/RewardConfig" + } + ] }, "safety_tax_rate": { "description": "Percentage of fees that are sent to the safety fund", @@ -73,54 +88,38 @@ }, "additionalProperties": false, "definitions": { - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, "Decimal": { "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", "type": "string" }, - "NeutronIbcConfig": { + "RewardConfig": { "type": "object", "required": [ - "acc_fee", - "source_port", - "timeout_fee" + "target_denom", + "transfer_type" ], "properties": { - "acc_fee": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "source_port": { + "target_denom": { + "description": "The denomination in which rewards will be distributed", "type": "string" }, - "timeout_fee": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } + "transfer_type": { + "description": "The method of reward distribution (IBC or Bank transfer)", + "allOf": [ + { + "$ref": "#/definitions/TransferType" + } + ] } }, "additionalProperties": false }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" + "TransferType": { + "type": "string", + "enum": [ + "ibc", + "bank" + ] } } }, @@ -225,7 +224,7 @@ "additionalProperties": false }, { - "description": "Distribute the accrued protocol income between the safety fund and the fee modules on mars hub, according to the split set in config. Callable by any address.", + "description": "Distribute the accrued protocol income between the safety fund, fee collector and revenue share addresses, according to the split set in config. Callable by any address.", "type": "object", "required": [ "distribute_rewards" @@ -237,16 +236,6 @@ "denom" ], "properties": { - "amount": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, "denom": { "type": "string" } @@ -993,32 +982,6 @@ } ] }, - "NeutronIbcConfig": { - "type": "object", - "required": [ - "acc_fee", - "source_port", - "timeout_fee" - ], - "properties": { - "acc_fee": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "source_port": { - "type": "string" - }, - "timeout_fee": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - } - }, - "additionalProperties": false - }, "OsmoRoute": { "type": "object", "required": [ @@ -1131,6 +1094,28 @@ } ] }, + "RewardConfig": { + "type": "object", + "required": [ + "target_denom", + "transfer_type" + ], + "properties": { + "target_denom": { + "description": "The denomination in which rewards will be distributed", + "type": "string" + }, + "transfer_type": { + "description": "The method of reward distribution (IBC or Bank transfer)", + "allOf": [ + { + "$ref": "#/definitions/TransferType" + } + ] + } + }, + "additionalProperties": false + }, "SwapperRoute": { "oneOf": [ { @@ -1159,6 +1144,13 @@ } ] }, + "TransferType": { + "type": "string", + "enum": [ + "ibc", + "bank" + ] + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" @@ -1174,35 +1166,54 @@ ] }, "channel_id": { - "description": "The channel id of the mars hub", + "description": "The channel id for osmosis -> neutron", "type": [ "string", "null" ] }, - "fee_collector_denom": { - "description": "The asset to which the fee collector share is converted", - "type": [ - "string", - "null" + "fee_collector_config": { + "description": "Fee collector configuration", + "anyOf": [ + { + "$ref": "#/definitions/RewardConfig" + }, + { + "type": "null" + } ] }, - "neutron_ibc_config": { - "description": "Neutron Ibc config", + "revenue_share_config": { + "description": "Revenue share configuration", "anyOf": [ { - "$ref": "#/definitions/NeutronIbcConfig" + "$ref": "#/definitions/RewardConfig" }, { "type": "null" } ] }, - "safety_fund_denom": { - "description": "The asset to which the safety fund share is converted", - "type": [ - "string", - "null" + "revenue_share_tax_rate": { + "description": "Percentage of fees that are sent to the revenue share", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "safety_fund_config": { + "description": "Safety fund configuration", + "anyOf": [ + { + "$ref": "#/definitions/RewardConfig" + }, + { + "type": "null" + } ] }, "safety_tax_rate": { @@ -1291,8 +1302,10 @@ "required": [ "address_provider", "channel_id", - "fee_collector_denom", - "safety_fund_denom", + "fee_collector_config", + "revenue_share_config", + "revenue_share_tax_rate", + "safety_fund_config", "safety_tax_rate", "slippage_tolerance", "timeout_seconds" @@ -1303,21 +1316,14 @@ "type": "string" }, "channel_id": { - "description": "The channel ID of the mars hub", + "description": "The channel ID for osmosis -> neutron", "type": "string" }, - "fee_collector_denom": { - "description": "The asset to which the fee collector share is converted", - "type": "string" - }, - "neutron_ibc_config": { - "description": "Neutron Ibc config", - "anyOf": [ - { - "$ref": "#/definitions/NeutronIbcConfig" - }, + "fee_collector_config": { + "description": "Configuration for the fee collector parameters", + "allOf": [ { - "type": "null" + "$ref": "#/definitions/RewardConfig" } ] }, @@ -1335,9 +1341,29 @@ "null" ] }, - "safety_fund_denom": { - "description": "The asset to which the safety fund share is converted", - "type": "string" + "revenue_share_config": { + "description": "Configuration for the revenue share parameters", + "allOf": [ + { + "$ref": "#/definitions/RewardConfig" + } + ] + }, + "revenue_share_tax_rate": { + "description": "Percentage of fees that are sent to the revenue share", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "safety_fund_config": { + "description": "Configuration for the safety fund parameters", + "allOf": [ + { + "$ref": "#/definitions/RewardConfig" + } + ] }, "safety_tax_rate": { "description": "Percentage of fees that are sent to the safety fund", @@ -1364,54 +1390,38 @@ }, "additionalProperties": false, "definitions": { - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, "Decimal": { "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", "type": "string" }, - "NeutronIbcConfig": { + "RewardConfig": { "type": "object", "required": [ - "acc_fee", - "source_port", - "timeout_fee" + "target_denom", + "transfer_type" ], "properties": { - "acc_fee": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "source_port": { + "target_denom": { + "description": "The denomination in which rewards will be distributed", "type": "string" }, - "timeout_fee": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } + "transfer_type": { + "description": "The method of reward distribution (IBC or Bank transfer)", + "allOf": [ + { + "$ref": "#/definitions/TransferType" + } + ] } }, "additionalProperties": false }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" + "TransferType": { + "type": "string", + "enum": [ + "ibc", + "bank" + ] } } } diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index fa5440090..f7390014c 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -393,12 +393,13 @@ export class Deployer { owner: this.deployerAddr, address_provider: this.storage.addresses['addressProvider']!, safety_tax_rate: this.config.rewardsCollector.safetyFundFeeShare, - safety_fund_denom: this.config.rewardsCollector.safetyFundDenom, - fee_collector_denom: this.config.rewardsCollector.feeCollectorDenom, + revenue_share_tax_rate: this.config.rewardsCollector.revenueShare, + revenue_share_config: this.config.rewardsCollector.revenueShareConfig, + safety_fund_config: this.config.rewardsCollector.safetyFundConfig, + fee_collector_config: this.config.rewardsCollector.feeCollectorConfig, channel_id: this.config.rewardsCollector.channelId, timeout_seconds: this.config.rewardsCollector.timeoutSeconds, slippage_tolerance: this.config.rewardsCollector.slippageTolerance, - neutron_ibc_config: this.config.rewardsCollector.neutronIbcConfig, } await this.instantiate('rewardsCollector', this.storage.codeIds['rewardsCollector']!, msg) } diff --git a/scripts/deploy/neutron/devnet-config.ts b/scripts/deploy/neutron/devnet-config.ts index e72f19ef4..04a16db97 100644 --- a/scripts/deploy/neutron/devnet-config.ts +++ b/scripts/deploy/neutron/devnet-config.ts @@ -1,5 +1,4 @@ import { DeploymentConfig, AssetConfig, OracleConfig } from '../../types/config' -import { NeutronIbcConfig } from '../../types/generated/mars-rewards-collector-base/MarsRewardsCollectorBase.types' const nobleUsdcDenom = 'ibc/B559A80D62249C8AA07A380E2A2BEA6E5CA9A6F079C912C3A9E9B494105E4F81' const atomDenom = 'ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9' @@ -44,23 +43,6 @@ const pythAtomID = 'b00b60f88b03a6a625a8d1c048c3f66653edf217439983d037e7222c4e61 const pythUsdcID = 'eaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a' const pythNtrnID = 'a8e6517966a52cb1df864b2764f3629fde3f21d2b640b5c572fcd654cbccd65e' -// IBC config for rewards-collector. See https://rest-palvus.pion-1.ntrn.tech/neutron-org/neutron/feerefunder/params -export const neutronIbcConfig: NeutronIbcConfig = { - source_port: 'transfer', - acc_fee: [ - { - denom: 'untrn', - amount: '1000', - }, - ], - timeout_fee: [ - { - denom: 'untrn', - amount: '1000', - }, - ], -} - // Oracle configurations export const ntrnOracle: OracleConfig = { denom: 'untrn', @@ -441,11 +423,21 @@ export const neutronDevnetConfig: DeploymentConfig = { name: 'neutron', timeoutSeconds: 600, channelId: marsNeutronChannelId, - safetyFundFeeShare: '0.5', - feeCollectorDenom: marsDenom, - safetyFundDenom: nobleUsdcDenom, + safetyFundFeeShare: '0.45', + revenueShare: '0.1', + revenueShareConfig: { + target_denom: nobleUsdcDenom, + transfer_type: 'bank', + }, + safetyFundConfig: { + target_denom: nobleUsdcDenom, + transfer_type: 'bank', + }, + feeCollectorConfig: { + target_denom: marsDenom, + transfer_type: 'ibc', + }, slippageTolerance: '0.01', - neutronIbcConfig: neutronIbcConfig, }, incentives: { epochDuration: 604800, // 1 week diff --git a/scripts/deploy/neutron/mainnet-config.ts b/scripts/deploy/neutron/mainnet-config.ts index 94aa4a910..8b2d322ce 100644 --- a/scripts/deploy/neutron/mainnet-config.ts +++ b/scripts/deploy/neutron/mainnet-config.ts @@ -1,5 +1,4 @@ import { DeploymentConfig, AssetConfig, OracleConfig } from '../../types/config' -import { NeutronIbcConfig } from '../../types/generated/mars-rewards-collector-base/MarsRewardsCollectorBase.types' const axlUsdcDenom = 'ibc/F082B65C88E4B6D5EF1DB243CDA1D331D002759E938A0F5CD3FFDC5D53B3E349' const atomDenom = 'ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9' @@ -27,23 +26,6 @@ const pythAddr = 'neutron1m2emc93m9gpwgsrsf2vylv9xvgqh654630v7dfrhrkmr5slly53spg const pythAtomID = 'b00b60f88b03a6a625a8d1c048c3f66653edf217439983d037e7222c4e612819' const pythUsdcID = 'eaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a' -// IBC config for rewards-collector. See https://neutron.rpc.p2p.world/qgrnU6PsQZA8F9S5Fb8Fn3tV3kXmMBl2M9bcc9jWLjQy8p/lcd/neutron-org/neutron/feerefunder/params -export const neutronIbcConfig: NeutronIbcConfig = { - source_port: 'transfer', - acc_fee: [ - { - denom: 'untrn', - amount: '100000', - }, - ], - timeout_fee: [ - { - denom: 'untrn', - amount: '100000', - }, - ], -} - // Oracle configurations export const marsOracle: OracleConfig = { denom: marsDenom, @@ -381,11 +363,21 @@ export const neutronMainnetConfig: DeploymentConfig = { name: 'neutron', timeoutSeconds: 600, channelId: marsNeutronChannelId, - safetyFundFeeShare: '0.5', - feeCollectorDenom: marsDenom, - safetyFundDenom: axlUsdcDenom, + safetyFundFeeShare: '0.45', + revenueShare: '0.1', + revenueShareConfig: { + target_denom: axlUsdcDenom, + transfer_type: 'bank', + }, + safetyFundConfig: { + target_denom: axlUsdcDenom, + transfer_type: 'bank', + }, + feeCollectorConfig: { + target_denom: marsDenom, + transfer_type: 'ibc', + }, slippageTolerance: '0.01', - neutronIbcConfig: neutronIbcConfig, }, incentives: { epochDuration: 604800, // 1 week diff --git a/scripts/deploy/neutron/testnet-config.ts b/scripts/deploy/neutron/testnet-config.ts index 1d9e3a051..56e3766ea 100644 --- a/scripts/deploy/neutron/testnet-config.ts +++ b/scripts/deploy/neutron/testnet-config.ts @@ -1,5 +1,4 @@ import { DeploymentConfig, AssetConfig, OracleConfig } from '../../types/config' -import { NeutronIbcConfig } from '../../types/generated/mars-rewards-collector-base/MarsRewardsCollectorBase.types' const nobleUsdcDenom = 'ibc/4C19E7EC06C1AB2EC2D70C6855FEB6D48E9CE174913991DA0A517D21978E7E42' const atomDenom = 'ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9' @@ -32,23 +31,6 @@ const pythAtomID = 'b00b60f88b03a6a625a8d1c048c3f66653edf217439983d037e7222c4e61 const pythUsdcID = 'eaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a' const pythNtrnID = 'a8e6517966a52cb1df864b2764f3629fde3f21d2b640b5c572fcd654cbccd65e' -// IBC config for rewards-collector. See https://rest-palvus.pion-1.ntrn.tech/neutron-org/neutron/feerefunder/params -export const neutronIbcConfig: NeutronIbcConfig = { - source_port: 'transfer', - acc_fee: [ - { - denom: 'untrn', - amount: '1000', - }, - ], - timeout_fee: [ - { - denom: 'untrn', - amount: '1000', - }, - ], -} - // Oracle configurations export const ntrnOracle: OracleConfig = { denom: 'untrn', @@ -460,11 +442,21 @@ export const neutronTestnetConfig: DeploymentConfig = { name: 'neutron', timeoutSeconds: 600, channelId: marsNeutronChannelId, - safetyFundFeeShare: '0.5', - feeCollectorDenom: marsDenom, - safetyFundDenom: nobleUsdcDenom, + safetyFundFeeShare: '0.45', + revenueShare: '0.1', + revenueShareConfig: { + target_denom: nobleUsdcDenom, + transfer_type: 'bank', + }, + safetyFundConfig: { + target_denom: nobleUsdcDenom, + transfer_type: 'bank', + }, + feeCollectorConfig: { + target_denom: marsDenom, + transfer_type: 'ibc', + }, slippageTolerance: '0.01', - neutronIbcConfig: neutronIbcConfig, }, incentives: { epochDuration: 604800, // 1 week diff --git a/scripts/deploy/osmosis/mainnet-config.ts b/scripts/deploy/osmosis/mainnet-config.ts index c6ce31bba..ec44a7c5b 100644 --- a/scripts/deploy/osmosis/mainnet-config.ts +++ b/scripts/deploy/osmosis/mainnet-config.ts @@ -1016,9 +1016,20 @@ export const osmosisMainnetConfig: DeploymentConfig = { name: 'osmosis', timeoutSeconds: 600, channelId: 'channel-557', - safetyFundFeeShare: '0.5', - feeCollectorDenom: mars, - safetyFundDenom: axlUSDC, + safetyFundFeeShare: '0.45', + revenueShare: '0.1', + feeCollectorConfig: { + target_denom: mars, + transfer_type: 'ibc', + }, + safetyFundConfig: { + target_denom: axlUSDC, + transfer_type: 'ibc', + }, + revenueShareConfig: { + target_denom: axlUSDC, + transfer_type: 'ibc', + }, slippageTolerance: '0.01', }, incentives: { diff --git a/scripts/deploy/osmosis/testnet-config.ts b/scripts/deploy/osmosis/testnet-config.ts index eb4ad8cdc..5d6ef9952 100644 --- a/scripts/deploy/osmosis/testnet-config.ts +++ b/scripts/deploy/osmosis/testnet-config.ts @@ -257,9 +257,20 @@ export const osmosisTestnetConfig: DeploymentConfig = { name: 'osmosis', timeoutSeconds: 600, channelId: 'channel-2083', - safetyFundFeeShare: '0.5', - feeCollectorDenom: mars, - safetyFundDenom: aUSDC, + safetyFundFeeShare: '0.45', + revenueShare: '0.1', + feeCollectorConfig: { + target_denom: mars, + transfer_type: 'ibc', + }, + revenueShareConfig: { + target_denom: aUSDC, + transfer_type: 'bank', + }, + safetyFundConfig: { + target_denom: aUSDC, + transfer_type: 'bank', + }, slippageTolerance: '0.01', }, incentives: { diff --git a/scripts/types/config.ts b/scripts/types/config.ts index 7039daed2..ebc7cbc32 100644 --- a/scripts/types/config.ts +++ b/scripts/types/config.ts @@ -12,9 +12,9 @@ import { RedBankSettings, VaultConfigBaseForString, } from './generated/mars-params/MarsParams.types' -import { NeutronIbcConfig } from './generated/mars-rewards-collector-base/MarsRewardsCollectorBase.types' import { Uint128 } from './generated/mars-red-bank/MarsRedBank.types' import { Duration, VaultInfoResponse } from './generated/mars-mock-vault/MarsMockVault.types' +import { RewardConfig } from './generated/mars-rewards-collector-base/MarsRewardsCollectorBase.types' type SwapRoute = { denom_in: string @@ -63,11 +63,12 @@ export interface DeploymentConfig { rewardsCollector: { name: string timeoutSeconds: number - neutronIbcConfig?: NeutronIbcConfig | null channelId: string safetyFundFeeShare: string - feeCollectorDenom: string - safetyFundDenom: string + revenueShare: string + revenueShareConfig: RewardConfig + safetyFundConfig: RewardConfig + feeCollectorConfig: RewardConfig slippageTolerance: string } incentives: { diff --git a/scripts/types/generated/mars-address-provider/MarsAddressProvider.types.ts b/scripts/types/generated/mars-address-provider/MarsAddressProvider.types.ts index a2545bd31..6cef6fb7c 100644 --- a/scripts/types/generated/mars-address-provider/MarsAddressProvider.types.ts +++ b/scripts/types/generated/mars-address-provider/MarsAddressProvider.types.ts @@ -26,6 +26,7 @@ export type MarsAddressType = | 'safety_fund' | 'swapper' | 'astroport_incentives' + | 'revenue_share' export type OwnerUpdate = | { propose_new_owner: { diff --git a/scripts/types/generated/mars-rewards-collector-base/MarsRewardsCollectorBase.client.ts b/scripts/types/generated/mars-rewards-collector-base/MarsRewardsCollectorBase.client.ts index 020d7a495..e307ffd9d 100644 --- a/scripts/types/generated/mars-rewards-collector-base/MarsRewardsCollectorBase.client.ts +++ b/scripts/types/generated/mars-rewards-collector-base/MarsRewardsCollectorBase.client.ts @@ -8,19 +8,20 @@ import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/cosmwasm-stargate' import { StdFee } from '@cosmjs/amino' import { - Uint128, + TransferType, Decimal, InstantiateMsg, - NeutronIbcConfig, - Coin, + RewardConfig, ExecuteMsg, OwnerUpdate, + Uint128, Action, ActionAmount, LiquidateRequestForVaultBaseForString, VaultPositionType, SwapperRoute, UpdateConfig, + Coin, ActionCoin, VaultBaseForString, AstroRoute, @@ -96,10 +97,8 @@ export interface MarsRewardsCollectorBaseInterface ) => Promise distributeRewards: ( { - amount, denom, }: { - amount?: Uint128 denom: string }, fee?: number | StdFee | 'auto', @@ -255,10 +254,8 @@ export class MarsRewardsCollectorBaseClient } distributeRewards = async ( { - amount, denom, }: { - amount?: Uint128 denom: string }, fee: number | StdFee | 'auto' = 'auto', @@ -270,7 +267,6 @@ export class MarsRewardsCollectorBaseClient this.contractAddress, { distribute_rewards: { - amount, denom, }, }, diff --git a/scripts/types/generated/mars-rewards-collector-base/MarsRewardsCollectorBase.react-query.ts b/scripts/types/generated/mars-rewards-collector-base/MarsRewardsCollectorBase.react-query.ts index 10853a7e0..7f2e68e0b 100644 --- a/scripts/types/generated/mars-rewards-collector-base/MarsRewardsCollectorBase.react-query.ts +++ b/scripts/types/generated/mars-rewards-collector-base/MarsRewardsCollectorBase.react-query.ts @@ -9,19 +9,20 @@ import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tan import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' import { StdFee } from '@cosmjs/amino' import { - Uint128, + TransferType, Decimal, InstantiateMsg, - NeutronIbcConfig, - Coin, + RewardConfig, ExecuteMsg, OwnerUpdate, + Uint128, Action, ActionAmount, LiquidateRequestForVaultBaseForString, VaultPositionType, SwapperRoute, UpdateConfig, + Coin, ActionCoin, VaultBaseForString, AstroRoute, @@ -136,7 +137,6 @@ export function useMarsRewardsCollectorBaseSwapAssetMutation( export interface MarsRewardsCollectorBaseDistributeRewardsMutation { client: MarsRewardsCollectorBaseClient msg: { - amount?: Uint128 denom: string } args?: { diff --git a/scripts/types/generated/mars-rewards-collector-base/MarsRewardsCollectorBase.types.ts b/scripts/types/generated/mars-rewards-collector-base/MarsRewardsCollectorBase.types.ts index f566c4af5..81987ce3d 100644 --- a/scripts/types/generated/mars-rewards-collector-base/MarsRewardsCollectorBase.types.ts +++ b/scripts/types/generated/mars-rewards-collector-base/MarsRewardsCollectorBase.types.ts @@ -5,28 +5,23 @@ * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -export type Uint128 = string +export type TransferType = 'ibc' | 'bank' export type Decimal = string export interface InstantiateMsg { address_provider: string channel_id: string - fee_collector_denom: string - neutron_ibc_config?: NeutronIbcConfig | null + fee_collector_config: RewardConfig owner: string - safety_fund_denom: string + revenue_share_config: RewardConfig + revenue_share_tax_rate: Decimal + safety_fund_config: RewardConfig safety_tax_rate: Decimal slippage_tolerance: Decimal timeout_seconds: number } -export interface NeutronIbcConfig { - acc_fee: Coin[] - source_port: string - timeout_fee: Coin[] -} -export interface Coin { - amount: Uint128 - denom: string - [k: string]: unknown +export interface RewardConfig { + target_denom: string + transfer_type: TransferType } export type ExecuteMsg = | { @@ -51,7 +46,6 @@ export type ExecuteMsg = } | { distribute_rewards: { - amount?: Uint128 | null denom: string } } @@ -87,6 +81,7 @@ export type OwnerUpdate = } } | 'clear_emergency_owner' +export type Uint128 = string export type Action = | { deposit: Coin @@ -220,13 +215,19 @@ export type SwapperRoute = export interface UpdateConfig { address_provider?: string | null channel_id?: string | null - fee_collector_denom?: string | null - neutron_ibc_config?: NeutronIbcConfig | null - safety_fund_denom?: string | null + fee_collector_config?: RewardConfig | null + revenue_share_config?: RewardConfig | null + revenue_share_tax_rate?: Decimal | null + safety_fund_config?: RewardConfig | null safety_tax_rate?: Decimal | null slippage_tolerance?: Decimal | null timeout_seconds?: number | null } +export interface Coin { + amount: Uint128 + denom: string + [k: string]: unknown +} export interface ActionCoin { amount: ActionAmount denom: string @@ -254,11 +255,12 @@ export type QueryMsg = { export interface ConfigResponse { address_provider: string channel_id: string - fee_collector_denom: string - neutron_ibc_config?: NeutronIbcConfig | null + fee_collector_config: RewardConfig owner?: string | null proposed_new_owner?: string | null - safety_fund_denom: string + revenue_share_config: RewardConfig + revenue_share_tax_rate: Decimal + safety_fund_config: RewardConfig safety_tax_rate: Decimal slippage_tolerance: Decimal timeout_seconds: number