From 7d29f3c26eb280210cd3bc34c14cbbec0bdddea3 Mon Sep 17 00:00:00 2001 From: kaimen-sano Date: Sun, 14 Apr 2024 19:42:48 +1200 Subject: [PATCH] feat(pool-manager): allow updating config --- .../pool-manager/src/contract.rs | 20 +- .../pool-manager/src/manager/mod.rs | 3 + .../pool-manager/src/manager/update_config.rs | 42 ++++ .../liquidity_hub/pool-manager/src/queries.rs | 9 +- .../liquidity_hub/pool-manager/src/state.rs | 15 +- .../src/tests/integration_tests.rs | 191 ++++++++++++------ .../pool-manager/src/tests/suite.rs | 53 ++++- packages/white-whale-std/src/pool_manager.rs | 31 ++- 8 files changed, 273 insertions(+), 91 deletions(-) create mode 100644 contracts/liquidity_hub/pool-manager/src/manager/update_config.rs diff --git a/contracts/liquidity_hub/pool-manager/src/contract.rs b/contracts/liquidity_hub/pool-manager/src/contract.rs index bf096dd20..18c601359 100644 --- a/contracts/liquidity_hub/pool-manager/src/contract.rs +++ b/contracts/liquidity_hub/pool-manager/src/contract.rs @@ -84,11 +84,7 @@ pub fn execute( to, pair_identifier, } => { - let to_addr = if let Some(to_addr) = to { - Some(deps.api.addr_validate(&to_addr)?) - } else { - None - }; + let to_addr = to.map(|addr| deps.api.addr_validate(&addr)).transpose()?; swap::commands::swap( deps, @@ -152,6 +148,19 @@ pub fn execute( // ) // } ExecuteMsg::AddSwapRoutes { swap_routes: _ } => Ok(Response::new()), + ExecuteMsg::UpdateConfig { + whale_lair_addr, + owner_addr, + pool_creation_fee, + feature_toggle, + } => manager::update_config( + deps, + info, + owner_addr, + whale_lair_addr, + pool_creation_fee, + feature_toggle, + ), } } @@ -174,6 +183,7 @@ fn optional_addr_validate( #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result { match msg { + QueryMsg::Config {} => Ok(to_json_binary(&queries::query_config(deps)?)?), QueryMsg::NativeTokenDecimals { denom } => Ok(to_json_binary( &queries::query_native_token_decimal(deps, denom)?, )?), diff --git a/contracts/liquidity_hub/pool-manager/src/manager/mod.rs b/contracts/liquidity_hub/pool-manager/src/manager/mod.rs index 82b6da3c0..6a13468f3 100644 --- a/contracts/liquidity_hub/pool-manager/src/manager/mod.rs +++ b/contracts/liquidity_hub/pool-manager/src/manager/mod.rs @@ -1 +1,4 @@ pub mod commands; + +mod update_config; +pub use update_config::update_config; diff --git a/contracts/liquidity_hub/pool-manager/src/manager/update_config.rs b/contracts/liquidity_hub/pool-manager/src/manager/update_config.rs new file mode 100644 index 000000000..54d3cc1c1 --- /dev/null +++ b/contracts/liquidity_hub/pool-manager/src/manager/update_config.rs @@ -0,0 +1,42 @@ +use cosmwasm_std::{Coin, DepsMut, MessageInfo, Response}; +use white_whale_std::pool_network::pair::FeatureToggle; + +use crate::{state::MANAGER_CONFIG, ContractError}; + +pub fn update_config( + deps: DepsMut, + info: MessageInfo, + owner_addr: Option, + whale_lair_addr: Option, + pool_creation_fee: Option, + feature_toggle: Option, +) -> Result { + MANAGER_CONFIG.update(deps.storage, |mut config| { + // permission check + if info.sender != config.owner { + return Err(ContractError::Unauthorized {}); + } + + if let Some(owner) = owner_addr { + let owner_addr = deps.api.addr_validate(&owner)?; + config.owner = owner_addr; + } + + if let Some(whale_lair_addr) = whale_lair_addr { + let whale_lair_addr = deps.api.addr_validate(&whale_lair_addr)?; + config.whale_lair_addr = whale_lair_addr; + } + + if let Some(pool_creation_fee) = pool_creation_fee { + config.pool_creation_fee = pool_creation_fee; + } + + if let Some(feature_toggle) = feature_toggle { + config.feature_toggle = feature_toggle; + } + + Ok(config) + })?; + + Ok(Response::new().add_attribute("action", "update_config")) +} diff --git a/contracts/liquidity_hub/pool-manager/src/queries.rs b/contracts/liquidity_hub/pool-manager/src/queries.rs index 3090ed6ad..0ae87dad7 100644 --- a/contracts/liquidity_hub/pool-manager/src/queries.rs +++ b/contracts/liquidity_hub/pool-manager/src/queries.rs @@ -1,7 +1,7 @@ use std::cmp::Ordering; use cosmwasm_std::{Coin, Decimal256, Deps, Env, Fraction, Order, StdResult, Uint128}; -use white_whale_std::pool_manager::{SwapOperation, SwapRouteResponse}; +use white_whale_std::pool_manager::{Config, SwapOperation, SwapRouteResponse}; use white_whale_std::pool_network::{ asset::PairType, factory::NativeTokenDecimalsResponse, @@ -9,7 +9,7 @@ use white_whale_std::pool_network::{ // router::SimulateSwapOperationsResponse, }; -use crate::state::NATIVE_TOKEN_DECIMALS; +use crate::state::{MANAGER_CONFIG, NATIVE_TOKEN_DECIMALS}; use crate::{ helpers::{self, calculate_stableswap_y, StableSwapDirection}, state::get_pair_by_identifier, @@ -17,6 +17,11 @@ use crate::{ }; use crate::{math::Decimal256Helper, state::SWAP_ROUTES}; +/// Query the config of the contract. +pub fn query_config(deps: Deps) -> Result { + Ok(MANAGER_CONFIG.load(deps.storage)?) +} + /// Query the native token decimals pub fn query_native_token_decimal( deps: Deps, diff --git a/contracts/liquidity_hub/pool-manager/src/state.rs b/contracts/liquidity_hub/pool-manager/src/state.rs index 896bf509c..1e5251e96 100644 --- a/contracts/liquidity_hub/pool-manager/src/state.rs +++ b/contracts/liquidity_hub/pool-manager/src/state.rs @@ -1,8 +1,6 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Coin, Deps}; +use cosmwasm_std::Deps; use cw_storage_plus::{Index, IndexList, IndexedMap, Item, Map, UniqueIndex}; use white_whale_std::pool_manager::{PairInfo, SwapOperation}; -use white_whale_std::pool_network::pair::FeatureToggle; use crate::ContractError; @@ -40,15 +38,6 @@ pub const NATIVE_TOKEN_DECIMALS: Map<&[u8], u8> = Map::new("allow_native_token") // Swap routes are used to establish defined routes for a given fee token to a desired fee token and is used for fee collection pub const SWAP_ROUTES: Map<(&str, &str), Vec> = Map::new("swap_routes"); +pub use white_whale_std::pool_manager::Config; pub const MANAGER_CONFIG: Item = Item::new("manager_config"); pub const PAIR_COUNTER: Item = Item::new("vault_count"); - -#[cw_serde] -pub struct Config { - pub whale_lair_addr: Addr, - pub owner: Addr, - // We must set a creation fee on instantiation to prevent spamming of pools - pub pool_creation_fee: Coin, - // Whether or not swaps, deposits, and withdrawals are enabled - pub feature_toggle: FeatureToggle, -} diff --git a/contracts/liquidity_hub/pool-manager/src/tests/integration_tests.rs b/contracts/liquidity_hub/pool-manager/src/tests/integration_tests.rs index 2ffe6794c..8f4fe553d 100644 --- a/contracts/liquidity_hub/pool-manager/src/tests/integration_tests.rs +++ b/contracts/liquidity_hub/pool-manager/src/tests/integration_tests.rs @@ -16,68 +16,6 @@ fn instantiate_normal() { suite.instantiate(suite.senders[0].to_string()); } -#[test] -fn verify_ownership() { - let mut suite = TestingSuite::default_with_balances(vec![]); - let creator = suite.creator(); - let other = suite.senders[1].clone(); - let unauthorized = suite.senders[2].clone(); - - suite - .instantiate_default() - .query_ownership(|result| { - let ownership = result.unwrap(); - assert_eq!(Addr::unchecked(ownership.owner.unwrap()), creator); - }) - .update_ownership( - unauthorized, - cw_ownable::Action::TransferOwnership { - new_owner: other.to_string(), - expiry: None, - }, - |result| { - let err = result.unwrap_err().downcast::().unwrap(); - - match err { - ContractError::OwnershipError { .. } => {} - _ => panic!("Wrong error type, should return ContractError::OwnershipError"), - } - }, - ) - .update_ownership( - creator, - cw_ownable::Action::TransferOwnership { - new_owner: other.to_string(), - expiry: None, - }, - |result| { - result.unwrap(); - }, - ) - .update_ownership( - other.clone(), - cw_ownable::Action::AcceptOwnership, - |result| { - result.unwrap(); - }, - ) - .query_ownership(|result| { - let ownership = result.unwrap(); - assert_eq!(Addr::unchecked(ownership.owner.unwrap()), other); - }) - .update_ownership( - other.clone(), - cw_ownable::Action::RenounceOwnership, - |result| { - result.unwrap(); - }, - ) - .query_ownership(|result| { - let ownership = result.unwrap(); - assert!(ownership.owner.is_none()); - }); -} - // add features `token_factory` so tests are compiled using the correct flag #[test] fn deposit_and_withdraw_sanity_check() { @@ -1832,3 +1770,132 @@ mod swapping { ); } } + +mod ownership { + use white_whale_std::pool_network::pair::FeatureToggle; + + use super::*; + + #[test] + fn verify_ownership() { + let mut suite = TestingSuite::default_with_balances(vec![]); + let creator = suite.creator(); + let other = suite.senders[1].clone(); + let unauthorized = suite.senders[2].clone(); + + suite + .instantiate_default() + .query_ownership(|result| { + let ownership = result.unwrap(); + assert_eq!(Addr::unchecked(ownership.owner.unwrap()), creator); + }) + .update_ownership( + unauthorized, + cw_ownable::Action::TransferOwnership { + new_owner: other.to_string(), + expiry: None, + }, + |result| { + let err = result.unwrap_err().downcast::().unwrap(); + + match err { + ContractError::OwnershipError { .. } => {} + _ => { + panic!("Wrong error type, should return ContractError::OwnershipError") + } + } + }, + ) + .update_ownership( + creator, + cw_ownable::Action::TransferOwnership { + new_owner: other.to_string(), + expiry: None, + }, + |result| { + result.unwrap(); + }, + ) + .update_ownership( + other.clone(), + cw_ownable::Action::AcceptOwnership, + |result| { + result.unwrap(); + }, + ) + .query_ownership(|result| { + let ownership = result.unwrap(); + assert_eq!(Addr::unchecked(ownership.owner.unwrap()), other); + }) + .update_ownership( + other.clone(), + cw_ownable::Action::RenounceOwnership, + |result| { + result.unwrap(); + }, + ) + .query_ownership(|result| { + let ownership = result.unwrap(); + assert!(ownership.owner.is_none()); + }); + } + + #[test] + fn checks_ownership_when_updating_config() { + let mut suite = TestingSuite::default_with_balances(vec![]); + let unauthorized = suite.senders[2].clone(); + + suite.instantiate_default().update_config( + unauthorized.clone(), + None, + None, + None, + None, + |res| { + assert_eq!( + res.unwrap_err().downcast_ref::(), + Some(&ContractError::Unauthorized {}) + ) + }, + ); + } + + #[test] + fn updates_config_fields() { + let mut suite = TestingSuite::default_with_balances(vec![]); + let creator = suite.creator(); + let other = suite.senders[1].clone(); + + suite.instantiate_default(); + let current_pool_creation_fee = suite.query_config().pool_creation_fee; + let initial_config = suite.query_config(); + + suite.update_config( + creator, + Some(other.clone()), + Some(other), + Some(coin( + current_pool_creation_fee + .amount + .checked_add(Uint128::from(1u32)) + .unwrap() + .u128(), + current_pool_creation_fee.denom, + )), + Some(FeatureToggle { + deposits_enabled: false, + swaps_enabled: false, + withdrawals_enabled: false, + }), + |res| { + res.unwrap(); + }, + ); + + let config = suite.query_config(); + assert_ne!(config.owner, initial_config.owner); + assert_ne!(config.whale_lair_addr, initial_config.whale_lair_addr); + assert_ne!(config.pool_creation_fee, initial_config.pool_creation_fee); + assert_ne!(config.feature_toggle, initial_config.feature_toggle); + } +} diff --git a/contracts/liquidity_hub/pool-manager/src/tests/suite.rs b/contracts/liquidity_hub/pool-manager/src/tests/suite.rs index d74de6e50..f1827b354 100644 --- a/contracts/liquidity_hub/pool-manager/src/tests/suite.rs +++ b/contracts/liquidity_hub/pool-manager/src/tests/suite.rs @@ -1,15 +1,17 @@ use cosmwasm_std::testing::MockStorage; -use white_whale_std::pool_manager::SwapOperation; +use white_whale_std::pool_manager::{Config, SwapOperation}; use white_whale_std::pool_manager::{InstantiateMsg, PairInfo}; -use cosmwasm_std::{Addr, Coin, Decimal, Empty, StdResult, Timestamp, Uint128, Uint64}; +use cosmwasm_std::{coin, Addr, Coin, Decimal, Empty, StdResult, Timestamp, Uint128, Uint64}; use cw_multi_test::{ App, AppBuilder, AppResponse, BankKeeper, Contract, ContractWrapper, DistributionKeeper, Executor, FailingModule, GovFailingModule, IbcFailingModule, StakeKeeper, WasmKeeper, }; use white_whale_std::fee::PoolFee; use white_whale_std::pool_network::asset::{AssetInfo, PairType}; -use white_whale_std::pool_network::pair::{ReverseSimulationResponse, SimulationResponse}; +use white_whale_std::pool_network::pair::{ + FeatureToggle, ReverseSimulationResponse, SimulationResponse, +}; use white_whale_testing::multi_test::stargate_mock::StargateMock; use cw_multi_test::addons::{MockAddressGenerator, MockApiBech32}; @@ -121,10 +123,7 @@ impl TestingSuite { let msg = InstantiateMsg { fee_collector_addr: whale_lair_addr, owner: self.creator().to_string(), - pool_creation_fee: Coin { - amount: Uint128::from(1_000u128), - denom: "uusd".to_string(), - }, + pool_creation_fee: coin(1_000, "uusd"), }; let pool_manager_id = self.app.store_code(contract_pool_manager()); @@ -361,6 +360,35 @@ impl TestingSuite { self } + + /// Updates the configuration of the contract. + /// + /// Any parameters which are set to `None` when passed will not update + /// the current configuration. + #[track_caller] + pub(crate) fn update_config( + &mut self, + sender: Addr, + new_whale_lair_addr: Option, + new_owner_addr: Option, + new_pool_creation_fee: Option, + new_feature_toggle: Option, + result: impl Fn(Result), + ) -> &mut Self { + result(self.app.execute_contract( + sender, + self.pool_manager_addr.clone(), + &white_whale_std::pool_manager::ExecuteMsg::UpdateConfig { + whale_lair_addr: new_whale_lair_addr.map(|addr| addr.to_string()), + owner_addr: new_owner_addr.map(|addr| addr.to_string()), + pool_creation_fee: new_pool_creation_fee, + feature_toggle: new_feature_toggle, + }, + &[], + )); + + self + } } /// queries @@ -516,4 +544,15 @@ impl TestingSuite { // Get balance of LP token, if native we can just query balance otherwise we need to go to cw20 lp_token_response.lp_denom } + + /// Retrieves the current configuration of the pool manager contract. + pub(crate) fn query_config(&mut self) -> Config { + self.app + .wrap() + .query_wasm_smart( + &self.pool_manager_addr, + &white_whale_std::pool_manager::QueryMsg::Config {}, + ) + .unwrap() + } } diff --git a/packages/white-whale-std/src/pool_manager.rs b/packages/white-whale-std/src/pool_manager.rs index 6c12a4e71..db88d8399 100644 --- a/packages/white-whale-std/src/pool_manager.rs +++ b/packages/white-whale-std/src/pool_manager.rs @@ -5,11 +5,11 @@ use crate::{ pool_network::{ asset::PairType, factory::NativeTokenDecimalsResponse, - pair::{ReverseSimulationResponse, SimulationResponse}, + pair::{FeatureToggle, ReverseSimulationResponse, SimulationResponse}, }, }; use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Coin, Decimal, Uint128}; +use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; use cw_ownable::{cw_ownable_execute, cw_ownable_query}; #[cw_serde] @@ -112,6 +112,16 @@ pub struct PairInfo { } impl PairInfo {} +#[cw_serde] +pub struct Config { + pub whale_lair_addr: Addr, + pub owner: Addr, + // We must set a creation fee on instantiation to prevent spamming of pools + pub pool_creation_fee: Coin, + // Whether or not swaps, deposits, and withdrawals are enabled + pub feature_toggle: FeatureToggle, +} + #[cw_serde] pub struct InstantiateMsg { pub fee_collector_addr: String, @@ -194,12 +204,29 @@ pub enum ExecuteMsg { AddSwapRoutes { swap_routes: Vec, }, + /// Updates the configuration of the contract. + /// If a field is not specified (i.e., set to `None`), it will not be modified. + UpdateConfig { + /// The new whale-lair contract address. + whale_lair_addr: Option, + /// The new owner address of the contract, which is allowed to update + /// configuration. + owner_addr: Option, + /// The new fee that must be paid when a pool is created. + pool_creation_fee: Option, + /// The new feature toggles of the contract, allowing fine-tuned + /// control over which operations are allowed. + feature_toggle: Option, + }, } #[cw_ownable_query] #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { + #[returns(Config)] + Config {}, + /// Retrieves the decimals for the given native or ibc denom. #[returns(NativeTokenDecimalsResponse)] NativeTokenDecimals { denom: String },