Skip to content

Commit

Permalink
fix: Move PoolFee to fee.rs and add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
0xFable committed Apr 10, 2024
1 parent fc84756 commit 8a7432d
Show file tree
Hide file tree
Showing 8 changed files with 172 additions and 121 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion contracts/liquidity_hub/pool-manager/src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::ops::Mul;
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Coin, Decimal, Decimal256, StdError, StdResult, Storage, Uint128, Uint256};

use white_whale_std::pool_manager::PoolFee;
use white_whale_std::fee::PoolFee;
use white_whale_std::pool_network::asset::{Asset, AssetInfo, PairType};

use crate::error::ContractError;
Expand Down
4 changes: 2 additions & 2 deletions contracts/liquidity_hub/pool-manager/src/manager/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ use cosmwasm_std::{
attr, Attribute, Coin, CosmosMsg, DepsMut, Env, MessageInfo, Response, Uint128,
};
use white_whale_std::{
pool_manager::PoolFee,
pool_network::{asset::PairType, querier::query_native_decimals},
whale_lair::fill_rewards_msg,
fee::PoolFee,
};

use crate::state::{get_pair_by_identifier, NATIVE_TOKEN_DECIMALS, PAIR_COUNTER};
Expand All @@ -28,7 +28,7 @@ pub const LP_SYMBOL: &str = "uLP";
/// ```rust
/// # use cosmwasm_std::{DepsMut, Decimal, Env, MessageInfo, Response, CosmosMsg, WasmMsg, to_json_binary};
/// # use white_whale_std::pool_network::{asset::{PairType}};
/// # use white_whale_std::pool_manager::PoolFee;
/// # use white_whale_std::fee::PoolFee;
/// # use white_whale_std::fee::Fee;
/// # use pool_manager::error::ContractError;
/// # use pool_manager::manager::commands::MAX_ASSETS_PER_POOL;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::ContractError;
use cosmwasm_std::{coin, Addr, Coin, Decimal, Uint128};
use white_whale_std::fee::Fee;
use white_whale_std::pool_manager::PoolFee;
use white_whale_std::fee::PoolFee;
use white_whale_std::pool_network::asset::MINIMUM_LIQUIDITY_AMOUNT;

use super::suite::TestingSuite;
Expand Down
2 changes: 1 addition & 1 deletion contracts/liquidity_hub/pool-manager/src/tests/suite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use cw_multi_test::{
App, AppBuilder, AppResponse, BankKeeper, Contract, ContractWrapper, DistributionKeeper,
Executor, FailingModule, GovFailingModule, IbcFailingModule, StakeKeeper, WasmKeeper,
};
use white_whale_std::pool_manager::PoolFee;
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_testing::multi_test::stargate_mock::StargateMock;
Expand Down
1 change: 1 addition & 0 deletions packages/white-whale-std/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,4 @@ thiserror.workspace = true

[dev-dependencies]
cw-multi-test.workspace = true
test-case.workspace = true
166 changes: 163 additions & 3 deletions packages/white-whale-std/src/fee.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Decimal, Decimal256, StdError, StdResult, Uint256};
use cosmwasm_std::{Decimal, Decimal256, StdError, StdResult, Uint128, Uint256};
use std::fmt::{Display, Formatter};

#[cw_serde]
Expand Down Expand Up @@ -62,11 +62,125 @@ impl VaultFee {
}
}

