Skip to main content

ab_executor_slots/
lib.rs

1#![expect(
2    clippy::rest_pattern_accessible_field,
3    reason = "Intentionally not needing other fields, too verbose otherwise"
4)]
5#![no_std]
6
7#[cfg(test)]
8mod tests;
9
10extern crate alloc;
11
12use ab_aligned_buffer::{OwnedAlignedBuffer, SharedAlignedBuffer};
13use ab_core_primitives::address::Address;
14use alloc::boxed::Box;
15use core::mem::ManuallyDrop;
16use replace_with::replace_with_or_abort;
17use smallvec::SmallVec;
18use tracing::debug;
19
20/// Small number of elements to store without heap allocation in some data structures.
21///
22/// This is both large enough for many practical use cases and small enough to bring significant
23/// performance improvement.
24const INLINE_SIZE: usize = 8;
25/// It should be rare that more than 2 contracts are created in the same transaction
26const NEW_CONTRACTS_INLINE: usize = 2;
27/// Modified slots with at least this much unused capacity are copied into buffers of the exact
28/// size when extracted with [`Slots::into_slots()`].
29///
30/// Slot buffers are allocated with the recommended capacity of their type for contracts to write
31/// into, which may be much larger than what was actually written.
32const COMPACTION_THRESHOLD: u32 = 4096;
33
34/// Key of the slot in [`Slots`]
35#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
36pub struct SlotKey {
37    /// Owner of the slot
38    pub owner: Address,
39    /// Contract that manages the slot
40    pub contract: Address,
41}
42
43/// Opaque slot index, used to identify a used slot [`Slots`]
44#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
45pub struct SlotIndex(usize);
46
47impl From<SlotIndex> for usize {
48    #[inline(always)]
49    fn from(value: SlotIndex) -> Self {
50        value.0
51    }
52}
53
54#[derive(Debug, Clone)]
55pub enum Slot {
56    ReadOnly {
57        key: SlotKey,
58        buffer: SharedAlignedBuffer,
59    },
60    ReadWrite {
61        key: SlotKey,
62        buffer: SharedAlignedBuffer,
63    },
64}
65
66impl Slot {
67    fn is_null_contract(&self) -> bool {
68        let slot_key = match self {
69            Slot::ReadOnly { key, .. } => key,
70            Slot::ReadWrite { key, .. } => key,
71        };
72
73        slot_key.contract == Address::NULL
74    }
75}
76
77#[derive(Debug, Clone)]
78enum SlotState {
79    /// Original slot as given to the execution environment that can only be read.
80    ///
81    /// It never changes, so reads of it are not tracked.
82    ReadOnly(SharedAlignedBuffer),
83    /// Original slot as given to the execution environment, not accessed yet
84    Original(SharedAlignedBuffer),
85    /// Original slot as given to the execution environment that is currently being accessed
86    OriginalReadOnly(SharedAlignedBuffer),
87    /// Previously modified slot
88    Modified(SharedAlignedBuffer),
89    /// Previously modified slot that is currently being accessed for reads
90    ModifiedReadOnly(SharedAlignedBuffer),
91    /// Original slot as given to the execution environment that is currently being modified
92    OriginalReadWrite {
93        buffer: OwnedAlignedBuffer,
94        /// What it was in [`Self::Original`] before becoming [`Self::OriginalReadWrite`]
95        previous: SharedAlignedBuffer,
96    },
97    /// Previously modified slot that is currently being modified
98    ModifiedReadWrite {
99        buffer: OwnedAlignedBuffer,
100        /// What it was in [`Self::Modified`] before becoming [`Self::ModifiedReadWrite`]
101        previous: SharedAlignedBuffer,
102    },
103}
104
105#[derive(Debug, Clone)]
106enum SlotAccessKind {
107    /// Slot is currently accessed for reads
108    ReadOnly,
109    /// Slot is currently accessed for writes
110    ReadWrite,
111    /// Modification by nested instances that already integrated their changes into the parent,
112    /// with the state of the slot from before the modification, such that the parent can roll it
113    /// back
114    Integrated { previous: SlotState },
115}
116
117#[derive(Debug, Clone)]
118struct SlotAccess {
119    slot_index: SlotIndex,
120    kind: SlotAccessKind,
121}
122
123/// Find the active access of a slot: `Some(false)` for read-only and `Some(true)` for read-write.
124///
125/// A slot can have multiple accesses, but only the latest one can be active.
126#[inline(always)]
127fn active_access(slot_access: &[SlotAccess], slot_index: SlotIndex) -> Option<bool> {
128    let slot_access = slot_access
129        .iter()
130        .rev()
131        .find(|slot_access| slot_access.slot_index == slot_index)?;
132
133    match slot_access.kind {
134        SlotAccessKind::ReadOnly => Some(false),
135        SlotAccessKind::ReadWrite => Some(true),
136        SlotAccessKind::Integrated { .. } => None,
137    }
138}
139
140#[derive(Debug, Clone)]
141struct Inner {
142    slots: SmallVec<[(SlotKey, SlotState); INLINE_SIZE]>,
143    /// Accesses of slots, including those of nested instances that already integrated their
144    /// changes into the parent, which may still need to roll them back
145    slot_access: SmallVec<[SlotAccess; INLINE_SIZE]>,
146    /// The list of new addresses that were created during transaction processing and couldn't be
147    /// known beforehand.
148    ///
149    /// Addresses in this list are allowed to create slots for any owner, and other contacts are
150    /// allowed to create slots owned by these addresses.
151    new_contracts: SmallVec<[Address; NEW_CONTRACTS_INLINE]>,
152}
153
154/// Collection of slots, primarily for the execution environment
155#[derive(Debug, Clone)]
156pub struct Slots(Box<Inner>);
157
158impl Slots {
159    /// Create a new instance from a hashmap containing existing slots.
160    ///
161    /// Only slots that are present in the input can be modified. The only exception is slots for
162    /// owners created during runtime and initialized with [`Self::add_new_contract()`].
163    ///
164    /// "Empty" slots must still have a value in the form of an empty [`SharedAlignedBuffer`].
165    #[inline(always)]
166    pub fn new<I>(slots: I) -> Self
167    where
168        I: IntoIterator<Item = Slot>,
169    {
170        let slots = slots
171            .into_iter()
172            .filter_map(|slot| {
173                // `Address::NULL` is used for `#[tmp]` and is ephemeral. Reads and writes are
174                // allowed for any owner, and they will all be thrown away after transaction
175                // processing if finished.
176                if slot.is_null_contract() {
177                    return None;
178                }
179
180                Some(match slot {
181                    Slot::ReadOnly { key, buffer } => {
182                        // Make sure it can't be written to at all
183                        (key, SlotState::ReadOnly(buffer))
184                    }
185                    Slot::ReadWrite { key, buffer } => (key, SlotState::Original(buffer)),
186                })
187            })
188            .collect();
189
190        let inner = Inner {
191            slots,
192            slot_access: SmallVec::new(),
193            new_contracts: SmallVec::new(),
194        };
195
196        Self(Box::new(inner))
197    }
198
199    /// Create a new read-write [`NestedSlots`] instance.
200    ///
201    /// Changes of the nested instance are reset when it is dropped, unless they are integrated into
202    /// the parent with [`NestedSlots::commit()`] first.
203    #[inline(always)]
204    pub fn new_nested_rw(&mut self) -> NestedSlots<'_> {
205        let parent_slots_len = self.0.slots.len();
206        let parent_new_contracts_len = self.0.new_contracts.len();
207
208        NestedSlots(NestedSlotsInner::ReadWrite {
209            inner: &mut self.0,
210            parent_slot_access_len: 0,
211            parent_slots_len,
212            parent_new_contracts_len,
213            parent_slot_access_start: None,
214        })
215    }
216
217    /// Create a new read-only [`NestedSlots`] instance
218    #[inline(always)]
219    pub fn new_nested_ro(&self) -> NestedSlots<'_> {
220        NestedSlots(NestedSlotsInner::ReadOnly { inner: &self.0 })
221    }
222
223    /// Add a new contract that didn't exist before.
224    ///
225    /// In contrast to contracts in [`Self::new()`], this contract will be allowed to have any slots
226    /// related to it being modified.
227    ///
228    /// Returns `false` if a contract already exits in a map, which is also considered as an access
229    /// violation.
230    #[must_use = "Check if contract already exists in a map"]
231    #[inline(always)]
232    pub fn add_new_contract(&mut self, owner: Address) -> bool {
233        let new_contracts = &mut self.0.new_contracts;
234
235        if new_contracts.contains(&owner) {
236            debug!(?owner, "Not adding a new contract duplicate");
237            return false;
238        }
239
240        new_contracts.push(owner);
241        true
242    }
243
244    /// Iterate over all slots in the collection
245    #[inline]
246    pub fn iter(&self) -> impl ExactSizeIterator<Item = (&SlotKey, &SharedAlignedBuffer)> + '_ {
247        self.0.slots.iter().map(|(slot_key, slot)| match slot {
248            SlotState::ReadOnly(buffer) => (slot_key, buffer),
249            SlotState::Original(buffer) => (slot_key, buffer),
250            SlotState::OriginalReadOnly(_) => {
251                unreachable!("Only original and modified slots can exist at the `Slots` level; qed")
252            }
253            SlotState::Modified(buffer) => (slot_key, buffer),
254            SlotState::ModifiedReadOnly(_) => {
255                unreachable!("Only original and modified slots can exist at the `Slots` level; qed")
256            }
257            SlotState::OriginalReadWrite { .. } => {
258                unreachable!("Only original and modified slots can exist at the `Slots` level; qed")
259            }
260            SlotState::ModifiedReadWrite { .. } => {
261                unreachable!("Only original and modified slots can exist at the `Slots` level; qed")
262            }
263        })
264    }
265
266    /// Iterate over modified slots in the collection
267    #[inline]
268    pub fn iter_modified(&self) -> impl Iterator<Item = (&SlotKey, &SharedAlignedBuffer)> + '_ {
269        self.0
270            .slots
271            .iter()
272            .filter_map(|(slot_key, slot)| match slot {
273                SlotState::ReadOnly(_) => None,
274                SlotState::Original(_) => None,
275                SlotState::OriginalReadOnly(_) => unreachable!(
276                    "Only original and modified slots can exist at the `Slots` level; qed"
277                ),
278                SlotState::Modified(buffer) => Some((slot_key, buffer)),
279                SlotState::ModifiedReadOnly(_) => unreachable!(
280                    "Only original and modified slots can exist at the `Slots` level; qed"
281                ),
282                SlotState::OriginalReadWrite { .. } => unreachable!(
283                    "Only original and modified slots can exist at the `Slots` level; qed"
284                ),
285                SlotState::ModifiedReadWrite { .. } => unreachable!(
286                    "Only original and modified slots can exist at the `Slots` level; qed"
287                ),
288            })
289    }
290
291    /// Extract all slots in the collection
292    #[inline]
293    pub fn into_slots(self) -> impl ExactSizeIterator<Item = (SlotKey, SharedAlignedBuffer)> {
294        self.0.slots.into_iter().map(|(slot_key, slot)| match slot {
295            SlotState::ReadOnly(buffer) => (slot_key, buffer),
296            SlotState::Original(buffer) => (slot_key, buffer),
297            SlotState::OriginalReadOnly(_) => {
298                unreachable!("Only original and modified slots can exist at the `Slots` level; qed")
299            }
300            SlotState::Modified(buffer) => {
301                if buffer.capacity() - buffer.len() >= COMPACTION_THRESHOLD {
302                    // Modified slots with a lot of unused capacity are copied into buffers of the
303                    // exact size to reduce peak memory usage
304                    (slot_key, SharedAlignedBuffer::from_bytes(buffer.as_slice()))
305                } else {
306                    (slot_key, buffer)
307                }
308            }
309            SlotState::ModifiedReadOnly(_) => {
310                unreachable!("Only original and modified slots can exist at the `Slots` level; qed")
311            }
312            SlotState::OriginalReadWrite { .. } => {
313                unreachable!("Only original and modified slots can exist at the `Slots` level; qed")
314            }
315            SlotState::ModifiedReadWrite { .. } => {
316                unreachable!("Only original and modified slots can exist at the `Slots` level; qed")
317            }
318        })
319    }
320}
321
322/// Container for `Slots` just to not expose this enum to the outside
323#[derive(Debug)]
324enum NestedSlotsInner<'a> {
325    /// Similar to [`Self::Original`], but has a parent (another read-write instance or original)
326    ReadWrite {
327        inner: &'a mut Inner,
328        parent_slot_access_len: usize,
329        /// Slots added after this length are removed on reset
330        parent_slots_len: usize,
331        /// New contracts added after this length are removed on reset
332        parent_new_contracts_len: usize,
333        /// Accesses of the parent start at this index, `None` when the parent is [`Slots`]
334        parent_slot_access_start: Option<usize>,
335    },
336    /// Read-only instance, non-exclusive access to [`Inner`], but not allowed to modify anything
337    ReadOnly { inner: &'a Inner },
338}
339
340#[derive(Debug)]
341pub struct NestedSlots<'a>(NestedSlotsInner<'a>);
342
343impl Drop for NestedSlots<'_> {
344    #[inline(always)]
345    fn drop(&mut self) {
346        // Read-only instances have nothing to reset. They are checked here, so that dropping them
347        // doesn't call a cold function.
348        if matches!(self.0, NestedSlotsInner::ReadWrite { .. }) {
349            self.reset();
350        }
351    }
352}
353
354impl<'a> NestedSlots<'a> {
355    #[inline(always)]
356    fn inner_ro(&self) -> &Inner {
357        match &self.0 {
358            NestedSlotsInner::ReadWrite { inner, .. } => inner,
359            NestedSlotsInner::ReadOnly { inner } => inner,
360        }
361    }
362
363    #[inline(always)]
364    fn inner_rw(&mut self) -> Option<&mut Inner> {
365        match &mut self.0 {
366            NestedSlotsInner::ReadWrite { inner, .. } => Some(inner),
367            NestedSlotsInner::ReadOnly { .. } => None,
368        }
369    }
370
371    /// Create a new read-write [`NestedSlots`] instance.
372    ///
373    /// Changes of the nested instance are reset when it is dropped, unless they are integrated into
374    /// the parent with [`Self::commit()`] first.
375    ///
376    /// Returns `None` when attempted on a read-only instance.
377    #[inline(always)]
378    pub fn new_nested_rw<'b>(&'b mut self) -> Option<NestedSlots<'b>>
379    where
380        'a: 'b,
381    {
382        let (inner, parent_slot_access_start) = match &mut self.0 {
383            NestedSlotsInner::ReadWrite {
384                inner,
385                parent_slot_access_len,
386                ..
387            } => (&mut **inner, *parent_slot_access_len),
388            NestedSlotsInner::ReadOnly { .. } => {
389                return None;
390            }
391        };
392
393        let parent_slot_access_len = inner.slot_access.len();
394        let parent_slots_len = inner.slots.len();
395        let parent_new_contracts_len = inner.new_contracts.len();
396
397        Some(NestedSlots(NestedSlotsInner::ReadWrite {
398            inner,
399            parent_slot_access_len,
400            parent_slots_len,
401            parent_new_contracts_len,
402            parent_slot_access_start: Some(parent_slot_access_start),
403        }))
404    }
405
406    /// Create a new nested read-only slots instance
407    #[inline(always)]
408    pub fn new_nested_ro<'b>(&'b self) -> NestedSlots<'b>
409    where
410        'a: 'b,
411    {
412        let inner = match &self.0 {
413            NestedSlotsInner::ReadWrite { inner, .. } => &**inner,
414            NestedSlotsInner::ReadOnly { inner } => &**inner,
415        };
416
417        NestedSlots(NestedSlotsInner::ReadOnly { inner })
418    }
419
420    /// Add a new contract that didn't exist before.
421    ///
422    /// In contrast to contracts in [`Slots::new()`], this contract will be allowed to have any
423    /// slots related to it being modified.
424    ///
425    /// Returns `false` if a contract already exits in a map, which is also considered as an access
426    /// violation.
427    #[must_use = "Check if contract already exists in a map"]
428    #[inline(always)]
429    pub fn add_new_contract(&mut self, owner: Address) -> bool {
430        let Some(inner) = self.inner_rw() else {
431            debug!(?owner, "`add_new_contract` access violation");
432            return false;
433        };
434
435        let new_contracts = &mut inner.new_contracts;
436
437        if new_contracts.contains(&owner) {
438            debug!(?owner, "Not adding a new contract duplicate");
439            return false;
440        }
441
442        new_contracts.push(owner);
443        true
444    }
445
446    /// Get code for `owner`.
447    ///
448    /// The biggest difference from [`Self::use_ro()`] is that the slot is not marked as used,
449    /// instead the current code is cloned and returned.
450    ///
451    /// Returns `None` in case of access violation or if code is missing.
452    #[inline(always)]
453    pub fn get_code(&self, owner: Address) -> Option<SharedAlignedBuffer> {
454        let result = self.get_code_internal(owner);
455
456        if result.is_none() {
457            debug!(?owner, "`get_code` access violation");
458        }
459
460        result
461    }
462
463    #[inline(always)]
464    fn get_code_internal(&self, owner: Address) -> Option<SharedAlignedBuffer> {
465        let inner = self.inner_ro();
466        let slots = &inner.slots;
467        let slot_access = &inner.slot_access;
468
469        let contract = Address::SYSTEM_CODE;
470
471        let slot_index = slots.iter().position(|(slot_key, _slot)| {
472            slot_key.owner == owner && slot_key.contract == contract
473        })?;
474        let slot_index = SlotIndex(slot_index);
475
476        // Ensure code is not currently being written to
477        if active_access(slot_access, slot_index) == Some(true) {
478            return None;
479        }
480
481        let buffer = match &slots
482            .get(usize::from(slot_index))
483            .expect("Just found; qed")
484            .1
485        {
486            SlotState::ReadOnly(buffer)
487            | SlotState::Original(buffer)
488            | SlotState::OriginalReadOnly(buffer)
489            | SlotState::Modified(buffer)
490            | SlotState::ModifiedReadOnly(buffer) => buffer,
491            SlotState::OriginalReadWrite { .. } | SlotState::ModifiedReadWrite { .. } => {
492                return None;
493            }
494        };
495
496        Some(buffer.clone())
497    }
498
499    /// Read-only access to a slot with a specified owner and contract.
500    ///
501    /// Marks the slot as used, except for read-only slots, which never change, so their reads are
502    /// not tracked.
503    ///
504    /// Returns `None` in case of access violation.
505    #[inline(always)]
506    pub fn use_ro(&mut self, slot_key: SlotKey) -> Option<&SharedAlignedBuffer> {
507        let inner_rw = match &mut self.0 {
508            NestedSlotsInner::ReadWrite { inner, .. } => &mut **inner,
509            NestedSlotsInner::ReadOnly { inner } => {
510                // Simplified version that doesn't do access tracking
511                let result = Self::use_ro_internal_read_only(
512                    slot_key,
513                    &inner.slots,
514                    &inner.slot_access,
515                    &inner.new_contracts,
516                );
517
518                if result.is_none() {
519                    debug!(?slot_key, "`use_ro` access violation");
520                }
521
522                return result;
523            }
524        };
525
526        let result = Self::use_ro_internal(
527            slot_key,
528            &mut inner_rw.slots,
529            &mut inner_rw.slot_access,
530            &inner_rw.new_contracts,
531        );
532
533        if result.is_none() {
534            debug!(?slot_key, "`use_ro` access violation");
535        }
536
537        result
538    }
539
540    #[inline(always)]
541    fn use_ro_internal<'b>(
542        slot_key: SlotKey,
543        slots: &'b mut SmallVec<[(SlotKey, SlotState); INLINE_SIZE]>,
544        slot_access: &mut SmallVec<[SlotAccess; INLINE_SIZE]>,
545        new_contracts: &[Address],
546    ) -> Option<&'b SharedAlignedBuffer> {
547        let maybe_slot_index = slots
548            .iter()
549            .position(|(slot_key_candidate, _slot)| slot_key_candidate == &slot_key)
550            .map(SlotIndex);
551
552        if let Some(slot_index) = maybe_slot_index {
553            // Ensure that the slot is not currently being written to
554            let maybe_read_write = active_access(slot_access, slot_index);
555            if maybe_read_write == Some(true) {
556                return None;
557            }
558
559            let slot = &mut slots
560                .get_mut(usize::from(slot_index))
561                .expect("Just found; qed")
562                .1;
563
564            // The slot that is currently being written to is not allowed for read access
565            let buffer = match slot {
566                SlotState::ReadOnly(buffer) => {
567                    // Read-only slots never change, so reading them is not tracked
568                    return Some(buffer);
569                }
570                SlotState::Original(buffer) => {
571                    let buffer = buffer.clone();
572                    *slot = SlotState::OriginalReadOnly(buffer);
573                    let SlotState::OriginalReadOnly(buffer) = slot else {
574                        unreachable!("Just inserted; qed");
575                    };
576                    buffer
577                }
578                SlotState::OriginalReadOnly(buffer) | SlotState::ModifiedReadOnly(buffer) => buffer,
579                SlotState::Modified(buffer) => {
580                    let buffer = buffer.clone();
581                    *slot = SlotState::ModifiedReadOnly(buffer);
582                    let SlotState::ModifiedReadOnly(buffer) = slot else {
583                        unreachable!("Just inserted; qed");
584                    };
585                    buffer
586                }
587                SlotState::OriginalReadWrite { .. } | SlotState::ModifiedReadWrite { .. } => {
588                    return None;
589                }
590            };
591
592            if maybe_read_write.is_none() {
593                slot_access.push(SlotAccess {
594                    slot_index,
595                    kind: SlotAccessKind::ReadOnly,
596                });
597            }
598
599            Some(buffer)
600        } else {
601            // `Address::NULL` is used for `#[tmp]` and is ephemeral. Reads and writes are allowed
602            // for any owner, and they will all be thrown away after transaction processing if
603            // finished.
604            if !(slot_key.contract == Address::NULL
605                || new_contracts
606                    .iter()
607                    .any(|candidate| candidate == slot_key.owner || candidate == slot_key.contract))
608            {
609                return None;
610            }
611
612            slot_access.push(SlotAccess {
613                slot_index: SlotIndex(slots.len()),
614                kind: SlotAccessKind::ReadOnly,
615            });
616
617            let slot = SlotState::OriginalReadOnly(SharedAlignedBuffer::default());
618            slots.push((slot_key, slot));
619            let slot = &slots.last().expect("Just inserted; qed").1;
620            let SlotState::OriginalReadOnly(buffer) = slot else {
621                unreachable!("Just inserted; qed");
622            };
623
624            Some(buffer)
625        }
626    }
627
628    /// Similar to [`Self::use_ro_internal()`], but for read-only instance
629    #[inline(always)]
630    fn use_ro_internal_read_only<'b>(
631        slot_key: SlotKey,
632        slots: &'b SmallVec<[(SlotKey, SlotState); INLINE_SIZE]>,
633        slot_access: &SmallVec<[SlotAccess; INLINE_SIZE]>,
634        new_contracts: &[Address],
635    ) -> Option<&'b SharedAlignedBuffer> {
636        let maybe_slot_index = slots
637            .iter()
638            .position(|(slot_key_candidate, _slot)| slot_key_candidate == &slot_key)
639            .map(SlotIndex);
640
641        if let Some(slot_index) = maybe_slot_index {
642            // Ensure that the slot is not currently being written to
643            if active_access(slot_access, slot_index) == Some(true) {
644                return None;
645            }
646
647            let slot = &slots
648                .get(usize::from(slot_index))
649                .expect("Just found; qed")
650                .1;
651
652            // The slot that is currently being written to is not allowed for read access
653            match slot {
654                SlotState::ReadOnly(buffer)
655                | SlotState::Original(buffer)
656                | SlotState::OriginalReadOnly(buffer)
657                | SlotState::ModifiedReadOnly(buffer)
658                | SlotState::Modified(buffer) => Some(buffer),
659                SlotState::OriginalReadWrite { .. } | SlotState::ModifiedReadWrite { .. } => None,
660            }
661        } else {
662            // `Address::NULL` is used for `#[tmp]` and is ephemeral. Reads and writes are
663            // allowed for any owner, and they will all be thrown away after transaction
664            // processing if finished.
665            if !(slot_key.contract == Address::NULL
666                || new_contracts
667                    .iter()
668                    .any(|candidate| candidate == slot_key.owner || candidate == slot_key.contract))
669            {
670                return None;
671            }
672
673            Some(SharedAlignedBuffer::empty_ref())
674        }
675    }
676
677    /// Read-write access to a slot with a specified owner and contract, marks it as used.
678    ///
679    /// The returned slot is no longer accessible through [`Self::use_ro()`] or [`Self::use_rw()`]
680    /// during the lifetime of this `NestedSlots` instance (and can be safely turned into a
681    /// pointer). The only way to get another mutable reference is to call
682    /// [`Self::access_used_rw()`].
683    ///
684    /// Returns `None` in case of access violation.
685    #[inline(always)]
686    pub fn use_rw(
687        &mut self,
688        slot_key: SlotKey,
689        capacity: u32,
690    ) -> Option<(SlotIndex, &mut OwnedAlignedBuffer)> {
691        let inner = self.inner_rw()?;
692        let slots = &mut inner.slots;
693        let slot_access = &mut inner.slot_access;
694        let new_contracts = &inner.new_contracts;
695
696        let result = Self::use_rw_internal(slot_key, capacity, slots, slot_access, new_contracts);
697
698        if result.is_none() {
699            debug!(?slot_key, "`use_rw` access violation");
700        }
701
702        result
703    }
704
705    #[inline(always)]
706    fn use_rw_internal<'b>(
707        slot_key: SlotKey,
708        capacity: u32,
709        slots: &'b mut SmallVec<[(SlotKey, SlotState); INLINE_SIZE]>,
710        slot_access: &mut SmallVec<[SlotAccess; INLINE_SIZE]>,
711        new_contracts: &[Address],
712    ) -> Option<(SlotIndex, &'b mut OwnedAlignedBuffer)> {
713        let maybe_slot_index = slots
714            .iter()
715            .position(|(slot_key_candidate, _slot)| slot_key_candidate == &slot_key)
716            .map(SlotIndex);
717
718        if let Some(slot_index) = maybe_slot_index {
719            // Ensure that slot is not accessed right now
720            if active_access(slot_access, slot_index).is_some() {
721                return None;
722            }
723
724            let slot = &mut slots
725                .get_mut(usize::from(slot_index))
726                .expect("Just found; qed")
727                .1;
728
729            // Read-only slots and slots currently being accessed are not allowed for writing
730            let buffer = match slot {
731                SlotState::ReadOnly(_buffer)
732                | SlotState::OriginalReadOnly(_buffer)
733                | SlotState::ModifiedReadOnly(_buffer) => {
734                    return None;
735                }
736                SlotState::Original(buffer) => {
737                    let mut new_buffer =
738                        OwnedAlignedBuffer::with_capacity(capacity.max(buffer.len()));
739                    new_buffer.copy_from_slice(buffer.as_slice());
740
741                    *slot = SlotState::OriginalReadWrite {
742                        buffer: new_buffer,
743                        previous: buffer.clone(),
744                    };
745                    let SlotState::OriginalReadWrite { buffer, .. } = slot else {
746                        unreachable!("Just inserted; qed");
747                    };
748                    buffer
749                }
750                SlotState::Modified(buffer) => {
751                    let mut new_buffer =
752                        OwnedAlignedBuffer::with_capacity(capacity.max(buffer.len()));
753                    new_buffer.copy_from_slice(buffer.as_slice());
754
755                    *slot = SlotState::ModifiedReadWrite {
756                        buffer: new_buffer,
757                        previous: buffer.clone(),
758                    };
759                    let SlotState::ModifiedReadWrite { buffer, .. } = slot else {
760                        unreachable!("Just inserted; qed");
761                    };
762                    buffer
763                }
764                SlotState::OriginalReadWrite { buffer, .. }
765                | SlotState::ModifiedReadWrite { buffer, .. } => {
766                    buffer.ensure_capacity(capacity);
767                    buffer
768                }
769            };
770
771            slot_access.push(SlotAccess {
772                slot_index,
773                kind: SlotAccessKind::ReadWrite,
774            });
775
776            Some((slot_index, buffer))
777        } else {
778            // `Address::NULL` is used for `#[tmp]` and is ephemeral. Reads and writes are allowed
779            // for any owner, and they will all be thrown away after transaction processing if
780            // finished.
781            if !(slot_key.contract == Address::NULL
782                || new_contracts
783                    .iter()
784                    .any(|candidate| candidate == slot_key.owner || candidate == slot_key.contract))
785            {
786                return None;
787            }
788
789            let slot_index = SlotIndex(slots.len());
790            slot_access.push(SlotAccess {
791                slot_index,
792                kind: SlotAccessKind::ReadWrite,
793            });
794
795            let slot = SlotState::OriginalReadWrite {
796                buffer: OwnedAlignedBuffer::with_capacity(capacity),
797                previous: SharedAlignedBuffer::default(),
798            };
799            slots.push((slot_key, slot));
800            let slot = &mut slots.last_mut().expect("Just inserted; qed").1;
801            let SlotState::OriginalReadWrite { buffer, .. } = slot else {
802                unreachable!("Just inserted; qed");
803            };
804
805            Some((slot_index, buffer))
806        }
807    }
808
809    /// Read-write access to a slot with a specified owner and contract that is currently marked as
810    /// used due to an earlier call to [`Self::use_rw()`].
811    ///
812    /// NOTE: Calling this method means that any pointers that might have been stored to the result
813    /// of [`Self::use_rw()`] call are now invalid!
814    ///
815    /// Returns `None` in case of access violation.
816    pub fn access_used_rw(&mut self, slot_index: SlotIndex) -> Option<&mut OwnedAlignedBuffer> {
817        let maybe_slot = self
818            .inner_rw()?
819            .slots
820            .get_mut(usize::from(slot_index))
821            .map(|(_slot_key, slot)| slot);
822
823        let Some(slot) = maybe_slot else {
824            debug!(?slot_index, "`access_used_rw` access violation (not found)");
825            return None;
826        };
827
828        // Must be currently accessed for writing
829        match slot {
830            SlotState::ReadOnly(_buffer)
831            | SlotState::Original(_buffer)
832            | SlotState::OriginalReadOnly(_buffer)
833            | SlotState::Modified(_buffer)
834            | SlotState::ModifiedReadOnly(_buffer) => {
835                debug!(?slot_index, "`access_used_rw` access violation (read only)");
836                None
837            }
838            SlotState::OriginalReadWrite { buffer, .. }
839            | SlotState::ModifiedReadWrite { buffer, .. } => Some(buffer),
840        }
841    }
842
843    /// Integrate changes into the parent.
844    ///
845    /// Without this, changes are reset when the instance is dropped.
846    #[inline(always)]
847    pub fn commit(self) {
848        // Changes are integrated here instead of being reset on drop
849        let mut this = ManuallyDrop::new(self);
850
851        let (inner, parent_slot_access_len, parent_slot_access_start) = match &mut this.0 {
852            NestedSlotsInner::ReadWrite {
853                inner,
854                parent_slot_access_len,
855                parent_slot_access_start,
856                ..
857            } => (
858                &mut **inner,
859                *parent_slot_access_len,
860                *parent_slot_access_start,
861            ),
862            NestedSlotsInner::ReadOnly { .. } => {
863                // No need to integrate changes into the parent
864                return;
865            }
866        };
867
868        let slots = &mut inner.slots;
869        let slot_access = &mut inner.slot_access;
870
871        // Fix-up slots that were accessed by this instance. Accesses that modified a slot become
872        // modifications with the previous state of the slot, such that the parent can roll them
873        // back later.
874        for slot_access in slot_access.iter_mut().skip(parent_slot_access_len) {
875            if matches!(slot_access.kind, SlotAccessKind::Integrated { .. }) {
876                // Modification by a nested instance that already integrated its changes
877                continue;
878            }
879
880            let slot = &mut slots
881                .get_mut(usize::from(slot_access.slot_index))
882                .expect("Accessed slot exists; qed")
883                .1;
884
885            let mut previous = None;
886            replace_with_or_abort(slot, |slot| match slot {
887                SlotState::ReadOnly(_buffer) => {
888                    unreachable!("Accesses of read-only slots are not tracked; qed")
889                }
890                SlotState::Original(_buffer) => {
891                    unreachable!("Slot can't be in `Original` state after being accessed; qed")
892                }
893                SlotState::OriginalReadOnly(buffer) => SlotState::Original(buffer),
894                SlotState::Modified(buffer) => SlotState::Modified(buffer),
895                SlotState::ModifiedReadOnly(buffer) => SlotState::Modified(buffer),
896                SlotState::OriginalReadWrite {
897                    buffer,
898                    previous: previous_buffer,
899                } => {
900                    previous = Some(SlotState::Original(previous_buffer));
901                    SlotState::Modified(buffer.into_shared())
902                }
903                SlotState::ModifiedReadWrite {
904                    buffer,
905                    previous: previous_buffer,
906                } => {
907                    previous = Some(SlotState::Modified(previous_buffer));
908                    SlotState::Modified(buffer.into_shared())
909                }
910            });
911            if let Some(previous) = previous {
912                slot_access.kind = SlotAccessKind::Integrated { previous };
913            }
914        }
915
916        let Some(parent_slot_access_start) = parent_slot_access_start else {
917            // There is no parent to roll back changes
918            slot_access.clear();
919
920            // Remove temporary values for `Address::NULL` contract, these are used as `#[tmp]`
921            // "slots" by convention in the execution environment since there is no code behind
922            // `Address::NULL` to possibly use it for anything
923            slots.retain(|(slot_key, _slot)| slot_key.contract != Address::NULL);
924            return;
925        };
926
927        // Keep only what the parent needs to roll back changes: one modification per slot, the
928        // earliest one, which has the oldest state of the slot
929        let accesses = slot_access
930            .drain(parent_slot_access_len..)
931            .collect::<SmallVec<[_; INLINE_SIZE]>>();
932        for access in accesses {
933            if !matches!(access.kind, SlotAccessKind::Integrated { .. }) {
934                // Accesses that didn't modify the slot have nothing to roll back
935                continue;
936            }
937
938            if slot_access
939                .iter()
940                .skip(parent_slot_access_start)
941                .any(|slot_access| slot_access.slot_index == access.slot_index)
942            {
943                // The parent already has an earlier modification of the slot with an older state
944                continue;
945            }
946
947            slot_access.push(access);
948        }
949    }
950
951    /// Reset any changes that might have been done on this level, including changes of nested
952    /// instances that already integrated them
953    #[cold]
954    fn reset(&mut self) {
955        let (inner, parent_slot_access_len, parent_slots_len, parent_new_contracts_len) =
956            match &mut self.0 {
957                NestedSlotsInner::ReadWrite {
958                    inner,
959                    parent_slot_access_len,
960                    parent_slots_len,
961                    parent_new_contracts_len,
962                    parent_slot_access_start: _,
963                } => (
964                    &mut **inner,
965                    *parent_slot_access_len,
966                    *parent_slots_len,
967                    *parent_new_contracts_len,
968                ),
969                NestedSlotsInner::ReadOnly { .. } => {
970                    // Read-only instances have nothing to reset
971                    return;
972                }
973            };
974
975        let slots = &mut inner.slots;
976        let slot_access = &mut inner.slot_access;
977
978        // Fix-up slots that were modified during access. Accesses are rolled back from the latest
979        // to the earliest, such that each slot ends up in the state from before the earliest one.
980        for slot_access in slot_access.drain(parent_slot_access_len..).rev() {
981            let slot = &mut slots
982                .get_mut(usize::from(slot_access.slot_index))
983                .expect("Accessed slot exists; qed")
984                .1;
985
986            if let SlotAccessKind::Integrated { previous } = slot_access.kind {
987                *slot = previous;
988                continue;
989            }
990
991            replace_with_or_abort(slot, |slot| match slot {
992                SlotState::ReadOnly(_buffer) => {
993                    unreachable!("Accesses of read-only slots are not tracked; qed");
994                }
995                SlotState::Original(_buffer) => {
996                    unreachable!("Slot can't be in `Original` state after being accessed; qed");
997                }
998                SlotState::OriginalReadOnly(buffer) => SlotState::Original(buffer),
999                SlotState::Modified(buffer) => SlotState::Modified(buffer),
1000                SlotState::ModifiedReadOnly(buffer) => SlotState::Modified(buffer),
1001                SlotState::OriginalReadWrite { previous, .. } => SlotState::Original(previous),
1002                SlotState::ModifiedReadWrite { previous, .. } => SlotState::Modified(previous),
1003            });
1004        }
1005
1006        // Slots and contracts that didn't exist before can't be accessed by the parent anymore
1007        slots.truncate(parent_slots_len);
1008        inner.new_contracts.truncate(parent_new_contracts_len);
1009    }
1010}