1pub(crate) mod accumulator;
5mod fingerprint;
6pub(crate) mod object_store;
7
8use crate::object_runtime::object_store::{CacheMetadata, ChildObjectEffectV1};
9
10use self::object_store::{ChildObjectEffectV0, ChildObjectEffects, ObjectResult};
11use super::get_object_id;
12use better_any::{Tid, TidAble};
13use indexmap::map::IndexMap;
14use indexmap::set::IndexSet;
15use move_binary_format::errors::{PartialVMError, PartialVMResult};
16use move_core_types::{
17 account_address::AccountAddress,
18 annotated_value::{MoveTypeLayout, MoveValue},
19 annotated_visitor as AV,
20 effects::Op,
21 language_storage::StructTag,
22 runtime_value as R,
23 vm_status::StatusCode,
24};
25use move_vm_runtime::native_extensions::NativeExtensionMarker;
26use move_vm_types::values::{GlobalValue, Value};
27use object_store::{ActiveChildObject, ChildObjectStore};
28use std::{
29 collections::{BTreeMap, BTreeSet},
30 sync::Arc,
31};
32use sui_protocol_config::{LimitThresholdCrossed, ProtocolConfig, check_limit_by_meter};
33use sui_types::{
34 SUI_ACCUMULATOR_ROOT_OBJECT_ID, SUI_AUTHENTICATOR_STATE_OBJECT_ID, SUI_BRIDGE_OBJECT_ID,
35 SUI_CLOCK_OBJECT_ID, SUI_COIN_REGISTRY_OBJECT_ID, SUI_DENY_LIST_OBJECT_ID,
36 SUI_DISPLAY_REGISTRY_OBJECT_ID, SUI_RANDOMNESS_STATE_OBJECT_ID, SUI_SYSTEM_STATE_OBJECT_ID,
37 TypeTag,
38 base_types::{MoveObjectType, ObjectID, SequenceNumber, SuiAddress},
39 committee::EpochId,
40 error::{ExecutionError, ExecutionErrorKind, VMMemoryLimitExceededSubStatusCode},
41 execution::DynamicallyLoadedObjectMetadata,
42 id::UID,
43 metrics::LimitsMetrics,
44 object::{MoveObject, Owner},
45 storage::ChildObjectResolver,
46};
47use tracing::error;
48
49pub use accumulator::*;
50
51pub enum ObjectEvent {
52 Transfer(Owner, MoveObject),
54 DeleteObjectID(ObjectID),
56}
57
58type Set<K> = IndexSet<K>;
59
60#[derive(Default)]
61pub(crate) struct TestInventories {
62 pub(crate) objects: BTreeMap<ObjectID, Value>,
63 pub(crate) address_inventories: BTreeMap<SuiAddress, BTreeMap<MoveObjectType, Set<ObjectID>>>,
65 pub(crate) shared_inventory: BTreeMap<MoveObjectType, Set<ObjectID>>,
67 pub(crate) immutable_inventory: BTreeMap<MoveObjectType, Set<ObjectID>>,
68 pub(crate) taken_immutable_values: BTreeMap<MoveObjectType, BTreeMap<ObjectID, Value>>,
69 pub(crate) taken: BTreeMap<ObjectID, Owner>,
71 pub(crate) allocated_tickets: BTreeMap<ObjectID, (DynamicallyLoadedObjectMetadata, Value)>,
73}
74
75pub struct LoadedRuntimeObject {
76 pub version: SequenceNumber,
77 pub is_modified: bool,
78}
79
80pub struct RuntimeResults {
81 pub writes: IndexMap<ObjectID, (Owner, MoveObjectType, Value)>,
82 pub user_events: Vec<(StructTag, Value)>,
83 pub accumulator_events: Vec<MoveAccumulatorEvent>,
84 pub loaded_child_objects: BTreeMap<ObjectID, LoadedRuntimeObject>,
86 pub created_object_ids: Set<ObjectID>,
87 pub deleted_object_ids: Set<ObjectID>,
88 pub settlement_input_sui: u64,
89 pub settlement_output_sui: u64,
90}
91
92#[derive(Default)]
93pub(crate) struct ObjectRuntimeState {
94 pub(crate) input_objects: BTreeMap<ObjectID, Owner>,
95 new_ids: Set<ObjectID>,
97 generated_ids: Set<ObjectID>,
99 deleted_ids: Set<ObjectID>,
101 transfers: IndexMap<ObjectID, (Owner, MoveObjectType, Value)>,
104 events: Vec<(StructTag, Value)>,
105 accumulator_events: Vec<MoveAccumulatorEvent>,
106 total_events_size: u64,
108 received: IndexMap<ObjectID, DynamicallyLoadedObjectMetadata>,
109 settlement_input_sui: u64,
115 settlement_output_sui: u64,
116}
117
118#[derive(Tid)]
119pub struct ObjectRuntime<'a> {
120 child_object_store: ChildObjectStore<'a>,
121 pub(crate) test_inventories: TestInventories,
123 pub(crate) state: ObjectRuntimeState,
125 is_metered: bool,
127
128 pub(crate) protocol_config: &'a ProtocolConfig,
129 pub(crate) metrics: Arc<LimitsMetrics>,
130}
131
132impl<'a> NativeExtensionMarker<'a> for ObjectRuntime<'a> {}
133
134pub enum TransferResult {
135 New,
136 SameOwner,
137 OwnerChanged,
138}
139
140pub struct InputObject {
141 pub contained_uids: BTreeSet<ObjectID>,
142 pub version: SequenceNumber,
143 pub owner: Owner,
144}
145
146impl TestInventories {
147 fn new() -> Self {
148 Self::default()
149 }
150}
151
152impl<'a> ObjectRuntime<'a> {
153 pub fn new(
154 object_resolver: &'a dyn ChildObjectResolver,
155 input_objects: BTreeMap<ObjectID, InputObject>,
156 is_metered: bool,
157 protocol_config: &'a ProtocolConfig,
158 metrics: Arc<LimitsMetrics>,
159 epoch_id: EpochId,
160 ) -> Self {
161 let mut input_object_owners = BTreeMap::new();
162 let mut root_version = BTreeMap::new();
163 let mut wrapped_object_containers = BTreeMap::new();
164 for (id, input_object) in input_objects {
165 let InputObject {
166 contained_uids,
167 version,
168 owner,
169 } = input_object;
170 input_object_owners.insert(id, owner);
171 debug_assert!(contained_uids.contains(&id));
172 for contained_uid in contained_uids {
173 root_version.insert(contained_uid, version);
174 if contained_uid != id {
175 let prev = wrapped_object_containers.insert(contained_uid, id);
176 debug_assert!(prev.is_none());
177 }
178 }
179 }
180 Self {
181 child_object_store: ChildObjectStore::new(
182 object_resolver,
183 root_version,
184 wrapped_object_containers,
185 is_metered,
186 protocol_config,
187 metrics.clone(),
188 epoch_id,
189 ),
190 test_inventories: TestInventories::new(),
191 state: ObjectRuntimeState {
192 input_objects: input_object_owners,
193 new_ids: Set::new(),
194 generated_ids: Set::new(),
195 deleted_ids: Set::new(),
196 transfers: IndexMap::new(),
197 events: vec![],
198 accumulator_events: vec![],
199 total_events_size: 0,
200 received: IndexMap::new(),
201 settlement_input_sui: 0,
202 settlement_output_sui: 0,
203 },
204 is_metered,
205 protocol_config,
206 metrics,
207 }
208 }
209
210 pub fn new_id(&mut self, id: ObjectID) -> PartialVMResult<()> {
211 if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
214 self.is_metered,
215 self.state.new_ids.len(),
216 self.protocol_config.max_num_new_move_object_ids(),
217 self.protocol_config.max_num_new_move_object_ids_system_tx(),
218 self.metrics.excessive_new_move_object_ids
219 ) {
220 return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
221 .with_message(format!("Creating more than {} IDs is not allowed", lim))
222 .with_sub_status(
223 VMMemoryLimitExceededSubStatusCode::NEW_ID_COUNT_LIMIT_EXCEEDED as u64,
224 ));
225 };
226
227 let was_present = self.state.deleted_ids.shift_remove(&id);
231 if !was_present {
232 self.state.generated_ids.insert(id);
234 self.state.new_ids.insert(id);
235 }
236 Ok(())
237 }
238
239 pub fn delete_id(&mut self, id: ObjectID) -> PartialVMResult<()> {
240 if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
245 self.is_metered,
246 self.state.deleted_ids.len(),
247 self.protocol_config.max_num_deleted_move_object_ids(),
248 self.protocol_config
249 .max_num_deleted_move_object_ids_system_tx(),
250 self.metrics.excessive_deleted_move_object_ids
251 ) {
252 return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
253 .with_message(format!("Deleting more than {} IDs is not allowed", lim))
254 .with_sub_status(
255 VMMemoryLimitExceededSubStatusCode::DELETED_ID_COUNT_LIMIT_EXCEEDED as u64,
256 ));
257 };
258
259 let was_new = self.state.new_ids.shift_remove(&id);
260 if !was_new {
261 self.state.deleted_ids.insert(id);
262 }
263 Ok(())
264 }
265
266 pub fn transfer(
269 &mut self,
270 owner: Owner,
271 ty: MoveObjectType,
272 obj: Value,
273 end_of_transaction: bool,
274 ) -> PartialVMResult<TransferResult> {
275 let id: ObjectID = get_object_id(obj.copy_value()?)?
276 .value_as::<AccountAddress>()?
277 .into();
278 let is_framework_obj = [
284 SUI_SYSTEM_STATE_OBJECT_ID,
285 SUI_CLOCK_OBJECT_ID,
286 SUI_AUTHENTICATOR_STATE_OBJECT_ID,
287 SUI_RANDOMNESS_STATE_OBJECT_ID,
288 SUI_DENY_LIST_OBJECT_ID,
289 SUI_BRIDGE_OBJECT_ID,
290 SUI_ACCUMULATOR_ROOT_OBJECT_ID,
291 SUI_COIN_REGISTRY_OBJECT_ID,
292 SUI_DISPLAY_REGISTRY_OBJECT_ID,
293 ]
294 .contains(&id);
295 let transfer_result = if self.state.new_ids.contains(&id) {
296 TransferResult::New
297 } else if let Some(prev_owner) = self.state.input_objects.get(&id) {
298 match (&owner, prev_owner) {
299 (Owner::Shared { .. }, Owner::Shared { .. }) => TransferResult::SameOwner,
301 (
302 Owner::ConsensusAddressOwner {
303 owner: new_owner, ..
304 },
305 Owner::ConsensusAddressOwner {
306 owner: old_owner, ..
307 },
308 ) if new_owner == old_owner => TransferResult::SameOwner,
309 (new, old) if new == old => TransferResult::SameOwner,
310 _ => TransferResult::OwnerChanged,
311 }
312 } else if is_framework_obj {
313 self.state.new_ids.insert(id);
316 self.state.generated_ids.insert(id);
317 TransferResult::New
318 } else {
319 TransferResult::OwnerChanged
320 };
321 if end_of_transaction && !matches!(transfer_result, TransferResult::SameOwner) {
323 return Err(
324 PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
325 .with_message(format!("Untransferred object {} had its owner change", id)),
326 );
327 }
328
329 if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
332 self.is_metered && !is_framework_obj && !end_of_transaction,
337 self.state.transfers.len(),
338 self.protocol_config.max_num_transferred_move_object_ids(),
339 self.protocol_config
340 .max_num_transferred_move_object_ids_system_tx(),
341 self.metrics.excessive_transferred_move_object_ids
342 ) {
343 return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
344 .with_message(format!("Transferring more than {} IDs is not allowed", lim))
345 .with_sub_status(
346 VMMemoryLimitExceededSubStatusCode::TRANSFER_ID_COUNT_LIMIT_EXCEEDED as u64,
347 ));
348 };
349
350 self.state.transfers.insert(id, (owner, ty, obj));
351 Ok(transfer_result)
352 }
353
354 pub fn emit_event(&mut self, tag: StructTag, event: Value) -> PartialVMResult<()> {
355 if self.state.events.len() >= (self.protocol_config.max_num_event_emit() as usize) {
356 return Err(max_event_error(self.protocol_config.max_num_event_emit()));
357 }
358 self.state.events.push((tag, event));
359 Ok(())
360 }
361
362 pub fn take_user_events(&mut self) -> Vec<(StructTag, Value)> {
363 std::mem::take(&mut self.state.events)
364 }
365
366 pub fn emit_accumulator_event(
367 &mut self,
368 accumulator_id: ObjectID,
369 action: MoveAccumulatorAction,
370 target_addr: AccountAddress,
371 target_ty: TypeTag,
372 value: MoveAccumulatorValue,
373 ) -> PartialVMResult<()> {
374 let event = MoveAccumulatorEvent {
375 accumulator_id,
376 action,
377 target_addr,
378 target_ty,
379 value,
380 };
381 self.state.accumulator_events.push(event);
382 Ok(())
383 }
384
385 pub(crate) fn child_object_exists(
386 &mut self,
387 parent: ObjectID,
388 child: ObjectID,
389 ) -> PartialVMResult<CacheMetadata<bool>> {
390 self.child_object_store.object_exists(parent, child)
391 }
392
393 pub(crate) fn child_object_exists_and_has_type(
394 &mut self,
395 parent: ObjectID,
396 child: ObjectID,
397 child_type: &MoveObjectType,
398 ) -> PartialVMResult<CacheMetadata<bool>> {
399 self.child_object_store
400 .object_exists_and_has_type(parent, child, child_type)
401 }
402
403 pub(super) fn receive_object(
404 &mut self,
405 parent: ObjectID,
406 child: ObjectID,
407 child_version: SequenceNumber,
408 child_layout: &R::MoveTypeLayout,
409 child_fully_annotated_layout: &MoveTypeLayout,
410 child_move_type: MoveObjectType,
411 ) -> PartialVMResult<Option<ObjectResult<CacheMetadata<Value>>>> {
412 let Some((value, obj_meta)) = self.child_object_store.receive_object(
413 parent,
414 child,
415 child_version,
416 child_layout,
417 child_fully_annotated_layout,
418 child_move_type,
419 )?
420 else {
421 return Ok(None);
422 };
423 if self.state.received.insert(child, obj_meta).is_some() {
426 return Err(
429 PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message(format!(
430 "Object {child} at version {child_version} already received. This can only happen \
431 if multiple `Receiving` arguments exist for the same object in the transaction which is impossible."
432 )),
433 );
434 }
435 Ok(Some(value))
436 }
437
438 pub(crate) fn get_or_fetch_child_object(
439 &mut self,
440 parent: ObjectID,
441 child: ObjectID,
442 child_layout: &R::MoveTypeLayout,
443 child_fully_annotated_layout: &MoveTypeLayout,
444 child_move_type: MoveObjectType,
445 ) -> PartialVMResult<ObjectResult<CacheMetadata<&mut GlobalValue>>> {
446 let res = self.child_object_store.get_or_fetch_object(
447 parent,
448 child,
449 child_layout,
450 child_fully_annotated_layout,
451 child_move_type,
452 )?;
453 Ok(match res {
454 ObjectResult::MismatchedType => ObjectResult::MismatchedType,
455 ObjectResult::Loaded((cache_info, child_object)) => {
456 ObjectResult::Loaded((cache_info, &mut child_object.value))
457 }
458 })
459 }
460
461 pub(crate) fn add_child_object(
462 &mut self,
463 parent: ObjectID,
464 child: ObjectID,
465 child_move_type: MoveObjectType,
466 child_value: Value,
467 ) -> PartialVMResult<()> {
468 self.child_object_store
469 .add_object(parent, child, child_move_type, child_value)
470 }
471
472 pub(crate) fn config_setting_unsequenced_read(
473 &mut self,
474 config_id: ObjectID,
475 name_df_id: ObjectID,
476 field_setting_layout: &R::MoveTypeLayout,
477 field_setting_object_type: &MoveObjectType,
478 ) -> Option<Value> {
479 match self.child_object_store.config_setting_unsequenced_read(
480 config_id,
481 name_df_id,
482 field_setting_layout,
483 field_setting_object_type,
484 ) {
485 Err(e) => {
486 error!(
487 "Failed to read config setting.
488 config_id: {config_id},
489 name_df_id: {name_df_id},
490 field_setting_object_type: {field_setting_object_type:?},
491 error: {e}"
492 );
493 None
494 }
495 Ok(ObjectResult::MismatchedType) | Ok(ObjectResult::Loaded(None)) => None,
496 Ok(ObjectResult::Loaded(Some(value))) => Some(value),
497 }
498 }
499
500 pub(super) fn config_setting_cache_update(
501 &mut self,
502 config_id: ObjectID,
503 name_df_id: ObjectID,
504 setting_value_object_type: MoveObjectType,
505 value: Option<Value>,
506 ) {
507 self.child_object_store.config_setting_cache_update(
508 config_id,
509 name_df_id,
510 setting_value_object_type,
511 value,
512 )
513 }
514
515 pub(crate) fn take_state(&mut self) -> ObjectRuntimeState {
517 std::mem::take(&mut self.state)
518 }
519
520 pub fn is_deleted(&self, id: &ObjectID) -> bool {
521 self.state.deleted_ids.contains(id)
522 }
523
524 pub fn is_transferred(&self, id: &ObjectID) -> Option<Owner> {
525 self.state
526 .transfers
527 .get(id)
528 .map(|(owner, _, _)| owner.clone())
529 }
530
531 pub fn finish(mut self) -> Result<RuntimeResults, ExecutionError> {
532 let loaded_child_objects = self.loaded_runtime_objects();
533 let child_effects = self.child_object_store.take_effects().map_err(|e| {
534 ExecutionError::invariant_violation(format!("Failed to take child object effects: {e}"))
535 })?;
536 self.state.finish(loaded_child_objects, child_effects)
537 }
538
539 pub(crate) fn all_active_child_objects(&self) -> impl Iterator<Item = ActiveChildObject<'_>> {
540 self.child_object_store.all_active_objects()
541 }
542
543 pub fn loaded_runtime_objects(&self) -> BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata> {
544 debug_assert!(
547 self.child_object_store
548 .cached_objects()
549 .keys()
550 .all(|id| !self.state.received.contains_key(id))
551 );
552 self.child_object_store
553 .cached_objects()
554 .iter()
555 .filter_map(|(id, obj_opt)| {
556 obj_opt.as_ref().map(|obj| {
557 (
558 *id,
559 DynamicallyLoadedObjectMetadata {
560 version: obj.version(),
561 digest: obj.digest(),
562 storage_rebate: obj.storage_rebate,
563 owner: obj.owner.clone(),
564 previous_transaction: obj.previous_transaction,
565 },
566 )
567 })
568 })
569 .chain(
570 self.state
571 .received
572 .iter()
573 .map(|(id, meta)| (*id, meta.clone())),
574 )
575 .collect()
576 }
577
578 pub fn wrapped_object_containers(&self) -> BTreeMap<ObjectID, ObjectID> {
581 self.child_object_store.wrapped_object_containers().clone()
582 }
583
584 pub fn record_settlement_sui_conservation(&mut self, input_sui: u64, output_sui: u64) {
585 self.state.settlement_input_sui += input_sui;
586 self.state.settlement_output_sui += output_sui;
587 }
588
589 pub fn generated_object_ids(&self) -> BTreeSet<ObjectID> {
592 self.state.generated_ids.iter().cloned().collect()
593 }
594}
595
596pub fn max_event_error(max_events: u64) -> PartialVMError {
597 PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
598 .with_message(format!(
599 "Emitting more than {} events is not allowed",
600 max_events
601 ))
602 .with_sub_status(VMMemoryLimitExceededSubStatusCode::EVENT_COUNT_LIMIT_EXCEEDED as u64)
603}
604
605impl ObjectRuntimeState {
606 pub(crate) fn finish(
615 mut self,
616 loaded_child_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
617 child_object_effects: ChildObjectEffects,
618 ) -> Result<RuntimeResults, ExecutionError> {
619 let mut loaded_child_objects: BTreeMap<_, _> = loaded_child_objects
620 .into_iter()
621 .map(|(id, metadata)| {
622 (
623 id,
624 LoadedRuntimeObject {
625 version: metadata.version,
626 is_modified: false,
627 },
628 )
629 })
630 .collect();
631 self.apply_child_object_effects(&mut loaded_child_objects, child_object_effects);
632 let ObjectRuntimeState {
633 input_objects: _,
634 new_ids,
635 generated_ids,
636 deleted_ids,
637 transfers,
638 events: user_events,
639 total_events_size: _,
640 received,
641 accumulator_events,
642 settlement_input_sui,
643 settlement_output_sui,
644 } = self;
645
646 debug_assert!(new_ids.is_subset(&generated_ids));
648
649 check_circular_ownership(
652 transfers
653 .iter()
654 .map(|(id, (owner, _, _))| (*id, owner.clone())),
655 )?;
656 let written_objects: IndexMap<_, _> = transfers
663 .into_iter()
664 .map(|(id, (owner, type_, value))| {
665 if let Some(loaded_child) = loaded_child_objects.get_mut(&id) {
666 loaded_child.is_modified = true;
667 }
668 (id, (owner, type_, value))
669 })
670 .collect();
671 for deleted_id in &deleted_ids {
672 if let Some(loaded_child) = loaded_child_objects.get_mut(deleted_id) {
673 loaded_child.is_modified = true;
674 }
675 }
676
677 for (received_object, _) in received.into_iter() {
681 match loaded_child_objects.get_mut(&received_object) {
682 Some(loaded_child) => {
683 loaded_child.is_modified = true;
684 }
685 None => {
686 return Err(ExecutionError::invariant_violation(format!(
687 "Failed to find received UID {received_object} in loaded child objects."
688 )));
689 }
690 }
691 }
692
693 Ok(RuntimeResults {
694 writes: written_objects,
695 user_events,
696 accumulator_events,
697 loaded_child_objects,
698 created_object_ids: new_ids,
699 deleted_object_ids: deleted_ids,
700 settlement_input_sui,
701 settlement_output_sui,
702 })
703 }
704
705 pub fn events(&self) -> &[(StructTag, Value)] {
706 &self.events
707 }
708
709 pub fn total_events_size(&self) -> u64 {
710 self.total_events_size
711 }
712
713 pub fn incr_total_events_size(&mut self, size: u64) {
714 self.total_events_size += size;
715 }
716
717 fn apply_child_object_effects(
718 &mut self,
719 loaded_child_objects: &mut BTreeMap<ObjectID, LoadedRuntimeObject>,
720 child_object_effects: ChildObjectEffects,
721 ) {
722 match child_object_effects {
723 ChildObjectEffects::V0(child_object_effects) => {
724 self.apply_child_object_effects_v0(loaded_child_objects, child_object_effects)
725 }
726 ChildObjectEffects::V1(child_object_effects) => {
727 self.apply_child_object_effects_v1(loaded_child_objects, child_object_effects)
728 }
729 }
730 }
731
732 fn apply_child_object_effects_v0(
733 &mut self,
734 loaded_child_objects: &mut BTreeMap<ObjectID, LoadedRuntimeObject>,
735 child_object_effects: BTreeMap<ObjectID, ChildObjectEffectV0>,
736 ) {
737 for (child, child_object_effect) in child_object_effects {
738 let ChildObjectEffectV0 {
739 owner: parent,
740 ty,
741 effect,
742 } = child_object_effect;
743
744 if let Some(loaded_child) = loaded_child_objects.get_mut(&child) {
745 loaded_child.is_modified = true;
746 }
747
748 match effect {
749 Op::Modify(v) => {
751 debug_assert!(!self.transfers.contains_key(&child));
752 debug_assert!(!self.new_ids.contains(&child));
753 debug_assert!(loaded_child_objects.contains_key(&child));
754 self.transfers
755 .insert(child, (Owner::ObjectOwner(parent.into()), ty, v));
756 }
757
758 Op::New(v) => {
759 debug_assert!(!self.transfers.contains_key(&child));
760 self.transfers
761 .insert(child, (Owner::ObjectOwner(parent.into()), ty, v));
762 }
763
764 Op::Delete => {
765 if self.transfers.contains_key(&child) {
767 debug_assert!(!self.deleted_ids.contains(&child));
768 }
769 if self.deleted_ids.contains(&child) {
771 debug_assert!(!self.transfers.contains_key(&child));
772 debug_assert!(!self.new_ids.contains(&child));
773 }
774 }
775 }
776 }
777 }
778
779 fn apply_child_object_effects_v1(
780 &mut self,
781 loaded_child_objects: &mut BTreeMap<ObjectID, LoadedRuntimeObject>,
782 child_object_effects: BTreeMap<ObjectID, ChildObjectEffectV1>,
783 ) {
784 for (child, child_object_effect) in child_object_effects {
785 let ChildObjectEffectV1 {
786 owner: parent,
787 ty,
788 final_value,
789 object_changed,
790 } = child_object_effect;
791
792 if object_changed {
793 if let Some(loaded_child) = loaded_child_objects.get_mut(&child) {
794 loaded_child.is_modified = true;
795 }
796
797 match final_value {
798 None => {
799 debug_assert!(
805 !self.transfers.contains_key(&child)
806 || !self.deleted_ids.contains(&child)
807 );
808 debug_assert!(
812 !self.deleted_ids.contains(&child)
813 || (!self.transfers.contains_key(&child)
814 && !self.new_ids.contains(&child))
815 );
816 }
817 Some(v) => {
818 debug_assert!(
822 !self.transfers.contains_key(&child)
823 && !self.deleted_ids.contains(&child)
824 );
825 debug_assert!(
830 !loaded_child_objects.contains_key(&child)
831 || !self.new_ids.contains(&child)
832 );
833 self.transfers
835 .insert(child, (Owner::ObjectOwner(parent.into()), ty, v));
836 }
837 }
838 } else {
839 debug_assert!(
854 final_value.is_none()
855 || (loaded_child_objects.contains_key(&child)
856 && !self.deleted_ids.contains(&child)
857 && !self.transfers.contains_key(&child)
858 && !self.input_objects.contains_key(&child)
859 && !self.received.contains_key(&child))
860 );
861 debug_assert!(
863 loaded_child_objects
864 .get(&child)
865 .is_none_or(|loaded_child| !loaded_child.is_modified)
866 );
867 }
868 }
869 }
870}
871
872fn check_circular_ownership(
873 transfers: impl IntoIterator<Item = (ObjectID, Owner)>,
874) -> Result<(), ExecutionError> {
875 let mut object_owner_map = BTreeMap::new();
876 for (id, recipient) in transfers {
877 object_owner_map.remove(&id);
878 match recipient {
879 Owner::AddressOwner(_)
880 | Owner::Shared { .. }
881 | Owner::Immutable
882 | Owner::ConsensusAddressOwner { .. } => (),
883 Owner::ObjectOwner(new_owner) => {
884 let new_owner: ObjectID = new_owner.into();
885 let mut cur = new_owner;
886 loop {
887 if cur == id {
888 return Err(ExecutionError::from_kind(
889 ExecutionErrorKind::CircularObjectOwnership { object: cur },
890 ));
891 }
892 if let Some(parent) = object_owner_map.get(&cur) {
893 cur = *parent;
894 } else {
895 break;
896 }
897 }
898 object_owner_map.insert(id, new_owner);
899 }
900 }
901 }
902 Ok(())
903}
904
905pub fn get_all_uids(
911 fully_annotated_layout: &MoveTypeLayout,
912 bcs_bytes: &[u8],
913) -> Result<BTreeSet<ObjectID>, String> {
914 let mut ids = BTreeSet::new();
915 struct UIDTraversal<'i>(&'i mut BTreeSet<ObjectID>);
916 struct UIDCollector<'i>(&'i mut BTreeSet<ObjectID>);
917
918 impl<'b, 'l> AV::Traversal<'b, 'l> for UIDTraversal<'_> {
919 type Error = AV::Error;
920
921 fn traverse_struct(
922 &mut self,
923 driver: &mut AV::StructDriver<'_, 'b, 'l>,
924 ) -> Result<(), Self::Error> {
925 if driver.struct_layout().type_ == UID::type_() {
926 while driver.next_field(&mut UIDCollector(self.0))?.is_some() {}
927 } else {
928 while driver.next_field(self)?.is_some() {}
929 }
930 Ok(())
931 }
932 }
933
934 impl<'b, 'l> AV::Traversal<'b, 'l> for UIDCollector<'_> {
935 type Error = AV::Error;
936 fn traverse_address(
937 &mut self,
938 _driver: &AV::ValueDriver<'_, 'b, 'l>,
939 value: AccountAddress,
940 ) -> Result<(), Self::Error> {
941 self.0.insert(value.into());
942 Ok(())
943 }
944 }
945
946 MoveValue::visit_deserialize(
947 bcs_bytes,
948 fully_annotated_layout,
949 &mut UIDTraversal(&mut ids),
950 )
951 .map_err(|e| format!("Failed to deserialize. {e}"))?;
952 Ok(ids)
953}