diff --git a/apps/contracts/factory/src/events.rs b/apps/contracts/factory/src/events.rs index 479cc43a..c0c23b03 100644 --- a/apps/contracts/factory/src/events.rs +++ b/apps/contracts/factory/src/events.rs @@ -8,14 +8,14 @@ use crate::defindex::AssetAllocation; pub struct InitializedEvent { pub admin: Address, pub defindex_receiver: Address, - pub fee_rate: u32, + pub defindex_fee: u32, } -pub(crate) fn emit_initialized(e: &Env, admin: Address, defindex_receiver: Address, fee_rate: u32) { +pub(crate) fn emit_initialized(e: &Env, admin: Address, defindex_receiver: Address, defindex_fee: u32) { let event: InitializedEvent = InitializedEvent { admin, defindex_receiver, - fee_rate, + defindex_fee, }; e.events() .publish(("DeFindexFactory", symbol_short!("init")), event); @@ -28,7 +28,7 @@ pub struct CreateDeFindexEvent { pub emergency_manager: Address, pub fee_receiver: Address, pub manager: Address, - pub vault_share: u32, + pub vault_fee: u32, pub assets: Vec } @@ -38,14 +38,14 @@ pub(crate) fn emit_create_defindex_vault( emergency_manager: Address, fee_receiver: Address, manager: Address, - vault_share: u32, + vault_fee: u32, assets: Vec, ) { let event = CreateDeFindexEvent { emergency_manager, fee_receiver, manager, - vault_share, + vault_fee, assets, }; @@ -81,16 +81,16 @@ pub(crate) fn emit_new_defindex_receiver(e: &Env, new_defindex_receiver: Address .publish(("DeFindexFactory", symbol_short!("nreceiver")), event); } -// NEW FEE RATE EVENT +// NEW DEFINDEX FEE EVENT #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] pub struct NewFeeRateEvent { - pub new_fee_rate: u32, + pub new_defindex_fee: u32, } -pub(crate) fn emit_new_fee_rate(e: &Env, new_fee_rate: u32) { - let event = NewFeeRateEvent { new_fee_rate }; +pub(crate) fn emit_new_defindex_fee(e: &Env, new_defindex_fee: u32) { + let event = NewFeeRateEvent { new_defindex_fee }; e.events() - .publish(("DeFindexFactory", symbol_short!("nfee_rate")), event); + .publish(("DeFindexFactory", symbol_short!("n_fee")), event); } \ No newline at end of file diff --git a/apps/contracts/factory/src/lib.rs b/apps/contracts/factory/src/lib.rs index f02aa883..f29d9b70 100644 --- a/apps/contracts/factory/src/lib.rs +++ b/apps/contracts/factory/src/lib.rs @@ -10,7 +10,7 @@ use soroban_sdk::{ }; use error::FactoryError; use defindex::{create_contract, AssetAllocation}; -use storage::{ add_new_defindex, extend_instance_ttl, get_admin, get_defi_wasm_hash, get_defindex_receiver, get_deployed_defindexes, get_fee_rate, has_admin, put_admin, put_defi_wasm_hash, put_defindex_receiver, put_fee_rate }; +use storage::{ add_new_defindex, extend_instance_ttl, get_admin, get_defi_wasm_hash, get_defindex_receiver, get_deployed_defindexes, get_fee_rate, has_admin, put_admin, put_defi_wasm_hash, put_defindex_receiver, put_defindex_fee }; fn check_initialized(e: &Env) -> Result<(), FactoryError> { if !has_admin(e) { @@ -26,7 +26,7 @@ pub trait FactoryTrait { /// * `e` - The environment in which the contract is running. /// * `admin` - The address of the contract administrator, who can manage settings. /// * `defindex_receiver` - The default address designated to receive a portion of fees. - /// * `fee_rate` - The initial annual fee rate (in basis points). + /// * `defindex_fee` - The initial annual fee rate (in basis points). /// * `defindex_wasm_hash` - The hash of the DeFindex Vault's WASM file for deploying new vaults. /// /// # Returns @@ -35,7 +35,7 @@ pub trait FactoryTrait { e: Env, admin: Address, defindex_receiver: Address, - fee_rate: u32, + defindex_fee: u32, defindex_wasm_hash: BytesN<32> ) -> Result<(), FactoryError>; @@ -45,7 +45,7 @@ pub trait FactoryTrait { /// * `e` - The environment in which the contract is running. /// * `emergency_manager` - The address assigned emergency control over the vault. /// * `fee_receiver` - The address designated to receive fees from the vault. - /// * `vault_share` - The percentage share of fees allocated to the vault's fee receiver. + /// * `vault_fee` - The percentage share of fees allocated to the vault's fee receiver. /// * `vault_name` - The name of the vault. /// * `vault_symbol` - The symbol of the vault. /// * `manager` - The address assigned as the vault manager. @@ -58,7 +58,7 @@ pub trait FactoryTrait { e: Env, emergency_manager: Address, fee_receiver: Address, - vault_share: u32, + vault_fee: u32, vault_name: String, vault_symbol: String, manager: Address, @@ -72,7 +72,7 @@ pub trait FactoryTrait { /// * `e` - The environment in which the contract is running. /// * `emergency_manager` - The address assigned emergency control over the vault. /// * `fee_receiver` - The address designated to receive fees from the vault. - /// * `vault_share` - The percentage share of fees allocated to the vault's fee receiver. + /// * `vault_fee` - The percentage share of fees allocated to the vault's fee receiver. /// * `vault_name` - The name of the vault. /// * `vault_symbol` - The symbol of the vault. /// * `manager` - The address assigned as the vault manager. @@ -87,7 +87,7 @@ pub trait FactoryTrait { caller: Address, emergency_manager: Address, fee_receiver: Address, - vault_share: u32, + vault_fee: u32, vault_name: String, vault_symbol: String, manager: Address, @@ -126,7 +126,7 @@ pub trait FactoryTrait { /// /// # Returns /// * `Result<(), FactoryError>` - Returns Ok(()) if successful, or an error if not authorized. - fn set_fee_rate(e: Env, new_fee_rate: u32) -> Result<(), FactoryError>; + fn set_defindex_fee(e: Env, new_fee_rate: u32) -> Result<(), FactoryError>; // --- Read Methods --- @@ -164,7 +164,7 @@ pub trait FactoryTrait { /// /// # Returns /// * `Result` - Returns the fee rate in basis points or an error if not found. - fn fee_rate(e: Env) -> Result; + fn defindex_fee(e: Env) -> Result; } #[contract] @@ -179,7 +179,7 @@ impl FactoryTrait for DeFindexFactory { /// * `e` - The environment in which the contract is running. /// * `admin` - The address of the contract administrator, who can manage settings. /// * `defindex_receiver` - The default address designated to receive a portion of fees. - /// * `fee_rate` - The initial annual fee rate (in basis points). + /// * `defindex_fee` - The initial annual fee rate (in basis points). /// * `defindex_wasm_hash` - The hash of the DeFindex Vault's WASM file for deploying new vaults. /// /// # Returns @@ -188,7 +188,7 @@ impl FactoryTrait for DeFindexFactory { e: Env, admin: Address, defindex_receiver: Address, - fee_rate: u32, + defindex_fee: u32, defi_wasm_hash: BytesN<32> ) -> Result<(), FactoryError> { if has_admin(&e) { @@ -198,9 +198,9 @@ impl FactoryTrait for DeFindexFactory { put_admin(&e, &admin); put_defindex_receiver(&e, &defindex_receiver); put_defi_wasm_hash(&e, defi_wasm_hash); - put_fee_rate(&e, &fee_rate); + put_defindex_fee(&e, &defindex_fee); - events::emit_initialized(&e, admin, defindex_receiver, fee_rate); + events::emit_initialized(&e, admin, defindex_receiver, defindex_fee); extend_instance_ttl(&e); Ok(()) } @@ -211,7 +211,7 @@ impl FactoryTrait for DeFindexFactory { /// * `e` - The environment in which the contract is running. /// * `emergency_manager` - The address assigned emergency control over the vault. /// * `fee_receiver` - The address designated to receive fees from the vault. - /// * `vault_share` - The percentage share of fees allocated to the vault's fee receiver. + /// * `vault_fee` - The percentage share of fees allocated to the vault's fee receiver. /// * `manager` - The address assigned as the vault manager. /// * `assets` - A vector of `AssetAllocation` structs that define the assets managed by the vault. /// * `salt` - A salt used for ensuring unique addresses for each deployed vault. @@ -222,7 +222,7 @@ impl FactoryTrait for DeFindexFactory { e: Env, emergency_manager: Address, fee_receiver: Address, - vault_share: u32, + vault_fee: u32, vault_name: String, vault_symbol: String, manager: Address, @@ -243,7 +243,7 @@ impl FactoryTrait for DeFindexFactory { &manager, &emergency_manager, &fee_receiver, - &vault_share, + &vault_fee, &defindex_receiver, ¤t_contract, &vault_name, @@ -251,7 +251,7 @@ impl FactoryTrait for DeFindexFactory { ); add_new_defindex(&e, defindex_address.clone()); - events::emit_create_defindex_vault(&e, emergency_manager, fee_receiver, manager, vault_share, assets); + events::emit_create_defindex_vault(&e, emergency_manager, fee_receiver, manager, vault_fee, assets); Ok(defindex_address) } @@ -261,7 +261,7 @@ impl FactoryTrait for DeFindexFactory { /// * `e` - The environment in which the contract is running. /// * `emergency_manager` - The address assigned emergency control over the vault. /// * `fee_receiver` - The address designated to receive fees from the vault. - /// * `vault_share` - The percentage share of fees allocated to the vault's fee receiver. + /// * `vault_fee` - The percentage share of fees allocated to the vault's fee receiver. /// * `vault_name` - The name of the vault. /// * `vault_symbol` - The symbol of the vault. /// * `manager` - The address assigned as the vault manager. @@ -276,7 +276,7 @@ impl FactoryTrait for DeFindexFactory { caller: Address, emergency_manager: Address, fee_receiver: Address, - vault_share: u32, + vault_fee: u32, vault_name: String, vault_symbol: String, manager: Address, @@ -305,7 +305,7 @@ impl FactoryTrait for DeFindexFactory { &manager, &emergency_manager, &fee_receiver, - &vault_share, + &vault_fee, &defindex_receiver, ¤t_contract, &vault_name, @@ -324,7 +324,7 @@ impl FactoryTrait for DeFindexFactory { ); add_new_defindex(&e, defindex_address.clone()); - events::emit_create_defindex_vault(&e, emergency_manager, fee_receiver, manager, vault_share, assets); + events::emit_create_defindex_vault(&e, emergency_manager, fee_receiver, manager, vault_fee, assets); Ok(defindex_address) } @@ -376,14 +376,14 @@ impl FactoryTrait for DeFindexFactory { /// /// # Returns /// * `Result<(), FactoryError>` - Returns Ok(()) if successful, or an error if not authorized. - fn set_fee_rate(e: Env, fee_rate: u32) -> Result<(), FactoryError> { + fn set_defindex_fee(e: Env, defindex_fee: u32) -> Result<(), FactoryError> { check_initialized(&e)?; extend_instance_ttl(&e); let admin = get_admin(&e); admin.require_auth(); - put_fee_rate(&e, &fee_rate); - events::emit_new_fee_rate(&e, fee_rate); + put_defindex_fee(&e, &defindex_fee); + events::emit_new_defindex_fee(&e, defindex_fee); Ok(()) } @@ -435,7 +435,7 @@ impl FactoryTrait for DeFindexFactory { /// /// # Returns /// * `Result` - Returns the fee rate in basis points or an error if not found. - fn fee_rate(e: Env) -> Result { + fn defindex_fee(e: Env) -> Result { check_initialized(&e)?; extend_instance_ttl(&e); Ok(get_fee_rate(&e)) diff --git a/apps/contracts/factory/src/storage.rs b/apps/contracts/factory/src/storage.rs index 92589bb5..bde91cd0 100644 --- a/apps/contracts/factory/src/storage.rs +++ b/apps/contracts/factory/src/storage.rs @@ -99,7 +99,7 @@ pub fn get_defindex_receiver(e: &Env) -> Address { } // Fee Rate BPS (MAX BPS = 10000) -pub fn put_fee_rate(e: &Env, value: &u32) { +pub fn put_defindex_fee(e: &Env, value: &u32) { e.storage().instance().set(&DataKey::FeeRate, value); } diff --git a/apps/contracts/src/test.ts b/apps/contracts/src/test.ts index d8e91ae7..73b75e4f 100644 --- a/apps/contracts/src/test.ts +++ b/apps/contracts/src/test.ts @@ -84,7 +84,7 @@ export async function test_factory(addressBook: AddressBook) { const createDeFindexParams: xdr.ScVal[] = [ new Address(emergencyManager.publicKey()).toScVal(), new Address(feeReceiver.publicKey()).toScVal(), - nativeToScVal(100, { type: "u32" }), // Setting vault_share as 100 bps for demonstration + nativeToScVal(100, { type: "u32" }), // Setting vault_fee as 100 bps for demonstration nativeToScVal("Test Vault", { type: "string" }), nativeToScVal("DFT-Test-Vault", { type: "string" }), new Address(manager.publicKey()).toScVal(), diff --git a/apps/contracts/vault/src/fee.rs b/apps/contracts/vault/src/fee.rs index 36d4cd3e..5a5169e4 100644 --- a/apps/contracts/vault/src/fee.rs +++ b/apps/contracts/vault/src/fee.rs @@ -1,67 +1,71 @@ -use soroban_sdk::{Address, Env, Map, Symbol, Vec}; +use soroban_sdk::{Env, Symbol, Vec}; use crate::{ access::AccessControl, constants::{MAX_BPS, SECONDS_PER_YEAR}, events, - funds::fetch_total_managed_funds, storage::{ - get_defindex_protocol_fee_receiver, get_factory, get_last_fee_assesment, get_vault_share, + get_defindex_protocol_fee_receiver, get_factory, get_last_fee_assesment, get_vault_fee, set_last_fee_assesment, }, - token::internal_mint, - utils::calculate_dftokens_from_asset_amounts, + token::{internal_mint, VaultToken}, ContractError, }; /// Fetches the current fee rate from the factory contract. /// The fee rate is expressed in basis points (BPS). -fn fetch_fee_rate(e: &Env) -> u32 { +fn fetch_defindex_fee(e: &Env) -> u32 { let factory_address = get_factory(e); // Interacts with the factory contract to get the fee rate. - e.invoke_contract(&factory_address, &Symbol::new(&e, "fee_rate"), Vec::new(&e)) + e.invoke_contract( + &factory_address, + &Symbol::new(&e, "defindex_fee"), + Vec::new(&e) + ) } +/// Calculates the required fees in dfTokens based on the current APR fee rate. fn calculate_fees(e: &Env, time_elapsed: u64, fee_rate: u32) -> Result { - let total_managed_funds = fetch_total_managed_funds(e); // Get total managed funds per asset - - let seconds_per_year = SECONDS_PER_YEAR; // 365 days in seconds - - let mut total_fees_per_asset: Map = Map::new(&e); - - // Iterate over each asset in the vault - for (asset_address, amount) in total_managed_funds.iter() { - // Fetch current managed funds for each asset - let current_asset_value = amount; - - // Calculate the fee for this asset based on the fee rate and time elapsed - let asset_fee = (current_asset_value * fee_rate as i128 * time_elapsed as i128) - / (seconds_per_year * MAX_BPS); - - total_fees_per_asset.set(asset_address.clone(), asset_fee); - } - - let total_fees_in_dftokens = calculate_dftokens_from_asset_amounts(e, total_fees_per_asset)?; - - Ok(total_fees_in_dftokens) + let total_supply = VaultToken::total_supply(e.clone()); + + // fee_rate as i128 * total_supply * time_elapsed / SECONDS_PER_YEAR * MAX_BPS - fee_rate as i128 * time_elapsed; + let numerator = (fee_rate as i128) + .checked_mul(total_supply) + .unwrap() + .checked_mul(time_elapsed as i128) + .unwrap(); + let denominator = SECONDS_PER_YEAR + .checked_mul(MAX_BPS) + .unwrap() + .checked_sub((fee_rate as i128).checked_mul(time_elapsed as i128).unwrap()) + .unwrap(); + let fees = numerator.checked_div(denominator).unwrap(); + + Ok(fees) } +/// Collects and mints fees in dfTokens, distributing them to the appropriate fee receivers. pub fn collect_fees(e: &Env) -> Result<(), ContractError> { let current_timestamp = e.ledger().timestamp(); let last_fee_assessment = get_last_fee_assesment(e); let time_elapsed = current_timestamp.checked_sub(last_fee_assessment).unwrap(); + // If no time has passed since the last fee assessment, no fees are collected if time_elapsed == 0 { return Ok(()); } - let fee_rate = fetch_fee_rate(e); + // Fetch the individual fees for DeFindex and Vault, then calculate the total rate + let defindex_fee = fetch_defindex_fee(e); + let vault_fee = get_vault_fee(e); + let total_fee_rate = defindex_fee.checked_add(vault_fee).unwrap(); - let total_fees = calculate_fees(e, time_elapsed, fee_rate)?; + // Calculate the total fees in dfTokens based on the combined fee rate + let total_fees = calculate_fees(e, time_elapsed, total_fee_rate)?; - // Mint the total fees as dfTokens - mint_fees(e, total_fees)?; + // Mint and distribute the fees proportionally + mint_fees(e, total_fees, defindex_fee, vault_fee)?; // Update the last fee assessment timestamp set_last_fee_assesment(e, ¤t_timestamp); @@ -69,18 +73,19 @@ pub fn collect_fees(e: &Env) -> Result<(), ContractError> { Ok(()) } -fn mint_fees(e: &Env, total_fees: i128) -> Result<(), ContractError> { +/// Mints dfTokens for fees and distributes them to the vault fee receiver and DeFindex receiver. +fn mint_fees(e: &Env, total_fees: i128, defindex_fee: u32, vault_fee: u32) -> Result<(), ContractError> { let access_control = AccessControl::new(&e); let vault_fee_receiver = access_control.get_fee_receiver()?; let defindex_protocol_receiver = get_defindex_protocol_fee_receiver(e); - let vault_share_bps = get_vault_share(e); - - let vault_shares = (total_fees * vault_share_bps as i128) / MAX_BPS; - - let defindex_shares = total_fees - vault_shares; + // Calculate shares for each receiver based on their fee proportion + let total_fee_bps = defindex_fee as i128 + vault_fee as i128; + let defindex_shares = (total_fees * defindex_fee as i128) / total_fee_bps; + let vault_shares = total_fees - defindex_shares; + // Mint shares for both receivers internal_mint(e.clone(), vault_fee_receiver.clone(), vault_shares); internal_mint( e.clone(), diff --git a/apps/contracts/vault/src/lib.rs b/apps/contracts/vault/src/lib.rs index 925c979d..0aff3cc1 100755 --- a/apps/contracts/vault/src/lib.rs +++ b/apps/contracts/vault/src/lib.rs @@ -180,6 +180,9 @@ impl VaultTrait for DeFindexVault { set_last_fee_assesment(&e, &e.ledger().timestamp()); } + // fees assesment + collect_fees(&e)?; + // get assets let assets = get_assets(&e); let assets_length = assets.len(); @@ -236,8 +239,6 @@ impl VaultTrait for DeFindexVault { events::emit_deposit_event(&e, from, amounts, shares_to_mint); - // fees assesment - collect_fees(&e)?; // TODO return amounts and shares to mint Ok(()) } @@ -260,6 +261,9 @@ impl VaultTrait for DeFindexVault { check_nonnegative_amount(df_amount)?; from.require_auth(); + // fees assesment + collect_fees(&e)?; + // Check if the user has enough dfTokens let df_user_balance = VaultToken::balance(e.clone(), from.clone()); if df_user_balance < df_amount { @@ -327,10 +331,7 @@ impl VaultTrait for DeFindexVault { } events::emit_withdraw_event(&e, from, df_amount, amounts_withdrawn.clone()); - - // fees assesment - collect_fees(&e)?; - + Ok(amounts_withdrawn) } diff --git a/apps/contracts/vault/src/storage.rs b/apps/contracts/vault/src/storage.rs index 90df7c61..a546223a 100644 --- a/apps/contracts/vault/src/storage.rs +++ b/apps/contracts/vault/src/storage.rs @@ -10,7 +10,7 @@ enum DataKey { DeFindexProtocolFeeReceiver, Factory, LastFeeAssessment, - VaultShare, + VaultFee, } // Assets Management @@ -83,9 +83,14 @@ pub fn get_last_fee_assesment(e: &Env) -> u64 { // Vault Share pub fn set_vault_fee(e: &Env, vault_fee: &u32) { - e.storage().instance().set(&DataKey::VaultShare, vault_fee); + e.storage() + .instance() + .set(&DataKey::VaultFee, vault_fee); } -pub fn get_vault_share(e: &Env) -> u32 { - e.storage().instance().get(&DataKey::VaultShare).unwrap() +pub fn get_vault_fee(e: &Env) -> u32 { + e.storage() + .instance() + .get(&DataKey::VaultFee) + .unwrap() } diff --git a/apps/contracts/vault/src/utils.rs b/apps/contracts/vault/src/utils.rs index 0d93e27d..96f0ed88 100644 --- a/apps/contracts/vault/src/utils.rs +++ b/apps/contracts/vault/src/utils.rs @@ -86,9 +86,9 @@ pub fn calculate_asset_amounts_for_dftokens( pub fn calculate_dftokens_from_asset_amounts( env: &Env, asset_amounts: Map, // The input asset amounts + total_managed_funds: Map, // The total managed funds for each asset ) -> Result { let total_supply = VaultToken::total_supply(env.clone()); // Total dfToken supply - let total_managed_funds = fetch_total_managed_funds(&env); // Fetch all managed assets // Initialize the minimum dfTokens corresponding to each asset let mut min_df_tokens: Option = None; diff --git a/apps/docs/10-whitepaper/03-the-defindex-approach/02-contracts/01-vault-contract.md b/apps/docs/10-whitepaper/03-the-defindex-approach/02-contracts/01-vault-contract.md index e1144219..2f2783d7 100644 --- a/apps/docs/10-whitepaper/03-the-defindex-approach/02-contracts/01-vault-contract.md +++ b/apps/docs/10-whitepaper/03-the-defindex-approach/02-contracts/01-vault-contract.md @@ -230,7 +230,7 @@ The remaining 1.01 dfTokens represent the collected fee, backed by around: $ 1.01 \, \text{dfTokens} \times 1.069 \, \text{USDC per share} \approx 1.08 \, \text{USDC} -\] +$ ---