miden_objects/account/code/
procedure.rs

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