/// Represents the fee structure for transactions within a pool.
///
///
/// # Fields
/// - `protocol_fee`: The fee percentage charged by the protocol on each transaction to support
/// operational and developmental needs.
/// - `swap_fee`: The fee percentage allocated to liquidity providers as a reward for supplying
/// liquidity to the pool, incentivizing participation and ensuring pool health.
/// - `burn_fee`: A fee percentage that is burned on each transaction, helping manage the token
/// economy by reducing supply over time, potentially increasing token value.
/// - `osmosis_fee` (optional): Specific to the Osmosis feature, this fee is charged on each
/// transaction when the Osmosis feature is enabled, supporting specific ecosystem requirements.
/// - `extra_fees`: A vector of custom fees allowing for extensible and adaptable fee structures
/// to meet diverse and evolving needs. Validation ensures that the total of all fees does not
/// exceed 100%, maintaining fairness and avoiding overcharging.
///
/// # Features
/// - `osmosis`: Enables the `osmosis_fee` field, integrating specific fee requirements for the
/// Osmosis protocol within the pool's fee structure.
#[cw_serde]
pub struct PoolFee {
/// Fee percentage charged on each transaction for the protocol's benefit.
pub protocol_fee: Fee,

/// Fee percentage allocated to liquidity providers on each swap.
pub swap_fee: Fee,

/// Fee percentage that is burned on each transaction. Burning a portion of the transaction fee
/// helps in reducing the overall token supply.
pub burn_fee: Fee,

/// Fee percentage charged on each transaction specifically for Osmosis integrations. This fee
/// is only applicable when the `osmosis` feature is enabled
#[cfg(feature = "osmosis")]
pub osmosis_fee: Fee,

/// A list of custom, additional fees that can be defined for specific use cases or additional
/// functionalities. This vector enables the flexibility to introduce new fees without altering
/// the core fee structure. Total of all fees, including custom ones, is validated to not exceed
/// 100%, ensuring a balanced and fair fee distribution.
pub extra_fees: Vec<Fee>,
}
impl PoolFee {
/// Validates the PoolFee structure to ensure no individual fee is zero or negative
/// and the sum of all fees does not exceed 20%.
pub fn is_valid(&self) -> StdResult<()> {
let mut total_share = Decimal::zero();

// Validate predefined fees and accumulate their shares
let predefined_fees = [
&self.protocol_fee,

Check warning on line 115 in packages/white-whale-std/src/fee.rs

View check run for this annotation

Codecov / codecov/patch

packages/white-whale-std/src/fee.rs#L115

Added line #L115 was not covered by tests
&self.swap_fee,
&self.burn_fee,
#[cfg(feature = "osmosis")]
&self.osmosis_fee,

Check warning on line 119 in packages/white-whale-std/src/fee.rs

View check run for this annotation

Codecov / codecov/patch

packages/white-whale-std/src/fee.rs#L118-L119

Added lines #L118 - L119 were not covered by tests
];

for fee in predefined_fees.iter().filter_map(|f| Some(*f)) {
fee.is_valid()?; // Validates the fee is not >= 100%
total_share = total_share + fee.share;
}

// Validate extra fees and accumulate their shares
for fee in &self.extra_fees {
fee.is_valid()?; // Validates the fee is not >= 100%
total_share = total_share + fee.share;
}

// Check if the total share exceeds 20%
if total_share > Decimal::percent(20) {
return Err(StdError::generic_err("Total fees cannot exceed 20%"));
}

Ok(())
}

/// Computes and applies all defined fees to a given amount.
/// Returns the total amount of fees deducted.
pub fn compute_and_apply_fees(&self, amount: Uint256) -> StdResult<Uint128> {
let mut total_fee_amount = Uint256::zero();

// Compute protocol fee
let protocol_fee_amount = self.protocol_fee.compute(amount);
total_fee_amount += protocol_fee_amount;

// Compute swap fee
let swap_fee_amount = self.swap_fee.compute(amount);
total_fee_amount += swap_fee_amount;

// Compute burn fee
let burn_fee_amount = self.burn_fee.compute(amount);
total_fee_amount += burn_fee_amount;

// Compute osmosis fee if applicable
#[cfg(feature = "osmosis")]{
let osmosis_fee_amount = self.osmosis_fee.compute(amount);

Check warning on line 160 in packages/white-whale-std/src/fee.rs

View check run for this annotation

Codecov / codecov/patch

packages/white-whale-std/src/fee.rs#L160

Added line #L160 was not covered by tests

total_fee_amount += osmosis_fee_amount;

Check warning on line 162 in packages/white-whale-std/src/fee.rs

View check run for this annotation

Codecov / codecov/patch

packages/white-whale-std/src/fee.rs#L162

Added line #L162 was not covered by tests
}

// Compute extra fees
for extra_fee in &self.extra_fees {
let extra_fee_amount = extra_fee.compute(amount);
total_fee_amount += extra_fee_amount;

Check warning on line 168 in packages/white-whale-std/src/fee.rs

View check run for this annotation

Codecov / codecov/patch

packages/white-whale-std/src/fee.rs#L167-L168

Added lines #L167 - L168 were not covered by tests
}

// Convert the total fee amount to Uint128 (or handle potential conversion failure)
Uint128::try_from(total_fee_amount).map_err(|_| StdError::generic_err("Fee conversion error"))
}

}


