mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-06-30 03:59:38 +00:00
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.
113 lines
4.2 KiB
Rust
113 lines
4.2 KiB
Rust
use amm_core::{
|
|
assert_supported_fee_tier, compute_config_pda, compute_pool_pda_seed,
|
|
read_vault_fungible_balances, spot_price_q64_64, AmmConfig, PoolDefinition, MINIMUM_LIQUIDITY,
|
|
};
|
|
use clock_core::CLOCK_01_PROGRAM_ACCOUNT_ID;
|
|
use nssa_core::{
|
|
account::{AccountWithMetadata, Data},
|
|
program::{AccountPostState, ChainedCall, ProgramId},
|
|
};
|
|
use twap_oracle_core::compute_current_tick_account_pda;
|
|
|
|
pub fn sync_reserves(
|
|
config: AccountWithMetadata,
|
|
pool: AccountWithMetadata,
|
|
vault_a: AccountWithMetadata,
|
|
vault_b: AccountWithMetadata,
|
|
current_tick_account: AccountWithMetadata,
|
|
clock: AccountWithMetadata,
|
|
amm_program_id: ProgramId,
|
|
) -> (Vec<AccountPostState>, Vec<ChainedCall>) {
|
|
let pool_def_data = PoolDefinition::try_from(&pool.account.data)
|
|
.expect("Sync reserves: AMM Program expects a valid Pool Definition Account");
|
|
assert_supported_fee_tier(pool_def_data.fees);
|
|
|
|
// The TWAP oracle program ID is taken from the config account. Validating the config PDA is
|
|
// also the Program's initialization gate.
|
|
assert_eq!(
|
|
config.account_id,
|
|
compute_config_pda(amm_program_id),
|
|
"Sync reserves: AMM config Account ID does not match PDA"
|
|
);
|
|
let twap_oracle_program_id = AmmConfig::try_from(&config.account.data)
|
|
.expect("Sync reserves: AMM Program must be initialized before use")
|
|
.twap_oracle_program_id;
|
|
|
|
assert!(
|
|
pool_def_data.liquidity_pool_supply >= MINIMUM_LIQUIDITY,
|
|
"Pool liquidity supply is below minimum liquidity"
|
|
);
|
|
assert_eq!(
|
|
vault_a.account_id, pool_def_data.vault_a_id,
|
|
"Vault A was not provided"
|
|
);
|
|
assert_eq!(
|
|
vault_b.account_id, pool_def_data.vault_b_id,
|
|
"Vault B was not provided"
|
|
);
|
|
// The current tick is refreshed by a chained call to the oracle; validate its PDA and the
|
|
// clock here so the sync is rejected early with an AMM-level error.
|
|
assert_eq!(
|
|
clock.account_id, CLOCK_01_PROGRAM_ACCOUNT_ID,
|
|
"Sync reserves: clock account must be the canonical 1-block LEZ clock account"
|
|
);
|
|
assert_eq!(
|
|
current_tick_account.account_id,
|
|
compute_current_tick_account_pda(twap_oracle_program_id, pool.account_id),
|
|
"Sync reserves: current tick Account ID does not match PDA"
|
|
);
|
|
|
|
let (vault_a_balance, vault_b_balance) =
|
|
read_vault_fungible_balances("Sync reserves", &vault_a, &vault_b);
|
|
assert!(
|
|
vault_a_balance >= pool_def_data.reserve_a,
|
|
"Sync reserves: vault A balance is less than its reserve"
|
|
);
|
|
assert!(
|
|
vault_b_balance >= pool_def_data.reserve_b,
|
|
"Sync reserves: vault B balance is less than its reserve"
|
|
);
|
|
|
|
let pool_post_definition = PoolDefinition {
|
|
reserve_a: vault_a_balance,
|
|
reserve_b: vault_b_balance,
|
|
..pool_def_data
|
|
};
|
|
let mut pool_post = pool.account.clone();
|
|
pool_post.data = Data::from(&pool_post_definition);
|
|
|
|
// Refresh the pool's TWAP current tick from the synced spot price. The pool is already owned by
|
|
// this program, so it is passed (in its synced state) as the authorized price source.
|
|
let new_price = spot_price_q64_64(vault_a_balance, vault_b_balance);
|
|
let pool_price_source = AccountWithMetadata {
|
|
account: pool_post.clone(),
|
|
is_authorized: true,
|
|
account_id: pool.account_id,
|
|
};
|
|
let update_tick_call = ChainedCall::new(
|
|
twap_oracle_program_id,
|
|
vec![
|
|
current_tick_account.clone(),
|
|
pool_price_source,
|
|
clock.clone(),
|
|
],
|
|
&twap_oracle_core::Instruction::UpdateCurrentTick { price: new_price },
|
|
)
|
|
.with_pda_seeds(vec![compute_pool_pda_seed(
|
|
pool_def_data.definition_token_a_id,
|
|
pool_def_data.definition_token_b_id,
|
|
)]);
|
|
|
|
(
|
|
vec![
|
|
AccountPostState::new(config.account.clone()),
|
|
AccountPostState::new(pool_post),
|
|
AccountPostState::new(vault_a.account.clone()),
|
|
AccountPostState::new(vault_b.account.clone()),
|
|
AccountPostState::new(current_tick_account.account.clone()),
|
|
AccountPostState::new(clock.account.clone()),
|
|
],
|
|
vec![update_tick_call],
|
|
)
|
|
}
|