1use std::collections::BTreeMap;
19use std::fmt;
20
21use solana_address::{Address, MAX_SEEDS};
22use solana_instruction::AccountMeta;
23use spl_discriminator::{ArrayDiscriminator, SplDiscriminate};
24use spl_tlv_account_resolution::account::ExtraAccountMeta;
25use spl_tlv_account_resolution::pubkey_data::PubkeyData;
26use spl_tlv_account_resolution::seeds::Seed;
27use spl_tlv_account_resolution::solana_program_error::ProgramError;
28use spl_tlv_account_resolution::state::ExtraAccountMetaList;
29use spl_token_2022_interface::extension::transfer_hook::TransferHook;
30use spl_type_length_value::error::TlvError;
31use spl_type_length_value::state::TlvStateBorrowed;
32
33use crate::transfer_fee::{
34 TransferFeeConfig, extension, mint_extensions, spl_token_mint,
35};
36use crate::utils::{MintInfo, TradeMints};
37
38pub mod route;
39
40pub const EXECUTE_DISCRIMINATOR: [u8; 8] =
43 [105, 37, 101, 197, 75, 251, 102, 26];
44
45pub const VALIDATION_SEED: &[u8] = b"extra-account-metas";
48
49const VALIDATION_INDEX: usize = 4;
52const EXECUTE_ACCOUNTS: usize = 5;
53
54const EXECUTE_DATA_LEN: usize = 16;
57
58const TOKEN_ACCOUNT_KNOWN_LEN: usize = 64;
60
61const PUBKEY_LEN: usize = 32;
62
63const META_FIXED: u8 = 0;
65const META_HOOK_PDA: u8 = 1;
66const META_PUBKEY_DATA: u8 = 2;
67const META_EXTERNAL_PDA: u8 = 128;
68
69#[derive(Clone, Copy, Debug, PartialEq, Eq)]
70pub struct AccountView<'a> {
71 pub owner: &'a Address,
72 pub data: &'a [u8],
73}
74
75#[derive(Clone, Copy, Debug, PartialEq, Eq)]
77pub enum Fetched<'a> {
78 Account(AccountView<'a>),
79 Missing,
81 NotFetched,
83}
84
85#[must_use]
86pub fn find_validation_address(mint: &Address, program: &Address) -> Address {
87 Address::find_program_address(&[VALIDATION_SEED, mint.as_ref()], program).0
88}
89
90#[derive(Clone, Copy, Debug, PartialEq)]
92pub(crate) struct MintState {
93 pub token_program: Address,
94 pub decimals: u8,
96 pub transfer_fee: Option<TransferFeeConfig>,
97 pub transfer_hook: Option<Address>,
100}
101
102impl MintState {
103 pub(crate) fn load(mint: AccountView<'_>) -> Result<Self, ProgramError> {
108 let token_program = *mint.owner;
109 let Some(state) = mint_extensions(mint.data, &token_program)? else {
110 return Ok(Self {
111 token_program,
112 decimals: spl_token_mint(mint.data)?.decimals,
113 transfer_fee: None,
114 transfer_hook: None,
115 });
116 };
117 Ok(Self {
118 token_program,
119 decimals: state.base.decimals,
120 transfer_fee: extension::<TransferFeeConfig>(&state)?.copied(),
121 transfer_hook: extension::<TransferHook>(&state)?
123 .and_then(|hook| Option::from(hook.program_id)),
124 })
125 }
126}
127
128#[derive(Clone, Copy, Debug, PartialEq, Eq)]
131#[non_exhaustive]
132pub enum Unresolvable {
133 MalformedList,
134 NoExecuteEntry,
135 ListTooLong,
137 MalformedExtra,
138 MalformedSeed,
139 TooManySeeds,
141 SeedTooLong,
142 ExtraUnresolved,
145 ReadsTransferAmount,
147 ReadsPastInstructionData,
150 NamesUnresolvedAccount,
152 ReadsSwapTimeData,
156}
157
158impl fmt::Display for Unresolvable {
159 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160 f.write_str(match self {
161 Self::MalformedList => "malformed list",
162 Self::NoExecuteEntry => "no Execute entry in the list",
163 Self::ListTooLong => "list too long",
164 Self::MalformedExtra => "malformed extra",
165 Self::MalformedSeed => "malformed seed",
166 Self::TooManySeeds => "a PDA has more seeds than an address allows",
167 Self::SeedTooLong => "seed longer than 32 bytes",
168 Self::ExtraUnresolved => "an extra does not resolve",
169 Self::ReadsTransferAmount => "a seed reads the transfer amount",
170 Self::ReadsPastInstructionData => {
171 "an extra reads past the end of the hook's instruction data"
172 }
173 Self::NamesUnresolvedAccount => {
174 "an extra names an account not yet resolved"
175 }
176 Self::ReadsSwapTimeData => {
177 "an extra reads account data the instruction changes first"
178 }
179 })
180 }
181}
182
183#[derive(Clone, Debug, PartialEq, Eq)]
184#[non_exhaustive]
185pub enum HookError {
186 NotLoaded(Vec<Address>),
190 NoList,
193 MalformedMint,
196 Unresolvable(Unresolvable),
197}
198
199impl fmt::Display for HookError {
200 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201 match self {
202 Self::NotLoaded(_) => {
203 write!(f, "transfer hook accounts not loaded")
204 }
205 Self::NoList => {
206 write!(f, "transfer hook has no ExtraAccountMetaList")
207 }
208 Self::MalformedMint => {
209 write!(f, "a transfer's mint does not decode as a mint")
210 }
211 Self::Unresolvable(reason) => write!(
212 f,
213 "transfer hook cannot be resolved ahead of the instruction: \
214 {reason}"
215 ),
216 }
217 }
218}
219
220impl std::error::Error for HookError {}
221
222impl From<Unresolvable> for HookError {
223 fn from(reason: Unresolvable) -> Self {
224 Self::Unresolvable(reason)
225 }
226}
227
228#[derive(Clone, Debug, PartialEq, Eq)]
230pub(crate) struct Hook {
231 mint: Address,
232 program: Address,
233 validation: Address,
234 list: List,
235}
236
237#[derive(Clone, Debug, PartialEq, Eq)]
238enum List {
239 NotFetched,
240 Missing,
241 Loaded(Vec<u8>),
242}
243
244impl Hook {
245 pub(crate) fn load<'a>(
249 mint: &Address,
250 program: Option<Address>,
251 accounts: impl Fn(&Address) -> Fetched<'a>,
252 ) -> Option<Self> {
253 let program = program?;
254 let validation = find_validation_address(mint, &program);
255 let list = match accounts(&validation) {
256 Fetched::Account(account) if *account.owner == program => {
257 List::Loaded(account.data.to_vec())
258 }
259 Fetched::Account(_) | Fetched::Missing => List::Missing,
260 Fetched::NotFetched => List::NotFetched,
261 };
262 Some(Self {
263 mint: *mint,
264 program,
265 validation,
266 list,
267 })
268 }
269
270 fn list(&self) -> Option<&[u8]> {
272 match &self.list {
273 List::Loaded(data) => Some(data),
274 List::NotFetched | List::Missing => None,
275 }
276 }
277
278 fn extras(&self) -> Result<Vec<ExtraAccountMeta>, HookError> {
279 match &self.list {
280 List::NotFetched => {
281 Err(HookError::NotLoaded(vec![self.validation]))
282 }
283 List::Missing => Err(HookError::NoList),
284 List::Loaded(data) => Ok(screen_list(data)?),
285 }
286 }
287}
288
289#[derive(Clone, Debug, PartialEq, Eq)]
292pub(crate) struct MarketHooks {
293 base: Option<Hook>,
294 quote: Option<Hook>,
295}
296
297impl MarketHooks {
298 pub(crate) fn load(base: &MintInfo, quote: &MintInfo) -> Self {
302 Self {
303 base: Self::hook_of(base),
304 quote: Self::hook_of(quote),
305 }
306 }
307
308 fn hook_of(info: &MintInfo) -> Option<Hook> {
309 Hook::load(&info.address, info.hook_program, |address| {
310 info.fetched(address)
311 })
312 }
313
314 pub(crate) fn hooks(&self) -> Vec<&Hook> {
316 [&self.base, &self.quote].into_iter().flatten().collect()
317 }
318}
319
320pub(crate) fn resolve_market<'i, 'a: 'i>(
329 mints: TradeMints<'i>,
330 legs: &HookLegs,
331 cache: impl Fn(&Address) -> Fetched<'a>,
332) -> Result<Step<Resolved>, HookError> {
333 let hooks = MarketHooks::load(mints.base, mints.quote);
334 let hooks = hooks.hooks();
335 let lists: Vec<Address> = hooks
336 .iter()
337 .filter(|hook| {
338 hook.list == List::NotFetched
339 && legs.legs.iter().any(|leg| leg.mint == hook.mint)
340 })
341 .map(|hook| hook.validation)
342 .collect();
343 if !lists.is_empty() {
344 return Err(HookError::NotLoaded(lists));
345 }
346 resolve_legs(&hooks, legs, &|key| {
347 [mints.base, mints.quote]
348 .into_iter()
349 .map(|info| info.fetched(key))
350 .find(|fetched| !matches!(fetched, Fetched::NotFetched))
351 .unwrap_or_else(|| cache(key))
352 })
353}
354
355#[derive(Clone, Copy, Debug, PartialEq, Eq)]
357pub(crate) struct HookLeg {
358 pub source: Address,
359 pub mint: Address,
360 pub destination: Address,
361 pub authority: Address,
363 pub destination_owner: Address,
364}
365
366#[derive(Clone, Debug, Default, PartialEq, Eq)]
368pub(crate) struct HookLegs {
369 pub legs: Vec<HookLeg>,
370 pub written: Vec<Address>,
373}
374
375#[derive(Clone, Debug, PartialEq, Eq)]
377pub struct FetchedAccount {
378 pub owner: Address,
379 pub data: Vec<u8>,
380}
381
382#[derive(Clone, Debug, Default, PartialEq, Eq)]
394pub struct HookState {
395 pub accounts: BTreeMap<Address, Option<FetchedAccount>>,
398}
399
400impl HookState {
401 pub fn insert(
403 &mut self,
404 address: Address,
405 account: Option<FetchedAccount>,
406 ) {
407 self.accounts.insert(address, account);
408 }
409
410 #[must_use]
412 pub fn fetched(&self, address: &Address) -> Fetched<'_> {
413 match self.accounts.get(address) {
414 Some(Some(account)) => Fetched::Account(AccountView {
415 owner: &account.owner,
416 data: &account.data,
417 }),
418 Some(None) => Fetched::Missing,
419 None => Fetched::NotFetched,
420 }
421 }
422
423 pub fn missing(
440 &self,
441 mints: &[Address],
442 ) -> Result<Vec<Address>, HookError> {
443 let mut distinct: Vec<Address> = Vec::new();
444 for mint in mints {
445 if !distinct.contains(mint) {
446 distinct.push(*mint);
447 }
448 }
449 let unread: Vec<Address> = distinct
450 .iter()
451 .filter(|mint| !self.accounts.contains_key(mint))
452 .copied()
453 .collect();
454 if !unread.is_empty() {
455 return Ok(unread);
456 }
457 let mut hooks = Vec::new();
458 let mut fetch = Vec::new();
459 for mint in &distinct {
460 let Fetched::Account(account) = self.fetched(mint) else {
461 continue;
462 };
463 let state = MintState::load(account)
464 .map_err(|_| HookError::MalformedMint)?;
465 match Hook::load(mint, state.transfer_hook, |address| {
466 self.fetched(address)
467 }) {
468 Some(hook) if hook.list == List::NotFetched => {
469 fetch.push(hook.validation);
470 }
471 Some(hook) => hooks.push(hook),
472 None => {}
473 }
474 }
475 if !fetch.is_empty() {
476 return Ok(fetch);
477 }
478 for hook in &hooks {
479 for address in self.fixed_reads(hook) {
480 if !fetch.contains(&address) {
481 fetch.push(address);
482 }
483 }
484 }
485 Ok(fetch)
486 }
487
488 fn fixed_reads(&self, hook: &Hook) -> Vec<Address> {
491 let Ok(extras) = hook.extras() else {
492 return Vec::new();
493 };
494 let list = hook.list().unwrap_or_default();
495 let data = execute_data();
496 let mut fixed: Vec<Option<Address>> =
499 vec![None, Some(hook.mint), None, None, Some(hook.validation)];
500 let mut fetch = Vec::new();
501 for extra in &extras {
502 let mut bytes: Vec<Option<Vec<u8>>> = vec![None; fixed.len()];
503 let mut known = true;
504 for (index, _) in data_reads(extra) {
505 let address = fixed.get(index).copied().flatten();
507 let read = if index == VALIDATION_INDEX {
508 Some(list.to_vec())
509 } else {
510 address.and_then(|address| match self.fetched(&address) {
511 Fetched::Account(account) => {
512 Some(account.data.to_vec())
513 }
514 Fetched::Missing => Some(Vec::new()),
516 Fetched::NotFetched => {
517 if !fetch.contains(&address) {
518 fetch.push(address);
519 }
520 None
521 }
522 })
523 };
524 match (read, bytes.get_mut(index)) {
525 (Some(read), Some(slot)) => *slot = Some(read),
526 _ => known = false,
527 }
528 }
529 let keyed = key_reads(extra)
530 .into_iter()
531 .all(|index| fixed.get(index).copied().flatten().is_some());
532 let resolved = (known && keyed)
533 .then(|| {
534 extra
535 .resolve(&data, &hook.program, |index| {
536 fixed.get(index).and_then(Option::as_ref).map(
537 |key| {
538 (
539 key,
540 bytes
541 .get(index)
542 .and_then(|read| read.as_deref()),
543 )
544 },
545 )
546 })
547 .ok()
548 })
549 .flatten();
550 fixed.push(resolved.map(|meta| meta.pubkey));
551 }
552 fetch
553 }
554}
555
556#[derive(Clone, Debug, PartialEq, Eq)]
559pub(crate) struct LegExecute {
560 pub(crate) program: Address,
561 pub(crate) metas: Vec<AccountMeta>,
564 pub(crate) declared: Vec<(bool, bool)>,
566}
567
568impl LegExecute {
569 fn trailing(&self) -> impl Iterator<Item = AccountMeta> + '_ {
572 let validation = self
573 .metas
574 .get(VALIDATION_INDEX)
575 .map_or_else(Address::default, |meta| meta.pubkey);
576 self.metas.iter().skip(EXECUTE_ACCOUNTS).cloned().chain([
577 AccountMeta::new_readonly(self.program, false),
578 AccountMeta::new_readonly(validation, false),
579 ])
580 }
581}
582
583pub(crate) struct Resolved {
584 pub(crate) accounts: Vec<AccountMeta>,
585 pub(crate) executes: Vec<LegExecute>,
586 pub(crate) reads: Vec<Address>,
588}
589
590pub(crate) enum Step<T> {
591 Done(T),
592 Fetch(Vec<Address>),
593}
594
595fn moved_accounts(
597 legs: &HookLegs,
598) -> Vec<(Address, [u8; TOKEN_ACCOUNT_KNOWN_LEN])> {
599 let mut moved: Vec<(Address, [u8; TOKEN_ACCOUNT_KNOWN_LEN])> = Vec::new();
600 for leg in &legs.legs {
601 for (account, owner) in [
602 (leg.source, leg.authority),
603 (leg.destination, leg.destination_owner),
604 ] {
605 if !moved.iter().any(|(key, _)| *key == account) {
606 moved.push((account, token_account_prefix(&leg.mint, &owner)));
607 }
608 }
609 }
610 moved
611}
612
613fn token_account_prefix(
614 mint: &Address,
615 owner: &Address,
616) -> [u8; TOKEN_ACCOUNT_KNOWN_LEN] {
617 let mut prefix = [0; TOKEN_ACCOUNT_KNOWN_LEN];
618 let (mint_bytes, owner_bytes) = prefix.split_at_mut(PUBKEY_LEN);
619 mint_bytes.copy_from_slice(mint.as_ref());
620 owner_bytes.copy_from_slice(owner.as_ref());
621 prefix
622}
623
624pub(crate) fn resolve_legs<'a>(
646 hooks: &[&Hook],
647 legs: &HookLegs,
648 accounts: &impl Fn(&Address) -> Fetched<'a>,
649) -> Result<Step<Resolved>, HookError> {
650 let moved = moved_accounts(legs);
651 let mut executes = Vec::new();
652 let mut fetch: Vec<Address> = Vec::new();
653 let mut reader = Reader {
654 moved: &moved,
655 written: &legs.written,
656 accounts,
657 reads: Vec::new(),
658 };
659 for leg in &legs.legs {
660 let Some(hook) = hooks.iter().find(|hook| hook.mint == leg.mint) else {
661 continue;
662 };
663 match resolve_leg(hook, leg, &mut reader)? {
664 Step::Done(execute) => executes.push(execute),
665 Step::Fetch(addresses) => {
667 for address in addresses {
668 if !fetch.contains(&address) {
669 fetch.push(address);
670 }
671 }
672 }
673 }
674 }
675 if !fetch.is_empty() {
676 return Ok(Step::Fetch(fetch));
677 }
678 let mut merged: Vec<AccountMeta> = Vec::new();
679 for execute in &executes {
680 for meta in execute.trailing() {
681 match merged
682 .iter_mut()
683 .find(|account| account.pubkey == meta.pubkey)
684 {
685 Some(existing) => existing.is_writable |= meta.is_writable,
686 None => merged.push(meta),
687 }
688 }
689 }
690 Ok(Step::Done(Resolved {
691 accounts: merged,
692 executes,
693 reads: reader.reads,
694 }))
695}
696
697struct Reader<'r, F> {
699 moved: &'r [(Address, [u8; TOKEN_ACCOUNT_KNOWN_LEN])],
700 written: &'r [Address],
701 accounts: &'r F,
702 reads: Vec<Address>,
704}
705
706enum Data {
707 Bytes(Vec<u8>),
708 NotFetched,
709}
710
711impl<'a, F: Fn(&Address) -> Fetched<'a>> Reader<'_, F> {
712 fn read(
714 &mut self,
715 key: &Address,
716 end: usize,
717 ) -> Result<Data, Unresolvable> {
718 if let Some((_, prefix)) =
719 self.moved.iter().find(|(account, _)| account == key)
720 {
721 if end > TOKEN_ACCOUNT_KNOWN_LEN {
723 return Err(Unresolvable::ReadsSwapTimeData);
724 }
725 return Ok(Data::Bytes(prefix.to_vec()));
726 }
727 if self.written.contains(key) {
728 return Err(Unresolvable::ReadsSwapTimeData);
729 }
730 Ok(match (self.accounts)(key) {
731 Fetched::Account(account) => {
732 self.record(key);
733 Data::Bytes(account.data.to_vec())
734 }
735 Fetched::Missing => {
736 self.record(key);
737 Data::Bytes(Vec::new())
739 }
740 Fetched::NotFetched => Data::NotFetched,
741 })
742 }
743
744 fn record(&mut self, key: &Address) {
745 if !self.reads.contains(key) {
746 self.reads.push(*key);
747 }
748 }
749}
750
751fn resolve_leg<'a, F: Fn(&Address) -> Fetched<'a>>(
753 hook: &Hook,
754 leg: &HookLeg,
755 reader: &mut Reader<'_, F>,
756) -> Result<Step<LegExecute>, HookError> {
757 let extras = hook.extras()?;
758 let list = hook.list().unwrap_or_default();
759 let data = execute_data();
760
761 let mut metas: Vec<AccountMeta> = [
763 leg.source,
764 leg.mint,
765 leg.destination,
766 leg.authority,
767 hook.validation,
768 ]
769 .into_iter()
770 .map(|key| AccountMeta::new_readonly(key, false))
771 .collect();
772 let mut declared = Vec::with_capacity(extras.len());
773 for extra in &extras {
774 let mut bytes: Vec<Option<Vec<u8>>> = vec![None; metas.len()];
775 for (index, end) in data_reads(extra) {
776 let Some(slot) = bytes.get_mut(index) else {
777 return Err(Unresolvable::NamesUnresolvedAccount.into());
779 };
780 if index == VALIDATION_INDEX {
781 *slot = Some(list.to_vec());
782 continue;
783 }
784 let key =
785 metas.get(index).map(|meta| meta.pubkey).unwrap_or_default();
786 match reader.read(&key, end)? {
787 Data::Bytes(read) => *slot = Some(read),
788 Data::NotFetched => return Ok(Step::Fetch(vec![key])),
789 }
790 }
791 let resolved = extra
792 .resolve(&data, &hook.program, |index| {
793 metas.get(index).map(|meta| {
794 (
795 &meta.pubkey,
796 bytes.get(index).and_then(|read| read.as_deref()),
797 )
798 })
799 })
800 .map_err(|_| Unresolvable::ExtraUnresolved)?;
801 let earlier = metas
804 .iter()
805 .filter(|meta| meta.pubkey == resolved.pubkey)
806 .map(|meta| meta.is_writable)
807 .reduce(|any, writable| any || writable);
808 metas.push(AccountMeta {
809 pubkey: resolved.pubkey,
810 is_signer: false,
811 is_writable: resolved.is_writable && earlier != Some(false),
812 });
813 declared
814 .push((bool::from(extra.is_signer), bool::from(extra.is_writable)));
815 }
816 Ok(Step::Done(LegExecute {
817 program: hook.program,
818 metas,
819 declared,
820 }))
821}
822
823const fn execute_data() -> [u8; EXECUTE_DATA_LEN] {
826 let mut data = [0; EXECUTE_DATA_LEN];
827 let (discriminator, _) = data.split_at_mut(EXECUTE_DISCRIMINATOR.len());
828 discriminator.copy_from_slice(&EXECUTE_DISCRIMINATOR);
829 data
830}
831
832struct Execute;
833
834impl SplDiscriminate for Execute {
835 const SPL_DISCRIMINATOR: ArrayDiscriminator =
836 ArrayDiscriminator::new(EXECUTE_DISCRIMINATOR);
837}
838
839fn unpack_list(data: &[u8]) -> Result<Vec<ExtraAccountMeta>, Unresolvable> {
840 let list_error = |error: ProgramError| {
841 if error == ProgramError::from(TlvError::TypeNotFound) {
842 Unresolvable::NoExecuteEntry
843 } else {
844 Unresolvable::MalformedList
845 }
846 };
847 let state = TlvStateBorrowed::unpack(data).map_err(list_error)?;
848 let extras = ExtraAccountMetaList::unpack_with_tlv_state::<Execute>(&state)
849 .map_err(list_error)?;
850 Ok(extras.iter().copied().collect())
851}
852
853fn screen_list(data: &[u8]) -> Result<Vec<ExtraAccountMeta>, Unresolvable> {
856 let extras = unpack_list(data)?;
857 for (index, extra) in extras.iter().enumerate() {
858 let position = index
859 .checked_add(EXECUTE_ACCOUNTS)
860 .filter(|position| u8::try_from(*position).is_ok())
861 .ok_or(Unresolvable::ListTooLong)?;
862 screen_extra(extra, position)?;
863 }
864 if reads_amount(&extras) {
865 return Err(Unresolvable::ReadsTransferAmount);
866 }
867 Ok(extras)
868}
869
870fn screen_extra(
871 extra: &ExtraAccountMeta,
872 position: usize,
873) -> Result<(), Unresolvable> {
874 match extra.discriminator {
875 META_FIXED => Ok(()),
876 META_HOOK_PDA => screen_seeds(&extra.address_config, position),
877 META_PUBKEY_DATA => match PubkeyData::unpack(&extra.address_config)
878 .map_err(|_| Unresolvable::MalformedExtra)?
879 {
880 PubkeyData::AccountData { account_index, .. } => {
881 prior(account_index, position)
882 }
883 PubkeyData::InstructionData { .. } => {
885 Err(Unresolvable::ReadsPastInstructionData)
886 }
887 PubkeyData::Uninitialized => Err(Unresolvable::MalformedExtra),
888 },
889 external => match external.checked_sub(META_EXTERNAL_PDA) {
890 Some(program) => {
891 prior(program, position)?;
892 screen_seeds(&extra.address_config, position)
893 }
894 None => Err(Unresolvable::MalformedExtra),
895 },
896 }
897}
898
899fn seeds_of(extra: &ExtraAccountMeta) -> Vec<Seed> {
900 match extra.discriminator {
901 META_FIXED | META_PUBKEY_DATA => Vec::new(),
902 _ => Seed::unpack_address_config(&extra.address_config)
903 .unwrap_or_default(),
904 }
905}
906
907fn screen_seeds(
908 config: &[u8; 32],
909 position: usize,
910) -> Result<(), Unresolvable> {
911 let seeds = Seed::unpack_address_config(config)
912 .map_err(|_| Unresolvable::MalformedSeed)?;
913 if seeds.len() >= MAX_SEEDS {
915 return Err(Unresolvable::TooManySeeds);
916 }
917 for seed in &seeds {
918 match *seed {
919 Seed::Uninitialized | Seed::Literal { .. } => {}
921 Seed::InstructionData { index, length } => {
922 let end = usize::from(index)
923 .checked_add(usize::from(length))
924 .ok_or(Unresolvable::MalformedSeed)?;
925 if end > EXECUTE_DATA_LEN {
926 return Err(Unresolvable::ReadsPastInstructionData);
927 }
928 }
929 Seed::AccountKey { index } => prior(index, position)?,
930 Seed::AccountData {
931 account_index,
932 length,
933 ..
934 } => {
935 if usize::from(length) > PUBKEY_LEN {
936 return Err(Unresolvable::SeedTooLong);
937 }
938 prior(account_index, position)?;
939 }
940 }
941 }
942 Ok(())
943}
944
945fn prior(index: u8, position: usize) -> Result<(), Unresolvable> {
947 if usize::from(index) < position {
948 Ok(())
949 } else {
950 Err(Unresolvable::NamesUnresolvedAccount)
951 }
952}
953
954fn reads_amount(extras: &[ExtraAccountMeta]) -> bool {
956 extras.iter().flat_map(seeds_of).any(|seed| match seed {
957 Seed::InstructionData { index, length } => {
958 length > 0 && read_end(index, length) > EXECUTE_DISCRIMINATOR.len()
959 }
960 _ => false,
961 })
962}
963
964#[expect(
966 clippy::arithmetic_side_effects,
967 reason = "two u8s cannot overflow a u16"
968)]
969fn read_end(offset: u8, length: u8) -> usize {
970 usize::from(u16::from(offset) + u16::from(length))
971}
972
973fn data_reads(extra: &ExtraAccountMeta) -> Vec<(usize, usize)> {
975 if extra.discriminator == META_PUBKEY_DATA {
976 return match PubkeyData::unpack(&extra.address_config) {
977 Ok(PubkeyData::AccountData {
978 account_index,
979 data_index,
980 }) => vec![(
981 usize::from(account_index),
982 read_end(data_index, 32),
984 )],
985 _ => Vec::new(),
986 };
987 }
988 seeds_of(extra)
989 .into_iter()
990 .filter_map(|seed| match seed {
991 Seed::AccountData {
992 account_index,
993 data_index,
994 length,
995 } => {
996 Some((usize::from(account_index), read_end(data_index, length)))
997 }
998 _ => None,
999 })
1000 .collect()
1001}
1002
1003fn key_reads(extra: &ExtraAccountMeta) -> Vec<usize> {
1006 let program = extra
1007 .discriminator
1008 .checked_sub(META_EXTERNAL_PDA)
1009 .map(usize::from);
1010 program
1011 .into_iter()
1012 .chain(seeds_of(extra).into_iter().filter_map(|seed| match seed {
1013 Seed::AccountKey { index } => Some(usize::from(index)),
1014 _ => None,
1015 }))
1016 .collect()
1017}
1018
1019#[cfg(test)]
1020mod tests;