iota_move_natives_latest/object_runtime/
object_store.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{
6    collections::{BTreeMap, btree_map},
7    sync::Arc,
8};
9
10use iota_protocol_config::{LimitThresholdCrossed, ProtocolConfig, check_limit_by_meter};
11use iota_types::{
12    base_types::{MoveObjectType, ObjectID, SequenceNumber},
13    committee::EpochId,
14    error::VMMemoryLimitExceededSubStatusCode,
15    execution::DynamicallyLoadedObjectMetadata,
16    metrics::LimitsMetrics,
17    object::{Data, MoveObject, Object, Owner},
18    storage::ChildObjectResolver,
19};
20use move_binary_format::errors::{PartialVMError, PartialVMResult};
21use move_core_types::{
22    annotated_value as A, effects::Op, runtime_value as R, vm_status::StatusCode,
23};
24use move_vm_types::{
25    loaded_data::runtime_types::Type,
26    values::{GlobalValue, StructRef, Value},
27};
28
29use crate::object_runtime::get_all_uids;
30
31pub(super) struct ChildObject {
32    pub(super) owner: ObjectID,
33    pub(super) ty: Type,
34    pub(super) move_type: MoveObjectType,
35    pub(super) value: GlobalValue,
36}
37
38pub(crate) struct ActiveChildObject<'a> {
39    pub(crate) id: &'a ObjectID,
40    pub(crate) owner: &'a ObjectID,
41    pub(crate) ty: &'a Type,
42    pub(crate) move_type: &'a MoveObjectType,
43    pub(crate) copied_value: Option<Value>,
44}
45
46#[derive(Debug)]
47struct ConfigSetting {
48    config: ObjectID,
49    ty: MoveObjectType,
50    value: Value,
51}
52
53#[derive(Debug)]
54pub(crate) struct ChildObjectEffect {
55    pub(super) owner: ObjectID,
56    pub(super) ty: Type,
57    pub(super) effect: Op<Value>,
58}
59
60struct Inner<'a> {
61    // used for loading child objects
62    resolver: &'a dyn ChildObjectResolver,
63    // The version of the root object in ownership at the beginning of the transaction.
64    // If it was a child object, it resolves to the root parent's sequence number.
65    // Otherwise, it is just the sequence number at the beginning of the transaction.
66    root_version: BTreeMap<ObjectID, SequenceNumber>,
67    // A map from a wrapped object to the object it was contained in at the
68    // beginning of the transaction.
69    wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
70    // cached objects from the resolver. An object might be in this map but not in the store
71    // if it's existence was queried, but the value was not used.
72    cached_objects: BTreeMap<ObjectID, Option<Object>>,
73    // whether or not this TX is gas metered
74    is_metered: bool,
75    // Protocol config used to enforce limits
76    protocol_config: &'a ProtocolConfig,
77    // Metrics for reporting exceeded limits
78    metrics: Arc<LimitsMetrics>,
79    // Epoch ID for the current transaction. Used for receiving objects.
80    current_epoch_id: EpochId,
81}
82
83// maintains the runtime GlobalValues for child objects and manages the fetching
84// of objects from storage, through the `ChildObjectResolver`
85pub(super) struct ChildObjectStore<'a> {
86    // contains object resolver and object cache
87    // kept as a separate struct to deal with lifetime issues where the `store` is accessed
88    // at the same time as the `cached_objects` is populated
89    inner: Inner<'a>,
90    // Maps of populated GlobalValues, meaning the child object has been accessed in this
91    // transaction
92    store: BTreeMap<ObjectID, ChildObject>,
93    config_setting_cache: BTreeMap<ObjectID, ConfigSetting>,
94    // whether or not this TX is gas metered
95    is_metered: bool,
96}
97
98pub(crate) enum ObjectResult<V> {
99    // object exists but type does not match. Should result in an abort
100    MismatchedType,
101    Loaded(V),
102}
103
104type LoadedWithMetadataResult<V> = Option<(V, DynamicallyLoadedObjectMetadata)>;
105
106macro_rules! fetch_child_object_unbounded {
107    ($inner:ident, $parent:ident, $child:ident, $parents_root_version:expr, $had_parent_root_version:expr) => {{
108        let child_opt = $inner
109            .resolver
110            .read_child_object(&$parent, &$child, $parents_root_version)
111            .map_err(|msg| {
112                PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(format!("{msg}"))
113            })?;
114        if let Some(object) = child_opt {
115            // if there was no root version, guard against reading a child object. A newly
116            // created parent should not have a child in storage
117            if !$had_parent_root_version {
118                return Err(
119                    PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(format!(
120                        "A new parent {} should not have a child object {}.",
121                        $parent, $child
122                    )),
123                );
124            }
125            // guard against bugs in `read_child_object`: if it returns a child object such
126            // that C.parent != parent, we raise an invariant violation
127            match &object.owner {
128                Owner::ObjectOwner(id) => {
129                    if ObjectID::from(*id) != $parent {
130                        return Err(PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(
131                            format!(
132                                "Bad owner for {}. Expected owner {} but found owner {}",
133                                $child, $parent, id
134                            ),
135                        ));
136                    }
137                }
138                Owner::AddressOwner(_) | Owner::Immutable | Owner::Shared { .. } => {
139                    return Err(PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(
140                        format!(
141                            "Bad owner for {}. \
142                            Expected an id owner {} but found an address, \
143                            immutable, or shared owner",
144                            $child, $parent
145                        ),
146                    ));
147                }
148            };
149            match object.data {
150                Data::Package(_) => {
151                    return Err(PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(
152                        format!(
153                            "Mismatched object type for {}. \
154                            Expected a Move object but found a Move package",
155                            $child
156                        ),
157                    ));
158                }
159                Data::Move(_) => Some(object),
160            }
161        } else {
162            None
163        }
164    }};
165}
166
167impl Inner<'_> {
168    fn receive_object_from_store(
169        &self,
170        owner: ObjectID,
171        child: ObjectID,
172        version: SequenceNumber,
173    ) -> PartialVMResult<LoadedWithMetadataResult<MoveObject>> {
174        let child_opt = self
175            .resolver
176            .get_object_received_at_version(&owner, &child, version, self.current_epoch_id)
177            .map_err(|msg| {
178                PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(format!("{msg}"))
179            })?;
180        let obj_opt = if let Some(object) = child_opt {
181            // guard against bugs in `receive_object_at_version`: if it returns a child
182            // object such that C.parent != parent, we raise an invariant
183            // violation since that should be checked by
184            // `receive_object_at_version`.
185            if object.owner != Owner::AddressOwner(owner.into()) {
186                return Err(
187                    PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(format!(
188                        "Bad owner for {child}. \
189                        Expected owner {owner} but found owner {}",
190                        object.owner
191                    )),
192                );
193            }
194            let loaded_metadata = DynamicallyLoadedObjectMetadata {
195                version,
196                digest: object.digest(),
197                storage_rebate: object.storage_rebate,
198                owner: object.owner,
199                previous_transaction: object.previous_transaction,
200            };
201
202            // `ChildObjectResolver::receive_object_at_version` should return the object at
203            // the version or nothing at all. If it returns an object with a
204            // different version, we should raise an invariant violation since
205            // it should be checked by `receive_object_at_version`.
206            if object.version() != version {
207                return Err(
208                    PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(format!(
209                        "Bad version for {child}. \
210                        Expected version {version} but found version {}",
211                        object.version()
212                    )),
213                );
214            }
215            match object.into_inner().data {
216                Data::Package(_) => {
217                    return Err(PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(
218                        format!(
219                            "Mismatched object type for {child}. \
220                                Expected a Move object but found a Move package"
221                        ),
222                    ));
223                }
224                Data::Move(mo @ MoveObject { .. }) => Some((mo, loaded_metadata)),
225            }
226        } else {
227            None
228        };
229        Ok(obj_opt)
230    }
231
232    fn get_or_fetch_object_from_store(
233        &mut self,
234        parent: ObjectID,
235        child: ObjectID,
236    ) -> PartialVMResult<Option<&MoveObject>> {
237        let cached_objects_count = self.cached_objects.len() as u64;
238        let parents_root_version = self.root_version.get(&parent).copied();
239        let had_parent_root_version = parents_root_version.is_some();
240        // if not found, it must be new so it won't have any child objects, thus
241        // we can return SequenceNumber(0) as no child object will be found
242        let parents_root_version = parents_root_version.unwrap_or(SequenceNumber::new());
243        if let btree_map::Entry::Vacant(e) = self.cached_objects.entry(child) {
244            let obj_opt = fetch_child_object_unbounded!(
245                self,
246                parent,
247                child,
248                parents_root_version,
249                had_parent_root_version
250            );
251
252            if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
253                self.is_metered,
254                cached_objects_count,
255                self.protocol_config.object_runtime_max_num_cached_objects(),
256                self.protocol_config
257                    .object_runtime_max_num_cached_objects_system_tx(),
258                self.metrics.excessive_object_runtime_cached_objects
259            ) {
260                return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
261                    .with_message(format!(
262                        "Object runtime cached objects limit ({} entries) reached",
263                        lim
264                    ))
265                    .with_sub_status(
266                        VMMemoryLimitExceededSubStatusCode::OBJECT_RUNTIME_CACHE_LIMIT_EXCEEDED
267                            as u64,
268                    ));
269            };
270
271            e.insert(obj_opt);
272        }
273        Ok(self
274            .cached_objects
275            .get(&child)
276            .unwrap()
277            .as_ref()
278            .map(|obj| {
279                obj.data
280                    .try_as_move()
281                    // unwrap safe because we only insert Move objects
282                    .unwrap()
283            }))
284    }
285
286    fn fetch_object_impl(
287        &mut self,
288        parent: ObjectID,
289        child: ObjectID,
290        child_ty: &Type,
291        child_ty_layout: &R::MoveTypeLayout,
292        child_ty_fully_annotated_layout: &A::MoveTypeLayout,
293        child_move_type: &MoveObjectType,
294    ) -> PartialVMResult<ObjectResult<(Type, GlobalValue)>> {
295        let obj = match self.get_or_fetch_object_from_store(parent, child)? {
296            None => {
297                return Ok(ObjectResult::Loaded((
298                    child_ty.clone(),
299                    GlobalValue::none(),
300                )));
301            }
302            Some(obj) => obj,
303        };
304        // object exists, but the type does not match
305        if obj.type_() != child_move_type {
306            return Ok(ObjectResult::MismatchedType);
307        }
308        // generate a GlobalValue
309        let obj_contents = obj.contents();
310        let v = match Value::simple_deserialize(obj_contents, child_ty_layout) {
311            Some(v) => v,
312            None => return Err(
313                PartialVMError::new(StatusCode::FAILED_TO_DESERIALIZE_RESOURCE).with_message(
314                    format!("Failed to deserialize object {child} with type {child_move_type}",),
315                ),
316            ),
317        };
318        let global_value =
319            match GlobalValue::cached(v) {
320                Ok(gv) => gv,
321                Err(e) => {
322                    return Err(PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(
323                        format!("Object {child} did not deserialize to a struct Value. Error: {e}"),
324                    ));
325                }
326            };
327        // Find all UIDs inside of the value and update the object parent maps
328        let contained_uids =
329            get_all_uids(child_ty_fully_annotated_layout, obj_contents).map_err(|e| {
330                PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
331                    .with_message(format!("Failed to find UIDs. ERROR: {e}"))
332            })?;
333        let parents_root_version = self.root_version.get(&parent).copied();
334        if let Some(v) = parents_root_version {
335            debug_assert!(contained_uids.contains(&child));
336            for id in contained_uids {
337                self.root_version.insert(id, v);
338                if id != child {
339                    let prev = self.wrapped_object_containers.insert(id, child);
340                    debug_assert!(prev.is_none())
341                }
342            }
343        }
344        Ok(ObjectResult::Loaded((child_ty.clone(), global_value)))
345    }
346}
347
348fn deserialize_move_object(
349    obj: &MoveObject,
350    child_ty: &Type,
351    child_ty_layout: &R::MoveTypeLayout,
352    child_move_type: MoveObjectType,
353) -> PartialVMResult<ObjectResult<(Type, MoveObjectType, Value)>> {
354    let child_id = obj.id();
355    // object exists, but the type does not match
356    if obj.type_() != &child_move_type {
357        return Ok(ObjectResult::MismatchedType);
358    }
359    let value = match Value::simple_deserialize(obj.contents(), child_ty_layout) {
360        Some(v) => v,
361        None => {
362            return Err(
363                PartialVMError::new(StatusCode::FAILED_TO_DESERIALIZE_RESOURCE).with_message(
364                    format!("Failed to deserialize object {child_id} with type {child_move_type}",),
365                ),
366            );
367        }
368    };
369    Ok(ObjectResult::Loaded((
370        child_ty.clone(),
371        child_move_type,
372        value,
373    )))
374}
375
376impl<'a> ChildObjectStore<'a> {
377    pub(super) fn new(
378        resolver: &'a dyn ChildObjectResolver,
379        root_version: BTreeMap<ObjectID, SequenceNumber>,
380        wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
381        is_metered: bool,
382        protocol_config: &'a ProtocolConfig,
383        metrics: Arc<LimitsMetrics>,
384        current_epoch_id: EpochId,
385    ) -> Self {
386        Self {
387            inner: Inner {
388                resolver,
389                root_version,
390                wrapped_object_containers,
391                cached_objects: BTreeMap::new(),
392                is_metered,
393                protocol_config,
394                metrics,
395                current_epoch_id,
396            },
397            store: BTreeMap::new(),
398            config_setting_cache: BTreeMap::new(),
399            is_metered,
400        }
401    }
402
403    pub(super) fn receive_object(
404        &mut self,
405        parent: ObjectID,
406        child: ObjectID,
407        child_version: SequenceNumber,
408        child_ty: &Type,
409        child_layout: &R::MoveTypeLayout,
410        child_fully_annotated_layout: &A::MoveTypeLayout,
411        child_move_type: MoveObjectType,
412    ) -> PartialVMResult<LoadedWithMetadataResult<ObjectResult<Value>>> {
413        let Some((obj, obj_meta)) =
414            self.inner
415                .receive_object_from_store(parent, child, child_version)?
416        else {
417            return Ok(None);
418        };
419
420        Ok(Some(
421            match deserialize_move_object(&obj, child_ty, child_layout, child_move_type)? {
422                ObjectResult::MismatchedType => (ObjectResult::MismatchedType, obj_meta),
423                ObjectResult::Loaded((_, _, v)) => {
424                    // Find all UIDs inside of the value and update the object parent maps with the
425                    // contained UIDs in the received value. They should all
426                    // have an upper bound version as the receiving object. Only
427                    // do this if we successfully load the object though.
428                    let contained_uids = get_all_uids(child_fully_annotated_layout, obj.contents())
429                        .map_err(|e| {
430                            PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
431                                .with_message(format!(
432                                    "Failed to find UIDs for receiving object. ERROR: {e}"
433                                ))
434                        })?;
435                    for id in contained_uids {
436                        self.inner.root_version.insert(id, child_version);
437                        if id != child {
438                            let prev = self.inner.wrapped_object_containers.insert(id, child);
439                            debug_assert!(prev.is_none())
440                        }
441                    }
442                    (ObjectResult::Loaded(v), obj_meta)
443                }
444            },
445        ))
446    }
447
448    pub(super) fn object_exists(
449        &mut self,
450        parent: ObjectID,
451        child: ObjectID,
452    ) -> PartialVMResult<bool> {
453        if let Some(child_object) = self.store.get(&child) {
454            return child_object.value.exists();
455        }
456        Ok(self
457            .inner
458            .get_or_fetch_object_from_store(parent, child)?
459            .is_some())
460    }
461
462    pub(super) fn object_exists_and_has_type(
463        &mut self,
464        parent: ObjectID,
465        child: ObjectID,
466        child_move_type: &MoveObjectType,
467    ) -> PartialVMResult<bool> {
468        if let Some(child_object) = self.store.get(&child) {
469            // exists and has same type
470            return Ok(child_object.value.exists()? && &child_object.move_type == child_move_type);
471        }
472        Ok(self
473            .inner
474            .get_or_fetch_object_from_store(parent, child)?
475            .map(|move_obj| move_obj.type_() == child_move_type)
476            .unwrap_or(false))
477    }
478
479    pub(super) fn get_or_fetch_object(
480        &mut self,
481        parent: ObjectID,
482        child: ObjectID,
483        child_ty: &Type,
484        child_layout: &R::MoveTypeLayout,
485        child_fully_annotated_layout: &A::MoveTypeLayout,
486        child_move_type: MoveObjectType,
487    ) -> PartialVMResult<ObjectResult<&mut ChildObject>> {
488        let store_entries_count = self.store.len() as u64;
489        let child_object = match self.store.entry(child) {
490            btree_map::Entry::Vacant(e) => {
491                let (ty, value) = match self.inner.fetch_object_impl(
492                    parent,
493                    child,
494                    child_ty,
495                    child_layout,
496                    child_fully_annotated_layout,
497                    &child_move_type,
498                )? {
499                    ObjectResult::MismatchedType => return Ok(ObjectResult::MismatchedType),
500                    ObjectResult::Loaded(res) => res,
501                };
502
503                if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
504                    self.is_metered,
505                    store_entries_count,
506                    self.inner
507                        .protocol_config
508                        .object_runtime_max_num_store_entries(),
509                    self.inner
510                        .protocol_config
511                        .object_runtime_max_num_store_entries_system_tx(),
512                    self.inner.metrics.excessive_object_runtime_store_entries
513                ) {
514                    return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
515                        .with_message(format!(
516                            "Object runtime store limit ({} entries) reached",
517                            lim
518                        ))
519                        .with_sub_status(
520                            VMMemoryLimitExceededSubStatusCode::OBJECT_RUNTIME_STORE_LIMIT_EXCEEDED
521                                as u64,
522                        ));
523                };
524
525                e.insert(ChildObject {
526                    owner: parent,
527                    ty,
528                    move_type: child_move_type,
529                    value,
530                })
531            }
532            btree_map::Entry::Occupied(e) => {
533                let child_object = e.into_mut();
534                if child_object.move_type != child_move_type {
535                    return Ok(ObjectResult::MismatchedType);
536                }
537                child_object
538            }
539        };
540        Ok(ObjectResult::Loaded(child_object))
541    }
542
543    pub(super) fn add_object(
544        &mut self,
545        parent: ObjectID,
546        child: ObjectID,
547        child_ty: &Type,
548        child_move_type: MoveObjectType,
549        child_value: Value,
550    ) -> PartialVMResult<()> {
551        if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
552            self.is_metered,
553            self.store.len(),
554            self.inner
555                .protocol_config
556                .object_runtime_max_num_store_entries(),
557            self.inner
558                .protocol_config
559                .object_runtime_max_num_store_entries_system_tx(),
560            self.inner.metrics.excessive_object_runtime_store_entries
561        ) {
562            return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
563                .with_message(format!(
564                    "Object runtime store limit ({} entries) reached",
565                    lim
566                ))
567                .with_sub_status(
568                    VMMemoryLimitExceededSubStatusCode::OBJECT_RUNTIME_STORE_LIMIT_EXCEEDED as u64,
569                ));
570        };
571
572        let mut value = if let Some(ChildObject { value, .. }) = self.store.remove(&child) {
573            if value.exists()? {
574                return Err(
575                    PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
576                        .with_message(
577                            "Duplicate addition of a child object. \
578                            The previous value cannot be dropped. Indicates possible duplication \
579                            of objects as an object was fetched more than once from two different \
580                            parents, yet was not removed from one first"
581                                .to_string(),
582                        ),
583                );
584            }
585            value
586        } else {
587            GlobalValue::none()
588        };
589        if let Err((e, _)) = value.move_to(child_value) {
590            return Err(
591                PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message(
592                    format!("Unable to set value for child {child}, with error {e}",),
593                ),
594            );
595        }
596        let child_object = ChildObject {
597            owner: parent,
598            ty: child_ty.clone(),
599            move_type: child_move_type,
600            value,
601        };
602        self.store.insert(child, child_object);
603        Ok(())
604    }
605
606    pub(super) fn config_setting_unsequenced_read(
607        &mut self,
608        config_id: ObjectID,
609        name_df_id: ObjectID,
610        _field_setting_ty: &Type,
611        field_setting_layout: &R::MoveTypeLayout,
612        field_setting_object_type: &MoveObjectType,
613    ) -> PartialVMResult<ObjectResult<Option<Value>>> {
614        let parent = config_id;
615        let child = name_df_id;
616
617        let setting = match self.config_setting_cache.entry(child) {
618            btree_map::Entry::Vacant(e) => {
619                let child_move_type = field_setting_object_type;
620                let inner = &self.inner;
621                let obj_opt =
622                    fetch_child_object_unbounded!(inner, parent, child, SequenceNumber::MAX, true);
623                let Some(move_obj) = obj_opt.as_ref().map(|obj| obj.data.try_as_move().unwrap())
624                else {
625                    return Ok(ObjectResult::Loaded(None));
626                };
627                let Some(value) =
628                    Value::simple_deserialize(move_obj.contents(), field_setting_layout)
629                else {
630                    return Err(
631                        PartialVMError::new(StatusCode::FAILED_TO_DESERIALIZE_RESOURCE)
632                            .with_message(format!(
633                            "Failed to deserialize object {child} with type {field_setting_layout}",
634                        )),
635                    );
636                };
637                e.insert(ConfigSetting {
638                    config: parent,
639                    ty: child_move_type.clone(),
640                    value,
641                })
642            }
643            btree_map::Entry::Occupied(e) => {
644                let setting = e.into_mut();
645                if setting.ty != *field_setting_object_type {
646                    return Ok(ObjectResult::MismatchedType);
647                }
648                if setting.config != parent {
649                    return Err(
650                        PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
651                            .with_message(format!(
652                                "Parent for config setting changed. Potential hash collision?
653                                parent: {parent},
654                                child: {child},
655                                setting_value_object_type: {field_setting_object_type},
656                                setting: {setting:#?}"
657                            )),
658                    );
659                }
660                setting
661            }
662        };
663        let value = setting.value.copy_value().map_err(|e| {
664            PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message(
665                format!("Failed to copy value for config setting {child}, with error {e}",),
666            )
667        })?;
668        Ok(ObjectResult::Loaded(Some(value)))
669    }
670
671    /// Used by test scenario to insert a config setting into the cache, which
672    /// replicates the behavior of a config already being in the object
673    /// store.
674    pub(super) fn config_setting_cache_update(
675        &mut self,
676        config_id: ObjectID,
677        name_df_id: ObjectID,
678        setting_value_object_type: MoveObjectType,
679        value: Option<Value>,
680    ) {
681        let child_move_type = setting_value_object_type;
682        match value {
683            Some(value) => {
684                let setting = ConfigSetting {
685                    config: config_id,
686                    ty: child_move_type,
687                    value,
688                };
689                self.config_setting_cache.insert(name_df_id, setting);
690            }
691            None => {
692                self.config_setting_cache.remove(&name_df_id);
693            }
694        }
695    }
696
697    pub(super) fn cached_objects(&self) -> &BTreeMap<ObjectID, Option<Object>> {
698        &self.inner.cached_objects
699    }
700
701    pub(super) fn wrapped_object_containers(&self) -> &BTreeMap<ObjectID, ObjectID> {
702        &self.inner.wrapped_object_containers
703    }
704
705    // retrieve the `Op` effects for the child objects
706    pub(super) fn take_effects(&mut self) -> BTreeMap<ObjectID, ChildObjectEffect> {
707        std::mem::take(&mut self.store)
708            .into_iter()
709            .filter_map(|(id, child_object)| {
710                let ChildObject {
711                    owner,
712                    ty,
713                    move_type: _,
714                    value,
715                } = child_object;
716                let effect = value.into_effect()?;
717                let child_effect = ChildObjectEffect { owner, ty, effect };
718                Some((id, child_effect))
719            })
720            .collect()
721    }
722
723    pub(super) fn all_active_objects(&self) -> impl Iterator<Item = ActiveChildObject<'_>> {
724        self.store.iter().map(|(id, child_object)| {
725            let copied_child_value = if child_object.value.exists().unwrap() {
726                Some(
727                    child_object
728                        .value
729                        .borrow_global()
730                        .unwrap()
731                        .value_as::<StructRef>()
732                        .unwrap()
733                        .read_ref()
734                        .unwrap(),
735                )
736            } else {
737                None
738            };
739            ActiveChildObject {
740                id,
741                owner: &child_object.owner,
742                ty: &child_object.ty,
743                move_type: &child_object.move_type,
744                copied_value: copied_child_value,
745            }
746        })
747    }
748}