1pub(crate) mod accumulator;
5mod fingerprint;
6pub(crate) mod object_store;
7#[cfg(test)]
8mod unit_tests;
9
10use crate::object_runtime::object_store::{CacheMetadata, ChildObjectEffect};
11
12use self::object_store::{ChildObjectEffects, ObjectResult};
13use super::get_object_id;
14use better_any::{Tid, TidAble};
15use indexmap::map::IndexMap;
16use indexmap::set::IndexSet;
17use move_binary_format::errors::{PartialVMError, PartialVMResult};
18use move_core_types::{
19 account_address::AccountAddress,
20 annotated_value::{MoveTypeLayout, MoveValue},
21 annotated_visitor as AV,
22 language_storage::StructTag,
23 runtime_value as R,
24 u256::U256,
25 vm_status::StatusCode,
26};
27use move_vm_runtime::execution::values::{GlobalValue, Value};
28use move_vm_runtime::natives::extensions::NativeExtensionMarker;
29use object_store::{ActiveChildObject, ChildObjectStore};
30use std::{
31 collections::{BTreeMap, BTreeSet},
32 sync::Arc,
33};
34use sui_protocol_config::{LimitThresholdCrossed, ProtocolConfig, check_limit_by_meter};
35use sui_types::{
36 SUI_ACCUMULATOR_ROOT_OBJECT_ID, SUI_ADDRESS_ALIAS_STATE_OBJECT_ID,
37 SUI_AUTHENTICATOR_STATE_OBJECT_ID, SUI_BRIDGE_OBJECT_ID, SUI_CLOCK_OBJECT_ID,
38 SUI_COIN_REGISTRY_OBJECT_ID, SUI_DENY_LIST_OBJECT_ID, SUI_DISPLAY_REGISTRY_OBJECT_ID,
39 SUI_FORWARDING_ADDRESS_REGISTRY_OBJECT_ID, SUI_RANDOMNESS_STATE_OBJECT_ID,
40 SUI_SYSTEM_STATE_OBJECT_ID, TypeTag,
41 base_types::{MoveObjectType, ObjectID, SequenceNumber, SuiAddress},
42 committee::EpochId,
43 error::{ExecutionError, VMMemoryLimitExceededSubStatusCode},
44 execution::DynamicallyLoadedObjectMetadata,
45 execution_status::ExecutionErrorKind,
46 id::UID,
47 metrics::ExecutionMetrics,
48 move_package::MovePackage,
49 object::{MoveObject, Owner},
50 storage::{ImplicitSystemObjectResolver, ObjectFundsSufficiency, RuntimeObjectResolver},
51};
52use tracing::error;
53
54pub use accumulator::*;
55
56pub enum ObjectEvent {
57 Transfer(Owner, MoveObject),
59 DeleteObjectID(ObjectID),
61}
62
63type Set<K> = IndexSet<K>;
64
65#[derive(Default)]
66pub(crate) struct TestInventories {
67 pub(crate) objects: BTreeMap<ObjectID, Value>,
68 pub(crate) address_inventories: BTreeMap<SuiAddress, BTreeMap<MoveObjectType, Set<ObjectID>>>,
70 pub(crate) shared_inventory: BTreeMap<MoveObjectType, Set<ObjectID>>,
72 pub(crate) immutable_inventory: BTreeMap<MoveObjectType, Set<ObjectID>>,
73 pub(crate) taken_immutable_values: BTreeMap<MoveObjectType, BTreeMap<ObjectID, Value>>,
74 pub(crate) taken: BTreeMap<ObjectID, Owner>,
76 pub(crate) allocated_tickets: BTreeMap<ObjectID, (DynamicallyLoadedObjectMetadata, Value)>,
78}
79
80#[derive(Debug)]
81pub struct LoadedRuntimeObject {
82 pub version: SequenceNumber,
83 pub is_modified: bool,
84}
85
86pub struct RuntimeResults {
87 pub writes: IndexMap<ObjectID, (Owner, MoveObjectType, Value)>,
88 pub user_events: Vec<(StructTag, Value)>,
89 pub accumulator_events: Vec<MoveAccumulatorEvent>,
90 pub loaded_child_objects: BTreeMap<ObjectID, LoadedRuntimeObject>,
92 pub created_object_ids: Set<ObjectID>,
93 pub deleted_object_ids: Set<ObjectID>,
94 pub settlement_input_sui: u64,
95 pub settlement_output_sui: u64,
96}
97
98#[derive(Clone, Copy)]
99struct ObjectFundsAvailable {
100 available: U256,
104 queried: bool,
106}
107
108#[derive(Default)]
109pub(crate) struct ObjectRuntimeState {
110 pub(crate) input_objects: BTreeMap<ObjectID, Owner>,
111 new_ids: Set<ObjectID>,
113 generated_ids: Set<ObjectID>,
115 deleted_ids: Set<ObjectID>,
117 transfers: IndexMap<ObjectID, (Owner, MoveObjectType, Value)>,
120 events: Vec<(StructTag, Value)>,
121 accumulator_events: Vec<MoveAccumulatorEvent>,
122 total_events_size: u64,
124 total_events_emitted: u64,
125 received: IndexMap<ObjectID, DynamicallyLoadedObjectMetadata>,
126 settlement_input_sui: u64,
132 settlement_output_sui: u64,
133 accumulator_merge_totals: BTreeMap<(AccountAddress, TypeTag), u128>,
134 accumulator_split_totals: BTreeMap<(AccountAddress, TypeTag), u128>,
135 object_funds_available: BTreeMap<(AccountAddress, TypeTag), ObjectFundsAvailable>,
136}
137
138#[derive(Tid)]
139pub struct ObjectRuntime<'a> {
140 child_object_store: ChildObjectStore<'a>,
141 system_object_resolver: &'a dyn ImplicitSystemObjectResolver,
142 pub(crate) test_inventories: TestInventories,
144 pub(crate) state: ObjectRuntimeState,
146 is_metered: bool,
148
149 pub(crate) protocol_config: &'a ProtocolConfig,
150 pub(crate) metrics: Arc<ExecutionMetrics>,
151}
152
153impl<'a> NativeExtensionMarker<'a> for ObjectRuntime<'a> {}
154
155pub enum TransferResult {
156 New,
157 SameOwner,
158 OwnerChanged,
159}
160
161pub struct InputObject {
162 pub contained_uids: BTreeSet<ObjectID>,
163 pub version: SequenceNumber,
164 pub owner: Owner,
165}
166
167impl TestInventories {
168 fn new() -> Self {
169 Self::default()
170 }
171}
172
173impl ObjectFundsAvailable {
174 fn init() -> Self {
176 Self {
177 available: U256::from(0u64),
178 queried: false,
179 }
180 }
181
182 fn needs_store_read(&self, amount: U256) -> bool {
183 self.available < amount && !self.queried
184 }
185}
186
187impl<'a> ObjectRuntime<'a> {
188 pub fn new(
189 object_resolver: &'a dyn RuntimeObjectResolver,
190 system_object_resolver: &'a dyn ImplicitSystemObjectResolver,
191 input_objects: BTreeMap<ObjectID, InputObject>,
192 is_metered: bool,
193 protocol_config: &'a ProtocolConfig,
194 metrics: Arc<ExecutionMetrics>,
195 epoch_id: EpochId,
196 ) -> Self {
197 let mut input_object_owners = BTreeMap::new();
198 let mut root_version = BTreeMap::new();
199 let mut wrapped_object_containers = BTreeMap::new();
200 for (id, input_object) in input_objects {
201 let InputObject {
202 contained_uids,
203 version,
204 owner,
205 } = input_object;
206 input_object_owners.insert(id, owner);
207 debug_assert!(contained_uids.contains(&id));
208 for contained_uid in contained_uids {
209 root_version.insert(contained_uid, version);
210 if contained_uid != id {
211 let prev = wrapped_object_containers.insert(contained_uid, id);
212 debug_assert!(prev.is_none());
213 }
214 }
215 }
216 Self {
217 child_object_store: ChildObjectStore::new(
218 object_resolver,
219 root_version,
220 wrapped_object_containers,
221 is_metered,
222 protocol_config,
223 metrics.clone(),
224 epoch_id,
225 ),
226 system_object_resolver,
227 test_inventories: TestInventories::new(),
228 state: ObjectRuntimeState {
229 input_objects: input_object_owners,
230 new_ids: Set::new(),
231 generated_ids: Set::new(),
232 deleted_ids: Set::new(),
233 transfers: IndexMap::new(),
234 events: vec![],
235 accumulator_events: vec![],
236 total_events_size: 0,
237 total_events_emitted: 0,
238 received: IndexMap::new(),
239 settlement_input_sui: 0,
240 settlement_output_sui: 0,
241 accumulator_merge_totals: BTreeMap::new(),
242 accumulator_split_totals: BTreeMap::new(),
243 object_funds_available: BTreeMap::new(),
244 },
245 is_metered,
246 protocol_config,
247 metrics,
248 }
249 }
250
251 pub fn check_object_funds_sufficiency(
252 &mut self,
253 owner: SuiAddress,
254 type_: &TypeTag,
255 amount: U256,
256 ) -> ObjectFundsSufficiency {
257 let key = (owner.into(), type_.clone());
258 let entry = self
259 .state
260 .object_funds_available
261 .entry(key)
262 .or_insert_with(ObjectFundsAvailable::init);
263 if entry.needs_store_read(amount) {
264 let settled_available = match self
265 .system_object_resolver
266 .object_available_balance(owner, type_)
267 {
268 Ok(balance) => balance,
269 Err(e) => {
270 return ObjectFundsSufficiency::LoadError(e.to_string());
271 }
272 };
273 let Some(available) = entry.available.checked_add(U256::from(settled_available)) else {
274 return ObjectFundsSufficiency::Overflow;
275 };
276 entry.available = available;
277 entry.queried = true;
278 }
279 if entry.available >= amount {
280 entry.available -= amount;
281 ObjectFundsSufficiency::Sufficient
282 } else {
283 ObjectFundsSufficiency::Insufficient
284 }
285 }
286
287 pub(crate) fn object_funds_sufficiency_needs_store_read(
288 &self,
289 owner: SuiAddress,
290 type_: &TypeTag,
291 amount: U256,
292 ) -> bool {
293 self.state
294 .object_funds_available
295 .get(&(owner.into(), type_.clone()))
296 .copied()
297 .unwrap_or_else(ObjectFundsAvailable::init)
298 .needs_store_read(amount)
299 }
300
301 pub fn new_id(&mut self, id: ObjectID) -> PartialVMResult<()> {
302 if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
305 self.is_metered,
306 self.state.new_ids.len(),
307 self.protocol_config.max_num_new_move_object_ids(),
308 self.protocol_config.max_num_new_move_object_ids_system_tx(),
309 self.metrics.limits_metrics.excessive_new_move_object_ids
310 ) {
311 return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
312 .with_message(format!("Creating more than {} IDs is not allowed", lim))
313 .with_sub_status(
314 VMMemoryLimitExceededSubStatusCode::NEW_ID_COUNT_LIMIT_EXCEEDED as u64,
315 ));
316 };
317
318 let was_present = self.state.deleted_ids.shift_remove(&id);
322 if !was_present {
323 self.state.generated_ids.insert(id);
325 self.state.new_ids.insert(id);
326 }
327 Ok(())
328 }
329
330 pub fn new_id_from_hash(&mut self, parent: ObjectID, id: ObjectID) -> PartialVMResult<()> {
334 self.new_id(id)?;
335 self.child_object_store
336 .inherit_root_version_from_parent(parent, id)?;
337 Ok(())
338 }
339
340 pub fn delete_id(&mut self, id: ObjectID) -> PartialVMResult<()> {
341 if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
346 self.is_metered,
347 self.state.deleted_ids.len(),
348 self.protocol_config.max_num_deleted_move_object_ids(),
349 self.protocol_config
350 .max_num_deleted_move_object_ids_system_tx(),
351 self.metrics
352 .limits_metrics
353 .excessive_deleted_move_object_ids
354 ) {
355 return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
356 .with_message(format!("Deleting more than {} IDs is not allowed", lim))
357 .with_sub_status(
358 VMMemoryLimitExceededSubStatusCode::DELETED_ID_COUNT_LIMIT_EXCEEDED as u64,
359 ));
360 };
361
362 let was_new = self.state.new_ids.shift_remove(&id);
363 if !was_new {
364 self.state.deleted_ids.insert(id);
365 }
366 Ok(())
367 }
368
369 pub fn transfer(
372 &mut self,
373 owner: Owner,
374 ty: MoveObjectType,
375 obj: Value,
376 end_of_transaction: bool,
377 ) -> PartialVMResult<TransferResult> {
378 let id: ObjectID = get_object_id(obj.copy_value())?
379 .value_as::<AccountAddress>()?
380 .into();
381 let is_framework_obj = [
387 SUI_SYSTEM_STATE_OBJECT_ID,
388 SUI_CLOCK_OBJECT_ID,
389 SUI_AUTHENTICATOR_STATE_OBJECT_ID,
390 SUI_RANDOMNESS_STATE_OBJECT_ID,
391 SUI_DENY_LIST_OBJECT_ID,
392 SUI_BRIDGE_OBJECT_ID,
393 SUI_ACCUMULATOR_ROOT_OBJECT_ID,
394 SUI_COIN_REGISTRY_OBJECT_ID,
395 SUI_DISPLAY_REGISTRY_OBJECT_ID,
396 SUI_ADDRESS_ALIAS_STATE_OBJECT_ID,
397 SUI_FORWARDING_ADDRESS_REGISTRY_OBJECT_ID,
398 ]
399 .contains(&id);
400 let transfer_result = if self.state.new_ids.contains(&id) {
401 TransferResult::New
402 } else if let Some(prev_owner) = self.state.input_objects.get(&id) {
403 match (&owner, prev_owner) {
404 (Owner::Shared { .. }, Owner::Shared { .. }) => TransferResult::SameOwner,
406 (
407 Owner::ConsensusAddressOwner {
408 owner: new_owner, ..
409 },
410 Owner::ConsensusAddressOwner {
411 owner: old_owner, ..
412 },
413 ) if new_owner == old_owner => TransferResult::SameOwner,
414 (
415 Owner::Party {
416 permissions: new_permissions,
417 ..
418 },
419 Owner::Party {
420 permissions: old_permissions,
421 ..
422 },
423 ) if new_permissions == old_permissions => TransferResult::SameOwner,
424 (new @ Owner::AddressOwner(_), old)
425 | (new @ Owner::ObjectOwner(_), old)
426 | (new @ Owner::Immutable, old)
427 if new == old =>
428 {
429 TransferResult::SameOwner
430 }
431 _ => TransferResult::OwnerChanged,
432 }
433 } else if is_framework_obj {
434 self.state.new_ids.insert(id);
437 self.state.generated_ids.insert(id);
438 TransferResult::New
439 } else {
440 TransferResult::OwnerChanged
441 };
442 if end_of_transaction
444 && !matches!(
445 transfer_result,
446 TransferResult::New | TransferResult::SameOwner
447 )
448 {
449 return Err(
450 PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message(
451 format!(
452 "Untransferred object {} had its owner change or was not new",
453 id
454 ),
455 ),
456 );
457 }
458
459 if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
462 self.is_metered && !is_framework_obj && !end_of_transaction,
467 self.state.transfers.len(),
468 self.protocol_config.max_num_transferred_move_object_ids(),
469 self.protocol_config
470 .max_num_transferred_move_object_ids_system_tx(),
471 self.metrics
472 .limits_metrics
473 .excessive_transferred_move_object_ids
474 ) {
475 return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
476 .with_message(format!("Transferring more than {} IDs is not allowed", lim))
477 .with_sub_status(
478 VMMemoryLimitExceededSubStatusCode::TRANSFER_ID_COUNT_LIMIT_EXCEEDED as u64,
479 ));
480 };
481
482 self.state.transfers.insert(id, (owner, ty, obj));
483 Ok(transfer_result)
484 }
485
486 pub fn emit_event(&mut self, tag: StructTag, event: Value) -> PartialVMResult<()> {
487 if self.state.events.len() >= (self.protocol_config.max_num_event_emit() as usize) {
488 return Err(max_event_error(self.protocol_config.max_num_event_emit()));
489 }
490 self.state.events.push((tag, event));
491 self.state.total_events_emitted += 1;
492 Ok(())
493 }
494
495 pub fn take_user_events(&mut self) -> Vec<(StructTag, Value)> {
496 std::mem::take(&mut self.state.events)
497 }
498
499 pub fn emit_accumulator_event(
502 &mut self,
503 accumulator_id: ObjectID,
504 action: MoveAccumulatorAction,
505 target_addr: AccountAddress,
506 target_ty: TypeTag,
507 value: MoveAccumulatorValue,
508 ) -> PartialVMResult<()> {
509 if let MoveAccumulatorValue::U64(amount) = value {
510 let key = (target_addr, target_ty.clone());
511
512 match action {
513 MoveAccumulatorAction::Merge => {
514 let current = self
515 .state
516 .accumulator_merge_totals
517 .get(&key)
518 .copied()
519 .unwrap_or(0);
520 let new_total = current + amount as u128;
521 if new_total > u64::MAX as u128 {
522 return Err(PartialVMError::new(StatusCode::ARITHMETIC_ERROR)
523 .with_message(format!(
524 "accumulator merge overflow: total merges {} exceed u64::MAX",
525 new_total
526 )));
527 }
528 self.state.accumulator_merge_totals.insert(key, new_total);
529 if self
530 .protocol_config
531 .check_object_funds_withdraw_in_execution()
532 {
533 let entry = self
534 .state
535 .object_funds_available
536 .entry((target_addr, target_ty.clone()))
537 .or_insert_with(ObjectFundsAvailable::init);
538 entry.available = entry
539 .available
540 .checked_add(U256::from(amount as u128))
541 .ok_or_else(|| {
542 PartialVMError::new(StatusCode::ARITHMETIC_ERROR)
543 .with_message("object funds available balance overflow".to_string())
544 })?;
545 }
546 }
547 MoveAccumulatorAction::Split => {
548 let current = self
549 .state
550 .accumulator_split_totals
551 .get(&key)
552 .copied()
553 .unwrap_or(0);
554 let new_total = current + amount as u128;
555 if new_total > u64::MAX as u128 {
556 return Err(PartialVMError::new(StatusCode::ARITHMETIC_ERROR)
557 .with_message(format!(
558 "accumulator split overflow: total splits {} exceed u64::MAX",
559 new_total
560 )));
561 }
562 self.state.accumulator_split_totals.insert(key, new_total);
563 }
564 }
565 }
566
567 let event = MoveAccumulatorEvent {
568 accumulator_id,
569 action,
570 target_addr,
571 target_ty,
572 value,
573 };
574 self.state.accumulator_events.push(event);
575 Ok(())
576 }
577
578 pub(crate) fn child_object_exists(
579 &mut self,
580 parent: ObjectID,
581 child: ObjectID,
582 ) -> PartialVMResult<CacheMetadata<bool>> {
583 self.child_object_store.object_exists(parent, child)
584 }
585
586 pub(crate) fn child_object_exists_and_has_type(
587 &mut self,
588 parent: ObjectID,
589 child: ObjectID,
590 child_type: &MoveObjectType,
591 ) -> PartialVMResult<CacheMetadata<bool>> {
592 self.child_object_store
593 .object_exists_and_has_type(parent, child, child_type)
594 }
595
596 pub(super) fn receive_object(
597 &mut self,
598 parent: ObjectID,
599 child: ObjectID,
600 child_version: SequenceNumber,
601 child_layout: &R::MoveTypeLayout,
602 child_fully_annotated_layout: &MoveTypeLayout,
603 child_move_type: MoveObjectType,
604 ) -> PartialVMResult<Option<ObjectResult<CacheMetadata<Value>>>> {
605 let Some((value, obj_meta)) = self.child_object_store.receive_object(
606 parent,
607 child,
608 child_version,
609 child_layout,
610 child_fully_annotated_layout,
611 child_move_type,
612 )?
613 else {
614 return Ok(None);
615 };
616
617 if self
618 .protocol_config
619 .early_return_receive_object_mismatched_type()
620 && let ObjectResult::MismatchedType = &value
621 && self.state.received.contains_key(&child)
622 {
623 return Ok(Some(ObjectResult::MismatchedType));
626 }
627
628 if self.state.received.insert(child, obj_meta).is_some() {
631 return Err(
634 PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message(format!(
635 "Object {child} at version {child_version} already received. This can only happen \
636 if multiple `Receiving` arguments exist for the same object in the transaction which is impossible."
637 )),
638 );
639 }
640 Ok(Some(value))
641 }
642
643 pub(crate) fn get_or_fetch_child_object(
644 &mut self,
645 parent: ObjectID,
646 child: ObjectID,
647 child_layout: &R::MoveTypeLayout,
648 child_fully_annotated_layout: &MoveTypeLayout,
649 child_move_type: MoveObjectType,
650 ) -> PartialVMResult<ObjectResult<CacheMetadata<&mut GlobalValue>>> {
651 let res = self.child_object_store.get_or_fetch_object(
652 parent,
653 child,
654 child_layout,
655 child_fully_annotated_layout,
656 child_move_type,
657 )?;
658 Ok(match res {
659 ObjectResult::MismatchedType => ObjectResult::MismatchedType,
660 ObjectResult::Loaded((cache_info, child_object)) => {
661 ObjectResult::Loaded((cache_info, &mut child_object.value))
662 }
663 })
664 }
665
666 pub(crate) fn add_child_object(
667 &mut self,
668 parent: ObjectID,
669 child: ObjectID,
670 child_move_type: MoveObjectType,
671 child_value: Value,
672 ) -> PartialVMResult<()> {
673 self.child_object_store
674 .add_object(parent, child, child_move_type, child_value)
675 }
676
677 pub(crate) fn config_setting_unsequenced_read(
678 &mut self,
679 config_id: ObjectID,
680 name_df_id: ObjectID,
681 field_setting_layout: &R::MoveTypeLayout,
682 field_setting_object_type: &MoveObjectType,
683 ) -> Option<Value> {
684 match self.child_object_store.config_setting_unsequenced_read(
685 config_id,
686 name_df_id,
687 field_setting_layout,
688 field_setting_object_type,
689 ) {
690 Err(e) => {
691 error!(
692 "Failed to read config setting.
693 config_id: {config_id},
694 name_df_id: {name_df_id},
695 field_setting_object_type: {field_setting_object_type:?},
696 error: {e}"
697 );
698 None
699 }
700 Ok(ObjectResult::MismatchedType) | Ok(ObjectResult::Loaded(None)) => None,
701 Ok(ObjectResult::Loaded(Some(value))) => Some(value),
702 }
703 }
704
705 pub(super) fn config_setting_cache_update(
706 &mut self,
707 config_id: ObjectID,
708 name_df_id: ObjectID,
709 setting_value_object_type: MoveObjectType,
710 value: Option<Value>,
711 ) {
712 self.child_object_store.config_setting_cache_update(
713 config_id,
714 name_df_id,
715 setting_value_object_type,
716 value,
717 )
718 }
719
720 pub fn get_package_at_version(
721 &self,
722 package_id: ObjectID,
723 version: SequenceNumber,
724 ) -> Option<MovePackage> {
725 self.child_object_store
726 .get_package_at_version(package_id, version)
727 }
728
729 pub(crate) fn take_state(&mut self) -> ObjectRuntimeState {
731 std::mem::take(&mut self.state)
732 }
733
734 pub fn is_deleted(&self, id: &ObjectID) -> bool {
735 self.state.deleted_ids.contains(id)
736 }
737
738 pub fn is_transferred(&self, id: &ObjectID) -> Option<Owner> {
739 self.state
740 .transfers
741 .get(id)
742 .map(|(owner, _, _)| owner.clone())
743 }
744
745 pub fn finish(mut self) -> Result<RuntimeResults, ExecutionError> {
746 let loaded_child_objects = self.loaded_runtime_objects();
747 let child_effects = self.child_object_store.take_effects().map_err(|e| {
748 ExecutionError::invariant_violation(format!("Failed to take child object effects: {e}"))
749 })?;
750 self.state.finish(loaded_child_objects, child_effects)
751 }
752
753 pub(crate) fn all_active_child_objects(&self) -> impl Iterator<Item = ActiveChildObject<'_>> {
754 self.child_object_store.all_active_objects()
755 }
756
757 pub fn loaded_runtime_objects(&self) -> BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata> {
758 debug_assert!(
761 self.child_object_store
762 .cached_objects()
763 .keys()
764 .all(|id| !self.state.received.contains_key(id))
765 );
766 self.child_object_store
767 .cached_objects()
768 .iter()
769 .filter_map(|(id, obj_opt)| {
770 obj_opt.as_ref().map(|obj| {
771 (
772 *id,
773 DynamicallyLoadedObjectMetadata {
774 version: obj.version(),
775 digest: obj.digest(),
776 storage_rebate: obj.storage_rebate,
777 owner: obj.owner.clone(),
778 previous_transaction: obj.previous_transaction,
779 },
780 )
781 })
782 })
783 .chain(
784 self.state
785 .received
786 .iter()
787 .map(|(id, meta)| (*id, meta.clone())),
788 )
789 .collect()
790 }
791
792 pub fn wrapped_object_containers(&self) -> BTreeMap<ObjectID, ObjectID> {
795 self.child_object_store.wrapped_object_containers().clone()
796 }
797
798 pub fn record_settlement_sui_conservation(&mut self, input_sui: u64, output_sui: u64) {
799 self.state.settlement_input_sui += input_sui;
800 self.state.settlement_output_sui += output_sui;
801 }
802
803 pub fn generated_object_ids(&self) -> BTreeSet<ObjectID> {
806 self.state.generated_ids.iter().cloned().collect()
807 }
808}
809
810pub fn max_event_error(max_events: u64) -> PartialVMError {
811 PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
812 .with_message(format!(
813 "Emitting more than {} events is not allowed",
814 max_events
815 ))
816 .with_sub_status(VMMemoryLimitExceededSubStatusCode::EVENT_COUNT_LIMIT_EXCEEDED as u64)
817}
818
819impl ObjectRuntimeState {
820 pub(crate) fn finish(
829 mut self,
830 loaded_child_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
831 child_object_effects: ChildObjectEffects,
832 ) -> Result<RuntimeResults, ExecutionError> {
833 let mut loaded_child_objects: BTreeMap<_, _> = loaded_child_objects
834 .into_iter()
835 .map(|(id, metadata)| {
836 (
837 id,
838 LoadedRuntimeObject {
839 version: metadata.version,
840 is_modified: false,
841 },
842 )
843 })
844 .collect();
845 self.apply_child_object_effects(&mut loaded_child_objects, child_object_effects);
846 let ObjectRuntimeState {
847 input_objects: _,
848 new_ids,
849 generated_ids,
850 deleted_ids,
851 transfers,
852 events: user_events,
853 total_events_size: _,
854 received,
855 accumulator_events,
856 settlement_input_sui,
857 settlement_output_sui,
858 accumulator_merge_totals: _,
859 accumulator_split_totals: _,
860 object_funds_available: _,
861 total_events_emitted: _,
862 } = self;
863
864 debug_assert!(new_ids.is_subset(&generated_ids));
866
867 check_circular_ownership(
870 transfers
871 .iter()
872 .map(|(id, (owner, _, _))| (*id, owner.clone())),
873 )?;
874 let written_objects: IndexMap<_, _> = transfers
881 .into_iter()
882 .map(|(id, (owner, type_, value))| {
883 if let Some(loaded_child) = loaded_child_objects.get_mut(&id) {
884 loaded_child.is_modified = true;
885 }
886 (id, (owner, type_, value))
887 })
888 .collect();
889 for deleted_id in &deleted_ids {
890 if let Some(loaded_child) = loaded_child_objects.get_mut(deleted_id) {
891 loaded_child.is_modified = true;
892 }
893 }
894
895 for (received_object, _) in received.into_iter() {
899 match loaded_child_objects.get_mut(&received_object) {
900 Some(loaded_child) => {
901 loaded_child.is_modified = true;
902 }
903 None => {
904 return Err(ExecutionError::invariant_violation(format!(
905 "Failed to find received UID {received_object} in loaded child objects."
906 )));
907 }
908 }
909 }
910
911 Ok(RuntimeResults {
912 writes: written_objects,
913 user_events,
914 accumulator_events,
915 loaded_child_objects,
916 created_object_ids: new_ids,
917 deleted_object_ids: deleted_ids,
918 settlement_input_sui,
919 settlement_output_sui,
920 })
921 }
922
923 pub fn events(&self) -> &[(StructTag, Value)] {
924 &self.events
925 }
926
927 pub fn total_events_emitted(&self) -> u64 {
928 self.total_events_emitted
929 }
930
931 pub fn total_events_size(&self) -> u64 {
932 self.total_events_size
933 }
934
935 pub fn incr_total_events_size(&mut self, size: u64) {
936 self.total_events_size += size;
937 }
938
939 fn apply_child_object_effects(
940 &mut self,
941 loaded_child_objects: &mut BTreeMap<ObjectID, LoadedRuntimeObject>,
942 child_object_effects: ChildObjectEffects,
943 ) {
944 for (child, child_object_effect) in child_object_effects {
945 let ChildObjectEffect {
946 owner: parent,
947 ty,
948 final_value,
949 object_changed,
950 } = child_object_effect;
951
952 if object_changed {
953 if let Some(loaded_child) = loaded_child_objects.get_mut(&child) {
954 loaded_child.is_modified = true;
955 }
956
957 match final_value {
958 None => {
959 debug_assert!(
965 !self.transfers.contains_key(&child)
966 || !self.deleted_ids.contains(&child)
967 );
968 debug_assert!(
972 !self.deleted_ids.contains(&child)
973 || (!self.transfers.contains_key(&child)
974 && !self.new_ids.contains(&child))
975 );
976 }
977 Some(v) => {
978 debug_assert!(
982 !self.transfers.contains_key(&child)
983 && !self.deleted_ids.contains(&child)
984 );
985 debug_assert!(
990 !loaded_child_objects.contains_key(&child)
991 || !self.new_ids.contains(&child)
992 );
993 self.transfers
995 .insert(child, (Owner::ObjectOwner(parent.into()), ty, v));
996 }
997 }
998 } else {
999 debug_assert!(
1014 final_value.is_none()
1015 || (loaded_child_objects.contains_key(&child)
1016 && !self.deleted_ids.contains(&child)
1017 && !self.transfers.contains_key(&child)
1018 && !self.input_objects.contains_key(&child)
1019 && !self.received.contains_key(&child))
1020 );
1021 debug_assert!(
1023 loaded_child_objects
1024 .get(&child)
1025 .is_none_or(|loaded_child| !loaded_child.is_modified)
1026 );
1027 }
1028 }
1029 }
1030}
1031
1032fn check_circular_ownership(
1033 transfers: impl IntoIterator<Item = (ObjectID, Owner)>,
1034) -> Result<(), ExecutionError> {
1035 let mut object_owner_map = BTreeMap::new();
1036 for (id, recipient) in transfers {
1037 object_owner_map.remove(&id);
1038 match recipient {
1039 Owner::AddressOwner(_)
1040 | Owner::Shared { .. }
1041 | Owner::Immutable
1042 | Owner::ConsensusAddressOwner { .. }
1043 | Owner::Party { .. } => (),
1044 Owner::ObjectOwner(new_owner) => {
1045 let new_owner: ObjectID = new_owner.into();
1046 let mut cur = new_owner;
1047 loop {
1048 if cur == id {
1049 return Err(ExecutionError::from_kind(
1050 ExecutionErrorKind::CircularObjectOwnership { object: cur },
1051 ));
1052 }
1053 if let Some(parent) = object_owner_map.get(&cur) {
1054 cur = *parent;
1055 } else {
1056 break;
1057 }
1058 }
1059 object_owner_map.insert(id, new_owner);
1060 }
1061 }
1062 }
1063 Ok(())
1064}
1065
1066pub fn get_all_uids(
1072 fully_annotated_layout: &MoveTypeLayout,
1073 bcs_bytes: &[u8],
1074) -> Result<BTreeSet<ObjectID>, String> {
1075 let mut ids = BTreeSet::new();
1076 struct UIDTraversal<'i>(&'i mut BTreeSet<ObjectID>);
1077 struct UIDCollector<'i>(&'i mut BTreeSet<ObjectID>);
1078
1079 impl<'b, 'l> AV::Traversal<'b, 'l> for UIDTraversal<'_> {
1080 type Error = AV::Error;
1081
1082 fn traverse_struct(
1083 &mut self,
1084 driver: &mut AV::StructDriver<'_, 'b, 'l>,
1085 ) -> Result<(), Self::Error> {
1086 if driver.struct_layout().type_ == UID::type_() {
1087 while driver.next_field(&mut UIDCollector(self.0))?.is_some() {}
1088 } else {
1089 while driver.next_field(self)?.is_some() {}
1090 }
1091 Ok(())
1092 }
1093 }
1094
1095 impl<'b, 'l> AV::Traversal<'b, 'l> for UIDCollector<'_> {
1096 type Error = AV::Error;
1097 fn traverse_address(
1098 &mut self,
1099 _driver: &AV::ValueDriver<'_, 'b, 'l>,
1100 value: AccountAddress,
1101 ) -> Result<(), Self::Error> {
1102 self.0.insert(value.into());
1103 Ok(())
1104 }
1105 }
1106
1107 MoveValue::visit_deserialize(
1108 bcs_bytes,
1109 fully_annotated_layout,
1110 &mut UIDTraversal(&mut ids),
1111 )
1112 .map_err(|e| format!("Failed to deserialize. {e}"))?;
1113 Ok(ids)
1114}