diff --git a/Cargo.lock b/Cargo.lock index ffd00a83..8158b555 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -541,7 +541,7 @@ dependencies = [ "semver", "serde", "thiserror", - "white-whale-std 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "white-whale-std 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -587,12 +587,12 @@ dependencies = [ "schemars", "serde", "thiserror", - "white-whale-std 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "white-whale-std 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "fee_collector" -version = "1.1.7" +version = "1.2.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -614,7 +614,7 @@ dependencies = [ "vault", "vault_factory", "whale-lair", - "white-whale-std 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "white-whale-std 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -631,7 +631,7 @@ dependencies = [ "semver", "serde", "thiserror", - "white-whale-std 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "white-whale-std 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -685,7 +685,7 @@ dependencies = [ "serde", "terraswap-pair", "thiserror", - "white-whale-std 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "white-whale-std 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -787,7 +787,7 @@ dependencies = [ "serde", "terraswap-token", "thiserror", - "white-whale-std 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "white-whale-std 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -804,7 +804,7 @@ dependencies = [ "semver", "serde", "thiserror", - "white-whale-std 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "white-whale-std 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1532,7 +1532,7 @@ dependencies = [ "serde", "stable-swap-sim", "thiserror", - "white-whale-std 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "white-whale-std 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1602,7 +1602,7 @@ dependencies = [ "semver", "serde", "thiserror", - "white-whale-std 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "white-whale-std 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1623,7 +1623,7 @@ dependencies = [ "semver", "serde", "thiserror", - "white-whale-std 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "white-whale-std 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1640,7 +1640,7 @@ dependencies = [ "semver", "serde", "thiserror", - "white-whale-std 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "white-whale-std 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1657,7 +1657,7 @@ dependencies = [ "schemars", "serde", "thiserror", - "white-whale-std 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "white-whale-std 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1733,7 +1733,7 @@ dependencies = [ "semver", "serde", "thiserror", - "white-whale-std 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "white-whale-std 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1754,7 +1754,7 @@ dependencies = [ "serde", "thiserror", "vault", - "white-whale-std 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "white-whale-std 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1776,7 +1776,7 @@ dependencies = [ "thiserror", "vault", "vault_factory", - "white-whale-std 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "white-whale-std 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1820,13 +1820,13 @@ dependencies = [ "semver", "serde", "thiserror", - "white-whale-std 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "white-whale-std 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "white-whale-testing", ] [[package]] name = "white-whale-std" -version = "1.1.5" +version = "1.2.6" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1843,9 +1843,9 @@ dependencies = [ [[package]] name = "white-whale-std" -version = "1.1.5" +version = "1.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3015d5c0c46b6e1cf2fb02c036e8d1481174508a32572d62e6f240a4dec4077" +checksum = "079931866b9dc1aec9c1f1872979070e3531f2a40d0ef63e7650dfee74faf0a8" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1872,7 +1872,7 @@ dependencies = [ "schemars", "serde", "whale-lair", - "white-whale-std 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "white-whale-std 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 4e2cc7ca..02f06864 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ prost = { version = "0.11.9", default-features = false, features = [ prost-types = { version = "0.11.9", default-features = false } # for local development #white-whale-std = { path = "packages/white-whale-std" } -white-whale-std = { version = "1.1.5" } +white-whale-std = { version = "1.2.6" } white-whale-testing = { path = "./packages/white-whale-testing" } cw-multi-test = { version = "0.16.5" } uint = "0.9.5" diff --git a/contracts/liquidity_hub/fee_collector/Cargo.toml b/contracts/liquidity_hub/fee_collector/Cargo.toml index fec63ae0..97b26aa1 100644 --- a/contracts/liquidity_hub/fee_collector/Cargo.toml +++ b/contracts/liquidity_hub/fee_collector/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fee_collector" -version = "1.1.7" +version = "1.2.0" authors = ["Kerber0x "] edition.workspace = true description = "Contract to collect the fees accrued by the pools and vaults in the liquidity hub" diff --git a/contracts/liquidity_hub/fee_collector/schema/fee_collector.json b/contracts/liquidity_hub/fee_collector/schema/fee_collector.json index 8552cba1..66815d46 100644 --- a/contracts/liquidity_hub/fee_collector/schema/fee_collector.json +++ b/contracts/liquidity_hub/fee_collector/schema/fee_collector.json @@ -98,6 +98,12 @@ "null" ] }, + "is_take_rate_active": { + "type": [ + "boolean", + "null" + ] + }, "owner": { "type": [ "string", @@ -116,6 +122,22 @@ "null" ] }, + "take_rate": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "take_rate_dao_address": { + "type": [ + "string", + "null" + ] + }, "vault_factory": { "type": [ "string", @@ -241,6 +263,10 @@ } ] }, + "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" + }, "Epoch": { "type": "object", "required": [ @@ -760,15 +786,21 @@ "type": "object", "required": [ "fee_distributor", + "is_take_rate_active", "owner", "pool_factory", "pool_router", + "take_rate", + "take_rate_dao_address", "vault_factory" ], "properties": { "fee_distributor": { "$ref": "#/definitions/Addr" }, + "is_take_rate_active": { + "type": "boolean" + }, "owner": { "$ref": "#/definitions/Addr" }, @@ -778,6 +810,12 @@ "pool_router": { "$ref": "#/definitions/Addr" }, + "take_rate": { + "$ref": "#/definitions/Decimal" + }, + "take_rate_dao_address": { + "$ref": "#/definitions/Addr" + }, "vault_factory": { "$ref": "#/definitions/Addr" } @@ -787,6 +825,10 @@ "Addr": { "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", "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" } } }, diff --git a/contracts/liquidity_hub/fee_collector/schema/raw/execute.json b/contracts/liquidity_hub/fee_collector/schema/raw/execute.json index 840dd6b2..986b9284 100644 --- a/contracts/liquidity_hub/fee_collector/schema/raw/execute.json +++ b/contracts/liquidity_hub/fee_collector/schema/raw/execute.json @@ -88,6 +88,12 @@ "null" ] }, + "is_take_rate_active": { + "type": [ + "boolean", + "null" + ] + }, "owner": { "type": [ "string", @@ -106,6 +112,22 @@ "null" ] }, + "take_rate": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "take_rate_dao_address": { + "type": [ + "string", + "null" + ] + }, "vault_factory": { "type": [ "string", @@ -231,6 +253,10 @@ } ] }, + "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" + }, "Epoch": { "type": "object", "required": [ diff --git a/contracts/liquidity_hub/fee_collector/schema/raw/response_to_config.json b/contracts/liquidity_hub/fee_collector/schema/raw/response_to_config.json index de279618..40cc3cfc 100644 --- a/contracts/liquidity_hub/fee_collector/schema/raw/response_to_config.json +++ b/contracts/liquidity_hub/fee_collector/schema/raw/response_to_config.json @@ -4,15 +4,21 @@ "type": "object", "required": [ "fee_distributor", + "is_take_rate_active", "owner", "pool_factory", "pool_router", + "take_rate", + "take_rate_dao_address", "vault_factory" ], "properties": { "fee_distributor": { "$ref": "#/definitions/Addr" }, + "is_take_rate_active": { + "type": "boolean" + }, "owner": { "$ref": "#/definitions/Addr" }, @@ -22,6 +28,12 @@ "pool_router": { "$ref": "#/definitions/Addr" }, + "take_rate": { + "$ref": "#/definitions/Decimal" + }, + "take_rate_dao_address": { + "$ref": "#/definitions/Addr" + }, "vault_factory": { "$ref": "#/definitions/Addr" } @@ -31,6 +43,10 @@ "Addr": { "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", "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" } } } diff --git a/contracts/liquidity_hub/fee_collector/src/commands.rs b/contracts/liquidity_hub/fee_collector/src/commands.rs index 0a1dd632..1c26a1ba 100644 --- a/contracts/liquidity_hub/fee_collector/src/commands.rs +++ b/contracts/liquidity_hub/fee_collector/src/commands.rs @@ -1,6 +1,7 @@ use cosmwasm_std::{ - to_json_binary, Addr, BalanceResponse, BankQuery, Coin, CosmosMsg, Decimal, DepsMut, Env, - MessageInfo, QueryRequest, ReplyOn, Response, StdResult, SubMsg, Uint128, WasmMsg, WasmQuery, + ensure, to_json_binary, Addr, BalanceResponse, BankQuery, Coin, CosmosMsg, Decimal, DepsMut, + Env, MessageInfo, QueryRequest, ReplyOn, Response, StdResult, SubMsg, Uint128, WasmMsg, + WasmQuery, }; use cw20::{Cw20ExecuteMsg, Cw20QueryMsg}; @@ -111,6 +112,7 @@ fn collect_fees_for_factory( Ok(result) } +#[allow(clippy::too_many_arguments)] pub fn update_config( deps: DepsMut, info: MessageInfo, @@ -119,6 +121,9 @@ pub fn update_config( fee_distributor: Option, pool_factory: Option, vault_factory: Option, + take_rate: Option, + take_rate_dao_address: Option, + is_take_rate_active: Option, ) -> Result { let mut config: Config = CONFIG.load(deps.storage)?; @@ -152,6 +157,22 @@ pub fn update_config( config.vault_factory = vault_factory; } + if let Some(take_rate) = take_rate { + ensure!( + take_rate < Decimal::one(), + ContractError::InvalidTakeRate {} + ); + config.take_rate = take_rate; + } + + if let Some(take_rate_dao_address) = take_rate_dao_address { + config.take_rate_dao_address = deps.api.addr_validate(&take_rate_dao_address)?; + } + + if let Some(is_take_rate_active) = is_take_rate_active { + config.is_take_rate_active = is_take_rate_active; + } + CONFIG.save(deps.storage, &config)?; Ok(Response::default().add_attribute("action", "update_config")) } diff --git a/contracts/liquidity_hub/fee_collector/src/contract.rs b/contracts/liquidity_hub/fee_collector/src/contract.rs index 9db41c92..75b77b73 100644 --- a/contracts/liquidity_hub/fee_collector/src/contract.rs +++ b/contracts/liquidity_hub/fee_collector/src/contract.rs @@ -1,8 +1,8 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_json_binary, Addr, BalanceResponse, BankMsg, BankQuery, Binary, CosmosMsg, Deps, DepsMut, - Env, MessageInfo, QueryRequest, Reply, Response, StdResult, Uint128, + coin, to_json_binary, Addr, BalanceResponse, BankMsg, BankQuery, Binary, CosmosMsg, Decimal, + Deps, DepsMut, Env, MessageInfo, QueryRequest, Reply, Response, StdResult, Uint128, }; use cw2::{get_contract_version, set_contract_version}; use semver::Version; @@ -14,7 +14,7 @@ use white_whale_std::pool_network::asset::{Asset, AssetInfo, ToCoins}; use crate::error::ContractError; use crate::queries::query_distribution_asset; -use crate::state::{CONFIG, TMP_EPOCH}; +use crate::state::{CONFIG, TAKE_RATE_HISTORY, TMP_EPOCH}; use crate::ContractError::MigrateInvalidVersion; use crate::{commands, migrations, queries}; @@ -38,6 +38,9 @@ pub fn instantiate( fee_distributor: Addr::unchecked(""), pool_factory: Addr::unchecked(""), vault_factory: Addr::unchecked(""), + take_rate: Decimal::zero(), + take_rate_dao_address: Addr::unchecked(""), + is_take_rate_active: false, }; CONFIG.save(deps.storage, &config)?; @@ -56,7 +59,7 @@ pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result { return Err(ContractError::InvalidContractsFeeAggregation {}) } @@ -72,6 +75,37 @@ pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result commands::update_config( deps, info, @@ -127,6 +164,9 @@ pub fn execute( fee_distributor, pool_factory, vault_factory, + take_rate, + take_rate_dao_address, + is_take_rate_active, ), ExecuteMsg::AggregateFees { aggregate_fees_for } => { commands::aggregate_fees(deps, env, aggregate_fees_for) @@ -147,6 +187,9 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { query_fees_for, all_time.unwrap_or(false), )?), + QueryMsg::TakeRateHistory { epoch_id } => { + to_json_binary(&queries::query_take_rate_history(deps, epoch_id)?) + } } } @@ -167,8 +210,8 @@ pub fn migrate(mut deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result for ContractError { diff --git a/contracts/liquidity_hub/fee_collector/src/migrations.rs b/contracts/liquidity_hub/fee_collector/src/migrations.rs index cfc4d9b5..db239c62 100644 --- a/contracts/liquidity_hub/fee_collector/src/migrations.rs +++ b/contracts/liquidity_hub/fee_collector/src/migrations.rs @@ -1,21 +1,16 @@ #![cfg(not(tarpaulin_include))] use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, DepsMut, StdError}; +use cosmwasm_std::{Addr, Decimal, DepsMut, StdError}; use cw_storage_plus::Item; use white_whale_std::fee_collector::Config; use crate::state::CONFIG; -/// Migrates state from v1.0.5 and lower to v1.1.0, which includes different contract addresses -/// in the Config. -pub fn migrate_to_v110(deps: DepsMut) -> Result<(), StdError> { +/// Migrates state from pre v1.2.0, which includes the take rate, the take rate dao address and the +/// feature flag for the take rate +pub fn migrate_to_v120(deps: DepsMut) -> Result<(), StdError> { #[cw_serde] - struct ConfigV105 { - pub owner: Addr, - } - - #[cw_serde] - struct ConfigV110 { + struct ConfigPreV120 { pub owner: Addr, pub pool_router: Addr, pub fee_distributor: Addr, @@ -23,15 +18,18 @@ pub fn migrate_to_v110(deps: DepsMut) -> Result<(), StdError> { pub vault_factory: Addr, } - const CONFIGV105: Item = Item::new("config"); - let config_v105 = CONFIGV105.load(deps.storage)?; + const CONFIGPREV120: Item = Item::new("config"); + let config_pre_v120 = CONFIGPREV120.load(deps.storage)?; let config = Config { - owner: config_v105.owner, - pool_router: Addr::unchecked(""), - fee_distributor: Addr::unchecked(""), - pool_factory: Addr::unchecked(""), - vault_factory: Addr::unchecked(""), + owner: config_pre_v120.owner, + pool_router: config_pre_v120.pool_router, + fee_distributor: config_pre_v120.fee_distributor, + pool_factory: config_pre_v120.pool_factory, + vault_factory: config_pre_v120.vault_factory, + take_rate: Decimal::zero(), + take_rate_dao_address: Addr::unchecked(""), + is_take_rate_active: false, }; CONFIG.save(deps.storage, &config)?; diff --git a/contracts/liquidity_hub/fee_collector/src/queries.rs b/contracts/liquidity_hub/fee_collector/src/queries.rs index ab6bb4a0..448f91a9 100644 --- a/contracts/liquidity_hub/fee_collector/src/queries.rs +++ b/contracts/liquidity_hub/fee_collector/src/queries.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{to_json_binary, Addr, Deps, QueryRequest, StdResult, WasmQuery}; +use cosmwasm_std::{to_json_binary, Addr, Coin, Deps, QueryRequest, StdResult, Uint64, WasmQuery}; use white_whale_std::fee_collector::{Config, ContractType, FactoryType, FeesFor}; use white_whale_std::pool_network; @@ -8,7 +8,7 @@ use white_whale_std::pool_network::pair::ProtocolFeesResponse as ProtocolPairFee use white_whale_std::vault_network::vault::ProtocolFeesResponse as ProtocolVaultFeesResponse; use white_whale_std::vault_network::vault_factory::VaultsResponse; -use crate::state::CONFIG; +use crate::state::{CONFIG, TAKE_RATE_HISTORY}; /// Queries the [Config], which contains the owner address pub fn query_config(deps: Deps) -> StdResult { @@ -16,6 +16,12 @@ pub fn query_config(deps: Deps) -> StdResult { Ok(config) } +/// Queries the take rate for the given epoch id +pub fn query_take_rate_history(deps: Deps, epoch_id: Uint64) -> StdResult { + let take_rate = TAKE_RATE_HISTORY.load(deps.storage, epoch_id.u64())?; + Ok(take_rate) +} + /// Queries the fees in [Asset] for contracts or Factories defined by [FeesFor] pub fn query_fees(deps: Deps, query_fees_for: FeesFor, all_time: bool) -> StdResult> { let mut fees: Vec = Vec::new(); diff --git a/contracts/liquidity_hub/fee_collector/src/state.rs b/contracts/liquidity_hub/fee_collector/src/state.rs index 4e09a68e..93285b4f 100644 --- a/contracts/liquidity_hub/fee_collector/src/state.rs +++ b/contracts/liquidity_hub/fee_collector/src/state.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{DepsMut, Order, StdResult}; +use cosmwasm_std::{Coin, DepsMut, Order, StdResult}; use cw_storage_plus::{Item, Map}; use white_whale_std::fee_collector::Config; use white_whale_std::fee_distributor::Epoch; @@ -8,6 +8,9 @@ pub const CONFIG: Item = Item::new("config"); pub const TMP_ASSET_INFOS: Map = Map::new("tmp_asset_infos"); pub const TMP_EPOCH: Item = Item::new("tmp_epoch"); +// The take rate history is a map containing how much was the take rate at a given epoch +pub const TAKE_RATE_HISTORY: Map = Map::new("take_rate_history"); + pub fn store_temporal_asset_info(deps: DepsMut, asset_info: AssetInfo) -> StdResult<()> { let key = asset_info .clone() diff --git a/contracts/liquidity_hub/fee_collector/src/tests/integration.rs b/contracts/liquidity_hub/fee_collector/src/tests/integration.rs index 58f0f0b0..21a6bae3 100644 --- a/contracts/liquidity_hub/fee_collector/src/tests/integration.rs +++ b/contracts/liquidity_hub/fee_collector/src/tests/integration.rs @@ -1,8 +1,8 @@ use std::collections::HashMap; use cosmwasm_std::{ - coin, coins, to_json_binary, Addr, BankMsg, BlockInfo, Coin, Decimal, Timestamp, Uint128, - Uint256, Uint64, + assert_approx_eq, coin, coins, to_json_binary, Addr, BankMsg, BlockInfo, Coin, Decimal, + Timestamp, Uint128, Uint256, Uint64, }; use cw20::{BalanceResponse, Cw20Coin, Cw20ExecuteMsg, MinterResponse}; use cw_multi_test::Executor; @@ -102,6 +102,9 @@ fn collect_all_factories_cw20_fees_successfully() { fee_distributor: None, pool_factory: None, vault_factory: None, + take_rate: None, + take_rate_dao_address: None, + is_take_rate_active: None, }, &[], ) @@ -434,6 +437,9 @@ fn collect_all_factories_cw20_fees_successfully() { fee_distributor: Some(fee_distributor_address.to_string()), pool_factory: None, vault_factory: None, + take_rate: None, + take_rate_dao_address: None, + is_take_rate_active: None, }, &[], ) @@ -1105,6 +1111,9 @@ fn collect_pools_native_fees_successfully() { fee_distributor: None, pool_factory: None, vault_factory: None, + take_rate: None, + take_rate_dao_address: None, + is_take_rate_active: None, }, &[], ) @@ -1423,6 +1432,9 @@ fn collect_pools_native_fees_successfully() { fee_distributor: Some(fee_distributor_address.to_string()), pool_factory: None, vault_factory: None, + take_rate: None, + take_rate_dao_address: None, + is_take_rate_active: None, }, &[], ) @@ -2378,6 +2390,9 @@ fn aggregate_fees_for_vault() { fee_distributor: Some(fee_distributor_address.to_string()), pool_factory: None, vault_factory: None, + take_rate: None, + take_rate_dao_address: None, + is_take_rate_active: None, }, &[], ) @@ -2501,6 +2516,9 @@ fn aggregate_fees_for_vault() { fee_distributor: None, pool_factory: None, vault_factory: None, + take_rate: None, + take_rate_dao_address: None, + is_take_rate_active: None, }, &[], ) @@ -3149,6 +3167,9 @@ fn collect_and_distribute_fees_successfully() { fee_distributor: Some(fee_distributor_address.to_string()), pool_factory: Some(pool_factory_address.to_string()), vault_factory: Some(vault_factory_address.to_string()), + take_rate: None, + take_rate_dao_address: None, + is_take_rate_active: None, }, &[], ) @@ -3566,6 +3587,9 @@ fn collect_and_dist_fees_where_one_bonder_is_increasing_weight_no_claims_until_e fee_distributor: Some(fee_distributor_address.to_string()), pool_factory: Some(pool_factory_address.to_string()), vault_factory: Some(vault_factory_address.to_string()), + take_rate: None, + take_rate_dao_address: None, + is_take_rate_active: None, }, &[], ) @@ -4538,6 +4562,9 @@ fn collect_and_distribute_fees_with_expiring_epoch_successfully() { fee_distributor: Some(fee_distributor_address.to_string()), pool_factory: Some(pool_factory_address.to_string()), vault_factory: Some(vault_factory_address.to_string()), + take_rate: None, + take_rate_dao_address: None, + is_take_rate_active: None, }, &[], ) @@ -5099,6 +5126,9 @@ fn create_epoch_unsuccessfully() { fee_distributor: Some(fee_distributor_address.to_string()), pool_factory: Some(pool_factory_address.to_string()), vault_factory: Some(vault_factory_address.to_string()), + take_rate: None, + take_rate_dao_address: None, + is_take_rate_active: None, }, &[], ) @@ -5381,6 +5411,9 @@ fn aggregate_fees_unsuccessfully() { fee_distributor: Some(fee_distributor_address.to_string()), pool_factory: None, vault_factory: None, + take_rate: None, + take_rate_dao_address: None, + is_take_rate_active: None, }, &[], ) @@ -5604,6 +5637,9 @@ fn decrease_grace_period_fee_distributor() { fee_distributor: Some(fee_distributor_address.to_string()), pool_factory: Some(pool_factory_address.to_string()), vault_factory: Some(vault_factory_address.to_string()), + take_rate: None, + take_rate_dao_address: None, + is_take_rate_active: None, }, &[], ) @@ -6039,6 +6075,9 @@ fn users_cannot_claim_rewards_from_past_epochs() { fee_distributor: Some(fee_distributor_address.to_string()), pool_factory: Some(pool_factory_address.to_string()), vault_factory: Some(vault_factory_address.to_string()), + take_rate: None, + take_rate_dao_address: None, + is_take_rate_active: None, }, &[], ) @@ -6606,6 +6645,9 @@ fn user_can_claim_even_when_his_weight_increases_for_past_epochs() { fee_distributor: Some(fee_distributor_address.to_string()), pool_factory: Some(pool_factory_address.to_string()), vault_factory: Some(vault_factory_address.to_string()), + take_rate: None, + take_rate_dao_address: None, + is_take_rate_active: None, }, &[], ) @@ -7265,6 +7307,9 @@ fn user_weight_accounts_for_unbondings() { fee_distributor: Some(fee_distributor_address.to_string()), pool_factory: Some(pool_factory_address.to_string()), vault_factory: Some(vault_factory_address.to_string()), + take_rate: None, + take_rate_dao_address: None, + is_take_rate_active: None, }, &[], ) @@ -8121,6 +8166,9 @@ fn users_can_claim_even_when_global_index_was_taken_after_epoch_was_created() { fee_distributor: Some(fee_distributor_address.to_string()), pool_factory: Some(pool_factory_address.to_string()), vault_factory: Some(vault_factory_address.to_string()), + take_rate: None, + take_rate_dao_address: None, + is_take_rate_active: None, }, &[], ) @@ -8644,6 +8692,9 @@ fn collect_distribute_with_unbonders() { fee_distributor: Some(fee_distributor_address.to_string()), pool_factory: Some(pool_factory_address.to_string()), vault_factory: Some(vault_factory_address.to_string()), + take_rate: None, + take_rate_dao_address: None, + is_take_rate_active: None, }, &[], ) @@ -9233,3 +9284,524 @@ fn collect_distribute_with_unbonders() { assert_ne!(user_2_whale_received, user_1_whale_received); } + +#[cfg(not(feature = "osmosis"))] +#[test] +fn test_take_rate_dao() { + let creator = mock_creator(); + let balances = vec![ + ( + creator.clone().sender, + vec![ + coin(1_000_000_000, "usdc"), + coin(1_000_000_000, "uwhale"), + coin(1_000_000_000, "ampWHALE"), + coin(1_000_000_000, "bWHALE"), + ], + ), + ( + Addr::unchecked("other"), + vec![ + coin(1_000_000_000, "usdc"), + coin(1_000_000_000, "uwhale"), + coin(1_000_000_000, "ampWHALE"), + coin(1_000_000_000, "bWHALE"), + ], + ), + ]; + + let mut app = mock_app_with_balance(balances); + + let fee_collector_id = store_fee_collector_code(&mut app); + let fee_distributor_id = store_fee_distributor_code(&mut app); + let whale_lair_id = store_whale_lair_code(&mut app); + let pool_factory_id = store_pool_factory_code(&mut app); + let pool_router_id = store_pool_router_code(&mut app); + let pair_id = store_pair_code(&mut app); + let trio_id = store_trio_code(&mut app); + let token_id = store_token_code(&mut app); + let vault_factory_id = store_vault_factory_code(&mut app); + let vault_id = store_vault_code(&mut app); + + let fee_collector_address = app + .instantiate_contract( + fee_collector_id.clone(), + creator.clone().sender, + &InstantiateMsg {}, + &[], + "fee_collector", + None, + ) + .unwrap(); + + let dao_mock = app + .instantiate_contract( + fee_collector_id, + creator.clone().sender, + &InstantiateMsg {}, + &[], + "take_rate_dao_mock", + None, + ) + .unwrap(); + + #[cfg(not(feature = "osmosis"))] + let instantiate_msg = pool_network::factory::InstantiateMsg { + pair_code_id: pair_id, + trio_code_id: trio_id, + token_code_id: token_id, + fee_collector_addr: fee_collector_address.to_string(), + }; + + let pool_factory_address = app + .instantiate_contract( + pool_factory_id, + creator.clone().sender, + &instantiate_msg, + &[], + "fee_collector", + None, + ) + .unwrap(); + + let pool_router_address = app + .instantiate_contract( + pool_router_id, + creator.clone().sender, + &pool_network::router::InstantiateMsg { + terraswap_factory: pool_factory_address.to_string(), + }, + &[], + "pool_router", + None, + ) + .unwrap(); + + let vault_factory_address = app + .instantiate_contract( + vault_factory_id, + creator.clone().sender, + &vault_network::vault_factory::InstantiateMsg { + owner: creator.clone().sender.to_string(), + vault_id, + token_id, + fee_collector_addr: fee_collector_address.to_string(), + }, + &[], + "pool_router", + None, + ) + .unwrap(); + + let whale_lair_address = app + .instantiate_contract( + whale_lair_id, + creator.clone().sender, + &white_whale_std::whale_lair::InstantiateMsg { + unbonding_period: Uint64::new(1u64), + growth_rate: Decimal::one(), + bonding_assets: vec![ + AssetInfo::NativeToken { + denom: "ampWHALE".to_string(), + }, + AssetInfo::NativeToken { + denom: "bWHALE".to_string(), + }, + ], + }, + &[], + "whale_lair", + None, + ) + .unwrap(); + + let fee_distributor_address = app + .instantiate_contract( + fee_distributor_id, + creator.clone().sender, + &white_whale_std::fee_distributor::InstantiateMsg { + bonding_contract_addr: whale_lair_address.clone().to_string(), + fee_collector_addr: fee_collector_address.clone().to_string(), + grace_period: Uint64::new(1), + epoch_config: EpochConfig { + duration: Uint64::new(86_400_000_000_000u64), // a day + genesis_epoch: Uint64::new(1678802400_000000000u64), // March 14, 2023 2:00:00 PM + }, + distribution_asset: AssetInfo::NativeToken { + denom: "uwhale".to_string(), + }, + }, + &[], + "fee_distributor", + None, + ) + .unwrap(); + + // add the fee distributor address to the whale lair contract so we can use it as a clock + app.execute_contract( + creator.sender.clone(), + whale_lair_address.clone(), + &white_whale_std::whale_lair::ExecuteMsg::UpdateConfig { + fee_distributor_addr: Some(fee_distributor_address.to_string()), + owner: None, + unbonding_period: None, + growth_rate: None, + }, + &[], + ) + .unwrap(); + + // add pool router address to the fee collector to be able to aggregate fees, also add take rate + app.execute_contract( + creator.sender.clone(), + fee_collector_address.clone(), + &UpdateConfig { + owner: None, + pool_router: Some(pool_router_address.to_string()), + fee_distributor: Some(fee_distributor_address.to_string()), + pool_factory: Some(pool_factory_address.to_string()), + vault_factory: Some(vault_factory_address.to_string()), + take_rate: Some(Decimal::percent(50u64)), + take_rate_dao_address: Some(dao_mock.to_string()), + is_take_rate_active: Some(true), + }, + &[], + ) + .unwrap(); + + // add native tokens to the factory + app.execute_contract( + creator.sender.clone(), + pool_factory_address.clone(), + &AddNativeTokenDecimals { + denom: "uwhale".to_string(), + decimals: 6, + }, + &[Coin { + denom: "uwhale".to_string(), + amount: Uint128::new(1u128), + }], + ) + .unwrap(); + + app.execute_contract( + creator.sender.clone(), + pool_factory_address.clone(), + &AddNativeTokenDecimals { + denom: "usdc".to_string(), + decimals: 6, + }, + &[Coin { + denom: "usdc".to_string(), + amount: Uint128::new(1u128), + }], + ) + .unwrap(); + + app.execute_contract( + creator.sender.clone(), + pool_factory_address.clone(), + &AddNativeTokenDecimals { + denom: "ampWHALE".to_string(), + decimals: 6, + }, + &[Coin { + denom: "ampWHALE".to_string(), + amount: Uint128::new(1u128), + }], + ) + .unwrap(); + + app.execute_contract( + creator.sender.clone(), + pool_factory_address.clone(), + &AddNativeTokenDecimals { + denom: "bWHALE".to_string(), + decimals: 6, + }, + &[Coin { + denom: "bWHALE".to_string(), + amount: Uint128::new(1u128), + }], + ) + .unwrap(); + + // Create few pools + let native_tokens: Vec<&str> = vec!["usdc", "ampWHALE", "bWHALE"]; + let mut pair_tokens: Vec = Vec::new(); + for native_token in native_tokens.clone() { + let res = app + .execute_contract( + creator.sender.clone(), + pool_factory_address.clone(), + &CreatePair { + asset_infos: [ + AssetInfo::NativeToken { + denom: "uwhale".to_string(), + }, + AssetInfo::NativeToken { + denom: native_token.to_string(), + }, + ], + pool_fees: PoolFee { + protocol_fee: Fee { + share: Decimal::percent(5u64), + }, + swap_fee: Fee { + share: Decimal::percent(7u64), + }, + burn_fee: Fee { + share: Decimal::zero(), + }, + }, + pair_type: PairType::ConstantProduct, + token_factory_lp: false, + }, + &[], + ) + .unwrap(); + + let pair_address = Addr::unchecked( + res.events + .last() + .unwrap() + .attributes + .clone() + .get(1) + .unwrap() + .clone() + .value, + ); + pair_tokens.push(pair_address); + } + + // Provide liquidity into pools + for (i, native_token) in native_tokens.clone().iter().enumerate() { + app.execute_contract( + creator.sender.clone(), + pair_tokens[i].clone(), + &pool_network::pair::ExecuteMsg::ProvideLiquidity { + assets: [ + Asset { + info: AssetInfo::NativeToken { + denom: "uwhale".to_string(), + }, + amount: Uint128::new(500_000u128), + }, + Asset { + info: AssetInfo::NativeToken { + denom: native_token.to_string(), + }, + amount: Uint128::new(500_000u128), + }, + ], + slippage_tolerance: None, + receiver: None, + }, + &[ + Coin { + denom: "uwhale".to_string(), + amount: Uint128::new(500_000u128), + }, + Coin { + denom: native_token.to_string(), + amount: Uint128::new(500_000u128), + }, + ], + ) + .unwrap(); + } + + let uwhale_balance_on_dao = app + .wrap() + .query_balance(dao_mock.clone(), "uwhale") + .unwrap() + .amount; + + assert_eq!(uwhale_balance_on_dao, Uint128::zero()); + + // Create some epoch + + // Create EPOCH 1 with over 1000 whale + // whale -> native + app.execute_contract( + creator.sender.clone(), + pair_tokens[0].clone(), + &pool_network::pair::ExecuteMsg::Swap { + offer_asset: Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(21_100u128), + }, + belief_price: None, + max_spread: Some(Decimal::percent(30u64)), + to: None, + }, + &[Coin { + denom: "usdc".to_string(), + amount: Uint128::new(21_100u128), + }], + ) + .unwrap(); + + // advance the time to one day after the first epoch was created + app.set_block(BlockInfo { + height: 123456789u64, + time: Timestamp::from_nanos(1678888800_000000000u64), + chain_id: "".to_string(), + }); + + // Create new epoch, which triggers fee collection, aggregation and distribution + // Verify epoch 1 + app.execute_contract( + creator.sender.clone(), + fee_distributor_address.clone(), + &NewEpoch {}, + &[], + ) + .unwrap(); + + // check that a new epoch was created + let expiring_epoch_res: EpochResponse = app + .wrap() + .query_wasm_smart( + fee_distributor_address.clone(), + &white_whale_std::fee_distributor::QueryMsg::CurrentEpoch {}, + ) + .unwrap(); + assert_eq!(expiring_epoch_res.epoch.id, Uint64::one()); + assert_eq!( + expiring_epoch_res.epoch.available, + expiring_epoch_res.epoch.total + ); + assert!(expiring_epoch_res.epoch.claimed.is_empty()); + // Verify expiring_epoch_res.epoch.available, has 1_012 / 2 whale as an Asset + assert_eq!( + expiring_epoch_res.epoch.available, + vec![Asset { + info: AssetInfo::NativeToken { + denom: "uwhale".to_string(), + }, + amount: Uint128::new(1_012u128 - 506u128), + }] + ); + + // the other half of the rewards went to the dao as per take rate + let uwhale_balance_on_dao = app + .wrap() + .query_balance(dao_mock.clone(), "uwhale") + .unwrap() + .amount; + + assert_eq!(uwhale_balance_on_dao, Uint128::new(506u128)); + + // check the take rate query + let take_rate: Coin = app + .wrap() + .query_wasm_smart( + fee_collector_address.clone(), + &QueryMsg::TakeRateHistory { + epoch_id: Uint64::one(), + }, + ) + .unwrap(); + + assert_eq!(take_rate, coin(Uint128::new(506u128).u128(), "uwhale")); + + // this query should err cuz there's no take rate history for epoch 10 + app.wrap() + .query_wasm_smart::( + fee_collector_address.clone(), + &QueryMsg::TakeRateHistory { + epoch_id: Uint64::new(10), + }, + ) + .unwrap_err(); + + // When creating the second epoch, the first one will be expiring since the grace_period was set to 1. + // Make sure the available tokens on the expiring epoch are transferred to the second one. + app.execute_contract( + creator.sender.clone(), + pair_tokens[0].clone(), + &pool_network::pair::ExecuteMsg::Swap { + offer_asset: Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(25_000u128), + }, + belief_price: None, + max_spread: Some(Decimal::percent(30u64)), + to: None, + }, + &[Coin { + denom: "usdc".to_string(), + amount: Uint128::new(25_000u128), + }], + ) + .unwrap(); + + // advance the time to one day after the first epoch was created + app.set_block(BlockInfo { + height: 123456789u64, + time: Timestamp::from_nanos(1678888800_000000000u64), + chain_id: "".to_string(), + }); + + // the other half of the rewards went to the dao as per take rate + let uwhale_balance_on_dao = app + .wrap() + .query_balance(dao_mock.clone(), "uwhale") + .unwrap() + .amount; + + assert_eq!(uwhale_balance_on_dao, Uint128::new(506u128)); + + // Create new epoch, which triggers fee collection, aggregation and distribution + // Create EPOCH 2 + app.execute_contract( + creator.sender.clone(), + fee_distributor_address.clone(), + &NewEpoch {}, + &[], + ) + .unwrap(); + + // check that the second epoch was created + let new_epoch_res: EpochResponse = app + .wrap() + .query_wasm_smart( + fee_distributor_address.clone(), + &white_whale_std::fee_distributor::QueryMsg::CurrentEpoch {}, + ) + .unwrap(); + + assert_eq!(new_epoch_res.epoch.id, Uint64::new(2u64)); + assert_eq!(new_epoch_res.epoch.available, new_epoch_res.epoch.total); + assert_approx_eq!( + new_epoch_res.epoch.total[0].amount, + Uint128::new(550u128 + 506u128), + "0.001" + ); + assert!(new_epoch_res.epoch.claimed.is_empty()); + + // check that the available assets for the expired epoch are zero/empty + let expired_epoch_res: EpochResponse = app + .wrap() + .query_wasm_smart( + fee_distributor_address.clone(), + &white_whale_std::fee_distributor::QueryMsg::Epoch { id: Uint64::one() }, + ) + .unwrap(); + assert!(expired_epoch_res.epoch.available.is_empty()); + + // the other half of the rewards went to the dao as per take rate + let uwhale_balance_on_dao = app + .wrap() + .query_balance(dao_mock.clone(), "uwhale") + .unwrap() + .amount; + + assert_eq!(uwhale_balance_on_dao, Uint128::new(506u128 + 550u128)); +} diff --git a/contracts/liquidity_hub/fee_collector/src/tests/testing.rs b/contracts/liquidity_hub/fee_collector/src/tests/testing.rs index d0d03d39..bb8c9c2a 100644 --- a/contracts/liquidity_hub/fee_collector/src/tests/testing.rs +++ b/contracts/liquidity_hub/fee_collector/src/tests/testing.rs @@ -1,7 +1,8 @@ use cosmwasm_std::testing::{mock_env, mock_info}; -use cosmwasm_std::{from_json, Addr, DepsMut, MessageInfo, Response}; +use cosmwasm_std::{from_json, Addr, Decimal, DepsMut, MessageInfo, Response}; use cw2::{get_contract_version, set_contract_version, ContractVersion}; use std::env; +use std::str::FromStr; use crate::contract::{execute, instantiate, migrate, query}; use white_whale_std::pool_network::mock_querier::mock_dependencies; @@ -44,6 +45,9 @@ fn test_update_config_successfully() { fee_distributor: None, pool_factory: None, vault_factory: None, + take_rate: Some(Decimal::from_str("0.5").unwrap()), + take_rate_dao_address: Some("take_rate_dao_address".to_string()), + is_take_rate_active: Some(true), }; execute(deps.as_mut(), mock_env(), info, msg).unwrap(); @@ -52,18 +56,43 @@ fn test_update_config_successfully() { let config_res: Config = from_json(&query_res).unwrap(); assert_eq!(config_res.owner, Addr::unchecked("new_owner")); assert_eq!(config_res.pool_router, Addr::unchecked("new_router")); + assert_eq!( + config_res.take_rate_dao_address, + Addr::unchecked("take_rate_dao_address") + ); + assert_eq!(config_res.is_take_rate_active, true); + assert_eq!(config_res.take_rate, Decimal::from_str("0.5").unwrap()); } #[test] fn test_update_config_unsuccessfully_unauthorized() { let mut deps = mock_dependencies(&[]); let info = mock_info("owner", &[]); - mock_instantiation(deps.as_mut(), info).unwrap(); + mock_instantiation(deps.as_mut(), info.clone()).unwrap(); let query_res = query(deps.as_ref(), mock_env(), QueryMsg::Config {}).unwrap(); let config_res: Config = from_json(&query_res).unwrap(); assert_eq!(config_res.owner, Addr::unchecked("owner")); + let msg = ExecuteMsg::UpdateConfig { + owner: None, + pool_router: None, + fee_distributor: None, + pool_factory: None, + vault_factory: None, + take_rate: Some(Decimal::one()), + take_rate_dao_address: None, + is_take_rate_active: None, + }; + + let err = execute(deps.as_mut(), mock_env(), info, msg); + + match err { + Ok(_) => panic!("should return ContractError::InvalidTakeRate"), + Err(ContractError::InvalidTakeRate {}) => (), + _ => panic!("should return ContractError::InvalidTakeRate"), + } + let info = mock_info("unauthorized", &[]); let msg = ExecuteMsg::UpdateConfig { owner: Some("new_owner".to_string()), @@ -71,6 +100,9 @@ fn test_update_config_unsuccessfully_unauthorized() { fee_distributor: None, pool_factory: None, vault_factory: None, + take_rate: None, + take_rate_dao_address: None, + is_take_rate_active: None, }; let res = execute(deps.as_mut(), mock_env(), info, msg); diff --git a/packages/white-whale-std/Cargo.toml b/packages/white-whale-std/Cargo.toml index f716910a..a92b88c0 100644 --- a/packages/white-whale-std/Cargo.toml +++ b/packages/white-whale-std/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "white-whale-std" -version = "1.1.5" +version = "1.2.6" edition.workspace = true authors = [ "Kerber0x ", diff --git a/packages/white-whale-std/src/fee_collector.rs b/packages/white-whale-std/src/fee_collector.rs index d4c68196..ab99398e 100644 --- a/packages/white-whale-std/src/fee_collector.rs +++ b/packages/white-whale-std/src/fee_collector.rs @@ -1,7 +1,7 @@ use crate::fee_distributor::Epoch; use crate::pool_network::asset::{Asset, AssetInfo}; use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::Addr; +use cosmwasm_std::{Addr, Coin, Decimal, Uint64}; #[cw_serde] pub struct InstantiateMsg {} @@ -25,6 +25,9 @@ pub enum ExecuteMsg { fee_distributor: Option, pool_factory: Option, vault_factory: Option, + take_rate: Option, + take_rate_dao_address: Option, + is_take_rate_active: Option, }, } @@ -51,6 +54,9 @@ pub enum QueryMsg { query_fees_for: FeesFor, all_time: Option, }, + /// Queries the take rate taken for the given epoch id. + #[returns(Coin)] + TakeRateHistory { epoch_id: Uint64 }, } #[cw_serde] @@ -96,4 +102,7 @@ pub struct Config { pub fee_distributor: Addr, pub pool_factory: Addr, pub vault_factory: Addr, + pub take_rate: Decimal, + pub take_rate_dao_address: Addr, + pub is_take_rate_active: bool, }