Security
The integration suite is the executable specification. The invariants below are tested against a real validator; any change that breaks them must update both the test and the program in the same commit.
Invariants
- Single-author math — every reserve, fee accumulator and share calculation uses
checked_*arithmetic onu128intermediates. - Fee isolation — a bloom that enters at slot
Scannot collect any fees from swaps that completed at slot< S. - Permissionless settle —
settle_bloomafterend_slotsucceeds for any caller, but funds always go to the bloom's owner. - Owner-only chirigiwa —
chirigiwarequiresbloom.owner == ctx.accounts.user.key(). - No double settle —
bloom.settledis set on first settle; second call returnsAlreadySettled. - Penalty stays in pool —
chirigiwareducespool.reserve_*byshare_* - penalty_*, leaving the penalty in the vault for remaining LPs.
Account constraints
Every Anchor accounts struct uses explicit constraint = ... clauses for:
#[account(
mut,
constraint = vault_a.key() == pool.vault_a @ HanamiError::InvalidVault,
)]
pub vault_a: Box<Account<'info, TokenAccount>>,
#[account(
mut,
constraint = user_token_a.mint == pool.token_a_mint @ HanamiError::InvalidMint,
constraint = user_token_a.owner == user.key() @ HanamiError::Unauthorized,
)]
pub user_token_a: Box<Account<'info, TokenAccount>>,
#[account(
mut,
constraint = bloom.owner == user.key() @ HanamiError::Unauthorized,
constraint = bloom.pool == pool.key() @ HanamiError::PoolMismatch,
)]
pub bloom: Box<Account<'info, BloomPosition>>,