feat(amm): create TWAP price observations on behalf of the pool

Add a `CreatePriceObservations` instruction that registers a TWAP
price-observations account for a pool over a time window, via a chained
call to the configured TWAP oracle program. The pool acts as the price
source: the AMM authorizes it with its pool PDA seed so the oracle ties
the feed to that pool.

The feed's initial tick is read from the pool's authoritative
`CurrentTickAccount` (validated against its pool-derived PDA) rather than
being supplied by the caller, so the feed cannot be seeded at a forged
price — mirroring what `RecordTick` does. The clock is verified to be the
canonical 1-block LEZ clock, and creation is rejected if the observations
account already exists.

To support the chained call, `AmmConfig` and the `Initialize` instruction
are extended with a `twap_oracle_program_id` that the instruction reads.
This commit is contained in:
r4bbit
2026-06-22 09:47:45 +02:00
parent 1d9e3dcb49
commit 4e4338945d
13 changed files with 1554 additions and 69 deletions
+748 -52
View File
File diff suppressed because it is too large Load Diff
+37
View File
@@ -29,11 +29,13 @@ mod amm {
ctx: ProgramContext,
config: AccountWithMetadata,
token_program_id: ProgramId,
twap_oracle_program_id: ProgramId,
authority: AccountId,
) -> SpelResult {
let post_states = amm_program::initialize::initialize(
config,
token_program_id,
twap_oracle_program_id,
authority,
ctx.self_program_id,
);
@@ -51,18 +53,53 @@ mod amm {
config: AccountWithMetadata,
authority: AccountWithMetadata,
token_program_id: Option<ProgramId>,
twap_oracle_program_id: Option<ProgramId>,
new_authority: Option<AccountId>,
) -> SpelResult {
let post_states = amm_program::update_config::update_config(
config,
authority,
token_program_id,
twap_oracle_program_id,
new_authority,
ctx.self_program_id,
);
Ok(spel_framework::SpelOutput::execute(post_states, vec![]))
}
/// Creates a TWAP price-observations account for a pool over a time window, on behalf of the
/// AMM, via a chained call to the configured TWAP oracle program.
///
/// Expected accounts:
/// 1. `config` — initialized AMM config account.
/// 2. `pool` — initialized AMM pool; acts as the (authorized) price source.
/// 3. `current_tick_account` — the pool's initialized TWAP current-tick PDA; supplies the
/// initial tick.
/// 4. `price_observations` — uninitialized TWAP PDA for `(pool, window_duration)`.
/// 5. `clock` — the canonical 1-block LEZ clock account.
#[instruction]
pub fn create_price_observations(
ctx: ProgramContext,
config: AccountWithMetadata,
pool: AccountWithMetadata,
current_tick_account: AccountWithMetadata,
price_observations: AccountWithMetadata,
clock: AccountWithMetadata,
window_duration: u64,
) -> SpelResult {
let (post_states, chained_calls) =
amm_program::create_price_observations::create_price_observations(
config,
pool,
current_tick_account,
price_observations,
clock,
window_duration,
ctx.self_program_id,
);
Ok(spel_framework::SpelOutput::execute(post_states, chained_calls))
}
/// Initializes a new Pool (or re-initializes an existing zero-supply Pool).
/// A fresh user LP holding must be explicitly authorized by the caller.
#[expect(