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