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
|
|
|
use amm_core::{compute_config_pda, compute_config_pda_seed, AmmConfig};
|
|
|
|
|
use nssa_core::{
|
2026-06-18 16:46:38 +02:00
|
|
|
account::{Account, AccountId, AccountWithMetadata, Data},
|
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::{AccountPostState, Claim, ProgramId},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// Initializes the AMM Program by creating its singleton configuration account.
|
|
|
|
|
///
|
|
|
|
|
/// The config account is a PDA derived from the constant `"CONFIG"` seed
|
2026-06-18 16:46:38 +02:00
|
|
|
/// (`compute_config_pda(amm_program_id)`) and stores `token_program_id` (the Token Program the
|
|
|
|
|
/// AMM issues every chained call to) and `authority` (the admin allowed to change configuration
|
|
|
|
|
/// later via `update_config`). Its existence is the Program's "initialized" flag: the
|
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
|
|
|
/// chained-call instructions read the Token Program ID from it and reject calls until it exists.
|
|
|
|
|
///
|
|
|
|
|
/// # Panics
|
|
|
|
|
/// Panics if:
|
|
|
|
|
/// - `config.account_id` does not match `compute_config_pda(amm_program_id)`.
|
|
|
|
|
/// - `config.account` is not the default (the Program is already initialized).
|
|
|
|
|
pub fn initialize(
|
|
|
|
|
config: AccountWithMetadata,
|
|
|
|
|
token_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
|
|
|
amm_program_id: ProgramId,
|
|
|
|
|
) -> Vec<AccountPostState> {
|
|
|
|
|
assert_eq!(
|
|
|
|
|
config.account_id,
|
|
|
|
|
compute_config_pda(amm_program_id),
|
|
|
|
|
"Initialize: AMM config Account ID does not match PDA"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
config.account,
|
|
|
|
|
Account::default(),
|
|
|
|
|
"Initialize: AMM config account must be uninitialized"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let mut config_post = config.account.clone();
|
2026-06-18 16:46:38 +02:00
|
|
|
config_post.data = Data::from(&AmmConfig {
|
|
|
|
|
token_program_id,
|
|
|
|
|
authority,
|
|
|
|
|
});
|
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
|
|
|
|
|
|
|
|
vec![AccountPostState::new_claimed(
|
|
|
|
|
config_post,
|
|
|
|
|
Claim::Pda(compute_config_pda_seed()),
|
|
|
|
|
)]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use amm_core::compute_config_pda;
|
|
|
|
|
use nssa_core::account::{AccountId, Nonce};
|
|
|
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
const AMM_PROGRAM_ID: ProgramId = [42; 8];
|
|
|
|
|
const TOKEN_PROGRAM_ID: ProgramId = [15; 8];
|
|
|
|
|
|
2026-06-18 16:46:38 +02:00
|
|
|
fn authority() -> AccountId {
|
|
|
|
|
AccountId::new([9; 32])
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
fn config_uninit() -> AccountWithMetadata {
|
|
|
|
|
AccountWithMetadata {
|
|
|
|
|
account: Account::default(),
|
|
|
|
|
is_authorized: false,
|
|
|
|
|
account_id: compute_config_pda(AMM_PROGRAM_ID),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn returns_single_pda_claimed_post_state() {
|
2026-06-18 16:46:38 +02:00
|
|
|
let post_states = initialize(
|
|
|
|
|
config_uninit(),
|
|
|
|
|
TOKEN_PROGRAM_ID,
|
|
|
|
|
authority(),
|
|
|
|
|
AMM_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
|
|
|
assert_eq!(post_states.len(), 1);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
post_states[0].required_claim(),
|
|
|
|
|
Some(Claim::Pda(compute_config_pda_seed()))
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2026-06-18 16:46:38 +02:00
|
|
|
fn stores_token_program_id_and_authority() {
|
|
|
|
|
let post_states = initialize(
|
|
|
|
|
config_uninit(),
|
|
|
|
|
TOKEN_PROGRAM_ID,
|
|
|
|
|
authority(),
|
|
|
|
|
AMM_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
|
|
|
let config = AmmConfig::try_from(&post_states[0].account().data)
|
|
|
|
|
.expect("post state must contain a valid AmmConfig");
|
|
|
|
|
assert_eq!(config.token_program_id, TOKEN_PROGRAM_ID);
|
2026-06-18 16:46:38 +02:00
|
|
|
assert_eq!(config.authority, authority());
|
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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[should_panic(expected = "AMM config Account ID does not match PDA")]
|
|
|
|
|
fn wrong_config_account_id_panics() {
|
|
|
|
|
let mut wrong = config_uninit();
|
|
|
|
|
wrong.account_id = AccountId::new([0; 32]);
|
2026-06-18 16:46:38 +02:00
|
|
|
initialize(wrong, TOKEN_PROGRAM_ID, authority(), AMM_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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[should_panic(expected = "AMM config account must be uninitialized")]
|
|
|
|
|
fn already_initialized_config_panics() {
|
|
|
|
|
let mut initialized = config_uninit();
|
|
|
|
|
initialized.account.data = Data::from(&AmmConfig {
|
|
|
|
|
token_program_id: TOKEN_PROGRAM_ID,
|
2026-06-18 16:46:38 +02:00
|
|
|
authority: authority(),
|
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
|
|
|
});
|
|
|
|
|
initialized.account.nonce = Nonce(0);
|
2026-06-18 16:46:38 +02:00
|
|
|
initialize(initialized, TOKEN_PROGRAM_ID, authority(), AMM_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
|
|
|
}
|
|
|
|
|
}
|