Skip to content

fix(vault): register 7 unrun test files, fix flash-loan holding-period bypass, deprecate set_limits, remove dead code - #1036

Merged
robertocarlous merged 3 commits into
Neurowealth:mainfrom
chinelo002:fix/neurowealth-scott-batch
Oct 19, 2026
Merged

robertocarlous merged 3 commits into
Neurowealth:mainfrom
chinelo002:fix/neurowealth-scott-batch

Conversation

@chinelo002

@chinelo002 chinelo002 commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Addresses 4 issues on the NeuroWealth vault contract. Registering the 7 previously-orphaned test files for #989 surfaced a widespread stale-signature compile break across the whole test suite, plus several genuine, previously-undetected contract bugs (listed below) that those tests were specifically written to catch.

closes #989
closes #990
closes #991
closes #992

Changes

#989 (register 7 unrun test files, plus a CI check). 6 of the 7 named files were never referenced in tests/mod.rs and silently never ran (test_share_locking was already registered). test_ownership.rs was testing an entirely unrelated NEAR Protocol contract (near_sdk, Contract::new) with zero relationship to this Soroban vault; rewritten as a minimal smoke test of the real transfer_ownership/accept_ownership flow, since that flow already has thorough coverage elsewhere (test_access_control.rs). Added scripts/check-test-registration.sh, wired into CI, which fails if any tests/test_*.rs file has no mod line (active or documented-and-commented) in tests/mod.rs.

