Skip to content

Commit 9fb03be

Browse files
committed
vault-strategy: raise MAX_ASSETS to 16
Bump the basket cap from 8 to 16 (used only in add_asset's TooManyAssets guard; assets are separate PDAs, so no layout impact). Update the worked account-count numbers in the MAX_ASSETS comment and README: at 16 assets deposit references 94 accounts and withdraw 74, still within the 128-account transaction lock limit but past the 1232-byte legacy size, so large baskets need a v0 transaction with an Address Lookup Table. Add test_add_asset_enforces_max_assets: fill a strategy to 16 assets (625 bps each) and assert the 17th reverts with TooManyAssets. 20 tests pass.
1 parent 293d5b4 commit 9fb03be

3 files changed

Lines changed: 56 additions & 4 deletions

File tree

finance/vault-strategy/anchor/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ A note on the word **vault**: by the common standard (ERC-4626) a vault holds a
2525

2626
Because the asset set is dynamic, `deposit` must value *every* asset. The assets live at PDAs indexed `0..asset_count`, and `deposit` re-derives that complete range from the accounts it is given, refusing to run if any asset is missing (`IncompleteAssetAccounts`). This makes it structurally impossible to omit an asset and understate NAV.
2727

28-
Referencing every asset has a transaction-size cost: `deposit` pulls in `14 + 5N` accounts and `withdraw` `10 + 4N`, where `N` is the asset count. That stays within Solana's 128-account transaction lock limit at the `MAX_ASSETS` cap of 8 (54 accounts for `deposit`), but a basket beyond roughly three assets no longer fits a legacy transaction's 1232-byte limit, so the client must send a v0 transaction with an [Address Lookup Table](https://docs.anza.xyz/proposals/versioned-transactions).
28+
Referencing every asset has a transaction-size cost: `deposit` pulls in `14 + 5N` accounts and `withdraw` `10 + 4N`, where `N` is the asset count. That stays within Solana's 128-account transaction lock limit at the `MAX_ASSETS` cap of 16 (94 accounts for `deposit`), but a basket beyond roughly three assets no longer fits a legacy transaction's 1232-byte limit, so the client must send a v0 transaction with an [Address Lookup Table](https://docs.anza.xyz/proposals/versioned-transactions).
2929

3030
Prices come from [Pyth Network](https://pyth.network/) `PriceUpdateV2` accounts. A 60-second staleness window is enforced; zero or negative prices are rejected.
3131

finance/vault-strategy/anchor/programs/vault-strategy/src/state/strategy.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ use anchor_lang::prelude::*;
44
/// (each asset is its own account); the cap bounds how many accounts deposit and
55
/// withdraw, which must reference every asset at once, pull into a single
66
/// instruction: deposit uses 14 + 5*N accounts and withdraw 10 + 4*N, where N is the
7-
/// asset count. At the cap of 8 that is 54 accounts for deposit, comfortably within
8-
/// Solana's 128-account transaction lock limit but past the 1232-byte legacy
7+
/// asset count. At the cap of 16 that is 94 accounts for deposit (74 for withdraw),
8+
/// within Solana's 128-account transaction lock limit but past the 1232-byte legacy
99
/// transaction size (which fits only ~3 assets), so a client depositing into a large
1010
/// basket must send a v0 transaction with an Address Lookup Table. USDC is the base
1111
/// currency, held separately, and does not count against this.
12-
pub const MAX_ASSETS: u8 = 8;
12+
pub const MAX_ASSETS: u8 = 16;
1313

1414
/// One strategy (basket). Its address is a PDA seeded by a caller-chosen index,
1515
/// e.g. seeds `"strategy" + 0`, so strategies are addressed by a simple counter

finance/vault-strategy/anchor/programs/vault-strategy/tests/vault_strategy.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,58 @@ fn test_add_asset_rejects_weight_overflow() {
755755
assert!(result.is_err(), "weights over 10000 bps must fail");
756756
}
757757

758+
/// Create a fresh mint and whitelist it in the registry. The bound price feed is an
759+
/// arbitrary pubkey: callers that never value this asset (e.g. the cap boundary test)
760+
/// do not need a real feed account.
761+
fn create_and_whitelist_mint(ctx: &mut TestContext) -> (Pubkey, Pubkey) {
762+
let mint = create_token_mint(&mut ctx.svm, &ctx.payer, TOKEN_DECIMALS, None).unwrap();
763+
let (entry, _) = Pubkey::find_program_address(
764+
&[b"whitelist", ctx.registry_pda.as_ref(), mint.as_ref()],
765+
&ctx.vault_program_id,
766+
);
767+
let ix = Instruction::new_with_bytes(
768+
ctx.vault_program_id,
769+
&vault_strategy::instruction::WhitelistAsset {
770+
price_feed: Keypair::new().pubkey(),
771+
}
772+
.data(),
773+
vault_strategy::accounts::WhitelistAssetAccountConstraints {
774+
authority: ctx.payer.pubkey(),
775+
registry: ctx.registry_pda,
776+
asset_mint: mint,
777+
whitelist_entry: entry,
778+
system_program: system_program::id(),
779+
}
780+
.to_account_metas(None),
781+
);
782+
send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[&ctx.payer], &ctx.payer.pubkey())
783+
.unwrap();
784+
(mint, entry)
785+
}
786+
787+
#[test]
788+
fn test_add_asset_enforces_max_assets() {
789+
let mut ctx = setup_full();
790+
let router = ctx.router_program_id;
791+
init_strategy(&mut ctx, FEE_BPS, SLIPPAGE_BPS, router);
792+
793+
// Fill the basket to the cap: 16 assets at 625 bps each = 10000.
794+
for index in 0..16u8 {
795+
let (mint, entry) = create_and_whitelist_mint(&mut ctx);
796+
let vault = derive_ata(&ctx.strategy_pda, &mint);
797+
add_asset(&mut ctx, index, mint, entry, vault, 625).unwrap();
798+
}
799+
let strategy = read_strategy(&ctx);
800+
assert_eq!(strategy.asset_count, 16);
801+
assert_eq!(strategy.total_weight_bps, 10_000);
802+
803+
// The 17th asset must be rejected. Weight 0 so the cap, not the weight sum, trips.
804+
let (mint, entry) = create_and_whitelist_mint(&mut ctx);
805+
let vault = derive_ata(&ctx.strategy_pda, &mint);
806+
let result = add_asset(&mut ctx, 16, mint, entry, vault, 0);
807+
assert!(result.is_err(), "adding beyond MAX_ASSETS must revert");
808+
}
809+
758810
#[test]
759811
fn test_initialize_rejects_excessive_fee() {
760812
let mut ctx = setup_full();

0 commit comments

Comments
 (0)