ab_executor_slots/
lib.rs

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