Skip to content
Open
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: 6 additions & 0 deletions finance/managed-fund/anchor-v1/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased (2026-11-10)

### Added

- **Tests for the price-feed and recipient checks.** No test triggered `InvalidPriceFeed`, `NegativePrice`, `StalePriceFeed` or `InvalidRecipient`. Each new test asserts the error and that no tokens or shares moved: `test_wrong_price_feed_rejected` passes TSLAx's fresh, fully verified feed in NVDAx's place and checks that a deposit and a rebalance both fail with `InvalidPriceFeed`, then that NVDAx's own feed gives `test_rebalance`'s trade; `test_truncated_price_feed_rejected` writes NVDAx's price account cut off at 125 bytes, before its `posted_slot`, and checks `InvalidPriceFeed`; `test_non_positive_price_rejected` checks that a price of zero and one of −$180 both fail with `NegativePrice`, and that at $180 again Bob's 480 USDC mints 480,000,000 shares; `test_stale_price_rejected` checks that a price 61 seconds old fails with `StalePriceFeed` and one exactly 60 seconds old is accepted; `test_withdraw_rejects_recipient_owned_by_someone_else` checks that Alice naming Bob's TSLAx account fails with `InvalidRecipient`, and `test_withdraw_rejects_recipient_for_another_mint` that naming her NVDAx account for her TSLAx does too. The tests gain `write_price_feed_account`, `withdraw_remaining_to`, `withdraw_instruction` and `balances` helpers. The program is unchanged.

## [Unreleased] - 2026-10-05

