2026-05-06 17:08:15 -03:00
|
|
|
#![cfg_attr(not(test), no_main)]
|
2026-06-26 16:55:23 -03:00
|
|
|
#![allow(
|
|
|
|
|
clippy::cloned_ref_to_slice_refs,
|
|
|
|
|
reason = "SPEL macro emits cloned validation slices for one-account instructions"
|
|
|
|
|
)]
|
2026-03-17 18:08:53 +01:00
|
|
|
|
|
|
|
|
use std::num::NonZeroU128;
|
|
|
|
|
|
|
|
|
|
use spel_framework::prelude::*;
|
2026-05-04 10:34:10 -03:00
|
|
|
use spel_framework::context::ProgramContext;
|
2026-03-17 18:08:53 +01:00
|
|
|
use nssa_core::{
|
|
|
|
|
account::{AccountId, AccountWithMetadata},
|
feat(amm): add Initialize instruction with config-gated chained calls
Introduce a singleton AMM configuration account, a PDA derived from the
constant "CONFIG" seed, created once via a new `Initialize` instruction.
The config stores the Token Program ID the AMM issues every chained call
to, replacing the previous behavior of trusting the program owner of a
caller-supplied holding.
The config account's existence is the Program's initialization gate: the
chained-call instructions (new_definition, add_liquidity, remove_liquidity,
swap_exact_input, swap_exact_output) now take the config as their first
account, validate it against `compute_config_pda(self_program_id)`, and
read the Token Program ID from it on demand — rejecting calls until the
Program is initialized. Vaults and user holdings are asserted to match the
configured Token Program. sync_reserves is left ungated, as it cannot act
on a pool that could not have existed before initialization.
- amm_core: AmmConfig type, compute_config_pda/_seed, Initialize variant
- amm: initialize.rs + config threading through chained-call instructions
- guest: initialize instruction; config + self_program_id on gated calls
- tests: config fixtures, init-gate unit tests, end-to-end Initialize VM test
2026-06-17 16:29:30 +02:00
|
|
|
program::ProgramId,
|
2026-03-17 18:08:53 +01:00
|
|
|
};
|
|
|
|
|
|
2026-05-06 17:08:15 -03:00
|
|
|
#[cfg(not(test))]
|
2026-03-17 18:08:53 +01:00
|
|
|
risc0_zkvm::guest::entry!(main);
|
|
|
|
|
|
|
|
|
|
#[lez_program(instruction = "amm_core::Instruction")]
|
|
|
|
|
mod amm {
|
2026-05-06 17:08:15 -03:00
|
|
|
#[expect(
|
|
|
|
|
unused_imports,
|
|
|
|
|
reason = "SPEL instruction macro requires importing parent-scope handler types"
|
|
|
|
|
)]
|
2026-03-17 18:08:53 +01:00
|
|
|
use super::*;
|
|
|
|
|
|
feat(amm): add Initialize instruction with config-gated chained calls
Introduce a singleton AMM configuration account, a PDA derived from the
constant "CONFIG" seed, created once via a new `Initialize` instruction.
The config stores the Token Program ID the AMM issues every chained call
to, replacing the previous behavior of trusting the program owner of a
caller-supplied holding.
The config account's existence is the Program's initialization gate: the
chained-call instructions (new_definition, add_liquidity, remove_liquidity,
swap_exact_input, swap_exact_output) now take the config as their first
account, validate it against `compute_config_pda(self_program_id)`, and
read the Token Program ID from it on demand — rejecting calls until the
Program is initialized. Vaults and user holdings are asserted to match the
configured Token Program. sync_reserves is left ungated, as it cannot act
on a pool that could not have existed before initialization.
- amm_core: AmmConfig type, compute_config_pda/_seed, Initialize variant
- amm: initialize.rs + config threading through chained-call instructions
- guest: initialize instruction; config + self_program_id on gated calls
- tests: config fixtures, init-gate unit tests, end-to-end Initialize VM test
2026-06-17 16:29:30 +02:00
|
|
|
/// Initializes the AMM Program by creating its singleton config account.
|
|
|
|
|
///
|
|
|
|
|
/// Expected accounts:
|
|
|
|
|
/// 1. `config` — uninitialized config PDA derived from `compute_config_pda(self_program_id)`.
|
|
|
|
|
#[instruction]
|
|
|
|
|
pub fn initialize(
|
|
|
|
|
ctx: ProgramContext,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(init)]
|
feat(amm): add Initialize instruction with config-gated chained calls
Introduce a singleton AMM configuration account, a PDA derived from the
constant "CONFIG" seed, created once via a new `Initialize` instruction.
The config stores the Token Program ID the AMM issues every chained call
to, replacing the previous behavior of trusting the program owner of a
caller-supplied holding.
The config account's existence is the Program's initialization gate: the
chained-call instructions (new_definition, add_liquidity, remove_liquidity,
swap_exact_input, swap_exact_output) now take the config as their first
account, validate it against `compute_config_pda(self_program_id)`, and
read the Token Program ID from it on demand — rejecting calls until the
Program is initialized. Vaults and user holdings are asserted to match the
configured Token Program. sync_reserves is left ungated, as it cannot act
on a pool that could not have existed before initialization.
- amm_core: AmmConfig type, compute_config_pda/_seed, Initialize variant
- amm: initialize.rs + config threading through chained-call instructions
- guest: initialize instruction; config + self_program_id on gated calls
- tests: config fixtures, init-gate unit tests, end-to-end Initialize VM test
2026-06-17 16:29:30 +02:00
|
|
|
config: AccountWithMetadata,
|
|
|
|
|
token_program_id: ProgramId,
|
2026-06-18 09:01:10 +02:00
|
|
|
twap_oracle_program_id: ProgramId,
|
2026-06-18 16:46:38 +02:00
|
|
|
authority: AccountId,
|
feat(amm): add Initialize instruction with config-gated chained calls
Introduce a singleton AMM configuration account, a PDA derived from the
constant "CONFIG" seed, created once via a new `Initialize` instruction.
The config stores the Token Program ID the AMM issues every chained call
to, replacing the previous behavior of trusting the program owner of a
caller-supplied holding.
The config account's existence is the Program's initialization gate: the
chained-call instructions (new_definition, add_liquidity, remove_liquidity,
swap_exact_input, swap_exact_output) now take the config as their first
account, validate it against `compute_config_pda(self_program_id)`, and
read the Token Program ID from it on demand — rejecting calls until the
Program is initialized. Vaults and user holdings are asserted to match the
configured Token Program. sync_reserves is left ungated, as it cannot act
on a pool that could not have existed before initialization.
- amm_core: AmmConfig type, compute_config_pda/_seed, Initialize variant
- amm: initialize.rs + config threading through chained-call instructions
- guest: initialize instruction; config + self_program_id on gated calls
- tests: config fixtures, init-gate unit tests, end-to-end Initialize VM test
2026-06-17 16:29:30 +02:00
|
|
|
) -> SpelResult {
|
2026-06-18 16:46:38 +02:00
|
|
|
let post_states = amm_program::initialize::initialize(
|
|
|
|
|
config,
|
|
|
|
|
token_program_id,
|
2026-06-18 09:01:10 +02:00
|
|
|
twap_oracle_program_id,
|
2026-06-18 16:46:38 +02:00
|
|
|
authority,
|
|
|
|
|
ctx.self_program_id,
|
|
|
|
|
);
|
|
|
|
|
Ok(spel_framework::SpelOutput::execute(post_states, vec![]))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Updates the AMM Program's configuration. Only the configured admin authority may call this.
|
|
|
|
|
///
|
|
|
|
|
/// Expected accounts:
|
|
|
|
|
/// 1. `config` — initialized AMM config account.
|
|
|
|
|
/// 2. `authority` — the config's current admin, passed authorized (signed).
|
|
|
|
|
#[instruction]
|
|
|
|
|
pub fn update_config(
|
|
|
|
|
ctx: ProgramContext,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut)]
|
2026-06-18 16:46:38 +02:00
|
|
|
config: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(signer)]
|
2026-06-18 16:46:38 +02:00
|
|
|
authority: AccountWithMetadata,
|
|
|
|
|
token_program_id: Option<ProgramId>,
|
2026-06-18 09:01:10 +02:00
|
|
|
twap_oracle_program_id: Option<ProgramId>,
|
2026-06-18 16:46:38 +02:00
|
|
|
new_authority: Option<AccountId>,
|
|
|
|
|
) -> SpelResult {
|
|
|
|
|
let post_states = amm_program::update_config::update_config(
|
|
|
|
|
config,
|
|
|
|
|
authority,
|
|
|
|
|
token_program_id,
|
2026-06-18 09:01:10 +02:00
|
|
|
twap_oracle_program_id,
|
2026-06-18 16:46:38 +02:00
|
|
|
new_authority,
|
|
|
|
|
ctx.self_program_id,
|
|
|
|
|
);
|
feat(amm): add Initialize instruction with config-gated chained calls
Introduce a singleton AMM configuration account, a PDA derived from the
constant "CONFIG" seed, created once via a new `Initialize` instruction.
The config stores the Token Program ID the AMM issues every chained call
to, replacing the previous behavior of trusting the program owner of a
caller-supplied holding.
The config account's existence is the Program's initialization gate: the
chained-call instructions (new_definition, add_liquidity, remove_liquidity,
swap_exact_input, swap_exact_output) now take the config as their first
account, validate it against `compute_config_pda(self_program_id)`, and
read the Token Program ID from it on demand — rejecting calls until the
Program is initialized. Vaults and user holdings are asserted to match the
configured Token Program. sync_reserves is left ungated, as it cannot act
on a pool that could not have existed before initialization.
- amm_core: AmmConfig type, compute_config_pda/_seed, Initialize variant
- amm: initialize.rs + config threading through chained-call instructions
- guest: initialize instruction; config + self_program_id on gated calls
- tests: config fixtures, init-gate unit tests, end-to-end Initialize VM test
2026-06-17 16:29:30 +02:00
|
|
|
Ok(spel_framework::SpelOutput::execute(post_states, vec![]))
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-18 09:01:10 +02:00
|
|
|
/// 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,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(init)]
|
2026-06-18 09:01:10 +02:00
|
|
|
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))
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-23 17:28:22 +02:00
|
|
|
/// Creates a TWAP oracle price 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 and supplies the
|
|
|
|
|
/// asset pair and the initial (spot) price.
|
|
|
|
|
/// 3. `oracle_price_account` — uninitialized TWAP price-account PDA for `(pool, window_duration)`.
|
|
|
|
|
/// 4. `clock` — the canonical 1-block LEZ clock account.
|
|
|
|
|
#[instruction]
|
|
|
|
|
pub fn create_oracle_price_account(
|
|
|
|
|
ctx: ProgramContext,
|
|
|
|
|
config: AccountWithMetadata,
|
|
|
|
|
pool: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(init)]
|
2026-06-23 17:28:22 +02:00
|
|
|
oracle_price_account: AccountWithMetadata,
|
|
|
|
|
clock: AccountWithMetadata,
|
|
|
|
|
window_duration: u64,
|
|
|
|
|
) -> SpelResult {
|
|
|
|
|
let (post_states, chained_calls) =
|
|
|
|
|
amm_program::create_oracle_price_account::create_oracle_price_account(
|
|
|
|
|
config,
|
|
|
|
|
pool,
|
|
|
|
|
oracle_price_account,
|
|
|
|
|
clock,
|
|
|
|
|
window_duration,
|
|
|
|
|
ctx.self_program_id,
|
|
|
|
|
);
|
|
|
|
|
Ok(spel_framework::SpelOutput::execute(post_states, chained_calls))
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 15:43:13 -03:00
|
|
|
/// Initializes a new Pool (or re-initializes an existing zero-supply Pool).
|
2026-04-15 14:55:04 -03:00
|
|
|
/// A fresh user LP holding must be explicitly authorized by the caller.
|
2026-05-06 17:08:15 -03:00
|
|
|
#[expect(
|
|
|
|
|
clippy::too_many_arguments,
|
feat(amm): add Initialize instruction with config-gated chained calls
Introduce a singleton AMM configuration account, a PDA derived from the
constant "CONFIG" seed, created once via a new `Initialize` instruction.
The config stores the Token Program ID the AMM issues every chained call
to, replacing the previous behavior of trusting the program owner of a
caller-supplied holding.
The config account's existence is the Program's initialization gate: the
chained-call instructions (new_definition, add_liquidity, remove_liquidity,
swap_exact_input, swap_exact_output) now take the config as their first
account, validate it against `compute_config_pda(self_program_id)`, and
read the Token Program ID from it on demand — rejecting calls until the
Program is initialized. Vaults and user holdings are asserted to match the
configured Token Program. sync_reserves is left ungated, as it cannot act
on a pool that could not have existed before initialization.
- amm_core: AmmConfig type, compute_config_pda/_seed, Initialize variant
- amm: initialize.rs + config threading through chained-call instructions
- guest: initialize instruction; config + self_program_id on gated calls
- tests: config fixtures, init-gate unit tests, end-to-end Initialize VM test
2026-06-17 16:29:30 +02:00
|
|
|
reason = "instruction interface requires explicit config, pool, vault, mint, lock, and user accounts"
|
2026-05-06 17:08:15 -03:00
|
|
|
)]
|
2026-03-17 18:08:53 +01:00
|
|
|
#[instruction]
|
|
|
|
|
pub fn new_definition(
|
2026-05-04 10:34:10 -03:00
|
|
|
ctx: ProgramContext,
|
feat(amm): add Initialize instruction with config-gated chained calls
Introduce a singleton AMM configuration account, a PDA derived from the
constant "CONFIG" seed, created once via a new `Initialize` instruction.
The config stores the Token Program ID the AMM issues every chained call
to, replacing the previous behavior of trusting the program owner of a
caller-supplied holding.
The config account's existence is the Program's initialization gate: the
chained-call instructions (new_definition, add_liquidity, remove_liquidity,
swap_exact_input, swap_exact_output) now take the config as their first
account, validate it against `compute_config_pda(self_program_id)`, and
read the Token Program ID from it on demand — rejecting calls until the
Program is initialized. Vaults and user holdings are asserted to match the
configured Token Program. sync_reserves is left ungated, as it cannot act
on a pool that could not have existed before initialization.
- amm_core: AmmConfig type, compute_config_pda/_seed, Initialize variant
- amm: initialize.rs + config threading through chained-call instructions
- guest: initialize instruction; config + self_program_id on gated calls
- tests: config fixtures, init-gate unit tests, end-to-end Initialize VM test
2026-06-17 16:29:30 +02:00
|
|
|
config: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(init)]
|
2026-03-17 18:08:53 +01:00
|
|
|
pool: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut)]
|
2026-03-17 18:08:53 +01:00
|
|
|
vault_a: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut)]
|
2026-03-17 18:08:53 +01:00
|
|
|
vault_b: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(init)]
|
2026-03-17 18:08:53 +01:00
|
|
|
pool_definition_lp: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(init)]
|
2026-04-08 17:48:13 -03:00
|
|
|
lp_lock_holding: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut, signer)]
|
2026-03-17 18:08:53 +01:00
|
|
|
user_holding_a: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut, signer)]
|
2026-03-17 18:08:53 +01:00
|
|
|
user_holding_b: AccountWithMetadata,
|
2026-06-28 23:49:39 +02:00
|
|
|
#[account(mut, signer)]
|
2026-03-17 18:08:53 +01:00
|
|
|
user_holding_lp: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(init)]
|
2026-06-18 14:07:04 +02:00
|
|
|
current_tick_account: AccountWithMetadata,
|
|
|
|
|
clock: AccountWithMetadata,
|
2026-03-17 18:08:53 +01:00
|
|
|
token_a_amount: u128,
|
|
|
|
|
token_b_amount: u128,
|
2026-03-31 20:45:57 -03:00
|
|
|
fees: u128,
|
2026-04-23 17:19:15 +02:00
|
|
|
deadline: u64,
|
2026-03-17 18:08:53 +01:00
|
|
|
) -> SpelResult {
|
|
|
|
|
let (post_states, chained_calls) = amm_program::new_definition::new_definition(
|
feat(amm): add Initialize instruction with config-gated chained calls
Introduce a singleton AMM configuration account, a PDA derived from the
constant "CONFIG" seed, created once via a new `Initialize` instruction.
The config stores the Token Program ID the AMM issues every chained call
to, replacing the previous behavior of trusting the program owner of a
caller-supplied holding.
The config account's existence is the Program's initialization gate: the
chained-call instructions (new_definition, add_liquidity, remove_liquidity,
swap_exact_input, swap_exact_output) now take the config as their first
account, validate it against `compute_config_pda(self_program_id)`, and
read the Token Program ID from it on demand — rejecting calls until the
Program is initialized. Vaults and user holdings are asserted to match the
configured Token Program. sync_reserves is left ungated, as it cannot act
on a pool that could not have existed before initialization.
- amm_core: AmmConfig type, compute_config_pda/_seed, Initialize variant
- amm: initialize.rs + config threading through chained-call instructions
- guest: initialize instruction; config + self_program_id on gated calls
- tests: config fixtures, init-gate unit tests, end-to-end Initialize VM test
2026-06-17 16:29:30 +02:00
|
|
|
config,
|
2026-03-17 18:08:53 +01:00
|
|
|
pool,
|
|
|
|
|
vault_a,
|
|
|
|
|
vault_b,
|
|
|
|
|
pool_definition_lp,
|
2026-04-08 17:48:13 -03:00
|
|
|
lp_lock_holding,
|
2026-03-17 18:08:53 +01:00
|
|
|
user_holding_a,
|
|
|
|
|
user_holding_b,
|
|
|
|
|
user_holding_lp,
|
2026-06-18 14:07:04 +02:00
|
|
|
current_tick_account,
|
|
|
|
|
clock,
|
2026-03-17 18:08:53 +01:00
|
|
|
NonZeroU128::new(token_a_amount).expect("token_a_amount must be nonzero"),
|
|
|
|
|
NonZeroU128::new(token_b_amount).expect("token_b_amount must be nonzero"),
|
2026-03-31 20:45:57 -03:00
|
|
|
fees,
|
2026-05-04 10:34:10 -03:00
|
|
|
ctx.self_program_id,
|
2026-03-17 18:08:53 +01:00
|
|
|
);
|
2026-05-12 11:33:19 +02:00
|
|
|
Ok(spel_framework::SpelOutput::execute(post_states, chained_calls)
|
2026-04-23 17:19:15 +02:00
|
|
|
.with_timestamp_validity_window(..deadline))
|
2026-03-17 18:08:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Adds liquidity to the Pool.
|
2026-05-06 17:08:15 -03:00
|
|
|
#[expect(
|
|
|
|
|
clippy::too_many_arguments,
|
|
|
|
|
reason = "instruction interface requires explicit pool, vault, and user accounts"
|
|
|
|
|
)]
|
2026-03-17 18:08:53 +01:00
|
|
|
#[instruction]
|
|
|
|
|
pub fn add_liquidity(
|
feat(amm): add Initialize instruction with config-gated chained calls
Introduce a singleton AMM configuration account, a PDA derived from the
constant "CONFIG" seed, created once via a new `Initialize` instruction.
The config stores the Token Program ID the AMM issues every chained call
to, replacing the previous behavior of trusting the program owner of a
caller-supplied holding.
The config account's existence is the Program's initialization gate: the
chained-call instructions (new_definition, add_liquidity, remove_liquidity,
swap_exact_input, swap_exact_output) now take the config as their first
account, validate it against `compute_config_pda(self_program_id)`, and
read the Token Program ID from it on demand — rejecting calls until the
Program is initialized. Vaults and user holdings are asserted to match the
configured Token Program. sync_reserves is left ungated, as it cannot act
on a pool that could not have existed before initialization.
- amm_core: AmmConfig type, compute_config_pda/_seed, Initialize variant
- amm: initialize.rs + config threading through chained-call instructions
- guest: initialize instruction; config + self_program_id on gated calls
- tests: config fixtures, init-gate unit tests, end-to-end Initialize VM test
2026-06-17 16:29:30 +02:00
|
|
|
ctx: ProgramContext,
|
|
|
|
|
config: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut)]
|
2026-03-17 18:08:53 +01:00
|
|
|
pool: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut)]
|
2026-03-17 18:08:53 +01:00
|
|
|
vault_a: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut)]
|
2026-03-17 18:08:53 +01:00
|
|
|
vault_b: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut)]
|
2026-03-17 18:08:53 +01:00
|
|
|
pool_definition_lp: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut, signer)]
|
2026-03-17 18:08:53 +01:00
|
|
|
user_holding_a: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut, signer)]
|
2026-03-17 18:08:53 +01:00
|
|
|
user_holding_b: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut)]
|
2026-03-17 18:08:53 +01:00
|
|
|
user_holding_lp: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut)]
|
feat(amm): refresh TWAP current tick on every reserve-mutating instruction
Make swap_exact_input, swap_exact_output, add_liquidity, remove_liquidity,
and sync_reserves keep the pool's TWAP current tick in sync with its
reserves. Each now takes the current-tick and clock accounts, reads the
TWAP program ID from the config account, validates the clock account and
the current-tick PDA, and after computing the post-op pool chains an
UpdateCurrentTick to the oracle carrying the post-op spot price, with the
pool passed as the authorized price source via its pool PDA seed.
sync_reserves additionally now takes the config account so it can resolve
the TWAP program ID and gate on initialization, consistent with the other
instructions.
The invariant current_tick == tick(reserves) therefore holds after every
operation. Proportional add/remove preserve the price, so the tick is
unchanged for them, but the refresh still runs and lands on the correct
value.
2026-06-18 15:50:11 +02:00
|
|
|
current_tick_account: AccountWithMetadata,
|
|
|
|
|
clock: AccountWithMetadata,
|
2026-03-17 18:08:53 +01:00
|
|
|
min_amount_liquidity: u128,
|
|
|
|
|
max_amount_to_add_token_a: u128,
|
|
|
|
|
max_amount_to_add_token_b: u128,
|
2026-04-23 17:19:15 +02:00
|
|
|
deadline: u64,
|
2026-03-17 18:08:53 +01:00
|
|
|
) -> SpelResult {
|
|
|
|
|
let (post_states, chained_calls) = amm_program::add::add_liquidity(
|
feat(amm): add Initialize instruction with config-gated chained calls
Introduce a singleton AMM configuration account, a PDA derived from the
constant "CONFIG" seed, created once via a new `Initialize` instruction.
The config stores the Token Program ID the AMM issues every chained call
to, replacing the previous behavior of trusting the program owner of a
caller-supplied holding.
The config account's existence is the Program's initialization gate: the
chained-call instructions (new_definition, add_liquidity, remove_liquidity,
swap_exact_input, swap_exact_output) now take the config as their first
account, validate it against `compute_config_pda(self_program_id)`, and
read the Token Program ID from it on demand — rejecting calls until the
Program is initialized. Vaults and user holdings are asserted to match the
configured Token Program. sync_reserves is left ungated, as it cannot act
on a pool that could not have existed before initialization.
- amm_core: AmmConfig type, compute_config_pda/_seed, Initialize variant
- amm: initialize.rs + config threading through chained-call instructions
- guest: initialize instruction; config + self_program_id on gated calls
- tests: config fixtures, init-gate unit tests, end-to-end Initialize VM test
2026-06-17 16:29:30 +02:00
|
|
|
config,
|
2026-03-17 18:08:53 +01:00
|
|
|
pool,
|
|
|
|
|
vault_a,
|
|
|
|
|
vault_b,
|
|
|
|
|
pool_definition_lp,
|
|
|
|
|
user_holding_a,
|
|
|
|
|
user_holding_b,
|
|
|
|
|
user_holding_lp,
|
feat(amm): refresh TWAP current tick on every reserve-mutating instruction
Make swap_exact_input, swap_exact_output, add_liquidity, remove_liquidity,
and sync_reserves keep the pool's TWAP current tick in sync with its
reserves. Each now takes the current-tick and clock accounts, reads the
TWAP program ID from the config account, validates the clock account and
the current-tick PDA, and after computing the post-op pool chains an
UpdateCurrentTick to the oracle carrying the post-op spot price, with the
pool passed as the authorized price source via its pool PDA seed.
sync_reserves additionally now takes the config account so it can resolve
the TWAP program ID and gate on initialization, consistent with the other
instructions.
The invariant current_tick == tick(reserves) therefore holds after every
operation. Proportional add/remove preserve the price, so the tick is
unchanged for them, but the refresh still runs and lands on the correct
value.
2026-06-18 15:50:11 +02:00
|
|
|
current_tick_account,
|
|
|
|
|
clock,
|
2026-03-17 18:08:53 +01:00
|
|
|
NonZeroU128::new(min_amount_liquidity).expect("min_amount_liquidity must be nonzero"),
|
|
|
|
|
max_amount_to_add_token_a,
|
|
|
|
|
max_amount_to_add_token_b,
|
feat(amm): add Initialize instruction with config-gated chained calls
Introduce a singleton AMM configuration account, a PDA derived from the
constant "CONFIG" seed, created once via a new `Initialize` instruction.
The config stores the Token Program ID the AMM issues every chained call
to, replacing the previous behavior of trusting the program owner of a
caller-supplied holding.
The config account's existence is the Program's initialization gate: the
chained-call instructions (new_definition, add_liquidity, remove_liquidity,
swap_exact_input, swap_exact_output) now take the config as their first
account, validate it against `compute_config_pda(self_program_id)`, and
read the Token Program ID from it on demand — rejecting calls until the
Program is initialized. Vaults and user holdings are asserted to match the
configured Token Program. sync_reserves is left ungated, as it cannot act
on a pool that could not have existed before initialization.
- amm_core: AmmConfig type, compute_config_pda/_seed, Initialize variant
- amm: initialize.rs + config threading through chained-call instructions
- guest: initialize instruction; config + self_program_id on gated calls
- tests: config fixtures, init-gate unit tests, end-to-end Initialize VM test
2026-06-17 16:29:30 +02:00
|
|
|
ctx.self_program_id,
|
2026-03-17 18:08:53 +01:00
|
|
|
);
|
2026-05-12 11:33:19 +02:00
|
|
|
Ok(spel_framework::SpelOutput::execute(post_states, chained_calls)
|
2026-04-23 17:19:15 +02:00
|
|
|
.with_timestamp_validity_window(..deadline))
|
2026-03-17 18:08:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Removes liquidity from the Pool.
|
2026-05-06 17:08:15 -03:00
|
|
|
#[expect(
|
|
|
|
|
clippy::too_many_arguments,
|
|
|
|
|
reason = "instruction interface requires explicit pool, vault, and user accounts"
|
|
|
|
|
)]
|
2026-03-17 18:08:53 +01:00
|
|
|
#[instruction]
|
|
|
|
|
pub fn remove_liquidity(
|
feat(amm): add Initialize instruction with config-gated chained calls
Introduce a singleton AMM configuration account, a PDA derived from the
constant "CONFIG" seed, created once via a new `Initialize` instruction.
The config stores the Token Program ID the AMM issues every chained call
to, replacing the previous behavior of trusting the program owner of a
caller-supplied holding.
The config account's existence is the Program's initialization gate: the
chained-call instructions (new_definition, add_liquidity, remove_liquidity,
swap_exact_input, swap_exact_output) now take the config as their first
account, validate it against `compute_config_pda(self_program_id)`, and
read the Token Program ID from it on demand — rejecting calls until the
Program is initialized. Vaults and user holdings are asserted to match the
configured Token Program. sync_reserves is left ungated, as it cannot act
on a pool that could not have existed before initialization.
- amm_core: AmmConfig type, compute_config_pda/_seed, Initialize variant
- amm: initialize.rs + config threading through chained-call instructions
- guest: initialize instruction; config + self_program_id on gated calls
- tests: config fixtures, init-gate unit tests, end-to-end Initialize VM test
2026-06-17 16:29:30 +02:00
|
|
|
ctx: ProgramContext,
|
|
|
|
|
config: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut)]
|
2026-03-17 18:08:53 +01:00
|
|
|
pool: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut)]
|
2026-03-17 18:08:53 +01:00
|
|
|
vault_a: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut)]
|
2026-03-17 18:08:53 +01:00
|
|
|
vault_b: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut)]
|
2026-03-17 18:08:53 +01:00
|
|
|
pool_definition_lp: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut)]
|
2026-03-17 18:08:53 +01:00
|
|
|
user_holding_a: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut)]
|
2026-03-17 18:08:53 +01:00
|
|
|
user_holding_b: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut, signer)]
|
2026-03-17 18:08:53 +01:00
|
|
|
user_holding_lp: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut)]
|
feat(amm): refresh TWAP current tick on every reserve-mutating instruction
Make swap_exact_input, swap_exact_output, add_liquidity, remove_liquidity,
and sync_reserves keep the pool's TWAP current tick in sync with its
reserves. Each now takes the current-tick and clock accounts, reads the
TWAP program ID from the config account, validates the clock account and
the current-tick PDA, and after computing the post-op pool chains an
UpdateCurrentTick to the oracle carrying the post-op spot price, with the
pool passed as the authorized price source via its pool PDA seed.
sync_reserves additionally now takes the config account so it can resolve
the TWAP program ID and gate on initialization, consistent with the other
instructions.
The invariant current_tick == tick(reserves) therefore holds after every
operation. Proportional add/remove preserve the price, so the tick is
unchanged for them, but the refresh still runs and lands on the correct
value.
2026-06-18 15:50:11 +02:00
|
|
|
current_tick_account: AccountWithMetadata,
|
|
|
|
|
clock: AccountWithMetadata,
|
2026-03-17 18:08:53 +01:00
|
|
|
remove_liquidity_amount: u128,
|
|
|
|
|
min_amount_to_remove_token_a: u128,
|
|
|
|
|
min_amount_to_remove_token_b: u128,
|
2026-04-23 17:19:15 +02:00
|
|
|
deadline: u64,
|
2026-03-17 18:08:53 +01:00
|
|
|
) -> SpelResult {
|
|
|
|
|
let (post_states, chained_calls) = amm_program::remove::remove_liquidity(
|
feat(amm): add Initialize instruction with config-gated chained calls
Introduce a singleton AMM configuration account, a PDA derived from the
constant "CONFIG" seed, created once via a new `Initialize` instruction.
The config stores the Token Program ID the AMM issues every chained call
to, replacing the previous behavior of trusting the program owner of a
caller-supplied holding.
The config account's existence is the Program's initialization gate: the
chained-call instructions (new_definition, add_liquidity, remove_liquidity,
swap_exact_input, swap_exact_output) now take the config as their first
account, validate it against `compute_config_pda(self_program_id)`, and
read the Token Program ID from it on demand — rejecting calls until the
Program is initialized. Vaults and user holdings are asserted to match the
configured Token Program. sync_reserves is left ungated, as it cannot act
on a pool that could not have existed before initialization.
- amm_core: AmmConfig type, compute_config_pda/_seed, Initialize variant
- amm: initialize.rs + config threading through chained-call instructions
- guest: initialize instruction; config + self_program_id on gated calls
- tests: config fixtures, init-gate unit tests, end-to-end Initialize VM test
2026-06-17 16:29:30 +02:00
|
|
|
config,
|
2026-03-17 18:08:53 +01:00
|
|
|
pool,
|
|
|
|
|
vault_a,
|
|
|
|
|
vault_b,
|
|
|
|
|
pool_definition_lp,
|
|
|
|
|
user_holding_a,
|
|
|
|
|
user_holding_b,
|
|
|
|
|
user_holding_lp,
|
feat(amm): refresh TWAP current tick on every reserve-mutating instruction
Make swap_exact_input, swap_exact_output, add_liquidity, remove_liquidity,
and sync_reserves keep the pool's TWAP current tick in sync with its
reserves. Each now takes the current-tick and clock accounts, reads the
TWAP program ID from the config account, validates the clock account and
the current-tick PDA, and after computing the post-op pool chains an
UpdateCurrentTick to the oracle carrying the post-op spot price, with the
pool passed as the authorized price source via its pool PDA seed.
sync_reserves additionally now takes the config account so it can resolve
the TWAP program ID and gate on initialization, consistent with the other
instructions.
The invariant current_tick == tick(reserves) therefore holds after every
operation. Proportional add/remove preserve the price, so the tick is
unchanged for them, but the refresh still runs and lands on the correct
value.
2026-06-18 15:50:11 +02:00
|
|
|
current_tick_account,
|
|
|
|
|
clock,
|
2026-03-17 18:08:53 +01:00
|
|
|
NonZeroU128::new(remove_liquidity_amount)
|
|
|
|
|
.expect("remove_liquidity_amount must be nonzero"),
|
|
|
|
|
min_amount_to_remove_token_a,
|
|
|
|
|
min_amount_to_remove_token_b,
|
feat(amm): add Initialize instruction with config-gated chained calls
Introduce a singleton AMM configuration account, a PDA derived from the
constant "CONFIG" seed, created once via a new `Initialize` instruction.
The config stores the Token Program ID the AMM issues every chained call
to, replacing the previous behavior of trusting the program owner of a
caller-supplied holding.
The config account's existence is the Program's initialization gate: the
chained-call instructions (new_definition, add_liquidity, remove_liquidity,
swap_exact_input, swap_exact_output) now take the config as their first
account, validate it against `compute_config_pda(self_program_id)`, and
read the Token Program ID from it on demand — rejecting calls until the
Program is initialized. Vaults and user holdings are asserted to match the
configured Token Program. sync_reserves is left ungated, as it cannot act
on a pool that could not have existed before initialization.
- amm_core: AmmConfig type, compute_config_pda/_seed, Initialize variant
- amm: initialize.rs + config threading through chained-call instructions
- guest: initialize instruction; config + self_program_id on gated calls
- tests: config fixtures, init-gate unit tests, end-to-end Initialize VM test
2026-06-17 16:29:30 +02:00
|
|
|
ctx.self_program_id,
|
2026-03-17 18:08:53 +01:00
|
|
|
);
|
2026-05-12 11:33:19 +02:00
|
|
|
Ok(spel_framework::SpelOutput::execute(post_states, chained_calls)
|
2026-04-23 17:19:15 +02:00
|
|
|
.with_timestamp_validity_window(..deadline))
|
2026-03-17 18:08:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Swap some quantity of tokens while maintaining the pool constant product.
|
2026-05-06 17:08:15 -03:00
|
|
|
#[expect(
|
|
|
|
|
clippy::too_many_arguments,
|
|
|
|
|
reason = "instruction interface requires explicit pool, vault, user accounts, and bounds"
|
|
|
|
|
)]
|
2026-03-17 18:08:53 +01:00
|
|
|
#[instruction]
|
2026-04-07 09:31:32 +02:00
|
|
|
pub fn swap_exact_input(
|
feat(amm): add Initialize instruction with config-gated chained calls
Introduce a singleton AMM configuration account, a PDA derived from the
constant "CONFIG" seed, created once via a new `Initialize` instruction.
The config stores the Token Program ID the AMM issues every chained call
to, replacing the previous behavior of trusting the program owner of a
caller-supplied holding.
The config account's existence is the Program's initialization gate: the
chained-call instructions (new_definition, add_liquidity, remove_liquidity,
swap_exact_input, swap_exact_output) now take the config as their first
account, validate it against `compute_config_pda(self_program_id)`, and
read the Token Program ID from it on demand — rejecting calls until the
Program is initialized. Vaults and user holdings are asserted to match the
configured Token Program. sync_reserves is left ungated, as it cannot act
on a pool that could not have existed before initialization.
- amm_core: AmmConfig type, compute_config_pda/_seed, Initialize variant
- amm: initialize.rs + config threading through chained-call instructions
- guest: initialize instruction; config + self_program_id on gated calls
- tests: config fixtures, init-gate unit tests, end-to-end Initialize VM test
2026-06-17 16:29:30 +02:00
|
|
|
ctx: ProgramContext,
|
|
|
|
|
config: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut)]
|
2026-03-17 18:08:53 +01:00
|
|
|
pool: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut)]
|
2026-03-17 18:08:53 +01:00
|
|
|
vault_a: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut)]
|
2026-03-17 18:08:53 +01:00
|
|
|
vault_b: AccountWithMetadata,
|
2026-06-28 23:49:39 +02:00
|
|
|
#[account(mut, signer)]
|
2026-03-17 18:08:53 +01:00
|
|
|
user_holding_a: AccountWithMetadata,
|
2026-06-28 23:49:39 +02:00
|
|
|
#[account(mut, signer)]
|
2026-03-17 18:08:53 +01:00
|
|
|
user_holding_b: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut)]
|
feat(amm): refresh TWAP current tick on every reserve-mutating instruction
Make swap_exact_input, swap_exact_output, add_liquidity, remove_liquidity,
and sync_reserves keep the pool's TWAP current tick in sync with its
reserves. Each now takes the current-tick and clock accounts, reads the
TWAP program ID from the config account, validates the clock account and
the current-tick PDA, and after computing the post-op pool chains an
UpdateCurrentTick to the oracle carrying the post-op spot price, with the
pool passed as the authorized price source via its pool PDA seed.
sync_reserves additionally now takes the config account so it can resolve
the TWAP program ID and gate on initialization, consistent with the other
instructions.
The invariant current_tick == tick(reserves) therefore holds after every
operation. Proportional add/remove preserve the price, so the tick is
unchanged for them, but the refresh still runs and lands on the correct
value.
2026-06-18 15:50:11 +02:00
|
|
|
current_tick_account: AccountWithMetadata,
|
|
|
|
|
clock: AccountWithMetadata,
|
2026-03-17 18:08:53 +01:00
|
|
|
swap_amount_in: u128,
|
|
|
|
|
min_amount_out: u128,
|
|
|
|
|
token_definition_id_in: AccountId,
|
2026-04-23 17:19:15 +02:00
|
|
|
deadline: u64,
|
2026-03-17 18:08:53 +01:00
|
|
|
) -> SpelResult {
|
2026-04-07 09:31:32 +02:00
|
|
|
let (post_states, chained_calls) = amm_program::swap::swap_exact_input(
|
feat(amm): add Initialize instruction with config-gated chained calls
Introduce a singleton AMM configuration account, a PDA derived from the
constant "CONFIG" seed, created once via a new `Initialize` instruction.
The config stores the Token Program ID the AMM issues every chained call
to, replacing the previous behavior of trusting the program owner of a
caller-supplied holding.
The config account's existence is the Program's initialization gate: the
chained-call instructions (new_definition, add_liquidity, remove_liquidity,
swap_exact_input, swap_exact_output) now take the config as their first
account, validate it against `compute_config_pda(self_program_id)`, and
read the Token Program ID from it on demand — rejecting calls until the
Program is initialized. Vaults and user holdings are asserted to match the
configured Token Program. sync_reserves is left ungated, as it cannot act
on a pool that could not have existed before initialization.
- amm_core: AmmConfig type, compute_config_pda/_seed, Initialize variant
- amm: initialize.rs + config threading through chained-call instructions
- guest: initialize instruction; config + self_program_id on gated calls
- tests: config fixtures, init-gate unit tests, end-to-end Initialize VM test
2026-06-17 16:29:30 +02:00
|
|
|
config,
|
2026-03-17 18:08:53 +01:00
|
|
|
pool,
|
|
|
|
|
vault_a,
|
|
|
|
|
vault_b,
|
|
|
|
|
user_holding_a,
|
|
|
|
|
user_holding_b,
|
feat(amm): refresh TWAP current tick on every reserve-mutating instruction
Make swap_exact_input, swap_exact_output, add_liquidity, remove_liquidity,
and sync_reserves keep the pool's TWAP current tick in sync with its
reserves. Each now takes the current-tick and clock accounts, reads the
TWAP program ID from the config account, validates the clock account and
the current-tick PDA, and after computing the post-op pool chains an
UpdateCurrentTick to the oracle carrying the post-op spot price, with the
pool passed as the authorized price source via its pool PDA seed.
sync_reserves additionally now takes the config account so it can resolve
the TWAP program ID and gate on initialization, consistent with the other
instructions.
The invariant current_tick == tick(reserves) therefore holds after every
operation. Proportional add/remove preserve the price, so the tick is
unchanged for them, but the refresh still runs and lands on the correct
value.
2026-06-18 15:50:11 +02:00
|
|
|
current_tick_account,
|
|
|
|
|
clock,
|
2026-03-17 18:08:53 +01:00
|
|
|
swap_amount_in,
|
|
|
|
|
min_amount_out,
|
|
|
|
|
token_definition_id_in,
|
feat(amm): add Initialize instruction with config-gated chained calls
Introduce a singleton AMM configuration account, a PDA derived from the
constant "CONFIG" seed, created once via a new `Initialize` instruction.
The config stores the Token Program ID the AMM issues every chained call
to, replacing the previous behavior of trusting the program owner of a
caller-supplied holding.
The config account's existence is the Program's initialization gate: the
chained-call instructions (new_definition, add_liquidity, remove_liquidity,
swap_exact_input, swap_exact_output) now take the config as their first
account, validate it against `compute_config_pda(self_program_id)`, and
read the Token Program ID from it on demand — rejecting calls until the
Program is initialized. Vaults and user holdings are asserted to match the
configured Token Program. sync_reserves is left ungated, as it cannot act
on a pool that could not have existed before initialization.
- amm_core: AmmConfig type, compute_config_pda/_seed, Initialize variant
- amm: initialize.rs + config threading through chained-call instructions
- guest: initialize instruction; config + self_program_id on gated calls
- tests: config fixtures, init-gate unit tests, end-to-end Initialize VM test
2026-06-17 16:29:30 +02:00
|
|
|
ctx.self_program_id,
|
2026-03-17 18:08:53 +01:00
|
|
|
);
|
2026-05-12 11:33:19 +02:00
|
|
|
Ok(spel_framework::SpelOutput::execute(post_states, chained_calls)
|
2026-04-23 17:19:15 +02:00
|
|
|
.with_timestamp_validity_window(..deadline))
|
2026-03-17 18:08:53 +01:00
|
|
|
}
|
2026-04-08 10:57:47 -03:00
|
|
|
|
2026-04-02 17:16:53 +02:00
|
|
|
/// Swap tokens specifying the exact desired output amount.
|
2026-05-06 17:08:15 -03:00
|
|
|
#[expect(
|
|
|
|
|
clippy::too_many_arguments,
|
|
|
|
|
reason = "instruction interface requires explicit pool, vault, user accounts, and bounds"
|
|
|
|
|
)]
|
2026-04-02 17:16:53 +02:00
|
|
|
#[instruction]
|
|
|
|
|
pub fn swap_exact_output(
|
feat(amm): add Initialize instruction with config-gated chained calls
Introduce a singleton AMM configuration account, a PDA derived from the
constant "CONFIG" seed, created once via a new `Initialize` instruction.
The config stores the Token Program ID the AMM issues every chained call
to, replacing the previous behavior of trusting the program owner of a
caller-supplied holding.
The config account's existence is the Program's initialization gate: the
chained-call instructions (new_definition, add_liquidity, remove_liquidity,
swap_exact_input, swap_exact_output) now take the config as their first
account, validate it against `compute_config_pda(self_program_id)`, and
read the Token Program ID from it on demand — rejecting calls until the
Program is initialized. Vaults and user holdings are asserted to match the
configured Token Program. sync_reserves is left ungated, as it cannot act
on a pool that could not have existed before initialization.
- amm_core: AmmConfig type, compute_config_pda/_seed, Initialize variant
- amm: initialize.rs + config threading through chained-call instructions
- guest: initialize instruction; config + self_program_id on gated calls
- tests: config fixtures, init-gate unit tests, end-to-end Initialize VM test
2026-06-17 16:29:30 +02:00
|
|
|
ctx: ProgramContext,
|
|
|
|
|
config: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut)]
|
2026-04-02 17:16:53 +02:00
|
|
|
pool: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut)]
|
2026-04-02 17:16:53 +02:00
|
|
|
vault_a: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut)]
|
2026-04-02 17:16:53 +02:00
|
|
|
vault_b: AccountWithMetadata,
|
2026-06-28 23:49:39 +02:00
|
|
|
#[account(mut, signer)]
|
2026-04-02 17:16:53 +02:00
|
|
|
user_holding_a: AccountWithMetadata,
|
2026-06-28 23:49:39 +02:00
|
|
|
#[account(mut, signer)]
|
2026-04-02 17:16:53 +02:00
|
|
|
user_holding_b: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut)]
|
feat(amm): refresh TWAP current tick on every reserve-mutating instruction
Make swap_exact_input, swap_exact_output, add_liquidity, remove_liquidity,
and sync_reserves keep the pool's TWAP current tick in sync with its
reserves. Each now takes the current-tick and clock accounts, reads the
TWAP program ID from the config account, validates the clock account and
the current-tick PDA, and after computing the post-op pool chains an
UpdateCurrentTick to the oracle carrying the post-op spot price, with the
pool passed as the authorized price source via its pool PDA seed.
sync_reserves additionally now takes the config account so it can resolve
the TWAP program ID and gate on initialization, consistent with the other
instructions.
The invariant current_tick == tick(reserves) therefore holds after every
operation. Proportional add/remove preserve the price, so the tick is
unchanged for them, but the refresh still runs and lands on the correct
value.
2026-06-18 15:50:11 +02:00
|
|
|
current_tick_account: AccountWithMetadata,
|
|
|
|
|
clock: AccountWithMetadata,
|
2026-04-02 17:16:53 +02:00
|
|
|
exact_amount_out: u128,
|
|
|
|
|
max_amount_in: u128,
|
|
|
|
|
token_definition_id_in: AccountId,
|
2026-04-23 17:19:15 +02:00
|
|
|
deadline: u64,
|
2026-04-02 17:16:53 +02:00
|
|
|
) -> SpelResult {
|
|
|
|
|
let (post_states, chained_calls) = amm_program::swap::swap_exact_output(
|
feat(amm): add Initialize instruction with config-gated chained calls
Introduce a singleton AMM configuration account, a PDA derived from the
constant "CONFIG" seed, created once via a new `Initialize` instruction.
The config stores the Token Program ID the AMM issues every chained call
to, replacing the previous behavior of trusting the program owner of a
caller-supplied holding.
The config account's existence is the Program's initialization gate: the
chained-call instructions (new_definition, add_liquidity, remove_liquidity,
swap_exact_input, swap_exact_output) now take the config as their first
account, validate it against `compute_config_pda(self_program_id)`, and
read the Token Program ID from it on demand — rejecting calls until the
Program is initialized. Vaults and user holdings are asserted to match the
configured Token Program. sync_reserves is left ungated, as it cannot act
on a pool that could not have existed before initialization.
- amm_core: AmmConfig type, compute_config_pda/_seed, Initialize variant
- amm: initialize.rs + config threading through chained-call instructions
- guest: initialize instruction; config + self_program_id on gated calls
- tests: config fixtures, init-gate unit tests, end-to-end Initialize VM test
2026-06-17 16:29:30 +02:00
|
|
|
config,
|
2026-04-02 17:16:53 +02:00
|
|
|
pool,
|
|
|
|
|
vault_a,
|
|
|
|
|
vault_b,
|
|
|
|
|
user_holding_a,
|
|
|
|
|
user_holding_b,
|
feat(amm): refresh TWAP current tick on every reserve-mutating instruction
Make swap_exact_input, swap_exact_output, add_liquidity, remove_liquidity,
and sync_reserves keep the pool's TWAP current tick in sync with its
reserves. Each now takes the current-tick and clock accounts, reads the
TWAP program ID from the config account, validates the clock account and
the current-tick PDA, and after computing the post-op pool chains an
UpdateCurrentTick to the oracle carrying the post-op spot price, with the
pool passed as the authorized price source via its pool PDA seed.
sync_reserves additionally now takes the config account so it can resolve
the TWAP program ID and gate on initialization, consistent with the other
instructions.
The invariant current_tick == tick(reserves) therefore holds after every
operation. Proportional add/remove preserve the price, so the tick is
unchanged for them, but the refresh still runs and lands on the correct
value.
2026-06-18 15:50:11 +02:00
|
|
|
current_tick_account,
|
|
|
|
|
clock,
|
2026-04-02 17:16:53 +02:00
|
|
|
exact_amount_out,
|
|
|
|
|
max_amount_in,
|
|
|
|
|
token_definition_id_in,
|
feat(amm): add Initialize instruction with config-gated chained calls
Introduce a singleton AMM configuration account, a PDA derived from the
constant "CONFIG" seed, created once via a new `Initialize` instruction.
The config stores the Token Program ID the AMM issues every chained call
to, replacing the previous behavior of trusting the program owner of a
caller-supplied holding.
The config account's existence is the Program's initialization gate: the
chained-call instructions (new_definition, add_liquidity, remove_liquidity,
swap_exact_input, swap_exact_output) now take the config as their first
account, validate it against `compute_config_pda(self_program_id)`, and
read the Token Program ID from it on demand — rejecting calls until the
Program is initialized. Vaults and user holdings are asserted to match the
configured Token Program. sync_reserves is left ungated, as it cannot act
on a pool that could not have existed before initialization.
- amm_core: AmmConfig type, compute_config_pda/_seed, Initialize variant
- amm: initialize.rs + config threading through chained-call instructions
- guest: initialize instruction; config + self_program_id on gated calls
- tests: config fixtures, init-gate unit tests, end-to-end Initialize VM test
2026-06-17 16:29:30 +02:00
|
|
|
ctx.self_program_id,
|
2026-04-02 17:16:53 +02:00
|
|
|
);
|
2026-05-12 11:33:19 +02:00
|
|
|
Ok(spel_framework::SpelOutput::execute(post_states, chained_calls)
|
2026-04-23 17:19:15 +02:00
|
|
|
.with_timestamp_validity_window(..deadline))
|
2026-04-02 17:16:53 +02:00
|
|
|
}
|
|
|
|
|
|
feat(amm): refresh TWAP current tick on every reserve-mutating instruction
Make swap_exact_input, swap_exact_output, add_liquidity, remove_liquidity,
and sync_reserves keep the pool's TWAP current tick in sync with its
reserves. Each now takes the current-tick and clock accounts, reads the
TWAP program ID from the config account, validates the clock account and
the current-tick PDA, and after computing the post-op pool chains an
UpdateCurrentTick to the oracle carrying the post-op spot price, with the
pool passed as the authorized price source via its pool PDA seed.
sync_reserves additionally now takes the config account so it can resolve
the TWAP program ID and gate on initialization, consistent with the other
instructions.
The invariant current_tick == tick(reserves) therefore holds after every
operation. Proportional add/remove preserve the price, so the tick is
unchanged for them, but the refresh still runs and lands on the correct
value.
2026-06-18 15:50:11 +02:00
|
|
|
/// Sync pool reserves with current vault balances, refreshing the pool's TWAP current tick.
|
2026-04-08 10:57:47 -03:00
|
|
|
#[instruction]
|
|
|
|
|
pub fn sync_reserves(
|
feat(amm): refresh TWAP current tick on every reserve-mutating instruction
Make swap_exact_input, swap_exact_output, add_liquidity, remove_liquidity,
and sync_reserves keep the pool's TWAP current tick in sync with its
reserves. Each now takes the current-tick and clock accounts, reads the
TWAP program ID from the config account, validates the clock account and
the current-tick PDA, and after computing the post-op pool chains an
UpdateCurrentTick to the oracle carrying the post-op spot price, with the
pool passed as the authorized price source via its pool PDA seed.
sync_reserves additionally now takes the config account so it can resolve
the TWAP program ID and gate on initialization, consistent with the other
instructions.
The invariant current_tick == tick(reserves) therefore holds after every
operation. Proportional add/remove preserve the price, so the tick is
unchanged for them, but the refresh still runs and lands on the correct
value.
2026-06-18 15:50:11 +02:00
|
|
|
ctx: ProgramContext,
|
|
|
|
|
config: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut)]
|
2026-04-08 10:57:47 -03:00
|
|
|
pool: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
// vault_a / vault_b are only read to compute balances in
|
|
|
|
|
// amm_program::sync::sync_reserves (their post-states are unchanged
|
|
|
|
|
// clones), so they are not writable — no `mut` metadata.
|
2026-04-08 10:57:47 -03:00
|
|
|
vault_a: AccountWithMetadata,
|
|
|
|
|
vault_b: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut)]
|
feat(amm): refresh TWAP current tick on every reserve-mutating instruction
Make swap_exact_input, swap_exact_output, add_liquidity, remove_liquidity,
and sync_reserves keep the pool's TWAP current tick in sync with its
reserves. Each now takes the current-tick and clock accounts, reads the
TWAP program ID from the config account, validates the clock account and
the current-tick PDA, and after computing the post-op pool chains an
UpdateCurrentTick to the oracle carrying the post-op spot price, with the
pool passed as the authorized price source via its pool PDA seed.
sync_reserves additionally now takes the config account so it can resolve
the TWAP program ID and gate on initialization, consistent with the other
instructions.
The invariant current_tick == tick(reserves) therefore holds after every
operation. Proportional add/remove preserve the price, so the tick is
unchanged for them, but the refresh still runs and lands on the correct
value.
2026-06-18 15:50:11 +02:00
|
|
|
current_tick_account: AccountWithMetadata,
|
|
|
|
|
clock: AccountWithMetadata,
|
2026-04-08 10:57:47 -03:00
|
|
|
) -> SpelResult {
|
feat(amm): refresh TWAP current tick on every reserve-mutating instruction
Make swap_exact_input, swap_exact_output, add_liquidity, remove_liquidity,
and sync_reserves keep the pool's TWAP current tick in sync with its
reserves. Each now takes the current-tick and clock accounts, reads the
TWAP program ID from the config account, validates the clock account and
the current-tick PDA, and after computing the post-op pool chains an
UpdateCurrentTick to the oracle carrying the post-op spot price, with the
pool passed as the authorized price source via its pool PDA seed.
sync_reserves additionally now takes the config account so it can resolve
the TWAP program ID and gate on initialization, consistent with the other
instructions.
The invariant current_tick == tick(reserves) therefore holds after every
operation. Proportional add/remove preserve the price, so the tick is
unchanged for them, but the refresh still runs and lands on the correct
value.
2026-06-18 15:50:11 +02:00
|
|
|
let (post_states, chained_calls) = amm_program::sync::sync_reserves(
|
|
|
|
|
config,
|
|
|
|
|
pool,
|
|
|
|
|
vault_a,
|
|
|
|
|
vault_b,
|
|
|
|
|
current_tick_account,
|
|
|
|
|
clock,
|
|
|
|
|
ctx.self_program_id,
|
|
|
|
|
);
|
2026-05-12 11:33:19 +02:00
|
|
|
Ok(spel_framework::SpelOutput::execute(post_states, chained_calls))
|
2026-04-08 10:57:47 -03:00
|
|
|
}
|
2026-03-17 18:08:53 +01:00
|
|
|
}
|