#[cfg(test)]
mod tests {
use cosmwasm_std::{Decimal, StdError, Uint128};
use cosmwasm_std::{Decimal, StdError, Uint128, Uint256};

use crate::fee::{Fee, VaultFee};
use crate::fee::{Fee, PoolFee, VaultFee};
use test_case::test_case;

#[test]
fn valid_fee() {
Expand Down Expand Up @@ -206,4 +320,50 @@ mod tests {
};
assert_eq!(vault_fee.is_valid(), Ok(()));
}

#[test_case(Decimal::permille(1), Decimal::permille(2), Decimal::permille(1), Uint256::from(1000u128), Uint128::from(4u128); "low fee scenario")]
#[test_case(Decimal::percent(1), Decimal::percent(2), Decimal::zero(), Uint256::from(1000u128), Uint128::from(30u128); "higher fee scenario")]
fn pool_fee_application(
protocol_fee_share: Decimal,
swap_fee_share: Decimal,
burn_fee_share: Decimal,
amount: Uint256,
expected_fee_deducted: Uint128,
) {
let protocol_fee = Fee { share: protocol_fee_share };
let swap_fee = Fee { share: swap_fee_share };
let burn_fee = Fee { share: burn_fee_share };
let extra_fees = vec![]; // Assuming no extra fees for simplicity

let pool_fee = PoolFee {
protocol_fee,
swap_fee,
burn_fee,
#[cfg(feature = "osmosis")]
osmosis_fee: Fee { share: Decimal::zero() },
extra_fees,
};

let total_fee_deducted = pool_fee.compute_and_apply_fees(amount).unwrap();
assert_eq!(total_fee_deducted, expected_fee_deducted, "The total deducted fees did not match the expected value.");
}

#[test]
fn pool_fee_exceeds_limit() {
let protocol_fee = Fee { share: Decimal::percent(10) };
let swap_fee = Fee { share: Decimal::percent(5) };
let burn_fee = Fee { share: Decimal::percent(5) };
let extra_fees = vec![Fee { share: Decimal::percent(1) }]; // Sum is 21%

let pool_fee = PoolFee {
protocol_fee,
swap_fee,
burn_fee,
#[cfg(feature = "osmosis")]
osmosis_fee: Fee { share: Decimal::zero() },
extra_fees,
};

assert_eq!(pool_fee.is_valid(), Err(StdError::generic_err("Total fees cannot exceed 20%")));
}
}
115 changes: 2 additions & 113 deletions packages/white-whale-std/src/pool_manager.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use std::fmt;

