use super::*;
use solana_instruction::AccountMeta;
use spl_tlv_account_resolution::account::ExtraAccountMeta;
use spl_tlv_account_resolution::pubkey_data::PubkeyData;
use spl_tlv_account_resolution::seeds::Seed;
use spl_tlv_account_resolution::state::ExtraAccountMetaList;
use crate::constants::{
ATA_PROGRAM_ID, DEFAULT_PARTNER, DEX_PROGRAM_ID, LAUNCHPAD_PROGRAM_ID,
SYSTEM_PROGRAM_ID, TOKEN_2022_PROGRAM_ID,
};
use crate::transfer_hook::{
AccountView, Execute, FetchedAccount, HookError, HookState, Unresolvable,
find_validation_address,
};
const HOOK: Address = Address::new_from_array([40; 32]);
const RENT_SYSVAR: Address =
solana_address::address!("SysvarRent111111111111111111111111111111111");
const STATE: Address = Address::new_from_array([41; 32]);
const TRADE: TradeAccounts = TradeAccounts {
user: Address::new_from_array([1; 32]),
payer: Address::new_from_array([2; 32]),
market: Address::new_from_array([3; 32]),
base_mint: Address::new_from_array([4; 32]),
quote_mint: Address::new_from_array([5; 32]),
base_vault: Address::new_from_array([6; 32]),
quote_vault: Address::new_from_array([7; 32]),
user_base_account: Address::new_from_array([8; 32]),
user_quote_account: Address::new_from_array([9; 32]),
partner: DEFAULT_PARTNER,
partner_config: Address::new_from_array([10; 32]),
quote_token_program: TOKEN_2022_PROGRAM_ID,
};
const PROGRAMS: [TradeProgram; 2] =
[TradeProgram::Launchpad, TradeProgram::Dex];
const DIRECTIONS: [TradeDirection; 2] =
[TradeDirection::Buy, TradeDirection::Sell];
fn list(extras: &[ExtraAccountMeta]) -> Vec<u8> {
let mut data =
vec![0; ExtraAccountMetaList::size_of(extras.len()).unwrap()];
ExtraAccountMetaList::init::<Execute>(&mut data, extras).unwrap();
data
}
fn fixed(pubkey: Address, writable: bool) -> ExtraAccountMeta {
ExtraAccountMeta::new_with_pubkey(&pubkey, false, writable).unwrap()
}
fn destination_owner(writable: bool) -> ExtraAccountMeta {
ExtraAccountMeta::new_with_pubkey_data(
&PubkeyData::AccountData {
account_index: 2,
data_index: 32,
},
false,
writable,
)
.unwrap()
}
#[derive(Clone, Copy, Debug)]
enum Hooked {
Base,
Quote,
}
impl Hooked {
const fn mint(self) -> Address {
match self {
Self::Base => TRADE.base_mint,
Self::Quote => TRADE.quote_mint,
}
}
}
struct Market {
base: MintInfo,
quote: MintInfo,
}
impl Market {
const fn exact_in(
&self,
program: TradeProgram,
direction: TradeDirection,
) -> TradeRoute<'_> {
TradeRoute {
program,
trade: &TRADE,
base_mint_info: &self.base,
quote_mint_info: &self.quote,
direction,
mode: TradeMode::ExactIn,
amount: 1_000,
limit: 0,
}
}
}
const fn plain(mint: Address) -> MintInfo {
MintInfo::plain(mint, TOKEN_2022_PROGRAM_ID, 6)
}
fn hooked_info(mint: Address, accounts: &[(Address, Vec<u8>)]) -> MintInfo {
let mut hook = HookState::default();
for (address, data) in accounts {
hook.insert(
*address,
Some(FetchedAccount {
owner: HOOK,
data: data.clone(),
}),
);
}
MintInfo {
hook_program: Some(HOOK),
hook: Some(hook),
..plain(mint)
}
}
fn market_of(hooked: Hooked, extras: &[ExtraAccountMeta]) -> Market {
let info = |mint: Address| {
if mint == hooked.mint() {
hooked_info(mint, &[(validation(hooked), list(extras))])
} else {
plain(mint)
}
};
Market {
base: info(TRADE.base_mint),
quote: info(TRADE.quote_mint),
}
}
const fn empty(_: &Address) -> Fetched<'static> {
Fetched::NotFetched
}
fn validation(hooked: Hooked) -> Address {
find_validation_address(&hooked.mint(), &HOOK)
}
fn trailing(route: &Route) -> &[AccountMeta] {
route.instruction.accounts.get(17..).unwrap()
}
#[test]
fn a_writable_repeat_of_a_read_only_trade_account_does_not_route() {
let cases = [
(Hooked::Quote, TradeDirection::Buy, TRADE.market),
(Hooked::Quote, TradeDirection::Buy, TRADE.base_mint),
(Hooked::Base, TradeDirection::Sell, TRADE.quote_mint),
(Hooked::Quote, TradeDirection::Buy, TRADE.partner_config),
(Hooked::Quote, TradeDirection::Sell, DEFAULT_PARTNER),
(Hooked::Base, TradeDirection::Buy, RENT_SYSVAR),
];
for program in PROGRAMS {
for (hooked, direction, account) in cases {
let market = market_of(hooked, &[fixed(account, true)]);
assert_eq!(
trade(market.exact_in(program, direction), empty),
Err(Unroutable::WritableRepeat(account)),
"{program:?} {direction:?}: {account}"
);
let market = market_of(hooked, &[fixed(account, false)]);
let route =
trade(market.exact_in(program, direction), empty).unwrap();
assert_eq!(
trailing(&route),
[
AccountMeta::new_readonly(account, false),
AccountMeta::new_readonly(HOOK, false),
AccountMeta::new_readonly(validation(hooked), false),
]
);
}
}
assert_eq!(SYSTEM_PROGRAM_ID, DEFAULT_PARTNER);
assert_eq!(
Unroutable::WritableRepeat(TRADE.market).to_string(),
format!(
"a transfer hook wants {} writable, which the trade holds \
read-only",
TRADE.market
)
);
}
#[test]
fn a_writable_program_the_runtime_does_not_demote_routes() {
for (program, own) in [
(TradeProgram::Launchpad, LAUNCHPAD_PROGRAM_ID),
(TradeProgram::Dex, DEX_PROGRAM_ID),
] {
for account in [TOKEN_2022_PROGRAM_ID, ATA_PROGRAM_ID, own] {
let market = market_of(Hooked::Quote, &[fixed(account, true)]);
for direction in DIRECTIONS {
let route =
trade(market.exact_in(program, direction), empty).unwrap();
assert_eq!(route.instruction.program_id, own);
assert_eq!(
trailing(&route),
[
AccountMeta::new(account, false),
AccountMeta::new_readonly(HOOK, false),
AccountMeta::new_readonly(
validation(Hooked::Quote),
false
),
],
"{program:?} {direction:?}: {account}"
);
assert_eq!(route.reads, Vec::<Address>::new());
}
}
}
}
#[test]
fn each_direction_routes_on_its_own() {
for program in PROGRAMS {
for (hooked, refused, routed) in [
(Hooked::Quote, TradeDirection::Buy, TradeDirection::Sell),
(Hooked::Base, TradeDirection::Sell, TradeDirection::Buy),
] {
let market = market_of(hooked, &[destination_owner(true)]);
assert_eq!(
trade(market.exact_in(program, refused), empty),
Err(Unroutable::WritableRepeat(TRADE.market)),
"{program:?} {hooked:?}"
);
let route = trade(market.exact_in(program, routed), empty).unwrap();
assert_eq!(
trailing(&route),
[
AccountMeta::new(TRADE.user, false),
AccountMeta::new_readonly(HOOK, false),
AccountMeta::new_readonly(validation(hooked), false),
],
"{program:?} {hooked:?}"
);
assert_eq!(route.reads, Vec::<Address>::new());
}
}
}
#[test]
fn an_extra_that_must_sign_does_not_route() {
let signer =
ExtraAccountMeta::new_with_pubkey(&STATE, true, false).unwrap();
let market = market_of(Hooked::Quote, &[signer]);
for program in PROGRAMS {
for direction in DIRECTIONS {
assert_eq!(
trade(market.exact_in(program, direction), empty),
Err(Unroutable::ExtraSigns)
);
}
}
assert_eq!(
Unroutable::ExtraSigns.to_string(),
"a transfer hook extra must sign"
);
}
#[test]
fn an_execute_role_conflict_does_not_route() {
for extras in [
vec![fixed(TRADE.quote_mint, true)],
vec![fixed(STATE, true), fixed(STATE, false)],
] {
let market = market_of(Hooked::Quote, &extras);
for program in PROGRAMS {
for direction in DIRECTIONS {
assert_eq!(
trade(market.exact_in(program, direction), empty),
Err(Unroutable::ExecuteRoleConflict),
"{program:?} {direction:?}: {extras:?}"
);
}
}
}
let market =
market_of(Hooked::Quote, &[fixed(STATE, true), fixed(STATE, true)]);
let route = trade(
market.exact_in(TradeProgram::Launchpad, TradeDirection::Buy),
empty,
)
.unwrap();
assert_eq!(
trailing(&route),
[
AccountMeta::new(STATE, false),
AccountMeta::new_readonly(HOOK, false),
AccountMeta::new_readonly(validation(Hooked::Quote), false),
]
);
assert_eq!(
Unroutable::ExecuteRoleConflict.to_string(),
"a transfer hook wants one account of its Execute both read-only and \
writable"
);
}
#[test]
fn a_read_the_cache_lacks_is_not_cached() {
let market = market_of(
Hooked::Quote,
&[
fixed(STATE, false),
ExtraAccountMeta::new_with_seeds(
&[Seed::AccountData {
account_index: 5,
data_index: 0,
length: 32,
}],
false,
false,
)
.unwrap(),
],
);
let route = market.exact_in(TradeProgram::Launchpad, TradeDirection::Buy);
assert_eq!(trade(route, empty), Err(Unroutable::NotCached(vec![STATE])));
let data = [7; 32];
let routed = trade(route, |key| {
if *key == STATE {
Fetched::Account(AccountView {
owner: &SYSTEM_PROGRAM_ID,
data: &data,
})
} else {
Fetched::NotFetched
}
})
.unwrap();
assert_eq!(routed.reads, vec![STATE]);
assert_eq!(
trailing(&routed),
[
AccountMeta::new_readonly(STATE, false),
AccountMeta::new_readonly(
Address::find_program_address(&[&data], &HOOK).0,
false
),
AccountMeta::new_readonly(HOOK, false),
AccountMeta::new_readonly(validation(Hooked::Quote), false),
]
);
}
#[test]
fn the_resolvers_refusals_do_not_route() {
let unloaded = Market {
base: hooked_info(TRADE.base_mint, &[]),
quote: plain(TRADE.quote_mint),
};
let route = unloaded.exact_in(TradeProgram::Launchpad, TradeDirection::Buy);
let not_loaded =
Err(Unroutable::Hook(HookError::NotLoaded(vec![validation(
Hooked::Base,
)])));
assert_eq!(trade(route, empty), not_loaded);
let data = list(&[fixed(STATE, false)]);
assert_eq!(
trade(route, |key| {
if *key == validation(Hooked::Base) {
Fetched::Account(AccountView {
owner: &HOOK,
data: &data,
})
} else {
Fetched::NotFetched
}
}),
not_loaded
);
}
#[test]
fn a_hook_free_mint_a_list_reads_is_not_cached_until_cached() {
let market = market_of(
Hooked::Quote,
&[
fixed(TRADE.base_mint, false),
ExtraAccountMeta::new_with_seeds(
&[Seed::AccountData {
account_index: 5,
data_index: 44,
length: 2,
}],
false,
false,
)
.unwrap(),
],
);
for program in PROGRAMS {
for direction in DIRECTIONS {
let route = market.exact_in(program, direction);
assert_eq!(
trade(route, empty),
Err(Unroutable::NotCached(vec![TRADE.base_mint])),
"{program:?} {direction:?}"
);
let mut base = vec![0; 82];
base[44] = 6;
base[45] = 1;
let routed = trade(route, |key| {
if *key == TRADE.base_mint {
Fetched::Account(AccountView {
owner: &TOKEN_2022_PROGRAM_ID,
data: &base,
})
} else {
Fetched::NotFetched
}
})
.unwrap();
assert_eq!(routed.reads, vec![TRADE.base_mint]);
assert_eq!(
trailing(&routed),
[
AccountMeta::new_readonly(TRADE.base_mint, false),
AccountMeta::new_readonly(
Address::find_program_address(&[&[6, 1]], &HOOK).0,
false
),
AccountMeta::new_readonly(HOOK, false),
AccountMeta::new_readonly(validation(Hooked::Quote), false),
],
"{program:?} {direction:?}"
);
}
}
assert_eq!(
Unroutable::NotCached(vec![TRADE.base_mint]).to_string(),
"transfer hook accounts not cached"
);
}
#[test]
fn a_read_comes_from_the_infos_before_the_cache() {
let extras = [
fixed(STATE, false),
ExtraAccountMeta::new_with_seeds(
&[Seed::AccountData {
account_index: 5,
data_index: 0,
length: 32,
}],
false,
false,
)
.unwrap(),
];
let held = vec![7; 32];
let cached = [8; 32];
let cache = |key: &Address| {
if *key == STATE {
Fetched::Account(AccountView {
owner: &SYSTEM_PROGRAM_ID,
data: &cached,
})
} else {
Fetched::NotFetched
}
};
let expected = |seed: &[u8]| {
vec![
AccountMeta::new_readonly(STATE, false),
AccountMeta::new_readonly(
Address::find_program_address(&[seed], &HOOK).0,
false,
),
AccountMeta::new_readonly(HOOK, false),
AccountMeta::new_readonly(validation(Hooked::Quote), false),
]
};
let quote = |accounts: &[(Address, Vec<u8>)]| {
hooked_info(
TRADE.quote_mint,
&[
[(validation(Hooked::Quote), list(&extras))].as_slice(),
accounts,
]
.concat(),
)
};
for (market, expected) in [
(
Market {
base: plain(TRADE.base_mint),
quote: quote(&[(STATE, held.clone())]),
},
expected(&held),
),
(
Market {
base: hooked_info(
TRADE.base_mint,
&[
(validation(Hooked::Base), list(&[])),
(STATE, held.clone()),
],
),
quote: quote(&[]),
},
[
expected(&held),
vec![AccountMeta::new_readonly(
validation(Hooked::Base),
false,
)],
]
.concat(),
),
] {
let routed = trade(
market.exact_in(TradeProgram::Launchpad, TradeDirection::Buy),
cache,
)
.unwrap();
assert_eq!(trailing(&routed), expected);
assert_eq!(routed.reads, vec![STATE]);
}
let routed = trade(
Market {
base: plain(TRADE.base_mint),
quote: quote(&[]),
}
.exact_in(TradeProgram::Launchpad, TradeDirection::Buy),
cache,
)
.unwrap();
assert_eq!(trailing(&routed), expected(&cached));
}
#[test]
fn two_hook_free_mints_route_hook_free() {
let market = Market {
base: plain(TRADE.base_mint),
quote: plain(TRADE.quote_mint),
};
for program in PROGRAMS {
for direction in DIRECTIONS {
let routed =
trade(market.exact_in(program, direction), empty).unwrap();
assert_eq!(routed.instruction.accounts.len(), 17);
assert_eq!(routed.reads, Vec::<Address>::new());
}
}
}
#[test]
fn an_amount_seed_does_not_route() {
let amount = ExtraAccountMeta::new_with_seeds(
&[Seed::InstructionData {
index: 8,
length: 8,
}],
false,
false,
)
.unwrap();
let refused = Unroutable::Hook(HookError::Unresolvable(
Unresolvable::ReadsTransferAmount,
));
for hooked in [Hooked::Base, Hooked::Quote] {
let market = market_of(hooked, &[amount]);
for program in PROGRAMS {
for direction in DIRECTIONS {
for mode in [TradeMode::ExactIn, TradeMode::ExactOut] {
assert_eq!(
trade(
TradeRoute {
mode,
..market.exact_in(program, direction)
},
empty,
),
Err(refused.clone()),
"{hooked:?} {program:?} {direction:?} {mode:?}"
);
}
}
}
}
assert_eq!(
refused.to_string(),
"transfer hook cannot be resolved ahead of the instruction: a seed \
reads the transfer amount"
);
assert_eq!(
Unroutable::UncacheableRead.to_string(),
"a transfer hook reads an account that depends on the trader"
);
}
#[test]
fn an_info_of_another_mint_does_not_route() {
let other = Address::new_from_array([42; 32]);
for (base, quote, refused) in [
(plain(other), plain(TRADE.quote_mint), TRADE.base_mint),
(plain(TRADE.base_mint), plain(other), TRADE.quote_mint),
(
plain(TRADE.quote_mint),
plain(TRADE.base_mint),
TRADE.base_mint,
),
] {
let market = Market { base, quote };
for program in PROGRAMS {
for direction in DIRECTIONS {
assert_eq!(
trade(market.exact_in(program, direction), empty),
Err(Unroutable::MintMismatch(refused)),
"{program:?} {direction:?}"
);
}
}
}
assert_eq!(
Unroutable::MintMismatch(TRADE.base_mint).to_string(),
format!(
"the mint info is not the info of the trade's mint {}",
TRADE.base_mint
)
);
}
#[test]
fn a_base_mint_that_is_not_a_token_2022_mint_does_not_route() {
let market = Market {
base: MintInfo::plain(
TRADE.base_mint,
crate::constants::TOKEN_PROGRAM_ID,
6,
),
quote: plain(TRADE.quote_mint),
};
for program in PROGRAMS {
for direction in DIRECTIONS {
assert_eq!(
trade(market.exact_in(program, direction), empty),
Err(Unroutable::BaseNotToken2022(TRADE.base_mint)),
"{program:?} {direction:?}"
);
}
}
assert_eq!(
Unroutable::BaseNotToken2022(TRADE.base_mint).to_string(),
format!("the base mint {} is not a Token-2022 mint", TRADE.base_mint)
);
}
#[test]
fn a_quote_token_program_that_is_not_the_quote_mints_does_not_route() {
let market = Market {
base: plain(TRADE.base_mint),
quote: MintInfo::plain(
TRADE.quote_mint,
crate::constants::TOKEN_PROGRAM_ID,
6,
),
};
for program in PROGRAMS {
for direction in DIRECTIONS {
assert_eq!(
trade(market.exact_in(program, direction), empty),
Err(Unroutable::QuoteTokenProgramMismatch(TRADE.quote_mint)),
"{program:?} {direction:?}"
);
}
}
assert_eq!(
Unroutable::QuoteTokenProgramMismatch(TRADE.quote_mint).to_string(),
format!(
"the quote token program is not the token program of the quote \
mint {}",
TRADE.quote_mint
)
);
}
#[test]
fn a_hook_program_with_no_hook_does_not_route() {
let data = list(&[fixed(RENT_SYSVAR, false)]);
for hooked in [Hooked::Base, Hooked::Quote] {
let mut market = market_of(hooked, &[fixed(RENT_SYSVAR, false)]);
match hooked {
Hooked::Base => market.base.hook = None,
Hooked::Quote => market.quote.hook = None,
}
let cached = |address: &Address| {
if *address == validation(hooked) {
Fetched::Account(AccountView {
owner: &HOOK,
data: &data,
})
} else {
Fetched::NotFetched
}
};
for program in PROGRAMS {
for direction in DIRECTIONS {
assert_eq!(
trade(market.exact_in(program, direction), cached),
Err(Unroutable::HookNotRead(hooked.mint())),
"{hooked:?} {program:?} {direction:?}"
);
}
}
}
assert_eq!(
Unroutable::HookNotRead(TRADE.quote_mint).to_string(),
format!(
"the mint {} has a hook program and no hook",
TRADE.quote_mint
)
);
}
const STAND_IN_USERS: [Address; 2] = [
Address::new_from_array([1; 32]),
Address::new_from_array([5; 32]),
];
fn hook_pda(seed: &Address) -> Address {
Address::find_program_address(&[seed.as_ref()], &HOOK).0
}
fn reads_a_pda_of_the_trader() -> Market {
market_of(
Hooked::Quote,
&[
ExtraAccountMeta::new_with_seeds(
&[Seed::AccountKey { index: 3 }],
false,
false,
)
.unwrap(),
ExtraAccountMeta::new_with_seeds(
&[Seed::AccountData {
account_index: 5,
data_index: 0,
length: 32,
}],
false,
false,
)
.unwrap(),
],
)
}
fn screen_buy(
market: &Market,
accounts: impl Fn(&Address) -> Fetched<'static>,
) -> Screen {
screen(
market.exact_in(TradeProgram::Launchpad, TradeDirection::Buy),
accounts,
)
}
#[test]
fn screen_resolves_the_trade_for_each_stand_in_trader() {
let market = market_of(
Hooked::Quote,
&[ExtraAccountMeta::new_with_seeds(
&[Seed::AccountKey { index: 3 }],
false,
false,
)
.unwrap()],
);
let screened = screen_buy(&market, empty);
assert_eq!(screened.route, Ok(()));
assert_eq!(screened.not_cached, Vec::<Address>::new());
for (route, user) in screened.stand_ins.iter().zip(STAND_IN_USERS) {
let route = route.as_ref().unwrap();
assert_eq!(route.instruction.accounts[0].pubkey, user);
assert_eq!(
trailing(route),
[
AccountMeta::new_readonly(hook_pda(&user), false),
AccountMeta::new_readonly(HOOK, false),
AccountMeta::new_readonly(validation(Hooked::Quote), false),
]
);
}
let other_trader = TradeAccounts {
user: Address::new_from_array([77; 32]),
..TRADE
};
assert_eq!(
screen(
TradeRoute {
trade: &other_trader,
..market.exact_in(TradeProgram::Launchpad, TradeDirection::Buy)
},
empty,
),
screened
);
}
#[test]
fn screen_routes_a_hook_free_market() {
let market = Market {
base: plain(TRADE.base_mint),
quote: plain(TRADE.quote_mint),
};
for program in PROGRAMS {
for direction in DIRECTIONS {
let screened = screen(market.exact_in(program, direction), empty);
assert_eq!(screened.route, Ok(()));
assert_eq!(screened.not_cached, Vec::<Address>::new());
for route in &screened.stand_ins {
assert_eq!(
route.as_ref().unwrap().instruction.accounts.len(),
17
);
}
}
}
}
#[test]
fn screen_names_what_both_stand_ins_lack() {
let market = market_of(
Hooked::Quote,
&[
fixed(STATE, false),
ExtraAccountMeta::new_with_seeds(
&[Seed::AccountData {
account_index: 5,
data_index: 0,
length: 32,
}],
false,
false,
)
.unwrap(),
],
);
let screened = screen_buy(&market, empty);
assert_eq!(screened.route, Err(Unroutable::NotCached(vec![STATE])));
assert_eq!(screened.not_cached, vec![STATE]);
let screened = screen_buy(&market, |key| {
if *key == STATE {
Fetched::Account(AccountView {
owner: &SYSTEM_PROGRAM_ID,
data: &[7; 32],
})
} else {
Fetched::NotFetched
}
});
assert_eq!(screened.route, Ok(()));
assert_eq!(screened.not_cached, Vec::<Address>::new());
assert_eq!(
screened.stand_ins.map(|route| route.unwrap().reads),
[vec![STATE], vec![STATE]]
);
}
#[test]
fn screen_refuses_a_list_that_lacks_a_different_account_per_trader() {
let screened = screen_buy(&reads_a_pda_of_the_trader(), empty);
assert_eq!(screened.route, Err(Unroutable::UncacheableRead));
assert_eq!(screened.not_cached, Vec::<Address>::new());
assert_eq!(
screened.stand_ins.map(Result::err),
STAND_IN_USERS
.map(|user| Some(Unroutable::NotCached(vec![hook_pda(&user)])))
);
}
#[test]
fn screen_refuses_a_list_that_reads_a_different_account_per_trader() {
let [first, second] = STAND_IN_USERS.map(|user| hook_pda(&user));
let screened = screen_buy(&reads_a_pda_of_the_trader(), move |key| {
if *key == first || *key == second {
Fetched::Account(AccountView {
owner: &SYSTEM_PROGRAM_ID,
data: &[7; 32],
})
} else {
Fetched::NotFetched
}
});
assert_eq!(screened.route, Err(Unroutable::UncacheableRead));
assert_eq!(
screened.stand_ins.map(|route| route.unwrap().reads),
[vec![first], vec![second]]
);
}
#[test]
fn screen_refuses_stand_ins_that_fail_differently() {
let short = |cut: Address| {
move |key: &Address| {
if *key == cut {
Fetched::Account(AccountView {
owner: &SYSTEM_PROGRAM_ID,
data: &[7; 8],
})
} else {
Fetched::NotFetched
}
}
};
let unresolved = Unroutable::Hook(HookError::Unresolvable(
Unresolvable::ExtraUnresolved,
));
let [first, second] = STAND_IN_USERS.map(|user| hook_pda(&user));
for (cut, outcomes) in [
(
first,
[
Some(unresolved.clone()),
Some(Unroutable::NotCached(vec![second])),
],
),
(
second,
[Some(Unroutable::NotCached(vec![first])), Some(unresolved)],
),
] {
let screened = screen_buy(&reads_a_pda_of_the_trader(), short(cut));
assert_eq!(screened.stand_ins.clone().map(Result::err), outcomes);
assert_eq!(screened.route, Err(Unroutable::UncacheableRead));
assert_eq!(screened.not_cached, Vec::<Address>::new());
}
}
#[test]
fn screen_keeps_an_error_both_stand_ins_share() {
let amount = ExtraAccountMeta::new_with_seeds(
&[Seed::InstructionData {
index: 8,
length: 8,
}],
false,
false,
)
.unwrap();
let screened = screen_buy(&market_of(Hooked::Quote, &[amount]), empty);
assert_eq!(
screened.route,
Err(Unroutable::Hook(HookError::Unresolvable(
Unresolvable::ReadsTransferAmount
)))
);
}
fn hooked_quote_mint() -> FetchedAccount {
let mut data = vec![0; 165];
data[44] = 6;
data[45] = 1;
data.push(1);
data.extend_from_slice(
&u16::from(
spl_token_2022_interface::extension::ExtensionType::TransferHook,
)
.to_le_bytes(),
);
data.extend_from_slice(&64_u16.to_le_bytes());
data.extend_from_slice(&[7; 32]);
data.extend_from_slice(&HOOK.to_bytes());
FetchedAccount {
owner: TOKEN_2022_PROGRAM_ID,
data,
}
}
fn stored_in(account_index: u8, signer: bool) -> ExtraAccountMeta {
ExtraAccountMeta::new_with_pubkey_data(
&PubkeyData::AccountData {
account_index,
data_index: 0,
},
signer,
false,
)
.unwrap()
}
fn record_of_destination() -> ExtraAccountMeta {
ExtraAccountMeta::new_with_seeds(
&[
Seed::Literal {
bytes: b"rec".to_vec(),
},
Seed::AccountKey { index: 2 },
],
false,
false,
)
.unwrap()
}
fn record_of(destination: &Address) -> Address {
Address::find_program_address(&[b"rec", destination.as_ref()], &HOOK).0
}
struct Declared {
accounts: Vec<(Address, FetchedAccount)>,
declared: Vec<Address>,
}
impl Declared {
fn new(
extras: &[ExtraAccountMeta],
accounts: &[(Address, Address)],
) -> Self {
let mut all = vec![(
validation(Hooked::Quote),
FetchedAccount {
owner: HOOK,
data: list(extras),
},
)];
for (address, stored) in accounts {
all.push((
*address,
FetchedAccount {
owner: HOOK,
data: stored.to_bytes().to_vec(),
},
));
}
Self {
accounts: all,
declared: Vec::new(),
}
}
fn update(&mut self) -> LoadedMarket {
let mint = hooked_quote_mint();
let quote = MintInfo::load_at(
TRADE.quote_mint,
AccountView {
owner: &mint.owner,
data: &mint.data,
},
0,
)
.unwrap();
let mut load = MarketLoad::new(MarketLoadArgs {
program: TradeProgram::Launchpad,
market: TRADE,
base_mint_info: plain(TRADE.base_mint),
quote_mint_info: quote,
});
loop {
let next = load.missing().unwrap();
if next.is_empty() {
break;
}
for address in next {
if self.declared.contains(&address) {
let account = self
.accounts
.iter()
.find(|(key, _)| *key == address)
.map(|(_, account)| account.clone());
load.insert(address, account);
}
}
}
let loaded = load.finish();
self.declared.clone_from(&loaded.declare);
loaded
}
fn buys(
&mut self,
updates: usize,
) -> Vec<(Result<(), Unroutable>, Vec<Address>)> {
(0..updates)
.map(|_| {
let loaded = self.update();
let [buy, _] = loaded.screens.unwrap();
(buy.route, loaded.declare)
})
.collect()
}
}
fn list_not_loaded() -> Result<(), Unroutable> {
Err(Unroutable::Hook(HookError::NotLoaded(vec![validation(
Hooked::Quote,
)])))
}
#[test]
fn a_market_load_loads_a_chain_of_market_reads_one_link_at_each_update() {
let record = record_of(&TRADE.quote_vault);
let [second, third] = [[61; 32], [62; 32]].map(Address::new_from_array);
let mut router = Declared::new(
&[
record_of_destination(),
stored_in(5, false),
stored_in(6, false),
],
&[(record, second), (second, third)],
);
let list = validation(Hooked::Quote);
assert_eq!(
router.buys(5),
[
(list_not_loaded(), vec![list]),
(Err(Unroutable::NotCached(vec![record])), vec![list, record]),
(
Err(Unroutable::NotCached(vec![second])),
vec![list, record, second]
),
(Ok(()), vec![list, record, second]),
(Ok(()), vec![list, record, second]),
]
);
let loaded = router.update();
let [_, sell] = loaded.screens.unwrap();
assert_eq!(sell.route, Err(Unroutable::UncacheableRead));
let resolved = trade(
TradeRoute {
program: TradeProgram::Launchpad,
trade: &TRADE,
base_mint_info: &loaded.base_mint_info,
quote_mint_info: &loaded.quote_mint_info,
direction: TradeDirection::Buy,
mode: TradeMode::ExactIn,
amount: 1_000,
limit: 0,
},
|address| loaded.cache.fetched(address),
)
.unwrap();
assert_eq!(
trailing(&resolved),
[
AccountMeta::new_readonly(record, false),
AccountMeta::new_readonly(second, false),
AccountMeta::new_readonly(third, false),
AccountMeta::new_readonly(HOOK, false),
AccountMeta::new_readonly(list, false),
]
);
}
#[test]
fn a_market_load_keeps_a_refused_directions_reads_declared() {
let record = record_of(&TRADE.quote_vault);
let mut router = Declared::new(
&[record_of_destination(), stored_in(5, true)],
&[(record, Address::new_from_array([61; 32]))],
);
let list = validation(Hooked::Quote);
assert_eq!(
router.buys(5),
[
(list_not_loaded(), vec![list]),
(Err(Unroutable::NotCached(vec![record])), vec![list, record]),
(Err(Unroutable::ExtraSigns), vec![list, record]),
(Err(Unroutable::ExtraSigns), vec![list, record]),
(Err(Unroutable::ExtraSigns), vec![list, record]),
]
);
}
#[test]
fn a_market_load_names_independent_fixed_reads_together() {
let [first, second] = [[61; 32], [62; 32]].map(Address::new_from_array);
let mut router = Declared::new(
&[
fixed(first, false),
fixed(second, false),
stored_in(5, false),
stored_in(6, false),
],
&[
(first, Address::new_from_array([63; 32])),
(second, Address::new_from_array([64; 32])),
],
);
let list = validation(Hooked::Quote);
assert_eq!(
router.buys(3),
[
(list_not_loaded(), vec![list]),
(
Err(Unroutable::NotCached(vec![first])),
vec![list, first, second]
),
(Ok(()), vec![list, first, second]),
]
);
}
#[test]
fn a_market_load_of_hook_free_mints_names_nothing() {
let mut load = MarketLoad::new(MarketLoadArgs {
program: TradeProgram::Dex,
market: TRADE,
base_mint_info: plain(TRADE.base_mint),
quote_mint_info: plain(TRADE.quote_mint),
});
assert_eq!(load.missing(), Ok(Vec::new()));
let loaded = load.finish();
assert_eq!(loaded.screens, None);
assert_eq!(loaded.declare, Vec::<Address>::new());
assert_eq!(loaded.cache, HookState::default());
}