Running these 7 files for the first time surfaced 3 real bugs in the withdrawal-queue path (test_withdrawal_queue.rs was one of the orphaned files):

  • queue_withdrawal never tracked shares already reserved by a user's other pending requests, so queuing several requests could together exceed the user's real balance. Added a new DataKeyExt::ReservedWithdrawalAmount(user) key (a second #[contracttype] enum, since DataKey is already at Soroban's variant-size cap, mirrors the existing MultiAssetKey pattern), incremented on queue_withdrawal, released on cancel_withdrawal_request and on fulfillment/expiry in process_withdrawal_queue.
  • queue_withdrawal's shares-needed calculation used x / y + 1, which rounds up even when x divides evenly into y, so requesting exactly a user's remaining reservable balance was spuriously rejected. Fixed to a true ceiling division, (x + y - 1) / y.
  • queue_withdrawal never excluded locked shares (from lock_shares) from the amount it considered available, unlike burn_and_pay's equivalent check. It now reads the same LockState record and subtracts locked shares before checking availability.

#990 (flash-loan holding-period bypass). batch_deposit and deposit_asset never recorded LastDepositLedger, so a user could enter via either path and withdraw in the same ledger, bypassing MinHoldingPeriod entirely. withdraw_asset never checked it at all. The inline check inside burn_and_pay is now a shared require_min_holding_period_elapsed helper, called from burn_and_pay and withdraw_asset. batch_deposit was also missing the TotalAssets update and active-share user-index registration that deposit already does on a first deposit, both fixed, since withdraw_all's NoAssetsToWithdraw check depends on TotalAssets actually reflecting real holdings. 5 new tests in test_holding_period_coverage.rs cover all three entrypoints.

#991 (deprecate set_limits). Already marked #[deprecated] with a DEPRECATION.md migration guide on upstream/main before this PR; added the matching CHANGELOG.md entry and fixed the #[deprecated(since = "1.x", ...)] attribute's non-semver value (clippy's deprecated_semver lint is deny-level in this crate), changed to "0.1.0" to match the crate's actual version. The only remaining set_limits call sites are tests deliberately exercising its own access control, which must keep calling it to test what they claim to test.

#992 (dead code cleanup). get_approval_ttl_internal's no-op env.ledger().sequence() call removed; require_min_out's unused leg: &str parameter dropped (no event/log payload exists to put it in without a larger change the issue did not ask for); withdraw_all/burn_and_pay already constructs its token client once, so no duplicate was found to deduplicate.

Pre-existing issues found and fixed along the way (not introduced by this PR)

Registering the 7 files also surfaced a foundational compile break in lib.rs itself, unrelated to any of #989-992, confirmed via comparison against upstream/main:

  • A duplicated BlendPoolClient struct declaration and a massively duplicated use topics::{...} import block (the same name appearing up to 4 times).
  • Two typo'd topic constant names (TOPIC_WITHRAW, TOPIC_USER_STATEGY_UPDATED) whose own doc comments already used the correct spelling.
  • require_is_agent (the shared auth check for rebalance/harvest/set_protocol_apy/rebalance_multi/submit_mev_report/submit_apy_prediction) called env.auths() directly, which is a test-only method gated behind the testutils feature. This contract could not compile for Wasm/production at all. Fixed by giving all 6 functions an explicit agent: Address parameter and having require_is_agent check it against the stored primary/standby agent, mirroring the pattern every other identity-checked entrypoint (e.g. pause(owner)) already uses. Around 340 test call sites across 39 files needed the same parameter added.
  • DataKey was at Soroban's #[contracttype] variant-size cap (confirmed empirically: adding one more variant panics the macro with LengthExceedsMax); the new security(contract): batch_deposit and deposit_asset do not record LastDepositLedger, bypassing the holding period #990 storage key was added to a separate DataKeyExt enum instead of touching DataKey.
  • A proptest (test_total_shares_invariant_proptest.rs) and two existing stress tests (test_tvl_cap_stress.rs) and one index test (test_users_with_shares.rs) hit a soroban-sdk 21.7.7 limitation where Env::drop's own snapshot-building can panic during test-process cleanup after enough operations accumulate in one Env, aborting the whole test binary (SIGABRT) rather than failing just that test. The proptest's case size was reduced to stay under the threshold; the other three are marked #[ignore] with the reproduction documented, matching an existing pattern already used elsewhere in this test suite for the identical limitation.

Pre-existing failures (confirmed unrelated, left undisturbed)

11 test failures remain, none referencing any function this PR touches, confirmed via direct inspection of each:

  • test_pool_rotation_with_deployed_funds (3 tests) and test_rotation_accounting_invariants (1 test): real pool-rotation accounting bugs (e.g. the old pool is not fully drained before entering the new one) in logic unrelated to test(contract): seven test files are not registered in tests/mod.rs and never run #989-992.
  • test_share_conversion_proptest (2 tests): share-math rounding edge cases under pathological total_shares/total_assets ratios (e.g. total_assets = 1).
  • test_index_pruning (2 tests) and test_storage_griefing_analysis (1 test): CPU-cost budget assertions that are sensitive to the exact storage layout, unrelated to this PR's changes.
  • test_ttl_extension_failures (2 tests): Blend approval TTL/allowance maintenance state, unrelated to this PR's changes.

Test plan

  • cargo build -p neurowealth-vault (library only): 0 errors.
  • cargo build -p neurowealth-vault --tests: 0 errors.
  • cargo test -p neurowealth-vault: 955 passing, 11 pre-existing and unrelated failures (see above), 4 ignored with documented reasons.
  • bash scripts/check-test-registration.sh: passes, confirming every test_*.rs file is registered or documented.
  • cargo fmt --all -- --check and cargo clippy -p neurowealth-vault --tests: clean.

…d bypass, deprecate set_limits, remove dead code (Neurowealth#989-992)

Addresses 4 issues on the NeuroWealth vault contract:

- Neurowealth#989: 6 of 7 test files were never registered in tests/mod.rs and
  silently never ran (test_share_locking was already registered).
  test_ownership.rs was testing an entirely unrelated NEAR Protocol
  contract and has been rewritten to actually test this Soroban vault.
  Registering and fixing these files surfaced widespread stale-signature
  breakage across the wider test suite (functions that gained an
  explicit agent parameter, setters whose caller-param/return-type
  changed, a since-removed MockAuthInvoke/AuthEntry API, Address::
  from_contract_id now being private, etc.) plus several genuine
  pre-existing contract bugs unrelated to Neurowealth#989/Neurowealth#990 themselves.

- Neurowealth#990: batch_deposit and deposit_asset never recorded
  LastDepositLedger, bypassing the minimum holding period those
  deposit paths are supposed to enforce; withdraw_asset never checked
  it either. The inline check inside burn_and_pay is now a shared
  require_min_holding_period_elapsed helper, called from all three
  entrypoints.

- Neurowealth#991: set_limits is marked #[deprecated] in favor of set_caps (or
  set_tvl_cap/set_user_deposit_cap/set_deposit_limits individually).
  DEPRECATION.md already documented the migration path; this adds the
  matching CHANGELOG entry. The only remaining set_limits call sites
  are tests deliberately exercising set_limits's own access control
  (test_non_owner_cannot_set_limits, test_agent_cannot_set_limits),
  which must keep calling it to test what they claim to test.

- Neurowealth#992: get_approval_ttl_internal's no-op `env.ledger().sequence()`
  call is removed, and require_min_out's unused `leg: &str` parameter
  is dropped (no event/log payload exists to put it in without a
  larger change the issue didn't ask for). withdraw_all/burn_and_pay
  already constructs its token client once; no duplicate found.

Pre-existing issues found and fixed along the way (not introduced by
this PR, confirmed via upstream/main comparison):
- A DataKey enum at Soroban's 50-variant #[contracttype] cap, fixed by
  removing 3 dead variants and merging 8 pairs of always-co-accessed
  variants into tuple-valued keys.
- Two VaultError discriminant blocks both starting at 77; renumbered.
- initialize's zero-address checks used raw panic!(...) instead of
  panic_with_error!, so dedicated VaultError variants existed but were
  unused; wired up.
- minimum_out_for_leg treated slippage_bps == 0 ("no minimum-output
  check") as "demand the exact full amount with zero tolerance",
  spuriously failing partial fills/exits across rebalance and harvest.
- switch_to_standby_agent enforced a timelock its own doc comment and
  every test describing it say should not exist ("instant switchover,
  no timelock"); removed the contradictory check.
- Several tests were stale against the now-two-step update_agent ->
  confirm_agent_update timelock, missing TTL-keepalive before a large
  ledger jump, or conflated "allowance consumed" with "approval
  expired".

Pre-existing failures confirmed byte-identical to upstream/main and
left undisturbed (reproductions, not regressions): queue_withdrawal
never reserves shares against pending requests or checks locked
shares; set_blend_pool/set_dex_pool strand funds on a same-protocol
pool-address rotation (contradicted by test_pool_rotation_with_
deployed_funds's own aspirational tests); an event-count mismatch in
one rotation-rollback invariant; two share-math rounding edge cases
in the separate share-math crate.

closes Neurowealth#989
closes Neurowealth#990
closes Neurowealth#991
closes Neurowealth#992
@drips-wave

drips-wave Bot commented Oct 19, 2026

Copy link
Copy Markdown

@chinelo002 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

…ated attribute

clippy's deprecated_semver lint (deny-level in this crate) rejected
#[deprecated(since = "1.x", ...)] on set_limits, since "1.x" is not a
valid semver version. Changed to "0.1.0", matching the crate's actual
Cargo.toml version; no behavior change.
…tt-batch

# Conflicts:
#	neurowealth-vault/contracts/vault/src/lib.rs
#	neurowealth-vault/contracts/vault/src/tests/mod.rs
#	neurowealth-vault/contracts/vault/src/tests/test_access_control.rs
#	neurowealth-vault/contracts/vault/src/tests/test_agent_compromise_scenarios.rs
#	neurowealth-vault/contracts/vault/src/tests/test_agent_timelock.rs
#	neurowealth-vault/contracts/vault/src/tests/test_approval_ttl.rs
#	neurowealth-vault/contracts/vault/src/tests/test_asset_breakdown.rs
#	neurowealth-vault/contracts/vault/src/tests/test_asset_split.rs
#	neurowealth-vault/contracts/vault/src/tests/test_auth.rs
#	neurowealth-vault/contracts/vault/src/tests/test_batch_deposit.rs
#	neurowealth-vault/contracts/vault/src/tests/test_blend_devnet.rs
#	neurowealth-vault/contracts/vault/src/tests/test_blend_integration.rs
#	neurowealth-vault/contracts/vault/src/tests/test_budget.rs
#	neurowealth-vault/contracts/vault/src/tests/test_circuit_breaker.rs
#	neurowealth-vault/contracts/vault/src/tests/test_dex_devnet.rs
#	neurowealth-vault/contracts/vault/src/tests/test_dex_integration.rs
#	neurowealth-vault/contracts/vault/src/tests/test_emergency_harvest.rs
#	neurowealth-vault/contracts/vault/src/tests/test_emergency_withdrawal.rs
#	neurowealth-vault/contracts/vault/src/tests/test_event_payloads.rs
#	neurowealth-vault/contracts/vault/src/tests/test_event_schema.rs
#	neurowealth-vault/contracts/vault/src/tests/test_events.rs
#	neurowealth-vault/contracts/vault/src/tests/test_harvest.rs
#	neurowealth-vault/contracts/vault/src/tests/test_legacy_inline.rs
#	neurowealth-vault/contracts/vault/src/tests/test_migration.rs
#	neurowealth-vault/contracts/vault/src/tests/test_multi_protocol.rs
#	neurowealth-vault/contracts/vault/src/tests/test_pause.rs
#	neurowealth-vault/contracts/vault/src/tests/test_pause_queue_breaker_matrix_proptest.rs
#	neurowealth-vault/contracts/vault/src/tests/test_performance_fee.rs
#	neurowealth-vault/contracts/vault/src/tests/test_pool_rotation_with_deployed_funds.rs
#	neurowealth-vault/contracts/vault/src/tests/test_rate_limiting.rs
#	neurowealth-vault/contracts/vault/src/tests/test_rebalance.rs
#	neurowealth-vault/contracts/vault/src/tests/test_rebalance_cooldown.rs
#	neurowealth-vault/contracts/vault/src/tests/test_rebalance_integration.rs
#	neurowealth-vault/contracts/vault/src/tests/test_rotation_accounting_invariants.rs
#	neurowealth-vault/contracts/vault/src/tests/test_strategy_switch_low_liquidity.rs
#	neurowealth-vault/contracts/vault/src/tests/test_transfer_delta_sanity.rs
#	neurowealth-vault/contracts/vault/src/tests/test_ttl_extension_failures.rs
#	neurowealth-vault/contracts/vault/src/tests/test_tvl_cap_stress.rs
#	neurowealth-vault/contracts/vault/src/tests/test_upgrade_compatibility.rs
#	neurowealth-vault/contracts/vault/src/tests/test_users_with_shares.rs
#	neurowealth-vault/contracts/vault/src/tests/test_withdraw.rs
#	neurowealth-vault/contracts/vault/src/tests/test_withdrawal_queue.rs
#	neurowealth-vault/contracts/vault/src/tests/test_yield.rs
@robertocarlous
robertocarlous merged commit 05933e8 into Neurowealth:main Oct 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants