mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-08-01 11:43:17 +00:00
refactor(amm)!: select swap direction by input holding, sign only the input
Replace the `token_definition_id_in` argument with a role-based account interface: swaps now take a `user_input_holding` and a `user_output_holding` instead of positional token-A/token-B holdings. Direction is derived from the input holding's own token definition, and the input slot is a framework-level `#[account(signer)]` so authorization is enforced before execution rather than delegated solely to the downstream token transfer. The output holding only receives and needs no signature. This removes the ambiguity the previous arg-based model carried (the arg could disagree with the signed holding) and makes the IDL express the signing rule: `user_input_holding` is `signer: true`, `user_output_holding` is `signer: false`. The two user-holding post-states are echoed in the guest's declared slot order (input, then output); the framework matches post-states to accounts by position, so the internal A/B mapping used for reserve bookkeeping must not leak into the returned order. BREAKING CHANGE: The AMM swap instruction interface changed and the guest ImageID/ProgramId changes as a result.
This commit is contained in:
parent
63d2fe6f0b
commit
cca063ce2d
@ -423,15 +423,15 @@
|
|||||||
"init": false
|
"init": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "user_holding_a",
|
"name": "user_input_holding",
|
||||||
"writable": true,
|
"writable": true,
|
||||||
"signer": true,
|
"signer": true,
|
||||||
"init": false
|
"init": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "user_holding_b",
|
"name": "user_output_holding",
|
||||||
"writable": true,
|
"writable": true,
|
||||||
"signer": true,
|
"signer": false,
|
||||||
"init": false
|
"init": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -456,10 +456,6 @@
|
|||||||
"name": "min_amount_out",
|
"name": "min_amount_out",
|
||||||
"type": "u128"
|
"type": "u128"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "token_definition_id_in",
|
|
||||||
"type": "account_id"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "deadline",
|
"name": "deadline",
|
||||||
"type": "u64"
|
"type": "u64"
|
||||||
@ -494,15 +490,15 @@
|
|||||||
"init": false
|
"init": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "user_holding_a",
|
"name": "user_input_holding",
|
||||||
"writable": true,
|
"writable": true,
|
||||||
"signer": true,
|
"signer": true,
|
||||||
"init": false
|
"init": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "user_holding_b",
|
"name": "user_output_holding",
|
||||||
"writable": true,
|
"writable": true,
|
||||||
"signer": true,
|
"signer": false,
|
||||||
"init": false
|
"init": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -527,10 +523,6 @@
|
|||||||
"name": "max_amount_in",
|
"name": "max_amount_in",
|
||||||
"type": "u128"
|
"type": "u128"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "token_definition_id_in",
|
|
||||||
"type": "account_id"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "deadline",
|
"name": "deadline",
|
||||||
"type": "u64"
|
"type": "u64"
|
||||||
|
|||||||
@ -172,15 +172,18 @@ pub enum Instruction {
|
|||||||
deadline: u64,
|
deadline: u64,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Swap some quantity of Tokens (either Token A or Token B)
|
/// Swap some quantity of tokens while maintaining the Pool constant product.
|
||||||
/// while maintaining the Pool constant product.
|
///
|
||||||
|
/// Swap direction is determined by the input holding: `user_input_holding`'s token definition
|
||||||
|
/// selects which pool token is sold. That holding must be signed so the downstream token
|
||||||
|
/// transfer can debit it; `user_output_holding` only receives and needs no signature.
|
||||||
///
|
///
|
||||||
/// Required accounts:
|
/// Required accounts:
|
||||||
/// - AMM Pool (initialized)
|
/// - AMM Pool (initialized)
|
||||||
/// - Vault Holding Account for Token A (initialized)
|
/// - Vault Holding Account for Token A (initialized)
|
||||||
/// - Vault Holding Account for Token B (initialized)
|
/// - Vault Holding Account for Token B (initialized)
|
||||||
/// - User Holding Account for Token A
|
/// - User Input Holding Account (initialized, signed) — the token being sold
|
||||||
/// - User Holding Account for Token B; either is authorized.
|
/// - User Output Holding Account (initialized) — receives the token being bought
|
||||||
/// - Current Tick Account, the pool's TWAP PDA derived as
|
/// - Current Tick Account, the pool's TWAP PDA derived as
|
||||||
/// `compute_current_tick_account_pda(twap_oracle_program_id, pool.account_id)`; refreshed
|
/// `compute_current_tick_account_pda(twap_oracle_program_id, pool.account_id)`; refreshed
|
||||||
/// with the new spot price
|
/// with the new spot price
|
||||||
@ -188,20 +191,23 @@ pub enum Instruction {
|
|||||||
SwapExactInput {
|
SwapExactInput {
|
||||||
swap_amount_in: u128,
|
swap_amount_in: u128,
|
||||||
min_amount_out: u128,
|
min_amount_out: u128,
|
||||||
token_definition_id_in: AccountId,
|
|
||||||
/// Unix timestamp (milliseconds) after which this transaction is invalid.
|
/// Unix timestamp (milliseconds) after which this transaction is invalid.
|
||||||
deadline: u64,
|
deadline: u64,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Swap tokens specifying the exact desired output amount,
|
/// Swap tokens specifying the exact desired output amount while maintaining the Pool constant
|
||||||
/// while maintaining the Pool constant product.
|
/// product.
|
||||||
|
///
|
||||||
|
/// Swap direction is determined by the input holding: `user_input_holding`'s token definition
|
||||||
|
/// selects which pool token is sold. That holding must be signed so the downstream token
|
||||||
|
/// transfer can debit it; `user_output_holding` only receives and needs no signature.
|
||||||
///
|
///
|
||||||
/// Required accounts:
|
/// Required accounts:
|
||||||
/// - AMM Pool (initialized)
|
/// - AMM Pool (initialized)
|
||||||
/// - Vault Holding Account for Token A (initialized)
|
/// - Vault Holding Account for Token A (initialized)
|
||||||
/// - Vault Holding Account for Token B (initialized)
|
/// - Vault Holding Account for Token B (initialized)
|
||||||
/// - User Holding Account for Token A
|
/// - User Input Holding Account (initialized, signed) — the token being sold
|
||||||
/// - User Holding Account for Token B; either is authorized.
|
/// - User Output Holding Account (initialized) — receives the token being bought
|
||||||
/// - Current Tick Account, the pool's TWAP PDA derived as
|
/// - Current Tick Account, the pool's TWAP PDA derived as
|
||||||
/// `compute_current_tick_account_pda(twap_oracle_program_id, pool.account_id)`; refreshed
|
/// `compute_current_tick_account_pda(twap_oracle_program_id, pool.account_id)`; refreshed
|
||||||
/// with the new spot price
|
/// with the new spot price
|
||||||
@ -209,7 +215,6 @@ pub enum Instruction {
|
|||||||
SwapExactOutput {
|
SwapExactOutput {
|
||||||
exact_amount_out: u128,
|
exact_amount_out: u128,
|
||||||
max_amount_in: u128,
|
max_amount_in: u128,
|
||||||
token_definition_id_in: AccountId,
|
|
||||||
/// Unix timestamp (milliseconds) after which this transaction is invalid.
|
/// Unix timestamp (milliseconds) after which this transaction is invalid.
|
||||||
deadline: u64,
|
deadline: u64,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -298,6 +298,9 @@ mod amm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Swap some quantity of tokens while maintaining the pool constant product.
|
/// Swap some quantity of tokens while maintaining the pool constant product.
|
||||||
|
///
|
||||||
|
/// The swap direction is the input holding's own token; `user_input_holding` must be signed so
|
||||||
|
/// the downstream token transfer can debit it. `user_output_holding` only receives.
|
||||||
#[expect(
|
#[expect(
|
||||||
clippy::too_many_arguments,
|
clippy::too_many_arguments,
|
||||||
reason = "instruction interface requires explicit pool, vault, user accounts, and bounds"
|
reason = "instruction interface requires explicit pool, vault, user accounts, and bounds"
|
||||||
@ -313,15 +316,14 @@ mod amm {
|
|||||||
#[account(mut)]
|
#[account(mut)]
|
||||||
vault_b: AccountWithMetadata,
|
vault_b: AccountWithMetadata,
|
||||||
#[account(mut, signer)]
|
#[account(mut, signer)]
|
||||||
user_holding_a: AccountWithMetadata,
|
user_input_holding: AccountWithMetadata,
|
||||||
#[account(mut, signer)]
|
#[account(mut)]
|
||||||
user_holding_b: AccountWithMetadata,
|
user_output_holding: AccountWithMetadata,
|
||||||
#[account(mut)]
|
#[account(mut)]
|
||||||
current_tick_account: AccountWithMetadata,
|
current_tick_account: AccountWithMetadata,
|
||||||
clock: AccountWithMetadata,
|
clock: AccountWithMetadata,
|
||||||
swap_amount_in: u128,
|
swap_amount_in: u128,
|
||||||
min_amount_out: u128,
|
min_amount_out: u128,
|
||||||
token_definition_id_in: AccountId,
|
|
||||||
deadline: u64,
|
deadline: u64,
|
||||||
) -> SpelResult {
|
) -> SpelResult {
|
||||||
let (post_states, chained_calls) = amm_program::swap::swap_exact_input(
|
let (post_states, chained_calls) = amm_program::swap::swap_exact_input(
|
||||||
@ -329,13 +331,12 @@ mod amm {
|
|||||||
pool,
|
pool,
|
||||||
vault_a,
|
vault_a,
|
||||||
vault_b,
|
vault_b,
|
||||||
user_holding_a,
|
user_input_holding,
|
||||||
user_holding_b,
|
user_output_holding,
|
||||||
current_tick_account,
|
current_tick_account,
|
||||||
clock,
|
clock,
|
||||||
swap_amount_in,
|
swap_amount_in,
|
||||||
min_amount_out,
|
min_amount_out,
|
||||||
token_definition_id_in,
|
|
||||||
ctx.self_program_id,
|
ctx.self_program_id,
|
||||||
);
|
);
|
||||||
Ok(spel_framework::SpelOutput::execute(post_states, chained_calls)
|
Ok(spel_framework::SpelOutput::execute(post_states, chained_calls)
|
||||||
@ -343,6 +344,9 @@ mod amm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Swap tokens specifying the exact desired output amount.
|
/// Swap tokens specifying the exact desired output amount.
|
||||||
|
///
|
||||||
|
/// The swap direction is the input holding's own token; `user_input_holding` must be signed so
|
||||||
|
/// the downstream token transfer can debit it. `user_output_holding` only receives.
|
||||||
#[expect(
|
#[expect(
|
||||||
clippy::too_many_arguments,
|
clippy::too_many_arguments,
|
||||||
reason = "instruction interface requires explicit pool, vault, user accounts, and bounds"
|
reason = "instruction interface requires explicit pool, vault, user accounts, and bounds"
|
||||||
@ -358,15 +362,14 @@ mod amm {
|
|||||||
#[account(mut)]
|
#[account(mut)]
|
||||||
vault_b: AccountWithMetadata,
|
vault_b: AccountWithMetadata,
|
||||||
#[account(mut, signer)]
|
#[account(mut, signer)]
|
||||||
user_holding_a: AccountWithMetadata,
|
user_input_holding: AccountWithMetadata,
|
||||||
#[account(mut, signer)]
|
#[account(mut)]
|
||||||
user_holding_b: AccountWithMetadata,
|
user_output_holding: AccountWithMetadata,
|
||||||
#[account(mut)]
|
#[account(mut)]
|
||||||
current_tick_account: AccountWithMetadata,
|
current_tick_account: AccountWithMetadata,
|
||||||
clock: AccountWithMetadata,
|
clock: AccountWithMetadata,
|
||||||
exact_amount_out: u128,
|
exact_amount_out: u128,
|
||||||
max_amount_in: u128,
|
max_amount_in: u128,
|
||||||
token_definition_id_in: AccountId,
|
|
||||||
deadline: u64,
|
deadline: u64,
|
||||||
) -> SpelResult {
|
) -> SpelResult {
|
||||||
let (post_states, chained_calls) = amm_program::swap::swap_exact_output(
|
let (post_states, chained_calls) = amm_program::swap::swap_exact_output(
|
||||||
@ -374,13 +377,12 @@ mod amm {
|
|||||||
pool,
|
pool,
|
||||||
vault_a,
|
vault_a,
|
||||||
vault_b,
|
vault_b,
|
||||||
user_holding_a,
|
user_input_holding,
|
||||||
user_holding_b,
|
user_output_holding,
|
||||||
current_tick_account,
|
current_tick_account,
|
||||||
clock,
|
clock,
|
||||||
exact_amount_out,
|
exact_amount_out,
|
||||||
max_amount_in,
|
max_amount_in,
|
||||||
token_definition_id_in,
|
|
||||||
ctx.self_program_id,
|
ctx.self_program_id,
|
||||||
);
|
);
|
||||||
Ok(spel_framework::SpelOutput::execute(post_states, chained_calls)
|
Ok(spel_framework::SpelOutput::execute(post_states, chained_calls)
|
||||||
|
|||||||
@ -65,8 +65,10 @@ fn finalize_swap(
|
|||||||
pool_def_data: PoolDefinition,
|
pool_def_data: PoolDefinition,
|
||||||
vault_a: AccountWithMetadata,
|
vault_a: AccountWithMetadata,
|
||||||
vault_b: AccountWithMetadata,
|
vault_b: AccountWithMetadata,
|
||||||
user_holding_a: AccountWithMetadata,
|
// Echoed back at the input/output slot positions the guest declared, so the framework matches
|
||||||
user_holding_b: AccountWithMetadata,
|
// each post-state to the correct account regardless of swap direction.
|
||||||
|
user_holding_input: AccountWithMetadata,
|
||||||
|
user_holding_output: AccountWithMetadata,
|
||||||
current_tick_account: AccountWithMetadata,
|
current_tick_account: AccountWithMetadata,
|
||||||
clock: AccountWithMetadata,
|
clock: AccountWithMetadata,
|
||||||
deposit_a: u128,
|
deposit_a: u128,
|
||||||
@ -124,8 +126,8 @@ fn finalize_swap(
|
|||||||
AccountPostState::new(pool_post),
|
AccountPostState::new(pool_post),
|
||||||
AccountPostState::new(vault_a.account),
|
AccountPostState::new(vault_a.account),
|
||||||
AccountPostState::new(vault_b.account),
|
AccountPostState::new(vault_b.account),
|
||||||
AccountPostState::new(user_holding_a.account),
|
AccountPostState::new(user_holding_input.account),
|
||||||
AccountPostState::new(user_holding_b.account),
|
AccountPostState::new(user_holding_output.account),
|
||||||
AccountPostState::new(current_tick_account.account),
|
AccountPostState::new(current_tick_account.account),
|
||||||
AccountPostState::new(clock.account),
|
AccountPostState::new(clock.account),
|
||||||
];
|
];
|
||||||
@ -143,13 +145,12 @@ pub fn swap_exact_input(
|
|||||||
pool: AccountWithMetadata,
|
pool: AccountWithMetadata,
|
||||||
vault_a: AccountWithMetadata,
|
vault_a: AccountWithMetadata,
|
||||||
vault_b: AccountWithMetadata,
|
vault_b: AccountWithMetadata,
|
||||||
user_holding_a: AccountWithMetadata,
|
user_input_holding: AccountWithMetadata,
|
||||||
user_holding_b: AccountWithMetadata,
|
user_output_holding: AccountWithMetadata,
|
||||||
current_tick_account: AccountWithMetadata,
|
current_tick_account: AccountWithMetadata,
|
||||||
clock: AccountWithMetadata,
|
clock: AccountWithMetadata,
|
||||||
swap_amount_in: u128,
|
swap_amount_in: u128,
|
||||||
min_amount_out: u128,
|
min_amount_out: u128,
|
||||||
token_in_id: AccountId,
|
|
||||||
amm_program_id: ProgramId,
|
amm_program_id: ProgramId,
|
||||||
) -> (Vec<AccountPostState>, Vec<ChainedCall>) {
|
) -> (Vec<AccountPostState>, Vec<ChainedCall>) {
|
||||||
let pool_def_data = validate_swap_setup(&pool, &vault_a, &vault_b);
|
let pool_def_data = validate_swap_setup(&pool, &vault_a, &vault_b);
|
||||||
@ -173,6 +174,20 @@ pub fn swap_exact_input(
|
|||||||
vault_b.account.program_owner, token_program_id,
|
vault_b.account.program_owner, token_program_id,
|
||||||
"Vault B must be owned by the configured Token Program"
|
"Vault B must be owned by the configured Token Program"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Swap direction is taken from the (signed) input holding's own token definition, then the
|
||||||
|
// role-based holdings are mapped back to the pool's stored A/B order so the rest of the
|
||||||
|
// routine — reserve bookkeeping and finalize — stays keyed to token A/B.
|
||||||
|
let token_in_id = token_core::TokenHolding::try_from(&user_input_holding.account.data)
|
||||||
|
.expect("Swap exact input: input holding must be a valid token holding")
|
||||||
|
.definition_id();
|
||||||
|
let (user_holding_a, user_holding_b) = if token_in_id == pool_def_data.definition_token_a_id {
|
||||||
|
(user_input_holding, user_output_holding)
|
||||||
|
} else if token_in_id == pool_def_data.definition_token_b_id {
|
||||||
|
(user_output_holding, user_input_holding)
|
||||||
|
} else {
|
||||||
|
panic!("Swap exact input: input holding token is not part of the pool");
|
||||||
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
user_holding_a.account.program_owner, token_program_id,
|
user_holding_a.account.program_owner, token_program_id,
|
||||||
"User Token A holding must be owned by the configured Token Program"
|
"User Token A holding must be owned by the configured Token Program"
|
||||||
@ -228,14 +243,23 @@ pub fn swap_exact_input(
|
|||||||
panic!("AccountId is not a token type for the pool");
|
panic!("AccountId is not a token type for the pool");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Echo the two user holdings in the guest's declared slot order (input, then output) so the
|
||||||
|
// framework matches each post-state to the right account. The a/b mapping above only drives the
|
||||||
|
// reserve/vault bookkeeping; post-states are matched to accounts positionally.
|
||||||
|
let (user_holding_input, user_holding_output) =
|
||||||
|
if token_in_id == pool_def_data.definition_token_a_id {
|
||||||
|
(user_holding_a, user_holding_b)
|
||||||
|
} else {
|
||||||
|
(user_holding_b, user_holding_a)
|
||||||
|
};
|
||||||
let (post_states, update_tick_call) = finalize_swap(
|
let (post_states, update_tick_call) = finalize_swap(
|
||||||
config,
|
config,
|
||||||
pool,
|
pool,
|
||||||
pool_def_data,
|
pool_def_data,
|
||||||
vault_a,
|
vault_a,
|
||||||
vault_b,
|
vault_b,
|
||||||
user_holding_a,
|
user_holding_input,
|
||||||
user_holding_b,
|
user_holding_output,
|
||||||
current_tick_account,
|
current_tick_account,
|
||||||
clock,
|
clock,
|
||||||
deposit_a,
|
deposit_a,
|
||||||
@ -343,13 +367,12 @@ pub fn swap_exact_output(
|
|||||||
pool: AccountWithMetadata,
|
pool: AccountWithMetadata,
|
||||||
vault_a: AccountWithMetadata,
|
vault_a: AccountWithMetadata,
|
||||||
vault_b: AccountWithMetadata,
|
vault_b: AccountWithMetadata,
|
||||||
user_holding_a: AccountWithMetadata,
|
user_input_holding: AccountWithMetadata,
|
||||||
user_holding_b: AccountWithMetadata,
|
user_output_holding: AccountWithMetadata,
|
||||||
current_tick_account: AccountWithMetadata,
|
current_tick_account: AccountWithMetadata,
|
||||||
clock: AccountWithMetadata,
|
clock: AccountWithMetadata,
|
||||||
exact_amount_out: u128,
|
exact_amount_out: u128,
|
||||||
max_amount_in: u128,
|
max_amount_in: u128,
|
||||||
token_in_id: AccountId,
|
|
||||||
amm_program_id: ProgramId,
|
amm_program_id: ProgramId,
|
||||||
) -> (Vec<AccountPostState>, Vec<ChainedCall>) {
|
) -> (Vec<AccountPostState>, Vec<ChainedCall>) {
|
||||||
let pool_def_data = validate_swap_setup(&pool, &vault_a, &vault_b);
|
let pool_def_data = validate_swap_setup(&pool, &vault_a, &vault_b);
|
||||||
@ -373,6 +396,20 @@ pub fn swap_exact_output(
|
|||||||
vault_b.account.program_owner, token_program_id,
|
vault_b.account.program_owner, token_program_id,
|
||||||
"Vault B must be owned by the configured Token Program"
|
"Vault B must be owned by the configured Token Program"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Swap direction is taken from the (signed) input holding's own token definition, then the
|
||||||
|
// role-based holdings are mapped back to the pool's stored A/B order so the rest of the
|
||||||
|
// routine — reserve bookkeeping and finalize — stays keyed to token A/B.
|
||||||
|
let token_in_id = token_core::TokenHolding::try_from(&user_input_holding.account.data)
|
||||||
|
.expect("Swap exact output: input holding must be a valid token holding")
|
||||||
|
.definition_id();
|
||||||
|
let (user_holding_a, user_holding_b) = if token_in_id == pool_def_data.definition_token_a_id {
|
||||||
|
(user_input_holding, user_output_holding)
|
||||||
|
} else if token_in_id == pool_def_data.definition_token_b_id {
|
||||||
|
(user_output_holding, user_input_holding)
|
||||||
|
} else {
|
||||||
|
panic!("Swap exact output: input holding token is not part of the pool");
|
||||||
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
user_holding_a.account.program_owner, token_program_id,
|
user_holding_a.account.program_owner, token_program_id,
|
||||||
"User Token A holding must be owned by the configured Token Program"
|
"User Token A holding must be owned by the configured Token Program"
|
||||||
@ -428,14 +465,23 @@ pub fn swap_exact_output(
|
|||||||
panic!("AccountId is not a token type for the pool");
|
panic!("AccountId is not a token type for the pool");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Echo the two user holdings in the guest's declared slot order (input, then output) so the
|
||||||
|
// framework matches each post-state to the right account. The a/b mapping above only drives the
|
||||||
|
// reserve/vault bookkeeping; post-states are matched to accounts positionally.
|
||||||
|
let (user_holding_input, user_holding_output) =
|
||||||
|
if token_in_id == pool_def_data.definition_token_a_id {
|
||||||
|
(user_holding_a, user_holding_b)
|
||||||
|
} else {
|
||||||
|
(user_holding_b, user_holding_a)
|
||||||
|
};
|
||||||
let (post_states, update_tick_call) = finalize_swap(
|
let (post_states, update_tick_call) = finalize_swap(
|
||||||
config,
|
config,
|
||||||
pool,
|
pool,
|
||||||
pool_def_data,
|
pool_def_data,
|
||||||
vault_a,
|
vault_a,
|
||||||
vault_b,
|
vault_b,
|
||||||
user_holding_a,
|
user_holding_input,
|
||||||
user_holding_b,
|
user_holding_output,
|
||||||
current_tick_account,
|
current_tick_account,
|
||||||
clock,
|
clock,
|
||||||
deposit_a,
|
deposit_a,
|
||||||
|
|||||||
@ -2424,7 +2424,7 @@ fn test_call_new_definition_chained_call_successful() {
|
|||||||
assert_eq!(post_states.len(), 11);
|
assert_eq!(post_states.len(), 11);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[should_panic(expected = "AccountId is not a token type for the pool")]
|
#[should_panic(expected = "Swap exact input: input holding token is not part of the pool")]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_call_swap_incorrect_token_type() {
|
fn test_call_swap_incorrect_token_type() {
|
||||||
let _post_states = swap_exact_input(
|
let _post_states = swap_exact_input(
|
||||||
@ -2432,13 +2432,12 @@ fn test_call_swap_incorrect_token_type() {
|
|||||||
AccountWithMetadataForTests::pool_definition_init(),
|
AccountWithMetadataForTests::pool_definition_init(),
|
||||||
AccountWithMetadataForTests::vault_a_init(),
|
AccountWithMetadataForTests::vault_a_init(),
|
||||||
AccountWithMetadataForTests::vault_b_init(),
|
AccountWithMetadataForTests::vault_b_init(),
|
||||||
AccountWithMetadataForTests::user_holding_a(),
|
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||||
AccountWithMetadataForTests::user_holding_b(),
|
AccountWithMetadataForTests::user_holding_b(),
|
||||||
AccountWithMetadataForTests::current_tick_account_uninit(),
|
AccountWithMetadataForTests::current_tick_account_uninit(),
|
||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
BalanceForTests::add_max_amount_a(),
|
BalanceForTests::add_max_amount_a(),
|
||||||
BalanceForTests::min_amount_out(),
|
BalanceForTests::min_amount_out(),
|
||||||
IdForTests::token_lp_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2457,7 +2456,6 @@ fn test_call_swap_vault_a_omitted() {
|
|||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
BalanceForTests::add_max_amount_a(),
|
BalanceForTests::add_max_amount_a(),
|
||||||
BalanceForTests::min_amount_out(),
|
BalanceForTests::min_amount_out(),
|
||||||
IdForTests::token_a_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2476,7 +2474,6 @@ fn test_call_swap_vault_b_omitted() {
|
|||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
BalanceForTests::add_max_amount_a(),
|
BalanceForTests::add_max_amount_a(),
|
||||||
BalanceForTests::min_amount_out(),
|
BalanceForTests::min_amount_out(),
|
||||||
IdForTests::token_a_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2495,7 +2492,6 @@ fn test_call_swap_reserves_vault_mismatch_1() {
|
|||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
BalanceForTests::add_max_amount_a(),
|
BalanceForTests::add_max_amount_a(),
|
||||||
BalanceForTests::min_amount_out(),
|
BalanceForTests::min_amount_out(),
|
||||||
IdForTests::token_a_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2514,7 +2510,6 @@ fn test_call_swap_reserves_vault_mismatch_2() {
|
|||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
BalanceForTests::add_max_amount_a(),
|
BalanceForTests::add_max_amount_a(),
|
||||||
BalanceForTests::min_amount_out(),
|
BalanceForTests::min_amount_out(),
|
||||||
IdForTests::token_a_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2533,7 +2528,6 @@ fn test_call_swap_below_minimum_liquidity() {
|
|||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
BalanceForTests::add_max_amount_a(),
|
BalanceForTests::add_max_amount_a(),
|
||||||
BalanceForTests::min_amount_out(),
|
BalanceForTests::min_amount_out(),
|
||||||
IdForTests::token_a_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2557,7 +2551,6 @@ fn test_call_swap_rejects_unsupported_fee_tier() {
|
|||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
BalanceForTests::add_max_amount_a(),
|
BalanceForTests::add_max_amount_a(),
|
||||||
BalanceForTests::add_max_amount_a_low(),
|
BalanceForTests::add_max_amount_a_low(),
|
||||||
IdForTests::token_a_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2576,7 +2569,6 @@ fn test_call_swap_below_min_out() {
|
|||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
BalanceForTests::add_max_amount_a(),
|
BalanceForTests::add_max_amount_a(),
|
||||||
BalanceForTests::min_amount_out_too_high(),
|
BalanceForTests::min_amount_out_too_high(),
|
||||||
IdForTests::token_a_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2595,7 +2587,6 @@ fn test_call_swap_effective_amount_zero() {
|
|||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
1,
|
1,
|
||||||
0,
|
0,
|
||||||
IdForTests::token_a_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2614,7 +2605,6 @@ fn test_call_swap_output_rounds_to_zero() {
|
|||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
2,
|
2,
|
||||||
0,
|
0,
|
||||||
IdForTests::token_a_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2633,7 +2623,6 @@ fn test_call_swap_exact_input_rejects_amount_that_rounds_down_below_target_outpu
|
|||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
2,
|
2,
|
||||||
1,
|
1,
|
||||||
IdForTests::token_a_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2651,7 +2640,6 @@ fn test_call_swap_exact_input_accepts_smallest_amount_for_rounded_boundary() {
|
|||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
3,
|
3,
|
||||||
1,
|
1,
|
||||||
IdForTests::token_a_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -2721,7 +2709,6 @@ fn test_call_swap_chained_call_successful_1() {
|
|||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
BalanceForTests::add_max_amount_a(),
|
BalanceForTests::add_max_amount_a(),
|
||||||
BalanceForTests::add_max_amount_a_low(),
|
BalanceForTests::add_max_amount_a_low(),
|
||||||
IdForTests::token_a_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -2754,13 +2741,12 @@ fn test_call_swap_chained_call_successful_2() {
|
|||||||
AccountWithMetadataForTests::pool_definition_init(),
|
AccountWithMetadataForTests::pool_definition_init(),
|
||||||
AccountWithMetadataForTests::vault_a_init(),
|
AccountWithMetadataForTests::vault_a_init(),
|
||||||
AccountWithMetadataForTests::vault_b_init(),
|
AccountWithMetadataForTests::vault_b_init(),
|
||||||
AccountWithMetadataForTests::user_holding_a(),
|
|
||||||
AccountWithMetadataForTests::user_holding_b(),
|
AccountWithMetadataForTests::user_holding_b(),
|
||||||
|
AccountWithMetadataForTests::user_holding_a(),
|
||||||
AccountWithMetadataForTests::current_tick_account_uninit(),
|
AccountWithMetadataForTests::current_tick_account_uninit(),
|
||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
BalanceForTests::add_max_amount_b(),
|
BalanceForTests::add_max_amount_b(),
|
||||||
BalanceForTests::min_amount_out(),
|
BalanceForTests::min_amount_out(),
|
||||||
IdForTests::token_b_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -2786,7 +2772,7 @@ fn test_call_swap_chained_call_successful_2() {
|
|||||||
assert_update_tick_call(&chained_calls, pool_post.account());
|
assert_update_tick_call(&chained_calls, pool_post.account());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[should_panic(expected = "AccountId is not a token type for the pool")]
|
#[should_panic(expected = "Swap exact output: input holding token is not part of the pool")]
|
||||||
#[test]
|
#[test]
|
||||||
fn call_swap_exact_output_incorrect_token_type() {
|
fn call_swap_exact_output_incorrect_token_type() {
|
||||||
let _post_states = swap_exact_output(
|
let _post_states = swap_exact_output(
|
||||||
@ -2794,13 +2780,12 @@ fn call_swap_exact_output_incorrect_token_type() {
|
|||||||
AccountWithMetadataForTests::pool_definition_init(),
|
AccountWithMetadataForTests::pool_definition_init(),
|
||||||
AccountWithMetadataForTests::vault_a_init(),
|
AccountWithMetadataForTests::vault_a_init(),
|
||||||
AccountWithMetadataForTests::vault_b_init(),
|
AccountWithMetadataForTests::vault_b_init(),
|
||||||
AccountWithMetadataForTests::user_holding_a(),
|
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||||
AccountWithMetadataForTests::user_holding_b(),
|
AccountWithMetadataForTests::user_holding_b(),
|
||||||
AccountWithMetadataForTests::current_tick_account_uninit(),
|
AccountWithMetadataForTests::current_tick_account_uninit(),
|
||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
BalanceForTests::add_max_amount_a(),
|
BalanceForTests::add_max_amount_a(),
|
||||||
BalanceForTests::max_amount_in(),
|
BalanceForTests::max_amount_in(),
|
||||||
IdForTests::token_lp_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2819,7 +2804,6 @@ fn call_swap_exact_output_vault_a_omitted() {
|
|||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
BalanceForTests::add_max_amount_a(),
|
BalanceForTests::add_max_amount_a(),
|
||||||
BalanceForTests::max_amount_in(),
|
BalanceForTests::max_amount_in(),
|
||||||
IdForTests::token_a_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2838,7 +2822,6 @@ fn call_swap_exact_output_vault_b_omitted() {
|
|||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
BalanceForTests::add_max_amount_a(),
|
BalanceForTests::add_max_amount_a(),
|
||||||
BalanceForTests::max_amount_in(),
|
BalanceForTests::max_amount_in(),
|
||||||
IdForTests::token_a_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2857,7 +2840,6 @@ fn call_swap_exact_output_reserves_vault_mismatch_1() {
|
|||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
BalanceForTests::add_max_amount_a(),
|
BalanceForTests::add_max_amount_a(),
|
||||||
BalanceForTests::max_amount_in(),
|
BalanceForTests::max_amount_in(),
|
||||||
IdForTests::token_a_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2876,7 +2858,6 @@ fn call_swap_exact_output_reserves_vault_mismatch_2() {
|
|||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
BalanceForTests::add_max_amount_a(),
|
BalanceForTests::add_max_amount_a(),
|
||||||
BalanceForTests::max_amount_in(),
|
BalanceForTests::max_amount_in(),
|
||||||
IdForTests::token_a_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2895,7 +2876,6 @@ fn call_swap_exact_output_below_minimum_liquidity() {
|
|||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
BalanceForTests::add_max_amount_a(),
|
BalanceForTests::add_max_amount_a(),
|
||||||
BalanceForTests::max_amount_in(),
|
BalanceForTests::max_amount_in(),
|
||||||
IdForTests::token_a_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2914,7 +2894,6 @@ fn call_swap_exact_output_exceeds_max_in() {
|
|||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
166_u128,
|
166_u128,
|
||||||
100_u128,
|
100_u128,
|
||||||
IdForTests::token_a_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2933,7 +2912,6 @@ fn call_swap_exact_output_zero() {
|
|||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
0_u128,
|
0_u128,
|
||||||
500_u128,
|
500_u128,
|
||||||
IdForTests::token_a_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2952,7 +2930,6 @@ fn call_swap_exact_output_exceeds_reserve() {
|
|||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
BalanceForTests::vault_b_reserve_init(),
|
BalanceForTests::vault_b_reserve_init(),
|
||||||
BalanceForTests::max_amount_in(),
|
BalanceForTests::max_amount_in(),
|
||||||
IdForTests::token_a_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2970,7 +2947,6 @@ fn call_swap_exact_output_chained_call_successful() {
|
|||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
BalanceForTests::max_amount_in(),
|
BalanceForTests::max_amount_in(),
|
||||||
BalanceForTests::vault_b_reserve_init(),
|
BalanceForTests::vault_b_reserve_init(),
|
||||||
IdForTests::token_a_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -3001,13 +2977,12 @@ fn call_swap_exact_output_chained_call_successful_2() {
|
|||||||
AccountWithMetadataForTests::pool_definition_swap_exact_output_init(),
|
AccountWithMetadataForTests::pool_definition_swap_exact_output_init(),
|
||||||
AccountWithMetadataForTests::vault_a_init(),
|
AccountWithMetadataForTests::vault_a_init(),
|
||||||
AccountWithMetadataForTests::vault_b_init(),
|
AccountWithMetadataForTests::vault_b_init(),
|
||||||
AccountWithMetadataForTests::user_holding_a(),
|
|
||||||
AccountWithMetadataForTests::user_holding_b(),
|
AccountWithMetadataForTests::user_holding_b(),
|
||||||
|
AccountWithMetadataForTests::user_holding_a(),
|
||||||
AccountWithMetadataForTests::current_tick_account_uninit(),
|
AccountWithMetadataForTests::current_tick_account_uninit(),
|
||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
285,
|
285,
|
||||||
300,
|
300,
|
||||||
IdForTests::token_b_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -3047,7 +3022,6 @@ fn call_swap_exact_output_fee_enforced() {
|
|||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
166_u128, // exact_amount_out: token_b
|
166_u128, // exact_amount_out: token_b
|
||||||
499_u128, // max_amount_in: still one short after fee rounding
|
499_u128, // max_amount_in: still one short after fee rounding
|
||||||
IdForTests::token_a_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -3069,7 +3043,6 @@ fn call_swap_exact_output_rejects_max_in_that_rounds_down_below_target_output()
|
|||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
1,
|
1,
|
||||||
2,
|
2,
|
||||||
IdForTests::token_a_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -3087,7 +3060,6 @@ fn call_swap_exact_output_accepts_smallest_max_in_for_rounded_boundary() {
|
|||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
1,
|
1,
|
||||||
3,
|
3,
|
||||||
IdForTests::token_a_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -3185,7 +3157,6 @@ fn swap_exact_output_overflow_protection() {
|
|||||||
2, // exact_amount_out: small, valid (< reserve_b)
|
2, // exact_amount_out: small, valid (< reserve_b)
|
||||||
1, // max_amount_in: tiny — real deposit would be enormous, but
|
1, // max_amount_in: tiny — real deposit would be enormous, but
|
||||||
// overflow wraps it to 0, making 0 <= 1 pass silently
|
// overflow wraps it to 0, making 0 <= 1 pass silently
|
||||||
IdForTests::token_a_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -3878,7 +3849,6 @@ fn swap_exact_input_overflow_protection() {
|
|||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
3,
|
3,
|
||||||
1,
|
1,
|
||||||
IdForTests::token_a_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -4076,7 +4046,6 @@ fn test_swap_exact_input_rejects_user_holding_a_wrong_program() {
|
|||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
BalanceForTests::add_max_amount_a(),
|
BalanceForTests::add_max_amount_a(),
|
||||||
BalanceForTests::min_amount_out(),
|
BalanceForTests::min_amount_out(),
|
||||||
IdForTests::token_a_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -4095,7 +4064,6 @@ fn test_swap_exact_input_rejects_user_holding_b_wrong_program() {
|
|||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
BalanceForTests::add_max_amount_a(),
|
BalanceForTests::add_max_amount_a(),
|
||||||
BalanceForTests::min_amount_out(),
|
BalanceForTests::min_amount_out(),
|
||||||
IdForTests::token_a_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -4114,7 +4082,6 @@ fn test_swap_exact_output_rejects_user_holding_a_wrong_program() {
|
|||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
166,
|
166,
|
||||||
BalanceForTests::max_amount_in(),
|
BalanceForTests::max_amount_in(),
|
||||||
IdForTests::token_a_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -4133,7 +4100,6 @@ fn test_swap_exact_output_rejects_user_holding_b_wrong_program() {
|
|||||||
AccountWithMetadataForTests::clock(),
|
AccountWithMetadataForTests::clock(),
|
||||||
166,
|
166,
|
||||||
BalanceForTests::max_amount_in(),
|
BalanceForTests::max_amount_in(),
|
||||||
IdForTests::token_a_definition_id(),
|
|
||||||
AMM_PROGRAM_ID,
|
AMM_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -257,6 +257,46 @@ impl Balances {
|
|||||||
10_415
|
10_415
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn exact_output_a_to_b_amount_in() -> u128 {
|
||||||
|
437
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reserve_a_swap_exact_output_a_to_b() -> u128 {
|
||||||
|
Self::vault_a_init() + Self::exact_output_a_to_b_amount_in()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reserve_b_swap_exact_output_a_to_b() -> u128 {
|
||||||
|
Self::vault_b_init() - Self::swap_min_out()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn user_a_swap_exact_output_a_to_b() -> u128 {
|
||||||
|
Self::user_a_init() - Self::exact_output_a_to_b_amount_in()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn user_b_swap_exact_output_a_to_b() -> u128 {
|
||||||
|
Self::user_b_init() + Self::swap_min_out()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exact_output_b_to_a_amount_in() -> u128 {
|
||||||
|
106
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reserve_a_swap_exact_output_b_to_a() -> u128 {
|
||||||
|
Self::vault_a_init() - Self::swap_min_out()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reserve_b_swap_exact_output_b_to_a() -> u128 {
|
||||||
|
Self::vault_b_init() + Self::exact_output_b_to_a_amount_in()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn user_a_swap_exact_output_b_to_a() -> u128 {
|
||||||
|
Self::user_a_init() + Self::swap_min_out()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn user_b_swap_exact_output_b_to_a() -> u128 {
|
||||||
|
Self::user_b_init() - Self::exact_output_b_to_a_amount_in()
|
||||||
|
}
|
||||||
|
|
||||||
fn vault_a_add() -> u128 {
|
fn vault_a_add() -> u128 {
|
||||||
7_000
|
7_000
|
||||||
}
|
}
|
||||||
@ -536,8 +576,7 @@ impl Accounts {
|
|||||||
definition_id: Ids::token_a_definition(),
|
definition_id: Ids::token_a_definition(),
|
||||||
balance: Balances::user_a_swap_1(),
|
balance: Balances::user_a_swap_1(),
|
||||||
}),
|
}),
|
||||||
// Both user holdings are now swap signers, so this holding's nonce increments too.
|
nonce: Nonce(0),
|
||||||
nonce: Nonce(1),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -616,7 +655,140 @@ impl Accounts {
|
|||||||
definition_id: Ids::token_b_definition(),
|
definition_id: Ids::token_b_definition(),
|
||||||
balance: Balances::user_b_swap_2(),
|
balance: Balances::user_b_swap_2(),
|
||||||
}),
|
}),
|
||||||
// Both user holdings are now swap signers, so this holding's nonce increments too.
|
nonce: Nonce(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pool_definition_swap_exact_output_a_to_b() -> Account {
|
||||||
|
Account {
|
||||||
|
program_owner: Ids::amm_program(),
|
||||||
|
balance: 0_u128,
|
||||||
|
data: Data::from(&PoolDefinition {
|
||||||
|
definition_token_a_id: Ids::token_a_definition(),
|
||||||
|
definition_token_b_id: Ids::token_b_definition(),
|
||||||
|
vault_a_id: Ids::vault_a(),
|
||||||
|
vault_b_id: Ids::vault_b(),
|
||||||
|
liquidity_pool_id: Ids::token_lp_definition(),
|
||||||
|
liquidity_pool_supply: Balances::pool_lp_supply_init(),
|
||||||
|
reserve_a: Balances::reserve_a_swap_exact_output_a_to_b(),
|
||||||
|
reserve_b: Balances::reserve_b_swap_exact_output_a_to_b(),
|
||||||
|
fees: Balances::fee_tier(),
|
||||||
|
}),
|
||||||
|
nonce: Nonce(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vault_a_swap_exact_output_a_to_b() -> Account {
|
||||||
|
Account {
|
||||||
|
program_owner: Ids::token_program(),
|
||||||
|
balance: 0_u128,
|
||||||
|
data: Data::from(&TokenHolding::Fungible {
|
||||||
|
definition_id: Ids::token_a_definition(),
|
||||||
|
balance: Balances::reserve_a_swap_exact_output_a_to_b(),
|
||||||
|
}),
|
||||||
|
nonce: Nonce(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vault_b_swap_exact_output_a_to_b() -> Account {
|
||||||
|
Account {
|
||||||
|
program_owner: Ids::token_program(),
|
||||||
|
balance: 0_u128,
|
||||||
|
data: Data::from(&TokenHolding::Fungible {
|
||||||
|
definition_id: Ids::token_b_definition(),
|
||||||
|
balance: Balances::reserve_b_swap_exact_output_a_to_b(),
|
||||||
|
}),
|
||||||
|
nonce: Nonce(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn user_a_holding_swap_exact_output_a_to_b() -> Account {
|
||||||
|
Account {
|
||||||
|
program_owner: Ids::token_program(),
|
||||||
|
balance: 0_u128,
|
||||||
|
data: Data::from(&TokenHolding::Fungible {
|
||||||
|
definition_id: Ids::token_a_definition(),
|
||||||
|
balance: Balances::user_a_swap_exact_output_a_to_b(),
|
||||||
|
}),
|
||||||
|
nonce: Nonce(1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn user_b_holding_swap_exact_output_a_to_b() -> Account {
|
||||||
|
Account {
|
||||||
|
program_owner: Ids::token_program(),
|
||||||
|
balance: 0_u128,
|
||||||
|
data: Data::from(&TokenHolding::Fungible {
|
||||||
|
definition_id: Ids::token_b_definition(),
|
||||||
|
balance: Balances::user_b_swap_exact_output_a_to_b(),
|
||||||
|
}),
|
||||||
|
nonce: Nonce(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pool_definition_swap_exact_output_b_to_a() -> Account {
|
||||||
|
Account {
|
||||||
|
program_owner: Ids::amm_program(),
|
||||||
|
balance: 0_u128,
|
||||||
|
data: Data::from(&PoolDefinition {
|
||||||
|
definition_token_a_id: Ids::token_a_definition(),
|
||||||
|
definition_token_b_id: Ids::token_b_definition(),
|
||||||
|
vault_a_id: Ids::vault_a(),
|
||||||
|
vault_b_id: Ids::vault_b(),
|
||||||
|
liquidity_pool_id: Ids::token_lp_definition(),
|
||||||
|
liquidity_pool_supply: Balances::pool_lp_supply_init(),
|
||||||
|
reserve_a: Balances::reserve_a_swap_exact_output_b_to_a(),
|
||||||
|
reserve_b: Balances::reserve_b_swap_exact_output_b_to_a(),
|
||||||
|
fees: Balances::fee_tier(),
|
||||||
|
}),
|
||||||
|
nonce: Nonce(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vault_a_swap_exact_output_b_to_a() -> Account {
|
||||||
|
Account {
|
||||||
|
program_owner: Ids::token_program(),
|
||||||
|
balance: 0_u128,
|
||||||
|
data: Data::from(&TokenHolding::Fungible {
|
||||||
|
definition_id: Ids::token_a_definition(),
|
||||||
|
balance: Balances::reserve_a_swap_exact_output_b_to_a(),
|
||||||
|
}),
|
||||||
|
nonce: Nonce(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vault_b_swap_exact_output_b_to_a() -> Account {
|
||||||
|
Account {
|
||||||
|
program_owner: Ids::token_program(),
|
||||||
|
balance: 0_u128,
|
||||||
|
data: Data::from(&TokenHolding::Fungible {
|
||||||
|
definition_id: Ids::token_b_definition(),
|
||||||
|
balance: Balances::reserve_b_swap_exact_output_b_to_a(),
|
||||||
|
}),
|
||||||
|
nonce: Nonce(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn user_a_holding_swap_exact_output_b_to_a() -> Account {
|
||||||
|
Account {
|
||||||
|
program_owner: Ids::token_program(),
|
||||||
|
balance: 0_u128,
|
||||||
|
data: Data::from(&TokenHolding::Fungible {
|
||||||
|
definition_id: Ids::token_a_definition(),
|
||||||
|
balance: Balances::user_a_swap_exact_output_b_to_a(),
|
||||||
|
}),
|
||||||
|
nonce: Nonce(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn user_b_holding_swap_exact_output_b_to_a() -> Account {
|
||||||
|
Account {
|
||||||
|
program_owner: Ids::token_program(),
|
||||||
|
balance: 0_u128,
|
||||||
|
data: Data::from(&TokenHolding::Fungible {
|
||||||
|
definition_id: Ids::token_b_definition(),
|
||||||
|
balance: Balances::user_b_swap_exact_output_b_to_a(),
|
||||||
|
}),
|
||||||
nonce: Nonce(1),
|
nonce: Nonce(1),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -990,13 +1162,9 @@ fn state_for_amm_tests() -> V03State {
|
|||||||
state.force_insert_account(Ids::pool_definition(), Accounts::pool_definition_init());
|
state.force_insert_account(Ids::pool_definition(), Accounts::pool_definition_init());
|
||||||
// Seed the pool's current-tick account so swaps and syncs can refresh it. Its initial value is
|
// Seed the pool's current-tick account so swaps and syncs can refresh it. Its initial value is
|
||||||
// the tick of the opening reserves; swap/sync tests assert it is updated to the new price.
|
// the tick of the opening reserves; swap/sync tests assert it is updated to the new price.
|
||||||
let initial_tick = twap_oracle_core::price_to_tick(amm_core::spot_price_q64_64(
|
|
||||||
Balances::vault_a_init(),
|
|
||||||
Balances::vault_b_init(),
|
|
||||||
));
|
|
||||||
state.force_insert_account(
|
state.force_insert_account(
|
||||||
Ids::current_tick_account(),
|
Ids::current_tick_account(),
|
||||||
Accounts::current_tick_account(initial_tick),
|
Accounts::current_tick_account(initial_pool_tick()),
|
||||||
);
|
);
|
||||||
state.force_insert_account(
|
state.force_insert_account(
|
||||||
Ids::token_a_definition(),
|
Ids::token_a_definition(),
|
||||||
@ -1118,7 +1286,6 @@ fn execute_swap_a_to_b(state: &mut V03State, swap_amount_in: u128, min_amount_ou
|
|||||||
let instruction = amm_core::Instruction::SwapExactInput {
|
let instruction = amm_core::Instruction::SwapExactInput {
|
||||||
swap_amount_in,
|
swap_amount_in,
|
||||||
min_amount_out,
|
min_amount_out,
|
||||||
token_definition_id_in: Ids::token_a_definition(),
|
|
||||||
deadline: u64::MAX,
|
deadline: u64::MAX,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1134,16 +1301,12 @@ fn execute_swap_a_to_b(state: &mut V03State, swap_amount_in: u128, min_amount_ou
|
|||||||
Ids::current_tick_account(),
|
Ids::current_tick_account(),
|
||||||
CLOCK_01_PROGRAM_ACCOUNT_ID,
|
CLOCK_01_PROGRAM_ACCOUNT_ID,
|
||||||
],
|
],
|
||||||
vec![
|
vec![current_nonce(state, Ids::user_a())],
|
||||||
current_nonce(state, Ids::user_a()),
|
|
||||||
current_nonce(state, Ids::user_b()),
|
|
||||||
],
|
|
||||||
instruction,
|
instruction,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let witness_set =
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a()]);
|
||||||
public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a(), &Keys::user_b()]);
|
|
||||||
|
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
|
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
|
||||||
@ -1154,10 +1317,10 @@ fn execute_swap_b_to_a(state: &mut V03State, swap_amount_in: u128, min_amount_ou
|
|||||||
let instruction = amm_core::Instruction::SwapExactInput {
|
let instruction = amm_core::Instruction::SwapExactInput {
|
||||||
swap_amount_in,
|
swap_amount_in,
|
||||||
min_amount_out,
|
min_amount_out,
|
||||||
token_definition_id_in: Ids::token_b_definition(),
|
|
||||||
deadline: u64::MAX,
|
deadline: u64::MAX,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Token B is the input, so the B holding occupies the signed input slot.
|
||||||
let message = public_transaction::Message::try_new(
|
let message = public_transaction::Message::try_new(
|
||||||
Ids::amm_program(),
|
Ids::amm_program(),
|
||||||
vec![
|
vec![
|
||||||
@ -1165,21 +1328,17 @@ fn execute_swap_b_to_a(state: &mut V03State, swap_amount_in: u128, min_amount_ou
|
|||||||
Ids::pool_definition(),
|
Ids::pool_definition(),
|
||||||
Ids::vault_a(),
|
Ids::vault_a(),
|
||||||
Ids::vault_b(),
|
Ids::vault_b(),
|
||||||
Ids::user_a(),
|
|
||||||
Ids::user_b(),
|
Ids::user_b(),
|
||||||
|
Ids::user_a(),
|
||||||
Ids::current_tick_account(),
|
Ids::current_tick_account(),
|
||||||
CLOCK_01_PROGRAM_ACCOUNT_ID,
|
CLOCK_01_PROGRAM_ACCOUNT_ID,
|
||||||
],
|
],
|
||||||
vec![
|
vec![current_nonce(state, Ids::user_b())],
|
||||||
current_nonce(state, Ids::user_a()),
|
|
||||||
current_nonce(state, Ids::user_b()),
|
|
||||||
],
|
|
||||||
instruction,
|
instruction,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let witness_set =
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_b()]);
|
||||||
public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a(), &Keys::user_b()]);
|
|
||||||
|
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
|
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
|
||||||
@ -1391,6 +1550,54 @@ fn pool_definition(account: &Account) -> PoolDefinition {
|
|||||||
PoolDefinition::try_from(&account.data).expect("expected pool definition")
|
PoolDefinition::try_from(&account.data).expect("expected pool definition")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn initial_pool_tick() -> i32 {
|
||||||
|
twap_oracle_core::price_to_tick(amm_core::spot_price_q64_64(
|
||||||
|
Balances::vault_a_init(),
|
||||||
|
Balances::vault_b_init(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_initial_swap_state(state: &V03State) {
|
||||||
|
assert_eq!(state.get_account_by_id(Ids::config()), Accounts::config());
|
||||||
|
assert_eq!(
|
||||||
|
state.get_account_by_id(Ids::pool_definition()),
|
||||||
|
Accounts::pool_definition_init()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
state.get_account_by_id(Ids::vault_a()),
|
||||||
|
Accounts::vault_a_init()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
state.get_account_by_id(Ids::vault_b()),
|
||||||
|
Accounts::vault_b_init()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
state.get_account_by_id(Ids::user_a()),
|
||||||
|
Accounts::user_a_holding()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
state.get_account_by_id(Ids::user_b()),
|
||||||
|
Accounts::user_b_holding()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
state.get_account_by_id(Ids::current_tick_account()),
|
||||||
|
Accounts::current_tick_account(initial_pool_tick())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_current_tick_matches_pool(state: &V03State) {
|
||||||
|
let pool = pool_definition(&state.get_account_by_id(Ids::pool_definition()));
|
||||||
|
let tick_account = twap_oracle_core::CurrentTickAccount::try_from(
|
||||||
|
&state.get_account_by_id(Ids::current_tick_account()).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(
|
||||||
|
pool.reserve_a,
|
||||||
|
pool.reserve_b,
|
||||||
|
));
|
||||||
|
assert_eq!(tick_account.tick, expected_tick);
|
||||||
|
}
|
||||||
|
|
||||||
fn fungible_total_supply(account: &Account) -> u128 {
|
fn fungible_total_supply(account: &Account) -> u128 {
|
||||||
let definition = TokenDefinition::try_from(&account.data).expect("expected token definition");
|
let definition = TokenDefinition::try_from(&account.data).expect("expected token definition");
|
||||||
let TokenDefinition::Fungible {
|
let TokenDefinition::Fungible {
|
||||||
@ -2517,10 +2724,10 @@ fn amm_swap_b_to_a() {
|
|||||||
let instruction = amm_core::Instruction::SwapExactInput {
|
let instruction = amm_core::Instruction::SwapExactInput {
|
||||||
swap_amount_in: Balances::swap_amount_in(),
|
swap_amount_in: Balances::swap_amount_in(),
|
||||||
min_amount_out: Balances::swap_min_out(),
|
min_amount_out: Balances::swap_min_out(),
|
||||||
token_definition_id_in: Ids::token_b_definition(),
|
|
||||||
deadline: u64::MAX,
|
deadline: u64::MAX,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Token B is the input, so the B holding occupies the signed input slot.
|
||||||
let message = public_transaction::Message::try_new(
|
let message = public_transaction::Message::try_new(
|
||||||
Ids::amm_program(),
|
Ids::amm_program(),
|
||||||
vec![
|
vec![
|
||||||
@ -2528,18 +2735,17 @@ fn amm_swap_b_to_a() {
|
|||||||
Ids::pool_definition(),
|
Ids::pool_definition(),
|
||||||
Ids::vault_a(),
|
Ids::vault_a(),
|
||||||
Ids::vault_b(),
|
Ids::vault_b(),
|
||||||
Ids::user_a(),
|
|
||||||
Ids::user_b(),
|
Ids::user_b(),
|
||||||
|
Ids::user_a(),
|
||||||
Ids::current_tick_account(),
|
Ids::current_tick_account(),
|
||||||
CLOCK_01_PROGRAM_ACCOUNT_ID,
|
CLOCK_01_PROGRAM_ACCOUNT_ID,
|
||||||
],
|
],
|
||||||
vec![Nonce(0), Nonce(0)],
|
vec![Nonce(0)],
|
||||||
instruction,
|
instruction,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let witness_set =
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_b()]);
|
||||||
public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a(), &Keys::user_b()]);
|
|
||||||
|
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
|
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
|
||||||
@ -2573,7 +2779,6 @@ fn amm_swap_a_to_b() {
|
|||||||
let instruction = amm_core::Instruction::SwapExactInput {
|
let instruction = amm_core::Instruction::SwapExactInput {
|
||||||
swap_amount_in: Balances::swap_amount_in(),
|
swap_amount_in: Balances::swap_amount_in(),
|
||||||
min_amount_out: Balances::swap_min_out(),
|
min_amount_out: Balances::swap_min_out(),
|
||||||
token_definition_id_in: Ids::token_a_definition(),
|
|
||||||
deadline: u64::MAX,
|
deadline: u64::MAX,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2589,13 +2794,12 @@ fn amm_swap_a_to_b() {
|
|||||||
Ids::current_tick_account(),
|
Ids::current_tick_account(),
|
||||||
CLOCK_01_PROGRAM_ACCOUNT_ID,
|
CLOCK_01_PROGRAM_ACCOUNT_ID,
|
||||||
],
|
],
|
||||||
vec![Nonce(0), Nonce(0)],
|
vec![Nonce(0)],
|
||||||
instruction,
|
instruction,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let witness_set =
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a()]);
|
||||||
public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a(), &Keys::user_b()]);
|
|
||||||
|
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
|
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
|
||||||
@ -2646,7 +2850,6 @@ fn amm_swap_exact_output_refreshes_current_tick() {
|
|||||||
let instruction = amm_core::Instruction::SwapExactOutput {
|
let instruction = amm_core::Instruction::SwapExactOutput {
|
||||||
exact_amount_out: Balances::swap_min_out(),
|
exact_amount_out: Balances::swap_min_out(),
|
||||||
max_amount_in: Balances::swap_amount_in(),
|
max_amount_in: Balances::swap_amount_in(),
|
||||||
token_definition_id_in: Ids::token_a_definition(),
|
|
||||||
deadline: u64::MAX,
|
deadline: u64::MAX,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2662,16 +2865,36 @@ fn amm_swap_exact_output_refreshes_current_tick() {
|
|||||||
Ids::current_tick_account(),
|
Ids::current_tick_account(),
|
||||||
CLOCK_01_PROGRAM_ACCOUNT_ID,
|
CLOCK_01_PROGRAM_ACCOUNT_ID,
|
||||||
],
|
],
|
||||||
vec![Nonce(0), Nonce(0)],
|
vec![Nonce(0)],
|
||||||
instruction,
|
instruction,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let witness_set =
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a()]);
|
||||||
public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a(), &Keys::user_b()]);
|
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
|
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
state.get_account_by_id(Ids::pool_definition()),
|
||||||
|
Accounts::pool_definition_swap_exact_output_a_to_b()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
state.get_account_by_id(Ids::vault_a()),
|
||||||
|
Accounts::vault_a_swap_exact_output_a_to_b()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
state.get_account_by_id(Ids::vault_b()),
|
||||||
|
Accounts::vault_b_swap_exact_output_a_to_b()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
state.get_account_by_id(Ids::user_a()),
|
||||||
|
Accounts::user_a_holding_swap_exact_output_a_to_b()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
state.get_account_by_id(Ids::user_b()),
|
||||||
|
Accounts::user_b_holding_swap_exact_output_a_to_b()
|
||||||
|
);
|
||||||
|
|
||||||
// The swap refreshed the pool's TWAP current tick to the post-swap spot price, computed from
|
// The swap refreshed the pool's TWAP current tick to the post-swap spot price, computed from
|
||||||
// the reserves the swap actually settled on.
|
// the reserves the swap actually settled on.
|
||||||
let pool = pool_definition(&state.get_account_by_id(Ids::pool_definition()));
|
let pool = pool_definition(&state.get_account_by_id(Ids::pool_definition()));
|
||||||
@ -2690,6 +2913,237 @@ fn amm_swap_exact_output_refreshes_current_tick() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn amm_swap_exact_output_b_to_a_signs_only_input() {
|
||||||
|
let mut state = state_for_amm_tests();
|
||||||
|
|
||||||
|
let instruction = amm_core::Instruction::SwapExactOutput {
|
||||||
|
exact_amount_out: Balances::swap_min_out(),
|
||||||
|
max_amount_in: Balances::swap_amount_in(),
|
||||||
|
deadline: u64::MAX,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Token B is the input, so the B holding occupies the signed input slot; only it is signed.
|
||||||
|
let message = public_transaction::Message::try_new(
|
||||||
|
Ids::amm_program(),
|
||||||
|
vec![
|
||||||
|
Ids::config(),
|
||||||
|
Ids::pool_definition(),
|
||||||
|
Ids::vault_a(),
|
||||||
|
Ids::vault_b(),
|
||||||
|
Ids::user_b(),
|
||||||
|
Ids::user_a(),
|
||||||
|
Ids::current_tick_account(),
|
||||||
|
CLOCK_01_PROGRAM_ACCOUNT_ID,
|
||||||
|
],
|
||||||
|
vec![Nonce(0)],
|
||||||
|
instruction,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_b()]);
|
||||||
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
state.get_account_by_id(Ids::pool_definition()),
|
||||||
|
Accounts::pool_definition_swap_exact_output_b_to_a()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
state.get_account_by_id(Ids::vault_a()),
|
||||||
|
Accounts::vault_a_swap_exact_output_b_to_a()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
state.get_account_by_id(Ids::vault_b()),
|
||||||
|
Accounts::vault_b_swap_exact_output_b_to_a()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
state.get_account_by_id(Ids::user_a()),
|
||||||
|
Accounts::user_a_holding_swap_exact_output_b_to_a()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
state.get_account_by_id(Ids::user_b()),
|
||||||
|
Accounts::user_b_holding_swap_exact_output_b_to_a()
|
||||||
|
);
|
||||||
|
|
||||||
|
let pool = pool_definition(&state.get_account_by_id(Ids::pool_definition()));
|
||||||
|
let required_input = pool
|
||||||
|
.reserve_b
|
||||||
|
.checked_sub(Balances::vault_b_init())
|
||||||
|
.expect("swap should increase input-token reserve");
|
||||||
|
let user_a_balance = fungible_balance(&state.get_account_by_id(Ids::user_a()));
|
||||||
|
let user_b_balance = fungible_balance(&state.get_account_by_id(Ids::user_b()));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
pool.reserve_a,
|
||||||
|
Balances::vault_a_init() - Balances::swap_min_out()
|
||||||
|
);
|
||||||
|
assert_ne!(required_input, 0);
|
||||||
|
assert!(required_input <= Balances::swap_amount_in());
|
||||||
|
assert_eq!(
|
||||||
|
Balances::user_b_init() - user_b_balance,
|
||||||
|
required_input,
|
||||||
|
"user debit must equal pool input reserve increase"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
user_a_balance,
|
||||||
|
Balances::user_a_init() + Balances::swap_min_out()
|
||||||
|
);
|
||||||
|
assert_current_tick_matches_pool(&state);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn amm_swap_exact_input_requires_input_signature() {
|
||||||
|
let mut state = state_for_amm_tests();
|
||||||
|
|
||||||
|
let instruction = amm_core::Instruction::SwapExactInput {
|
||||||
|
swap_amount_in: Balances::swap_amount_in(),
|
||||||
|
min_amount_out: Balances::swap_min_out(),
|
||||||
|
deadline: u64::MAX,
|
||||||
|
};
|
||||||
|
|
||||||
|
let message = public_transaction::Message::try_new(
|
||||||
|
Ids::amm_program(),
|
||||||
|
vec![
|
||||||
|
Ids::config(),
|
||||||
|
Ids::pool_definition(),
|
||||||
|
Ids::vault_a(),
|
||||||
|
Ids::vault_b(),
|
||||||
|
Ids::user_a(),
|
||||||
|
Ids::user_b(),
|
||||||
|
Ids::current_tick_account(),
|
||||||
|
CLOCK_01_PROGRAM_ACCOUNT_ID,
|
||||||
|
],
|
||||||
|
vec![],
|
||||||
|
instruction,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
assert!(matches!(
|
||||||
|
state.transition_from_public_transaction(&tx, 0, 0),
|
||||||
|
Err(LeeError::ProgramExecutionFailed(_))
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_initial_swap_state(&state);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn amm_swap_exact_input_rejects_when_only_output_holding_signed() {
|
||||||
|
let mut state = state_for_amm_tests();
|
||||||
|
|
||||||
|
let instruction = amm_core::Instruction::SwapExactInput {
|
||||||
|
swap_amount_in: Balances::swap_amount_in(),
|
||||||
|
min_amount_out: Balances::swap_min_out(),
|
||||||
|
deadline: u64::MAX,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Input slot holds token A but only the output (B) holding is signed: the input is unsigned, so
|
||||||
|
// the swap must be rejected.
|
||||||
|
let message = public_transaction::Message::try_new(
|
||||||
|
Ids::amm_program(),
|
||||||
|
vec![
|
||||||
|
Ids::config(),
|
||||||
|
Ids::pool_definition(),
|
||||||
|
Ids::vault_a(),
|
||||||
|
Ids::vault_b(),
|
||||||
|
Ids::user_a(),
|
||||||
|
Ids::user_b(),
|
||||||
|
Ids::current_tick_account(),
|
||||||
|
CLOCK_01_PROGRAM_ACCOUNT_ID,
|
||||||
|
],
|
||||||
|
vec![Nonce(0)],
|
||||||
|
instruction,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_b()]);
|
||||||
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
assert!(matches!(
|
||||||
|
state.transition_from_public_transaction(&tx, 0, 0),
|
||||||
|
Err(LeeError::ProgramExecutionFailed(_))
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_initial_swap_state(&state);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn amm_swap_exact_output_requires_input_signature() {
|
||||||
|
let mut state = state_for_amm_tests();
|
||||||
|
|
||||||
|
let instruction = amm_core::Instruction::SwapExactOutput {
|
||||||
|
exact_amount_out: Balances::swap_min_out(),
|
||||||
|
max_amount_in: Balances::swap_amount_in(),
|
||||||
|
deadline: u64::MAX,
|
||||||
|
};
|
||||||
|
|
||||||
|
let message = public_transaction::Message::try_new(
|
||||||
|
Ids::amm_program(),
|
||||||
|
vec![
|
||||||
|
Ids::config(),
|
||||||
|
Ids::pool_definition(),
|
||||||
|
Ids::vault_a(),
|
||||||
|
Ids::vault_b(),
|
||||||
|
Ids::user_a(),
|
||||||
|
Ids::user_b(),
|
||||||
|
Ids::current_tick_account(),
|
||||||
|
CLOCK_01_PROGRAM_ACCOUNT_ID,
|
||||||
|
],
|
||||||
|
vec![],
|
||||||
|
instruction,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
assert!(matches!(
|
||||||
|
state.transition_from_public_transaction(&tx, 0, 0),
|
||||||
|
Err(LeeError::ProgramExecutionFailed(_))
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_initial_swap_state(&state);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn amm_swap_exact_output_rejects_when_only_output_holding_signed() {
|
||||||
|
let mut state = state_for_amm_tests();
|
||||||
|
|
||||||
|
let instruction = amm_core::Instruction::SwapExactOutput {
|
||||||
|
exact_amount_out: Balances::swap_min_out(),
|
||||||
|
max_amount_in: Balances::swap_amount_in(),
|
||||||
|
deadline: u64::MAX,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Input slot holds token A but only the output (B) holding is signed: the input is unsigned, so
|
||||||
|
// the swap must be rejected.
|
||||||
|
let message = public_transaction::Message::try_new(
|
||||||
|
Ids::amm_program(),
|
||||||
|
vec![
|
||||||
|
Ids::config(),
|
||||||
|
Ids::pool_definition(),
|
||||||
|
Ids::vault_a(),
|
||||||
|
Ids::vault_b(),
|
||||||
|
Ids::user_a(),
|
||||||
|
Ids::user_b(),
|
||||||
|
Ids::current_tick_account(),
|
||||||
|
CLOCK_01_PROGRAM_ACCOUNT_ID,
|
||||||
|
],
|
||||||
|
vec![Nonce(0)],
|
||||||
|
instruction,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_b()]);
|
||||||
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
assert!(matches!(
|
||||||
|
state.transition_from_public_transaction(&tx, 0, 0),
|
||||||
|
Err(LeeError::ProgramExecutionFailed(_))
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_initial_swap_state(&state);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn amm_sync_reserves_updates_pool_and_current_tick() {
|
fn amm_sync_reserves_updates_pool_and_current_tick() {
|
||||||
let mut state = state_for_amm_tests();
|
let mut state = state_for_amm_tests();
|
||||||
@ -2783,7 +3237,6 @@ fn amm_swap_rejects_expired_deadline() {
|
|||||||
let instruction = amm_core::Instruction::SwapExactInput {
|
let instruction = amm_core::Instruction::SwapExactInput {
|
||||||
swap_amount_in: Balances::swap_amount_in(),
|
swap_amount_in: Balances::swap_amount_in(),
|
||||||
min_amount_out: Balances::swap_min_out(),
|
min_amount_out: Balances::swap_min_out(),
|
||||||
token_definition_id_in: Ids::token_a_definition(),
|
|
||||||
deadline: deadline_ms,
|
deadline: deadline_ms,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2799,13 +3252,12 @@ fn amm_swap_rejects_expired_deadline() {
|
|||||||
Ids::current_tick_account(),
|
Ids::current_tick_account(),
|
||||||
CLOCK_01_PROGRAM_ACCOUNT_ID,
|
CLOCK_01_PROGRAM_ACCOUNT_ID,
|
||||||
],
|
],
|
||||||
vec![Nonce(0), Nonce(0)],
|
vec![Nonce(0)],
|
||||||
instruction,
|
instruction,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let witness_set =
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a()]);
|
||||||
public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a(), &Keys::user_b()]);
|
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
state.transition_from_public_transaction(&tx, 0, block_timestamp_ms),
|
state.transition_from_public_transaction(&tx, 0, block_timestamp_ms),
|
||||||
@ -2823,7 +3275,6 @@ fn amm_swap_exact_output_rejects_expired_deadline() {
|
|||||||
let instruction = amm_core::Instruction::SwapExactOutput {
|
let instruction = amm_core::Instruction::SwapExactOutput {
|
||||||
exact_amount_out: Balances::swap_min_out(),
|
exact_amount_out: Balances::swap_min_out(),
|
||||||
max_amount_in: Balances::swap_amount_in(),
|
max_amount_in: Balances::swap_amount_in(),
|
||||||
token_definition_id_in: Ids::token_a_definition(),
|
|
||||||
deadline: deadline_ms,
|
deadline: deadline_ms,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2839,16 +3290,12 @@ fn amm_swap_exact_output_rejects_expired_deadline() {
|
|||||||
Ids::current_tick_account(),
|
Ids::current_tick_account(),
|
||||||
CLOCK_01_PROGRAM_ACCOUNT_ID,
|
CLOCK_01_PROGRAM_ACCOUNT_ID,
|
||||||
],
|
],
|
||||||
vec![
|
vec![current_nonce(&state, Ids::user_a())],
|
||||||
current_nonce(&state, Ids::user_a()),
|
|
||||||
current_nonce(&state, Ids::user_b()),
|
|
||||||
],
|
|
||||||
instruction,
|
instruction,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let witness_set =
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a()]);
|
||||||
public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a(), &Keys::user_b()]);
|
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
state.transition_from_public_transaction(&tx, 0, block_timestamp_ms),
|
state.transition_from_public_transaction(&tx, 0, block_timestamp_ms),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user