miden_objects/accounts/code/
procedure.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
use alloc::string::ToString;

use vm_core::{
    utils::{ByteReader, ByteWriter, Deserializable, Serializable},
    FieldElement,
};
use vm_processor::DeserializationError;

use super::{Digest, Felt};
use crate::{accounts::AccountStorage, AccountError};

// ACCOUNT PROCEDURE INFO
// ================================================================================================

/// Information about a procedure exposed in a public account interface.
///
/// The info included the MAST root of the procedure, the storage offset applied to all account
/// storage-related accesses made by this procedure and the storage size allowed to be accessed
/// by this procedure.
///
/// The offset is applied to any accesses made from within the procedure to the associated
/// account's storage. For example, if storage offset for a procedure is set ot 1, a call
/// to the account::get_item(storage_slot=4) made from this procedure would actually access
/// storage slot with index 5.
///
/// The size is used to limit how many storage slots a given procedure can access in the associated
/// account's storage. For example, if storage size for a procedure is set to 3, the procedure will
/// be bounded to access storage slots in the range [storage_offset, storage_offset + 3 - 1].
/// Furthermore storage_size = 0 indicates that a procedure does not need to access storage.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct AccountProcedureInfo {
    mast_root: Digest,
    storage_offset: u8,
    storage_size: u8,
}

impl AccountProcedureInfo {
    /// The number of field elements needed to represent an [AccountProcedureInfo] in kernel memory.
    pub const NUM_ELEMENTS_PER_PROC: usize = 8;

    // CONSTRUCTOR
    // --------------------------------------------------------------------------------------------

    /// Returns a new instance of an [AccountProcedureInfo].
    ///
    /// # Errors
    /// - If `storage_size` is 0 and `storage_offset` is not 0.
    /// - If `storage_size + storage_offset` is greater than `MAX_NUM_STORAGE_SLOTS`.
    pub fn new(
        mast_root: Digest,
        storage_offset: u8,
        storage_size: u8,
    ) -> Result<Self, AccountError> {
        if storage_size == 0 && storage_offset != 0 {
            return Err(AccountError::PureProcedureWithStorageOffset);
        }

        // Check if the addition would exceed AccountStorage::MAX_NUM_STORAGE_SLOTS (= 255) which is
        // the case if the addition overflows.
        if storage_offset.checked_add(storage_size).is_none() {
            return Err(AccountError::StorageOffsetOutOfBounds {
                max: AccountStorage::MAX_NUM_STORAGE_SLOTS as u8,
                actual: storage_offset as u16 + storage_size as u16,
            });
        }

        Ok(Self { mast_root, storage_offset, storage_size })
    }

    // PUBLIC ACCESSORS
    // --------------------------------------------------------------------------------------------

    /// Returns a reference to the procedure's mast root.
    pub fn mast_root(&self) -> &Digest {
        &self.mast_root
    }

    /// Returns the procedure's storage offset.
    pub fn storage_offset(&self) -> u8 {
        self.storage_offset
    }

    /// Returns the procedure's storage size.
    pub fn storage_size(&self) -> u8 {
        self.storage_size
    }
}

impl From<AccountProcedureInfo> for [Felt; 8] {
    fn from(value: AccountProcedureInfo) -> Self {
        let mut result = [Felt::ZERO; 8];

        // copy mast_root into first 4 elements
        result[0..4].copy_from_slice(value.mast_root().as_elements());

        // copy the storage offset into value[4]
        result[4] = Felt::from(value.storage_offset);

        // copy the storage size into value[5]
        result[5] = Felt::from(value.storage_size);

        result
    }
}

impl TryFrom<[Felt; 8]> for AccountProcedureInfo {
    type Error = AccountError;

    fn try_from(value: [Felt; 8]) -> Result<Self, Self::Error> {
        // get mast_root from first 4 elements
        let mast_root = Digest::from(<[Felt; 4]>::try_from(&value[0..4]).unwrap());

        // get storage_offset form value[4]
        let storage_offset: u8 = value[4]
            .try_into()
            .map_err(|_| AccountError::AccountCodeProcedureInvalidStorageOffset)?;

        // get storage_size form value[5]
        let storage_size: u8 = value[5]
            .try_into()
            .map_err(|_| AccountError::AccountCodeProcedureInvalidStorageSize)?;

        // Check if the remaining values are 0
        if value[6] != Felt::ZERO || value[7] != Felt::ZERO {
            return Err(AccountError::AccountCodeProcedureInvalidPadding);
        }

        Ok(Self { mast_root, storage_offset, storage_size })
    }
}

impl Serializable for AccountProcedureInfo {
    fn write_into<W: ByteWriter>(&self, target: &mut W) {
        target.write(self.mast_root);
        target.write_u8(self.storage_offset);
        target.write_u8(self.storage_size)
    }

    fn get_size_hint(&self) -> usize {
        self.mast_root.get_size_hint()
            + self.storage_offset.get_size_hint()
            + self.storage_size.get_size_hint()
    }
}

impl Deserializable for AccountProcedureInfo {
    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
        let mast_root: Digest = source.read()?;
        let storage_offset = source.read_u8()?;
        let storage_size = source.read_u8()?;
        Self::new(mast_root, storage_offset, storage_size)
            .map_err(|err| DeserializationError::InvalidValue(err.to_string()))
    }
}

// TESTS
// ================================================================================================

#[cfg(test)]
mod tests {

    use miden_crypto::utils::{Deserializable, Serializable};
    use vm_core::Felt;

    use crate::accounts::{AccountCode, AccountProcedureInfo};

    #[test]
    fn test_from_to_account_procedure() {
        let account_code = AccountCode::mock();

        let procedure = account_code.procedures()[0].clone();

        // from procedure to [Felt; 8]
        let felts: [Felt; 8] = procedure.clone().into();

        // try_from [Felt; 8] to procedure
        let final_procedure: AccountProcedureInfo = felts.try_into().unwrap();

        assert_eq!(procedure, final_procedure);
    }

    #[test]
    fn test_serde_account_procedure() {
        let account_code = AccountCode::mock();

        let serialized = account_code.procedures()[0].to_bytes();
        let deserialized = AccountProcedureInfo::read_from_bytes(&serialized).unwrap();

        assert_eq!(account_code.procedures()[0], deserialized);
    }
}