The atomic sandwich: a locked pool is no boundary against the authority
Source: crates/amm-frood-test/tests/sandwich.rs L170
- Given: Mallory holds the pool authority; Alice is an honest LP
- When: the pool is initialized with Mallory as authority
- When: Alice deposits liquidity
- When: Mallory freezes the pool: trading is publicly disabled
- When: anyone tries to swap the frozen pool
- Then: a swap against the locked pool is refused ✓
- When: Mallory unlocks, swaps, and re-locks in one transaction
- Then: Mallory traded despite the lock (X was spent) ✓
- Then: config.locked = true ✓
Result: ✓ success — 31775 CU · claims 3/3 ✓
sequenceDiagram
participant p0 as Mallory
participant p1 as Amm
participant p2 as token
p0->>p1: SetLocked
activate p1
p1-->>p0: ✓ 4081cu
deactivate p1
p0->>p1: Swap
activate p1
p1->>p2: transferChecked
activate p2
p2-->>p1: ✓ 105cu
deactivate p2
p1->>p2: transferChecked
activate p2
p2-->>p1: ✓ 105cu
deactivate p2
p1-->>p0: ✓ 23615cu
deactivate p1
p0->>p1: SetLocked
activate p1
p1-->>p0: ✓ 4079cu
deactivate p1
Authority
flowchart LR
Amm["Amm"]:::program
Mallory(["Mallory"]):::signer
Pool(["Pool"]):::signer
vaultXPoolMint[("vaultX(Pool, Mint)")]:::state
vaultYPoolMint2[("vaultY(Pool, Mint 2)")]:::state
userXMalloryMint[("userX(Mallory, Mint)")]:::state
userYMalloryMint2[("userY(Mallory, Mint 2)")]:::state
token["token"]:::program
Mallory -->|signs| Amm
Amm -->|writes| Pool
Amm -->|writes| vaultXPoolMint
Amm -->|writes| vaultYPoolMint2
Amm -->|writes| userXMalloryMint
Amm -->|writes| userYMalloryMint2
token -->|writes| userXMalloryMint
token -->|writes| vaultXPoolMint
Mallory -->|signs| token
token -->|writes| vaultYPoolMint2
token -->|writes| userYMalloryMint2
Pool -->|signs| token
classDef program fill:#dae8fc,stroke:#6c8ebf;
classDef signer fill:#d5e8d4,stroke:#82b366;
classDef state fill:#ffe6cc,stroke:#d79b00;
Ownership
flowchart LR
system["system"]:::program
Mallory[("Mallory")]:::state
Amm["Amm"]:::program
Pool[("Pool")]:::state
token["token"]:::program
vaultXPoolMint[("vaultX(Pool, Mint)")]:::state
vaultYPoolMint2[("vaultY(Pool, Mint 2)")]:::state
userXMalloryMint[("userX(Mallory, Mint)")]:::state
userYMalloryMint2[("userY(Mallory, Mint 2)")]:::state
system -->|owns| Mallory
Amm -->|owns| Pool
token -->|owns| vaultXPoolMint
token -->|owns| vaultYPoolMint2
token -->|owns| userXMalloryMint
token -->|owns| userYMalloryMint2
classDef program fill:#dae8fc,stroke:#6c8ebf;
classDef signer fill:#d5e8d4,stroke:#82b366;
classDef state fill:#ffe6cc,stroke:#d79b00;
Accounts
| Account | Signer | Writable | Owner |
|---|---|---|---|
| Mallory | ✓ | ✓ | system |
| Pool | · | ✓ | Amm |
| Mint | · | · | token |
| Mint 2 | · | · | token |
| vaultX(Pool, Mint) | · | ✓ | token |
| vaultY(Pool, Mint 2) | · | ✓ | token |
| userX(Mallory, Mint) | · | ✓ | token |
| userY(Mallory, Mint 2) | · | ✓ | token |
| token | · | · | BPFL… |
Call tree and logs
The atomic sandwich: a locked pool is no boundary against the authority
Mallory (31775cu)
├─ Amm::SetLocked ✓ 4081cu
├─ Amm::Swap ✓ 23615cu
│ ├─ token::transferChecked ✓ 105cu
│ └─ token::transferChecked ✓ 105cu
└─ Amm::SetLocked ✓ 4079cu
Program 5aDxxnPDGeVEuLXerisV4GF5f8tcwTjbMK7Bn1dYUXSi invoke [1]
Program log: Instruction: SetLocked
Program 5aDxxnPDGeVEuLXerisV4GF5f8tcwTjbMK7Bn1dYUXSi consumed 4081 of 600000 compute units
Program 5aDxxnPDGeVEuLXerisV4GF5f8tcwTjbMK7Bn1dYUXSi success
Program 5aDxxnPDGeVEuLXerisV4GF5f8tcwTjbMK7Bn1dYUXSi invoke [1]
Program log: Instruction: Swap
Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]
Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 105 of 574904 compute units
Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success
Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]
Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 105 of 572931 compute units
Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success
Program 5aDxxnPDGeVEuLXerisV4GF5f8tcwTjbMK7Bn1dYUXSi consumed 23615 of 595919 compute units
Program 5aDxxnPDGeVEuLXerisV4GF5f8tcwTjbMK7Bn1dYUXSi success
Program 5aDxxnPDGeVEuLXerisV4GF5f8tcwTjbMK7Bn1dYUXSi invoke [1]
Program log: Instruction: SetLocked
Program 5aDxxnPDGeVEuLXerisV4GF5f8tcwTjbMK7Bn1dYUXSi consumed 4079 of 572304 compute units
Program 5aDxxnPDGeVEuLXerisV4GF5f8tcwTjbMK7Bn1dYUXSi success
Integration
What an integrator writes: the derivations and the instruction, byte-identical to the transaction this page witnessed. Addresses are this scenario’s; supply your own.
#![allow(unused)]
fn main() {
use solana_instruction::{AccountMeta, Instruction};
use solana_pubkey::{pubkey, Pubkey};
// --- setLocked ---
let program_id: Pubkey = pubkey!("5aDxxnPDGeVEuLXerisV4GF5f8tcwTjbMK7Bn1dYUXSi");
let authority_1: Pubkey = pubkey!("2vG6xpsbeMe9s2VBYroRrjYzkaPzJXbh8Pn24UfMGuzx"); // Mallory: this scenario's value; supply your own
let config_1: Pubkey = pubkey!("DWm4MrK6YroDTYNXjcpjh7k5TLbriQDfkQjePWuAmLVc"); // Pool: this scenario's value; supply your own
let ix_1 = Instruction {
program_id,
accounts: vec![
AccountMeta::new_readonly(authority_1, true), // authority
AccountMeta::new(config_1, false), // config
],
// discriminator ++ borsh(locked: false)
data: vec![0x9d, 0x03, 0x30, 0x38, 0x1f, 0xd4, 0xf2, 0x58, 0x00],
};
// --- swap ---
let program_id: Pubkey = pubkey!("5aDxxnPDGeVEuLXerisV4GF5f8tcwTjbMK7Bn1dYUXSi");
let user_2: Pubkey = pubkey!("2vG6xpsbeMe9s2VBYroRrjYzkaPzJXbh8Pn24UfMGuzx"); // Mallory: this scenario's value; supply your own
let mint_x_2: Pubkey = pubkey!("Dq26t7JZEn9jCpma5Cn77d76SECZ79m2xwxpDDVnf6jn"); // Mint: this scenario's value; supply your own
let mint_y_2: Pubkey = pubkey!("DAxYEAkg7hsB7qZA8bLEJJPkNEgtEYqbxjcZx1VfLK5k"); // Mint 2: this scenario's value; supply your own
let config_2: Pubkey = pubkey!("DWm4MrK6YroDTYNXjcpjh7k5TLbriQDfkQjePWuAmLVc"); // Pool: this scenario's value; supply your own
let token_program_2: Pubkey = pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); // the token program
let vault_x_2: Pubkey = pubkey!("BEmMWKwvGZMBG47GzsU1noJYpgRtRAmJAjKzxmYAr1PY"); // derivation elided (a seed's encoding is beyond this snippet); address from the run
let vault_y_2: Pubkey = pubkey!("4dGtYZgpFMtNGJEpckGCvwYZs9VxYa1j3hPaeDsMejjy"); // derivation elided (a seed's encoding is beyond this snippet); address from the run
let user_x_2: Pubkey = pubkey!("G4yBZJK59Dvd4PZpL12d7BFZyL7GY6G2n3Y32o34BXG9"); // derivation elided (a seed's encoding is beyond this snippet); address from the run
let user_y_2: Pubkey = pubkey!("9YfuNPSd8wuBPh2FcizzSMK4A8dWNjApLjRkHwL5FGMB"); // derivation elided (a seed's encoding is beyond this snippet); address from the run
let ix_2 = Instruction {
program_id,
accounts: vec![
AccountMeta::new(user_2, true), // user
AccountMeta::new_readonly(mint_x_2, false), // mintX
AccountMeta::new_readonly(mint_y_2, false), // mintY
AccountMeta::new_readonly(config_2, false), // config
AccountMeta::new(vault_x_2, false), // vaultX
AccountMeta::new(vault_y_2, false), // vaultY
AccountMeta::new(user_x_2, false), // userX
AccountMeta::new(user_y_2, false), // userY
AccountMeta::new_readonly(token_program_2, false), // tokenProgram
],
// discriminator ++ borsh(kind: …, aToB: true)
data: vec![0xf8, 0xc6, 0x9e, 0x91, 0xe1, 0x75, 0x87, 0xc8, 0x00, 0xe8, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01],
};
// --- setLocked ---
let program_id: Pubkey = pubkey!("5aDxxnPDGeVEuLXerisV4GF5f8tcwTjbMK7Bn1dYUXSi");
let authority_3: Pubkey = pubkey!("2vG6xpsbeMe9s2VBYroRrjYzkaPzJXbh8Pn24UfMGuzx"); // Mallory: this scenario's value; supply your own
let config_3: Pubkey = pubkey!("DWm4MrK6YroDTYNXjcpjh7k5TLbriQDfkQjePWuAmLVc"); // Pool: this scenario's value; supply your own
let ix_3 = Instruction {
program_id,
accounts: vec![
AccountMeta::new_readonly(authority_3, true), // authority
AccountMeta::new(config_3, false), // config
],
// discriminator ++ borsh(locked: true)
data: vec![0x9d, 0x03, 0x30, 0x38, 0x1f, 0xd4, 0xf2, 0x58, 0x01],
};
}
The same call through the Codama-generated TypeScript client (the async builder derives every PDA the resolver derived here, so only the free inputs appear):
import { address } from "@solana/kit";
import { getSetLockedInstructionAsync } from "your-generated-client";
const ix = await getSetLockedInstructionAsync({
authority: authoritySigner, // Mallory (a TransactionSigner)
config: address("DWm4MrK6YroDTYNXjcpjh7k5TLbriQDfkQjePWuAmLVc"), // Pool
locked: false,
});
import { getSwapInstructionAsync } from "your-generated-client";
const ix = await getSwapInstructionAsync({
user: userSigner, // Mallory (a TransactionSigner)
mintX: address("Dq26t7JZEn9jCpma5Cn77d76SECZ79m2xwxpDDVnf6jn"), // Mint
mintY: address("DAxYEAkg7hsB7qZA8bLEJJPkNEgtEYqbxjcZx1VfLK5k"), // Mint 2
config: address("DWm4MrK6YroDTYNXjcpjh7k5TLbriQDfkQjePWuAmLVc"), // Pool
// tokenProgram defaults to the token program
kind: { __kind: "exactInput" },
aToB: true,
});
import { getSetLockedInstructionAsync } from "your-generated-client";
const ix = await getSetLockedInstructionAsync({
authority: authoritySigner, // Mallory (a TransactionSigner)
config: address("DWm4MrK6YroDTYNXjcpjh7k5TLbriQDfkQjePWuAmLVc"), // Pool
locked: true,
});