### Changed
Expand Down
2 changes: 1 addition & 1 deletion finance/managed-fund/anchor-v1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ cargo build-sbf --manifest-path programs/managed-fund/Cargo.toml
cargo test --manifest-path programs/managed-fund/Cargo.toml
```

Tests live in `programs/managed-fund/tests/managed_fund.rs` and use [LiteSVM](https://github.com/LiteSVM/litesvm). Both `.so` files are loaded from `target/deploy/`, so build before testing. TSLAx and NVDAx are minted with eight decimals, as the real tokens are, and USDC with six, so the basket amounts the tests assert are in eight-decimal minor units while shares and USDC stay in six. The suite covers the full lifecycle end to end (deposit with auto-deployment, a price move, rebalance back to target, a second depositor priced at the new NAV, a year's fee, in-kind withdrawal), retiring an asset with `set_weight` and reallocating to reopen deposits, and the rejection paths, each asserting the error code it fails with: unapproved asset, weight overflow, over-cap fee and slippage, oracle-bounded deposit slippage (the router's `SlippageExceeded`, raised inside the swap CPI), an under-allocated fund, non-manager `set_weight` (Anchor's own constraint error), unregistered router, and incomplete asset accounts on deposit and rebalance. The rebalance tests sign as a stranger, since anyone may call it: `test_rebalance_refuses_fund_at_target`, `test_rebalance_refuses_drift_below_threshold` and `test_rebalance_cannot_churn` check that a fund at its targets, or within its threshold, or just rebalanced, cannot be traded; `test_rebalance_refuses_buying_overweight_asset`, `test_rebalance_sells_retired_asset` and `test_initialize_rejects_threshold_out_of_range` cover the rest of its rules. `test_valuation_scales_by_decimals_and_exponent` runs the story with an eight-decimal TSLAx priced by an exponent −5 feed and gets the same share counts, and `test_valuation_scales_by_nine_decimals_and_exponent` does the same with TSLAx at nine decimals. `test_collect_fees` checks a year's fee comes out exact and `test_collect_fees_rounds_up` that a day's fee rounds up to the manager. `test_wide_confidence_price_rejected` widens NVDAx's confidence interval to 2% of its price and checks that deposit and rebalance fail with `OracleConfidenceTooWide`, that withdraw still pays out in kind, and that a band of exactly 1% is accepted. `test_partially_verified_price_rejected` writes NVDAx's feed as a `Partial` update signed by two signers and checks that a deposit fails with `PriceNotFullyVerified`, then rewrites it as `Full` at the same price and checks the deposit prices exactly as `test_deposit_first` does. `test_full_lifecycle` checks after every step that the recorded holdings equal the vaults' balances. `test_donation_does_not_inflate_share_price` runs the first-depositor attack (a one-minor-unit deposit, a 1,000 USDC transfer straight into the USDC vault, then a 1,000 USDC deposit with no `minimum_shares` floor) and checks the victim gets exactly the shares they would have got without the donation. `test_deposit_rejects_leg_that_buys_nothing` and `test_rebalance_ignores_donations` pin the other two guards: the second checks that donated tokens can neither force a rebalance nor be spent by one.
Tests live in `programs/managed-fund/tests/managed_fund.rs` and use [LiteSVM](https://github.com/LiteSVM/litesvm). Both `.so` files are loaded from `target/deploy/`, so build before testing. TSLAx and NVDAx are minted with eight decimals, as the real tokens are, and USDC with six, so the basket amounts the tests assert are in eight-decimal minor units while shares and USDC stay in six. The suite covers the full lifecycle end to end (deposit with auto-deployment, a price move, rebalance back to target, a second depositor priced at the new NAV, a year's fee, in-kind withdrawal), retiring an asset with `set_weight` and reallocating to reopen deposits, and the rejection paths, each asserting the error code it fails with: unapproved asset, weight overflow, over-cap fee and slippage, oracle-bounded deposit slippage (the router's `SlippageExceeded`, raised inside the swap CPI), an under-allocated fund, non-manager `set_weight` (Anchor's own constraint error), unregistered router, and incomplete asset accounts on deposit and rebalance. The rebalance tests sign as a stranger, since anyone may call it: `test_rebalance_refuses_fund_at_target`, `test_rebalance_refuses_drift_below_threshold` and `test_rebalance_cannot_churn` check that a fund at its targets, or within its threshold, or just rebalanced, cannot be traded; `test_rebalance_refuses_buying_overweight_asset`, `test_rebalance_sells_retired_asset` and `test_initialize_rejects_threshold_out_of_range` cover the rest of its rules. `test_valuation_scales_by_decimals_and_exponent` runs the story with an eight-decimal TSLAx priced by an exponent −5 feed and gets the same share counts, and `test_valuation_scales_by_nine_decimals_and_exponent` does the same with TSLAx at nine decimals. `test_collect_fees` checks a year's fee comes out exact and `test_collect_fees_rounds_up` that a day's fee rounds up to the manager. `test_wide_confidence_price_rejected` widens NVDAx's confidence interval to 2% of its price and checks that deposit and rebalance fail with `OracleConfidenceTooWide`, that withdraw still pays out in kind, and that a band of exactly 1% is accepted. `test_partially_verified_price_rejected` writes NVDAx's feed as a `Partial` update signed by two signers and checks that a deposit fails with `PriceNotFullyVerified`, then rewrites it as `Full` at the same price and checks the deposit prices exactly as `test_deposit_first` does. `test_wrong_price_feed_rejected` passes TSLAx's feed in NVDAx's place and checks that deposit and rebalance fail with `InvalidPriceFeed`, as `test_truncated_price_feed_rejected` does for a price account cut off before its `posted_slot`; `test_non_positive_price_rejected` checks that a price of zero or below fails with `NegativePrice`, and `test_stale_price_rejected` that a price 61 seconds old fails with `StalePriceFeed` while one exactly 60 seconds old is accepted. `test_withdraw_rejects_recipient_owned_by_someone_else` and `test_withdraw_rejects_recipient_for_another_mint` check that a withdrawal naming another wallet's token account, or an account for another asset, fails with `InvalidRecipient`. Each of these checks that no tokens or shares moved. `test_full_lifecycle` checks after every step that the recorded holdings equal the vaults' balances. `test_donation_does_not_inflate_share_price` runs the first-depositor attack (a one-minor-unit deposit, a 1,000 USDC transfer straight into the USDC vault, then a 1,000 USDC deposit with no `minimum_shares` floor) and checks the victim gets exactly the shares they would have got without the donation. `test_deposit_rejects_leg_that_buys_nothing` and `test_rebalance_ignores_donations` pin the other two guards: the second checks that donated tokens can neither force a rebalance nor be spent by one.

## FAQ

Expand Down
Loading
Loading