635 lines
19 KiB
Rust
Raw Normal View History

use std::collections::HashSet;
2026-03-25 14:55:23 -03:00
#[cfg(any(feature = "host", test))]
2026-03-20 13:16:52 -03:00
use borsh::{BorshDeserialize, BorshSerialize};
2025-11-26 00:27:20 +03:00
use risc0_zkvm::{DeserializeOwned, guest::env, serde::Deserializer};
2025-08-14 14:09:04 -03:00
use serde::{Deserialize, Serialize};
2025-08-06 20:05:04 -03:00
use crate::account::{Account, AccountId, AccountWithMetadata};
2025-11-26 00:27:20 +03:00
pub const DEFAULT_PROGRAM_ID: ProgramId = [0; 8];
2025-11-07 20:42:00 -03:00
pub const MAX_NUMBER_CHAINED_CALLS: usize = 10;
2025-08-06 20:05:04 -03:00
2026-03-04 18:42:33 +03:00
pub type ProgramId = [u32; 8];
pub type InstructionData = Vec<u32>;
2025-08-14 14:30:04 -03:00
pub struct ProgramInput<T> {
pub pre_states: Vec<AccountWithMetadata>,
pub instruction: T,
}
2026-03-03 23:21:08 +03:00
/// A 32-byte seed used to compute a *Program-Derived `AccountId`* (PDA).
2025-11-28 17:09:38 -03:00
///
2025-12-02 10:48:21 -03:00
/// Each program can derive up to `2^256` unique account IDs by choosing different
2025-11-28 17:09:38 -03:00
/// seeds. PDAs allow programs to control namespaced account identifiers without
/// collisions between programs.
#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
2025-11-27 12:08:27 -03:00
pub struct PdaSeed([u8; 32]);
impl PdaSeed {
2026-03-03 23:21:08 +03:00
#[must_use]
2025-12-12 13:00:16 -03:00
pub const fn new(value: [u8; 32]) -> Self {
Self(value)
}
}
2025-11-27 13:10:38 -03:00
impl From<(&ProgramId, &PdaSeed)> for AccountId {
fn from(value: (&ProgramId, &PdaSeed)) -> Self {
2026-03-04 18:42:33 +03:00
use risc0_zkvm::sha::{Impl, Sha256 as _};
2025-11-27 13:10:38 -03:00
const PROGRAM_DERIVED_ACCOUNT_ID_PREFIX: &[u8; 32] =
b"/NSSA/v0.2/AccountId/PDA/\x00\x00\x00\x00\x00\x00\x00";
let mut bytes = [0; 96];
bytes[0..32].copy_from_slice(PROGRAM_DERIVED_ACCOUNT_ID_PREFIX);
let program_id_bytes: &[u8] =
bytemuck::try_cast_slice(value.0).expect("ProgramId should be castable to &[u8]");
bytes[32..64].copy_from_slice(program_id_bytes);
bytes[64..].copy_from_slice(&value.1.0);
2026-03-09 18:27:56 +03:00
Self::new(
2025-11-27 13:10:38 -03:00
Impl::hash_bytes(&bytes)
.as_bytes()
.try_into()
.expect("Hash output must be exactly 32 bytes long"),
)
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct ChainedCall {
2026-03-10 00:17:43 +03:00
/// The program ID of the program to execute.
pub program_id: ProgramId,
pub pre_states: Vec<AccountWithMetadata>,
2026-03-10 00:17:43 +03:00
/// The instruction data to pass.
pub instruction_data: InstructionData,
2025-11-27 13:10:38 -03:00
pub pda_seeds: Vec<PdaSeed>,
}
impl ChainedCall {
/// Creates a new chained call serializing the given instruction.
pub fn new<I: Serialize>(
program_id: ProgramId,
pre_states: Vec<AccountWithMetadata>,
instruction: &I,
) -> Self {
Self {
program_id,
pre_states,
instruction_data: risc0_zkvm::serde::to_vec(instruction)
.expect("Serialization to Vec<u32> should not fail"),
pda_seeds: Vec::new(),
}
}
2026-03-03 23:21:08 +03:00
#[must_use]
pub fn with_pda_seeds(mut self, pda_seeds: Vec<PdaSeed>) -> Self {
self.pda_seeds = pda_seeds;
self
}
}
2025-12-04 10:10:01 -03:00
/// Represents the final state of an `Account` after a program execution.
2026-03-09 18:27:56 +03:00
///
2025-12-04 10:10:01 -03:00
/// A post state may optionally request that the executing program
/// becomes the owner of the account (a “claim”). This is used to signal
/// that the program intends to take ownership of the account.
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(any(feature = "host", test), derive(PartialEq, Eq))]
pub struct AccountPostState {
2025-12-04 16:26:40 -03:00
account: Account,
2025-12-03 16:39:33 -03:00
claim: bool,
}
2025-12-03 16:39:33 -03:00
impl AccountPostState {
2025-12-04 10:10:01 -03:00
/// Creates a post state without a claim request.
/// The executing program is not requesting ownership of the account.
2026-03-03 23:21:08 +03:00
#[must_use]
2026-03-09 18:27:56 +03:00
pub const fn new(account: Account) -> Self {
2025-12-03 16:39:33 -03:00
Self {
account,
claim: false,
}
}
2025-12-03 17:06:09 -03:00
2025-12-04 10:10:01 -03:00
/// Creates a post state that requests ownership of the account.
/// This indicates that the executing program intends to claim the
/// account as its own and is allowed to mutate it.
2026-03-03 23:21:08 +03:00
#[must_use]
2026-03-09 18:27:56 +03:00
pub const fn new_claimed(account: Account) -> Self {
2025-12-03 16:39:33 -03:00
Self {
account,
claim: true,
}
}
/// Creates a post state that requests ownership of the account
/// if the account's program owner is the default program ID.
2026-03-03 23:21:08 +03:00
#[must_use]
pub fn new_claimed_if_default(account: Account) -> Self {
let claim = account.program_owner == DEFAULT_PROGRAM_ID;
Self { account, claim }
}
2025-12-04 10:10:01 -03:00
/// Returns `true` if this post state requests that the account
/// be claimed (owned) by the executing program.
2026-03-03 23:21:08 +03:00
#[must_use]
2026-03-09 18:27:56 +03:00
pub const fn requires_claim(&self) -> bool {
2025-12-03 16:39:33 -03:00
self.claim
}
2025-12-04 16:26:40 -03:00
2026-03-10 00:17:43 +03:00
/// Returns the underlying account.
2026-03-03 23:21:08 +03:00
#[must_use]
2026-03-09 18:27:56 +03:00
pub const fn account(&self) -> &Account {
2025-12-04 16:26:40 -03:00
&self.account
}
2026-03-10 00:17:43 +03:00
/// Returns the underlying account.
2026-03-09 18:27:56 +03:00
pub const fn account_mut(&mut self) -> &mut Account {
2025-12-04 16:26:40 -03:00
&mut self.account
}
2026-03-10 00:17:43 +03:00
/// Consumes the post state and returns the underlying account.
2026-03-03 23:21:08 +03:00
#[must_use]
pub fn into_account(self) -> Account {
self.account
}
}
2026-03-19 12:10:02 -03:00
pub type BlockId = u64;
2026-03-20 13:16:52 -03:00
#[derive(Serialize, Deserialize, Clone, Copy)]
#[cfg_attr(
any(feature = "host", test),
derive(Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)
)]
pub struct ValidityWindow {
from: Option<BlockId>,
to: Option<BlockId>,
}
impl ValidityWindow {
2026-03-25 14:55:23 -03:00
/// Creates a window with no bounds, valid for every block ID.
2026-03-20 13:16:52 -03:00
#[must_use]
pub const fn new_unbounded() -> Self {
Self {
from: None,
to: None,
}
}
2026-03-25 14:55:23 -03:00
/// Returns `true` if `id` falls within the half-open range `[from, to)`.
/// A `None` bound on either side is treated as unbounded in that direction.
2026-03-20 13:16:52 -03:00
#[must_use]
pub fn is_valid_for_block_id(&self, id: BlockId) -> bool {
self.from.is_none_or(|start| id >= start) && self.to.is_none_or(|end| id < end)
}
2026-03-25 14:55:23 -03:00
/// Returns `Err(InvalidWindow)` if both bounds are set and `from >= to`.
2026-03-20 13:16:52 -03:00
const fn check_window(&self) -> Result<(), InvalidWindow> {
if let (Some(from_id), Some(until_id)) = (self.from, self.to)
&& from_id >= until_id
{
Err(InvalidWindow)
} else {
Ok(())
}
}
2026-03-25 14:55:23 -03:00
/// Inclusive lower bound. `None` means the window starts at the beginning of the chain.
2026-03-20 13:16:52 -03:00
#[must_use]
pub const fn from(&self) -> Option<BlockId> {
self.from
}
2026-03-25 14:55:23 -03:00
/// Exclusive upper bound. `None` means the window has no expiry.
2026-03-20 13:16:52 -03:00
#[must_use]
pub const fn to(&self) -> Option<BlockId> {
self.to
}
2026-03-25 14:55:23 -03:00
/// Sets the inclusive lower bound. Returns `Err` if the updated window would be empty or inverted.
pub fn set_from(&mut self, id: Option<BlockId>) -> Result<(), InvalidWindow> {
let prev = self.from;
self.from = id;
self.check_window().inspect_err(|_| self.from = prev)
}
/// Sets the exclusive upper bound. Returns `Err` if the updated window would be empty or inverted.
pub fn set_to(&mut self, id: Option<BlockId>) -> Result<(), InvalidWindow> {
let prev = self.to;
self.to = id;
self.check_window().inspect_err(|_| self.to = prev)
}
2026-03-20 13:16:52 -03:00
}
impl TryFrom<(Option<BlockId>, Option<BlockId>)> for ValidityWindow {
type Error = InvalidWindow;
fn try_from(value: (Option<BlockId>, Option<BlockId>)) -> Result<Self, Self::Error> {
let this = Self {
from: value.0,
to: value.1,
};
this.check_window()?;
Ok(this)
}
}
2026-03-25 14:55:23 -03:00
impl TryFrom<std::ops::Range<BlockId>> for ValidityWindow {
type Error = InvalidWindow;
fn try_from(value: std::ops::Range<BlockId>) -> Result<Self, Self::Error> {
(Some(value.start), Some(value.end)).try_into()
}
}
impl From<std::ops::RangeFrom<BlockId>> for ValidityWindow {
fn from(value: std::ops::RangeFrom<BlockId>) -> Self {
Self {
from: Some(value.start),
to: None,
}
}
}
impl From<std::ops::RangeTo<BlockId>> for ValidityWindow {
fn from(value: std::ops::RangeTo<BlockId>) -> Self {
Self {
from: None,
to: Some(value.end),
}
}
}
impl From<std::ops::RangeFull> for ValidityWindow {
fn from(_: std::ops::RangeFull) -> Self {
Self::new_unbounded()
}
}
2026-03-20 13:16:52 -03:00
#[derive(Debug, thiserror::Error, Clone, Copy, PartialEq, Eq)]
#[error("Invalid window")]
pub struct InvalidWindow;
2026-03-19 12:10:02 -03:00
#[derive(Serialize, Deserialize, Clone)]
#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))]
2025-08-14 14:09:04 -03:00
pub struct ProgramOutput {
2026-03-10 00:17:43 +03:00
/// The instruction data the program received to produce this output.
2025-11-18 01:38:47 -03:00
pub instruction_data: InstructionData,
2026-03-10 00:17:43 +03:00
/// The account pre states the program received to produce this output.
2025-08-14 14:09:04 -03:00
pub pre_states: Vec<AccountWithMetadata>,
2026-03-19 19:41:02 -03:00
/// The account post states the program execution produced.
pub post_states: Vec<AccountPostState>,
2026-03-19 19:41:02 -03:00
/// The list of chained calls to other programs.
2025-11-12 19:18:04 -03:00
pub chained_calls: Vec<ChainedCall>,
2026-03-19 19:41:02 -03:00
/// The window where the program output is valid.
2026-03-20 13:16:52 -03:00
/// Valid for block IDs in the range [from, to), where `from` is included and `to` is excluded.
/// `None` means unbounded on that side.
2026-03-19 15:03:45 -03:00
pub validity_window: ValidityWindow,
2025-08-14 14:09:04 -03:00
}
2026-03-19 12:10:02 -03:00
impl ProgramOutput {
2026-03-19 15:03:45 -03:00
#[must_use]
pub const fn new(
instruction_data: InstructionData,
pre_states: Vec<AccountWithMetadata>,
post_states: Vec<AccountPostState>,
) -> Self {
Self {
instruction_data,
pre_states,
post_states,
chained_calls: Vec::new(),
2026-03-20 13:16:52 -03:00
validity_window: ValidityWindow::new_unbounded(),
2026-03-19 15:03:45 -03:00
}
}
pub fn write(self) {
env::commit(&self);
}
#[must_use]
pub fn with_chained_calls(mut self, chained_calls: Vec<ChainedCall>) -> Self {
self.chained_calls = chained_calls;
self
}
2026-03-20 13:16:52 -03:00
pub fn valid_from_id(mut self, id: Option<BlockId>) -> Result<Self, InvalidWindow> {
2026-03-25 14:55:23 -03:00
self.validity_window.set_from(id)?;
2026-03-20 13:16:52 -03:00
Ok(self)
2026-03-19 12:10:02 -03:00
}
2026-03-20 13:16:52 -03:00
pub fn valid_until_id(mut self, id: Option<BlockId>) -> Result<Self, InvalidWindow> {
2026-03-25 14:55:23 -03:00
self.validity_window.set_to(id)?;
2026-03-20 13:16:52 -03:00
Ok(self)
2026-03-19 12:10:02 -03:00
}
}
2026-03-04 18:42:33 +03:00
/// Representation of a number as `lo + hi * 2^128`.
#[derive(PartialEq, Eq)]
struct WrappedBalanceSum {
lo: u128,
hi: u128,
}
impl WrappedBalanceSum {
/// Constructs a [`WrappedBalanceSum`] from an iterator of balances.
///
/// Returns [`None`] if balance sum overflows `lo + hi * 2^128` representation, which is not
/// expected in practical scenarios.
fn from_balances(balances: impl Iterator<Item = u128>) -> Option<Self> {
2026-03-09 18:27:56 +03:00
let mut wrapped = Self { lo: 0, hi: 0 };
2026-03-04 18:42:33 +03:00
for balance in balances {
let (new_sum, did_overflow) = wrapped.lo.overflowing_add(balance);
if did_overflow {
wrapped.hi = wrapped.hi.checked_add(1)?;
}
wrapped.lo = new_sum;
}
Some(wrapped)
}
}
#[must_use]
pub fn compute_authorized_pdas(
caller_program_id: Option<ProgramId>,
pda_seeds: &[PdaSeed],
) -> HashSet<AccountId> {
caller_program_id
.map(|caller_program_id| {
pda_seeds
.iter()
.map(|pda_seed| AccountId::from((&caller_program_id, pda_seed)))
.collect()
})
.unwrap_or_default()
}
2026-03-03 23:21:08 +03:00
/// Reads the NSSA inputs from the guest environment.
#[must_use]
2025-11-18 01:38:47 -03:00
pub fn read_nssa_inputs<T: DeserializeOwned>() -> (ProgramInput<T>, InstructionData) {
2025-08-10 18:51:55 -03:00
let pre_states: Vec<AccountWithMetadata> = env::read();
2025-09-08 19:29:56 -03:00
let instruction_words: InstructionData = env::read();
let instruction = T::deserialize(&mut Deserializer::new(instruction_words.as_ref())).unwrap();
2025-11-18 01:38:47 -03:00
(
ProgramInput {
pre_states,
instruction,
},
instruction_words,
)
2025-08-10 18:51:55 -03:00
}
2025-08-14 14:09:04 -03:00
pub fn write_nssa_outputs(
2025-11-18 01:38:47 -03:00
instruction_data: InstructionData,
pre_states: Vec<AccountWithMetadata>,
post_states: Vec<AccountPostState>,
) {
2026-03-19 15:03:45 -03:00
ProgramOutput::new(instruction_data, pre_states, post_states).write();
2025-10-29 15:34:11 -03:00
}
pub fn write_nssa_outputs_with_chained_call(
2025-11-18 01:38:47 -03:00
instruction_data: InstructionData,
2025-10-29 15:34:11 -03:00
pre_states: Vec<AccountWithMetadata>,
post_states: Vec<AccountPostState>,
2025-11-27 09:56:52 -03:00
chained_calls: Vec<ChainedCall>,
2025-10-29 15:34:11 -03:00
) {
2026-03-19 15:03:45 -03:00
ProgramOutput::new(instruction_data, pre_states, post_states)
.with_chained_calls(chained_calls)
.write();
2025-08-14 14:09:04 -03:00
}
2026-03-10 00:17:43 +03:00
/// Validates well-behaved program execution.
2025-08-06 20:05:04 -03:00
///
/// # Parameters
/// - `pre_states`: The list of input accounts, each annotated with authorization metadata.
/// - `post_states`: The list of resulting accounts after executing the program logic.
/// - `executing_program_id`: The identifier of the program that was executed.
2026-03-03 23:21:08 +03:00
#[must_use]
2025-08-10 09:57:10 -03:00
pub fn validate_execution(
2025-08-06 20:05:04 -03:00
pre_states: &[AccountWithMetadata],
post_states: &[AccountPostState],
2025-08-06 20:05:04 -03:00
executing_program_id: ProgramId,
2025-08-09 19:49:07 -03:00
) -> bool {
// 1. Check account ids are all different
if !validate_uniqueness_of_account_ids(pre_states) {
return false;
}
// 2. Lengths must match
2025-08-06 20:05:04 -03:00
if pre_states.len() != post_states.len() {
2025-08-09 19:49:07 -03:00
return false;
2025-08-06 20:05:04 -03:00
}
for (pre, post) in pre_states.iter().zip(post_states) {
// 3. Nonce must remain unchanged
if pre.account.nonce != post.account.nonce {
2025-08-09 19:49:07 -03:00
return false;
2025-08-06 20:05:04 -03:00
}
// 4. Program ownership changes are not allowed
if pre.account.program_owner != post.account.program_owner {
2025-08-09 19:49:07 -03:00
return false;
2025-08-06 20:05:04 -03:00
}
2025-09-15 16:22:48 -03:00
let account_program_owner = pre.account.program_owner;
// 5. Decreasing balance only allowed if owned by executing program
if post.account.balance < pre.account.balance
&& account_program_owner != executing_program_id
{
2025-08-09 19:49:07 -03:00
return false;
2025-08-06 20:05:04 -03:00
}
// 6. Data changes only allowed if owned by executing program or if account pre state has
2025-09-15 16:22:48 -03:00
// default values
if pre.account.data != post.account.data
2025-09-15 16:22:48 -03:00
&& pre.account != Account::default()
&& account_program_owner != executing_program_id
2025-08-06 20:05:04 -03:00
{
2025-08-09 19:49:07 -03:00
return false;
2025-08-06 20:05:04 -03:00
}
// 7. If a post state has default program owner, the pre state must have been a default
2025-11-26 00:27:20 +03:00
// account
if post.account.program_owner == DEFAULT_PROGRAM_ID && pre.account != Account::default() {
return false;
}
2025-08-06 20:05:04 -03:00
}
// 8. Total balance is preserved
let Some(total_balance_pre_states) =
WrappedBalanceSum::from_balances(pre_states.iter().map(|pre| pre.account.balance))
else {
return false;
};
let Some(total_balance_post_states) =
WrappedBalanceSum::from_balances(post_states.iter().map(|post| post.account.balance))
else {
return false;
};
2025-08-06 20:05:04 -03:00
if total_balance_pre_states != total_balance_post_states {
2025-08-09 19:49:07 -03:00
return false;
2025-08-06 20:05:04 -03:00
}
2025-08-09 19:49:07 -03:00
true
2025-08-06 20:05:04 -03:00
}
fn validate_uniqueness_of_account_ids(pre_states: &[AccountWithMetadata]) -> bool {
let number_of_accounts = pre_states.len();
let number_of_account_ids = pre_states
.iter()
2025-11-22 17:48:29 -03:00
.map(|account| &account.account_id)
.collect::<HashSet<_>>()
.len();
number_of_accounts == number_of_account_ids
}
#[cfg(test)]
mod tests {
use super::*;
2026-03-25 14:55:23 -03:00
#[test]
fn validity_window_unbounded_accepts_any_block() {
let w = ValidityWindow::new_unbounded();
assert!(w.is_valid_for_block_id(0));
assert!(w.is_valid_for_block_id(u64::MAX));
}
#[test]
fn validity_window_bounded_range_includes_from_excludes_to() {
let w: ValidityWindow = (Some(5), Some(10)).try_into().unwrap();
assert!(!w.is_valid_for_block_id(4));
assert!(w.is_valid_for_block_id(5));
assert!(w.is_valid_for_block_id(9));
assert!(!w.is_valid_for_block_id(10));
}
#[test]
fn validity_window_only_from_bound() {
let w: ValidityWindow = (Some(5), None).try_into().unwrap();
assert!(!w.is_valid_for_block_id(4));
assert!(w.is_valid_for_block_id(5));
assert!(w.is_valid_for_block_id(u64::MAX));
}
#[test]
fn validity_window_only_to_bound() {
let w: ValidityWindow = (None, Some(5)).try_into().unwrap();
assert!(w.is_valid_for_block_id(0));
assert!(w.is_valid_for_block_id(4));
assert!(!w.is_valid_for_block_id(5));
}
#[test]
fn validity_window_adjacent_bounds_are_invalid() {
// [5, 5) is an empty range — from == to
assert!(ValidityWindow::try_from((Some(5), Some(5))).is_err());
}
#[test]
fn validity_window_inverted_bounds_are_invalid() {
assert!(ValidityWindow::try_from((Some(10), Some(5))).is_err());
}
#[test]
fn validity_window_getters_match_construction() {
let w: ValidityWindow = (Some(3), Some(7)).try_into().unwrap();
assert_eq!(w.from(), Some(3));
assert_eq!(w.to(), Some(7));
}
#[test]
fn validity_window_getters_for_unbounded() {
let w = ValidityWindow::new_unbounded();
assert_eq!(w.from(), None);
assert_eq!(w.to(), None);
}
#[test]
fn validity_window_from_range() {
let w = ValidityWindow::try_from(5u64..10).unwrap();
assert_eq!(w.from(), Some(5));
assert_eq!(w.to(), Some(10));
}
#[test]
fn validity_window_from_range_empty_is_invalid() {
assert!(ValidityWindow::try_from(5u64..5).is_err());
}
#[test]
fn validity_window_from_range_inverted_is_invalid() {
assert!(ValidityWindow::try_from(10u64..5).is_err());
}
#[test]
fn validity_window_from_range_from() {
let w: ValidityWindow = (5u64..).into();
assert_eq!(w.from(), Some(5));
assert_eq!(w.to(), None);
}
#[test]
fn validity_window_from_range_to() {
let w: ValidityWindow = (..10u64).into();
assert_eq!(w.from(), None);
assert_eq!(w.to(), Some(10));
}
#[test]
fn validity_window_from_range_full() {
let w: ValidityWindow = (..).into();
assert_eq!(w.from(), None);
assert_eq!(w.to(), None);
}
#[test]
2026-03-04 18:42:33 +03:00
fn post_state_new_with_claim_constructor() {
let account = Account {
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
balance: 1337,
data: vec![0xde, 0xad, 0xbe, 0xef].try_into().unwrap(),
2026-03-18 13:10:36 -04:00
nonce: 10_u128.into(),
};
2025-12-03 16:39:33 -03:00
let account_post_state = AccountPostState::new_claimed(account.clone());
assert_eq!(account, account_post_state.account);
2025-12-03 16:39:33 -03:00
assert!(account_post_state.requires_claim());
}
2025-12-03 16:39:33 -03:00
#[test]
2026-03-04 18:42:33 +03:00
fn post_state_new_without_claim_constructor() {
2025-12-03 16:39:33 -03:00
let account = Account {
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
balance: 1337,
data: vec![0xde, 0xad, 0xbe, 0xef].try_into().unwrap(),
2026-03-18 13:10:36 -04:00
nonce: 10_u128.into(),
2025-12-03 16:39:33 -03:00
};
let account_post_state = AccountPostState::new(account.clone());
assert_eq!(account, account_post_state.account);
assert!(!account_post_state.requires_claim());
}
2025-12-04 16:26:40 -03:00
#[test]
2026-03-04 18:42:33 +03:00
fn post_state_account_getter() {
2025-12-04 16:26:40 -03:00
let mut account = Account {
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
balance: 1337,
data: vec![0xde, 0xad, 0xbe, 0xef].try_into().unwrap(),
2026-03-18 13:10:36 -04:00
nonce: 10_u128.into(),
2025-12-04 16:26:40 -03:00
};
let mut account_post_state = AccountPostState::new(account.clone());
assert_eq!(account_post_state.account(), &account);
assert_eq!(account_post_state.account_mut(), &mut account);
}
}