feat(twap-oracle): implement CreateOraclePriceAccount instruction

Adds the CreateOraclePriceAccount instruction to the TWAP oracle program.
The instruction initialises a canonical OraclePriceAccount PDA for a given
price source and time window. The account starts with price = 0 and
timestamp = 0 — a deliberately invalid sentinel state that signals "not
yet published". Consumers are expected to reject any account whose
timestamp is zero or stale, so the transient invalid state requires no
special on-chain enforcement.

- PDA mirrors PriceObservations: derived from (oracle_program_id,
  price_source_id, window_duration) with a distinct seed constant, so
  each (source, window) pair maps to a distinct oracle price account that
  cannot collide with its corresponding observations account.

- source_id is not a parameter: it is always set to price_source.account_id.
  Accepting it as a free parameter would allow callers to register a price
  account that claims to represent a source it does not control. Deriving
  it from the authorized price source account closes that vector entirely.

- Authorization follows the same model as CreatePriceObservations:
  is_authorized = true on the price source proves the caller controls it;
  the PDA check ensures the supplied oracle price account address is the
  one derived from that specific source and window.

- price = 0 / timestamp = 0 is the correct initial state: coupling account
  creation to first publication would require the observation account to
  already hold a full window of ticks, blocking registration for up to the
  window duration. Consumers must validate oracle prices regardless, so the
  zero sentinel falls naturally out of the staleness check they already own.

Closes #129
This commit is contained in:
r4bbit
2026-05-29 09:32:43 +02:00
parent c59316b4a2
commit bb55bd3ccd
6 changed files with 516 additions and 19 deletions
@@ -1,6 +1,6 @@
#![cfg_attr(not(test), no_main)]
use nssa_core::account::AccountWithMetadata;
use nssa_core::account::{AccountId, AccountWithMetadata};
use spel_framework::context::ProgramContext;
use spel_framework::prelude::*;
@@ -39,4 +39,33 @@ mod twap_oracle {
);
Ok(spel_framework::SpelOutput::execute(post_states, vec![]))
}
/// Creates and initialises a canonical oracle price account for a price source and time
/// window.
///
/// Expected accounts:
/// 1. `oracle_price_account` — uninitialized PDA owned by this oracle program.
/// 2. `price_source` — account the caller controls (proven via `is_authorized = true`);
/// its ID ties this price account to the same source as the corresponding
/// `PriceObservations` account for the same window.
#[instruction]
pub fn create_oracle_price_account(
ctx: ProgramContext,
oracle_price_account: AccountWithMetadata,
price_source: AccountWithMetadata,
base_asset: AccountId,
quote_asset: AccountId,
window_duration: u64,
) -> SpelResult {
let post_states =
twap_oracle_program::create_oracle_price_account::create_oracle_price_account(
oracle_price_account,
price_source,
base_asset,
quote_asset,
window_duration,
ctx.self_program_id,
);
Ok(spel_framework::SpelOutput::execute(post_states, vec![]))
}
}