From b159636d19a36043961639a68b808cc1df98b190 Mon Sep 17 00:00:00 2001 From: Mark Watney <80194956+markonmars@users.noreply.github.com> Date: Wed, 19 Feb 2025 18:39:44 +0800 Subject: [PATCH 1/8] Add Revenue share to Rewards Collector (#205) * add revenue share to rewards collector * fmt * remove v2.0 migration test * update types * improve pop_array methods * remove estimate exact out for reward amounts * improve tests for rewards collector * small tidy * safety fund and revenue share use same denom * fix integration test * update schema * generate types * update deploy scripts * make distribute method configurable * tidy comments * review comments * fix ibc integration test * update types and schema * enforce rc config cannot have fee_collector_denom the same as other denoms --- Cargo.lock | 1 + .../perps/tests/tests/helpers/mock_env.rs | 847 ++++++++++++++++++ .../rewards-collector/base/src/contract.rs | 385 ++++++-- contracts/rewards-collector/base/src/error.rs | 24 +- .../rewards-collector/base/src/traits.rs | 45 +- .../rewards-collector/neutron/src/lib.rs | 56 +- .../rewards-collector/osmosis/Cargo.toml | 1 + .../rewards-collector/osmosis/src/lib.rs | 39 +- .../osmosis/tests/tests/helpers/mod.rs | 37 +- .../osmosis/tests/tests/test_admin.rs | 17 +- .../tests/tests/test_distribute_rewards.rs | 157 ++-- .../osmosis/tests/tests/test_migration_v2.rs | 8 +- .../osmosis/tests/tests/test_swap.rs | 233 +++-- integration-tests/tests/test_oracles.rs | 22 +- .../tests/test_rewards_collector.rs | 169 +++- packages/testing/src/integration/mock_env.rs | 44 +- packages/testing/src/lib.rs | 1 + packages/testing/src/mars_mock_querier.rs | 13 +- packages/testing/src/swapper_querier.rs | 35 + packages/types/src/address_provider.rs | 8 + packages/types/src/rewards_collector.rs | 125 ++- .../mars-address-provider.json | 70 ++ .../mars-rewards-collector-base.json | 316 +++---- scripts/deploy/base/deployer.ts | 7 +- scripts/deploy/neutron/devnet-config.ts | 36 +- scripts/deploy/neutron/mainnet-config.ts | 36 +- scripts/deploy/neutron/testnet-config.ts | 44 +- scripts/deploy/osmosis/mainnet-config.ts | 17 +- scripts/deploy/osmosis/testnet-config.ts | 17 +- scripts/types/config.ts | 9 +- .../MarsAddressProvider.types.ts | 2 + .../MarsRewardsCollectorBase.client.ts | 12 +- .../MarsRewardsCollectorBase.react-query.ts | 8 +- .../MarsRewardsCollectorBase.types.ts | 42 +- 34 files changed, 2189 insertions(+), 694 deletions(-) create mode 100644 contracts/perps/tests/tests/helpers/mock_env.rs create mode 100644 packages/testing/src/swapper_querier.rs diff --git a/Cargo.lock b/Cargo.lock index db4956a89..807bc44ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2542,6 +2542,7 @@ dependencies = [ "mars-utils 2.1.0", "osmosis-std 0.22.0", "serde", + "test-case", ] [[package]] diff --git a/contracts/perps/tests/tests/helpers/mock_env.rs b/contracts/perps/tests/tests/helpers/mock_env.rs new file mode 100644 index 000000000..a9ef5f91e --- /dev/null +++ b/contracts/perps/tests/tests/helpers/mock_env.rs @@ -0,0 +1,847 @@ +#![allow(dead_code)] +use std::mem::take; + +use anyhow::Result as AnyResult; +use cosmwasm_std::{coin, Addr, Coin, Decimal, Empty, Int128, Timestamp, Uint128}; +use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; +use cw_paginate::PaginationResponse; +use mars_oracle_osmosis::OsmosisPriceSourceUnchecked; +use mars_owner::{OwnerResponse, OwnerUpdate}; +use mars_testing::integration::mock_contracts::mock_rewards_collector_osmosis_contract; +use mars_types::{ + address_provider::{self, MarsAddressType}, + incentives, + oracle::{self, ActionKind}, + params::{ + self, EmergencyUpdate, + ExecuteMsg::{self, UpdatePerpParams}, + PerpParams, PerpParamsUpdate, + }, + perps::{ + self, AccountingResponse, Config, ConfigUpdates, MarketResponse, MarketStateResponse, + PnlAmounts, PositionFeesResponse, PositionResponse, PositionsByAccountResponse, TradingFee, + VaultPositionResponse, VaultResponse, + }, + rewards_collector::{self, RewardConfig, TransferType}, +}; + +use super::{ + contracts::{mock_oracle_contract, mock_perps_contract}, + mock_address_provider_contract, mock_credit_manager_contract, mock_incentives_contract, + mock_params_contract, +}; + +pub const ONE_HOUR_SEC: u64 = 3600u64; + +pub struct MockEnv { + app: BasicApp, + pub owner: Addr, + pub perps: Addr, + pub oracle: Addr, + pub params: Addr, + pub credit_manager: Addr, + pub address_provider: Addr, + pub rewards_collector: Addr, +} + +pub struct MockEnvBuilder { + app: BasicApp, + deployer: Addr, + oracle_base_denom: String, + perps_base_denom: String, + cooldown_period: u64, + max_positions: u8, + protocol_fee_rate: Decimal, + pub address_provider: Option, + target_vault_collateralization_ratio: Decimal, + pub emergency_owner: Option, + deleverage_enabled: bool, + withdraw_enabled: bool, + max_unlocks: u8, +} + +#[allow(clippy::new_ret_no_self)] +impl MockEnv { + pub fn new() -> MockEnvBuilder { + MockEnvBuilder { + app: App::default(), + deployer: Addr::unchecked("deployer"), + oracle_base_denom: "uusd".to_string(), + perps_base_denom: "uusdc".to_string(), + cooldown_period: 3600, + max_positions: 4, + protocol_fee_rate: Decimal::percent(0), + address_provider: None, + target_vault_collateralization_ratio: Decimal::percent(125), + emergency_owner: None, + deleverage_enabled: true, + withdraw_enabled: true, + max_unlocks: 5, + } + } + + pub fn fund_accounts(&mut self, addrs: &[&Addr], amount: u128, denoms: &[&str]) { + for addr in addrs { + let coins: Vec<_> = denoms.iter().map(|&d| coin(amount, d)).collect(); + self.fund_account(addr, &coins); + } + } + + pub fn fund_account(&mut self, addr: &Addr, coins: &[Coin]) { + self.app + .sudo(SudoMsg::Bank(BankSudo::Mint { + to_address: addr.to_string(), + amount: coins.to_vec(), + })) + .unwrap(); + } + + pub fn query_balance(&self, addr: &Addr, denom: &str) -> Coin { + self.app.wrap().query_balance(addr.clone(), denom).unwrap() + } + + pub fn increment_by_blocks(&mut self, num_of_blocks: u64) { + self.app.update_block(|block| { + block.height += num_of_blocks; + // assume block time = 6 sec + block.time = block.time.plus_seconds(num_of_blocks * 6); + }) + } + + pub fn increment_by_time(&mut self, seconds: u64) { + self.app.update_block(|block| { + block.height += seconds / 6; + // assume block time = 6 sec + block.time = block.time.plus_seconds(seconds); + }) + } + + pub fn set_block_time(&mut self, seconds: u64) { + self.app.update_block(|block| { + block.time = Timestamp::from_seconds(seconds); + }) + } + + pub fn query_block_time(&self) -> u64 { + self.app.block_info().time.seconds() + } + + //-------------------------------------------------------------------------------------------------- + // Execute Msgs + //-------------------------------------------------------------------------------------------------- + + pub fn update_owner(&mut self, sender: &Addr, update: OwnerUpdate) -> AnyResult { + self.app.execute_contract( + sender.clone(), + self.perps.clone(), + &perps::ExecuteMsg::UpdateOwner(update), + &[], + ) + } + + pub fn update_config( + &mut self, + sender: &Addr, + updates: ConfigUpdates, + ) -> AnyResult { + self.app.execute_contract( + sender.clone(), + self.perps.clone(), + &perps::ExecuteMsg::UpdateConfig { + updates, + }, + &[], + ) + } + + pub fn deposit_to_vault( + &mut self, + sender: &Addr, + account_id: Option<&str>, + max_shares_receivable: Option, + funds: &[Coin], + ) -> AnyResult { + self.app.execute_contract( + sender.clone(), + self.perps.clone(), + &perps::ExecuteMsg::Deposit { + account_id: account_id.map(|s| s.to_string()), + max_shares_receivable, + }, + funds, + ) + } + + pub fn unlock_from_vault( + &mut self, + sender: &Addr, + account_id: Option<&str>, + shares: Uint128, + ) -> AnyResult { + self.app.execute_contract( + sender.clone(), + self.perps.clone(), + &perps::ExecuteMsg::Unlock { + account_id: account_id.map(|s| s.to_string()), + shares, + }, + &[], + ) + } + + pub fn withdraw_from_vault( + &mut self, + sender: &Addr, + account_id: Option<&str>, + min_receive: Option, + ) -> AnyResult { + self.app.execute_contract( + sender.clone(), + self.perps.clone(), + &perps::ExecuteMsg::Withdraw { + account_id: account_id.map(|s| s.to_string()), + min_receive, + }, + &[], + ) + } + + pub fn execute_perp_order( + &mut self, + sender: &Addr, + account_id: &str, + denom: &str, + size: Int128, + reduce_only: Option, + funds: &[Coin], + ) -> AnyResult { + self.app.execute_contract( + sender.clone(), + self.perps.clone(), + &perps::ExecuteMsg::ExecuteOrder { + account_id: account_id.to_string(), + denom: denom.to_string(), + size, + reduce_only, + }, + funds, + ) + } + + pub fn close_all_positions( + &mut self, + sender: &Addr, + account_id: &str, + funds: &[Coin], + ) -> AnyResult { + self.app.execute_contract( + sender.clone(), + self.perps.clone(), + &perps::ExecuteMsg::CloseAllPositions { + account_id: account_id.to_string(), + action: None, + }, + funds, + ) + } + + pub fn set_price( + &mut self, + sender: &Addr, + denom: &str, + price: Decimal, + ) -> AnyResult { + self.app.execute_contract( + sender.clone(), + self.oracle.clone(), + &oracle::ExecuteMsg::::SetPriceSource { + denom: denom.to_string(), + price_source: OsmosisPriceSourceUnchecked::Fixed { + price, + }, + }, + &[], + ) + } + + pub fn update_perp_params(&mut self, sender: &Addr, update: PerpParamsUpdate) { + self.app + .execute_contract(sender.clone(), self.params.clone(), &UpdatePerpParams(update), &[]) + .unwrap(); + } + + pub fn update_market(&mut self, sender: &Addr, params: PerpParams) -> AnyResult { + self.app.execute_contract( + sender.clone(), + self.perps.clone(), + &perps::ExecuteMsg::UpdateMarket { + params, + }, + &[], + ) + } + + pub fn emergency_params_update( + &mut self, + sender: &Addr, + update: EmergencyUpdate, + ) -> AnyResult { + self.app.execute_contract( + sender.clone(), + self.params.clone(), + &mars_types::params::ExecuteMsg::EmergencyUpdate(update), + &[], + ) + } + + //-------------------------------------------------------------------------------------------------- + // Queries + //-------------------------------------------------------------------------------------------------- + + pub fn query_owner(&self) -> Addr { + let res = self.query_ownership(); + Addr::unchecked(res.owner.unwrap()) + } + + pub fn query_ownership(&self) -> OwnerResponse { + self.app.wrap().query_wasm_smart(self.perps.clone(), &perps::QueryMsg::Owner {}).unwrap() + } + + pub fn query_config(&self) -> Config { + self.app.wrap().query_wasm_smart(self.perps.clone(), &perps::QueryMsg::Config {}).unwrap() + } + + pub fn query_vault(&self) -> VaultResponse { + self.app + .wrap() + .query_wasm_smart( + self.perps.clone(), + &perps::QueryMsg::Vault { + action: None, + }, + ) + .unwrap() + } + + pub fn query_market_state(&self, denom: &str) -> MarketStateResponse { + self.app + .wrap() + .query_wasm_smart( + self.perps.clone(), + &perps::QueryMsg::MarketState { + denom: denom.to_string(), + }, + ) + .unwrap() + } + + pub fn query_market(&self, denom: &str) -> MarketResponse { + self.app + .wrap() + .query_wasm_smart( + self.perps.clone(), + &perps::QueryMsg::Market { + denom: denom.to_string(), + }, + ) + .unwrap() + } + + pub fn query_markets( + &self, + start_after: Option, + limit: Option, + ) -> PaginationResponse { + self.app + .wrap() + .query_wasm_smart( + self.perps.clone(), + &perps::QueryMsg::Markets { + start_after, + limit, + }, + ) + .unwrap() + } + + pub fn query_cm_vault_position(&self, account_id: &str) -> Option { + self.query_vault_position(self.credit_manager.as_str(), Some(account_id)) + } + + pub fn query_vault_position( + &self, + user_address: &str, + account_id: Option<&str>, + ) -> Option { + self.app + .wrap() + .query_wasm_smart( + self.perps.clone(), + &perps::QueryMsg::VaultPosition { + user_address: user_address.to_string(), + account_id: account_id.map(|s| s.to_string()), + }, + ) + .unwrap() + } + + pub fn query_position(&self, account_id: &str, denom: &str) -> PositionResponse { + self.query_position_with_order_size(account_id, denom, None) + } + + pub fn query_position_with_order_size( + &self, + account_id: &str, + denom: &str, + order_size: Option, + ) -> PositionResponse { + self.app + .wrap() + .query_wasm_smart( + self.perps.clone(), + &perps::QueryMsg::Position { + account_id: account_id.to_string(), + denom: denom.to_string(), + order_size, + reduce_only: None, + }, + ) + .unwrap() + } + + pub fn query_positions( + &self, + start_after: Option<(String, String)>, + limit: Option, + ) -> Vec { + self.app + .wrap() + .query_wasm_smart( + self.perps.clone(), + &perps::QueryMsg::Positions { + start_after, + limit, + }, + ) + .unwrap() + } + + pub fn query_positions_by_account_id( + &self, + account_id: &str, + action: ActionKind, + ) -> PositionsByAccountResponse { + self.app + .wrap() + .query_wasm_smart( + self.perps.clone(), + &perps::QueryMsg::PositionsByAccount { + account_id: account_id.to_string(), + action: Some(action), + }, + ) + .unwrap() + } + + pub fn query_market_accounting(&self, denom: &str) -> AccountingResponse { + self.app + .wrap() + .query_wasm_smart( + self.perps.clone(), + &perps::QueryMsg::MarketAccounting { + denom: denom.to_string(), + }, + ) + .unwrap() + } + + pub fn query_total_accounting(&self) -> AccountingResponse { + self.app + .wrap() + .query_wasm_smart(self.perps.clone(), &perps::QueryMsg::TotalAccounting {}) + .unwrap() + } + + pub fn query_realized_pnl_by_account_and_market( + &self, + account_id: &str, + denom: &str, + ) -> PnlAmounts { + self.app + .wrap() + .query_wasm_smart( + self.perps.clone(), + &perps::QueryMsg::RealizedPnlByAccountAndMarket { + account_id: account_id.to_string(), + denom: denom.to_string(), + }, + ) + .unwrap() + } + + pub fn query_opening_fee(&self, denom: &str, size: Int128) -> TradingFee { + self.app + .wrap() + .query_wasm_smart( + self.perps.clone(), + &perps::QueryMsg::OpeningFee { + denom: denom.to_string(), + size, + }, + ) + .unwrap() + } + + pub fn query_position_fees( + &self, + account_id: &str, + denom: &str, + new_size: Int128, + ) -> PositionFeesResponse { + self.app + .wrap() + .query_wasm_smart( + self.perps.clone(), + &perps::QueryMsg::PositionFees { + account_id: account_id.to_string(), + denom: denom.to_string(), + new_size, + }, + ) + .unwrap() + } + + pub fn query_perp_params(&self, denom: &str) -> PerpParams { + self.app + .wrap() + .query_wasm_smart( + self.params.clone(), + ¶ms::QueryMsg::PerpParams { + denom: denom.to_string(), + }, + ) + .unwrap() + } +} + +impl MockEnvBuilder { + pub fn build(&mut self) -> AnyResult { + let address_provider_contract = self.get_address_provider(); + let oracle_contract = self.deploy_oracle(); + let params_contract = self.deploy_params(address_provider_contract.as_str()); + let credit_manager_contract = self.deploy_credit_manager(); + let rewards_collector_contract = + self.deploy_rewards_collector(address_provider_contract.as_str()); + let perps_contract = self.deploy_perps(address_provider_contract.as_str()); + let incentives_contract = self.deploy_incentives(&address_provider_contract); + + self.update_address_provider( + &address_provider_contract, + MarsAddressType::Incentives, + &incentives_contract, + ); + self.update_address_provider( + &address_provider_contract, + MarsAddressType::Perps, + &perps_contract, + ); + + if self.emergency_owner.is_some() { + self.set_emergency_owner(¶ms_contract, &self.emergency_owner.clone().unwrap()); + } + + Ok(MockEnv { + app: take(&mut self.app), + owner: self.deployer.clone(), + perps: perps_contract, + oracle: oracle_contract, + params: params_contract, + credit_manager: credit_manager_contract, + address_provider: address_provider_contract, + rewards_collector: rewards_collector_contract, + }) + } + + fn deploy_address_provider(&mut self) -> Addr { + let contract = mock_address_provider_contract(); + let code_id = self.app.store_code(contract); + + self.app + .instantiate_contract( + code_id, + self.deployer.clone(), + &address_provider::InstantiateMsg { + owner: self.deployer.clone().to_string(), + prefix: "".to_string(), + }, + &[], + "mock-address-provider", + None, + ) + .unwrap() + } + + fn deploy_oracle(&mut self) -> Addr { + let contract = mock_oracle_contract(); + let code_id = self.app.store_code(contract); + + let addr = self + .app + .instantiate_contract( + code_id, + self.deployer.clone(), + &oracle::InstantiateMsg:: { + owner: self.deployer.clone().to_string(), + base_denom: self.oracle_base_denom.clone(), + custom_init: None, + }, + &[], + "mock-oracle", + None, + ) + .unwrap(); + + self.set_address(MarsAddressType::Oracle, addr.clone()); + + addr + } + + fn deploy_params(&mut self, address_provider: &str) -> Addr { + let contract = mock_params_contract(); + let code_id = self.app.store_code(contract); + + let addr = self + .app + .instantiate_contract( + code_id, + self.deployer.clone(), + ¶ms::InstantiateMsg { + owner: self.deployer.clone().to_string(), + risk_manager: None, + address_provider: address_provider.to_string(), + max_perp_params: 40, + }, + &[], + "mock-params", + None, + ) + .unwrap(); + + self.set_address(MarsAddressType::Params, addr.clone()); + + addr + } + + fn deploy_incentives(&mut self, address_provider_addr: &Addr) -> Addr { + let code_id = self.app.store_code(mock_incentives_contract()); + + self.app + .instantiate_contract( + code_id, + self.deployer.clone(), + &incentives::InstantiateMsg { + owner: self.deployer.to_string(), + address_provider: address_provider_addr.to_string(), + epoch_duration: 604800, + max_whitelisted_denoms: 10, + }, + &[], + "incentives", + None, + ) + .unwrap() + } + + fn deploy_perps(&mut self, address_provider: &str) -> Addr { + let code_id = self.app.store_code(mock_perps_contract()); + + self.app + .instantiate_contract( + code_id, + self.deployer.clone(), + &perps::InstantiateMsg { + address_provider: address_provider.to_string(), + base_denom: self.perps_base_denom.clone(), + cooldown_period: self.cooldown_period, + max_positions: self.max_positions, + protocol_fee_rate: self.protocol_fee_rate, + target_vault_collateralization_ratio: self.target_vault_collateralization_ratio, + deleverage_enabled: self.deleverage_enabled, + vault_withdraw_enabled: self.withdraw_enabled, + max_unlocks: self.max_unlocks, + }, + &[], + "mock-perps", + None, + ) + .unwrap() + } + + fn deploy_credit_manager(&mut self) -> Addr { + let contract = mock_credit_manager_contract(); + let code_id = self.app.store_code(contract); + + let addr = self + .app + .instantiate_contract( + code_id, + self.deployer.clone(), + &Empty {}, + &[], + "mock-credit-manager", + None, + ) + .unwrap(); + + self.set_address(MarsAddressType::CreditManager, addr.clone()); + + addr + } + + fn deploy_rewards_collector(&mut self, address_provider: &str) -> Addr { + let contract = mock_rewards_collector_osmosis_contract(); + let code_id = self.app.store_code(contract); + + let addr = self + .app + .instantiate_contract( + code_id, + self.deployer.clone(), + &rewards_collector::InstantiateMsg { + owner: self.deployer.clone().to_string(), + address_provider: address_provider.to_string(), + safety_tax_rate: Default::default(), + revenue_share_tax_rate: Default::default(), + 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: "".to_string(), + timeout_seconds: 1, + slippage_tolerance: Default::default(), + }, + &[], + "mock-rewards-collector", + None, + ) + .unwrap(); + + self.set_address(MarsAddressType::RewardsCollector, addr.clone()); + + addr + } + + fn set_address(&mut self, address_type: MarsAddressType, address: Addr) { + let address_provider_addr = self.get_address_provider(); + + self.app + .execute_contract( + self.deployer.clone(), + address_provider_addr, + &address_provider::ExecuteMsg::SetAddress { + address_type, + address: address.into(), + }, + &[], + ) + .unwrap(); + } + + fn get_address_provider(&mut self) -> Addr { + if self.address_provider.is_none() { + let addr = self.deploy_address_provider(); + + self.address_provider = Some(addr); + } + self.address_provider.clone().unwrap() + } + + fn update_address_provider( + &mut self, + address_provider_addr: &Addr, + address_type: MarsAddressType, + addr: &Addr, + ) { + self.app + .execute_contract( + self.deployer.clone(), + address_provider_addr.clone(), + &address_provider::ExecuteMsg::SetAddress { + address_type, + address: addr.to_string(), + }, + &[], + ) + .unwrap(); + } + + fn set_emergency_owner(&mut self, params_contract: &Addr, eo: &str) { + self.app + .execute_contract( + self.deployer.clone(), + params_contract.clone(), + &ExecuteMsg::UpdateOwner(OwnerUpdate::SetEmergencyOwner { + emergency_owner: eo.to_string(), + }), + &[], + ) + .unwrap(); + } + + //-------------------------------------------------------------------------------------------------- + // Setter functions + //-------------------------------------------------------------------------------------------------- + + pub fn oracle_base_denom(&mut self, denom: &str) -> &mut Self { + self.oracle_base_denom = denom.to_string(); + self + } + + pub fn perps_base_denom(&mut self, denom: &str) -> &mut Self { + self.perps_base_denom = denom.to_string(); + self + } + + pub fn cooldown_period(&mut self, cp: u64) -> &mut Self { + self.cooldown_period = cp; + self + } + + pub fn max_positions(&mut self, max_positions: u8) -> &mut Self { + self.max_positions = max_positions; + self + } + + pub fn protocol_fee_rate(&mut self, rate: Decimal) -> &mut Self { + self.protocol_fee_rate = rate; + self + } + + pub fn target_vault_collaterization_ratio(&mut self, ratio: Decimal) -> &mut Self { + self.target_vault_collateralization_ratio = ratio; + self + } + + pub fn withdraw_enabled(&mut self, enabled: bool) -> &mut Self { + self.withdraw_enabled = enabled; + self + } + + pub fn emergency_owner(&mut self, eo: &str) -> &mut Self { + self.emergency_owner = Some(eo.to_string()); + self + } + + pub fn max_unlocks(&mut self, max_unlocks: u8) -> &mut Self { + self.max_unlocks = max_unlocks; + self + } +} diff --git a/contracts/rewards-collector/base/src/contract.rs b/contracts/rewards-collector/base/src/contract.rs index 8d158fcfd..0e9bfd740 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_minumum: 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..dbc88cf3c 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_minumum}, `min_receive` given: {min_receive_given}")] + SlippageLimitExceeded { + denom_in: String, + denom_out: String, + min_receive_minumum: 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/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..3538a4027 100644 --- a/contracts/rewards-collector/osmosis/Cargo.toml +++ b/contracts/rewards-collector/osmosis/Cargo.toml @@ -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..61974f85f 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, 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/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 index edd57a9d5..20a1cb573 100644 --- a/contracts/rewards-collector/osmosis/tests/tests/test_migration_v2.rs +++ b/contracts/rewards-collector/osmosis/tests/tests/test_migration_v2.rs @@ -42,12 +42,12 @@ fn wrong_contract_version() { } #[test] -fn successful_migration() { +fn successful_migration_to_v2_1_0() { let mut deps = mock_dependencies(&[]); cw2::set_contract_version( deps.as_mut().storage, "crates.io:mars-rewards-collector-osmosis", - "2.0.1", + "2.0.0", ) .unwrap(); @@ -58,12 +58,12 @@ fn successful_migration() { 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")] + vec![attr("action", "migrate"), attr("from_version", "2.0.0"), attr("to_version", "2.2.0")] ); let new_contract_version = ContractVersion { contract: "crates.io:mars-rewards-collector-osmosis".to_string(), - version: "2.1.0".to_string(), + version: "2.2.0".to_string(), }; assert_eq!(cw2::get_contract_version(deps.as_ref().storage).unwrap(), new_contract_version); } diff --git a/contracts/rewards-collector/osmosis/tests/tests/test_swap.rs b/contracts/rewards-collector/osmosis/tests/tests/test_swap.rs index c6cfe529d..b99ed89ab 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 + // minumum revenue share (atom -> usdc) = 8888 * 12.125 = 107767 + // minumum safety fund (atom -> usdc) = 31110 * 12.125 = 377208 + // minumum 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_minumum: 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_minumum: 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..c19bea5f9 100644 --- a/packages/types/src/address_provider.rs +++ b/packages/types/src/address_provider.rs @@ -37,6 +37,10 @@ pub enum MarsAddressType { Swapper, /// Astroport incentives contract AstroportIncentives, + /// Perps contract + Perps, + /// The address that shall receive the revenue share given to neutron (10%) + RevenueShare, } impl fmt::Display for MarsAddressType { @@ -53,6 +57,8 @@ impl fmt::Display for MarsAddressType { MarsAddressType::SafetyFund => "safety_fund", MarsAddressType::Swapper => "swapper", MarsAddressType::AstroportIncentives => "astroport_incentives", + MarsAddressType::Perps => "perps", + MarsAddressType::RevenueShare => "revenue_share", }; write!(f, "{s}") } @@ -74,6 +80,8 @@ impl FromStr for MarsAddressType { "safety_fund" => Ok(MarsAddressType::SafetyFund), "swapper" => Ok(MarsAddressType::Swapper), "astroport_incentives" => Ok(MarsAddressType::AstroportIncentives), + "perps" => Ok(MarsAddressType::Perps), + "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..6af9cb764 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] diff --git a/schemas/mars-address-provider/mars-address-provider.json b/schemas/mars-address-provider/mars-address-provider.json index 78f030b4d..7cc314ab9 100644 --- a/schemas/mars-address-provider/mars-address-provider.json +++ b/schemas/mars-address-provider/mars-address-provider.json @@ -115,6 +115,20 @@ "enum": [ "astroport_incentives" ] + }, + { + "description": "Perps contract", + "type": "string", + "enum": [ + "perps" + ] + }, + { + "description": "The address that shall receive the revenue share given to neutron (10%)", + "type": "string", + "enum": [ + "revenue_share" + ] } ] }, @@ -326,6 +340,20 @@ "enum": [ "astroport_incentives" ] + }, + { + "description": "Perps contract", + "type": "string", + "enum": [ + "perps" + ] + }, + { + "description": "The address that shall receive the revenue share given to neutron (10%)", + "type": "string", + "enum": [ + "revenue_share" + ] } ] } @@ -405,6 +433,20 @@ "enum": [ "astroport_incentives" ] + }, + { + "description": "Perps contract", + "type": "string", + "enum": [ + "perps" + ] + }, + { + "description": "The address that shall receive the revenue share given to neutron (10%)", + "type": "string", + "enum": [ + "revenue_share" + ] } ] } @@ -487,6 +529,20 @@ "enum": [ "astroport_incentives" ] + }, + { + "description": "Perps contract", + "type": "string", + "enum": [ + "perps" + ] + }, + { + "description": "The address that shall receive the revenue share given to neutron (10%)", + "type": "string", + "enum": [ + "revenue_share" + ] } ] } @@ -569,6 +625,20 @@ "enum": [ "astroport_incentives" ] + }, + { + "description": "Perps contract", + "type": "string", + "enum": [ + "perps" + ] + }, + { + "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..84b6e40cc 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' +import { DeploymentConfig, AssetConfig, OracleConfig, PerpDenom } from '../../types/config' const nobleUsdcDenom = 'ibc/4C19E7EC06C1AB2EC2D70C6855FEB6D48E9CE174913991DA0A517D21978E7E42' const atomDenom = 'ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9' @@ -26,29 +25,6 @@ const astroportIncentives = 'neutron1slxs8heecwyw0n6zmj7unj3nenrfhk2zpagfz2lt87d const safetyFundAddr = 'mars1s4hgh56can3e33e0zqpnjxh0t5wdf7u3pze575' const feeCollectorAddr = 'mars17xpfvakm2amg962yls6f84z3kell8c5ldy6e7x' -// Pyth configuration -const pythAddr = 'neutron15ldst8t80982akgr8w8ekcytejzkmfpgdkeq4xgtge48qs7435jqp87u3t' -const pythAtomID = 'b00b60f88b03a6a625a8d1c048c3f66653edf217439983d037e7222c4e612819' -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 +436,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..0cb8e97dd 100644 --- a/scripts/types/generated/mars-address-provider/MarsAddressProvider.types.ts +++ b/scripts/types/generated/mars-address-provider/MarsAddressProvider.types.ts @@ -26,6 +26,8 @@ export type MarsAddressType = | 'safety_fund' | 'swapper' | 'astroport_incentives' + | 'perps' + | '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 From 0b33fadd7e426ab64d26ab8841286d7bf3a5d9f9 Mon Sep 17 00:00:00 2001 From: Mark Watney <80194956+markonmars@users.noreply.github.com> Date: Thu, 20 Feb 2025 16:04:45 +0800 Subject: [PATCH 2/8] Rewards Collector Contract migration (Neutron 2.2.0 -> 2.2.1, Osmosis 2.1.0 -> 2.1.1) (#208) * add revenue share to rewards collector * fmt * remove v2.0 migration test * update types * improve pop_array methods * remove estimate exact out for reward amounts * improve tests for rewards collector * small tidy * safety fund and revenue share use same denom * fix integration test * update schema * generate types * update deploy scripts * make distribute method configurable * tidy comments * review comments * fix ibc integration test * update types and schema * enforce rc config cannot have fee_collector_denom the same as other denoms * add neutron migration * fix migration tests for neutron rc * migration for address provider * update schema * add osmosis RC migration * fix old migration test * fix old migration test * clear old state on migration * fmt --- contracts/address-provider/src/contract.rs | 2 +- .../address-provider/src/migrations/mod.rs | 1 + .../address-provider/src/migrations/v2_1_1.rs | 22 ++++ .../rewards-collector/neutron/Cargo.toml | 4 +- .../rewards-collector/neutron/src/lib.rs | 2 +- .../neutron/src/migrations/mod.rs | 2 +- .../neutron/tests/tests/mod.rs | 2 +- .../neutron/tests/tests/test_migration_v2.rs | 4 +- .../rewards-collector/osmosis/src/lib.rs | 10 +- .../osmosis/src/migrations/mod.rs | 1 + .../osmosis/src/migrations/v2_1_1.rs | 107 ++++++++++++++++++ .../osmosis/tests/tests/mod.rs | 2 +- .../osmosis/tests/tests/test_migration_v2.rs | 69 ----------- .../tests/test_migration_v2_1_0_to_v2_1_1.rs | 100 ++++++++++++++++ packages/types/src/rewards_collector.rs | 12 +- scripts/deploy/neutron/testnet-config.ts | 8 +- 16 files changed, 264 insertions(+), 84 deletions(-) create mode 100644 contracts/address-provider/src/migrations/v2_1_1.rs create mode 100644 contracts/rewards-collector/osmosis/src/migrations/v2_1_1.rs delete mode 100644 contracts/rewards-collector/osmosis/tests/tests/test_migration_v2.rs create mode 100644 contracts/rewards-collector/osmosis/tests/tests/test_migration_v2_1_0_to_v2_1_1.rs 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/rewards-collector/neutron/Cargo.toml b/contracts/rewards-collector/neutron/Cargo.toml index 5f773f797..70cacbd8d 100644 --- a/contracts/rewards-collector/neutron/Cargo.toml +++ b/contracts/rewards-collector/neutron/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mars-rewards-collector-neutron" -version = { workspace = true } +version = "2.1.1" authors = { workspace = true } edition = { workspace = true } license = { workspace = true } @@ -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 ea9e6dee6..2708d9c58 100644 --- a/contracts/rewards-collector/neutron/src/lib.rs +++ b/contracts/rewards-collector/neutron/src/lib.rs @@ -47,6 +47,6 @@ pub mod entry { #[entry_point] pub fn migrate(deps: DepsMut, _env: Env, _msg: Empty) -> ContractResult { - migrations::v2_0_0::migrate(deps) + migrations::v2_0_0::migrate(deps) } } diff --git a/contracts/rewards-collector/neutron/src/migrations/mod.rs b/contracts/rewards-collector/neutron/src/migrations/mod.rs index 7592b6f12..ee8f73da3 100644 --- a/contracts/rewards-collector/neutron/src/migrations/mod.rs +++ b/contracts/rewards-collector/neutron/src/migrations/mod.rs @@ -1 +1 @@ -pub mod v2_0_0; +pub mod v2_0_0; \ No newline at end of file diff --git a/contracts/rewards-collector/neutron/tests/tests/mod.rs b/contracts/rewards-collector/neutron/tests/tests/mod.rs index c6a4f2bcb..cd51594c8 100644 --- a/contracts/rewards-collector/neutron/tests/tests/mod.rs +++ b/contracts/rewards-collector/neutron/tests/tests/mod.rs @@ -1 +1 @@ -mod test_migration_v2; +mod test_migration_v2; \ No newline at end of file diff --git a/contracts/rewards-collector/neutron/tests/tests/test_migration_v2.rs b/contracts/rewards-collector/neutron/tests/tests/test_migration_v2.rs index a56fb318e..20621f3cf 100644 --- a/contracts/rewards-collector/neutron/tests/tests/test_migration_v2.rs +++ b/contracts/rewards-collector/neutron/tests/tests/test_migration_v2.rs @@ -58,12 +58,12 @@ fn successful_migration() { assert!(res.data.is_none()); assert_eq!( res.attributes, - vec![attr("action", "migrate"), attr("from_version", "1.2.0"), attr("to_version", "2.1.0")] + vec![attr("action", "migrate"), attr("from_version", "2.1.0"), attr("to_version", "2.2.1")] ); let new_contract_version = ContractVersion { contract: "crates.io:mars-rewards-collector-neutron".to_string(), - version: "2.1.0".to_string(), + version: "2.2.1".to_string(), }; assert_eq!(cw2::get_contract_version(deps.as_ref().storage).unwrap(), new_contract_version); } diff --git a/contracts/rewards-collector/osmosis/src/lib.rs b/contracts/rewards-collector/osmosis/src/lib.rs index 61974f85f..36493bba4 100644 --- a/contracts/rewards-collector/osmosis/src/lib.rs +++ b/contracts/rewards-collector/osmosis/src/lib.rs @@ -36,7 +36,7 @@ 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, + entry_point, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult }; use cw2::set_contract_version; use mars_rewards_collector_base::{ContractError, ContractResult}; @@ -77,7 +77,11 @@ pub mod entry { } #[entry_point] - pub fn migrate(deps: DepsMut, _env: Env, _msg: Empty) -> Result { - migrations::v2_1_0::migrate(deps) + pub fn migrate( + deps: DepsMut, + _env: Env, + _msg: Empty, + ) -> Result { + 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/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_migration_v2.rs b/contracts/rewards-collector/osmosis/tests/tests/test_migration_v2.rs deleted file mode 100644 index 20a1cb573..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_to_v2_1_0() { - let mut deps = mock_dependencies(&[]); - cw2::set_contract_version( - deps.as_mut().storage, - "crates.io:mars-rewards-collector-osmosis", - "2.0.0", - ) - .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.0"), attr("to_version", "2.2.0")] - ); - - let new_contract_version = ContractVersion { - contract: "crates.io:mars-rewards-collector-osmosis".to_string(), - version: "2.2.0".to_string(), - }; - assert_eq!(cw2::get_contract_version(deps.as_ref().storage).unwrap(), new_contract_version); -} diff --git a/contracts/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/packages/types/src/rewards_collector.rs b/packages/types/src/rewards_collector.rs index 6af9cb764..e05d47540 100644 --- a/packages/types/src/rewards_collector.rs +++ b/packages/types/src/rewards_collector.rs @@ -240,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/scripts/deploy/neutron/testnet-config.ts b/scripts/deploy/neutron/testnet-config.ts index 84b6e40cc..56e3766ea 100644 --- a/scripts/deploy/neutron/testnet-config.ts +++ b/scripts/deploy/neutron/testnet-config.ts @@ -1,4 +1,4 @@ -import { DeploymentConfig, AssetConfig, OracleConfig, PerpDenom } from '../../types/config' +import { DeploymentConfig, AssetConfig, OracleConfig } from '../../types/config' const nobleUsdcDenom = 'ibc/4C19E7EC06C1AB2EC2D70C6855FEB6D48E9CE174913991DA0A517D21978E7E42' const atomDenom = 'ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9' @@ -25,6 +25,12 @@ const astroportIncentives = 'neutron1slxs8heecwyw0n6zmj7unj3nenrfhk2zpagfz2lt87d const safetyFundAddr = 'mars1s4hgh56can3e33e0zqpnjxh0t5wdf7u3pze575' const feeCollectorAddr = 'mars17xpfvakm2amg962yls6f84z3kell8c5ldy6e7x' +// Pyth configuration +const pythAddr = 'neutron15ldst8t80982akgr8w8ekcytejzkmfpgdkeq4xgtge48qs7435jqp87u3t' +const pythAtomID = 'b00b60f88b03a6a625a8d1c048c3f66653edf217439983d037e7222c4e612819' +const pythUsdcID = 'eaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a' +const pythNtrnID = 'a8e6517966a52cb1df864b2764f3629fde3f21d2b640b5c572fcd654cbccd65e' + // Oracle configurations export const ntrnOracle: OracleConfig = { denom: 'untrn', From e4864554bac2d979ac5ccf9465efc8ee4b29590e Mon Sep 17 00:00:00 2001 From: Mark Watney Date: Thu, 20 Feb 2025 22:21:25 +0800 Subject: [PATCH 3/8] update osmosis rc and ap versions --- Cargo.lock | 6 ++++-- contracts/address-provider/Cargo.toml | 2 +- .../address-provider/tests/tests/test_migration_v2.rs | 10 +++++----- contracts/rewards-collector/neutron/Cargo.toml | 2 +- contracts/rewards-collector/neutron/src/lib.rs | 2 +- .../rewards-collector/neutron/src/migrations/mod.rs | 2 +- contracts/rewards-collector/neutron/tests/tests/mod.rs | 2 +- .../neutron/tests/tests/test_migration_v2.rs | 4 ++-- contracts/rewards-collector/osmosis/Cargo.toml | 2 +- contracts/rewards-collector/osmosis/src/lib.rs | 8 ++------ .../mars-address-provider/mars-address-provider.json | 2 +- 11 files changed, 20 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 807bc44ed..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", 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/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/neutron/Cargo.toml b/contracts/rewards-collector/neutron/Cargo.toml index 70cacbd8d..1860b113c 100644 --- a/contracts/rewards-collector/neutron/Cargo.toml +++ b/contracts/rewards-collector/neutron/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mars-rewards-collector-neutron" -version = "2.1.1" +version = { workspace = true } authors = { workspace = true } edition = { workspace = true } license = { workspace = true } diff --git a/contracts/rewards-collector/neutron/src/lib.rs b/contracts/rewards-collector/neutron/src/lib.rs index 2708d9c58..ea9e6dee6 100644 --- a/contracts/rewards-collector/neutron/src/lib.rs +++ b/contracts/rewards-collector/neutron/src/lib.rs @@ -47,6 +47,6 @@ pub mod entry { #[entry_point] pub fn migrate(deps: DepsMut, _env: Env, _msg: Empty) -> ContractResult { - migrations::v2_0_0::migrate(deps) + migrations::v2_0_0::migrate(deps) } } diff --git a/contracts/rewards-collector/neutron/src/migrations/mod.rs b/contracts/rewards-collector/neutron/src/migrations/mod.rs index ee8f73da3..7592b6f12 100644 --- a/contracts/rewards-collector/neutron/src/migrations/mod.rs +++ b/contracts/rewards-collector/neutron/src/migrations/mod.rs @@ -1 +1 @@ -pub mod v2_0_0; \ No newline at end of file +pub mod v2_0_0; diff --git a/contracts/rewards-collector/neutron/tests/tests/mod.rs b/contracts/rewards-collector/neutron/tests/tests/mod.rs index cd51594c8..c6a4f2bcb 100644 --- a/contracts/rewards-collector/neutron/tests/tests/mod.rs +++ b/contracts/rewards-collector/neutron/tests/tests/mod.rs @@ -1 +1 @@ -mod test_migration_v2; \ No newline at end of file +mod test_migration_v2; diff --git a/contracts/rewards-collector/neutron/tests/tests/test_migration_v2.rs b/contracts/rewards-collector/neutron/tests/tests/test_migration_v2.rs index 20621f3cf..a56fb318e 100644 --- a/contracts/rewards-collector/neutron/tests/tests/test_migration_v2.rs +++ b/contracts/rewards-collector/neutron/tests/tests/test_migration_v2.rs @@ -58,12 +58,12 @@ fn successful_migration() { assert!(res.data.is_none()); assert_eq!( res.attributes, - vec![attr("action", "migrate"), attr("from_version", "2.1.0"), attr("to_version", "2.2.1")] + vec![attr("action", "migrate"), attr("from_version", "1.2.0"), attr("to_version", "2.1.0")] ); let new_contract_version = ContractVersion { contract: "crates.io:mars-rewards-collector-neutron".to_string(), - version: "2.2.1".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/Cargo.toml b/contracts/rewards-collector/osmosis/Cargo.toml index 3538a4027..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 } diff --git a/contracts/rewards-collector/osmosis/src/lib.rs b/contracts/rewards-collector/osmosis/src/lib.rs index 36493bba4..f580b36d1 100644 --- a/contracts/rewards-collector/osmosis/src/lib.rs +++ b/contracts/rewards-collector/osmosis/src/lib.rs @@ -36,7 +36,7 @@ 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 + entry_point, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, }; use cw2::set_contract_version; use mars_rewards_collector_base::{ContractError, ContractResult}; @@ -77,11 +77,7 @@ pub mod entry { } #[entry_point] - pub fn migrate( - deps: DepsMut, - _env: Env, - _msg: Empty, - ) -> Result { + pub fn migrate(deps: DepsMut, _env: Env, _msg: Empty) -> Result { migrations::v2_1_1::migrate(deps) } } diff --git a/schemas/mars-address-provider/mars-address-provider.json b/schemas/mars-address-provider/mars-address-provider.json index 7cc314ab9..20dd899bf 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#", From 537e89f9a93d314f5b27844d67bce3d491b0b21e Mon Sep 17 00:00:00 2001 From: Mark Watney Date: Thu, 20 Feb 2025 22:29:16 +0800 Subject: [PATCH 4/8] remove redundant file --- .../perps/tests/tests/helpers/mock_env.rs | 847 ------------------ 1 file changed, 847 deletions(-) delete mode 100644 contracts/perps/tests/tests/helpers/mock_env.rs diff --git a/contracts/perps/tests/tests/helpers/mock_env.rs b/contracts/perps/tests/tests/helpers/mock_env.rs deleted file mode 100644 index a9ef5f91e..000000000 --- a/contracts/perps/tests/tests/helpers/mock_env.rs +++ /dev/null @@ -1,847 +0,0 @@ -#![allow(dead_code)] -use std::mem::take; - -use anyhow::Result as AnyResult; -use cosmwasm_std::{coin, Addr, Coin, Decimal, Empty, Int128, Timestamp, Uint128}; -use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; -use cw_paginate::PaginationResponse; -use mars_oracle_osmosis::OsmosisPriceSourceUnchecked; -use mars_owner::{OwnerResponse, OwnerUpdate}; -use mars_testing::integration::mock_contracts::mock_rewards_collector_osmosis_contract; -use mars_types::{ - address_provider::{self, MarsAddressType}, - incentives, - oracle::{self, ActionKind}, - params::{ - self, EmergencyUpdate, - ExecuteMsg::{self, UpdatePerpParams}, - PerpParams, PerpParamsUpdate, - }, - perps::{ - self, AccountingResponse, Config, ConfigUpdates, MarketResponse, MarketStateResponse, - PnlAmounts, PositionFeesResponse, PositionResponse, PositionsByAccountResponse, TradingFee, - VaultPositionResponse, VaultResponse, - }, - rewards_collector::{self, RewardConfig, TransferType}, -}; - -use super::{ - contracts::{mock_oracle_contract, mock_perps_contract}, - mock_address_provider_contract, mock_credit_manager_contract, mock_incentives_contract, - mock_params_contract, -}; - -pub const ONE_HOUR_SEC: u64 = 3600u64; - -pub struct MockEnv { - app: BasicApp, - pub owner: Addr, - pub perps: Addr, - pub oracle: Addr, - pub params: Addr, - pub credit_manager: Addr, - pub address_provider: Addr, - pub rewards_collector: Addr, -} - -pub struct MockEnvBuilder { - app: BasicApp, - deployer: Addr, - oracle_base_denom: String, - perps_base_denom: String, - cooldown_period: u64, - max_positions: u8, - protocol_fee_rate: Decimal, - pub address_provider: Option, - target_vault_collateralization_ratio: Decimal, - pub emergency_owner: Option, - deleverage_enabled: bool, - withdraw_enabled: bool, - max_unlocks: u8, -} - -#[allow(clippy::new_ret_no_self)] -impl MockEnv { - pub fn new() -> MockEnvBuilder { - MockEnvBuilder { - app: App::default(), - deployer: Addr::unchecked("deployer"), - oracle_base_denom: "uusd".to_string(), - perps_base_denom: "uusdc".to_string(), - cooldown_period: 3600, - max_positions: 4, - protocol_fee_rate: Decimal::percent(0), - address_provider: None, - target_vault_collateralization_ratio: Decimal::percent(125), - emergency_owner: None, - deleverage_enabled: true, - withdraw_enabled: true, - max_unlocks: 5, - } - } - - pub fn fund_accounts(&mut self, addrs: &[&Addr], amount: u128, denoms: &[&str]) { - for addr in addrs { - let coins: Vec<_> = denoms.iter().map(|&d| coin(amount, d)).collect(); - self.fund_account(addr, &coins); - } - } - - pub fn fund_account(&mut self, addr: &Addr, coins: &[Coin]) { - self.app - .sudo(SudoMsg::Bank(BankSudo::Mint { - to_address: addr.to_string(), - amount: coins.to_vec(), - })) - .unwrap(); - } - - pub fn query_balance(&self, addr: &Addr, denom: &str) -> Coin { - self.app.wrap().query_balance(addr.clone(), denom).unwrap() - } - - pub fn increment_by_blocks(&mut self, num_of_blocks: u64) { - self.app.update_block(|block| { - block.height += num_of_blocks; - // assume block time = 6 sec - block.time = block.time.plus_seconds(num_of_blocks * 6); - }) - } - - pub fn increment_by_time(&mut self, seconds: u64) { - self.app.update_block(|block| { - block.height += seconds / 6; - // assume block time = 6 sec - block.time = block.time.plus_seconds(seconds); - }) - } - - pub fn set_block_time(&mut self, seconds: u64) { - self.app.update_block(|block| { - block.time = Timestamp::from_seconds(seconds); - }) - } - - pub fn query_block_time(&self) -> u64 { - self.app.block_info().time.seconds() - } - - //-------------------------------------------------------------------------------------------------- - // Execute Msgs - //-------------------------------------------------------------------------------------------------- - - pub fn update_owner(&mut self, sender: &Addr, update: OwnerUpdate) -> AnyResult { - self.app.execute_contract( - sender.clone(), - self.perps.clone(), - &perps::ExecuteMsg::UpdateOwner(update), - &[], - ) - } - - pub fn update_config( - &mut self, - sender: &Addr, - updates: ConfigUpdates, - ) -> AnyResult { - self.app.execute_contract( - sender.clone(), - self.perps.clone(), - &perps::ExecuteMsg::UpdateConfig { - updates, - }, - &[], - ) - } - - pub fn deposit_to_vault( - &mut self, - sender: &Addr, - account_id: Option<&str>, - max_shares_receivable: Option, - funds: &[Coin], - ) -> AnyResult { - self.app.execute_contract( - sender.clone(), - self.perps.clone(), - &perps::ExecuteMsg::Deposit { - account_id: account_id.map(|s| s.to_string()), - max_shares_receivable, - }, - funds, - ) - } - - pub fn unlock_from_vault( - &mut self, - sender: &Addr, - account_id: Option<&str>, - shares: Uint128, - ) -> AnyResult { - self.app.execute_contract( - sender.clone(), - self.perps.clone(), - &perps::ExecuteMsg::Unlock { - account_id: account_id.map(|s| s.to_string()), - shares, - }, - &[], - ) - } - - pub fn withdraw_from_vault( - &mut self, - sender: &Addr, - account_id: Option<&str>, - min_receive: Option, - ) -> AnyResult { - self.app.execute_contract( - sender.clone(), - self.perps.clone(), - &perps::ExecuteMsg::Withdraw { - account_id: account_id.map(|s| s.to_string()), - min_receive, - }, - &[], - ) - } - - pub fn execute_perp_order( - &mut self, - sender: &Addr, - account_id: &str, - denom: &str, - size: Int128, - reduce_only: Option, - funds: &[Coin], - ) -> AnyResult { - self.app.execute_contract( - sender.clone(), - self.perps.clone(), - &perps::ExecuteMsg::ExecuteOrder { - account_id: account_id.to_string(), - denom: denom.to_string(), - size, - reduce_only, - }, - funds, - ) - } - - pub fn close_all_positions( - &mut self, - sender: &Addr, - account_id: &str, - funds: &[Coin], - ) -> AnyResult { - self.app.execute_contract( - sender.clone(), - self.perps.clone(), - &perps::ExecuteMsg::CloseAllPositions { - account_id: account_id.to_string(), - action: None, - }, - funds, - ) - } - - pub fn set_price( - &mut self, - sender: &Addr, - denom: &str, - price: Decimal, - ) -> AnyResult { - self.app.execute_contract( - sender.clone(), - self.oracle.clone(), - &oracle::ExecuteMsg::::SetPriceSource { - denom: denom.to_string(), - price_source: OsmosisPriceSourceUnchecked::Fixed { - price, - }, - }, - &[], - ) - } - - pub fn update_perp_params(&mut self, sender: &Addr, update: PerpParamsUpdate) { - self.app - .execute_contract(sender.clone(), self.params.clone(), &UpdatePerpParams(update), &[]) - .unwrap(); - } - - pub fn update_market(&mut self, sender: &Addr, params: PerpParams) -> AnyResult { - self.app.execute_contract( - sender.clone(), - self.perps.clone(), - &perps::ExecuteMsg::UpdateMarket { - params, - }, - &[], - ) - } - - pub fn emergency_params_update( - &mut self, - sender: &Addr, - update: EmergencyUpdate, - ) -> AnyResult { - self.app.execute_contract( - sender.clone(), - self.params.clone(), - &mars_types::params::ExecuteMsg::EmergencyUpdate(update), - &[], - ) - } - - //-------------------------------------------------------------------------------------------------- - // Queries - //-------------------------------------------------------------------------------------------------- - - pub fn query_owner(&self) -> Addr { - let res = self.query_ownership(); - Addr::unchecked(res.owner.unwrap()) - } - - pub fn query_ownership(&self) -> OwnerResponse { - self.app.wrap().query_wasm_smart(self.perps.clone(), &perps::QueryMsg::Owner {}).unwrap() - } - - pub fn query_config(&self) -> Config { - self.app.wrap().query_wasm_smart(self.perps.clone(), &perps::QueryMsg::Config {}).unwrap() - } - - pub fn query_vault(&self) -> VaultResponse { - self.app - .wrap() - .query_wasm_smart( - self.perps.clone(), - &perps::QueryMsg::Vault { - action: None, - }, - ) - .unwrap() - } - - pub fn query_market_state(&self, denom: &str) -> MarketStateResponse { - self.app - .wrap() - .query_wasm_smart( - self.perps.clone(), - &perps::QueryMsg::MarketState { - denom: denom.to_string(), - }, - ) - .unwrap() - } - - pub fn query_market(&self, denom: &str) -> MarketResponse { - self.app - .wrap() - .query_wasm_smart( - self.perps.clone(), - &perps::QueryMsg::Market { - denom: denom.to_string(), - }, - ) - .unwrap() - } - - pub fn query_markets( - &self, - start_after: Option, - limit: Option, - ) -> PaginationResponse { - self.app - .wrap() - .query_wasm_smart( - self.perps.clone(), - &perps::QueryMsg::Markets { - start_after, - limit, - }, - ) - .unwrap() - } - - pub fn query_cm_vault_position(&self, account_id: &str) -> Option { - self.query_vault_position(self.credit_manager.as_str(), Some(account_id)) - } - - pub fn query_vault_position( - &self, - user_address: &str, - account_id: Option<&str>, - ) -> Option { - self.app - .wrap() - .query_wasm_smart( - self.perps.clone(), - &perps::QueryMsg::VaultPosition { - user_address: user_address.to_string(), - account_id: account_id.map(|s| s.to_string()), - }, - ) - .unwrap() - } - - pub fn query_position(&self, account_id: &str, denom: &str) -> PositionResponse { - self.query_position_with_order_size(account_id, denom, None) - } - - pub fn query_position_with_order_size( - &self, - account_id: &str, - denom: &str, - order_size: Option, - ) -> PositionResponse { - self.app - .wrap() - .query_wasm_smart( - self.perps.clone(), - &perps::QueryMsg::Position { - account_id: account_id.to_string(), - denom: denom.to_string(), - order_size, - reduce_only: None, - }, - ) - .unwrap() - } - - pub fn query_positions( - &self, - start_after: Option<(String, String)>, - limit: Option, - ) -> Vec { - self.app - .wrap() - .query_wasm_smart( - self.perps.clone(), - &perps::QueryMsg::Positions { - start_after, - limit, - }, - ) - .unwrap() - } - - pub fn query_positions_by_account_id( - &self, - account_id: &str, - action: ActionKind, - ) -> PositionsByAccountResponse { - self.app - .wrap() - .query_wasm_smart( - self.perps.clone(), - &perps::QueryMsg::PositionsByAccount { - account_id: account_id.to_string(), - action: Some(action), - }, - ) - .unwrap() - } - - pub fn query_market_accounting(&self, denom: &str) -> AccountingResponse { - self.app - .wrap() - .query_wasm_smart( - self.perps.clone(), - &perps::QueryMsg::MarketAccounting { - denom: denom.to_string(), - }, - ) - .unwrap() - } - - pub fn query_total_accounting(&self) -> AccountingResponse { - self.app - .wrap() - .query_wasm_smart(self.perps.clone(), &perps::QueryMsg::TotalAccounting {}) - .unwrap() - } - - pub fn query_realized_pnl_by_account_and_market( - &self, - account_id: &str, - denom: &str, - ) -> PnlAmounts { - self.app - .wrap() - .query_wasm_smart( - self.perps.clone(), - &perps::QueryMsg::RealizedPnlByAccountAndMarket { - account_id: account_id.to_string(), - denom: denom.to_string(), - }, - ) - .unwrap() - } - - pub fn query_opening_fee(&self, denom: &str, size: Int128) -> TradingFee { - self.app - .wrap() - .query_wasm_smart( - self.perps.clone(), - &perps::QueryMsg::OpeningFee { - denom: denom.to_string(), - size, - }, - ) - .unwrap() - } - - pub fn query_position_fees( - &self, - account_id: &str, - denom: &str, - new_size: Int128, - ) -> PositionFeesResponse { - self.app - .wrap() - .query_wasm_smart( - self.perps.clone(), - &perps::QueryMsg::PositionFees { - account_id: account_id.to_string(), - denom: denom.to_string(), - new_size, - }, - ) - .unwrap() - } - - pub fn query_perp_params(&self, denom: &str) -> PerpParams { - self.app - .wrap() - .query_wasm_smart( - self.params.clone(), - ¶ms::QueryMsg::PerpParams { - denom: denom.to_string(), - }, - ) - .unwrap() - } -} - -impl MockEnvBuilder { - pub fn build(&mut self) -> AnyResult { - let address_provider_contract = self.get_address_provider(); - let oracle_contract = self.deploy_oracle(); - let params_contract = self.deploy_params(address_provider_contract.as_str()); - let credit_manager_contract = self.deploy_credit_manager(); - let rewards_collector_contract = - self.deploy_rewards_collector(address_provider_contract.as_str()); - let perps_contract = self.deploy_perps(address_provider_contract.as_str()); - let incentives_contract = self.deploy_incentives(&address_provider_contract); - - self.update_address_provider( - &address_provider_contract, - MarsAddressType::Incentives, - &incentives_contract, - ); - self.update_address_provider( - &address_provider_contract, - MarsAddressType::Perps, - &perps_contract, - ); - - if self.emergency_owner.is_some() { - self.set_emergency_owner(¶ms_contract, &self.emergency_owner.clone().unwrap()); - } - - Ok(MockEnv { - app: take(&mut self.app), - owner: self.deployer.clone(), - perps: perps_contract, - oracle: oracle_contract, - params: params_contract, - credit_manager: credit_manager_contract, - address_provider: address_provider_contract, - rewards_collector: rewards_collector_contract, - }) - } - - fn deploy_address_provider(&mut self) -> Addr { - let contract = mock_address_provider_contract(); - let code_id = self.app.store_code(contract); - - self.app - .instantiate_contract( - code_id, - self.deployer.clone(), - &address_provider::InstantiateMsg { - owner: self.deployer.clone().to_string(), - prefix: "".to_string(), - }, - &[], - "mock-address-provider", - None, - ) - .unwrap() - } - - fn deploy_oracle(&mut self) -> Addr { - let contract = mock_oracle_contract(); - let code_id = self.app.store_code(contract); - - let addr = self - .app - .instantiate_contract( - code_id, - self.deployer.clone(), - &oracle::InstantiateMsg:: { - owner: self.deployer.clone().to_string(), - base_denom: self.oracle_base_denom.clone(), - custom_init: None, - }, - &[], - "mock-oracle", - None, - ) - .unwrap(); - - self.set_address(MarsAddressType::Oracle, addr.clone()); - - addr - } - - fn deploy_params(&mut self, address_provider: &str) -> Addr { - let contract = mock_params_contract(); - let code_id = self.app.store_code(contract); - - let addr = self - .app - .instantiate_contract( - code_id, - self.deployer.clone(), - ¶ms::InstantiateMsg { - owner: self.deployer.clone().to_string(), - risk_manager: None, - address_provider: address_provider.to_string(), - max_perp_params: 40, - }, - &[], - "mock-params", - None, - ) - .unwrap(); - - self.set_address(MarsAddressType::Params, addr.clone()); - - addr - } - - fn deploy_incentives(&mut self, address_provider_addr: &Addr) -> Addr { - let code_id = self.app.store_code(mock_incentives_contract()); - - self.app - .instantiate_contract( - code_id, - self.deployer.clone(), - &incentives::InstantiateMsg { - owner: self.deployer.to_string(), - address_provider: address_provider_addr.to_string(), - epoch_duration: 604800, - max_whitelisted_denoms: 10, - }, - &[], - "incentives", - None, - ) - .unwrap() - } - - fn deploy_perps(&mut self, address_provider: &str) -> Addr { - let code_id = self.app.store_code(mock_perps_contract()); - - self.app - .instantiate_contract( - code_id, - self.deployer.clone(), - &perps::InstantiateMsg { - address_provider: address_provider.to_string(), - base_denom: self.perps_base_denom.clone(), - cooldown_period: self.cooldown_period, - max_positions: self.max_positions, - protocol_fee_rate: self.protocol_fee_rate, - target_vault_collateralization_ratio: self.target_vault_collateralization_ratio, - deleverage_enabled: self.deleverage_enabled, - vault_withdraw_enabled: self.withdraw_enabled, - max_unlocks: self.max_unlocks, - }, - &[], - "mock-perps", - None, - ) - .unwrap() - } - - fn deploy_credit_manager(&mut self) -> Addr { - let contract = mock_credit_manager_contract(); - let code_id = self.app.store_code(contract); - - let addr = self - .app - .instantiate_contract( - code_id, - self.deployer.clone(), - &Empty {}, - &[], - "mock-credit-manager", - None, - ) - .unwrap(); - - self.set_address(MarsAddressType::CreditManager, addr.clone()); - - addr - } - - fn deploy_rewards_collector(&mut self, address_provider: &str) -> Addr { - let contract = mock_rewards_collector_osmosis_contract(); - let code_id = self.app.store_code(contract); - - let addr = self - .app - .instantiate_contract( - code_id, - self.deployer.clone(), - &rewards_collector::InstantiateMsg { - owner: self.deployer.clone().to_string(), - address_provider: address_provider.to_string(), - safety_tax_rate: Default::default(), - revenue_share_tax_rate: Default::default(), - 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: "".to_string(), - timeout_seconds: 1, - slippage_tolerance: Default::default(), - }, - &[], - "mock-rewards-collector", - None, - ) - .unwrap(); - - self.set_address(MarsAddressType::RewardsCollector, addr.clone()); - - addr - } - - fn set_address(&mut self, address_type: MarsAddressType, address: Addr) { - let address_provider_addr = self.get_address_provider(); - - self.app - .execute_contract( - self.deployer.clone(), - address_provider_addr, - &address_provider::ExecuteMsg::SetAddress { - address_type, - address: address.into(), - }, - &[], - ) - .unwrap(); - } - - fn get_address_provider(&mut self) -> Addr { - if self.address_provider.is_none() { - let addr = self.deploy_address_provider(); - - self.address_provider = Some(addr); - } - self.address_provider.clone().unwrap() - } - - fn update_address_provider( - &mut self, - address_provider_addr: &Addr, - address_type: MarsAddressType, - addr: &Addr, - ) { - self.app - .execute_contract( - self.deployer.clone(), - address_provider_addr.clone(), - &address_provider::ExecuteMsg::SetAddress { - address_type, - address: addr.to_string(), - }, - &[], - ) - .unwrap(); - } - - fn set_emergency_owner(&mut self, params_contract: &Addr, eo: &str) { - self.app - .execute_contract( - self.deployer.clone(), - params_contract.clone(), - &ExecuteMsg::UpdateOwner(OwnerUpdate::SetEmergencyOwner { - emergency_owner: eo.to_string(), - }), - &[], - ) - .unwrap(); - } - - //-------------------------------------------------------------------------------------------------- - // Setter functions - //-------------------------------------------------------------------------------------------------- - - pub fn oracle_base_denom(&mut self, denom: &str) -> &mut Self { - self.oracle_base_denom = denom.to_string(); - self - } - - pub fn perps_base_denom(&mut self, denom: &str) -> &mut Self { - self.perps_base_denom = denom.to_string(); - self - } - - pub fn cooldown_period(&mut self, cp: u64) -> &mut Self { - self.cooldown_period = cp; - self - } - - pub fn max_positions(&mut self, max_positions: u8) -> &mut Self { - self.max_positions = max_positions; - self - } - - pub fn protocol_fee_rate(&mut self, rate: Decimal) -> &mut Self { - self.protocol_fee_rate = rate; - self - } - - pub fn target_vault_collaterization_ratio(&mut self, ratio: Decimal) -> &mut Self { - self.target_vault_collateralization_ratio = ratio; - self - } - - pub fn withdraw_enabled(&mut self, enabled: bool) -> &mut Self { - self.withdraw_enabled = enabled; - self - } - - pub fn emergency_owner(&mut self, eo: &str) -> &mut Self { - self.emergency_owner = Some(eo.to_string()); - self - } - - pub fn max_unlocks(&mut self, max_unlocks: u8) -> &mut Self { - self.max_unlocks = max_unlocks; - self - } -} From e3de253725131fbc488d7e91e9f27726a0b20617 Mon Sep 17 00:00:00 2001 From: Mark Watney Date: Thu, 20 Feb 2025 23:09:10 +0800 Subject: [PATCH 5/8] remove perps from ap --- packages/types/src/address_provider.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/types/src/address_provider.rs b/packages/types/src/address_provider.rs index c19bea5f9..fd47f6d6b 100644 --- a/packages/types/src/address_provider.rs +++ b/packages/types/src/address_provider.rs @@ -37,8 +37,6 @@ pub enum MarsAddressType { Swapper, /// Astroport incentives contract AstroportIncentives, - /// Perps contract - Perps, /// The address that shall receive the revenue share given to neutron (10%) RevenueShare, } @@ -57,7 +55,6 @@ impl fmt::Display for MarsAddressType { MarsAddressType::SafetyFund => "safety_fund", MarsAddressType::Swapper => "swapper", MarsAddressType::AstroportIncentives => "astroport_incentives", - MarsAddressType::Perps => "perps", MarsAddressType::RevenueShare => "revenue_share", }; write!(f, "{s}") @@ -80,7 +77,6 @@ impl FromStr for MarsAddressType { "safety_fund" => Ok(MarsAddressType::SafetyFund), "swapper" => Ok(MarsAddressType::Swapper), "astroport_incentives" => Ok(MarsAddressType::AstroportIncentives), - "perps" => Ok(MarsAddressType::Perps), "revenue_share" => Ok(MarsAddressType::RevenueShare), _ => Err(StdError::parse_err(type_name::(), s)), } From 9977fbf4bfe082cfb40b2de27d53cea4a754a97e Mon Sep 17 00:00:00 2001 From: Mark Watney Date: Tue, 11 Mar 2025 12:01:38 +0800 Subject: [PATCH 6/8] update types --- .../mars-address-provider.json | 35 ------------------- .../MarsAddressProvider.types.ts | 1 - 2 files changed, 36 deletions(-) diff --git a/schemas/mars-address-provider/mars-address-provider.json b/schemas/mars-address-provider/mars-address-provider.json index 20dd899bf..239f1089a 100644 --- a/schemas/mars-address-provider/mars-address-provider.json +++ b/schemas/mars-address-provider/mars-address-provider.json @@ -116,13 +116,6 @@ "astroport_incentives" ] }, - { - "description": "Perps contract", - "type": "string", - "enum": [ - "perps" - ] - }, { "description": "The address that shall receive the revenue share given to neutron (10%)", "type": "string", @@ -341,13 +334,6 @@ "astroport_incentives" ] }, - { - "description": "Perps contract", - "type": "string", - "enum": [ - "perps" - ] - }, { "description": "The address that shall receive the revenue share given to neutron (10%)", "type": "string", @@ -434,13 +420,6 @@ "astroport_incentives" ] }, - { - "description": "Perps contract", - "type": "string", - "enum": [ - "perps" - ] - }, { "description": "The address that shall receive the revenue share given to neutron (10%)", "type": "string", @@ -530,13 +509,6 @@ "astroport_incentives" ] }, - { - "description": "Perps contract", - "type": "string", - "enum": [ - "perps" - ] - }, { "description": "The address that shall receive the revenue share given to neutron (10%)", "type": "string", @@ -626,13 +598,6 @@ "astroport_incentives" ] }, - { - "description": "Perps contract", - "type": "string", - "enum": [ - "perps" - ] - }, { "description": "The address that shall receive the revenue share given to neutron (10%)", "type": "string", diff --git a/scripts/types/generated/mars-address-provider/MarsAddressProvider.types.ts b/scripts/types/generated/mars-address-provider/MarsAddressProvider.types.ts index 0cb8e97dd..6cef6fb7c 100644 --- a/scripts/types/generated/mars-address-provider/MarsAddressProvider.types.ts +++ b/scripts/types/generated/mars-address-provider/MarsAddressProvider.types.ts @@ -26,7 +26,6 @@ export type MarsAddressType = | 'safety_fund' | 'swapper' | 'astroport_incentives' - | 'perps' | 'revenue_share' export type OwnerUpdate = | { From bf8b3769070ef28e09d99b292afe0c6f242b01c5 Mon Sep 17 00:00:00 2001 From: Mark Watney Date: Tue, 11 Mar 2025 17:55:52 +0800 Subject: [PATCH 7/8] fix audit warn --- .cargo/audit.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 From 9db613b05828f4aef69d80fff22098cfb7e95af3 Mon Sep 17 00:00:00 2001 From: Mark Watney <80194956+markonmars@users.noreply.github.com> Date: Wed, 12 Mar 2025 17:14:47 +0800 Subject: [PATCH 8/8] fix typo (#446) --- contracts/rewards-collector/base/src/contract.rs | 2 +- contracts/rewards-collector/base/src/error.rs | 4 ++-- .../rewards-collector/osmosis/tests/tests/test_swap.rs | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/contracts/rewards-collector/base/src/contract.rs b/contracts/rewards-collector/base/src/contract.rs index 0e9bfd740..9845be597 100644 --- a/contracts/rewards-collector/base/src/contract.rs +++ b/contracts/rewards-collector/base/src/contract.rs @@ -476,7 +476,7 @@ where return Err(ContractError::SlippageLimitExceeded { denom_in: asset_in_denom, denom_out: asset_out_denom, - min_receive_minumum: min_receive_lower_limit, + min_receive_minimum: min_receive_lower_limit, min_receive_given: min_receive, }); } diff --git a/contracts/rewards-collector/base/src/error.rs b/contracts/rewards-collector/base/src/error.rs index dbc88cf3c..71f9c16f8 100644 --- a/contracts/rewards-collector/base/src/error.rs +++ b/contracts/rewards-collector/base/src/error.rs @@ -54,11 +54,11 @@ pub enum ContractError { reason: String, }, - #[error("Min receive given for swap: {denom_in} -> {denom_out} is too small. `min_receive` allowed: {min_receive_minumum}, `min_receive` given: {min_receive_given}")] + #[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_minumum: Uint128, + min_receive_minimum: Uint128, min_receive_given: Uint128, }, diff --git a/contracts/rewards-collector/osmosis/tests/tests/test_swap.rs b/contracts/rewards-collector/osmosis/tests/tests/test_swap.rs index b99ed89ab..cb402f024 100644 --- a/contracts/rewards-collector/osmosis/tests/tests/test_swap.rs +++ b/contracts/rewards-collector/osmosis/tests/tests/test_swap.rs @@ -225,9 +225,9 @@ fn swap_fails_if_slippage_limit_exceeded() { // 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 - // minumum revenue share (atom -> usdc) = 8888 * 12.125 = 107767 - // minumum safety fund (atom -> usdc) = 31110 * 12.125 = 377208 - // minumum fee collector (atom -> mars) = 57778 * 24.25= 1401116 + // 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( @@ -259,7 +259,7 @@ fn swap_fails_if_slippage_limit_exceeded() { ContractError::SlippageLimitExceeded { denom_in: atom_denom.clone(), denom_out: usdc_denom.clone(), - min_receive_minumum: Uint128::new(377208), + min_receive_minimum: Uint128::new(377208), min_receive_given: Uint128::new(377207), } ); @@ -294,7 +294,7 @@ fn swap_fails_if_slippage_limit_exceeded() { ContractError::SlippageLimitExceeded { denom_in: atom_denom.clone(), denom_out: mars_denom.clone(), - min_receive_minumum: Uint128::new(1401116), + min_receive_minimum: Uint128::new(1401116), min_receive_given: Uint128::new(1401115), } );