mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-08-25 06:01:11 +00:00
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
This commit is contained in:
@@ -43,6 +43,10 @@ impl Ids {
|
||||
amm_methods::AMM_ID
|
||||
}
|
||||
|
||||
fn config() -> AccountId {
|
||||
amm_core::compute_config_pda(Self::amm_program())
|
||||
}
|
||||
|
||||
fn token_a_definition() -> AccountId {
|
||||
AccountId::new([3; 32])
|
||||
}
|
||||
@@ -283,6 +287,17 @@ impl Balances {
|
||||
}
|
||||
|
||||
impl Accounts {
|
||||
fn config() -> Account {
|
||||
Account {
|
||||
program_owner: Ids::amm_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&amm_core::AmmConfig {
|
||||
token_program_id: Ids::token_program(),
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn user_a_holding() -> Account {
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
@@ -914,6 +929,7 @@ fn deploy_programs(state: &mut V03State) {
|
||||
fn state_for_amm_tests() -> V03State {
|
||||
let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0);
|
||||
deploy_programs(&mut state);
|
||||
state.force_insert_account(Ids::config(), Accounts::config());
|
||||
state.force_insert_account(Ids::pool_definition(), Accounts::pool_definition_init());
|
||||
state.force_insert_account(
|
||||
Ids::token_a_definition(),
|
||||
@@ -938,6 +954,7 @@ fn state_for_amm_tests() -> V03State {
|
||||
fn state_for_amm_tests_with_new_def() -> V03State {
|
||||
let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0);
|
||||
deploy_programs(&mut state);
|
||||
state.force_insert_account(Ids::config(), Accounts::config());
|
||||
state.force_insert_account(
|
||||
Ids::token_a_definition(),
|
||||
Accounts::token_a_definition_account(),
|
||||
@@ -977,6 +994,7 @@ fn try_execute_new_definition(
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::amm_program(),
|
||||
vec![
|
||||
Ids::config(),
|
||||
Ids::pool_definition(),
|
||||
Ids::vault_a(),
|
||||
Ids::vault_b(),
|
||||
@@ -1032,6 +1050,7 @@ fn execute_swap_a_to_b(state: &mut V03State, swap_amount_in: u128, min_amount_ou
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::amm_program(),
|
||||
vec![
|
||||
Ids::config(),
|
||||
Ids::pool_definition(),
|
||||
Ids::vault_a(),
|
||||
Ids::vault_b(),
|
||||
@@ -1061,6 +1080,7 @@ fn execute_swap_b_to_a(state: &mut V03State, swap_amount_in: u128, min_amount_ou
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::amm_program(),
|
||||
vec![
|
||||
Ids::config(),
|
||||
Ids::pool_definition(),
|
||||
Ids::vault_a(),
|
||||
Ids::vault_b(),
|
||||
@@ -1095,6 +1115,7 @@ fn execute_add_liquidity(
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::amm_program(),
|
||||
vec![
|
||||
Ids::config(),
|
||||
Ids::pool_definition(),
|
||||
Ids::vault_a(),
|
||||
Ids::vault_b(),
|
||||
@@ -1135,6 +1156,7 @@ fn execute_remove_liquidity(
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::amm_program(),
|
||||
vec![
|
||||
Ids::config(),
|
||||
Ids::pool_definition(),
|
||||
Ids::vault_a(),
|
||||
Ids::vault_b(),
|
||||
@@ -1154,6 +1176,26 @@ fn execute_remove_liquidity(
|
||||
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn execute_initialize(state: &mut V03State) {
|
||||
let instruction = amm_core::Instruction::Initialize {
|
||||
token_program_id: Ids::token_program(),
|
||||
};
|
||||
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::amm_program(),
|
||||
vec![Ids::config()],
|
||||
vec![],
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
|
||||
}
|
||||
|
||||
fn fungible_balance(account: &Account) -> u128 {
|
||||
let holding = TokenHolding::try_from(&account.data).expect("expected token holding");
|
||||
let TokenHolding::Fungible {
|
||||
@@ -1185,6 +1227,26 @@ fn fungible_total_supply(account: &Account) -> u128 {
|
||||
total_supply
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn amm_initialize_creates_config_account() {
|
||||
let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0);
|
||||
deploy_programs(&mut state);
|
||||
|
||||
// Before initialization the config PDA does not exist.
|
||||
assert_eq!(state.get_account_by_id(Ids::config()), Account::default());
|
||||
|
||||
execute_initialize(&mut state);
|
||||
|
||||
// Initialization creates the config PDA, owned by the AMM program.
|
||||
let config_account = state.get_account_by_id(Ids::config());
|
||||
assert_eq!(config_account, Accounts::config());
|
||||
|
||||
// Explicitly assert the stored Token Program ID round-trips from the instruction argument.
|
||||
let config = amm_core::AmmConfig::try_from(&config_account.data)
|
||||
.expect("config account must hold a valid AmmConfig");
|
||||
assert_eq!(config.token_program_id, Ids::token_program());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn amm_remove_liquidity() {
|
||||
let mut state = state_for_amm_tests();
|
||||
@@ -1199,6 +1261,7 @@ fn amm_remove_liquidity() {
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::amm_program(),
|
||||
vec![
|
||||
Ids::config(),
|
||||
Ids::pool_definition(),
|
||||
Ids::vault_a(),
|
||||
Ids::vault_b(),
|
||||
@@ -1262,6 +1325,7 @@ fn amm_remove_liquidity_insufficient_user_lp_fails() {
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::amm_program(),
|
||||
vec![
|
||||
Ids::config(),
|
||||
Ids::pool_definition(),
|
||||
Ids::vault_a(),
|
||||
Ids::vault_b(),
|
||||
@@ -1487,6 +1551,7 @@ fn amm_add_liquidity() {
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::amm_program(),
|
||||
vec![
|
||||
Ids::config(),
|
||||
Ids::pool_definition(),
|
||||
Ids::vault_a(),
|
||||
Ids::vault_b(),
|
||||
@@ -1550,6 +1615,7 @@ fn amm_swap_b_to_a() {
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::amm_program(),
|
||||
vec![
|
||||
Ids::config(),
|
||||
Ids::pool_definition(),
|
||||
Ids::vault_a(),
|
||||
Ids::vault_b(),
|
||||
@@ -1602,6 +1668,7 @@ fn amm_swap_a_to_b() {
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::amm_program(),
|
||||
vec![
|
||||
Ids::config(),
|
||||
Ids::pool_definition(),
|
||||
Ids::vault_a(),
|
||||
Ids::vault_b(),
|
||||
@@ -1708,6 +1775,7 @@ fn amm_swap_rejects_expired_deadline() {
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::amm_program(),
|
||||
vec![
|
||||
Ids::config(),
|
||||
Ids::pool_definition(),
|
||||
Ids::vault_a(),
|
||||
Ids::vault_b(),
|
||||
@@ -1744,6 +1812,7 @@ fn amm_swap_exact_output_rejects_expired_deadline() {
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::amm_program(),
|
||||
vec![
|
||||
Ids::config(),
|
||||
Ids::pool_definition(),
|
||||
Ids::vault_a(),
|
||||
Ids::vault_b(),
|
||||
@@ -1780,6 +1849,7 @@ fn amm_add_liquidity_rejects_expired_deadline() {
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::amm_program(),
|
||||
vec![
|
||||
Ids::config(),
|
||||
Ids::pool_definition(),
|
||||
Ids::vault_a(),
|
||||
Ids::vault_b(),
|
||||
@@ -1822,6 +1892,7 @@ fn amm_remove_liquidity_rejects_expired_deadline() {
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::amm_program(),
|
||||
vec![
|
||||
Ids::config(),
|
||||
Ids::pool_definition(),
|
||||
Ids::vault_a(),
|
||||
Ids::vault_b(),
|
||||
@@ -1860,6 +1931,7 @@ fn amm_new_definition_rejects_expired_deadline() {
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::amm_program(),
|
||||
vec![
|
||||
Ids::config(),
|
||||
Ids::pool_definition(),
|
||||
Ids::vault_a(),
|
||||
Ids::vault_b(),
|
||||
|
||||
Reference in New Issue
Block a user