Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion neurowealth-vault/contracts/vault/src/insurance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ pub struct InsuranceConfig {

impl InsuranceConfig {
/// Create a new config.
pub fn new(contribution_rate_bps: i128, max_payout_per_incident: i128, min_threshold: i128) -> Self {
pub fn new(
contribution_rate_bps: i128,
max_payout_per_incident: i128,
min_threshold: i128,
) -> Self {
Self {
contribution_rate_bps,
max_payout_per_incident,
Expand Down
1,133 changes: 843 additions & 290 deletions neurowealth-vault/contracts/vault/src/lib.rs

Large diffs are not rendered by default.

44 changes: 22 additions & 22 deletions neurowealth-vault/contracts/vault/src/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod test_access_control;
mod test_adversarial_agent_simulation;
mod test_agent_timelock;
mod test_allowance_hygiene;
mod test_approval_ttl;
mod test_approval_ttl_schedule;
mod test_asset_breakdown;
Expand All @@ -21,22 +22,23 @@ mod test_deposit;
mod test_dex_devnet;
mod test_dex_integration;
mod test_emergency_harvest;
mod test_emergency_withdrawal;
mod test_event_payloads;
mod test_event_schema;
mod test_events;
mod test_exchange_rate;
mod test_fuzz_deposit_withdraw;
mod test_get_user_strategy_unset;
mod test_harvest;
mod test_inflation_attack;
mod test_index_pruning;
mod test_inflation_attack;
mod test_initialize;
mod test_legacy_inline;
mod test_limits;
mod test_math_limits;
mod test_multi_asset;
mod test_multi_protocol;
mod test_multi_user_concurrent;
mod test_multi_asset;
mod test_no_privilege_escalation_proptest;
mod test_pause;
mod test_pool_config_idempotent;
Expand All @@ -47,6 +49,8 @@ mod test_rebalance_cooldown;
mod test_rebalance_integration;
// Pre-existing compile failures (MockTokenDataKey not #[contracttype], no std).
// mod test_reentrancy_defense;
mod test_migration;
mod test_migration_timelock;
mod test_require_initialized;
mod test_rotation_accounting_invariants;
mod test_rounding_math;
Expand All @@ -55,8 +59,8 @@ mod test_security_agent_drain;
mod test_share_conversion_proptest;
mod test_share_locking;
mod test_shares;
mod test_standby_agent;
mod test_stale_state_audit;
mod test_standby_agent;
mod test_storage_griefing_analysis;
mod test_strategy_switch_low_liquidity;
mod test_total_assets_cap;
Expand All @@ -69,34 +73,30 @@ mod test_update_total_assets_blend;
mod test_update_total_assets_dex;
mod test_upgrade_compatibility;
mod test_upgrade_timelock;
mod test_migration_timelock;
mod test_user_strategy;
mod test_users_with_shares;
mod test_withdraw;
mod test_yield;
mod utils;


mod test_liquidity_mining_rewards;
mod test_performance_fee;


mod test_batch_deposit;
mod test_liquidity_mining_rewards;
mod test_performance_fee;
// Pre-existing compile failures — WIP modules committed with broken imports
// (`crate::tests::` paths that can never resolve: lib.rs mounts this file as
// `comprehensive_tests`), missing aux modules (`../insurance.rs`),
// ANSI-escape-corrupted attributes, and rejected API references
// (`BatchDepositItem`, `BelowMinimumWithdrawal`). Not harvested for coverage.
// mod test_batch_deposit;
// mod test_insurance_fund;
// mod test_min_withdraw;
// mod test_min_withdrawal;
// mod test_user_apy;
// mod test_withdrawal_queue;
mod test_user_apy;
// `test_insurance_fund` and `test_withdrawal_queue` were fixed during the
// #985-988 batch's unrelated compile-break cleanup: wrong `crate::tests::`
// paths (lib.rs mounts this directory as `comprehensive_tests`, not `tests`),
// ANSI-escape-corrupted and `#`-less `#[test]` attributes, bare `assert_eq(`
// calls, and a stale `setup_vault()` call shape rewritten against the real
// fixture API in `utils.rs`. Both now test real, existing entry points.
mod test_insurance_fund;
mod test_withdrawal_queue;
mod test_pause_queue_breaker_matrix_proptest;
mod test_withdrawal_queue;

// `test_user_apy` and `test_min_withdraw`/`test_min_withdrawal` stay disabled:
// they call `get_user_deposit_timestamp`/`get_user_realized_apy` and
// `set_min_withdrawal`/`get_min_withdrawal`/`VaultError::BelowMinimumWithdrawal`,
// none of which exist anywhere in the contract. Orphaned tests for features
// that were never implemented, not harvested for coverage.
// mod test_user_apy;
// mod test_min_withdraw;
// mod test_min_withdrawal;
45 changes: 41 additions & 4 deletions neurowealth-vault/contracts/vault/src/tests/test_access_control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,18 @@

use super::utils::*;
use crate::{AgentUpdatedEvent, TOPIC_AGENT_UPDATED};
use soroban_sdk::{symbol_short, testutils::Address as _, Address, BytesN, Env, TryFromVal};
use soroban_sdk::{
symbol_short, testutils::Address as _, testutils::Ledger as _, Address, BytesN, Env, TryFromVal,
};

/// Keeps storage entries alive across the large ledger jumps these tests make.
fn keep_storage_alive(env: &Env) {
env.ledger().with_mut(|li| {
li.min_persistent_entry_ttl = 1_000_000;
li.min_temp_entry_ttl = 1_000_000;
li.max_entry_ttl = 2_000_000;
});
}

// ============================================================================
// OWNER-ONLY HAPPY PATHS
Expand Down Expand Up @@ -100,13 +111,31 @@ fn test_owner_can_set_deposit_limits() {
fn test_owner_can_update_agent() {
let env = Env::default();
env.mock_all_auths();
keep_storage_alive(&env);

let (contract_id, old_agent, _owner, _usdc_token) = setup_vault_with_token(&env);
let client = NeuroWealthVaultClient::new(&env, &contract_id);

// update_agent only PROPOSES the change (step 1 of 2, issue #317); the
// active agent is unchanged until confirm_agent_update() is called after
// the timelock elapses.
let new_agent = Address::generate(&env);
client.update_agent(&new_agent);

assert_eq!(
client.get_agent(),
old_agent,
"agent must not change until confirmed"
);
let (pending_addr, expiry) = client
.get_pending_agent_update()
.expect("pending agent update should be recorded");
assert_eq!(pending_addr, new_agent);

// Advance past the timelock and confirm.
env.ledger().set_sequence_number(expiry);
client.confirm_agent_update();

assert_eq!(client.get_agent(), new_agent);
assert_ne!(client.get_agent(), old_agent);
}
Expand All @@ -115,13 +144,21 @@ fn test_owner_can_update_agent() {
fn test_update_agent_emits_event() {
let env = Env::default();
env.mock_all_auths();
keep_storage_alive(&env);

let (contract_id, old_agent, _owner, _usdc_token) = setup_vault_with_token(&env);
let client = NeuroWealthVaultClient::new(&env, &contract_id);

// update_agent only proposes (step 1 of 2, issue #317); TOPIC_AGENT_UPDATED /
// AgentUpdatedEvent is only emitted (for backward compatibility) by
// confirm_agent_update() once the timelock has elapsed.
let new_agent = Address::generate(&env);
client.update_agent(&new_agent);

let (_, expiry) = client.get_pending_agent_update().unwrap();
env.ledger().set_sequence_number(expiry);
client.confirm_agent_update();

let agent_events = find_events_by_topic(env.events().all(), &env, TOPIC_AGENT_UPDATED);
assert_eq!(
agent_events.len(),
Expand Down Expand Up @@ -312,7 +349,7 @@ fn test_non_owner_cannot_set_approval_ttl() {
}

#[test]
#[should_panic(expected = "vault: only owner can set blend approval ttl")]
#[should_panic(expected = "Error(Contract, #34)")]
fn test_non_owner_cannot_set_blend_approval_ttl() {
let env = Env::default();
env.mock_all_auths();
Expand Down Expand Up @@ -421,7 +458,7 @@ fn test_agent_can_rebalance() {
let client = NeuroWealthVaultClient::new(&env, &contract_id);

// "none" protocol is always safe — no external pool required
client.rebalance(&symbol_short!("none"), &500_i128, &0_i128);
client.rebalance(&_agent, &symbol_short!("none"), &500_i128, &0_i128);

assert_eq!(
client.get_current_protocol(),
Expand Down Expand Up @@ -801,7 +838,7 @@ fn test_rebalance_blocked_while_paused() {
let client = NeuroWealthVaultClient::new(&env, &contract_id);

client.pause(&owner);
client.rebalance(&symbol_short!("none"), &500_i128, &0_i128);
client.rebalance(&_agent, &symbol_short!("none"), &500_i128, &0_i128);
}

// ============================================================================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,7 @@ fn test_agent_rebalance_mutates_only_allowlisted_fields() {
let client = NeuroWealthVaultClient::new(&env, &contract_id);
let before = snapshot_vault_state(&client, &[agent.clone()]);

client.rebalance(&symbol_short!("none"), &500_i128, &0_i128);
client.rebalance(&agent, &symbol_short!("none"), &500_i128, &0_i128);

let after = snapshot_vault_state(&client, &[agent.clone()]);
let changed = diff_vault_state(&before, &after);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -971,7 +971,7 @@ fn test_agent_rebalance_cannot_touch_user_shares() {
let client = NeuroWealthVaultClient::new(&env, &contract_id);
let victim_shares = client.get_shares(&victim);

client.rebalance(&symbol_short!("none"), &500_i128, &0_i128);
client.rebalance(&agent, &symbol_short!("none"), &500_i128, &0_i128);

assert_eq!(client.get_shares(&victim), victim_shares);
assert_eq!(client.get_owner(), owner);
Expand All @@ -984,7 +984,7 @@ fn test_agent_submit_mev_report_cannot_steal() {
let victim_shares = client.get_shares(&victim);
let assets = client.get_total_assets();

client.submit_mev_report(&symbol_short!("blend"), &1_i128, &0_i128);
client.submit_mev_report(&_agent, &symbol_short!("blend"), &1_i128, &0_i128);

assert_eq!(client.get_shares(&victim), victim_shares);
assert_eq!(client.get_total_assets(), assets);
Expand All @@ -996,15 +996,18 @@ fn test_agent_submit_apy_prediction_cannot_steal() {
let client = NeuroWealthVaultClient::new(&env, &contract_id);
let victim_shares = client.get_shares(&victim);

client.submit_apy_prediction(&ApyPrediction {
protocol: symbol_short!("blend"),
predicted_apy_bps: 800,
apy_1h_bps: 800,
apy_6h_bps: 800,
apy_24h_bps: 800,
confidence_bps: 9_000,
submitted_at_ledger: 0,
});
client.submit_apy_prediction(
&_agent,
&ApyPrediction {
protocol: symbol_short!("blend"),
predicted_apy_bps: 800,
apy_1h_bps: 800,
apy_6h_bps: 800,
apy_24h_bps: 800,
confidence_bps: 9_000,
submitted_at_ledger: 0,
},
);

assert_eq!(client.get_shares(&victim), victim_shares);
}
Expand Down Expand Up @@ -1033,7 +1036,7 @@ fn test_random_user_cannot_impersonate_agent_rebalance() {
(symbol_short!("none"), 500_i128, 0_i128).into_val(&env),
);
let accepted = client
.try_rebalance(&symbol_short!("none"), &500_i128, &0_i128)
.try_rebalance(&victim, &symbol_short!("none"), &500_i128, &0_i128)
.is_ok();
env.mock_all_auths();
assert!(!accepted, "non-agent must not rebalance");
Expand Down
13 changes: 13 additions & 0 deletions neurowealth-vault/contracts/vault/src/tests/test_agent_timelock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@
use super::utils::*;
use soroban_sdk::{testutils::Address as _, testutils::Ledger as _, Address, Env};

/// Keeps storage entries alive across the large ledger jumps these tests make.
fn keep_storage_alive(env: &Env) {
env.ledger().with_mut(|li| {
li.min_persistent_entry_ttl = 1_000_000;
li.min_temp_entry_ttl = 1_000_000;
li.max_entry_ttl = 2_000_000;
});
}

/// A successful propose stores the pending agent and emits the proposal event.
#[test]
fn test_propose_agent_stores_pending_and_emits_event() {
Expand Down Expand Up @@ -79,6 +88,7 @@ fn test_confirm_before_timelock_rejected() {
fn test_confirm_after_timelock_applies_update() {
let env = Env::default();
env.mock_all_auths();
keep_storage_alive(&env);

let (contract_id, _old_agent, _owner, _usdc_token) = setup_vault_with_token(&env);
let client = NeuroWealthVaultClient::new(&env, &contract_id);
Expand Down Expand Up @@ -109,6 +119,7 @@ fn test_confirm_after_timelock_applies_update() {
fn test_new_proposal_allowed_after_confirm() {
let env = Env::default();
env.mock_all_auths();
keep_storage_alive(&env);

let (contract_id, _agent, _owner, _usdc_token) = setup_vault_with_token(&env);
let client = NeuroWealthVaultClient::new(&env, &contract_id);
Expand Down Expand Up @@ -221,6 +232,7 @@ fn test_get_pending_agent_update_none_initially() {
fn test_confirm_agent_update_long_after_expiry_succeeds() {
let env = Env::default();
env.mock_all_auths();
keep_storage_alive(&env);

let (contract_id, _old_agent, _owner, _usdc_token) = setup_vault_with_token(&env);
let client = NeuroWealthVaultClient::new(&env, &contract_id);
Expand Down Expand Up @@ -319,6 +331,7 @@ fn test_confirm_after_cancel_rejected() {
fn test_ownership_transfer_preserves_pending_agent_update() {
let env = Env::default();
env.mock_all_auths();
keep_storage_alive(&env);

let (contract_id, old_agent, _owner, _usdc_token) = setup_vault_with_token(&env);
let client = NeuroWealthVaultClient::new(&env, &contract_id);
Expand Down
Loading