Skip to main content

miden_protocol/account/patch/storage/
value_patch.rs

1use super::slot_patch::MergeOutcome;
2use crate::Word;
3use crate::account::{StoragePatchOperation, StorageSlotName};
4use crate::errors::AccountPatchError;
5use crate::utils::serde::{
6    ByteReader,
7    ByteWriter,
8    Deserializable,
9    DeserializationError,
10    Serializable,
11};
12
13// STORAGE VALUE PATCH
14// ================================================================================================
15
16/// The patch of a single value storage slot.
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub enum StorageValuePatch {
19    /// Records the creation of the slot with the given value.
20    Create { value: Word },
21
22    /// Records that an existing slot was set to the given value.
23    Update { value: Word },
24
25    /// Records that the slot was removed.
26    Remove,
27}
28
29impl StorageValuePatch {
30    // CONSTANTS
31    // ----------------------------------------------------------------------------------------
32
33    const CREATE: u8 = 0;
34    const UPDATE: u8 = 1;
35    const REMOVE: u8 = 2;
36
37    // ACCESSORS
38    // ----------------------------------------------------------------------------------------
39
40    /// Returns the new value of the slot for [`StorageValuePatch::Create`] and
41    /// [`StorageValuePatch::Update`], or `None` for [`StorageValuePatch::Remove`].
42    pub fn value(&self) -> Option<Word> {
43        match self {
44            StorageValuePatch::Create { value } | StorageValuePatch::Update { value } => {
45                Some(*value)
46            },
47            StorageValuePatch::Remove => None,
48        }
49    }
50
51    /// Returns the [`StoragePatchOperation`] that this patch represents.
52    pub fn patch_op(&self) -> StoragePatchOperation {
53        match self {
54            StorageValuePatch::Create { .. } => StoragePatchOperation::Create,
55            StorageValuePatch::Update { .. } => StoragePatchOperation::Update,
56            StorageValuePatch::Remove => StoragePatchOperation::Remove,
57        }
58    }
59
60    // HELPERS
61    // ----------------------------------------------------------------------------------------
62
63    /// Returns the value to commit to.
64    ///
65    /// Slot removal commits to [`Word::empty`].
66    pub(super) fn committed_value(&self) -> Word {
67        self.value().unwrap_or_default()
68    }
69
70    /// Maps a [`Word::empty`] value to `None` and any other value to `Some`, so that empty values
71    /// serialize compactly as a single `None` tag instead of a full [`Word`].
72    fn compact_value(value: &Word) -> Option<&Word> {
73        (!value.is_empty()).then_some(value)
74    }
75
76    /// Merges `other` into `self`, with `other` taking precedence.
77    ///
78    /// A slot that was created and then updated remains created (with the updated value). A slot
79    /// that was created and then removed cancels out, signalled via [`MergeOutcome::Remove`].
80    pub(super) fn merge(
81        &mut self,
82        slot_name: &StorageSlotName,
83        other: Self,
84    ) -> Result<MergeOutcome, AccountPatchError> {
85        match (self, other) {
86            // (Create, _) patterns
87            // ------------------------------------------------------------------------------------
88            (StorageValuePatch::Create { .. }, StorageValuePatch::Create { .. }) => {
89                return Err(AccountPatchError::StoragePatchMergeDoubleCreate(slot_name.clone()));
90            },
91            (
92                StorageValuePatch::Create { value: current },
93                StorageValuePatch::Update { value: incoming },
94            ) => *current = incoming,
95            (StorageValuePatch::Create { .. }, StorageValuePatch::Remove) => {
96                return Ok(MergeOutcome::Remove);
97            },
98
99            // (Update, _) patterns
100            // ------------------------------------------------------------------------------------
101            (StorageValuePatch::Update { .. }, StorageValuePatch::Create { .. }) => {
102                return Err(AccountPatchError::StoragePatchMergeCreateAfterUpdate(
103                    slot_name.clone(),
104                ));
105            },
106            (
107                StorageValuePatch::Update { value: current },
108                StorageValuePatch::Update { value: incoming },
109            ) => *current = incoming,
110            (current @ StorageValuePatch::Update { .. }, StorageValuePatch::Remove) => {
111                *current = StorageValuePatch::Remove
112            },
113
114            // (Remove, _) patterns
115            // ------------------------------------------------------------------------------------
116            (current @ StorageValuePatch::Remove, incoming @ StorageValuePatch::Create { .. }) => {
117                *current = incoming;
118            },
119            (StorageValuePatch::Remove, StorageValuePatch::Update { .. }) => {
120                return Err(AccountPatchError::StoragePatchMergeUpdateAfterRemove(
121                    slot_name.clone(),
122                ));
123            },
124            (StorageValuePatch::Remove, StorageValuePatch::Remove) => {
125                return Err(AccountPatchError::StoragePatchMergeDoubleRemove(slot_name.clone()));
126            },
127        }
128
129        Ok(MergeOutcome::Keep)
130    }
131}
132
133impl Serializable for StorageValuePatch {
134    fn write_into<W: ByteWriter>(&self, target: &mut W) {
135        match self {
136            StorageValuePatch::Create { value } => {
137                target.write_u8(Self::CREATE);
138                target.write(Self::compact_value(value));
139            },
140            StorageValuePatch::Update { value } => {
141                target.write_u8(Self::UPDATE);
142                target.write(Self::compact_value(value));
143            },
144            StorageValuePatch::Remove => {
145                target.write_u8(Self::REMOVE);
146            },
147        }
148    }
149
150    fn get_size_hint(&self) -> usize {
151        let tag_size = 0u8.get_size_hint();
152        match self {
153            StorageValuePatch::Create { value } | StorageValuePatch::Update { value } => {
154                tag_size + Self::compact_value(value).get_size_hint()
155            },
156            StorageValuePatch::Remove => tag_size,
157        }
158    }
159}
160
161impl Deserializable for StorageValuePatch {
162    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
163        match source.read_u8()? {
164            Self::CREATE => Ok(Self::Create {
165                value: source.read::<Option<Word>>()?.unwrap_or_default(),
166            }),
167            Self::UPDATE => Ok(Self::Update {
168                value: source.read::<Option<Word>>()?.unwrap_or_default(),
169            }),
170            Self::REMOVE => Ok(Self::Remove),
171            other => Err(DeserializationError::InvalidValue(format!(
172                "unknown storage value patch variant {other}"
173            ))),
174        }
175    }
176}