ab_system_contract_state/
lib.rs

1#![no_std]
2
3use ab_contracts_common::env::Env;
4use ab_contracts_common::{Address, ContractError};
5use ab_contracts_io_type::trivial_type::TrivialType;
6use ab_contracts_io_type::variable_bytes::VariableBytes;
7use ab_contracts_macros::contract;
8use core::mem::MaybeUninit;
9
10// TODO: How/where should this limit be defined?
11pub const RECOMMENDED_STATE_CAPACITY: u32 = 1024;
12
13/// Helper function that calls provided function with new empty state buffer
14#[inline(always)]
15pub fn with_state_buffer<F, R>(f: F) -> R
16where
17    F: FnOnce(&mut VariableBytes<RECOMMENDED_STATE_CAPACITY>) -> R,
18{
19    let mut state_bytes = [MaybeUninit::uninit(); RECOMMENDED_STATE_CAPACITY as usize];
20    let mut state_size = 0;
21    let mut new_state = VariableBytes::from_uninit(&mut state_bytes, &mut state_size);
22    f(&mut new_state)
23}
24
25#[derive(Debug, Copy, Clone, TrivialType)]
26#[repr(C)]
27pub struct State;
28
29#[contract]
30impl State {
31    /// Initialize state.
32    ///
33    /// Similar to [`State::write()`], but returns error if the state is not empty.
34    #[update]
35    pub fn initialize(
36        #[env] env: &mut Env<'_>,
37        #[slot] (address, contract_state): (
38            &Address,
39            &mut VariableBytes<RECOMMENDED_STATE_CAPACITY>,
40        ),
41        #[input] state: &VariableBytes<RECOMMENDED_STATE_CAPACITY>,
42    ) -> Result<(), ContractError> {
43        if !Self::is_empty(contract_state) {
44            return Err(ContractError::Conflict);
45        }
46
47        Self::write(env, (address, contract_state), state)
48    }
49
50    /// Write state.
51    ///
52    /// Only direct caller is allowed to write its own state for security reasons.
53    #[update]
54    pub fn write(
55        #[env] env: &mut Env<'_>,
56        // TODO: Allow to replace slot pointer with input pointer to make the input size zero and
57        //  allow zero-copy
58        #[slot] (address, contract_state): (
59            &Address,
60            &mut VariableBytes<RECOMMENDED_STATE_CAPACITY>,
61        ),
62        #[input] new_state: &VariableBytes<RECOMMENDED_STATE_CAPACITY>,
63    ) -> Result<(), ContractError> {
64        // TODO: Check shard?
65        if env.caller() != address {
66            return Err(ContractError::Forbidden);
67        }
68
69        if !contract_state.copy_from(new_state) {
70            return Err(ContractError::BadInput);
71        }
72
73        Ok(())
74    }
75
76    /// Read state
77    #[view]
78    pub fn read(
79        #[slot] contract_state: &VariableBytes<RECOMMENDED_STATE_CAPACITY>,
80        #[output] state: &mut VariableBytes<RECOMMENDED_STATE_CAPACITY>,
81    ) -> Result<(), ContractError> {
82        if state.copy_from(contract_state) {
83            Ok(())
84        } else {
85            Err(ContractError::BadInput)
86        }
87    }
88
89    /// Check if the state is empty
90    #[view]
91    pub fn is_empty(#[slot] contract_state: &VariableBytes<RECOMMENDED_STATE_CAPACITY>) -> bool {
92        contract_state.size() == 0
93    }
94}