mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-07-01 12:39:55 +00:00
feat(amm): bootstrap pool TWAP current-tick account at pool creation
Extend new_definition to also create the pool's TWAP current-tick account via a chained CreateCurrentTickAccount, so a pool and its price feed are born together. The opening tick is derived on-chain from the pool's own reserves (reserve_b / reserve_a as Q64.64), not caller-supplied, so it cannot be forged. The pool is passed in its post-claim state and authorized as the price source via its pool PDA seed. Add spot_price_q64_64 to amm_core (not the oracle): the reserves -> price mapping is the price source's concern; the oracle only converts price to a tick.
This commit is contained in:
parent
4e4338945d
commit
b997ca678e
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -80,9 +80,11 @@ dependencies = [
|
|||||||
name = "amm_core"
|
name = "amm_core"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"alloy-primitives",
|
||||||
"borsh",
|
"borsh",
|
||||||
"nssa_core",
|
"nssa_core",
|
||||||
"risc0-zkvm",
|
"risc0-zkvm",
|
||||||
|
"ruint",
|
||||||
"serde",
|
"serde",
|
||||||
"spel-framework-macros",
|
"spel-framework-macros",
|
||||||
"token_core",
|
"token_core",
|
||||||
|
|||||||
@ -161,6 +161,18 @@
|
|||||||
"writable": false,
|
"writable": false,
|
||||||
"signer": false,
|
"signer": false,
|
||||||
"init": false
|
"init": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "current_tick_account",
|
||||||
|
"writable": false,
|
||||||
|
"signer": false,
|
||||||
|
"init": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "clock",
|
||||||
|
"writable": false,
|
||||||
|
"signer": false,
|
||||||
|
"init": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"args": [
|
"args": [
|
||||||
|
|||||||
@ -13,3 +13,8 @@ token_core = { path = "../../token/core" }
|
|||||||
borsh = { version = "1.5", features = ["derive"] }
|
borsh = { version = "1.5", features = ["derive"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
risc0-zkvm = { version = "=3.0.5", default-features = false }
|
risc0-zkvm = { version = "=3.0.5", default-features = false }
|
||||||
|
alloy-primitives = { version = "1", default-features = false }
|
||||||
|
# Pin ruint (transitive via alloy-primitives) below 1.18, which raised its MSRV to rustc 1.90.
|
||||||
|
# The risc0 guest toolchain ships rustc 1.88, so 1.18+ fails the guest build. 1.17.0 (MSRV 1.85)
|
||||||
|
# is the newest compatible release. Remove this pin once the risc0 toolchain advances past 1.90.
|
||||||
|
ruint = { version = "=1.17.0", default-features = false }
|
||||||
|
|||||||
@ -222,6 +222,35 @@ pub fn assert_supported_fee_tier(fees: u128) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Computes a `Q64.64` spot price (`reserve_quote` per `reserve_base`) from raw pool reserves.
|
||||||
|
///
|
||||||
|
/// This is the constant-product AMM's spot price (`reserve_quote / reserve_base`) expressed as a
|
||||||
|
/// `Q64.64` fixed-point value: `(reserve_quote / reserve_base) * 2^64`. It is computed in 256-bit
|
||||||
|
/// precision and saturates at `u128::MAX` if the ratio exceeds the representable range. The TWAP
|
||||||
|
/// oracle consumes exactly this representation (it converts the `Q64.64` price to a tick), so the
|
||||||
|
/// AMM owns the reserves → price mapping and the oracle stays agnostic to how the price is formed.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// Panics if `reserve_base` is zero.
|
||||||
|
#[must_use]
|
||||||
|
pub fn spot_price_q64_64(reserve_base: u128, reserve_quote: u128) -> u128 {
|
||||||
|
use alloy_primitives::U256;
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
reserve_base != 0,
|
||||||
|
"spot_price_q64_64: reserve_base must be non-zero"
|
||||||
|
);
|
||||||
|
|
||||||
|
let numerator = U256::from(reserve_quote)
|
||||||
|
.checked_shl(64)
|
||||||
|
.expect("reserve_quote < 2^128, so reserve_quote << 64 fits in U256");
|
||||||
|
let price = numerator
|
||||||
|
.checked_div(U256::from(reserve_base))
|
||||||
|
.expect("reserve_base is non-zero after the assertion above");
|
||||||
|
|
||||||
|
u128::try_from(price).unwrap_or(u128::MAX)
|
||||||
|
}
|
||||||
|
|
||||||
impl TryFrom<&Data> for PoolDefinition {
|
impl TryFrom<&Data> for PoolDefinition {
|
||||||
type Error = std::io::Error;
|
type Error = std::io::Error;
|
||||||
|
|
||||||
@ -434,3 +463,45 @@ pub fn read_vault_fungible_balances(
|
|||||||
|
|
||||||
(vault_a_balance, vault_b_balance)
|
(vault_a_balance, vault_b_balance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// `1.0` in Q64.64 is `2^64`.
|
||||||
|
const ONE_Q64_64: u128 = 1u128 << 64;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn equal_reserves_map_to_unit_price() {
|
||||||
|
assert_eq!(spot_price_q64_64(1_000, 1_000), ONE_Q64_64);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn spot_price_reflects_reserve_ratio() {
|
||||||
|
// reserve_quote / reserve_base = 2.0 -> 2 * 2^64.
|
||||||
|
assert_eq!(spot_price_q64_64(1_000, 2_000), ONE_Q64_64 * 2);
|
||||||
|
// reserve_quote / reserve_base = 0.5 -> 2^64 / 2.
|
||||||
|
assert_eq!(spot_price_q64_64(2_000, 1_000), ONE_Q64_64 / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn spot_price_saturates_instead_of_overflowing() {
|
||||||
|
// A huge quote-to-base ratio would exceed u128 in Q64.64; it must saturate, not panic.
|
||||||
|
assert_eq!(spot_price_q64_64(1, u128::MAX), u128::MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn spot_price_handles_large_reserves_without_intermediate_overflow() {
|
||||||
|
// reserve_quote >= 2^64 would overflow a naive `reserve_quote << 64` in u128; the U256
|
||||||
|
// intermediate keeps it exact. Ratio here is 4.0.
|
||||||
|
let base = 1u128 << 64;
|
||||||
|
let quote = 1u128 << 66;
|
||||||
|
assert_eq!(spot_price_q64_64(base, quote), ONE_Q64_64 * 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "reserve_base must be non-zero")]
|
||||||
|
fn zero_reserve_base_panics() {
|
||||||
|
let _ = spot_price_q64_64(0, 1_000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
2
programs/amm/methods/guest/Cargo.lock
generated
2
programs/amm/methods/guest/Cargo.lock
generated
@ -85,9 +85,11 @@ dependencies = [
|
|||||||
name = "amm_core"
|
name = "amm_core"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"alloy-primitives",
|
||||||
"borsh",
|
"borsh",
|
||||||
"nssa_core",
|
"nssa_core",
|
||||||
"risc0-zkvm",
|
"risc0-zkvm",
|
||||||
|
"ruint",
|
||||||
"serde",
|
"serde",
|
||||||
"spel-framework-macros",
|
"spel-framework-macros",
|
||||||
"token_core",
|
"token_core",
|
||||||
|
|||||||
@ -118,6 +118,8 @@ mod amm {
|
|||||||
user_holding_a: AccountWithMetadata,
|
user_holding_a: AccountWithMetadata,
|
||||||
user_holding_b: AccountWithMetadata,
|
user_holding_b: AccountWithMetadata,
|
||||||
user_holding_lp: AccountWithMetadata,
|
user_holding_lp: AccountWithMetadata,
|
||||||
|
current_tick_account: AccountWithMetadata,
|
||||||
|
clock: AccountWithMetadata,
|
||||||
token_a_amount: u128,
|
token_a_amount: u128,
|
||||||
token_b_amount: u128,
|
token_b_amount: u128,
|
||||||
fees: u128,
|
fees: u128,
|
||||||
@ -133,6 +135,8 @@ mod amm {
|
|||||||
user_holding_a,
|
user_holding_a,
|
||||||
user_holding_b,
|
user_holding_b,
|
||||||
user_holding_lp,
|
user_holding_lp,
|
||||||
|
current_tick_account,
|
||||||
|
clock,
|
||||||
NonZeroU128::new(token_a_amount).expect("token_a_amount must be nonzero"),
|
NonZeroU128::new(token_a_amount).expect("token_a_amount must be nonzero"),
|
||||||
NonZeroU128::new(token_b_amount).expect("token_b_amount must be nonzero"),
|
NonZeroU128::new(token_b_amount).expect("token_b_amount must be nonzero"),
|
||||||
fees,
|
fees,
|
||||||
|
|||||||
@ -4,13 +4,15 @@ use amm_core::{
|
|||||||
assert_supported_fee_tier, compute_config_pda, compute_liquidity_token_pda,
|
assert_supported_fee_tier, compute_config_pda, compute_liquidity_token_pda,
|
||||||
compute_liquidity_token_pda_seed, compute_lp_lock_holding_pda,
|
compute_liquidity_token_pda_seed, compute_lp_lock_holding_pda,
|
||||||
compute_lp_lock_holding_pda_seed, compute_pool_pda, compute_pool_pda_seed, compute_vault_pda,
|
compute_lp_lock_holding_pda_seed, compute_pool_pda, compute_pool_pda_seed, compute_vault_pda,
|
||||||
compute_vault_pda_seed, AmmConfig, PoolDefinition, MINIMUM_LIQUIDITY,
|
compute_vault_pda_seed, spot_price_q64_64, AmmConfig, PoolDefinition, MINIMUM_LIQUIDITY,
|
||||||
};
|
};
|
||||||
|
use clock_core::CLOCK_01_PROGRAM_ACCOUNT_ID;
|
||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
account::{Account, AccountWithMetadata, Data},
|
account::{Account, AccountWithMetadata, Data},
|
||||||
program::{AccountPostState, ChainedCall, Claim, ProgramId},
|
program::{AccountPostState, ChainedCall, Claim, ProgramId},
|
||||||
};
|
};
|
||||||
use token_core::TokenDefinition;
|
use token_core::TokenDefinition;
|
||||||
|
use twap_oracle_core::compute_current_tick_account_pda;
|
||||||
|
|
||||||
#[expect(
|
#[expect(
|
||||||
clippy::too_many_arguments,
|
clippy::too_many_arguments,
|
||||||
@ -26,6 +28,8 @@ pub fn new_definition(
|
|||||||
user_holding_a: AccountWithMetadata,
|
user_holding_a: AccountWithMetadata,
|
||||||
user_holding_b: AccountWithMetadata,
|
user_holding_b: AccountWithMetadata,
|
||||||
user_holding_lp: AccountWithMetadata,
|
user_holding_lp: AccountWithMetadata,
|
||||||
|
current_tick_account: AccountWithMetadata,
|
||||||
|
clock: AccountWithMetadata,
|
||||||
token_a_amount: NonZeroU128,
|
token_a_amount: NonZeroU128,
|
||||||
token_b_amount: NonZeroU128,
|
token_b_amount: NonZeroU128,
|
||||||
fees: u128,
|
fees: u128,
|
||||||
@ -45,9 +49,10 @@ pub fn new_definition(
|
|||||||
compute_config_pda(amm_program_id),
|
compute_config_pda(amm_program_id),
|
||||||
"New definition: AMM config Account ID does not match PDA"
|
"New definition: AMM config Account ID does not match PDA"
|
||||||
);
|
);
|
||||||
let token_program_id = AmmConfig::try_from(&config.account.data)
|
let config_data = AmmConfig::try_from(&config.account.data)
|
||||||
.expect("New definition: AMM Program must be initialized before use")
|
.expect("New definition: AMM Program must be initialized before use");
|
||||||
.token_program_id;
|
let token_program_id = config_data.token_program_id;
|
||||||
|
let twap_oracle_program_id = config_data.twap_oracle_program_id;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
user_holding_a.account.program_owner, token_program_id,
|
user_holding_a.account.program_owner, token_program_id,
|
||||||
@ -100,6 +105,18 @@ pub fn new_definition(
|
|||||||
"Fresh user LP holding requires user authorization"
|
"Fresh user LP holding requires user authorization"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// The pool's TWAP current-tick account is created in the same transaction (a chained call to
|
||||||
|
// the oracle). Validate its PDA and that the clock is the canonical 1-block LEZ clock.
|
||||||
|
assert_eq!(
|
||||||
|
current_tick_account.account_id,
|
||||||
|
compute_current_tick_account_pda(twap_oracle_program_id, pool.account_id),
|
||||||
|
"New definition: current tick Account ID does not match PDA"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
clock.account_id, CLOCK_01_PROGRAM_ACCOUNT_ID,
|
||||||
|
"New definition: clock account must be the canonical 1-block LEZ clock account"
|
||||||
|
);
|
||||||
|
|
||||||
// LP Token minting calculation
|
// LP Token minting calculation
|
||||||
let initial_lp = token_a_amount
|
let initial_lp = token_a_amount
|
||||||
.get()
|
.get()
|
||||||
@ -115,7 +132,6 @@ pub fn new_definition(
|
|||||||
.expect("initial liquidity must exceed minimum liquidity after validation");
|
.expect("initial liquidity must exceed minimum liquidity after validation");
|
||||||
|
|
||||||
// Update pool account
|
// Update pool account
|
||||||
let mut pool_post = pool.account.clone();
|
|
||||||
let pool_post_definition = PoolDefinition {
|
let pool_post_definition = PoolDefinition {
|
||||||
definition_token_a_id,
|
definition_token_a_id,
|
||||||
definition_token_b_id,
|
definition_token_b_id,
|
||||||
@ -128,9 +144,10 @@ pub fn new_definition(
|
|||||||
fees,
|
fees,
|
||||||
};
|
};
|
||||||
|
|
||||||
pool_post.data = Data::from(&pool_post_definition);
|
let mut pool_initialized = pool.account.clone();
|
||||||
|
pool_initialized.data = Data::from(&pool_post_definition);
|
||||||
let pool_post: AccountPostState = AccountPostState::new_claimed(
|
let pool_post: AccountPostState = AccountPostState::new_claimed(
|
||||||
pool_post.clone(),
|
pool_initialized.clone(),
|
||||||
Claim::Pda(compute_pool_pda_seed(
|
Claim::Pda(compute_pool_pda_seed(
|
||||||
definition_token_a_id,
|
definition_token_a_id,
|
||||||
definition_token_b_id,
|
definition_token_b_id,
|
||||||
@ -202,11 +219,41 @@ pub fn new_definition(
|
|||||||
)
|
)
|
||||||
.with_pda_seeds(vec![compute_liquidity_token_pda_seed(pool.account_id)]);
|
.with_pda_seeds(vec![compute_liquidity_token_pda_seed(pool.account_id)]);
|
||||||
|
|
||||||
|
// Chain call to create the pool's TWAP current-tick account, with the pool as the price
|
||||||
|
// source. The oracle derives the tick from the opening spot price (reserve_b / reserve_a as a
|
||||||
|
// Q64.64 ratio), so the seed value is taken from the pool's own reserves, not the caller.
|
||||||
|
//
|
||||||
|
// The pool is claimed (and thus owned by this program) by this same instruction, so the
|
||||||
|
// chained call must present the pool in its post-claim state to match the accumulated state
|
||||||
|
// diff: the runtime sets the claimed pool's owner to this program, so we predict that here.
|
||||||
|
let initial_price = spot_price_q64_64(token_a_amount.get(), token_b_amount.get());
|
||||||
|
let mut pool_price_source_account = pool_initialized;
|
||||||
|
pool_price_source_account.program_owner = amm_program_id;
|
||||||
|
let pool_price_source = AccountWithMetadata {
|
||||||
|
account: pool_price_source_account,
|
||||||
|
is_authorized: true,
|
||||||
|
account_id: pool.account_id,
|
||||||
|
};
|
||||||
|
let call_create_current_tick = ChainedCall::new(
|
||||||
|
twap_oracle_program_id,
|
||||||
|
vec![
|
||||||
|
current_tick_account.clone(),
|
||||||
|
pool_price_source,
|
||||||
|
clock.clone(),
|
||||||
|
],
|
||||||
|
&twap_oracle_core::Instruction::CreateCurrentTickAccount { initial_price },
|
||||||
|
)
|
||||||
|
.with_pda_seeds(vec![compute_pool_pda_seed(
|
||||||
|
definition_token_a_id,
|
||||||
|
definition_token_b_id,
|
||||||
|
)]);
|
||||||
|
|
||||||
let chained_calls = vec![
|
let chained_calls = vec![
|
||||||
call_token_lp_lock,
|
call_token_lp_lock,
|
||||||
call_token_lp_user,
|
call_token_lp_user,
|
||||||
call_token_b,
|
call_token_b,
|
||||||
call_token_a,
|
call_token_a,
|
||||||
|
call_create_current_tick,
|
||||||
];
|
];
|
||||||
|
|
||||||
let post_states = vec![
|
let post_states = vec![
|
||||||
@ -219,6 +266,8 @@ pub fn new_definition(
|
|||||||
AccountPostState::new(user_holding_a.account.clone()),
|
AccountPostState::new(user_holding_a.account.clone()),
|
||||||
AccountPostState::new(user_holding_b.account.clone()),
|
AccountPostState::new(user_holding_b.account.clone()),
|
||||||
AccountPostState::new(user_holding_lp.account.clone()),
|
AccountPostState::new(user_holding_lp.account.clone()),
|
||||||
|
AccountPostState::new(current_tick_account.account.clone()),
|
||||||
|
AccountPostState::new(clock.account.clone()),
|
||||||
];
|
];
|
||||||
|
|
||||||
(post_states, chained_calls)
|
(post_states, chained_calls)
|
||||||
|
|||||||
@ -565,6 +565,33 @@ impl ChainedCallForTests {
|
|||||||
IdForTests::pool_definition_id(),
|
IdForTests::pool_definition_id(),
|
||||||
)])
|
)])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn cc_new_definition_create_current_tick() -> ChainedCall {
|
||||||
|
// The pool is passed to the oracle in its post-claim state: owned by the AMM program and
|
||||||
|
// carrying the freshly written PoolDefinition, authorized as the price source.
|
||||||
|
let mut pool_price_source = AccountForTests::pool_definition_init();
|
||||||
|
pool_price_source.account.program_owner = AMM_PROGRAM_ID;
|
||||||
|
pool_price_source.is_authorized = true;
|
||||||
|
|
||||||
|
let initial_price = amm_core::spot_price_q64_64(
|
||||||
|
BalanceForTests::vault_a_reserve_init(),
|
||||||
|
BalanceForTests::vault_b_reserve_init(),
|
||||||
|
);
|
||||||
|
|
||||||
|
ChainedCall::new(
|
||||||
|
TWAP_ORACLE_PROGRAM_ID,
|
||||||
|
vec![
|
||||||
|
AccountForTests::current_tick_account_uninit(),
|
||||||
|
pool_price_source,
|
||||||
|
AccountForTests::clock(),
|
||||||
|
],
|
||||||
|
&twap_oracle_core::Instruction::CreateCurrentTickAccount { initial_price },
|
||||||
|
)
|
||||||
|
.with_pda_seeds(vec![compute_pool_pda_seed(
|
||||||
|
IdForTests::token_a_definition_id(),
|
||||||
|
IdForTests::token_b_definition_id(),
|
||||||
|
)])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IdForTests {
|
impl IdForTests {
|
||||||
@ -655,6 +682,27 @@ impl AccountWithMetadataForTests {
|
|||||||
config
|
config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The pool's TWAP current-tick PDA, uninitialized (created by `new_definition`).
|
||||||
|
fn current_tick_account_uninit() -> AccountWithMetadata {
|
||||||
|
AccountWithMetadata {
|
||||||
|
account: Account::default(),
|
||||||
|
is_authorized: false,
|
||||||
|
account_id: twap_oracle_core::compute_current_tick_account_pda(
|
||||||
|
TWAP_ORACLE_PROGRAM_ID,
|
||||||
|
IdForTests::pool_definition_id(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The canonical 1-block LEZ clock account.
|
||||||
|
fn clock() -> AccountWithMetadata {
|
||||||
|
AccountWithMetadata {
|
||||||
|
account: Account::default(),
|
||||||
|
is_authorized: false,
|
||||||
|
account_id: clock_core::CLOCK_01_PROGRAM_ACCOUNT_ID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn user_holding_a() -> AccountWithMetadata {
|
fn user_holding_a() -> AccountWithMetadata {
|
||||||
AccountWithMetadata {
|
AccountWithMetadata {
|
||||||
account: Account {
|
account: Account {
|
||||||
@ -2049,6 +2097,8 @@ fn test_call_new_definition_with_zero_balance_1() {
|
|||||||
AccountWithMetadataForTests::user_holding_a(),
|
AccountWithMetadataForTests::user_holding_a(),
|
||||||
AccountWithMetadataForTests::user_holding_b(),
|
AccountWithMetadataForTests::user_holding_b(),
|
||||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||||
|
AccountWithMetadataForTests::current_tick_account_uninit(),
|
||||||
|
AccountWithMetadataForTests::clock(),
|
||||||
NonZero::new(0).expect("Balances must be nonzero"),
|
NonZero::new(0).expect("Balances must be nonzero"),
|
||||||
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
||||||
BalanceForTests::fee_tier(),
|
BalanceForTests::fee_tier(),
|
||||||
@ -2069,6 +2119,8 @@ fn test_call_new_definition_with_zero_balance_2() {
|
|||||||
AccountWithMetadataForTests::user_holding_a(),
|
AccountWithMetadataForTests::user_holding_a(),
|
||||||
AccountWithMetadataForTests::user_holding_b(),
|
AccountWithMetadataForTests::user_holding_b(),
|
||||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||||
|
AccountWithMetadataForTests::current_tick_account_uninit(),
|
||||||
|
AccountWithMetadataForTests::clock(),
|
||||||
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
||||||
NonZero::new(0).expect("Balances must be nonzero"),
|
NonZero::new(0).expect("Balances must be nonzero"),
|
||||||
BalanceForTests::fee_tier(),
|
BalanceForTests::fee_tier(),
|
||||||
@ -2089,6 +2141,8 @@ fn test_call_new_definition_same_token_definition() {
|
|||||||
AccountWithMetadataForTests::user_holding_a(),
|
AccountWithMetadataForTests::user_holding_a(),
|
||||||
AccountWithMetadataForTests::user_holding_a(),
|
AccountWithMetadataForTests::user_holding_a(),
|
||||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||||
|
AccountWithMetadataForTests::current_tick_account_uninit(),
|
||||||
|
AccountWithMetadataForTests::clock(),
|
||||||
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
||||||
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
||||||
BalanceForTests::fee_tier(),
|
BalanceForTests::fee_tier(),
|
||||||
@ -2109,6 +2163,8 @@ fn test_call_new_definition_wrong_liquidity_id() {
|
|||||||
AccountWithMetadataForTests::user_holding_a(),
|
AccountWithMetadataForTests::user_holding_a(),
|
||||||
AccountWithMetadataForTests::user_holding_b(),
|
AccountWithMetadataForTests::user_holding_b(),
|
||||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||||
|
AccountWithMetadataForTests::current_tick_account_uninit(),
|
||||||
|
AccountWithMetadataForTests::clock(),
|
||||||
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
||||||
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
||||||
BalanceForTests::fee_tier(),
|
BalanceForTests::fee_tier(),
|
||||||
@ -2129,6 +2185,8 @@ fn test_call_new_definition_wrong_lp_lock_holding_id() {
|
|||||||
AccountWithMetadataForTests::user_holding_a(),
|
AccountWithMetadataForTests::user_holding_a(),
|
||||||
AccountWithMetadataForTests::user_holding_b(),
|
AccountWithMetadataForTests::user_holding_b(),
|
||||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||||
|
AccountWithMetadataForTests::current_tick_account_uninit(),
|
||||||
|
AccountWithMetadataForTests::clock(),
|
||||||
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
||||||
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
||||||
BalanceForTests::fee_tier(),
|
BalanceForTests::fee_tier(),
|
||||||
@ -2149,6 +2207,8 @@ fn test_call_new_definition_wrong_pool_id() {
|
|||||||
AccountWithMetadataForTests::user_holding_a(),
|
AccountWithMetadataForTests::user_holding_a(),
|
||||||
AccountWithMetadataForTests::user_holding_b(),
|
AccountWithMetadataForTests::user_holding_b(),
|
||||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||||
|
AccountWithMetadataForTests::current_tick_account_uninit(),
|
||||||
|
AccountWithMetadataForTests::clock(),
|
||||||
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
||||||
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
||||||
BalanceForTests::fee_tier(),
|
BalanceForTests::fee_tier(),
|
||||||
@ -2169,6 +2229,8 @@ fn test_call_new_definition_wrong_vault_id_1() {
|
|||||||
AccountWithMetadataForTests::user_holding_a(),
|
AccountWithMetadataForTests::user_holding_a(),
|
||||||
AccountWithMetadataForTests::user_holding_b(),
|
AccountWithMetadataForTests::user_holding_b(),
|
||||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||||
|
AccountWithMetadataForTests::current_tick_account_uninit(),
|
||||||
|
AccountWithMetadataForTests::clock(),
|
||||||
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
||||||
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
||||||
BalanceForTests::fee_tier(),
|
BalanceForTests::fee_tier(),
|
||||||
@ -2189,6 +2251,8 @@ fn test_call_new_definition_wrong_vault_id_2() {
|
|||||||
AccountWithMetadataForTests::user_holding_a(),
|
AccountWithMetadataForTests::user_holding_a(),
|
||||||
AccountWithMetadataForTests::user_holding_b(),
|
AccountWithMetadataForTests::user_holding_b(),
|
||||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||||
|
AccountWithMetadataForTests::current_tick_account_uninit(),
|
||||||
|
AccountWithMetadataForTests::clock(),
|
||||||
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
||||||
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
||||||
BalanceForTests::fee_tier(),
|
BalanceForTests::fee_tier(),
|
||||||
@ -2210,6 +2274,8 @@ fn test_call_new_definition_rejects_initialized_pool() {
|
|||||||
AccountWithMetadataForTests::user_holding_a(),
|
AccountWithMetadataForTests::user_holding_a(),
|
||||||
AccountWithMetadataForTests::user_holding_b(),
|
AccountWithMetadataForTests::user_holding_b(),
|
||||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||||
|
AccountWithMetadataForTests::current_tick_account_uninit(),
|
||||||
|
AccountWithMetadataForTests::clock(),
|
||||||
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
||||||
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
||||||
BalanceForTests::fee_tier(),
|
BalanceForTests::fee_tier(),
|
||||||
@ -2231,6 +2297,8 @@ fn test_call_new_definition_initial_lp_too_small() {
|
|||||||
AccountWithMetadataForTests::user_holding_a(),
|
AccountWithMetadataForTests::user_holding_a(),
|
||||||
AccountWithMetadataForTests::user_holding_b(),
|
AccountWithMetadataForTests::user_holding_b(),
|
||||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||||
|
AccountWithMetadataForTests::current_tick_account_uninit(),
|
||||||
|
AccountWithMetadataForTests::clock(),
|
||||||
NonZero::new(MINIMUM_LIQUIDITY).unwrap(),
|
NonZero::new(MINIMUM_LIQUIDITY).unwrap(),
|
||||||
NonZero::new(MINIMUM_LIQUIDITY).unwrap(),
|
NonZero::new(MINIMUM_LIQUIDITY).unwrap(),
|
||||||
BalanceForTests::fee_tier(),
|
BalanceForTests::fee_tier(),
|
||||||
@ -2250,6 +2318,8 @@ fn test_call_new_definition_chained_call_successful() {
|
|||||||
AccountWithMetadataForTests::user_holding_a(),
|
AccountWithMetadataForTests::user_holding_a(),
|
||||||
AccountWithMetadataForTests::user_holding_b(),
|
AccountWithMetadataForTests::user_holding_b(),
|
||||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||||
|
AccountWithMetadataForTests::current_tick_account_uninit(),
|
||||||
|
AccountWithMetadataForTests::clock(),
|
||||||
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
||||||
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
||||||
BalanceForTests::fee_tier(),
|
BalanceForTests::fee_tier(),
|
||||||
@ -2276,6 +2346,14 @@ fn test_call_new_definition_chained_call_successful() {
|
|||||||
assert!(chained_call_b == ChainedCallForTests::cc_new_definition_token_b());
|
assert!(chained_call_b == ChainedCallForTests::cc_new_definition_token_b());
|
||||||
assert!(chained_call_lp_lock == ChainedCallForTests::cc_new_definition_token_lp_lock());
|
assert!(chained_call_lp_lock == ChainedCallForTests::cc_new_definition_token_lp_lock());
|
||||||
assert!(chained_call_lp_user == ChainedCallForTests::cc_new_definition_token_lp_user());
|
assert!(chained_call_lp_user == ChainedCallForTests::cc_new_definition_token_lp_user());
|
||||||
|
|
||||||
|
// The fifth chained call creates the pool's TWAP current-tick account, seeding the tick from
|
||||||
|
// the opening reserves.
|
||||||
|
assert_eq!(chained_calls.len(), 5);
|
||||||
|
assert!(chained_calls[4] == ChainedCallForTests::cc_new_definition_create_current_tick());
|
||||||
|
|
||||||
|
// Two extra post-states (current-tick + clock) are echoed back unchanged.
|
||||||
|
assert_eq!(post_states.len(), 11);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[should_panic(expected = "AccountId is not a token type for the pool")]
|
#[should_panic(expected = "AccountId is not a token type for the pool")]
|
||||||
@ -2957,6 +3035,8 @@ fn test_new_definition_lp_asymmetric_amounts() {
|
|||||||
AccountWithMetadataForTests::user_holding_a(),
|
AccountWithMetadataForTests::user_holding_a(),
|
||||||
AccountWithMetadataForTests::user_holding_b(),
|
AccountWithMetadataForTests::user_holding_b(),
|
||||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||||
|
AccountWithMetadataForTests::current_tick_account_uninit(),
|
||||||
|
AccountWithMetadataForTests::clock(),
|
||||||
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
||||||
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
||||||
BalanceForTests::fee_tier(),
|
BalanceForTests::fee_tier(),
|
||||||
@ -2995,6 +3075,8 @@ fn test_new_definition_lp_symmetric_amounts() {
|
|||||||
AccountWithMetadataForTests::user_holding_a(),
|
AccountWithMetadataForTests::user_holding_a(),
|
||||||
AccountWithMetadataForTests::user_holding_b(),
|
AccountWithMetadataForTests::user_holding_b(),
|
||||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||||
|
AccountWithMetadataForTests::current_tick_account_uninit(),
|
||||||
|
AccountWithMetadataForTests::clock(),
|
||||||
NonZero::new(token_a_amount).unwrap(),
|
NonZero::new(token_a_amount).unwrap(),
|
||||||
NonZero::new(token_b_amount).unwrap(),
|
NonZero::new(token_b_amount).unwrap(),
|
||||||
BalanceForTests::fee_tier(),
|
BalanceForTests::fee_tier(),
|
||||||
@ -3065,6 +3147,8 @@ fn test_minimum_liquidity_lock_and_remove_all_user_lp() {
|
|||||||
AccountForTests::user_holding_a(),
|
AccountForTests::user_holding_a(),
|
||||||
AccountForTests::user_holding_b(),
|
AccountForTests::user_holding_b(),
|
||||||
AccountForTests::user_holding_lp_uninit(),
|
AccountForTests::user_holding_lp_uninit(),
|
||||||
|
AccountWithMetadataForTests::current_tick_account_uninit(),
|
||||||
|
AccountWithMetadataForTests::clock(),
|
||||||
NonZero::new(token_a_amount).unwrap(),
|
NonZero::new(token_a_amount).unwrap(),
|
||||||
NonZero::new(token_b_amount).unwrap(),
|
NonZero::new(token_b_amount).unwrap(),
|
||||||
BalanceForTests::fee_tier(),
|
BalanceForTests::fee_tier(),
|
||||||
@ -3289,6 +3373,8 @@ fn new_definition_overflow_protection() {
|
|||||||
AccountWithMetadataForTests::user_holding_a(),
|
AccountWithMetadataForTests::user_holding_a(),
|
||||||
AccountWithMetadataForTests::user_holding_b(),
|
AccountWithMetadataForTests::user_holding_b(),
|
||||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||||
|
AccountWithMetadataForTests::current_tick_account_uninit(),
|
||||||
|
AccountWithMetadataForTests::clock(),
|
||||||
NonZero::new(large_amount).unwrap(),
|
NonZero::new(large_amount).unwrap(),
|
||||||
NonZero::new(2).unwrap(),
|
NonZero::new(2).unwrap(),
|
||||||
BalanceForTests::fee_tier(),
|
BalanceForTests::fee_tier(),
|
||||||
@ -3544,6 +3630,8 @@ fn test_new_definition_supports_all_fee_tiers() {
|
|||||||
AccountWithMetadataForTests::user_holding_a(),
|
AccountWithMetadataForTests::user_holding_a(),
|
||||||
AccountWithMetadataForTests::user_holding_b(),
|
AccountWithMetadataForTests::user_holding_b(),
|
||||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||||
|
AccountWithMetadataForTests::current_tick_account_uninit(),
|
||||||
|
AccountWithMetadataForTests::clock(),
|
||||||
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
||||||
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
||||||
fees,
|
fees,
|
||||||
@ -3569,6 +3657,8 @@ fn test_new_definition_rejects_unsupported_fee_tier() {
|
|||||||
AccountWithMetadataForTests::user_holding_a(),
|
AccountWithMetadataForTests::user_holding_a(),
|
||||||
AccountWithMetadataForTests::user_holding_b(),
|
AccountWithMetadataForTests::user_holding_b(),
|
||||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||||
|
AccountWithMetadataForTests::current_tick_account_uninit(),
|
||||||
|
AccountWithMetadataForTests::clock(),
|
||||||
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
||||||
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
||||||
2,
|
2,
|
||||||
|
|||||||
@ -328,20 +328,6 @@ impl Accounts {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The pool's TWAP current-tick account, owned by the oracle program. Seeded directly into
|
|
||||||
/// state so the AMM has an authoritative tick to read when creating observations.
|
|
||||||
fn current_tick_account(tick: i32) -> Account {
|
|
||||||
Account {
|
|
||||||
program_owner: Ids::twap_oracle_program(),
|
|
||||||
balance: 0_u128,
|
|
||||||
data: Data::from(&twap_oracle_core::CurrentTickAccount {
|
|
||||||
tick,
|
|
||||||
last_updated: 1_700_000_000_000,
|
|
||||||
}),
|
|
||||||
nonce: Nonce(0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn user_a_holding() -> Account {
|
fn user_a_holding() -> Account {
|
||||||
Account {
|
Account {
|
||||||
program_owner: Ids::token_program(),
|
program_owner: Ids::token_program(),
|
||||||
@ -1055,6 +1041,8 @@ fn try_execute_new_definition(
|
|||||||
Ids::user_a(),
|
Ids::user_a(),
|
||||||
Ids::user_b(),
|
Ids::user_b(),
|
||||||
Ids::user_lp(),
|
Ids::user_lp(),
|
||||||
|
Ids::current_tick_account(),
|
||||||
|
CLOCK_01_PROGRAM_ACCOUNT_ID,
|
||||||
],
|
],
|
||||||
if authorize_user_lp {
|
if authorize_user_lp {
|
||||||
vec![
|
vec![
|
||||||
@ -1276,6 +1264,18 @@ fn execute_create_price_observations(
|
|||||||
state.transition_from_public_transaction(&tx, 0, 0)
|
state.transition_from_public_transaction(&tx, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Builds a state whose pool was created through `new_definition`, which also creates the pool's
|
||||||
|
/// TWAP current-tick account (seeded from the opening reserves). Used by the observation tests so
|
||||||
|
/// they consume the real current-tick account rather than a hand-inserted one.
|
||||||
|
#[cfg(test)]
|
||||||
|
fn state_with_pool_created_via_new_definition() -> V03State {
|
||||||
|
let mut state = state_for_amm_tests_with_new_def();
|
||||||
|
state.force_insert_account(Ids::vault_a(), Accounts::vault_a_reinitializable());
|
||||||
|
state.force_insert_account(Ids::vault_b(), Accounts::vault_b_reinitializable());
|
||||||
|
execute_new_definition(&mut state, Balances::fee_tier());
|
||||||
|
state
|
||||||
|
}
|
||||||
|
|
||||||
fn fungible_balance(account: &Account) -> u128 {
|
fn fungible_balance(account: &Account) -> u128 {
|
||||||
let holding = TokenHolding::try_from(&account.data).expect("expected token holding");
|
let holding = TokenHolding::try_from(&account.data).expect("expected token holding");
|
||||||
let TokenHolding::Fungible {
|
let TokenHolding::Fungible {
|
||||||
@ -1424,15 +1424,15 @@ fn amm_update_config_authority_handoff_revokes_old_admin() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn amm_creates_price_observations_on_twap_oracle() {
|
fn amm_creates_price_observations_on_twap_oracle() {
|
||||||
let mut state = state_for_amm_tests();
|
let mut state = state_with_pool_created_via_new_definition();
|
||||||
let window_duration = 24 * 60 * 60 * 1_000u64;
|
let window_duration = 24 * 60 * 60 * 1_000u64;
|
||||||
let current_tick = 1_234_i32;
|
|
||||||
|
|
||||||
// The pool already has an authoritative current-tick account written by the oracle.
|
// The current-tick account created during pool creation supplies the authoritative seed tick,
|
||||||
state.force_insert_account(
|
// derived from the opening reserves (reserve_b / reserve_a as a Q64.64 spot price).
|
||||||
Ids::current_tick_account(),
|
let expected_tick = twap_oracle_core::price_to_tick(amm_core::spot_price_q64_64(
|
||||||
Accounts::current_tick_account(current_tick),
|
Balances::vault_a_init(),
|
||||||
);
|
Balances::vault_b_init(),
|
||||||
|
));
|
||||||
|
|
||||||
// The observations PDA does not exist before the AMM creates it.
|
// The observations PDA does not exist before the AMM creates it.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -1451,7 +1451,7 @@ fn amm_creates_price_observations_on_twap_oracle() {
|
|||||||
let feed = twap_oracle_core::PriceObservations::try_from(&account.data)
|
let feed = twap_oracle_core::PriceObservations::try_from(&account.data)
|
||||||
.expect("observations account must hold a valid PriceObservations");
|
.expect("observations account must hold a valid PriceObservations");
|
||||||
assert_eq!(feed.price_source_id, Ids::pool_definition());
|
assert_eq!(feed.price_source_id, Ids::pool_definition());
|
||||||
assert_eq!(feed.last_recorded_tick, current_tick);
|
assert_eq!(feed.last_recorded_tick, expected_tick);
|
||||||
assert_eq!(feed.write_index, 1);
|
assert_eq!(feed.write_index, 1);
|
||||||
assert_eq!(feed.total_entries, 1);
|
assert_eq!(feed.total_entries, 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -1464,18 +1464,14 @@ fn amm_creates_price_observations_on_twap_oracle() {
|
|||||||
assert_eq!(state.get_account_by_id(Ids::config()), Accounts::config());
|
assert_eq!(state.get_account_by_id(Ids::config()), Accounts::config());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
state.get_account_by_id(Ids::pool_definition()),
|
state.get_account_by_id(Ids::pool_definition()),
|
||||||
Accounts::pool_definition_init()
|
Accounts::pool_definition_new_init()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn amm_create_price_observations_rejects_existing_account() {
|
fn amm_create_price_observations_rejects_existing_account() {
|
||||||
let mut state = state_for_amm_tests();
|
let mut state = state_with_pool_created_via_new_definition();
|
||||||
let window_duration = 24 * 60 * 60 * 1_000u64;
|
let window_duration = 24 * 60 * 60 * 1_000u64;
|
||||||
state.force_insert_account(
|
|
||||||
Ids::current_tick_account(),
|
|
||||||
Accounts::current_tick_account(1_234),
|
|
||||||
);
|
|
||||||
|
|
||||||
// First creation succeeds.
|
// First creation succeeds.
|
||||||
execute_create_price_observations(&mut state, window_duration).unwrap();
|
execute_create_price_observations(&mut state, window_duration).unwrap();
|
||||||
@ -1651,6 +1647,19 @@ fn amm_new_definition_uninitialized_pool() {
|
|||||||
state.get_account_by_id(Ids::user_lp()),
|
state.get_account_by_id(Ids::user_lp()),
|
||||||
Accounts::user_lp_holding_new_init()
|
Accounts::user_lp_holding_new_init()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Pool creation also created the pool's TWAP current-tick account (a chained call to the
|
||||||
|
// oracle), owned by the oracle program and seeded with the tick derived from the opening
|
||||||
|
// reserves (reserve_b / reserve_a as a Q64.64 spot price).
|
||||||
|
let current_tick = state.get_account_by_id(Ids::current_tick_account());
|
||||||
|
assert_eq!(current_tick.program_owner, Ids::twap_oracle_program());
|
||||||
|
let tick_account = twap_oracle_core::CurrentTickAccount::try_from(¤t_tick.data)
|
||||||
|
.expect("current tick account must hold a valid CurrentTickAccount");
|
||||||
|
let expected_tick = twap_oracle_core::price_to_tick(amm_core::spot_price_q64_64(
|
||||||
|
Balances::vault_a_init(),
|
||||||
|
Balances::vault_b_init(),
|
||||||
|
));
|
||||||
|
assert_eq!(tick_account.tick, expected_tick);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -2206,6 +2215,8 @@ fn amm_new_definition_rejects_expired_deadline() {
|
|||||||
Ids::user_a(),
|
Ids::user_a(),
|
||||||
Ids::user_b(),
|
Ids::user_b(),
|
||||||
Ids::user_lp(),
|
Ids::user_lp(),
|
||||||
|
Ids::current_tick_account(),
|
||||||
|
CLOCK_01_PROGRAM_ACCOUNT_ID,
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
current_nonce(&state, Ids::user_a()),
|
current_nonce(&state, Ids::user_a()),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user