1pub(crate) mod accumulator;
5mod fingerprint;
6pub(crate) mod object_store;
7
8use crate::object_runtime::object_store::{CacheMetadata, ChildObjectEffect};
9
10use self::object_store::{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 language_storage::StructTag,
21 runtime_value as R,
22 vm_status::StatusCode,
23};
24use move_vm_runtime::execution::values::{GlobalValue, Value};
25use move_vm_runtime::natives::extensions::NativeExtensionMarker;
26use object_store::{ActiveChildObject, ChildObjectStore};
27use std::{
28 collections::{BTreeMap, BTreeSet},
29 sync::Arc,
30};
31use sui_protocol_config::{LimitThresholdCrossed, ProtocolConfig, check_limit_by_meter};
32use sui_types::{
33 SUI_ACCUMULATOR_ROOT_OBJECT_ID, SUI_ADDRESS_ALIAS_STATE_OBJECT_ID,
34 SUI_AUTHENTICATOR_STATE_OBJECT_ID, SUI_BRIDGE_OBJECT_ID, SUI_CLOCK_OBJECT_ID,
35 SUI_COIN_REGISTRY_OBJECT_ID, SUI_DENY_LIST_OBJECT_ID, SUI_DISPLAY_REGISTRY_OBJECT_ID,
36 SUI_FORWARDING_ADDRESS_REGISTRY_OBJECT_ID, SUI_RANDOMNESS_STATE_OBJECT_ID,
37 SUI_SYSTEM_STATE_OBJECT_ID, TypeTag,
38 base_types::{MoveObjectType, ObjectID, SequenceNumber, SuiAddress},
39 committee::EpochId,
40 error::{ExecutionError, VMMemoryLimitExceededSubStatusCode},
41 execution::DynamicallyLoadedObjectMetadata,
42 execution_status::ExecutionErrorKind,
43 id::UID,
44 metrics::ExecutionMetrics,
45 move_package::MovePackage,
46 object::{MoveObject, Owner},
47 storage::RuntimeObjectResolver,
48};
49use tracing::error;
50
51pub use accumulator::*;
52
53pub enum ObjectEvent {
54 Transfer(Owner, MoveObject),
56 DeleteObjectID(ObjectID),
58}
59
60type Set<K> = IndexSet<K>;
61
62#[derive(Default)]
63pub(crate) struct TestInventories {
64 pub(crate) objects: BTreeMap<ObjectID, Value>,
65 pub(crate) address_inventories: BTreeMap<SuiAddress, BTreeMap<MoveObjectType, Set<ObjectID>>>,
67 pub(crate) shared_inventory: BTreeMap<MoveObjectType, Set<ObjectID>>,
69 pub(crate) immutable_inventory: BTreeMap<MoveObjectType, Set<ObjectID>>,
70 pub(crate) taken_immutable_values: BTreeMap<MoveObjectType, BTreeMap<ObjectID, Value>>,
71 pub(crate) taken: BTreeMap<ObjectID, Owner>,
73 pub(crate) allocated_tickets: BTreeMap<ObjectID, (DynamicallyLoadedObjectMetadata, Value)>,
75}
76
77#[derive(Debug)]
78pub struct LoadedRuntimeObject {
79 pub version: SequenceNumber,
80 pub is_modified: bool,
81}
82
83pub struct RuntimeResults {
84 pub writes: IndexMap<ObjectID, (Owner, MoveObjectType, Value)>,
85 pub user_events: Vec<(StructTag, Value)>,
86 pub accumulator_events: Vec<MoveAccumulatorEvent>,
87 pub loaded_child_objects: BTreeMap<ObjectID, LoadedRuntimeObject>,
89 pub created_object_ids: Set<ObjectID>,
90 pub deleted_object_ids: Set<ObjectID>,
91 pub settlement_input_sui: u64,
92 pub settlement_output_sui: u64,
93}
94
95#[derive(Default)]
96pub(crate) struct ObjectRuntimeState {
97 pub(crate) input_objects: BTreeMap<ObjectID, Owner>,
98 new_ids: Set<ObjectID>,
100 generated_ids: Set<ObjectID>,
102 deleted_ids: Set<ObjectID>,
104 transfers: IndexMap<ObjectID, (Owner, MoveObjectType, Value)>,
107 events: Vec<(StructTag, Value)>,
108 accumulator_events: Vec<MoveAccumulatorEvent>,
109 total_events_size: u64,
111 total_events_emitted: u64,
112 received: IndexMap<ObjectID, DynamicallyLoadedObjectMetadata>,
113 settlement_input_sui: u64,
119 settlement_output_sui: u64,
120 accumulator_merge_totals: BTreeMap<(AccountAddress, TypeTag), u128>,
121 accumulator_split_totals: BTreeMap<(AccountAddress, TypeTag), u128>,
122}
123
124#[derive(Tid)]
125pub struct ObjectRuntime<'a> {
126 child_object_store: ChildObjectStore<'a>,
127 pub(crate) test_inventories: TestInventories,
129 pub(crate) state: ObjectRuntimeState,
131 is_metered: bool,
133
134 pub(crate) protocol_config: &'a ProtocolConfig,
135 pub(crate) metrics: Arc<ExecutionMetrics>,
136}
137
138impl<'a> NativeExtensionMarker<'a> for ObjectRuntime<'a> {}
139
140pub enum TransferResult {
141 New,
142 SameOwner,
143 OwnerChanged,
144}
145
146pub struct InputObject {
147 pub contained_uids: BTreeSet<ObjectID>,
148 pub version: SequenceNumber,
149 pub owner: Owner,
150}
151
152impl TestInventories {
153 fn new() -> Self {
154 Self::default()
155 }
156}
157
158impl<'a> ObjectRuntime<'a> {
159 pub fn new(
160 object_resolver: &'a dyn RuntimeObjectResolver,
161 input_objects: BTreeMap<ObjectID, InputObject>,
162 is_metered: bool,
163 protocol_config: &'a ProtocolConfig,
164 metrics: Arc<ExecutionMetrics>,
165 epoch_id: EpochId,
166 ) -> Self {
167 let mut input_object_owners = BTreeMap::new();
168 let mut root_version = BTreeMap::new();
169 let mut wrapped_object_containers = BTreeMap::new();
170 for (id, input_object) in input_objects {
171 let InputObject {
172 contained_uids,
173 version,
174 owner,
175 } = input_object;
176 input_object_owners.insert(id, owner);
177 debug_assert!(contained_uids.contains(&id));
178 for contained_uid in contained_uids {
179 root_version.insert(contained_uid, version);
180 if contained_uid != id {
181 let prev = wrapped_object_containers.insert(contained_uid, id);
182 debug_assert!(prev.is_none());
183 }
184 }
185 }
186 Self {
187 child_object_store: ChildObjectStore::new(
188 object_resolver,
189 root_version,
190 wrapped_object_containers,
191 is_metered,
192 protocol_config,
193 metrics.clone(),
194 epoch_id,
195 ),
196 test_inventories: TestInventories::new(),
197 state: ObjectRuntimeState {
198 input_objects: input_object_owners,
199 new_ids: Set::new(),
200 generated_ids: Set::new(),
201 deleted_ids: Set::new(),
202 transfers: IndexMap::new(),
203 events: vec![],
204 accumulator_events: vec![],
205 total_events_size: 0,
206 total_events_emitted: 0,
207 received: IndexMap::new(),
208 settlement_input_sui: 0,
209 settlement_output_sui: 0,
210 accumulator_merge_totals: BTreeMap::new(),
211 accumulator_split_totals: BTreeMap::new(),
212 },
213 is_metered,
214 protocol_config,
215 metrics,
216 }
217 }
218
219 pub fn new_id(&mut self, id: ObjectID) -> PartialVMResult<()> {
220 if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
223 self.is_metered,
224 self.state.new_ids.len(),
225 self.protocol_config.max_num_new_move_object_ids(),
226 self.protocol_config.max_num_new_move_object_ids_system_tx(),
227 self.metrics.limits_metrics.excessive_new_move_object_ids
228 ) {
229 return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
230 .with_message(format!("Creating more than {} IDs is not allowed", lim))
231 .with_sub_status(
232 VMMemoryLimitExceededSubStatusCode::NEW_ID_COUNT_LIMIT_EXCEEDED as u64,
233 ));
234 };
235
236 let was_present = self.state.deleted_ids.shift_remove(&id);
240 if !was_present {
241 self.state.generated_ids.insert(id);
243 self.state.new_ids.insert(id);
244 }
245 Ok(())
246 }
247
248 pub fn new_id_from_hash(&mut self, parent: ObjectID, id: ObjectID) -> PartialVMResult<()> {
252 self.new_id(id)?;
253 self.child_object_store
254 .inherit_root_version_from_parent(parent, id)?;
255 Ok(())
256 }
257
258 pub fn delete_id(&mut self, id: ObjectID) -> PartialVMResult<()> {
259 if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
264 self.is_metered,
265 self.state.deleted_ids.len(),
266 self.protocol_config.max_num_deleted_move_object_ids(),
267 self.protocol_config
268 .max_num_deleted_move_object_ids_system_tx(),
269 self.metrics
270 .limits_metrics
271 .excessive_deleted_move_object_ids
272 ) {
273 return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
274 .with_message(format!("Deleting more than {} IDs is not allowed", lim))
275 .with_sub_status(
276 VMMemoryLimitExceededSubStatusCode::DELETED_ID_COUNT_LIMIT_EXCEEDED as u64,
277 ));
278 };
279
280 let was_new = self.state.new_ids.shift_remove(&id);
281 if !was_new {
282 self.state.deleted_ids.insert(id);
283 }
284 Ok(())
285 }
286
287 pub fn transfer(
290 &mut self,
291 owner: Owner,
292 ty: MoveObjectType,
293 obj: Value,
294 end_of_transaction: bool,
295 ) -> PartialVMResult<TransferResult> {
296 let id: ObjectID = get_object_id(obj.copy_value())?
297 .value_as::<AccountAddress>()?
298 .into();
299 let is_framework_obj = [
305 SUI_SYSTEM_STATE_OBJECT_ID,
306 SUI_CLOCK_OBJECT_ID,
307 SUI_AUTHENTICATOR_STATE_OBJECT_ID,
308 SUI_RANDOMNESS_STATE_OBJECT_ID,
309 SUI_DENY_LIST_OBJECT_ID,
310 SUI_BRIDGE_OBJECT_ID,
311 SUI_ACCUMULATOR_ROOT_OBJECT_ID,
312 SUI_COIN_REGISTRY_OBJECT_ID,
313 SUI_DISPLAY_REGISTRY_OBJECT_ID,
314 SUI_ADDRESS_ALIAS_STATE_OBJECT_ID,
315 SUI_FORWARDING_ADDRESS_REGISTRY_OBJECT_ID,
316 ]
317 .contains(&id);
318 let transfer_result = if self.state.new_ids.contains(&id) {
319 TransferResult::New
320 } else if let Some(prev_owner) = self.state.input_objects.get(&id) {
321 match (&owner, prev_owner) {
322 (Owner::Shared { .. }, Owner::Shared { .. }) => TransferResult::SameOwner,
324 (
325 Owner::ConsensusAddressOwner {
326 owner: new_owner, ..
327 },
328 Owner::ConsensusAddressOwner {
329 owner: old_owner, ..
330 },
331 ) if new_owner == old_owner => TransferResult::SameOwner,
332 (
333 Owner::Party {
334 permissions: new_permissions,
335 ..
336 },
337 Owner::Party {
338 permissions: old_permissions,
339 ..
340 },
341 ) if new_permissions == old_permissions => TransferResult::SameOwner,
342 (new @ Owner::AddressOwner(_), old)
343 | (new @ Owner::ObjectOwner(_), old)
344 | (new @ Owner::Immutable, old)
345 if new == old =>
346 {
347 TransferResult::SameOwner
348 }
349 _ => TransferResult::OwnerChanged,
350 }
351 } else if is_framework_obj {
352 self.state.new_ids.insert(id);
355 self.state.generated_ids.insert(id);
356 TransferResult::New
357 } else {
358 TransferResult::OwnerChanged
359 };
360 if end_of_transaction
362 && !matches!(
363 transfer_result,
364 TransferResult::New | TransferResult::SameOwner
365 )
366 {
367 return Err(
368 PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message(
369 format!(
370 "Untransferred object {} had its owner change or was not new",
371 id
372 ),
373 ),
374 );
375 }
376
377 if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
380 self.is_metered && !is_framework_obj && !end_of_transaction,
385 self.state.transfers.len(),
386 self.protocol_config.max_num_transferred_move_object_ids(),
387 self.protocol_config
388 .max_num_transferred_move_object_ids_system_tx(),
389 self.metrics
390 .limits_metrics
391 .excessive_transferred_move_object_ids
392 ) {
393 return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
394 .with_message(format!("Transferring more than {} IDs is not allowed", lim))
395 .with_sub_status(
396 VMMemoryLimitExceededSubStatusCode::TRANSFER_ID_COUNT_LIMIT_EXCEEDED as u64,
397 ));
398 };
399
400 self.state.transfers.insert(id, (owner, ty, obj));
401 Ok(transfer_result)
402 }
403
404 pub fn emit_event(&mut self, tag: StructTag, event: Value) -> PartialVMResult<()> {
405 if self.state.events.len() >= (self.protocol_config.max_num_event_emit() as usize) {
406 return Err(max_event_error(self.protocol_config.max_num_event_emit()));
407 }
408 self.state.events.push((tag, event));
409 self.state.total_events_emitted += 1;
410 Ok(())
411 }
412
413 pub fn take_user_events(&mut self) -> Vec<(StructTag, Value)> {
414 std::mem::take(&mut self.state.events)
415 }
416
417 pub fn emit_accumulator_event(
418 &mut self,
419 accumulator_id: ObjectID,
420 action: MoveAccumulatorAction,
421 target_addr: AccountAddress,
422 target_ty: TypeTag,
423 value: MoveAccumulatorValue,
424 ) -> PartialVMResult<()> {
425 if let MoveAccumulatorValue::U64(amount) = value {
426 let key = (target_addr, target_ty.clone());
427
428 match action {
429 MoveAccumulatorAction::Merge => {
430 let current = self
431 .state
432 .accumulator_merge_totals
433 .get(&key)
434 .copied()
435 .unwrap_or(0);
436 let new_total = current + amount as u128;
437 if new_total > u64::MAX as u128 {
438 return Err(PartialVMError::new(StatusCode::ARITHMETIC_ERROR)
439 .with_message(format!(
440 "accumulator merge overflow: total merges {} exceed u64::MAX",
441 new_total
442 )));
443 }
444 self.state.accumulator_merge_totals.insert(key, new_total);
445 }
446 MoveAccumulatorAction::Split => {
447 let current = self
448 .state
449 .accumulator_split_totals
450 .get(&key)
451 .copied()
452 .unwrap_or(0);
453 let new_total = current + amount as u128;
454 if new_total > u64::MAX as u128 {
455 return Err(PartialVMError::new(StatusCode::ARITHMETIC_ERROR)
456 .with_message(format!(
457 "accumulator split overflow: total splits {} exceed u64::MAX",
458 new_total
459 )));
460 }
461 self.state.accumulator_split_totals.insert(key, new_total);
462 }
463 }
464 }
465
466 let event = MoveAccumulatorEvent {
467 accumulator_id,
468 action,
469 target_addr,
470 target_ty,
471 value,
472 };
473 self.state.accumulator_events.push(event);
474 Ok(())
475 }
476
477 pub(crate) fn child_object_exists(
478 &mut self,
479 parent: ObjectID,
480 child: ObjectID,
481 ) -> PartialVMResult<CacheMetadata<bool>> {
482 self.child_object_store.object_exists(parent, child)
483 }
484
485 pub(crate) fn child_object_exists_and_has_type(
486 &mut self,
487 parent: ObjectID,
488 child: ObjectID,
489 child_type: &MoveObjectType,
490 ) -> PartialVMResult<CacheMetadata<bool>> {
491 self.child_object_store
492 .object_exists_and_has_type(parent, child, child_type)
493 }
494
495 pub(super) fn receive_object(
496 &mut self,
497 parent: ObjectID,
498 child: ObjectID,
499 child_version: SequenceNumber,
500 child_layout: &R::MoveTypeLayout,
501 child_fully_annotated_layout: &MoveTypeLayout,
502 child_move_type: MoveObjectType,
503 ) -> PartialVMResult<Option<ObjectResult<CacheMetadata<Value>>>> {
504 let Some((value, obj_meta)) = self.child_object_store.receive_object(
505 parent,
506 child,
507 child_version,
508 child_layout,
509 child_fully_annotated_layout,
510 child_move_type,
511 )?
512 else {
513 return Ok(None);
514 };
515
516 if self
517 .protocol_config
518 .early_return_receive_object_mismatched_type()
519 && let ObjectResult::MismatchedType = &value
520 && self.state.received.contains_key(&child)
521 {
522 return Ok(Some(ObjectResult::MismatchedType));
525 }
526
527 if self.state.received.insert(child, obj_meta).is_some() {
530 return Err(
533 PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message(format!(
534 "Object {child} at version {child_version} already received. This can only happen \
535 if multiple `Receiving` arguments exist for the same object in the transaction which is impossible."
536 )),
537 );
538 }
539 Ok(Some(value))
540 }
541
542 pub(crate) fn get_or_fetch_child_object(
543 &mut self,
544 parent: ObjectID,
545 child: ObjectID,
546 child_layout: &R::MoveTypeLayout,
547 child_fully_annotated_layout: &MoveTypeLayout,
548 child_move_type: MoveObjectType,
549 ) -> PartialVMResult<ObjectResult<CacheMetadata<&mut GlobalValue>>> {
550 let res = self.child_object_store.get_or_fetch_object(
551 parent,
552 child,
553 child_layout,
554 child_fully_annotated_layout,
555 child_move_type,
556 )?;
557 Ok(match res {
558 ObjectResult::MismatchedType => ObjectResult::MismatchedType,
559 ObjectResult::Loaded((cache_info, child_object)) => {
560 ObjectResult::Loaded((cache_info, &mut child_object.value))
561 }
562 })
563 }
564
565 pub(crate) fn add_child_object(
566 &mut self,
567 parent: ObjectID,
568 child: ObjectID,
569 child_move_type: MoveObjectType,
570 child_value: Value,
571 ) -> PartialVMResult<()> {
572 self.child_object_store
573 .add_object(parent, child, child_move_type, child_value)
574 }
575
576 pub(crate) fn config_setting_unsequenced_read(
577 &mut self,
578 config_id: ObjectID,
579 name_df_id: ObjectID,
580 field_setting_layout: &R::MoveTypeLayout,
581 field_setting_object_type: &MoveObjectType,
582 ) -> Option<Value> {
583 match self.child_object_store.config_setting_unsequenced_read(
584 config_id,
585 name_df_id,
586 field_setting_layout,
587 field_setting_object_type,
588 ) {
589 Err(e) => {
590 error!(
591 "Failed to read config setting.
592 config_id: {config_id},
593 name_df_id: {name_df_id},
594 field_setting_object_type: {field_setting_object_type:?},
595 error: {e}"
596 );
597 None
598 }
599 Ok(ObjectResult::MismatchedType) | Ok(ObjectResult::Loaded(None)) => None,
600 Ok(ObjectResult::Loaded(Some(value))) => Some(value),
601 }
602 }
603
604 pub(super) fn config_setting_cache_update(
605 &mut self,
606 config_id: ObjectID,
607 name_df_id: ObjectID,
608 setting_value_object_type: MoveObjectType,
609 value: Option<Value>,
610 ) {
611 self.child_object_store.config_setting_cache_update(
612 config_id,
613 name_df_id,
614 setting_value_object_type,
615 value,
616 )
617 }
618
619 pub fn get_package_at_version(
620 &self,
621 package_id: ObjectID,
622 version: SequenceNumber,
623 ) -> Option<MovePackage> {
624 self.child_object_store
625 .get_package_at_version(package_id, version)
626 }
627
628 pub(crate) fn take_state(&mut self) -> ObjectRuntimeState {
630 std::mem::take(&mut self.state)
631 }
632
633 pub fn is_deleted(&self, id: &ObjectID) -> bool {
634 self.state.deleted_ids.contains(id)
635 }
636
637 pub fn is_transferred(&self, id: &ObjectID) -> Option<Owner> {
638 self.state
639 .transfers
640 .get(id)
641 .map(|(owner, _, _)| owner.clone())
642 }
643
644 pub fn finish(mut self) -> Result<RuntimeResults, ExecutionError> {
645 let loaded_child_objects = self.loaded_runtime_objects();
646 let child_effects = self.child_object_store.take_effects().map_err(|e| {
647 ExecutionError::invariant_violation(format!("Failed to take child object effects: {e}"))
648 })?;
649 self.state.finish(loaded_child_objects, child_effects)
650 }
651
652 pub(crate) fn all_active_child_objects(&self) -> impl Iterator<Item = ActiveChildObject<'_>> {
653 self.child_object_store.all_active_objects()
654 }
655
656 pub fn loaded_runtime_objects(&self) -> BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata> {
657 debug_assert!(
660 self.child_object_store
661 .cached_objects()
662 .keys()
663 .all(|id| !self.state.received.contains_key(id))
664 );
665 self.child_object_store
666 .cached_objects()
667 .iter()
668 .filter_map(|(id, obj_opt)| {
669 obj_opt.as_ref().map(|obj| {
670 (
671 *id,
672 DynamicallyLoadedObjectMetadata {
673 version: obj.version(),
674 digest: obj.digest(),
675 storage_rebate: obj.storage_rebate,
676 owner: obj.owner.clone(),
677 previous_transaction: obj.previous_transaction,
678 },
679 )
680 })
681 })
682 .chain(
683 self.state
684 .received
685 .iter()
686 .map(|(id, meta)| (*id, meta.clone())),
687 )
688 .collect()
689 }
690
691 pub fn wrapped_object_containers(&self) -> BTreeMap<ObjectID, ObjectID> {
694 self.child_object_store.wrapped_object_containers().clone()
695 }
696
697 pub fn record_settlement_sui_conservation(&mut self, input_sui: u64, output_sui: u64) {
698 self.state.settlement_input_sui += input_sui;
699 self.state.settlement_output_sui += output_sui;
700 }
701
702 pub fn generated_object_ids(&self) -> BTreeSet<ObjectID> {
705 self.state.generated_ids.iter().cloned().collect()
706 }
707}
708
709pub fn max_event_error(max_events: u64) -> PartialVMError {
710 PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
711 .with_message(format!(
712 "Emitting more than {} events is not allowed",
713 max_events
714 ))
715 .with_sub_status(VMMemoryLimitExceededSubStatusCode::EVENT_COUNT_LIMIT_EXCEEDED as u64)
716}
717
718impl ObjectRuntimeState {
719 pub(crate) fn finish(
728 mut self,
729 loaded_child_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
730 child_object_effects: ChildObjectEffects,
731 ) -> Result<RuntimeResults, ExecutionError> {
732 let mut loaded_child_objects: BTreeMap<_, _> = loaded_child_objects
733 .into_iter()
734 .map(|(id, metadata)| {
735 (
736 id,
737 LoadedRuntimeObject {
738 version: metadata.version,
739 is_modified: false,
740 },
741 )
742 })
743 .collect();
744 self.apply_child_object_effects(&mut loaded_child_objects, child_object_effects);
745 let ObjectRuntimeState {
746 input_objects: _,
747 new_ids,
748 generated_ids,
749 deleted_ids,
750 transfers,
751 events: user_events,
752 total_events_size: _,
753 received,
754 accumulator_events,
755 settlement_input_sui,
756 settlement_output_sui,
757 accumulator_merge_totals: _,
758 accumulator_split_totals: _,
759 total_events_emitted: _,
760 } = self;
761
762 debug_assert!(new_ids.is_subset(&generated_ids));
764
765 check_circular_ownership(
768 transfers
769 .iter()
770 .map(|(id, (owner, _, _))| (*id, owner.clone())),
771 )?;
772 let written_objects: IndexMap<_, _> = transfers
779 .into_iter()
780 .map(|(id, (owner, type_, value))| {
781 if let Some(loaded_child) = loaded_child_objects.get_mut(&id) {
782 loaded_child.is_modified = true;
783 }
784 (id, (owner, type_, value))
785 })
786 .collect();
787 for deleted_id in &deleted_ids {
788 if let Some(loaded_child) = loaded_child_objects.get_mut(deleted_id) {
789 loaded_child.is_modified = true;
790 }
791 }
792
793 for (received_object, _) in received.into_iter() {
797 match loaded_child_objects.get_mut(&received_object) {
798 Some(loaded_child) => {
799 loaded_child.is_modified = true;
800 }
801 None => {
802 return Err(ExecutionError::invariant_violation(format!(
803 "Failed to find received UID {received_object} in loaded child objects."
804 )));
805 }
806 }
807 }
808
809 Ok(RuntimeResults {
810 writes: written_objects,
811 user_events,
812 accumulator_events,
813 loaded_child_objects,
814 created_object_ids: new_ids,
815 deleted_object_ids: deleted_ids,
816 settlement_input_sui,
817 settlement_output_sui,
818 })
819 }
820
821 pub fn events(&self) -> &[(StructTag, Value)] {
822 &self.events
823 }
824
825 pub fn total_events_emitted(&self) -> u64 {
826 self.total_events_emitted
827 }
828
829 pub fn total_events_size(&self) -> u64 {
830 self.total_events_size
831 }
832
833 pub fn incr_total_events_size(&mut self, size: u64) {
834 self.total_events_size += size;
835 }
836
837 fn apply_child_object_effects(
838 &mut self,
839 loaded_child_objects: &mut BTreeMap<ObjectID, LoadedRuntimeObject>,
840 child_object_effects: ChildObjectEffects,
841 ) {
842 for (child, child_object_effect) in child_object_effects {
843 let ChildObjectEffect {
844 owner: parent,
845 ty,
846 final_value,
847 object_changed,
848 } = child_object_effect;
849
850 if object_changed {
851 if let Some(loaded_child) = loaded_child_objects.get_mut(&child) {
852 loaded_child.is_modified = true;
853 }
854
855 match final_value {
856 None => {
857 debug_assert!(
863 !self.transfers.contains_key(&child)
864 || !self.deleted_ids.contains(&child)
865 );
866 debug_assert!(
870 !self.deleted_ids.contains(&child)
871 || (!self.transfers.contains_key(&child)
872 && !self.new_ids.contains(&child))
873 );
874 }
875 Some(v) => {
876 debug_assert!(
880 !self.transfers.contains_key(&child)
881 && !self.deleted_ids.contains(&child)
882 );
883 debug_assert!(
888 !loaded_child_objects.contains_key(&child)
889 || !self.new_ids.contains(&child)
890 );
891 self.transfers
893 .insert(child, (Owner::ObjectOwner(parent.into()), ty, v));
894 }
895 }
896 } else {
897 debug_assert!(
912 final_value.is_none()
913 || (loaded_child_objects.contains_key(&child)
914 && !self.deleted_ids.contains(&child)
915 && !self.transfers.contains_key(&child)
916 && !self.input_objects.contains_key(&child)
917 && !self.received.contains_key(&child))
918 );
919 debug_assert!(
921 loaded_child_objects
922 .get(&child)
923 .is_none_or(|loaded_child| !loaded_child.is_modified)
924 );
925 }
926 }
927 }
928}
929
930fn check_circular_ownership(
931 transfers: impl IntoIterator<Item = (ObjectID, Owner)>,
932) -> Result<(), ExecutionError> {
933 let mut object_owner_map = BTreeMap::new();
934 for (id, recipient) in transfers {
935 object_owner_map.remove(&id);
936 match recipient {
937 Owner::AddressOwner(_)
938 | Owner::Shared { .. }
939 | Owner::Immutable
940 | Owner::ConsensusAddressOwner { .. }
941 | Owner::Party { .. } => (),
942 Owner::ObjectOwner(new_owner) => {
943 let new_owner: ObjectID = new_owner.into();
944 let mut cur = new_owner;
945 loop {
946 if cur == id {
947 return Err(ExecutionError::from_kind(
948 ExecutionErrorKind::CircularObjectOwnership { object: cur },
949 ));
950 }
951 if let Some(parent) = object_owner_map.get(&cur) {
952 cur = *parent;
953 } else {
954 break;
955 }
956 }
957 object_owner_map.insert(id, new_owner);
958 }
959 }
960 }
961 Ok(())
962}
963
964pub fn get_all_uids(
970 fully_annotated_layout: &MoveTypeLayout,
971 bcs_bytes: &[u8],
972) -> Result<BTreeSet<ObjectID>, String> {
973 let mut ids = BTreeSet::new();
974 struct UIDTraversal<'i>(&'i mut BTreeSet<ObjectID>);
975 struct UIDCollector<'i>(&'i mut BTreeSet<ObjectID>);
976
977 impl<'b, 'l> AV::Traversal<'b, 'l> for UIDTraversal<'_> {
978 type Error = AV::Error;
979
980 fn traverse_struct(
981 &mut self,
982 driver: &mut AV::StructDriver<'_, 'b, 'l>,
983 ) -> Result<(), Self::Error> {
984 if driver.struct_layout().type_ == UID::type_() {
985 while driver.next_field(&mut UIDCollector(self.0))?.is_some() {}
986 } else {
987 while driver.next_field(self)?.is_some() {}
988 }
989 Ok(())
990 }
991 }
992
993 impl<'b, 'l> AV::Traversal<'b, 'l> for UIDCollector<'_> {
994 type Error = AV::Error;
995 fn traverse_address(
996 &mut self,
997 _driver: &AV::ValueDriver<'_, 'b, 'l>,
998 value: AccountAddress,
999 ) -> Result<(), Self::Error> {
1000 self.0.insert(value.into());
1001 Ok(())
1002 }
1003 }
1004
1005 MoveValue::visit_deserialize(
1006 bcs_bytes,
1007 fully_annotated_layout,
1008 &mut UIDTraversal(&mut ids),
1009 )
1010 .map_err(|e| format!("Failed to deserialize. {e}"))?;
1011 Ok(ids)
1012}