1use hopper_runtime::{error::ProgramError, AccountView, Address, ProgramResult};
45
46pub type ValidateFn = fn(ctx: &ValidationContext) -> ProgramResult;
53
54pub struct ValidationContext<'a> {
56 pub program_id: &'a Address,
58 pub accounts: &'a [AccountView],
60 pub data: &'a [u8],
62}
63
64impl<'a> ValidationContext<'a> {
65 #[inline(always)]
67 pub fn new(program_id: &'a Address, accounts: &'a [AccountView], data: &'a [u8]) -> Self {
68 Self {
69 program_id,
70 accounts,
71 data,
72 }
73 }
74
75 #[inline(always)]
77 pub fn account(&self, index: usize) -> Result<&'a AccountView, ProgramError> {
78 self.accounts
79 .get(index)
80 .ok_or(ProgramError::NotEnoughAccountKeys)
81 }
82
83 #[inline(always)]
85 pub fn require_all_unique_accounts(&self) -> ProgramResult {
86 crate::check::guards::require_all_unique(self.accounts)
87 }
88
89 #[inline(always)]
91 pub fn require_unique_writable_accounts(&self) -> ProgramResult {
92 crate::check::guards::require_unique_writable(self.accounts)
93 }
94
95 #[inline(always)]
97 pub fn require_unique_signer_accounts(&self) -> ProgramResult {
98 crate::check::guards::require_unique_signers(self.accounts)
99 }
100}
101
102pub struct ValidationGraph<const N: usize> {
110 nodes: [Option<ValidateFn>; N],
111 count: usize,
112}
113
114impl<const N: usize> ValidationGraph<N> {
115 #[inline(always)]
117 pub fn new() -> Self {
118 Self {
119 nodes: [None; N],
120 count: 0,
121 }
122 }
123
124 #[inline]
126 pub fn add(&mut self, node: ValidateFn) -> Result<(), ProgramError> {
127 if self.count >= N {
128 return Err(ProgramError::InvalidArgument);
129 }
130 self.nodes[self.count] = Some(node);
131 self.count += 1;
132 Ok(())
133 }
134
135 #[inline(always)]
137 pub fn len(&self) -> usize {
138 self.count
139 }
140
141 #[inline(always)]
143 pub fn is_empty(&self) -> bool {
144 self.count == 0
145 }
146
147 #[inline]
149 pub fn run(&self, ctx: &ValidationContext) -> ProgramResult {
150 let mut i = 0;
151 while i < self.count {
152 if let Some(node) = self.nodes[i] {
153 node(ctx)?;
154 }
155 i += 1;
156 }
157 Ok(())
158 }
159
160 #[inline]
162 pub fn run_all(&self, ctx: &ValidationContext) -> ProgramResult {
163 let mut first_error: Option<ProgramError> = None;
164 let mut i = 0;
165 while i < self.count {
166 if let Some(node) = self.nodes[i] {
167 if let Err(e) = node(ctx) {
168 if first_error.is_none() {
169 first_error = Some(e);
170 }
171 }
172 }
173 i += 1;
174 }
175 match first_error {
176 Some(e) => Err(e),
177 None => Ok(()),
178 }
179 }
180}
181
182#[inline(always)]
186pub fn require_signer_at(index: usize) -> impl Fn(&ValidationContext) -> ProgramResult {
187 move |ctx| {
188 let acc = ctx.account(index)?;
189 crate::check::check_signer(acc)
190 }
191}
192
193#[inline(always)]
195pub fn require_writable_at(index: usize) -> impl Fn(&ValidationContext) -> ProgramResult {
196 move |ctx| {
197 let acc = ctx.account(index)?;
198 crate::check::check_writable(acc)
199 }
200}
201
202#[inline(always)]
204pub fn require_owned_at(index: usize) -> impl Fn(&ValidationContext) -> ProgramResult {
205 move |ctx| {
206 let acc = ctx.account(index)?;
207 crate::check::check_owner(acc, ctx.program_id)
208 }
209}
210
211#[inline(always)]
213pub fn require_data_min(min: usize) -> impl Fn(&ValidationContext) -> ProgramResult {
214 move |ctx| {
215 if ctx.data.len() < min {
216 Err(ProgramError::InvalidInstructionData)
217 } else {
218 Ok(())
219 }
220 }
221}
222
223#[inline(always)]
225pub fn require_keys_equal(a: usize, b: usize) -> impl Fn(&ValidationContext) -> ProgramResult {
226 move |ctx| {
227 let acc_a = ctx.account(a)?;
228 let acc_b = ctx.account(b)?;
229 crate::check::check_keys_eq(acc_a, acc_b)
230 }
231}
232
233#[inline(always)]
235pub fn require_unique(a: usize, b: usize) -> impl Fn(&ValidationContext) -> ProgramResult {
236 move |ctx| {
237 let acc_a = ctx.account(a)?;
238 let acc_b = ctx.account(b)?;
239 crate::check::check_accounts_unique(acc_a, acc_b)
240 }
241}
242
243#[inline(always)]
245pub fn require_all_unique_accounts() -> impl Fn(&ValidationContext) -> ProgramResult {
246 move |ctx| ctx.require_all_unique_accounts()
247}
248
249#[inline(always)]
251pub fn require_unique_writable_accounts() -> impl Fn(&ValidationContext) -> ProgramResult {
252 move |ctx| ctx.require_unique_writable_accounts()
253}
254
255#[inline(always)]
257pub fn require_unique_signer_accounts() -> impl Fn(&ValidationContext) -> ProgramResult {
258 move |ctx| ctx.require_unique_signer_accounts()
259}
260
261#[inline(always)]
263pub fn require_lamports_gte(
264 index: usize,
265 min: u64,
266) -> impl Fn(&ValidationContext) -> ProgramResult {
267 move |ctx| {
268 let acc = ctx.account(index)?;
269 crate::check::check_lamports_gte(acc, min)
270 }
271}
272
273pub struct AccountConstraint {
285 index: usize,
286 require_signer: bool,
287 require_writable: bool,
288 require_owned: bool,
289 require_executable: bool,
290}
291
292impl AccountConstraint {
293 #[inline(always)]
295 pub const fn on(index: usize) -> Self {
296 Self {
297 index,
298 require_signer: false,
299 require_writable: false,
300 require_owned: false,
301 require_executable: false,
302 }
303 }
304
305 #[inline(always)]
307 pub const fn signer(mut self) -> Self {
308 self.require_signer = true;
309 self
310 }
311
312 #[inline(always)]
314 pub const fn writable(mut self) -> Self {
315 self.require_writable = true;
316 self
317 }
318
319 #[inline(always)]
321 pub const fn owned(mut self) -> Self {
322 self.require_owned = true;
323 self
324 }
325
326 #[inline(always)]
328 pub const fn executable(mut self) -> Self {
329 self.require_executable = true;
330 self
331 }
332
333 #[inline]
335 pub fn validate(&self, ctx: &ValidationContext) -> ProgramResult {
336 let acc = ctx.account(self.index)?;
337
338 if self.require_signer {
339 crate::check::check_signer(acc)?;
340 }
341 if self.require_writable {
342 crate::check::check_writable(acc)?;
343 }
344 if self.require_owned {
345 crate::check::check_owner(acc, ctx.program_id)?;
346 }
347 if self.require_executable {
348 crate::check::check_executable(acc)?;
349 }
350
351 Ok(())
352 }
353}
354
355pub struct TransactionConstraint {
359 min_accounts: usize,
360 min_data_len: usize,
361 require_all_unique: bool,
362 require_unique_writable: bool,
363 require_unique_signers: bool,
364}
365
366impl TransactionConstraint {
367 #[inline(always)]
369 pub const fn new() -> Self {
370 Self {
371 min_accounts: 0,
372 min_data_len: 0,
373 require_all_unique: false,
374 require_unique_writable: false,
375 require_unique_signers: false,
376 }
377 }
378
379 #[inline(always)]
381 pub const fn min_accounts(mut self, n: usize) -> Self {
382 self.min_accounts = n;
383 self
384 }
385
386 #[inline(always)]
388 pub const fn min_data(mut self, n: usize) -> Self {
389 self.min_data_len = n;
390 self
391 }
392
393 #[inline(always)]
395 pub const fn all_unique(mut self) -> Self {
396 self.require_all_unique = true;
397 self
398 }
399
400 #[inline(always)]
402 pub const fn unique_writable(mut self) -> Self {
403 self.require_unique_writable = true;
404 self
405 }
406
407 #[inline(always)]
409 pub const fn unique_signers(mut self) -> Self {
410 self.require_unique_signers = true;
411 self
412 }
413
414 #[inline]
416 pub fn validate(&self, ctx: &ValidationContext) -> ProgramResult {
417 if ctx.accounts.len() < self.min_accounts {
418 return Err(ProgramError::NotEnoughAccountKeys);
419 }
420 if ctx.data.len() < self.min_data_len {
421 return Err(ProgramError::InvalidInstructionData);
422 }
423 if self.require_all_unique {
424 ctx.require_all_unique_accounts()?;
425 }
426 if self.require_unique_writable {
427 ctx.require_unique_writable_accounts()?;
428 }
429 if self.require_unique_signers {
430 ctx.require_unique_signer_accounts()?;
431 }
432 Ok(())
433 }
434}
435
436pub struct ValidationGroup<const N: usize> {
452 name: &'static str,
453 rules: [Option<ValidateFn>; N],
454 count: usize,
455}
456
457impl<const N: usize> ValidationGroup<N> {
458 #[inline(always)]
460 pub const fn new(name: &'static str) -> Self {
461 Self {
462 name,
463 rules: [None; N],
464 count: 0,
465 }
466 }
467
468 #[inline(always)]
470 pub const fn name(&self) -> &'static str {
471 self.name
472 }
473
474 #[inline]
476 pub fn add(&mut self, rule: ValidateFn) -> Result<(), ProgramError> {
477 if self.count >= N {
478 return Err(ProgramError::InvalidArgument);
479 }
480 self.rules[self.count] = Some(rule);
481 self.count += 1;
482 Ok(())
483 }
484
485 #[inline(always)]
487 pub fn len(&self) -> usize {
488 self.count
489 }
490
491 #[inline(always)]
493 pub fn is_empty(&self) -> bool {
494 self.count == 0
495 }
496
497 #[inline]
499 pub fn run(&self, ctx: &ValidationContext) -> ProgramResult {
500 let mut i = 0;
501 while i < self.count {
502 if let Some(rule) = self.rules[i] {
503 rule(ctx)?;
504 }
505 i += 1;
506 }
507 Ok(())
508 }
509}
510
511pub struct ValidationBundle<'a, const N: usize> {
526 groups: [Option<&'a dyn Validatable>; N],
527 count: usize,
528}
529
530pub trait Validatable {
532 fn validate(&self, ctx: &ValidationContext) -> ProgramResult;
534}
535
536impl<const M: usize> Validatable for ValidationGraph<M> {
537 #[inline]
538 fn validate(&self, ctx: &ValidationContext) -> ProgramResult {
539 self.run(ctx)
540 }
541}
542
543impl<const M: usize> Validatable for ValidationGroup<M> {
544 #[inline]
545 fn validate(&self, ctx: &ValidationContext) -> ProgramResult {
546 self.run(ctx)
547 }
548}
549
550impl Validatable for TransactionConstraint {
551 #[inline]
552 fn validate(&self, ctx: &ValidationContext) -> ProgramResult {
553 TransactionConstraint::validate(self, ctx)
554 }
555}
556
557impl<'a, const N: usize> ValidationBundle<'a, N> {
558 #[inline(always)]
560 pub const fn new() -> Self {
561 Self {
562 groups: [None; N],
563 count: 0,
564 }
565 }
566
567 #[inline]
569 pub fn add(&mut self, v: &'a dyn Validatable) -> Result<(), ProgramError> {
570 if self.count >= N {
571 return Err(ProgramError::InvalidArgument);
572 }
573 self.groups[self.count] = Some(v);
574 self.count += 1;
575 Ok(())
576 }
577
578 #[inline]
580 pub fn run(&self, ctx: &ValidationContext) -> ProgramResult {
581 let mut i = 0;
582 while i < self.count {
583 if let Some(v) = self.groups[i] {
584 v.validate(ctx)?;
585 }
586 i += 1;
587 }
588 Ok(())
589 }
590}
591
592pub type PostMutationFn = fn(accounts: &[AccountView], program_id: &Address) -> ProgramResult;
599
600pub struct PostMutationValidator<const N: usize> {
615 checks: [Option<PostMutationFn>; N],
616 count: usize,
617}
618
619impl<const N: usize> PostMutationValidator<N> {
620 #[inline(always)]
622 pub const fn new() -> Self {
623 Self {
624 checks: [None; N],
625 count: 0,
626 }
627 }
628
629 #[inline]
631 pub fn add(&mut self, check: PostMutationFn) -> Result<(), ProgramError> {
632 if self.count >= N {
633 return Err(ProgramError::InvalidArgument);
634 }
635 self.checks[self.count] = Some(check);
636 self.count += 1;
637 Ok(())
638 }
639
640 #[inline(always)]
642 pub fn len(&self) -> usize {
643 self.count
644 }
645
646 #[inline(always)]
648 pub fn is_empty(&self) -> bool {
649 self.count == 0
650 }
651
652 #[inline]
654 pub fn run(&self, accounts: &[AccountView], program_id: &Address) -> ProgramResult {
655 let mut i = 0;
656 while i < self.count {
657 if let Some(check) = self.checks[i] {
658 check(accounts, program_id)?;
659 }
660 i += 1;
661 }
662 Ok(())
663 }
664}
665
666pub type InstructionTag = u8;
670
671#[derive(Clone, Copy)]
673struct TransitionRuleEntry {
674 tag: InstructionTag,
675 rule: ValidateFn,
676}
677
678pub struct TransitionRulePack<const N: usize> {
694 entries: [Option<TransitionRuleEntry>; N],
695 count: usize,
696}
697
698impl<const N: usize> TransitionRulePack<N> {
699 #[inline(always)]
701 pub const fn new() -> Self {
702 Self {
703 entries: [None; N],
704 count: 0,
705 }
706 }
707
708 #[inline]
710 pub fn add(&mut self, tag: InstructionTag, rule: ValidateFn) -> Result<(), ProgramError> {
711 if self.count >= N {
712 return Err(ProgramError::InvalidArgument);
713 }
714 self.entries[self.count] = Some(TransitionRuleEntry { tag, rule });
715 self.count += 1;
716 Ok(())
717 }
718
719 #[inline]
721 pub fn run_for(&self, tag: InstructionTag, ctx: &ValidationContext) -> ProgramResult {
722 let mut i = 0;
723 while i < self.count {
724 if let Some(entry) = &self.entries[i] {
725 if entry.tag == tag {
726 (entry.rule)(ctx)?;
727 }
728 }
729 i += 1;
730 }
731 Ok(())
732 }
733
734 #[inline(always)]
736 pub fn len(&self) -> usize {
737 self.count
738 }
739
740 #[inline(always)]
742 pub fn is_empty(&self) -> bool {
743 self.count == 0
744 }
745}
746
747impl<const N: usize> Default for ValidationGraph<N> {
750 fn default() -> Self {
751 Self::new()
752 }
753}
754
755impl Default for TransactionConstraint {
756 fn default() -> Self {
757 Self::new()
758 }
759}
760
761impl<'a, const N: usize> Default for ValidationBundle<'a, N> {
762 fn default() -> Self {
763 Self::new()
764 }
765}
766
767impl<const N: usize> Default for PostMutationValidator<N> {
768 fn default() -> Self {
769 Self::new()
770 }
771}
772
773impl<const N: usize> Default for TransitionRulePack<N> {
774 fn default() -> Self {
775 Self::new()
776 }
777}