use crate::*; use near_sdk::{env, json_types::U128, near, require}; #[near] impl Contract { // ===== FlowCap Query Functions ===== /// Get current deposit flow status (usage, remaining capacity, window reset time) pub fn get_current_deposit_flow(&self) -> (U128, Option, Option) { let Some(cap) = self.vault_config.get_effective_deposit_cap() else { return (U128(0), None, None); // No cap configured }; let current_time = env::block_timestamp(); // Get current tracker state (immutable) let tracker = self.internal_get_current_tracker_state( &self.state.deposit_flow_tracker, current_time, cap.period_nanosec, ); let current_usage = tracker.accumulated_amount; let remaining_capacity = U128(cap.amount.0.saturating_sub(current_usage.0)); let window_reset_time = tracker.window_start_timestamp + cap.period_nanosec; ( current_usage, Some(remaining_capacity), Some(window_reset_time), ) } /// Get current withdrawal flow status (usage, remaining capacity, window reset time) pub fn get_current_withdrawal_flow(&self) -> (U128, Option, Option) { let Some(cap) = self.vault_config.get_effective_withdrawal_cap() else { return (U128(0), None, None); // No cap configured }; let current_time = env::block_timestamp(); // Get current tracker state (immutable) let tracker = self.internal_get_current_tracker_state( &self.state.withdrawal_flow_tracker, current_time, cap.period_nanosec, ); let current_usage = tracker.accumulated_amount; let remaining_capacity = U128(cap.amount.0.saturating_sub(current_usage.0)); let window_reset_time = tracker.window_start_timestamp + cap.period_nanosec; ( current_usage, Some(remaining_capacity), Some(window_reset_time), ) } /// Get flow window information for a specific type pub fn get_flow_window_info(&self, is_deposit: bool) -> Option<(u64, u64, u64)> { let cap_opt = if is_deposit { self.vault_config.get_effective_deposit_cap() } else { self.vault_config.get_effective_withdrawal_cap() }; cap_opt.map(|cap| { let current_time = env::block_timestamp(); let expected_window_start = self.internal_get_window_start(current_time, cap.period_nanosec); let window_reset_time = expected_window_start + cap.period_nanosec; (expected_window_start, window_reset_time, cap.period_nanosec) }) } } impl Contract { // ===== FlowCap Internal Functions ===== /// Force reset flow cap tracker to current window (internal implementation) pub fn internal_force_reset_flow_cap(&mut self, is_deposit: bool) { let current_time = env::block_timestamp(); if is_deposit { // get flow cap, panic if None let flow_cap = self .vault_config .get_effective_deposit_cap() .expect("No Flow Cap"); // calculate timestamp window let expected_window_start = self.internal_get_window_start(current_time, flow_cap.period_nanosec); self.state.deposit_flow_tracker = FlowTracker { accumulated_amount: U128(0), // reset to 0 window_start_timestamp: expected_window_start, } } else { // get flow cap, panic if None let flow_cap = self .vault_config .get_effective_withdrawal_cap() .expect("No Flow Cap"); // calculate timestamp window let expected_window_start = self.internal_get_window_start(current_time, flow_cap.period_nanosec); self.state.withdrawal_flow_tracker = FlowTracker { accumulated_amount: U128(0), // reset to 0 window_start_timestamp: expected_window_start, } } } /// Validate deposit against flow cap limits pub fn internal_validate_deposit_flow_cap(&mut self, deposit_amount: U128, asset: &Asset) { if let Some(cap) = self.vault_config.get_effective_deposit_cap().cloned() { let current_usage = self.internal_get_active_flow_tracker(cap.clone(), true); require!( self.internal_check_deposit_flow_cap( Some(cap.clone()), current_usage, deposit_amount, asset ), "Deposit exceeds flow cap limits" ); } } /// Validate withdrawal against flow cap limits pub fn internal_validate_withdrawal_flow_cap(&mut self, shares: U128) { if let Some(cap) = self.vault_config.get_effective_withdrawal_cap().cloned() { let current_usage = self.internal_get_active_flow_tracker(cap.clone(), false); require!( self.internal_check_withdrawal_flow_cap(Some(cap.clone()), current_usage, shares), "Withdrawal exceeds flow cap limits" ); } } /// Initialize flow tracker for processing (resets if window expired, returns current usage) pub fn internal_initialize_flow_tracker(&mut self, is_deposit: bool) -> u128 { if let Some(cap) = if is_deposit { self.vault_config.get_effective_deposit_cap().cloned() } else { self.vault_config.get_effective_withdrawal_cap().cloned() } { self.internal_get_active_flow_tracker(cap, is_deposit) } else { 0 // flow_cap is None } } /// Reset flow tracker if current time in next window, return accumulated_amount pub fn internal_get_active_flow_tracker( &mut self, flow_cap: FlowCap, is_deposit: bool, ) -> u128 { let current_time = env::block_timestamp(); let expected_window_start = self.internal_get_window_start(current_time, flow_cap.period_nanosec); if is_deposit { if self.state.deposit_flow_tracker.window_start_timestamp != expected_window_start { // flow window moved, reset values in state self.state.deposit_flow_tracker.accumulated_amount = U128(0); self.state.deposit_flow_tracker.window_start_timestamp = expected_window_start; 0u128 } else { self.state.deposit_flow_tracker.accumulated_amount.0 } } else { if self.state.withdrawal_flow_tracker.window_start_timestamp != expected_window_start { // flow window moved, reset values in state self.state.withdrawal_flow_tracker.accumulated_amount = U128(0); self.state.withdrawal_flow_tracker.window_start_timestamp = expected_window_start; 0u128 } else { self.state.withdrawal_flow_tracker.accumulated_amount.0 } } } /// Check if an amount fits within flow cap limits pub fn internal_check_flow_cap( &self, flow_cap: Option, current_usage: u128, amount: U128, ) -> bool { if let Some(cap) = flow_cap { let new_flow_total = current_usage.saturating_add(amount.0); new_flow_total <= cap.amount.0 } else { true // No cap configured } } /// Check if a deposit fits within flow cap limits pub fn internal_check_deposit_flow_cap( &self, flow_cap: Option, current_usage: u128, deposit_amount: U128, asset: &Asset, ) -> bool { let base_token_amount = self.internal_convert_amount_to_base_asset(deposit_amount, asset); self.internal_check_flow_cap(flow_cap, current_usage, base_token_amount) } /// Check if a withdrawal fits within flow cap limits pub fn internal_check_withdrawal_flow_cap( &self, flow_cap: Option, current_usage: u128, withdrawal_shares: U128, ) -> bool { // Convert shares to base_token equivalent let base_token_amount = self.convert_to_asset_amount(&self.base_asset, withdrawal_shares); self.internal_check_flow_cap(flow_cap, current_usage, base_token_amount) } /// Update running flow usage for a deposit (returns new usage) pub fn internal_update_deposit_flow_usage( &self, flow_cap: Option, current_usage: u128, deposit_amount: U128, asset: &Asset, ) -> u128 { if flow_cap.is_some() { let base_token_amount = self.internal_convert_amount_to_base_asset(deposit_amount, asset); current_usage.saturating_add(base_token_amount.0) } else { current_usage } } /// Update running flow usage for a withdrawal (returns new usage) pub fn internal_update_withdrawal_flow_usage( &self, flow_cap: Option, current_usage: u128, withdrawal_shares: U128, ) -> u128 { if flow_cap.is_some() { // Convert shares to base_token equivalent let base_token_amount = self.convert_to_asset_amount(&self.base_asset, withdrawal_shares); current_usage.saturating_add(base_token_amount.0) } else { current_usage } } /// Calculate the start timestamp of a fixed window for the current time pub fn internal_get_window_start(&self, current_time: u64, period_nanosec: u64) -> u64 { let period_nanosec_u128 = period_nanosec as u128; let current_time_u128 = current_time as u128; let result = (current_time_u128 / period_nanosec_u128) * period_nanosec_u128; result as u64 } /// Reset flow tracker if the current time is in a new window (immutable version for queries) pub fn internal_get_current_tracker_state( &self, tracker: &FlowTracker, current_time: u64, period_nanosec: u64, ) -> FlowTracker { let expected_window_start = self.internal_get_window_start(current_time, period_nanosec); if tracker.window_start_timestamp != expected_window_start { // New window - return reset tracker FlowTracker { accumulated_amount: U128(0), window_start_timestamp: expected_window_start, } } else { tracker.clone() } } /// Update deposit flow tracker after a successful deposit pub fn internal_update_deposit_flow(&mut self, amount: U128, asset: &Asset) { if let Some(_cap) = self.vault_config.get_effective_deposit_cap() { // Convert amount to base token equivalent let base_token_amount = self.internal_convert_amount_to_base_asset(amount, asset); // Add to accumulated amount self.state.deposit_flow_tracker.accumulated_amount.0 = self .state .deposit_flow_tracker .accumulated_amount .0 .checked_add(base_token_amount.0) .expect("Deposit flow cap overflow"); } } /// Update withdrawal flow tracker after a successful withdrawal pub fn internal_update_withdrawal_flow(&mut self, shares: U128) { if let Some(_cap) = self.vault_config.get_effective_withdrawal_cap() { // Convert shares to base_token equivalent let base_token_amount = self.convert_to_asset_amount(&self.base_asset, shares); // Add to accumulated amount self.state.withdrawal_flow_tracker.accumulated_amount.0 = self .state .withdrawal_flow_tracker .accumulated_amount .0 .checked_add(base_token_amount.0) .expect("Withdraw flow cap overflow"); } } }