miden_objects/account/code/
procedure.rs

1use alloc::string::ToString;
2
3use super::{Digest, Felt};
4use crate::{
5    AccountError, FieldElement,
6    utils::serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
7};
8
9// ACCOUNT PROCEDURE INFO
10// ================================================================================================
11
12/// Information about a procedure exposed in a public account interface.
13///
14/// The info included the MAST root of the procedure, the storage offset applied to all account
15/// storage-related accesses made by this procedure and the storage size allowed to be accessed
16/// by this procedure.
17///
18/// The offset is applied to any accesses made from within the procedure to the associated
19/// account's storage. For example, if storage offset for a procedure is set ot 1, a call
20/// to the account::get_item(storage_slot=4) made from this procedure would actually access
21/// storage slot with index 5.
22///
23/// The size is used to limit how many storage slots a given procedure can access in the associated
24/// account's storage. For example, if storage size for a procedure is set to 3, the procedure will
25/// be bounded to access storage slots in the range [storage_offset, storage_offset + 3 - 1].
26/// Furthermore storage_size = 0 indicates that a procedure does not need to access storage.
27#[derive(Debug, PartialEq, Eq, Clone, Copy)]
28pub struct AccountProcedureInfo {
29    mast_root: Digest,
30    storage_offset: u8,
31    storage_size: u8,
32}
33
34impl AccountProcedureInfo {
35    /// The number of field elements needed to represent an [AccountProcedureInfo] in kernel memory.
36    pub const NUM_ELEMENTS_PER_PROC: usize = 8;
37
38    // CONSTRUCTOR
39    // --------------------------------------------------------------------------------------------
40
41    /// Returns a new instance of an [AccountProcedureInfo].
42    ///
43    /// # Errors
44    /// - If `storage_size` is 0 and `storage_offset` is not 0.
45    /// - If `storage_size + storage_offset` is greater than `MAX_NUM_STORAGE_SLOTS`.
46    pub fn new(
47        mast_root: Digest,
48        storage_offset: u8,
49        storage_size: u8,
50    ) -> Result<Self, AccountError> {
51        if storage_size == 0 && storage_offset != 0 {
52            return Err(AccountError::PureProcedureWithStorageOffset);
53        }
54
55        // Check if the addition would exceed AccountStorage::MAX_NUM_STORAGE_SLOTS (= 255) which is
56        // the case if the addition overflows.
57        if storage_offset.checked_add(storage_size).is_none() {
58            return Err(AccountError::StorageOffsetPlusSizeOutOfBounds(
59                storage_offset as u16 + storage_size as u16,
60            ));
61        }
62
63        Ok(Self { mast_root, storage_offset, storage_size })
64    }
65
66    // PUBLIC ACCESSORS
67    // --------------------------------------------------------------------------------------------
68
69    /// Returns a reference to the procedure's mast root.
70    pub fn mast_root(&self) -> &Digest {
71        &self.mast_root
72    }
73
74    /// Returns the procedure's storage offset.
75    pub fn storage_offset(&self) -> u8 {
76        self.storage_offset
77    }
78
79    /// Returns the procedure's storage size.
80    pub fn storage_size(&self) -> u8 {
81        self.storage_size
82    }
83}
84
85impl From<AccountProcedureInfo> for [Felt; 8] {
86    fn from(value: AccountProcedureInfo) -> Self {
87        let mut result = [Felt::ZERO; 8];
88
89        // copy mast_root into first 4 elements
90        result[0..4].copy_from_slice(value.mast_root().as_elements());
91
92        // copy the storage offset into value[4]
93        result[4] = Felt::from(value.storage_offset);
94
95        // copy the storage size into value[5]
96        result[5] = Felt::from(value.storage_size);
97
98        result
99    }
100}
101
102impl TryFrom<[Felt; 8]> for AccountProcedureInfo {
103    type Error = AccountError;
104
105    fn try_from(value: [Felt; 8]) -> Result<Self, Self::Error> {
106        // get mast_root from first 4 elements
107        let mast_root = Digest::from(<[Felt; 4]>::try_from(&value[0..4]).unwrap());
108
109        // get storage_offset form value[4]
110        let storage_offset: u8 = value[4].try_into().map_err(|_| {
111            AccountError::AccountCodeProcedureStorageOffsetTooLarge(mast_root, value[4])
112        })?;
113
114        // get storage_size form value[5]
115        let storage_size: u8 = value[5].try_into().map_err(|_| {
116            AccountError::AccountCodeProcedureStorageSizeTooLarge(mast_root, value[5])
117        })?;
118
119        // Check if the remaining values are 0
120        if value[6] != Felt::ZERO || value[7] != Felt::ZERO {
121            return Err(AccountError::AccountCodeProcedureInvalidPadding(mast_root));
122        }
123
124        Ok(Self { mast_root, storage_offset, storage_size })
125    }
126}
127
128impl Serializable for AccountProcedureInfo {
129    fn write_into<W: ByteWriter>(&self, target: &mut W) {
130        target.write(self.mast_root);
131        target.write_u8(self.storage_offset);
132        target.write_u8(self.storage_size)
133    }
134
135    fn get_size_hint(&self) -> usize {
136        self.mast_root.get_size_hint()
137            + self.storage_offset.get_size_hint()
138            + self.storage_size.get_size_hint()
139    }
140}
141
142impl Deserializable for AccountProcedureInfo {
143    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
144        let mast_root: Digest = source.read()?;
145        let storage_offset = source.read_u8()?;
146        let storage_size = source.read_u8()?;
147        Self::new(mast_root, storage_offset, storage_size)
148            .map_err(|err| DeserializationError::InvalidValue(err.to_string()))
149    }
150}
151
152// TESTS
153// ================================================================================================
154
155#[cfg(test)]
156mod tests {
157
158    use miden_crypto::utils::{Deserializable, Serializable};
159    use vm_core::Felt;
160
161    use crate::account::{AccountCode, AccountProcedureInfo};
162
163    #[test]
164    fn test_from_to_account_procedure() {
165        let account_code = AccountCode::mock();
166
167        let procedure = account_code.procedures()[0];
168
169        // from procedure to [Felt; 8]
170        let felts: [Felt; 8] = procedure.into();
171
172        // try_from [Felt; 8] to procedure
173        let final_procedure: AccountProcedureInfo = felts.try_into().unwrap();
174
175        assert_eq!(procedure, final_procedure);
176    }
177
178    #[test]
179    fn test_serde_account_procedure() {
180        let account_code = AccountCode::mock();
181
182        let serialized = account_code.procedures()[0].to_bytes();
183        let deserialized = AccountProcedureInfo::read_from_bytes(&serialized).unwrap();
184
185        assert_eq!(account_code.procedures()[0], deserialized);
186    }
187}