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);
}
}