use hopper_runtime::{error::ProgramError, AccountView, Address, ProgramResult};
pub type ValidateFn = fn(ctx: &ValidationContext) -> ProgramResult;
pub struct ValidationContext<'a> {
pub program_id: &'a Address,
pub accounts: &'a [AccountView],
pub data: &'a [u8],
}
impl<'a> ValidationContext<'a> {
#[inline(always)]
pub fn new(program_id: &'a Address, accounts: &'a [AccountView], data: &'a [u8]) -> Self {
Self {
program_id,
accounts,
data,
}
}
#[inline(always)]
pub fn account(&self, index: usize) -> Result<&'a AccountView, ProgramError> {
self.accounts
.get(index)
.ok_or(ProgramError::NotEnoughAccountKeys)
}
#[inline(always)]
pub fn require_all_unique_accounts(&self) -> ProgramResult {
crate::check::guards::require_all_unique(self.accounts)
}
#[inline(always)]
pub fn require_unique_writable_accounts(&self) -> ProgramResult {
crate::check::guards::require_unique_writable(self.accounts)
}
#[inline(always)]
pub fn require_unique_signer_accounts(&self) -> ProgramResult {
crate::check::guards::require_unique_signers(self.accounts)
}
}
pub struct ValidationGraph<const N: usize> {
nodes: [Option<ValidateFn>; N],
count: usize,
}
impl<const N: usize> ValidationGraph<N> {
#[inline(always)]
pub fn new() -> Self {
Self {
nodes: [None; N],
count: 0,
}
}
#[inline]
pub fn add(&mut self, node: ValidateFn) -> Result<(), ProgramError> {
if self.count >= N {
return Err(ProgramError::InvalidArgument);
}
self.nodes[self.count] = Some(node);
self.count += 1;
Ok(())
}
#[inline(always)]
pub fn len(&self) -> usize {
self.count
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.count == 0
}
#[inline]
pub fn run(&self, ctx: &ValidationContext) -> ProgramResult {
let mut i = 0;
while i < self.count {
if let Some(node) = self.nodes[i] {
node(ctx)?;
}
i += 1;
}
Ok(())
}
#[inline]
pub fn run_all(&self, ctx: &ValidationContext) -> ProgramResult {
let mut first_error: Option<ProgramError> = None;
let mut i = 0;
while i < self.count {
if let Some(node) = self.nodes[i] {
if let Err(e) = node(ctx) {
if first_error.is_none() {
first_error = Some(e);
}
}
}
i += 1;
}
match first_error {
Some(e) => Err(e),
None => Ok(()),
}
}
}
#[inline(always)]
pub fn require_signer_at(index: usize) -> impl Fn(&ValidationContext) -> ProgramResult {
move |ctx| {
let acc = ctx.account(index)?;
crate::check::check_signer(acc)
}
}
#[inline(always)]
pub fn require_writable_at(index: usize) -> impl Fn(&ValidationContext) -> ProgramResult {
move |ctx| {
let acc = ctx.account(index)?;
crate::check::check_writable(acc)
}
}
#[inline(always)]
pub fn require_owned_at(index: usize) -> impl Fn(&ValidationContext) -> ProgramResult {
move |ctx| {
let acc = ctx.account(index)?;
crate::check::check_owner(acc, ctx.program_id)
}
}
#[inline(always)]
pub fn require_data_min(min: usize) -> impl Fn(&ValidationContext) -> ProgramResult {
move |ctx| {
if ctx.data.len() < min {
Err(ProgramError::InvalidInstructionData)
} else {
Ok(())
}
}
}
#[inline(always)]
pub fn require_keys_equal(a: usize, b: usize) -> impl Fn(&ValidationContext) -> ProgramResult {
move |ctx| {
let acc_a = ctx.account(a)?;
let acc_b = ctx.account(b)?;
crate::check::check_keys_eq(acc_a, acc_b)
}
}
#[inline(always)]
pub fn require_unique(a: usize, b: usize) -> impl Fn(&ValidationContext) -> ProgramResult {
move |ctx| {
let acc_a = ctx.account(a)?;
let acc_b = ctx.account(b)?;
crate::check::check_accounts_unique(acc_a, acc_b)
}
}
#[inline(always)]
pub fn require_all_unique_accounts() -> impl Fn(&ValidationContext) -> ProgramResult {
move |ctx| ctx.require_all_unique_accounts()
}
#[inline(always)]
pub fn require_unique_writable_accounts() -> impl Fn(&ValidationContext) -> ProgramResult {
move |ctx| ctx.require_unique_writable_accounts()
}
#[inline(always)]
pub fn require_unique_signer_accounts() -> impl Fn(&ValidationContext) -> ProgramResult {
move |ctx| ctx.require_unique_signer_accounts()
}
#[inline(always)]
pub fn require_lamports_gte(
index: usize,
min: u64,
) -> impl Fn(&ValidationContext) -> ProgramResult {
move |ctx| {
let acc = ctx.account(index)?;
crate::check::check_lamports_gte(acc, min)
}
}
pub struct AccountConstraint {
index: usize,
require_signer: bool,
require_writable: bool,
require_owned: bool,
require_executable: bool,
}
impl AccountConstraint {
#[inline(always)]
pub const fn on(index: usize) -> Self {
Self {
index,
require_signer: false,
require_writable: false,
require_owned: false,
require_executable: false,
}
}
#[inline(always)]
pub const fn signer(mut self) -> Self {
self.require_signer = true;
self
}
#[inline(always)]
pub const fn writable(mut self) -> Self {
self.require_writable = true;
self
}
#[inline(always)]
pub const fn owned(mut self) -> Self {
self.require_owned = true;
self
}
#[inline(always)]
pub const fn executable(mut self) -> Self {
self.require_executable = true;
self
}
#[inline]
pub fn validate(&self, ctx: &ValidationContext) -> ProgramResult {
let acc = ctx.account(self.index)?;
if self.require_signer {
crate::check::check_signer(acc)?;
}
if self.require_writable {
crate::check::check_writable(acc)?;
}
if self.require_owned {
crate::check::check_owner(acc, ctx.program_id)?;
}
if self.require_executable {
crate::check::check_executable(acc)?;
}
Ok(())
}
}
pub struct TransactionConstraint {
min_accounts: usize,
min_data_len: usize,
require_all_unique: bool,
require_unique_writable: bool,
require_unique_signers: bool,
}
impl TransactionConstraint {
#[inline(always)]
pub const fn new() -> Self {
Self {
min_accounts: 0,
min_data_len: 0,
require_all_unique: false,
require_unique_writable: false,
require_unique_signers: false,
}
}
#[inline(always)]
pub const fn min_accounts(mut self, n: usize) -> Self {
self.min_accounts = n;
self
}
#[inline(always)]
pub const fn min_data(mut self, n: usize) -> Self {
self.min_data_len = n;
self
}
#[inline(always)]
pub const fn all_unique(mut self) -> Self {
self.require_all_unique = true;
self
}
#[inline(always)]
pub const fn unique_writable(mut self) -> Self {
self.require_unique_writable = true;
self
}
#[inline(always)]
pub const fn unique_signers(mut self) -> Self {
self.require_unique_signers = true;
self
}
#[inline]
pub fn validate(&self, ctx: &ValidationContext) -> ProgramResult {
if ctx.accounts.len() < self.min_accounts {
return Err(ProgramError::NotEnoughAccountKeys);
}
if ctx.data.len() < self.min_data_len {
return Err(ProgramError::InvalidInstructionData);
}
if self.require_all_unique {
ctx.require_all_unique_accounts()?;
}
if self.require_unique_writable {
ctx.require_unique_writable_accounts()?;
}
if self.require_unique_signers {
ctx.require_unique_signer_accounts()?;
}
Ok(())
}
}
pub struct ValidationGroup<const N: usize> {
name: &'static str,
rules: [Option<ValidateFn>; N],
count: usize,
}
impl<const N: usize> ValidationGroup<N> {
#[inline(always)]
pub const fn new(name: &'static str) -> Self {
Self {
name,
rules: [None; N],
count: 0,
}
}
#[inline(always)]
pub const fn name(&self) -> &'static str {
self.name
}
#[inline]
pub fn add(&mut self, rule: ValidateFn) -> Result<(), ProgramError> {
if self.count >= N {
return Err(ProgramError::InvalidArgument);
}
self.rules[self.count] = Some(rule);
self.count += 1;
Ok(())
}
#[inline(always)]
pub fn len(&self) -> usize {
self.count
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.count == 0
}
#[inline]
pub fn run(&self, ctx: &ValidationContext) -> ProgramResult {
let mut i = 0;
while i < self.count {
if let Some(rule) = self.rules[i] {
rule(ctx)?;
}
i += 1;
}
Ok(())
}
}
pub struct ValidationBundle<'a, const N: usize> {
groups: [Option<&'a dyn Validatable>; N],
count: usize,
}
pub trait Validatable {
fn validate(&self, ctx: &ValidationContext) -> ProgramResult;
}
impl<const M: usize> Validatable for ValidationGraph<M> {
#[inline]
fn validate(&self, ctx: &ValidationContext) -> ProgramResult {
self.run(ctx)
}
}
impl<const M: usize> Validatable for ValidationGroup<M> {
#[inline]
fn validate(&self, ctx: &ValidationContext) -> ProgramResult {
self.run(ctx)
}
}
impl Validatable for TransactionConstraint {
#[inline]
fn validate(&self, ctx: &ValidationContext) -> ProgramResult {
TransactionConstraint::validate(self, ctx)
}
}
impl<'a, const N: usize> ValidationBundle<'a, N> {
#[inline(always)]
pub const fn new() -> Self {
Self {
groups: [None; N],
count: 0,
}
}
#[inline]
pub fn add(&mut self, v: &'a dyn Validatable) -> Result<(), ProgramError> {
if self.count >= N {
return Err(ProgramError::InvalidArgument);
}
self.groups[self.count] = Some(v);
self.count += 1;
Ok(())
}
#[inline]
pub fn run(&self, ctx: &ValidationContext) -> ProgramResult {
let mut i = 0;
while i < self.count {
if let Some(v) = self.groups[i] {
v.validate(ctx)?;
}
i += 1;
}
Ok(())
}
}
pub type PostMutationFn = fn(accounts: &[AccountView], program_id: &Address) -> ProgramResult;
pub struct PostMutationValidator<const N: usize> {
checks: [Option<PostMutationFn>; N],
count: usize,
}
impl<const N: usize> PostMutationValidator<N> {
#[inline(always)]
pub const fn new() -> Self {
Self {
checks: [None; N],
count: 0,
}
}
#[inline]
pub fn add(&mut self, check: PostMutationFn) -> Result<(), ProgramError> {
if self.count >= N {
return Err(ProgramError::InvalidArgument);
}
self.checks[self.count] = Some(check);
self.count += 1;
Ok(())
}
#[inline(always)]
pub fn len(&self) -> usize {
self.count
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.count == 0
}
#[inline]
pub fn run(&self, accounts: &[AccountView], program_id: &Address) -> ProgramResult {
let mut i = 0;
while i < self.count {
if let Some(check) = self.checks[i] {
check(accounts, program_id)?;
}
i += 1;
}
Ok(())
}
}
pub type InstructionTag = u8;
#[derive(Clone, Copy)]
struct TransitionRuleEntry {
tag: InstructionTag,
rule: ValidateFn,
}
pub struct TransitionRulePack<const N: usize> {
entries: [Option<TransitionRuleEntry>; N],
count: usize,
}
impl<const N: usize> TransitionRulePack<N> {
#[inline(always)]
pub const fn new() -> Self {
Self {
entries: [None; N],
count: 0,
}
}
#[inline]
pub fn add(&mut self, tag: InstructionTag, rule: ValidateFn) -> Result<(), ProgramError> {
if self.count >= N {
return Err(ProgramError::InvalidArgument);
}
self.entries[self.count] = Some(TransitionRuleEntry { tag, rule });
self.count += 1;
Ok(())
}
#[inline]
pub fn run_for(&self, tag: InstructionTag, ctx: &ValidationContext) -> ProgramResult {
let mut i = 0;
while i < self.count {
if let Some(entry) = &self.entries[i] {
if entry.tag == tag {
(entry.rule)(ctx)?;
}
}
i += 1;
}
Ok(())
}
#[inline(always)]
pub fn len(&self) -> usize {
self.count
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.count == 0
}
}
impl<const N: usize> Default for ValidationGraph<N> {
fn default() -> Self {
Self::new()
}
}
impl Default for TransactionConstraint {
fn default() -> Self {
Self::new()
}
}
impl<'a, const N: usize> Default for ValidationBundle<'a, N> {
fn default() -> Self {
Self::new()
}
}
impl<const N: usize> Default for PostMutationValidator<N> {
fn default() -> Self {
Self::new()
}
}
impl<const N: usize> Default for TransitionRulePack<N> {
fn default() -> Self {
Self::new()
}
}