use crate::address::Address;
use crate::error::ProgramError;
use crate::ProgramResult;
mod exact_cell_selector_sealed {
pub trait Sealed {}
impl Sealed for u8 {}
impl Sealed for u16 {}
impl Sealed for u32 {}
}
pub trait ExactCellSelector: exact_cell_selector_sealed::Sealed + Copy {
const WIRE_SIZE: u16;
fn to_u32(self) -> u32;
}
impl ExactCellSelector for u8 {
const WIRE_SIZE: u16 = 1;
#[inline(always)]
fn to_u32(self) -> u32 {
self as u32
}
}
impl ExactCellSelector for u16 {
const WIRE_SIZE: u16 = 2;
#[inline(always)]
fn to_u32(self) -> u32 {
self as u32
}
}
impl ExactCellSelector for u32 {
const WIRE_SIZE: u16 = 4;
#[inline(always)]
fn to_u32(self) -> u32 {
self
}
}
pub const WRITE_POLICY_VIOLATION_PAGE: u32 = 0xD0_00;
#[inline(always)]
pub const fn write_policy_violation(account_index: u8) -> ProgramError {
ProgramError::Custom(WRITE_POLICY_VIOLATION_PAGE | account_index as u32)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WriteRange {
pub account_index: u8,
pub offset: u32,
pub size: u32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ParametricWriteRange {
pub account_index: u8,
pub base_offset: u32,
pub stride: u32,
pub cell_size: u32,
pub count: u32,
pub argument_index: u8,
pub argument_name: &'static str,
pub segment_name: &'static str,
}
impl ParametricWriteRange {
#[inline(always)]
#[expect(
clippy::too_many_arguments,
reason = "the constructor mirrors the eight independent manifest selector fields"
)]
pub const fn new(
account_index: u8,
base_offset: u32,
stride: u32,
cell_size: u32,
count: u32,
argument_index: u8,
argument_name: &'static str,
segment_name: &'static str,
) -> Self {
Self {
account_index,
base_offset,
stride,
cell_size,
count,
argument_index,
argument_name,
segment_name,
}
}
#[inline(always)]
fn envelope_overlaps(&self, offset: u32, size: u32) -> bool {
let envelope_start = self.base_offset as u64;
let envelope_end = envelope_start
+ self.stride as u64 * self.count.saturating_sub(1) as u64
+ self.cell_size as u64;
let request_start = offset as u64;
let request_end = request_start + size as u64;
request_start < envelope_end && request_end > envelope_start
}
#[inline(always)]
fn selected_contains(&self, selected: u32, offset: u32, size: u32) -> bool {
if selected >= self.count {
return false;
}
let start = self.base_offset as u64 + self.stride as u64 * selected as u64;
let end = start + self.cell_size as u64;
let request_start = offset as u64;
let request_end = request_start + size as u64;
request_start >= start && request_end <= end
}
}
impl WriteRange {
#[inline(always)]
pub const fn new(account_index: u8, offset: u32, size: u32) -> Self {
Self {
account_index,
offset,
size,
}
}
#[inline(always)]
pub const fn whole_account(account_index: u8) -> Self {
Self {
account_index,
offset: 0,
size: u32::MAX,
}
}
#[inline(always)]
pub const fn tail_from(account_index: u8, offset: u32) -> Self {
Self {
account_index,
offset,
size: u32::MAX,
}
}
#[inline(always)]
pub const fn contains(&self, offset: u32, size: u32) -> bool {
let req_start = offset as u64;
let req_end = offset as u64 + size as u64;
let start = self.offset as u64;
let end = self.offset as u64 + self.size as u64;
req_start >= start && req_end <= end
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LamportPolicy {
Undeclared,
Declared(&'static [u8]),
}
#[derive(Debug)]
pub struct WritePolicy {
pub allows: &'static [WriteRange],
pub parametric: &'static [ParametricWriteRange],
pub lamports: LamportPolicy,
}
impl WritePolicy {
#[inline(always)]
pub const fn new(allows: &'static [WriteRange]) -> Self {
Self {
allows,
parametric: &[],
lamports: LamportPolicy::Undeclared,
}
}
#[inline(always)]
pub const fn with_lamports(
allows: &'static [WriteRange],
lamport_accounts: &'static [u8],
) -> Self {
Self {
allows,
parametric: &[],
lamports: LamportPolicy::Declared(lamport_accounts),
}
}
#[inline(always)]
pub const fn with_parametric(
allows: &'static [WriteRange],
parametric: &'static [ParametricWriteRange],
) -> Self {
Self {
allows,
parametric,
lamports: LamportPolicy::Undeclared,
}
}
#[inline(always)]
pub const fn with_parametric_and_lamports(
allows: &'static [WriteRange],
parametric: &'static [ParametricWriteRange],
lamport_accounts: &'static [u8],
) -> Self {
Self {
allows,
parametric,
lamports: LamportPolicy::Declared(lamport_accounts),
}
}
#[inline(always)]
pub const fn lamports_declared(&self) -> bool {
matches!(self.lamports, LamportPolicy::Declared(_))
}
#[inline(always)]
pub fn allows_lamport_mutation(&self, account_index: u8) -> bool {
match self.lamports {
LamportPolicy::Undeclared => true,
LamportPolicy::Declared(indices) => {
let mut i = 0;
while i < indices.len() {
if indices[i] == account_index {
return true;
}
i += 1;
}
false
}
}
}
#[inline(always)]
pub fn allows_whole_account_write(&self, account_index: u8) -> bool {
let ranges = self.allows;
let mut i = 0;
while i < ranges.len() {
let r = &ranges[i];
if r.account_index == account_index && r.contains(0, u32::MAX) {
return true;
}
i += 1;
}
false
}
#[inline(always)]
pub fn allows_any_account_write(&self, account_index: u8) -> bool {
let mut i = 0;
while i < self.allows.len() {
if self.allows[i].account_index == account_index {
return true;
}
i += 1;
}
false
}
#[inline(always)]
pub fn check_write(
&self,
account_index: u8,
offset: u32,
size: u32,
) -> Result<(), ProgramError> {
let ranges = self.allows;
let mut i = 0;
while i < ranges.len() {
let r = &ranges[i];
if r.account_index == account_index && r.contains(offset, size) {
return Ok(());
}
i += 1;
}
Err(write_policy_violation(account_index))
}
#[inline(always)]
pub fn check_write_with_args(
&self,
account_index: u8,
offset: u32,
size: u32,
args: &[u32],
) -> Result<(), ProgramError> {
let mut i = 0;
while i < self.parametric.len() {
let rule = &self.parametric[i];
if rule.account_index == account_index && rule.envelope_overlaps(offset, size) {
let argument = args.get(rule.argument_index as usize).copied();
if argument
.map(|selected| rule.selected_contains(selected, offset, size))
.unwrap_or(false)
{
return Ok(());
}
return Err(write_policy_violation(account_index));
}
i += 1;
}
self.check_write(account_index, offset, size)
}
#[inline]
pub fn first_unauthorized_byte_with_args(
&self,
account_index: u8,
offset: u32,
size: u32,
args: &[u32],
) -> Option<u64> {
let mut cursor = offset as u64;
let request_end = cursor + size as u64;
while cursor < request_end {
let mut governing_rule = None;
let mut i = 0;
while i < self.parametric.len() {
let rule = &self.parametric[i];
if rule.account_index == account_index {
let envelope_start = rule.base_offset as u64;
let envelope_end = envelope_start
+ rule.stride as u64 * rule.count.saturating_sub(1) as u64
+ rule.cell_size as u64;
if envelope_start <= cursor && cursor < envelope_end {
governing_rule = Some(rule);
break;
}
}
i += 1;
}
if let Some(rule) = governing_rule {
let Some(selected) = args.get(rule.argument_index as usize).copied() else {
return Some(cursor);
};
if selected >= rule.count {
return Some(cursor);
}
let selected_start = rule.base_offset as u64 + rule.stride as u64 * selected as u64;
let selected_end = selected_start + rule.cell_size as u64;
if selected_start <= cursor && cursor < selected_end {
cursor = core::cmp::min(selected_end, request_end);
continue;
}
return Some(cursor);
}
let mut covered_until = cursor;
let mut j = 0;
while j < self.allows.len() {
let range = &self.allows[j];
if range.account_index == account_index {
let range_start = range.offset as u64;
let range_end = range_start + range.size as u64;
if range_start <= cursor && cursor < range_end {
covered_until = core::cmp::max(covered_until, range_end);
}
}
j += 1;
}
if covered_until == cursor {
return Some(cursor);
}
let mut k = 0;
while k < self.parametric.len() {
let rule = &self.parametric[k];
let envelope_start = rule.base_offset as u64;
if rule.account_index == account_index
&& cursor < envelope_start
&& envelope_start < covered_until
{
covered_until = envelope_start;
}
k += 1;
}
cursor = core::cmp::min(covered_until, request_end);
}
None
}
#[inline(always)]
pub fn allows_write(&self, account_index: u8, offset: u32, size: u32) -> bool {
self.check_write(account_index, offset, size).is_ok()
}
}
pub const LAMPORT_GATE_INSTALL_ERROR_PAGE: u32 = 0xD1_00;
pub const LAMPORT_GATE_TOO_MANY_ACCOUNTS: ProgramError =
ProgramError::Custom(LAMPORT_GATE_INSTALL_ERROR_PAGE | 0x01);
pub const LAMPORT_GATE_DEPTH_EXCEEDED: ProgramError =
ProgramError::Custom(LAMPORT_GATE_INSTALL_ERROR_PAGE | 0x02);
pub const LAMPORT_GATE_CONTENDED: ProgramError =
ProgramError::Custom(LAMPORT_GATE_INSTALL_ERROR_PAGE | 0x03);
pub const AMBIENT_GATE_TOO_MANY_ARGUMENTS: ProgramError =
ProgramError::Custom(LAMPORT_GATE_INSTALL_ERROR_PAGE | 0x04);
pub const AMBIENT_GATE_UNGUARDED_BUILD: ProgramError =
ProgramError::Custom(LAMPORT_GATE_INSTALL_ERROR_PAGE | 0x05);
pub const LAMPORT_GATE_CAPACITY: usize = crate::MAX_TX_ACCOUNTS;
pub const AMBIENT_GATE_ARG_CAPACITY: usize = 8;
pub const LAMPORT_GATE_DEPTH: usize = 4;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum GateInstallError {
TooManyAccounts,
TooManyArguments,
NoFreeSlot,
}
#[derive(Clone, Copy)]
struct GateEntry {
address: Address,
index: u8,
allow_mutation: bool,
allow_delegation: bool,
#[cfg_attr(feature = "unguarded-raw-surfaces", allow(dead_code))]
allow_transition: bool,
}
impl GateEntry {
#[cfg_attr(target_os = "solana", allow(dead_code))]
const EMPTY: Self = Self {
address: Address::new([0; 32]),
index: 0,
allow_mutation: false,
allow_delegation: false,
allow_transition: false,
};
}
#[derive(Clone, Copy)]
struct GateSlot {
token: u64,
len: usize,
policy: Option<&'static WritePolicy>,
args: [u32; AMBIENT_GATE_ARG_CAPACITY],
args_len: usize,
entries: [GateEntry; LAMPORT_GATE_CAPACITY],
}
impl GateSlot {
#[cfg_attr(target_os = "solana", allow(dead_code))]
const FREE: Self = Self {
token: 0,
len: 0,
policy: None,
args: [0; AMBIENT_GATE_ARG_CAPACITY],
args_len: 0,
entries: [GateEntry::EMPTY; LAMPORT_GATE_CAPACITY],
};
}
#[derive(Clone, Copy)]
enum GateCheck {
Lamports,
#[cfg(not(feature = "unguarded-raw-surfaces"))]
Data {
offset: u32,
size: u32,
},
#[cfg(not(feature = "unguarded-raw-surfaces"))]
Transition,
Delegation,
}
struct GateStore<const DEPTH: usize> {
issued: u64,
installed: u64,
top_token: u64,
top_idx: u8,
slots: [GateSlot; DEPTH],
}
impl<const DEPTH: usize> GateStore<DEPTH> {
#[cfg_attr(target_os = "solana", allow(dead_code))]
const fn new() -> Self {
Self {
issued: 0,
installed: 0,
top_token: 0,
top_idx: 0,
slots: [GateSlot::FREE; DEPTH],
}
}
fn install_with_args(
&mut self,
accounts: &[crate::account::AccountView<'_>],
policy: &'static WritePolicy,
args: &[u32],
) -> Result<u64, GateInstallError> {
if accounts.len() > LAMPORT_GATE_CAPACITY {
return Err(GateInstallError::TooManyAccounts);
}
if args.len() > AMBIENT_GATE_ARG_CAPACITY {
return Err(GateInstallError::TooManyArguments);
}
let (slot_idx, slot) = self
.slots
.iter_mut()
.enumerate()
.find(|(_, s)| s.token == 0)
.ok_or(GateInstallError::NoFreeSlot)?;
let mut len = 0usize;
for (i, view) in accounts.iter().enumerate() {
let address = *view.address();
let (index, allow_mutation, allow_delegation, allow_transition) =
if i <= u8::MAX as usize {
let idx = i as u8;
let m = policy.allows_lamport_mutation(idx);
(
idx,
m,
policy.lamports_declared() && m && policy.allows_whole_account_write(idx),
policy.allows_any_account_write(idx),
)
} else {
(u8::MAX, false, false, false)
};
let Some(entry) = slot.entries.get_mut(len) else {
return Err(GateInstallError::TooManyAccounts);
};
*entry = GateEntry {
address,
index,
allow_mutation,
allow_delegation,
allow_transition,
};
len += 1;
}
slot.len = len;
slot.policy = Some(policy);
slot.args_len = args.len();
for (dst, src) in slot.args.iter_mut().zip(args.iter().copied()) {
*dst = src;
}
self.issued = self.issued.wrapping_add(1);
if self.issued == 0 {
self.issued = 1;
}
let token = self.issued;
slot.token = token;
self.top_token = token;
self.top_idx = slot_idx as u8;
self.installed += 1;
Ok(token)
}
fn remove(&mut self, token: u64) -> bool {
if let Some(slot) = self.slots.iter_mut().find(|s| s.token == token) {
slot.token = 0;
slot.len = 0;
slot.policy = None;
slot.args_len = 0;
self.installed = self.installed.saturating_sub(1);
if token == self.top_token {
let mut best_token = 0u64;
let mut best_idx = 0u8;
for (i, s) in self.slots.iter().enumerate() {
if s.token > best_token {
best_token = s.token;
best_idx = i as u8;
}
}
self.top_token = best_token;
self.top_idx = best_idx;
}
true
} else {
false
}
}
fn active_slot(&self) -> Option<&GateSlot> {
if self.top_token == 0 {
return None;
}
self.slots.get(self.top_idx as usize)
}
#[cfg_attr(feature = "unguarded-raw-surfaces", allow(unused_variables))]
fn check(&self, address: &Address, check: GateCheck) -> ProgramResult {
if self.installed == 0 {
return Ok(());
}
let Some(slot) = self.active_slot() else {
return Ok(());
};
let Some(policy) = slot.policy else {
return Err(write_policy_violation(u8::MAX));
};
if !policy.lamports_declared()
&& matches!(check, GateCheck::Lamports | GateCheck::Delegation)
{
return Ok(());
}
let seen = slot.entries.get(..slot.len).unwrap_or(&[]);
let args = slot.args.get(..slot.args_len).unwrap_or(&[]);
let mut first_matching_index = None;
for entry in seen {
if entry.address == *address {
if first_matching_index.is_none() {
first_matching_index = Some(entry.index);
}
let allowed = match check {
GateCheck::Lamports => entry.allow_mutation,
#[cfg(not(feature = "unguarded-raw-surfaces"))]
GateCheck::Data { offset, size } => policy
.check_write_with_args(entry.index, offset, size, args)
.is_ok(),
#[cfg(not(feature = "unguarded-raw-surfaces"))]
GateCheck::Transition => entry.allow_transition,
GateCheck::Delegation => entry.allow_delegation,
};
if allowed {
return Ok(());
}
}
}
Err(write_policy_violation(
first_matching_index.unwrap_or(u8::MAX),
))
}
fn any_active(&self) -> bool {
self.installed != 0
}
}
#[cfg(all(
not(target_os = "solana"),
any(test, not(feature = "thread-local-registry"))
))]
struct SpinlockGateStore {
lock: core::sync::atomic::AtomicBool,
cell: core::cell::UnsafeCell<GateStore<1>>,
}
#[cfg(all(
not(target_os = "solana"),
any(test, not(feature = "thread-local-registry"))
))]
unsafe impl Sync for SpinlockGateStore {}
#[cfg(all(
not(target_os = "solana"),
any(test, not(feature = "thread-local-registry"))
))]
impl SpinlockGateStore {
const fn new() -> Self {
Self {
lock: core::sync::atomic::AtomicBool::new(false),
cell: core::cell::UnsafeCell::new(GateStore::new()),
}
}
fn with_lock<R>(&self, f: impl FnOnce(&mut GateStore<1>) -> R) -> R {
use core::sync::atomic::Ordering;
while self
.lock
.compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_err()
{
core::hint::spin_loop();
}
let result = f(unsafe { &mut *self.cell.get() });
self.lock.store(false, Ordering::Release);
result
}
}
#[cfg(any(target_os = "solana", test))]
pub(crate) const SBF_GATE_DEPTH: usize = 2;
#[cfg(any(target_os = "solana", test))]
type GateChecker = fn(&GateStore<SBF_GATE_DEPTH>, &Address, GateCheck) -> ProgramResult;
#[cfg(any(target_os = "solana", test))]
#[repr(C)]
struct SbfGateState {
checker: Option<GateChecker>,
store: GateStore<SBF_GATE_DEPTH>,
}
#[cfg(any(target_os = "solana", test))]
impl SbfGateState {
fn install_with_args(
&mut self,
accounts: &[crate::account::AccountView<'_>],
policy: &'static WritePolicy,
args: &[u32],
) -> Result<u64, GateInstallError> {
let token = self.store.install_with_args(accounts, policy, args)?;
self.checker = Some(GateStore::check);
Ok(token)
}
fn remove(&mut self, token: u64) {
self.store.remove(token);
if !self.store.any_active() {
self.checker = None;
}
}
#[inline(always)]
fn check(&self, address: &Address, check: GateCheck) -> ProgramResult {
match self.checker {
Some(checker) => checker(&self.store, address, check),
None => Ok(()),
}
}
#[inline(always)]
fn any_active(&self) -> bool {
self.checker.is_some()
}
}
#[cfg(target_os = "solana")]
#[allow(dead_code)]
pub(crate) const SBF_GATE_HEAP_END: usize =
core::mem::size_of::<usize>() + core::mem::size_of::<SbfGateState>();
#[cfg(target_os = "solana")]
mod gate_store {
use super::{GateCheck, GateInstallError, SbfGateState, WritePolicy};
use crate::address::Address;
use crate::error::ProgramError;
use crate::ProgramResult;
const GATE_HEAP_OFFSET: usize = core::mem::size_of::<usize>();
const _: () = assert!(
core::mem::size_of::<SbfGateState>()
<= hopper_native::HEAP_RUNTIME_RESERVED - GATE_HEAP_OFFSET,
"GateStore exceeds HEAP_RUNTIME_RESERVED; grow the reservation in \
hopper-native/src/entrypoint.rs or shrink the store"
);
const _: () = assert!((hopper_native::HEAP_START_ADDRESS + GATE_HEAP_OFFSET) % 8 == 0);
pub(super) const NO_FREE_SLOT: ProgramError = super::LAMPORT_GATE_DEPTH_EXCEEDED;
#[inline(always)]
fn with_store<R>(f: impl FnOnce(&mut SbfGateState) -> R) -> R {
let ptr = (hopper_native::HEAP_START_ADDRESS + GATE_HEAP_OFFSET) as *mut SbfGateState;
f(unsafe { &mut *ptr })
}
pub(super) fn install_with_args(
accounts: &[crate::account::AccountView<'_>],
policy: &'static WritePolicy,
args: &[u32],
) -> Result<u64, GateInstallError> {
with_store(|store| store.install_with_args(accounts, policy, args))
}
pub(super) fn remove(token: u64) {
with_store(|store| store.remove(token));
}
#[inline(always)]
pub(super) fn check(address: &Address, check: GateCheck) -> ProgramResult {
with_store(|store| store.check(address, check))
}
#[inline(always)]
pub(super) fn any_active() -> bool {
with_store(|store| store.any_active())
}
}
#[cfg(all(
not(target_os = "solana"),
any(test, feature = "thread-local-registry")
))]
mod gate_store {
use super::{GateCheck, GateInstallError, GateStore, WritePolicy, LAMPORT_GATE_DEPTH};
use crate::address::Address;
use crate::error::ProgramError;
use crate::ProgramResult;
use std::cell::RefCell;
std::thread_local! {
static STORE: RefCell<GateStore<LAMPORT_GATE_DEPTH>> =
const { RefCell::new(GateStore::new()) };
}
pub(super) const NO_FREE_SLOT: ProgramError = super::LAMPORT_GATE_DEPTH_EXCEEDED;
fn with_store<R>(f: impl FnOnce(&mut GateStore<LAMPORT_GATE_DEPTH>) -> R) -> R {
STORE.with(|cell| f(&mut cell.borrow_mut()))
}
pub(super) fn install_with_args(
accounts: &[crate::account::AccountView<'_>],
policy: &'static WritePolicy,
args: &[u32],
) -> Result<u64, GateInstallError> {
with_store(|store| store.install_with_args(accounts, policy, args))
}
pub(super) fn remove(token: u64) {
with_store(|store| store.remove(token));
}
pub(super) fn check(address: &Address, check: GateCheck) -> ProgramResult {
with_store(|store| store.check(address, check))
}
pub(super) fn any_active() -> bool {
with_store(|store| store.any_active())
}
}
#[cfg(all(
not(target_os = "solana"),
not(any(test, feature = "thread-local-registry"))
))]
mod gate_store {
use super::{GateCheck, GateInstallError, SpinlockGateStore, WritePolicy};
use crate::address::Address;
use crate::error::ProgramError;
use crate::ProgramResult;
static STORE: SpinlockGateStore = SpinlockGateStore::new();
pub(super) const NO_FREE_SLOT: ProgramError = super::LAMPORT_GATE_CONTENDED;
pub(super) fn install_with_args(
accounts: &[crate::account::AccountView<'_>],
policy: &'static WritePolicy,
args: &[u32],
) -> Result<u64, GateInstallError> {
STORE.with_lock(|store| store.install_with_args(accounts, policy, args))
}
pub(super) fn remove(token: u64) {
STORE.with_lock(|store| store.remove(token));
}
pub(super) fn check(address: &Address, check: GateCheck) -> ProgramResult {
STORE.with_lock(|store| store.check(address, check))
}
pub(super) fn any_active() -> bool {
STORE.with_lock(|store| store.any_active())
}
}
#[derive(Debug)]
pub struct LamportGateGuard<'accounts> {
token: u64,
_accounts: core::marker::PhantomData<&'accounts ()>,
}
impl Drop for LamportGateGuard<'_> {
#[inline]
fn drop(&mut self) {
if self.token != 0 {
gate_store::remove(self.token);
}
}
}
#[inline]
pub fn try_install_lamport_gate<'accounts>(
accounts: &'accounts [crate::account::AccountView<'accounts>],
policy: &'static WritePolicy,
) -> Result<LamportGateGuard<'accounts>, ProgramError> {
try_install_ambient_gate_with_args(accounts, policy, &[])
}
#[inline]
pub fn try_install_ambient_gate_with_args<'accounts>(
accounts: &'accounts [crate::account::AccountView<'accounts>],
policy: &'static WritePolicy,
args: &[u32],
) -> Result<LamportGateGuard<'accounts>, ProgramError> {
#[cfg(feature = "unguarded-raw-surfaces")]
if !policy.allows.is_empty() || !policy.parametric.is_empty() {
return Err(AMBIENT_GATE_UNGUARDED_BUILD);
}
match gate_store::install_with_args(accounts, policy, args) {
Ok(token) => Ok(LamportGateGuard {
token,
_accounts: core::marker::PhantomData,
}),
Err(GateInstallError::TooManyAccounts) => Err(LAMPORT_GATE_TOO_MANY_ACCOUNTS),
Err(GateInstallError::TooManyArguments) => Err(AMBIENT_GATE_TOO_MANY_ARGUMENTS),
Err(GateInstallError::NoFreeSlot) => Err(gate_store::NO_FREE_SLOT),
}
}
#[inline]
pub fn install_lamport_gate<'accounts>(
accounts: &'accounts [crate::account::AccountView<'accounts>],
policy: &'static WritePolicy,
) -> LamportGateGuard<'accounts> {
match try_install_lamport_gate(accounts, policy) {
Ok(guard) => guard,
Err(_) => panic!(
"lamport gate install refused (capacity or slot occupancy); \
use try_install_lamport_gate to handle this as a ProgramError"
),
}
}
#[inline]
pub fn lamport_gate_active() -> bool {
gate_store::any_active()
}
#[inline(always)]
pub(crate) fn check_lamport_mutation(address: &Address) -> ProgramResult {
if !gate_store::any_active() {
return Ok(());
}
gate_store::check(address, GateCheck::Lamports)
}
#[inline(always)]
pub(crate) fn check_data_mutation(address: &Address, offset: u32, size: u32) -> ProgramResult {
#[cfg(not(feature = "unguarded-raw-surfaces"))]
{
if !gate_store::any_active() {
return Ok(());
}
gate_store::check(address, GateCheck::Data { offset, size })
}
#[cfg(feature = "unguarded-raw-surfaces")]
{
let _ = (address, offset, size);
Ok(())
}
}
#[inline(always)]
pub(crate) fn check_account_transition(address: &Address) -> ProgramResult {
#[cfg(not(feature = "unguarded-raw-surfaces"))]
{
if !gate_store::any_active() {
return Ok(());
}
gate_store::check(address, GateCheck::Transition)
}
#[cfg(feature = "unguarded-raw-surfaces")]
{
let _ = address;
Ok(())
}
}
pub const RAW_SURFACES_GUARDED: bool = !cfg!(feature = "unguarded-raw-surfaces");
#[inline]
pub(crate) fn check_lamport_delegation(address: &Address) -> ProgramResult {
gate_store::check(address, GateCheck::Delegation)
}
#[cfg(test)]
mod gate_store_layout_tests {
use super::*;
#[test]
fn zeroed_sbf_state_has_no_evaluator() {
let state: SbfGateState = unsafe { core::mem::zeroed() };
assert!(!state.any_active());
assert_eq!(state.store.installed, 0);
assert!(state
.check(&Address::default(), GateCheck::Lamports)
.is_ok());
assert_eq!(core::mem::offset_of!(SbfGateState, checker), 0);
assert_eq!(core::mem::size_of::<Option<GateChecker>>(), 8);
assert!(
core::mem::size_of::<SbfGateState>() + core::mem::size_of::<usize>()
<= hopper_native::HEAP_RUNTIME_RESERVED
);
}
#[test]
fn initial_gate_store_is_all_zero_bytes() {
let store = GateStore::<2>::new();
assert_eq!(store.issued, 0, "issued must start at 0, not 1");
assert_eq!(store.installed, 0, "installed count must start at 0");
for slot in &store.slots {
assert_eq!(slot.token, 0, "free-slot sentinel is 0");
assert_eq!(slot.len, 0);
for entry in slot.entries.iter() {
assert_eq!(*entry.address.as_array(), [0u8; 32]);
assert_eq!(entry.index, 0);
assert!(!entry.allow_mutation);
assert!(!entry.allow_delegation);
}
}
}
#[test]
fn issued_counter_hands_out_nonzero_monotonic_tokens() {
let mut store = GateStore::<2>::new();
store.issued = 0;
store.issued = store.issued.wrapping_add(1);
assert_eq!(store.issued, 1, "first token is 1, never the 0 sentinel");
store.issued = u64::MAX;
store.issued = store.issued.wrapping_add(1);
if store.issued == 0 {
store.issued = 1;
}
assert_eq!(store.issued, 1, "wrap skips the 0 sentinel");
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sbf_dispatch_preserves_nested_and_failed_install_enforcement() {
let (_b0, a0) = make_account(70);
let (_bf, foreign) = make_account(71);
let accounts = [a0];
static ALLOW: WritePolicy =
WritePolicy::with_lamports(&[WriteRange::whole_account(0)], &[0]);
static DENY: WritePolicy = WritePolicy::with_lamports(&[], &[]);
let mut state = SbfGateState {
checker: None,
store: GateStore::new(),
};
let outer = state.install_with_args(&accounts, &ALLOW, &[]).unwrap();
assert!(state.any_active());
assert!(state
.check(accounts[0].address(), GateCheck::Delegation)
.is_ok());
assert_eq!(
state.check(foreign.address(), GateCheck::Lamports),
Err(write_policy_violation(u8::MAX))
);
let inner = state.install_with_args(&accounts, &DENY, &[]).unwrap();
assert!(matches!(
state.install_with_args(&accounts, &ALLOW, &[]),
Err(GateInstallError::NoFreeSlot)
));
assert_eq!(
state.check(accounts[0].address(), GateCheck::Lamports),
Err(write_policy_violation(0))
);
state.remove(inner);
assert!(state
.check(accounts[0].address(), GateCheck::Lamports)
.is_ok());
let inner = state.install_with_args(&accounts, &DENY, &[]).unwrap();
state.remove(outer);
state.remove(outer); assert!(state.any_active());
assert_eq!(
state.check(accounts[0].address(), GateCheck::Delegation),
Err(write_policy_violation(0))
);
state.remove(inner);
assert!(!state.any_active());
assert!(state.check(foreign.address(), GateCheck::Lamports).is_ok());
}
#[test]
#[cfg(not(feature = "unguarded-raw-surfaces"))]
fn sbf_dispatch_preserves_byte_ranges_and_data_only_lamport_passthrough() {
let (_b0, a0) = make_account(72);
let (_bf, foreign) = make_account(73);
let accounts = [a0];
static NARROW: WritePolicy = WritePolicy::new(&[WriteRange::new(0, 8, 8)]);
let mut state = SbfGateState {
checker: None,
store: GateStore::new(),
};
let token = state.install_with_args(&accounts, &NARROW, &[]).unwrap();
assert!(state
.check(
accounts[0].address(),
GateCheck::Data { offset: 8, size: 8 }
)
.is_ok());
assert_eq!(
state.check(
accounts[0].address(),
GateCheck::Data { offset: 9, size: 8 }
),
Err(write_policy_violation(0))
);
assert_eq!(
state.check(foreign.address(), GateCheck::Transition),
Err(write_policy_violation(u8::MAX))
);
assert!(state.check(foreign.address(), GateCheck::Lamports).is_ok());
assert!(state
.check(foreign.address(), GateCheck::Delegation)
.is_ok());
state.remove(token);
assert!(state
.check(
foreign.address(),
GateCheck::Data {
offset: 0,
size: 32
}
)
.is_ok());
}
static POLICY: WritePolicy = WritePolicy::new(&[
WriteRange::new(1, 16, 8),
WriteRange::new(1, 24, 8),
WriteRange::whole_account(2),
]);
#[test]
fn declared_ranges_allow_exact_and_contained_writes() {
assert!(POLICY.check_write(1, 16, 8).is_ok());
assert!(POLICY.check_write(1, 24, 8).is_ok());
assert!(POLICY.check_write(1, 18, 4).is_ok());
assert!(POLICY.check_write(1, 20, 0).is_ok());
}
#[test]
fn parametric_column_allows_only_the_selected_cell() {
static PARAMETRIC: &[ParametricWriteRange] = &[ParametricWriteRange::new(
1, 100, 8, 8, 20, 0, "slot", "balances",
)];
static P: WritePolicy =
WritePolicy::with_parametric(&[WriteRange::new(1, 100, 20 * 8)], PARAMETRIC);
assert!(P.check_write_with_args(1, 100 + 7 * 8, 8, &[7]).is_ok());
assert!(P.check_write_with_args(1, 100 + 7 * 8 + 2, 4, &[7]).is_ok());
assert_eq!(
P.check_write_with_args(1, 100 + 8 * 8, 8, &[7]),
Err(ProgramError::Custom(0xD0_01)),
);
assert!(P.check_write_with_args(1, 100 + 19 * 8, 8, &[20]).is_err());
assert!(P.check_write_with_args(1, 100 + 7 * 8, 8, &[]).is_err());
}
#[test]
fn containment_oracle_resolves_parametric_cells_and_static_unions() {
static PARAMETRIC: &[ParametricWriteRange] = &[ParametricWriteRange::new(
1, 100, 8, 8, 20, 0, "slot", "balances",
)];
static P: WritePolicy = WritePolicy::with_parametric(
&[
WriteRange::new(1, 96, 4),
WriteRange::new(1, 100, 20 * 8),
WriteRange::new(1, 260, 4),
],
PARAMETRIC,
);
assert_eq!(
P.first_unauthorized_byte_with_args(1, 100 + 7 * 8, 8, &[7]),
None
);
assert_eq!(
P.first_unauthorized_byte_with_args(1, 100 + 8 * 8, 8, &[7]),
Some((100 + 8 * 8) as u64)
);
assert_eq!(P.first_unauthorized_byte_with_args(1, 96, 12, &[0]), None);
assert_eq!(
P.first_unauthorized_byte_with_args(1, 100, 8, &[]),
Some(100)
);
assert_eq!(
P.first_unauthorized_byte_with_args(1, 100, 8, &[20]),
Some(100)
);
assert_eq!(
POLICY.first_unauthorized_byte_with_args(1, 16, 16, &[]),
None
);
assert_eq!(
POLICY.first_unauthorized_byte_with_args(1, 15, 17, &[]),
Some(15)
);
}
#[test]
fn undeclared_ranges_are_refused_with_indexed_error() {
assert_eq!(
POLICY.check_write(1, 0, 8),
Err(ProgramError::Custom(0xD0_01))
);
assert!(POLICY.check_write(1, 12, 8).is_err());
assert!(POLICY.check_write(1, 16, 16).is_err());
assert_eq!(
POLICY.check_write(0, 16, 8),
Err(ProgramError::Custom(0xD0_00))
);
}
#[test]
fn whole_account_allowance_contains_any_request() {
assert!(POLICY.check_write(2, 0, 8).is_ok());
assert!(POLICY.check_write(2, 0, u32::MAX).is_ok());
assert!(POLICY.check_write(2, 4096, 10 * 1024 * 1024).is_ok());
}
#[test]
fn empty_policy_denies_all_writes() {
static READ_ONLY: WritePolicy = WritePolicy::new(&[]);
assert!(READ_ONLY.check_write(0, 0, 1).is_err());
assert!(READ_ONLY.check_write(255, 0, 0).is_err());
}
#[test]
fn open_ended_tail_range_allows_tail_refuses_head_and_is_not_whole_account() {
const TAIL_OFF: u32 = 24;
static P: WritePolicy = WritePolicy::new(&[WriteRange::tail_from(1, TAIL_OFF)]);
assert!(P.check_write(1, TAIL_OFF, 4).is_ok()); assert!(P.check_write(1, TAIL_OFF + 4, 32).is_ok()); assert!(P.check_write(1, TAIL_OFF, 10 * 1024 * 1024).is_ok()); assert!(P.check_write(1, TAIL_OFF + 1_000_000, 32).is_ok());
assert_eq!(P.check_write(1, 0, 8), Err(write_policy_violation(1)));
assert_eq!(P.check_write(1, 16, 8), Err(write_policy_violation(1)));
assert!(P.check_write(1, TAIL_OFF - 1, 8).is_err());
assert!(!P.allows_whole_account_write(1));
static P0: WritePolicy = WritePolicy::new(&[WriteRange::tail_from(1, 0)]);
assert!(P0.allows_whole_account_write(1));
}
#[test]
fn containment_survives_u32_boundary_arithmetic() {
static EDGE: WritePolicy = WritePolicy::new(&[WriteRange::new(0, u32::MAX - 8, 8)]);
assert!(EDGE.check_write(0, u32::MAX - 8, 8).is_ok());
assert!(EDGE.check_write(0, u32::MAX - 4, 8).is_err());
}
#[test]
fn undeclared_lamport_dimension_permits_everything_and_is_incomplete() {
assert!(!POLICY.lamports_declared());
assert!(POLICY.allows_lamport_mutation(0));
assert!(POLICY.allows_lamport_mutation(255));
}
#[test]
fn declared_lamport_dimension_permits_only_members() {
static P: WritePolicy =
WritePolicy::with_lamports(&[WriteRange::whole_account(0)], &[0, 3]);
assert!(P.lamports_declared());
assert!(P.allows_lamport_mutation(0));
assert!(P.allows_lamport_mutation(3));
assert!(!P.allows_lamport_mutation(1));
static NONE: WritePolicy = WritePolicy::with_lamports(&[], &[]);
assert!(NONE.lamports_declared());
assert!(!NONE.allows_lamport_mutation(0));
}
#[test]
fn whole_account_grant_is_required_for_delegation() {
static P: WritePolicy = WritePolicy::with_lamports(
&[WriteRange::whole_account(0), WriteRange::new(1, 16, 8)],
&[0, 1],
);
assert!(P.allows_whole_account_write(0));
assert!(!P.allows_whole_account_write(1));
assert!(!P.allows_whole_account_write(2));
}
use crate::account::AccountView;
use hopper_native::{
AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount, NOT_BORROWED,
};
fn make_account(seed: u8) -> (std::vec::Vec<u64>, AccountView<'static>) {
let mut backing = std::vec![0u64; (RuntimeAccount::SIZE + 32).div_ceil(8)];
let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
unsafe {
raw.write(RuntimeAccount {
borrow_state: NOT_BORROWED,
is_signer: 1,
is_writable: 1,
executable: 0,
resize_delta: 0,
address: NativeAddress::new_from_array([seed; 32]),
owner: NativeAddress::new_from_array([2; 32]),
lamports: 100,
data_len: 32,
});
}
let backend = unsafe { NativeAccountView::new_unchecked(raw) };
(backing, AccountView::from_backend(backend))
}
#[test]
#[cfg(not(feature = "unguarded-raw-surfaces"))]
fn gate_refuses_undeclared_and_allows_declared_lamport_mutation() {
let (_b0, a0) = make_account(10);
let (_b1, a1) = make_account(11);
let accounts = [a0, a1];
static P: WritePolicy = WritePolicy::with_lamports(&[WriteRange::whole_account(0)], &[0]);
assert!(check_lamport_mutation(accounts[1].address()).is_ok());
{
let _gate = install_lamport_gate(&accounts, &P);
assert!(lamport_gate_active());
assert!(check_lamport_mutation(accounts[0].address()).is_ok());
assert_eq!(
check_lamport_mutation(accounts[1].address()),
Err(write_policy_violation(1))
);
let (_bf, foreign) = make_account(99);
assert_eq!(
check_lamport_mutation(foreign.address()),
Err(write_policy_violation(u8::MAX))
);
}
assert!(!lamport_gate_active());
assert!(check_lamport_mutation(accounts[1].address()).is_ok());
}
#[test]
#[cfg(not(feature = "unguarded-raw-surfaces"))]
fn gate_delegation_requires_both_dimensions() {
let (_b0, a0) = make_account(20);
let (_b1, a1) = make_account(21);
let (_b2, a2) = make_account(22);
let accounts = [a0, a1, a2];
static P: WritePolicy = WritePolicy::with_lamports(
&[WriteRange::whole_account(0), WriteRange::new(2, 16, 8)],
&[0, 1],
);
let _gate = install_lamport_gate(&accounts, &P);
assert!(check_lamport_delegation(accounts[0].address()).is_ok());
assert_eq!(
check_lamport_delegation(accounts[1].address()),
Err(write_policy_violation(1))
);
assert_eq!(
check_lamport_delegation(accounts[2].address()),
Err(write_policy_violation(2))
);
assert!(check_lamport_mutation(accounts[1].address()).is_ok());
}
#[test]
#[cfg(feature = "unguarded-raw-surfaces")]
fn unguarded_build_refuses_data_declaring_installs_loudly() {
let (_b0, a0) = make_account(95);
let accounts = [a0];
static DATA: WritePolicy = WritePolicy::new(&[WriteRange::whole_account(0)]);
assert_eq!(
try_install_lamport_gate(&accounts, &DATA).map(|_| ()),
Err(AMBIENT_GATE_UNGUARDED_BUILD),
);
assert!(!lamport_gate_active(), "a refused install leaves no gate");
static LAMPORTS_ONLY: WritePolicy = WritePolicy::with_lamports(&[], &[0]);
let _gate = install_lamport_gate(&accounts, &LAMPORTS_ONLY);
let (_bf, foreign) = make_account(96);
assert!(check_lamport_mutation(accounts[0].address()).is_ok());
assert_eq!(
check_lamport_mutation(foreign.address()),
Err(write_policy_violation(u8::MAX)),
);
assert_eq!(
check_lamport_delegation(accounts[0].address()),
Err(write_policy_violation(0)),
);
assert_eq!(
check_lamport_delegation(foreign.address()),
Err(write_policy_violation(u8::MAX)),
);
assert!(check_data_mutation(foreign.address(), 0, 1).is_ok());
assert!(check_account_transition(foreign.address()).is_ok());
}
#[test]
#[cfg(not(feature = "unguarded-raw-surfaces"))]
fn public_raw_surfaces_are_governed_by_the_ambient_gate() {
let (_b0, a0) = make_account(90);
let (_bf, foreign) = make_account(91);
let accounts = [a0];
static P: WritePolicy = WritePolicy::new(&[WriteRange::new(0, 8, 8)]);
{
let mut reg = crate::segment_borrow::SegmentBorrowRegistry::new();
assert!(accounts[0].try_borrow_mut().is_ok());
assert!(accounts[0].segment_mut::<[u8; 8]>(&mut reg, 20, 8).is_ok());
}
let _gate = install_lamport_gate(&accounts, &P);
assert_eq!(
accounts[0].try_borrow_mut().map(|_| ()),
Err(write_policy_violation(0)),
);
assert_eq!(
foreign.try_borrow_mut().map(|_| ()),
Err(write_policy_violation(u8::MAX)),
);
let mut reg = crate::segment_borrow::SegmentBorrowRegistry::new();
assert!(accounts[0].segment_mut::<[u8; 8]>(&mut reg, 8, 8).is_ok());
let mut reg2 = crate::segment_borrow::SegmentBorrowRegistry::new();
assert_eq!(
accounts[0]
.segment_mut::<[u8; 8]>(&mut reg2, 9, 8)
.map(|_| ()),
Err(write_policy_violation(0)),
);
assert_eq!(foreign.resize(16), Err(write_policy_violation(u8::MAX)));
assert_eq!(foreign.close(), Err(write_policy_violation(u8::MAX)));
assert_eq!(
foreign.close_to_unchecked(&accounts[0]),
Err(write_policy_violation(u8::MAX)),
);
}
#[test]
#[cfg(not(feature = "unguarded-raw-surfaces"))]
fn data_only_policy_governs_data_but_passes_the_lamport_dimension() {
let (_b0, a0) = make_account(30);
let accounts = [a0];
static P: WritePolicy = WritePolicy::new(&[WriteRange::whole_account(0)]);
let _gate = install_lamport_gate(&accounts, &P);
assert!(
lamport_gate_active(),
"a data-only policy installs the ambient gate"
);
let (_bf, foreign) = make_account(31);
assert!(check_lamport_mutation(accounts[0].address()).is_ok());
assert!(check_lamport_mutation(foreign.address()).is_ok());
assert!(check_lamport_delegation(accounts[0].address()).is_ok());
assert!(check_lamport_delegation(foreign.address()).is_ok());
assert!(check_data_mutation(accounts[0].address(), 0, 1).is_ok());
assert_eq!(
check_data_mutation(foreign.address(), 0, 1),
Err(write_policy_violation(u8::MAX)),
);
assert!(check_account_transition(accounts[0].address()).is_ok());
assert_eq!(
check_account_transition(foreign.address()),
Err(write_policy_violation(u8::MAX)),
);
}
#[test]
#[cfg(not(feature = "unguarded-raw-surfaces"))]
fn mutation_complete_policy_still_governs_delegation() {
let (_b0, a0) = make_account(32); let (_b1, a1) = make_account(33); let accounts = [a0, a1];
static P: WritePolicy =
WritePolicy::with_lamports(&[WriteRange::whole_account(0)], &[0, 1]);
let _gate = install_lamport_gate(&accounts, &P);
let (_bf, foreign) = make_account(34);
assert!(check_lamport_delegation(accounts[0].address()).is_ok());
assert_eq!(
check_lamport_delegation(accounts[1].address()),
Err(write_policy_violation(1)),
);
assert_eq!(
check_lamport_delegation(foreign.address()),
Err(write_policy_violation(u8::MAX)),
);
}
#[test]
fn nested_gates_shadow_and_resume_like_a_stack() {
let (_b0, a0) = make_account(40);
let (_b1, a1) = make_account(41);
let outer_accounts = [a0];
let inner_accounts = [a1];
static OUTER: WritePolicy = WritePolicy::with_lamports(&[], &[0]);
static INNER: WritePolicy = WritePolicy::with_lamports(&[], &[]);
let _outer = install_lamport_gate(&outer_accounts, &OUTER);
assert!(check_lamport_mutation(outer_accounts[0].address()).is_ok());
{
let _inner = install_lamport_gate(&inner_accounts, &INNER);
assert_eq!(
check_lamport_mutation(inner_accounts[0].address()),
Err(write_policy_violation(0))
);
}
assert!(lamport_gate_active());
assert!(check_lamport_mutation(outer_accounts[0].address()).is_ok());
}
#[test]
#[cfg(not(feature = "unguarded-raw-surfaces"))]
fn forgotten_guard_leaves_stale_value_policy_never_ub() {
static P: WritePolicy = WritePolicy::with_lamports(&[WriteRange::whole_account(0)], &[0]);
let stale_address;
{
let (_b0, a0) = make_account(50);
let accounts = [a0];
stale_address = *accounts[0].address();
let guard = install_lamport_gate(&accounts, &P);
core::mem::forget(guard);
}
assert!(lamport_gate_active());
let (_bf, fresh) = make_account(51);
assert_eq!(
check_lamport_mutation(fresh.address()),
Err(write_policy_violation(u8::MAX))
);
assert!(check_lamport_mutation(&stale_address).is_ok());
}
#[test]
fn out_of_order_guard_drops_cannot_corrupt_other_gates() {
let (_b0, a0) = make_account(60);
let (_b1, a1) = make_account(61);
let outer_accounts = [a0];
let inner_accounts = [a1];
static OUTER: WritePolicy = WritePolicy::with_lamports(&[], &[0]);
static INNER: WritePolicy = WritePolicy::with_lamports(&[], &[]);
let outer = install_lamport_gate(&outer_accounts, &OUTER);
let inner = install_lamport_gate(&inner_accounts, &INNER);
assert_eq!(
check_lamport_mutation(inner_accounts[0].address()),
Err(write_policy_violation(0))
);
drop(outer);
assert!(lamport_gate_active());
assert_eq!(
check_lamport_mutation(inner_accounts[0].address()),
Err(write_policy_violation(0))
);
assert_eq!(
check_lamport_mutation(outer_accounts[0].address()),
Err(write_policy_violation(u8::MAX))
);
drop(inner);
assert!(!lamport_gate_active());
assert!(check_lamport_mutation(outer_accounts[0].address()).is_ok());
}
#[test]
fn depth_exhaustion_fails_closed_loudly() {
static P: WritePolicy = WritePolicy::with_lamports(&[], &[]);
let (_b, a) = make_account(70);
let accounts = [a];
let _g1 = try_install_lamport_gate(&accounts, &P).unwrap();
let _g2 = try_install_lamport_gate(&accounts, &P).unwrap();
let _g3 = try_install_lamport_gate(&accounts, &P).unwrap();
let _g4 = try_install_lamport_gate(&accounts, &P).unwrap();
assert_eq!(
try_install_lamport_gate(&accounts, &P).unwrap_err(),
LAMPORT_GATE_DEPTH_EXCEEDED
);
assert_eq!(
check_lamport_mutation(accounts[0].address()),
Err(write_policy_violation(0))
);
}
#[test]
fn install_fails_closed_when_accounts_exceed_capacity() {
static P: WritePolicy = WritePolicy::with_lamports(&[], &[]);
let mut backings = std::vec::Vec::new();
let mut views = std::vec::Vec::new();
let mut i = 0;
while i < LAMPORT_GATE_CAPACITY + 1 {
let (backing, view) = make_account((i % 256) as u8);
backings.push(backing);
views.push(view);
i += 1;
}
assert_eq!(
try_install_lamport_gate(&views, &P).unwrap_err(),
LAMPORT_GATE_TOO_MANY_ACCOUNTS
);
assert!(!lamport_gate_active());
}
#[test]
fn duplicate_addresses_share_one_merged_permission() {
let (_b0, a0) = make_account(75);
let (_b1, a1) = make_account(75);
let accounts = [a0, a1];
static P: WritePolicy = WritePolicy::with_lamports(&[], &[1]);
let _gate = install_lamport_gate(&accounts, &P);
assert!(check_lamport_mutation(accounts[0].address()).is_ok());
assert!(check_lamport_mutation(accounts[1].address()).is_ok());
}
#[test]
fn fallback_tier_second_install_is_refused_fail_closed() {
static P: WritePolicy = WritePolicy::with_lamports(&[], &[0]);
let (_b0, a0) = make_account(80);
let (_b1, a1) = make_account(81);
let first_accounts = [a0];
let second_accounts = [a1];
let store = SpinlockGateStore::new();
let token = store
.with_lock(|s| s.install_with_args(&first_accounts, &P, &[]))
.unwrap();
assert_eq!(
store
.with_lock(|s| s.install_with_args(&second_accounts, &P, &[]))
.unwrap_err(),
GateInstallError::NoFreeSlot
);
store.with_lock(|s| {
assert!(s
.check(first_accounts[0].address(), GateCheck::Lamports)
.is_ok());
assert_eq!(
s.check(second_accounts[0].address(), GateCheck::Lamports),
Err(write_policy_violation(u8::MAX))
);
});
store.with_lock(|s| s.remove(token));
assert!(store
.with_lock(|s| s.install_with_args(&second_accounts, &P, &[]))
.is_ok());
}
}