From 042fd97461a9909e468f6dad5f9ffaf4f6f35507 Mon Sep 17 00:00:00 2001 From: Mike MacCana Date: Wed, 30 Sep 2026 22:58:06 +0000 Subject: [PATCH] Options: rename underlying_locked and quote_locked to underlying_owed and quote_owed Each field counts what the market's vault owes: collateral to writers, and settlement proceeds waiting for a writer or holder to collect them. "Locked" did not say to whom, and could read as a lock in the file-locking sense. The Quasar helpers add_locked and sub_locked become add_owed and sub_owed, and the comments and test messages that said tokens were "locked to" someone now say "owed to". The account layout is unchanged; the IDL field names change. From quicknode/solana-book#190. Claude-Session: https://claude.ai/code/session_01JGEoAUjMm7Evv69k46eNcn --- finance/options/anchor-v1/CHANGELOG.md | 7 ++++ finance/options/anchor-v1/README.md | 4 +-- .../options/src/instructions/buy_option.rs | 2 +- .../options/src/instructions/cancel_option.rs | 8 ++--- .../options/src/instructions/collect_fees.rs | 2 +- .../src/instructions/collect_proceeds.rs | 8 ++--- .../src/instructions/exercise_option.rs | 16 ++++----- .../src/instructions/initialize_market.rs | 4 +-- .../src/instructions/reclaim_collateral.rs | 8 ++--- .../options/src/instructions/shared.rs | 4 +-- .../options/src/instructions/write_option.rs | 8 ++--- .../programs/options/src/state/market.rs | 4 +-- .../programs/options/tests/test_options.rs | 34 +++++++++---------- finance/options/anchor/CHANGELOG.md | 7 ++++ finance/options/anchor/README.md | 6 ++-- .../options/src/instructions/buy_option.rs | 2 +- .../options/src/instructions/cancel_option.rs | 8 ++--- .../options/src/instructions/collect_fees.rs | 2 +- .../src/instructions/collect_proceeds.rs | 8 ++--- .../src/instructions/exercise_option.rs | 16 ++++----- .../src/instructions/initialize_market.rs | 4 +-- .../src/instructions/reclaim_collateral.rs | 8 ++--- .../options/src/instructions/shared.rs | 4 +-- .../options/src/instructions/write_option.rs | 8 ++--- .../programs/options/src/state/market.rs | 4 +-- .../programs/options/tests/test_options.rs | 34 +++++++++---------- finance/options/kani-proofs/README.md | 4 +-- finance/options/kani-proofs/src/lib.rs | 32 ++++++++--------- finance/options/quasar/CHANGELOG.md | 8 +++++ .../quasar/src/instructions/buy_option.rs | 6 ++-- .../quasar/src/instructions/cancel_option.rs | 10 +++--- .../src/instructions/collect_proceeds.rs | 12 +++---- .../src/instructions/exercise_option.rs | 18 +++++----- .../src/instructions/initialize_market.rs | 4 +-- .../src/instructions/reclaim_collateral.rs | 12 +++---- .../options/quasar/src/instructions/shared.rs | 16 +++------ .../quasar/src/instructions/write_option.rs | 10 +++--- finance/options/quasar/src/state.rs | 4 +-- finance/options/quasar/src/tests.rs | 28 +++++++-------- 39 files changed, 196 insertions(+), 188 deletions(-) diff --git a/finance/options/anchor-v1/CHANGELOG.md b/finance/options/anchor-v1/CHANGELOG.md index bb5f93a6..914e1673 100644 --- a/finance/options/anchor-v1/CHANGELOG.md +++ b/finance/options/anchor-v1/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 2026-09-30 + +Rename the market's `underlying_locked` and `quote_locked` to `underlying_owed` +and `quote_owed`. Each counts what the vault owes, to writers as collateral and +to writers and holders as settlement proceeds waiting to be collected, and +"locked" did not say to whom. The account layout is unchanged. + ## 2026-09-10 The `Market` account is now the token authority of both vaults and signs diff --git a/finance/options/anchor-v1/README.md b/finance/options/anchor-v1/README.md index 1a0a2843..566c5837 100644 --- a/finance/options/anchor-v1/README.md +++ b/finance/options/anchor-v1/README.md @@ -165,8 +165,8 @@ insurance he did not need; Maria earned 1% of every premium. ## Custody The two vaults hold other people's money, so the `Market` account keeps a -ledger of what each vault owes: `underlying_locked` (call collateral, plus put -holders' deliveries awaiting collection), `quote_locked` (put collateral, plus +ledger of what each vault owes: `underlying_owed` (call collateral, plus put +holders' deliveries awaiting collection), `quote_owed` (put collateral, plus call holders' strike payments awaiting collection) and `fees_owed`. Every handler that moves tokens updates the ledger before any transfer and then asserts that each vault still covers what it owes (`CustodyInvariantViolated` diff --git a/finance/options/anchor-v1/programs/options/src/instructions/buy_option.rs b/finance/options/anchor-v1/programs/options/src/instructions/buy_option.rs index 261c7e18..2b0b681d 100644 --- a/finance/options/anchor-v1/programs/options/src/instructions/buy_option.rs +++ b/finance/options/anchor-v1/programs/options/src/instructions/buy_option.rs @@ -41,7 +41,7 @@ pub fn handle_buy_option(context: Context) -> Resul .amount .checked_add(fee) .ok_or(OptionsError::MathOverflow)?; - check_custody(market, market.underlying_locked, quote_after)?; + check_custody(market, market.underlying_owed, quote_after)?; transfer_from_signer( &context.accounts.token_program, diff --git a/finance/options/anchor-v1/programs/options/src/instructions/cancel_option.rs b/finance/options/anchor-v1/programs/options/src/instructions/cancel_option.rs index 8a5cf89c..d9ca081c 100644 --- a/finance/options/anchor-v1/programs/options/src/instructions/cancel_option.rs +++ b/finance/options/anchor-v1/programs/options/src/instructions/cancel_option.rs @@ -31,8 +31,8 @@ pub fn handle_cancel_option(context: Context) -> let mut quote_after = context.accounts.quote_vault.amount; match kind { OptionKind::Call => { - market.underlying_locked = market - .underlying_locked + market.underlying_owed = market + .underlying_owed .checked_sub(collateral) .ok_or(OptionsError::MathOverflow)?; underlying_after = underlying_after @@ -40,8 +40,8 @@ pub fn handle_cancel_option(context: Context) -> .ok_or(OptionsError::CustodyInvariantViolated)?; } OptionKind::Put => { - market.quote_locked = market - .quote_locked + market.quote_owed = market + .quote_owed .checked_sub(collateral) .ok_or(OptionsError::MathOverflow)?; quote_after = quote_after diff --git a/finance/options/anchor-v1/programs/options/src/instructions/collect_fees.rs b/finance/options/anchor-v1/programs/options/src/instructions/collect_fees.rs index 0aa13171..d3bff6e7 100644 --- a/finance/options/anchor-v1/programs/options/src/instructions/collect_fees.rs +++ b/finance/options/anchor-v1/programs/options/src/instructions/collect_fees.rs @@ -11,7 +11,7 @@ use crate::state::Market; /// The admin sweeps the fees the venue has earned on premiums. `fees_owed` /// is the only part of the quote vault the admin can reach: the collateral -/// and strike payments beside it are locked to their writers and holders. +/// and strike payments beside it are owed to their writers and holders. pub fn handle_collect_fees(context: Context) -> Result<()> { let market = &mut context.accounts.market; let amount = market.fees_owed; diff --git a/finance/options/anchor-v1/programs/options/src/instructions/collect_proceeds.rs b/finance/options/anchor-v1/programs/options/src/instructions/collect_proceeds.rs index 63e3c870..afb28e6e 100644 --- a/finance/options/anchor-v1/programs/options/src/instructions/collect_proceeds.rs +++ b/finance/options/anchor-v1/programs/options/src/instructions/collect_proceeds.rs @@ -35,8 +35,8 @@ pub fn handle_collect_proceeds(context: Context { - market.quote_locked = market - .quote_locked + market.quote_owed = market + .quote_owed .checked_sub(proceeds) .ok_or(OptionsError::MathOverflow)?; quote_after = quote_after @@ -45,8 +45,8 @@ pub fn handle_collect_proceeds(context: Context { - market.underlying_locked = market - .underlying_locked + market.underlying_owed = market + .underlying_owed .checked_sub(proceeds) .ok_or(OptionsError::MathOverflow)?; underlying_after = underlying_after diff --git a/finance/options/anchor-v1/programs/options/src/instructions/exercise_option.rs b/finance/options/anchor-v1/programs/options/src/instructions/exercise_option.rs index 57686422..69f9eeb6 100644 --- a/finance/options/anchor-v1/programs/options/src/instructions/exercise_option.rs +++ b/finance/options/anchor-v1/programs/options/src/instructions/exercise_option.rs @@ -48,12 +48,12 @@ pub fn handle_exercise_option(context: Context let quote_before = context.accounts.quote_vault.amount; let (underlying_after, quote_after) = match kind { OptionKind::Call => { - market.underlying_locked = market - .underlying_locked + market.underlying_owed = market + .underlying_owed .checked_sub(underlying_total) .ok_or(OptionsError::MathOverflow)?; - market.quote_locked = market - .quote_locked + market.quote_owed = market + .quote_owed .checked_add(strike_total) .ok_or(OptionsError::MathOverflow)?; ( @@ -66,12 +66,12 @@ pub fn handle_exercise_option(context: Context ) } OptionKind::Put => { - market.quote_locked = market - .quote_locked + market.quote_owed = market + .quote_owed .checked_sub(strike_total) .ok_or(OptionsError::MathOverflow)?; - market.underlying_locked = market - .underlying_locked + market.underlying_owed = market + .underlying_owed .checked_add(underlying_total) .ok_or(OptionsError::MathOverflow)?; ( diff --git a/finance/options/anchor-v1/programs/options/src/instructions/initialize_market.rs b/finance/options/anchor-v1/programs/options/src/instructions/initialize_market.rs index d44c42ed..fbdfb9d7 100644 --- a/finance/options/anchor-v1/programs/options/src/instructions/initialize_market.rs +++ b/finance/options/anchor-v1/programs/options/src/instructions/initialize_market.rs @@ -30,8 +30,8 @@ pub fn handle_initialize_market( market.quote_mint = context.accounts.quote_mint.key(); market.underlying_vault = context.accounts.underlying_vault.key(); market.quote_vault = context.accounts.quote_vault.key(); - market.underlying_locked = 0; - market.quote_locked = 0; + market.underlying_owed = 0; + market.quote_owed = 0; market.fees_owed = 0; market.fee_bps = fee_bps; market.bump = context.bumps.market; diff --git a/finance/options/anchor-v1/programs/options/src/instructions/reclaim_collateral.rs b/finance/options/anchor-v1/programs/options/src/instructions/reclaim_collateral.rs index 0778656f..e0976bd2 100644 --- a/finance/options/anchor-v1/programs/options/src/instructions/reclaim_collateral.rs +++ b/finance/options/anchor-v1/programs/options/src/instructions/reclaim_collateral.rs @@ -40,8 +40,8 @@ pub fn handle_reclaim_collateral( let mut quote_after = context.accounts.quote_vault.amount; match kind { OptionKind::Call => { - market.underlying_locked = market - .underlying_locked + market.underlying_owed = market + .underlying_owed .checked_sub(collateral) .ok_or(OptionsError::MathOverflow)?; underlying_after = underlying_after @@ -49,8 +49,8 @@ pub fn handle_reclaim_collateral( .ok_or(OptionsError::CustodyInvariantViolated)?; } OptionKind::Put => { - market.quote_locked = market - .quote_locked + market.quote_owed = market + .quote_owed .checked_sub(collateral) .ok_or(OptionsError::MathOverflow)?; quote_after = quote_after diff --git a/finance/options/anchor-v1/programs/options/src/instructions/shared.rs b/finance/options/anchor-v1/programs/options/src/instructions/shared.rs index a8d10fc5..3ff28a3e 100644 --- a/finance/options/anchor-v1/programs/options/src/instructions/shared.rs +++ b/finance/options/anchor-v1/programs/options/src/instructions/shared.rs @@ -13,11 +13,11 @@ use crate::state::Market; /// behind, computed from the balances read before any CPI ran. pub fn check_custody(market: &Market, underlying_after: u64, quote_after: u64) -> Result<()> { require!( - underlying_after >= market.underlying_locked, + underlying_after >= market.underlying_owed, OptionsError::CustodyInvariantViolated ); let quote_owed = market - .quote_locked + .quote_owed .checked_add(market.fees_owed) .ok_or(OptionsError::MathOverflow)?; require!( diff --git a/finance/options/anchor-v1/programs/options/src/instructions/write_option.rs b/finance/options/anchor-v1/programs/options/src/instructions/write_option.rs index 9968345f..16630927 100644 --- a/finance/options/anchor-v1/programs/options/src/instructions/write_option.rs +++ b/finance/options/anchor-v1/programs/options/src/instructions/write_option.rs @@ -93,8 +93,8 @@ pub fn handle_write_option( let mut quote_after = context.accounts.quote_vault.amount; match kind { OptionKind::Call => { - market.underlying_locked = market - .underlying_locked + market.underlying_owed = market + .underlying_owed .checked_add(collateral) .ok_or(OptionsError::MathOverflow)?; underlying_after = underlying_after @@ -102,8 +102,8 @@ pub fn handle_write_option( .ok_or(OptionsError::MathOverflow)?; } OptionKind::Put => { - market.quote_locked = market - .quote_locked + market.quote_owed = market + .quote_owed .checked_add(collateral) .ok_or(OptionsError::MathOverflow)?; quote_after = quote_after diff --git a/finance/options/anchor-v1/programs/options/src/state/market.rs b/finance/options/anchor-v1/programs/options/src/state/market.rs index f7042725..8e5e6023 100644 --- a/finance/options/anchor-v1/programs/options/src/state/market.rs +++ b/finance/options/anchor-v1/programs/options/src/state/market.rs @@ -28,11 +28,11 @@ pub struct Market { /// Underlying minor units the vault owes: call writers' collateral, plus /// put holders' deliveries awaiting the writer's `collect_proceeds`. - pub underlying_locked: u64, + pub underlying_owed: u64, /// Quote minor units the vault owes: put writers' collateral, plus call /// holders' strike payments awaiting the writer's `collect_proceeds`. - pub quote_locked: u64, + pub quote_owed: u64, /// Quote minor units held in the quote vault for the admin, accrued from /// the fee on each premium and swept by `collect_fees`. diff --git a/finance/options/anchor-v1/programs/options/tests/test_options.rs b/finance/options/anchor-v1/programs/options/tests/test_options.rs index 946adbac..de3b6afe 100644 --- a/finance/options/anchor-v1/programs/options/tests/test_options.rs +++ b/finance/options/anchor-v1/programs/options/tests/test_options.rs @@ -477,13 +477,13 @@ impl Venue { let market = self.market_state(); assert_eq!( self.balance(&self.underlying_vault), - market.underlying_locked, - "underlying vault must hold exactly the locked underlying" + market.underlying_owed, + "underlying vault must hold exactly the underlying owed" ); assert_eq!( self.balance(&self.quote_vault), - market.quote_locked + market.fees_owed, - "quote vault must hold exactly the locked quote plus the fees owed" + market.quote_owed + market.fees_owed, + "quote vault must hold exactly the quote owed plus the fees owed" ); } } @@ -521,7 +521,7 @@ fn test_write_call_moves_underlying_into_vault() { assert_eq!(state.contracts, CONTRACTS); assert_eq!(state.strike_per_contract, CALL_STRIKE); assert_eq!(state.premium, CALL_PREMIUM); - assert_eq!(venue.market_state().underlying_locked, FIVE_NVDAX); + assert_eq!(venue.market_state().underlying_owed, FIVE_NVDAX); venue.assert_vaults_match_ledger(); } @@ -575,8 +575,8 @@ fn test_exercise_call_swaps_the_strike_for_the_underlying() { assert_eq!(venue.balance(&venue.underlying_vault), 0); assert_eq!(venue.balance(&venue.quote_vault), strike_total + 250_000); let market = venue.market_state(); - assert_eq!(market.underlying_locked, 0); - assert_eq!(market.quote_locked, strike_total); + assert_eq!(market.underlying_owed, 0); + assert_eq!(market.quote_owed, strike_total); assert_eq!(venue.option_state(&option).status, OptionStatus::Exercised); venue.assert_vaults_match_ledger(); } @@ -609,13 +609,13 @@ fn test_collect_proceeds_pays_the_writer_and_closes_the_option() { "the option's rent must return to the writer" ); let market = venue.market_state(); - assert_eq!(market.quote_locked, 0); + assert_eq!(market.quote_owed, 0); assert_eq!(market.fees_owed, 250_000); venue.assert_vaults_match_ledger(); } /// Maria sweeps the venue's fee. Only the 0.25 USDC of fees leaves the -/// vault; the strike payment sitting beside it stays locked to Alice. +/// vault; the strike payment sitting beside it stays owed to Alice. #[test] fn test_collect_fees_pays_only_the_fees_owed() { let mut venue = Venue::new(); @@ -657,7 +657,7 @@ fn test_put_lifecycle_delivers_the_underlying_for_the_strike() { let collateral = 750 * ONE_TOKEN; assert_eq!(venue.balance(&carol.quote), STANDARD_USDC - collateral); assert_eq!(venue.balance(&venue.quote_vault), collateral); - assert_eq!(venue.market_state().quote_locked, collateral); + assert_eq!(venue.market_state().quote_owed, collateral); venue.assert_vaults_match_ledger(); venue.buy_option(&dave, &carol.pubkey(), &option).unwrap(); @@ -680,13 +680,13 @@ fn test_put_lifecycle_delivers_the_underlying_for_the_strike() { assert_eq!(venue.balance(&venue.underlying_vault), FIVE_NVDAX); assert_eq!(venue.balance(&venue.quote_vault), fee); let market = venue.market_state(); - assert_eq!(market.underlying_locked, FIVE_NVDAX); - assert_eq!(market.quote_locked, 0); + assert_eq!(market.underlying_owed, FIVE_NVDAX); + assert_eq!(market.quote_owed, 0); venue.assert_vaults_match_ledger(); venue.collect_proceeds(&carol, &option).unwrap(); assert_eq!(venue.balance(&carol.underlying), FIVE_NVDAX); - assert_eq!(venue.market_state().underlying_locked, 0); + assert_eq!(venue.market_state().underlying_owed, 0); assert!(!venue.option_exists(&option)); venue.assert_vaults_match_ledger(); } @@ -713,7 +713,7 @@ fn test_reclaim_collateral_after_expiry_returns_it_to_the_writer() { ); assert_eq!(venue.balance(&bob.quote), STANDARD_USDC - CALL_PREMIUM); assert!(!venue.option_exists(&option)); - assert_eq!(venue.market_state().underlying_locked, 0); + assert_eq!(venue.market_state().underlying_owed, 0); venue.assert_vaults_match_ledger(); } @@ -781,8 +781,8 @@ fn test_buy_is_refused_after_expiry() { // =========================================================================== /// An unsold option can be withdrawn at any time, collateral back, account -/// closed. Without this, an option nobody buys would lock the writer's tokens -/// forever. +/// closed. Without this, an option nobody buys would leave the writer's tokens +/// in the vault forever. #[test] fn test_cancel_unsold_option_returns_the_collateral() { let mut venue = Venue::new(); @@ -810,7 +810,7 @@ fn test_cancel_unsold_option_works_after_expiry() { venue.cancel_option(&carol, &option).unwrap(); assert_eq!(venue.balance(&carol.quote), STANDARD_USDC); - assert_eq!(venue.market_state().quote_locked, 0); + assert_eq!(venue.market_state().quote_owed, 0); venue.assert_vaults_match_ledger(); } diff --git a/finance/options/anchor/CHANGELOG.md b/finance/options/anchor/CHANGELOG.md index bb5f93a6..914e1673 100644 --- a/finance/options/anchor/CHANGELOG.md +++ b/finance/options/anchor/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 2026-09-30 + +Rename the market's `underlying_locked` and `quote_locked` to `underlying_owed` +and `quote_owed`. Each counts what the vault owes, to writers as collateral and +to writers and holders as settlement proceeds waiting to be collected, and +"locked" did not say to whom. The account layout is unchanged. + ## 2026-09-10 The `Market` account is now the token authority of both vaults and signs diff --git a/finance/options/anchor/README.md b/finance/options/anchor/README.md index d018618b..e86b497b 100644 --- a/finance/options/anchor/README.md +++ b/finance/options/anchor/README.md @@ -165,8 +165,8 @@ insurance he did not need; Maria earned 1% of every premium. ## Custody The two vaults hold other people's money, so the `Market` account keeps a -ledger of what each vault owes: `underlying_locked` (call collateral, plus put -holders' deliveries awaiting collection), `quote_locked` (put collateral, plus +ledger of what each vault owes: `underlying_owed` (call collateral, plus put +holders' deliveries awaiting collection), `quote_owed` (put collateral, plus call holders' strike payments awaiting collection) and `fees_owed`. Every handler that moves tokens updates the ledger before any transfer and then asserts that each vault still covers what it owes (`CustodyInvariantViolated` @@ -248,5 +248,5 @@ exercise. `buy_option` takes `fee_bps` of every premium into the quote vault, and the admin sweeps it with `collect_fees`. The fee is the admin's only reach into -the vault; collateral and strike payments are locked to their writers and +the vault; collateral and strike payments are owed to their writers and holders. diff --git a/finance/options/anchor/programs/options/src/instructions/buy_option.rs b/finance/options/anchor/programs/options/src/instructions/buy_option.rs index b7b88c2b..e7495555 100644 --- a/finance/options/anchor/programs/options/src/instructions/buy_option.rs +++ b/finance/options/anchor/programs/options/src/instructions/buy_option.rs @@ -41,7 +41,7 @@ pub fn handle_buy_option(context: &mut Context) -> .amount() .checked_add(fee) .ok_or(OptionsError::MathOverflow)?; - check_custody(market, market.underlying_locked, quote_after)?; + check_custody(market, market.underlying_owed, quote_after)?; transfer_from_signer( &context.accounts.token_program, diff --git a/finance/options/anchor/programs/options/src/instructions/cancel_option.rs b/finance/options/anchor/programs/options/src/instructions/cancel_option.rs index 1bdd248f..bfc06a8c 100644 --- a/finance/options/anchor/programs/options/src/instructions/cancel_option.rs +++ b/finance/options/anchor/programs/options/src/instructions/cancel_option.rs @@ -31,8 +31,8 @@ pub fn handle_cancel_option(context: &mut Context { - market.underlying_locked = market - .underlying_locked + market.underlying_owed = market + .underlying_owed .checked_sub(collateral) .ok_or(OptionsError::MathOverflow)?; underlying_after = underlying_after @@ -40,8 +40,8 @@ pub fn handle_cancel_option(context: &mut Context { - market.quote_locked = market - .quote_locked + market.quote_owed = market + .quote_owed .checked_sub(collateral) .ok_or(OptionsError::MathOverflow)?; quote_after = quote_after diff --git a/finance/options/anchor/programs/options/src/instructions/collect_fees.rs b/finance/options/anchor/programs/options/src/instructions/collect_fees.rs index c71368a4..a8b6be19 100644 --- a/finance/options/anchor/programs/options/src/instructions/collect_fees.rs +++ b/finance/options/anchor/programs/options/src/instructions/collect_fees.rs @@ -11,7 +11,7 @@ use crate::state::Market; /// The admin sweeps the fees the venue has earned on premiums. `fees_owed` /// is the only part of the quote vault the admin can reach: the collateral -/// and strike payments beside it are locked to their writers and holders. +/// and strike payments beside it are owed to their writers and holders. pub fn handle_collect_fees(context: &mut Context) -> Result<()> { let market = &mut context.accounts.market; let amount = market.fees_owed; diff --git a/finance/options/anchor/programs/options/src/instructions/collect_proceeds.rs b/finance/options/anchor/programs/options/src/instructions/collect_proceeds.rs index 240b2021..8be68709 100644 --- a/finance/options/anchor/programs/options/src/instructions/collect_proceeds.rs +++ b/finance/options/anchor/programs/options/src/instructions/collect_proceeds.rs @@ -37,8 +37,8 @@ pub fn handle_collect_proceeds( match kind { // A call's proceeds are the strike, in the quote token. OptionKind::Call => { - market.quote_locked = market - .quote_locked + market.quote_owed = market + .quote_owed .checked_sub(proceeds) .ok_or(OptionsError::MathOverflow)?; quote_after = quote_after @@ -47,8 +47,8 @@ pub fn handle_collect_proceeds( } // A put's proceeds are the delivered underlying. OptionKind::Put => { - market.underlying_locked = market - .underlying_locked + market.underlying_owed = market + .underlying_owed .checked_sub(proceeds) .ok_or(OptionsError::MathOverflow)?; underlying_after = underlying_after diff --git a/finance/options/anchor/programs/options/src/instructions/exercise_option.rs b/finance/options/anchor/programs/options/src/instructions/exercise_option.rs index a08dcaaf..719427c3 100644 --- a/finance/options/anchor/programs/options/src/instructions/exercise_option.rs +++ b/finance/options/anchor/programs/options/src/instructions/exercise_option.rs @@ -50,12 +50,12 @@ pub fn handle_exercise_option( let quote_before = context.accounts.quote_vault.amount(); let (underlying_after, quote_after) = match kind { OptionKind::Call => { - market.underlying_locked = market - .underlying_locked + market.underlying_owed = market + .underlying_owed .checked_sub(underlying_total) .ok_or(OptionsError::MathOverflow)?; - market.quote_locked = market - .quote_locked + market.quote_owed = market + .quote_owed .checked_add(strike_total) .ok_or(OptionsError::MathOverflow)?; ( @@ -68,12 +68,12 @@ pub fn handle_exercise_option( ) } OptionKind::Put => { - market.quote_locked = market - .quote_locked + market.quote_owed = market + .quote_owed .checked_sub(strike_total) .ok_or(OptionsError::MathOverflow)?; - market.underlying_locked = market - .underlying_locked + market.underlying_owed = market + .underlying_owed .checked_add(underlying_total) .ok_or(OptionsError::MathOverflow)?; ( diff --git a/finance/options/anchor/programs/options/src/instructions/initialize_market.rs b/finance/options/anchor/programs/options/src/instructions/initialize_market.rs index be2de424..5ae5d06e 100644 --- a/finance/options/anchor/programs/options/src/instructions/initialize_market.rs +++ b/finance/options/anchor/programs/options/src/instructions/initialize_market.rs @@ -31,8 +31,8 @@ pub fn handle_initialize_market( market.quote_mint = *context.accounts.quote_mint.address(); market.underlying_vault = *context.accounts.underlying_vault.address(); market.quote_vault = *context.accounts.quote_vault.address(); - market.underlying_locked = 0; - market.quote_locked = 0; + market.underlying_owed = 0; + market.quote_owed = 0; market.fees_owed = 0; market.fee_bps = fee_bps; market.bump = context.bumps.market; diff --git a/finance/options/anchor/programs/options/src/instructions/reclaim_collateral.rs b/finance/options/anchor/programs/options/src/instructions/reclaim_collateral.rs index dda0cb76..0b4ed984 100644 --- a/finance/options/anchor/programs/options/src/instructions/reclaim_collateral.rs +++ b/finance/options/anchor/programs/options/src/instructions/reclaim_collateral.rs @@ -40,8 +40,8 @@ pub fn handle_reclaim_collateral( let mut quote_after = context.accounts.quote_vault.amount(); match kind { OptionKind::Call => { - market.underlying_locked = market - .underlying_locked + market.underlying_owed = market + .underlying_owed .checked_sub(collateral) .ok_or(OptionsError::MathOverflow)?; underlying_after = underlying_after @@ -49,8 +49,8 @@ pub fn handle_reclaim_collateral( .ok_or(OptionsError::CustodyInvariantViolated)?; } OptionKind::Put => { - market.quote_locked = market - .quote_locked + market.quote_owed = market + .quote_owed .checked_sub(collateral) .ok_or(OptionsError::MathOverflow)?; quote_after = quote_after diff --git a/finance/options/anchor/programs/options/src/instructions/shared.rs b/finance/options/anchor/programs/options/src/instructions/shared.rs index 8d38fe60..5fb3a855 100644 --- a/finance/options/anchor/programs/options/src/instructions/shared.rs +++ b/finance/options/anchor/programs/options/src/instructions/shared.rs @@ -13,11 +13,11 @@ use crate::state::Market; /// behind, computed from the balances read before any CPI ran. pub fn check_custody(market: &Market, underlying_after: u64, quote_after: u64) -> Result<()> { require!( - underlying_after >= market.underlying_locked, + underlying_after >= market.underlying_owed, OptionsError::CustodyInvariantViolated ); let quote_owed = market - .quote_locked + .quote_owed .checked_add(market.fees_owed) .ok_or(OptionsError::MathOverflow)?; require!( diff --git a/finance/options/anchor/programs/options/src/instructions/write_option.rs b/finance/options/anchor/programs/options/src/instructions/write_option.rs index 92dbb690..ae6c2168 100644 --- a/finance/options/anchor/programs/options/src/instructions/write_option.rs +++ b/finance/options/anchor/programs/options/src/instructions/write_option.rs @@ -93,8 +93,8 @@ pub fn handle_write_option( let mut quote_after = context.accounts.quote_vault.amount(); match kind { OptionKind::Call => { - market.underlying_locked = market - .underlying_locked + market.underlying_owed = market + .underlying_owed .checked_add(collateral) .ok_or(OptionsError::MathOverflow)?; underlying_after = underlying_after @@ -102,8 +102,8 @@ pub fn handle_write_option( .ok_or(OptionsError::MathOverflow)?; } OptionKind::Put => { - market.quote_locked = market - .quote_locked + market.quote_owed = market + .quote_owed .checked_add(collateral) .ok_or(OptionsError::MathOverflow)?; quote_after = quote_after diff --git a/finance/options/anchor/programs/options/src/state/market.rs b/finance/options/anchor/programs/options/src/state/market.rs index 419e7c69..d534be50 100644 --- a/finance/options/anchor/programs/options/src/state/market.rs +++ b/finance/options/anchor/programs/options/src/state/market.rs @@ -28,11 +28,11 @@ pub struct Market { /// Underlying minor units the vault owes: call writers' collateral, plus /// put holders' deliveries awaiting the writer's `collect_proceeds`. - pub underlying_locked: u64, + pub underlying_owed: u64, /// Quote minor units the vault owes: put writers' collateral, plus call /// holders' strike payments awaiting the writer's `collect_proceeds`. - pub quote_locked: u64, + pub quote_owed: u64, /// Quote minor units held in the quote vault for the admin, accrued from /// the fee on each premium and swept by `collect_fees`. diff --git a/finance/options/anchor/programs/options/tests/test_options.rs b/finance/options/anchor/programs/options/tests/test_options.rs index 7b08047b..3387f53c 100644 --- a/finance/options/anchor/programs/options/tests/test_options.rs +++ b/finance/options/anchor/programs/options/tests/test_options.rs @@ -484,13 +484,13 @@ impl Venue { let market = self.market_state(); assert_eq!( self.balance(&self.underlying_vault), - market.underlying_locked, - "underlying vault must hold exactly the locked underlying" + market.underlying_owed, + "underlying vault must hold exactly the underlying owed" ); assert_eq!( self.balance(&self.quote_vault), - market.quote_locked + market.fees_owed, - "quote vault must hold exactly the locked quote plus the fees owed" + market.quote_owed + market.fees_owed, + "quote vault must hold exactly the quote owed plus the fees owed" ); } } @@ -528,7 +528,7 @@ fn test_write_call_moves_underlying_into_vault() { assert_eq!(state.contracts, CONTRACTS); assert_eq!(state.strike_per_contract, CALL_STRIKE); assert_eq!(state.premium, CALL_PREMIUM); - assert_eq!(venue.market_state().underlying_locked, FIVE_NVDAX); + assert_eq!(venue.market_state().underlying_owed, FIVE_NVDAX); venue.assert_vaults_match_ledger(); } @@ -582,8 +582,8 @@ fn test_exercise_call_swaps_the_strike_for_the_underlying() { assert_eq!(venue.balance(&venue.underlying_vault), 0); assert_eq!(venue.balance(&venue.quote_vault), strike_total + 250_000); let market = venue.market_state(); - assert_eq!(market.underlying_locked, 0); - assert_eq!(market.quote_locked, strike_total); + assert_eq!(market.underlying_owed, 0); + assert_eq!(market.quote_owed, strike_total); assert_eq!(venue.option_state(&option).status, OptionStatus::Exercised); venue.assert_vaults_match_ledger(); } @@ -616,13 +616,13 @@ fn test_collect_proceeds_pays_the_writer_and_closes_the_option() { "the option's rent must return to the writer" ); let market = venue.market_state(); - assert_eq!(market.quote_locked, 0); + assert_eq!(market.quote_owed, 0); assert_eq!(market.fees_owed, 250_000); venue.assert_vaults_match_ledger(); } /// Maria sweeps the venue's fee. Only the 0.25 USDC of fees leaves the -/// vault; the strike payment sitting beside it stays locked to Alice. +/// vault; the strike payment sitting beside it stays owed to Alice. #[test] fn test_collect_fees_pays_only_the_fees_owed() { let mut venue = Venue::new(); @@ -664,7 +664,7 @@ fn test_put_lifecycle_delivers_the_underlying_for_the_strike() { let collateral = 750 * ONE_TOKEN; assert_eq!(venue.balance(&carol.quote), STANDARD_USDC - collateral); assert_eq!(venue.balance(&venue.quote_vault), collateral); - assert_eq!(venue.market_state().quote_locked, collateral); + assert_eq!(venue.market_state().quote_owed, collateral); venue.assert_vaults_match_ledger(); venue.buy_option(&dave, &carol.pubkey(), &option).unwrap(); @@ -687,13 +687,13 @@ fn test_put_lifecycle_delivers_the_underlying_for_the_strike() { assert_eq!(venue.balance(&venue.underlying_vault), FIVE_NVDAX); assert_eq!(venue.balance(&venue.quote_vault), fee); let market = venue.market_state(); - assert_eq!(market.underlying_locked, FIVE_NVDAX); - assert_eq!(market.quote_locked, 0); + assert_eq!(market.underlying_owed, FIVE_NVDAX); + assert_eq!(market.quote_owed, 0); venue.assert_vaults_match_ledger(); venue.collect_proceeds(&carol, &option).unwrap(); assert_eq!(venue.balance(&carol.underlying), FIVE_NVDAX); - assert_eq!(venue.market_state().underlying_locked, 0); + assert_eq!(venue.market_state().underlying_owed, 0); assert!(!venue.option_exists(&option)); venue.assert_vaults_match_ledger(); } @@ -720,7 +720,7 @@ fn test_reclaim_collateral_after_expiry_returns_it_to_the_writer() { ); assert_eq!(venue.balance(&bob.quote), STANDARD_USDC - CALL_PREMIUM); assert!(!venue.option_exists(&option)); - assert_eq!(venue.market_state().underlying_locked, 0); + assert_eq!(venue.market_state().underlying_owed, 0); venue.assert_vaults_match_ledger(); } @@ -788,8 +788,8 @@ fn test_buy_is_refused_after_expiry() { // =========================================================================== /// An unsold option can be withdrawn at any time, collateral back, account -/// closed. Without this, an option nobody buys would lock the writer's tokens -/// forever. +/// closed. Without this, an option nobody buys would leave the writer's tokens +/// in the vault forever. #[test] fn test_cancel_unsold_option_returns_the_collateral() { let mut venue = Venue::new(); @@ -817,7 +817,7 @@ fn test_cancel_unsold_option_works_after_expiry() { venue.cancel_option(&carol, &option).unwrap(); assert_eq!(venue.balance(&carol.quote), STANDARD_USDC); - assert_eq!(venue.market_state().quote_locked, 0); + assert_eq!(venue.market_state().quote_owed, 0); venue.assert_vaults_match_ledger(); } diff --git a/finance/options/kani-proofs/README.md b/finance/options/kani-proofs/README.md index ea6dd8cc..e9357ebb 100644 --- a/finance/options/kani-proofs/README.md +++ b/finance/options/kani-proofs/README.md @@ -17,8 +17,8 @@ pure integer math, and small: every settlement amount is a product of two integers the writer chose, the only rounding in the program is the floor in the fee split, and the expiry window is one comparison and its complement. This crate reproduces those formulas (mirroring `options::contract_math`) and -the handlers' custody accounting (mirroring the `underlying_locked`, -`quote_locked` and `fees_owed` counters on the `Market` account) and checks, for every input +the handlers' custody accounting (mirroring the `underlying_owed`, +`quote_owed` and `fees_owed` counters on the `Market` account) and checks, for every input in the declared ranges: - `proof_exercise_moves_exactly_the_posted_terms`: for every option the program diff --git a/finance/options/kani-proofs/src/lib.rs b/finance/options/kani-proofs/src/lib.rs index d478bb51..59f5573a 100644 --- a/finance/options/kani-proofs/src/lib.rs +++ b/finance/options/kani-proofs/src/lib.rs @@ -220,8 +220,8 @@ fn proof_exercise_and_reclaim_windows_partition_time() { /// the three counters on the `Market` account. #[derive(Clone, Copy, Default, Debug, PartialEq, Eq)] pub struct Ledger { - pub underlying_locked: u64, - pub quote_locked: u64, + pub underlying_owed: u64, + pub quote_owed: u64, pub fees_owed: u64, /// The token balances the handlers' transfers leave in the two vaults. pub underlying_vault: u64, @@ -233,19 +233,19 @@ impl Ledger { /// `shared::check_custody`), strengthened to equality: with no donations, /// each vault holds exactly what the market owes. pub fn is_consistent(&self) -> bool { - self.underlying_vault == self.underlying_locked - && self.quote_vault as u128 == self.quote_locked as u128 + self.fees_owed as u128 + self.underlying_vault == self.underlying_owed + && self.quote_vault as u128 == self.quote_owed as u128 + self.fees_owed as u128 } /// `write_option`: collateral into the vault, owed back to the writer. pub fn write(&mut self, kind: OptionKind, collateral: u64) -> Option<()> { match kind { OptionKind::Call => { - self.underlying_locked = self.underlying_locked.checked_add(collateral)?; + self.underlying_owed = self.underlying_owed.checked_add(collateral)?; self.underlying_vault = self.underlying_vault.checked_add(collateral)?; } OptionKind::Put => { - self.quote_locked = self.quote_locked.checked_add(collateral)?; + self.quote_owed = self.quote_owed.checked_add(collateral)?; self.quote_vault = self.quote_vault.checked_add(collateral)?; } } @@ -265,15 +265,15 @@ impl Ledger { pub fn exercise(&mut self, kind: OptionKind, collateral: u64, payment: u64) -> Option<()> { match kind { OptionKind::Call => { - self.underlying_locked = self.underlying_locked.checked_sub(collateral)?; + self.underlying_owed = self.underlying_owed.checked_sub(collateral)?; self.underlying_vault = self.underlying_vault.checked_sub(collateral)?; - self.quote_locked = self.quote_locked.checked_add(payment)?; + self.quote_owed = self.quote_owed.checked_add(payment)?; self.quote_vault = self.quote_vault.checked_add(payment)?; } OptionKind::Put => { - self.quote_locked = self.quote_locked.checked_sub(collateral)?; + self.quote_owed = self.quote_owed.checked_sub(collateral)?; self.quote_vault = self.quote_vault.checked_sub(collateral)?; - self.underlying_locked = self.underlying_locked.checked_add(payment)?; + self.underlying_owed = self.underlying_owed.checked_add(payment)?; self.underlying_vault = self.underlying_vault.checked_add(payment)?; } } @@ -284,11 +284,11 @@ impl Ledger { pub fn collect_proceeds(&mut self, kind: OptionKind, payment: u64) -> Option<()> { match kind { OptionKind::Call => { - self.quote_locked = self.quote_locked.checked_sub(payment)?; + self.quote_owed = self.quote_owed.checked_sub(payment)?; self.quote_vault = self.quote_vault.checked_sub(payment)?; } OptionKind::Put => { - self.underlying_locked = self.underlying_locked.checked_sub(payment)?; + self.underlying_owed = self.underlying_owed.checked_sub(payment)?; self.underlying_vault = self.underlying_vault.checked_sub(payment)?; } } @@ -300,11 +300,11 @@ impl Ledger { pub fn return_collateral(&mut self, kind: OptionKind, collateral: u64) -> Option<()> { match kind { OptionKind::Call => { - self.underlying_locked = self.underlying_locked.checked_sub(collateral)?; + self.underlying_owed = self.underlying_owed.checked_sub(collateral)?; self.underlying_vault = self.underlying_vault.checked_sub(collateral)?; } OptionKind::Put => { - self.quote_locked = self.quote_locked.checked_sub(collateral)?; + self.quote_owed = self.quote_owed.checked_sub(collateral)?; self.quote_vault = self.quote_vault.checked_sub(collateral)?; } } @@ -403,8 +403,8 @@ fn proof_vault_ledger_stays_consistent_across_every_lifecycle() { } // With every option closed, nothing is owed to any writer or holder ... - assert_eq!(ledger.underlying_locked, 0); - assert_eq!(ledger.quote_locked, 0); + assert_eq!(ledger.underlying_owed, 0); + assert_eq!(ledger.quote_owed, 0); // ... and once the admin sweeps the fees, both vaults are empty: no token // was created or lost along any path. ledger.collect_fees().unwrap(); diff --git a/finance/options/quasar/CHANGELOG.md b/finance/options/quasar/CHANGELOG.md index 2424b1a1..fd942915 100644 --- a/finance/options/quasar/CHANGELOG.md +++ b/finance/options/quasar/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 2026-09-30 + +Rename the market's `underlying_locked` and `quote_locked` to `underlying_owed` +and `quote_owed`. Each counts what the vault owes, to writers as collateral and +to writers and holders as settlement proceeds waiting to be collected, and +"locked" did not say to whom. The Quasar helpers `add_locked` and `sub_locked` are now `add_owed` and +`sub_owed`. The account layout is unchanged. + ## 2026-09-10 The `Market` account is now the token authority of both vaults and signs diff --git a/finance/options/quasar/src/instructions/buy_option.rs b/finance/options/quasar/src/instructions/buy_option.rs index afbbbcf2..c58f02fe 100644 --- a/finance/options/quasar/src/instructions/buy_option.rs +++ b/finance/options/quasar/src/instructions/buy_option.rs @@ -2,7 +2,7 @@ use { crate::{ constants::{STATUS_HELD, STATUS_LISTED}, errors::OptionsError, - instructions::shared::{add_locked, check_custody, may_exercise, split_premium}, + instructions::shared::{add_owed, check_custody, may_exercise, split_premium}, state::{Market, OptionContract}, }, quasar_lang::{prelude::*, sysvars::Sysvar as _}, @@ -76,8 +76,8 @@ pub fn handle_buy_option(accounts: &mut BuyOptionAccountConstraints) -> Result<( accounts.option.holder = *accounts.buyer.address(); accounts.option.status = STATUS_HELD; let mut quote_after = accounts.quote_vault.amount(); - add_locked(&mut accounts.market.fees_owed, &mut quote_after, fee)?; - let underlying_after = accounts.market.underlying_locked.get(); + add_owed(&mut accounts.market.fees_owed, &mut quote_after, fee)?; + let underlying_after = accounts.market.underlying_owed.get(); check_custody(&accounts.market, underlying_after, quote_after)?; accounts diff --git a/finance/options/quasar/src/instructions/cancel_option.rs b/finance/options/quasar/src/instructions/cancel_option.rs index 48590924..95084388 100644 --- a/finance/options/quasar/src/instructions/cancel_option.rs +++ b/finance/options/quasar/src/instructions/cancel_option.rs @@ -2,7 +2,7 @@ use { crate::{ constants::STATUS_LISTED, errors::OptionsError, - instructions::shared::{check_custody, sub_locked, transfer_from_vault, Terms}, + instructions::shared::{check_custody, sub_owed, transfer_from_vault, Terms}, state::{Market, OptionContract}, }, quasar_lang::prelude::*, @@ -62,14 +62,14 @@ pub fn handle_cancel_option( let mut underlying_after = accounts.underlying_vault.amount(); let mut quote_after = accounts.quote_vault.amount(); if terms.is_call() { - sub_locked( - &mut accounts.market.underlying_locked, + sub_owed( + &mut accounts.market.underlying_owed, &mut underlying_after, collateral, )?; } else { - sub_locked( - &mut accounts.market.quote_locked, + sub_owed( + &mut accounts.market.quote_owed, &mut quote_after, collateral, )?; diff --git a/finance/options/quasar/src/instructions/collect_proceeds.rs b/finance/options/quasar/src/instructions/collect_proceeds.rs index 38600d7a..8ab0205c 100644 --- a/finance/options/quasar/src/instructions/collect_proceeds.rs +++ b/finance/options/quasar/src/instructions/collect_proceeds.rs @@ -2,7 +2,7 @@ use { crate::{ constants::STATUS_EXERCISED, errors::OptionsError, - instructions::shared::{check_custody, sub_locked, transfer_from_vault, Terms}, + instructions::shared::{check_custody, sub_owed, transfer_from_vault, Terms}, state::{Market, OptionContract}, }, quasar_lang::prelude::*, @@ -64,15 +64,11 @@ pub fn handle_collect_proceeds( let mut quote_after = accounts.quote_vault.amount(); if terms.is_call() { // A call's proceeds are the strike, in the quote token. - sub_locked( - &mut accounts.market.quote_locked, - &mut quote_after, - proceeds, - )?; + sub_owed(&mut accounts.market.quote_owed, &mut quote_after, proceeds)?; } else { // A put's proceeds are the delivered underlying. - sub_locked( - &mut accounts.market.underlying_locked, + sub_owed( + &mut accounts.market.underlying_owed, &mut underlying_after, proceeds, )?; diff --git a/finance/options/quasar/src/instructions/exercise_option.rs b/finance/options/quasar/src/instructions/exercise_option.rs index 0c716917..cef35fe3 100644 --- a/finance/options/quasar/src/instructions/exercise_option.rs +++ b/finance/options/quasar/src/instructions/exercise_option.rs @@ -3,7 +3,7 @@ use { constants::{STATUS_EXERCISED, STATUS_HELD}, errors::OptionsError, instructions::shared::{ - add_locked, check_custody, may_exercise, sub_locked, transfer_from_vault, Terms, + add_owed, check_custody, may_exercise, sub_owed, transfer_from_vault, Terms, }, state::{Market, OptionContract}, }, @@ -82,24 +82,24 @@ pub fn handle_exercise_option( let mut underlying_after = accounts.underlying_vault.amount(); let mut quote_after = accounts.quote_vault.amount(); if terms.is_call() { - sub_locked( - &mut accounts.market.underlying_locked, + sub_owed( + &mut accounts.market.underlying_owed, &mut underlying_after, underlying_total, )?; - add_locked( - &mut accounts.market.quote_locked, + add_owed( + &mut accounts.market.quote_owed, &mut quote_after, strike_total, )?; } else { - sub_locked( - &mut accounts.market.quote_locked, + sub_owed( + &mut accounts.market.quote_owed, &mut quote_after, strike_total, )?; - add_locked( - &mut accounts.market.underlying_locked, + add_owed( + &mut accounts.market.underlying_owed, &mut underlying_after, underlying_total, )?; diff --git a/finance/options/quasar/src/instructions/initialize_market.rs b/finance/options/quasar/src/instructions/initialize_market.rs index bc66c25d..9e1235e9 100644 --- a/finance/options/quasar/src/instructions/initialize_market.rs +++ b/finance/options/quasar/src/instructions/initialize_market.rs @@ -70,8 +70,8 @@ pub fn handle_initialize_market( quote_mint: *accounts.quote_mint.address(), underlying_vault: *accounts.underlying_vault.address(), quote_vault: *accounts.quote_vault.address(), - underlying_locked: 0, - quote_locked: 0, + underlying_owed: 0, + quote_owed: 0, fees_owed: 0, fee_bps, bump: bumps.market, diff --git a/finance/options/quasar/src/instructions/reclaim_collateral.rs b/finance/options/quasar/src/instructions/reclaim_collateral.rs index 19b462bd..7d046659 100644 --- a/finance/options/quasar/src/instructions/reclaim_collateral.rs +++ b/finance/options/quasar/src/instructions/reclaim_collateral.rs @@ -2,9 +2,7 @@ use { crate::{ constants::STATUS_HELD, errors::OptionsError, - instructions::shared::{ - check_custody, may_reclaim, sub_locked, transfer_from_vault, Terms, - }, + instructions::shared::{check_custody, may_reclaim, sub_owed, transfer_from_vault, Terms}, state::{Market, OptionContract}, }, quasar_lang::{prelude::*, sysvars::Sysvar as _}, @@ -73,14 +71,14 @@ pub fn handle_reclaim_collateral( let mut underlying_after = accounts.underlying_vault.amount(); let mut quote_after = accounts.quote_vault.amount(); if terms.is_call() { - sub_locked( - &mut accounts.market.underlying_locked, + sub_owed( + &mut accounts.market.underlying_owed, &mut underlying_after, collateral, )?; } else { - sub_locked( - &mut accounts.market.quote_locked, + sub_owed( + &mut accounts.market.quote_owed, &mut quote_after, collateral, )?; diff --git a/finance/options/quasar/src/instructions/shared.rs b/finance/options/quasar/src/instructions/shared.rs index 48f7462b..9fe28d94 100644 --- a/finance/options/quasar/src/instructions/shared.rs +++ b/finance/options/quasar/src/instructions/shared.rs @@ -103,11 +103,11 @@ pub fn check_custody( quote_after: u64, ) -> Result<(), ProgramError> { require!( - underlying_after >= market.underlying_locked.get(), + underlying_after >= market.underlying_owed.get(), OptionsError::CustodyInvariantViolated ); let quote_owed = market - .quote_locked + .quote_owed .get() .checked_add(market.fees_owed.get()) .ok_or(OptionsError::MathOverflow)?; @@ -119,11 +119,7 @@ pub fn check_custody( } /// Add `amount` to a ledger counter and to the matching projected balance. -pub fn add_locked( - counter: &mut PodU64, - balance: &mut u64, - amount: u64, -) -> Result<(), ProgramError> { +pub fn add_owed(counter: &mut PodU64, balance: &mut u64, amount: u64) -> Result<(), ProgramError> { counter.set( counter .get() @@ -138,11 +134,7 @@ pub fn add_locked( /// Subtract `amount` from a ledger counter and from the matching projected /// balance. A balance that cannot cover the subtraction is a custody failure. -pub fn sub_locked( - counter: &mut PodU64, - balance: &mut u64, - amount: u64, -) -> Result<(), ProgramError> { +pub fn sub_owed(counter: &mut PodU64, balance: &mut u64, amount: u64) -> Result<(), ProgramError> { counter.set( counter .get() diff --git a/finance/options/quasar/src/instructions/write_option.rs b/finance/options/quasar/src/instructions/write_option.rs index 58643f52..14124009 100644 --- a/finance/options/quasar/src/instructions/write_option.rs +++ b/finance/options/quasar/src/instructions/write_option.rs @@ -2,7 +2,7 @@ use { crate::{ constants::STATUS_LISTED, errors::OptionsError, - instructions::shared::{add_locked, check_custody, require_valid_kind, Terms}, + instructions::shared::{add_owed, check_custody, require_valid_kind, Terms}, state::{Market, OptionContract, OptionContractInner}, }, quasar_lang::{prelude::*, sysvars::Sysvar as _}, @@ -117,14 +117,14 @@ pub fn handle_write_option( let mut underlying_after = accounts.underlying_vault.amount(); let mut quote_after = accounts.quote_vault.amount(); if terms.is_call() { - add_locked( - &mut accounts.market.underlying_locked, + add_owed( + &mut accounts.market.underlying_owed, &mut underlying_after, collateral, )?; } else { - add_locked( - &mut accounts.market.quote_locked, + add_owed( + &mut accounts.market.quote_owed, &mut quote_after, collateral, )?; diff --git a/finance/options/quasar/src/state.rs b/finance/options/quasar/src/state.rs index 21e92f70..3a3070e4 100644 --- a/finance/options/quasar/src/state.rs +++ b/finance/options/quasar/src/state.rs @@ -14,10 +14,10 @@ pub struct Market { pub quote_vault: Address, /// Underlying minor units the vault owes: call writers' collateral, plus /// put holders' deliveries awaiting the writer's `collect_proceeds`. - pub underlying_locked: u64, + pub underlying_owed: u64, /// Quote minor units the vault owes: put writers' collateral, plus call /// holders' strike payments awaiting the writer's `collect_proceeds`. - pub quote_locked: u64, + pub quote_owed: u64, /// Quote minor units held for the admin, swept by `collect_fees`. pub fees_owed: u64, /// Fee charged on each premium, in basis points. diff --git a/finance/options/quasar/src/tests.rs b/finance/options/quasar/src/tests.rs index 1c26e0d0..55569b54 100644 --- a/finance/options/quasar/src/tests.rs +++ b/finance/options/quasar/src/tests.rs @@ -310,13 +310,13 @@ fn assert_vaults_match_ledger(test: &Test, env: &Env) { let market = test.read::(env.market); assert_eq!( test.tokens(env.underlying_vault), - u64::from(market.underlying_locked), - "underlying vault must hold exactly the locked underlying" + u64::from(market.underlying_owed), + "underlying vault must hold exactly the underlying owed" ); assert_eq!( test.tokens(env.quote_vault), - u64::from(market.quote_locked) + u64::from(market.fees_owed), - "quote vault must hold exactly the locked quote plus the fees owed" + u64::from(market.quote_owed) + u64::from(market.fees_owed), + "quote vault must hold exactly the quote owed plus the fees owed" ); } @@ -353,7 +353,7 @@ fn write_call_moves_underlying_into_vault(test: &mut Test) { assert_eq!(u64::from(state.premium), CALL_PREMIUM); assert_eq!(i64::from(state.expiry), EXPIRY); assert_eq!( - u64::from(test.read::(env.market).underlying_locked), + u64::from(test.read::(env.market).underlying_owed), FIVE_NVDAX ); assert_vaults_match_ledger(test, &env); @@ -399,8 +399,8 @@ fn exercise_call_swaps_the_strike_for_the_underlying(test: &mut Test) { .has_tokens(env.quote_vault, strike_total + 250_000); let market = test.read::(env.market); - assert_eq!(u64::from(market.underlying_locked), 0); - assert_eq!(u64::from(market.quote_locked), strike_total); + assert_eq!(u64::from(market.underlying_owed), 0); + assert_eq!(u64::from(market.quote_owed), strike_total); assert_eq!(test.read::(option).status, STATUS_EXERCISED); assert_vaults_match_ledger(test, &env); } @@ -423,13 +423,13 @@ fn collect_proceeds_pays_the_writer_and_closes_the_option(test: &mut Test) { .is_closed(option); let market = test.read::(env.market); - assert_eq!(u64::from(market.quote_locked), 0); + assert_eq!(u64::from(market.quote_owed), 0); assert_eq!(u64::from(market.fees_owed), 250_000); assert_vaults_match_ledger(test, &env); } /// Maria sweeps the venue's fee. Only the 0.25 USDC of fees leaves the -/// vault; the strike payment sitting beside it stays locked to Alice. +/// vault; the strike payment sitting beside it stays owed to Alice. #[quasar_test] fn collect_fees_pays_only_the_fees_owed(test: &mut Test) { let env = setup(test); @@ -477,8 +477,8 @@ fn put_lifecycle_delivers_the_underlying_for_the_strike(test: &mut Test) { .has_tokens(env.underlying_vault, FIVE_NVDAX) .has_tokens(env.quote_vault, fee); let market = test.read::(env.market); - assert_eq!(u64::from(market.underlying_locked), FIVE_NVDAX); - assert_eq!(u64::from(market.quote_locked), 0); + assert_eq!(u64::from(market.underlying_owed), FIVE_NVDAX); + assert_eq!(u64::from(market.quote_owed), 0); assert_vaults_match_ledger(test, &env); collect_proceeds(test, &env, &CAROL_P, PUT_ID) @@ -486,7 +486,7 @@ fn put_lifecycle_delivers_the_underlying_for_the_strike(test: &mut Test) { .has_tokens(CAROL_NVDAX, FIVE_NVDAX) .is_closed(option); assert_eq!( - u64::from(test.read::(env.market).underlying_locked), + u64::from(test.read::(env.market).underlying_owed), 0 ); assert_vaults_match_ledger(test, &env); @@ -509,7 +509,7 @@ fn reclaim_collateral_after_expiry_returns_it_to_the_writer(test: &mut Test) { // Bob is not part of the reclaim: he is left with nothing to claim. assert_eq!(test.tokens(BOB_USDC), STANDARD_USDC - CALL_PREMIUM); assert_eq!( - u64::from(test.read::(env.market).underlying_locked), + u64::from(test.read::(env.market).underlying_owed), 0 ); assert_vaults_match_ledger(test, &env); @@ -583,7 +583,7 @@ fn cancel_unsold_option_works_after_expiry(test: &mut Test) { cancel_option(test, &env, &CAROL_P, PUT_ID) .succeeds() .has_tokens(CAROL_USDC, STANDARD_USDC); - assert_eq!(u64::from(test.read::(env.market).quote_locked), 0); + assert_eq!(u64::from(test.read::(env.market).quote_owed), 0); assert_vaults_match_ledger(test, &env); }