use crate::{fee::Fee, pool_network::{
use crate::{fee::PoolFee, pool_network::{
asset::PairType,
factory::NativeTokenDecimalsResponse,
pair::{ReverseSimulationResponse, SimulationResponse},
}};
use cosmwasm_schema::{cw_serde, QueryResponses};
use cosmwasm_std::{Coin, Decimal, StdError, StdResult, Uint128, Uint256};
use cosmwasm_std::{Coin, Decimal, Uint128};
use cw_ownable::{cw_ownable_execute, cw_ownable_query};

#[cw_serde]
Expand Down Expand Up @@ -85,116 +85,6 @@ pub enum FeeTypes {
Custom(String),
}

/// Represents the fee structure for transactions within a pool.
///
///
/// # Fields
/// - `protocol_fee`: The fee percentage charged by the protocol on each transaction to support
/// operational and developmental needs.
/// - `swap_fee`: The fee percentage allocated to liquidity providers as a reward for supplying
/// liquidity to the pool, incentivizing participation and ensuring pool health.
/// - `burn_fee`: A fee percentage that is burned on each transaction, helping manage the token
/// economy by reducing supply over time, potentially increasing token value.
/// - `osmosis_fee` (optional): Specific to the Osmosis feature, this fee is charged on each
/// transaction when the Osmosis feature is enabled, supporting specific ecosystem requirements.
/// - `extra_fees`: A vector of custom fees allowing for extensible and adaptable fee structures
/// to meet diverse and evolving needs. Validation ensures that the total of all fees does not
/// exceed 100%, maintaining fairness and avoiding overcharging.
///
/// # Features
/// - `osmosis`: Enables the `osmosis_fee` field, integrating specific fee requirements for the
/// Osmosis protocol within the pool's fee structure.
#[cw_serde]
pub struct PoolFee {
/// Fee percentage charged on each transaction for the protocol's benefit.
pub protocol_fee: Fee,

/// Fee percentage allocated to liquidity providers on each swap.
pub swap_fee: Fee,

/// Fee percentage that is burned on each transaction. Burning a portion of the transaction fee
/// helps in reducing the overall token supply.
pub burn_fee: Fee,

/// Fee percentage charged on each transaction specifically for Osmosis integrations. This fee
/// is only applicable when the `osmosis` feature is enabled
#[cfg(feature = "osmosis")]
pub osmosis_fee: Fee,

/// A list of custom, additional fees that can be defined for specific use cases or additional
/// functionalities. This vector enables the flexibility to introduce new fees without altering
/// the core fee structure. Total of all fees, including custom ones, is validated to not exceed
/// 100%, ensuring a balanced and fair fee distribution.
pub extra_fees: Vec<Fee>,
}
impl PoolFee {
/// Validates the PoolFee structure to ensure no individual fee is zero or negative
/// and the sum of all fees does not exceed 20%.
pub fn is_valid(&self) -> StdResult<()> {
let mut total_share = Decimal::zero();

// Validate predefined fees and accumulate their shares
let predefined_fees = [
&self.protocol_fee,
&self.swap_fee,
&self.burn_fee,
#[cfg(feature = "osmosis")]
&self.osmosis_fee,
];

for fee in predefined_fees.iter().filter_map(|f| Some(*f)) {
fee.is_valid()?; // Validates the fee is not >= 100%
total_share = total_share + fee.share;
}

// Validate extra fees and accumulate their shares
for fee in &self.extra_fees {
fee.is_valid()?; // Validates the fee is not >= 100%
total_share = total_share + fee.share;
}

// Check if the total share exceeds 20%
if total_share > Decimal::percent(20) {
return Err(StdError::generic_err("Total fees cannot exceed 20%"));
}

Ok(())
}

/// Computes and applies all defined fees to a given amount.
/// Returns the total amount of fees deducted.
pub fn compute_and_apply_fees(&self, amount: Uint256) -> StdResult<Uint128> {
let mut total_fee_amount = Uint256::zero();

// Compute protocol fee
let protocol_fee_amount = self.protocol_fee.compute(amount);
total_fee_amount += protocol_fee_amount;

// Compute swap fee
let swap_fee_amount = self.swap_fee.compute(amount);
total_fee_amount += swap_fee_amount;

// Compute burn fee
let burn_fee_amount = self.burn_fee.compute(amount);
total_fee_amount += burn_fee_amount;

// Compute osmosis fee if applicable
#[cfg(feature = "osmosis")]{
let osmosis_fee_amount = self.osmosis_fee.compute(amount);

total_fee_amount += osmosis_fee_amount;
}

// Compute extra fees
for extra_fee in &self.extra_fees {
let extra_fee_amount = extra_fee.compute(amount);
total_fee_amount += extra_fee_amount;
}

// Convert the total fee amount to Uint128 (or handle potential conversion failure)
Uint128::try_from(total_fee_amount).map_err(|_| StdError::generic_err("Fee conversion error"))
}
}

#[cw_serde]

Expand Down Expand Up @@ -236,7 +126,6 @@ pub struct MigrateMsg {}
pub enum ExecuteMsg {
CreatePair {
asset_denoms: Vec<String>,
// TODO: Remap to NPoolFee maybe
pool_fees: PoolFee,
pair_type: PairType,
pair_identifier: Option<String>,
Expand Down

0 comments on commit 8a7432d

Please sign in to comment.