1use crate::execution_mode::ExecutionMode;
5use crate::gas_charger::{GasCharger, PaymentLocation};
6use mysten_common::{ZipDebugEqIteratorExt, debug_fatal};
7use mysten_metrics::monitored_scope;
8use parking_lot::RwLock;
9use std::collections::{BTreeMap, BTreeSet, HashSet};
10use sui_protocol_config::ProtocolConfig;
11use sui_types::accumulator_event::AccumulatorEvent;
12use sui_types::accumulator_root::AccumulatorObjId;
13use sui_types::base_types::VersionDigest;
14use sui_types::committee::EpochId;
15use sui_types::deny_list_v2::check_coin_deny_list_v2_during_execution;
16use sui_types::effects::{
17 AccumulatorOperation, AccumulatorValue, AccumulatorWriteV1, TransactionEffects,
18 TransactionEvents,
19};
20use sui_types::execution::{
21 DynamicallyLoadedObjectMetadata, ExecutionResults, ExecutionResultsV2, SharedInput,
22};
23use sui_types::execution_status::{ExecutionErrorKind, ExecutionStatus};
24use sui_types::inner_temporary_store::InnerTemporaryStore;
25use sui_types::layout_resolver::LayoutResolver;
26use sui_types::object::{Data, ObjectPermissions};
27use sui_types::storage::{BackingStore, DenyListResult, PackageObject};
28use sui_types::sui_system_state::{AdvanceEpochParams, get_sui_system_state_wrapper};
29use sui_types::{
30 SUI_DENY_LIST_OBJECT_ID,
31 base_types::{ObjectID, ObjectRef, SequenceNumber, SuiAddress, TransactionDigest},
32 effects::EffectsObjectChange,
33 error::{ExecutionError, SuiResult},
34 gas::GasCostSummary,
35 object::Object,
36 object::Owner,
37 storage::{BackingPackageStore, ChildObjectResolver, ParentSync, Storage},
38 transaction::InputObjects,
39};
40use sui_types::{SUI_SYSTEM_STATE_OBJECT_ID, TypeTag, is_system_package};
41
42pub struct TemporaryStore<'backing> {
43 store: &'backing dyn BackingStore,
49 tx_digest: TransactionDigest,
50 input_objects: BTreeMap<ObjectID, Object>,
51
52 non_exclusive_input_original_versions: BTreeMap<ObjectID, Object>,
55
56 stream_ended_consensus_objects: BTreeMap<ObjectID, SequenceNumber >,
57 lamport_timestamp: SequenceNumber,
59 mutable_input_refs: BTreeMap<ObjectID, (VersionDigest, Owner)>,
62 execution_results: ExecutionResultsV2,
63 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
65 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
67 protocol_config: &'backing ProtocolConfig,
68
69 runtime_packages_loaded_from_db: RwLock<BTreeMap<ObjectID, PackageObject>>,
72
73 receiving_objects: Vec<ObjectRef>,
76
77 generated_runtime_ids: BTreeSet<ObjectID>,
81
82 cur_epoch: EpochId,
85
86 loaded_per_epoch_config_objects: RwLock<BTreeSet<ObjectID>>,
89
90 ptb_emitted_accumulator_event_ranges: Vec<std::ops::Range<usize>>,
99}
100
101impl<'backing> TemporaryStore<'backing> {
102 pub fn new(
105 store: &'backing dyn BackingStore,
106 input_objects: InputObjects,
107 receiving_objects: Vec<ObjectRef>,
108 tx_digest: TransactionDigest,
109 protocol_config: &'backing ProtocolConfig,
110 cur_epoch: EpochId,
111 ) -> Self {
112 let mutable_input_refs = input_objects.exclusive_mutable_inputs();
113 let non_exclusive_input_original_versions = input_objects.non_exclusive_input_objects();
114
115 let lamport_timestamp = input_objects.lamport_timestamp(&receiving_objects);
116 let stream_ended_consensus_objects = input_objects.consensus_stream_ended_objects();
117 let objects = input_objects.into_object_map();
118 #[cfg(debug_assertions)]
119 {
120 assert!(
122 objects
123 .keys()
124 .collect::<HashSet<_>>()
125 .intersection(
126 &receiving_objects
127 .iter()
128 .map(|oref| &oref.0)
129 .collect::<HashSet<_>>()
130 )
131 .next()
132 .is_none()
133 );
134 }
135 Self {
136 store,
137 tx_digest,
138 input_objects: objects,
139 non_exclusive_input_original_versions,
140 stream_ended_consensus_objects,
141 lamport_timestamp,
142 mutable_input_refs,
143 execution_results: ExecutionResultsV2::default(),
144 protocol_config,
145 loaded_runtime_objects: BTreeMap::new(),
146 wrapped_object_containers: BTreeMap::new(),
147 runtime_packages_loaded_from_db: RwLock::new(BTreeMap::new()),
148 receiving_objects,
149 generated_runtime_ids: BTreeSet::new(),
150 cur_epoch,
151 loaded_per_epoch_config_objects: RwLock::new(BTreeSet::new()),
152 ptb_emitted_accumulator_event_ranges: Vec::new(),
153 }
154 }
155
156 pub fn objects(&self) -> &BTreeMap<ObjectID, Object> {
158 &self.input_objects
159 }
160
161 pub fn update_object_version_and_prev_tx(&mut self) {
162 self.execution_results.update_version_and_previous_tx(
163 self.lamport_timestamp,
164 self.tx_digest,
165 &self.input_objects,
166 self.protocol_config.reshare_at_same_initial_version(),
167 );
168
169 #[cfg(debug_assertions)]
170 {
171 self.check_invariants();
172 }
173 }
174
175 fn calculate_accumulator_running_max_withdraws(&self) -> BTreeMap<AccumulatorObjId, u128> {
176 let mut running_net_withdraws: BTreeMap<AccumulatorObjId, i128> = BTreeMap::new();
177 let mut running_max_withdraws: BTreeMap<AccumulatorObjId, u128> = BTreeMap::new();
178 for event in &self.execution_results.accumulator_events {
179 match &event.write.value {
180 AccumulatorValue::Integer(amount) => match event.write.operation {
181 AccumulatorOperation::Split => {
182 let entry = running_net_withdraws
183 .entry(event.accumulator_obj)
184 .or_default();
185 *entry += *amount as i128;
186 if *entry > 0 {
187 let max_entry = running_max_withdraws
188 .entry(event.accumulator_obj)
189 .or_default();
190 *max_entry = (*max_entry).max(*entry as u128);
191 }
192 }
193 AccumulatorOperation::Merge => {
194 let entry = running_net_withdraws
195 .entry(event.accumulator_obj)
196 .or_default();
197 *entry -= *amount as i128;
198 }
199 },
200 AccumulatorValue::IntegerTuple(_, _) | AccumulatorValue::EventDigest(_) => {}
201 }
202 }
203 running_max_withdraws
204 }
205
206 pub fn check_accumulator_amounts_representable(&self) -> Result<(), ExecutionError> {
234 let supply = sui_types::gas_coin::TOTAL_SUPPLY_MIST as u128;
235 let mut merge_totals: BTreeMap<AccumulatorObjId, u128> = BTreeMap::new();
236 let mut split_totals: BTreeMap<AccumulatorObjId, u128> = BTreeMap::new();
237 let mut total_sui_split: u128 = 0;
239 for event in &self.execution_results.accumulator_events {
240 let AccumulatorValue::Integer(amount) = event.write.value else {
241 continue;
242 };
243 let amount = amount as u128;
244 let is_sui = sui_types::gas_coin::GasCoin::is_gas_balance_type(&event.write.address.ty);
247 let limit = if is_sui { supply } else { u64::MAX as u128 };
248 let total = match event.write.operation {
249 AccumulatorOperation::Merge => {
250 merge_totals.entry(event.accumulator_obj).or_default()
251 }
252 AccumulatorOperation::Split => {
253 split_totals.entry(event.accumulator_obj).or_default()
254 }
255 };
256 *total += amount;
257 if *total > limit {
258 return Err(ExecutionError::new_with_source(
259 ExecutionErrorKind::CoinBalanceOverflow,
260 format!(
261 "accumulator balance change for {:?} exceeds the representable limit \
262 (gross total {}, limit {})",
263 event.accumulator_obj, *total, limit
264 ),
265 ));
266 }
267 if is_sui && matches!(event.write.operation, AccumulatorOperation::Split) {
268 total_sui_split += amount;
269 if total_sui_split > supply {
270 return Err(ExecutionError::new_with_source(
271 ExecutionErrorKind::CoinBalanceOverflow,
272 format!(
273 "total SUI withdrawn across all accumulators ({total_sui_split}) \
274 exceeds the total supply ({supply})"
275 ),
276 ));
277 }
278 }
279 }
280 Ok(())
281 }
282
283 fn merge_accumulator_events(&mut self) {
285 self.execution_results.accumulator_events = self
286 .execution_results
287 .accumulator_events
288 .iter()
289 .fold(
290 BTreeMap::<AccumulatorObjId, Vec<AccumulatorWriteV1>>::new(),
291 |mut map, event| {
292 map.entry(event.accumulator_obj)
293 .or_default()
294 .push(event.write.clone());
295 map
296 },
297 )
298 .into_iter()
299 .map(|(obj_id, writes)| {
300 AccumulatorEvent::new(obj_id, AccumulatorWriteV1::merge(writes))
301 })
302 .collect();
303 }
304
305 pub fn into_inner(
307 self,
308 accumulator_running_max_withdraws: BTreeMap<AccumulatorObjId, u128>,
309 ) -> InnerTemporaryStore {
310 let results = self.execution_results;
311 InnerTemporaryStore {
312 input_objects: self.input_objects,
313 stream_ended_consensus_objects: self.stream_ended_consensus_objects,
314 mutable_inputs: self.mutable_input_refs,
315 written: results.written_objects,
316 events: TransactionEvents {
317 data: results.user_events,
318 },
319 accumulator_events: results.accumulator_events,
320 loaded_runtime_objects: self.loaded_runtime_objects,
321 runtime_packages_loaded_from_db: self.runtime_packages_loaded_from_db.into_inner(),
322 lamport_version: self.lamport_timestamp,
323 binary_config: self.protocol_config.binary_config(None),
324 accumulator_running_max_withdraws,
325 }
326 }
327
328 pub(crate) fn ensure_active_inputs_mutated(&mut self) {
332 let mut to_be_updated = vec![];
333 for id in self.mutable_input_refs.keys() {
335 if !self.execution_results.modified_objects.contains(id) {
336 to_be_updated.push(self.input_objects[id].clone());
340 }
341 }
342 for object in to_be_updated {
343 self.mutate_input_object(object.clone());
345 }
346 }
347
348 fn get_object_changes(&self) -> BTreeMap<ObjectID, EffectsObjectChange> {
349 let results = &self.execution_results;
350 let all_ids = results
351 .created_object_ids
352 .iter()
353 .chain(&results.deleted_object_ids)
354 .chain(&results.modified_objects)
355 .chain(results.written_objects.keys())
356 .collect::<BTreeSet<_>>();
357 all_ids
358 .into_iter()
359 .map(|id| {
360 (
361 *id,
362 EffectsObjectChange::new(
363 self.get_object_modified_at(id)
364 .map(|metadata| ((metadata.version, metadata.digest), metadata.owner)),
365 results.written_objects.get(id),
366 results.created_object_ids.contains(id),
367 results.deleted_object_ids.contains(id),
368 ),
369 )
370 })
371 .chain(results.accumulator_events.iter().cloned().map(
372 |AccumulatorEvent {
373 accumulator_obj,
374 write,
375 }| {
376 (
377 *accumulator_obj.inner(),
378 EffectsObjectChange::new_from_accumulator_write(write),
379 )
380 },
381 ))
382 .collect()
383 }
384
385 pub fn into_effects(
386 mut self,
387 shared_object_refs: Vec<SharedInput>,
388 transaction_digest: &TransactionDigest,
389 mut transaction_dependencies: BTreeSet<TransactionDigest>,
390 gas_cost_summary: GasCostSummary,
391 status: ExecutionStatus,
392 gas_charger: &mut GasCharger,
393 epoch: EpochId,
394 ) -> (InnerTemporaryStore, TransactionEffects) {
395 for (id, obj) in &self.execution_results.written_objects {
398 assert!(
399 !matches!(obj.owner, Owner::Party { .. }),
400 "Party-owned objects are not yet supported (object {id})"
401 );
402 }
403
404 self.update_object_version_and_prev_tx();
405 let accumulator_running_max_withdraws = self.calculate_accumulator_running_max_withdraws();
407 self.merge_accumulator_events();
408
409 for (id, expected_version, expected_digest) in &self.receiving_objects {
412 if let Some(obj_meta) = self.loaded_runtime_objects.get(id) {
416 let loaded_via_receive = obj_meta.version == *expected_version
420 && obj_meta.digest == *expected_digest
421 && obj_meta.owner.is_address_owned();
422 if loaded_via_receive {
423 transaction_dependencies.insert(obj_meta.previous_transaction);
424 }
425 }
426 }
427
428 assert!(self.protocol_config.enable_effects_v2());
429
430 let gas_coin = gas_charger
435 .gas_payment_amount()
436 .and_then(|gp| match gp.location {
437 PaymentLocation::Coin(coin_id) => Some(coin_id),
438 PaymentLocation::AddressBalance(_) => None,
439 });
440
441 let object_changes = self.get_object_changes();
442
443 let lamport_version = self.lamport_timestamp;
444 let loaded_per_epoch_config_objects = self.loaded_per_epoch_config_objects.read().clone();
446 let inner = self.into_inner(accumulator_running_max_withdraws);
447
448 let effects = TransactionEffects::new_from_execution_v2(
449 status,
450 epoch,
451 gas_cost_summary,
452 shared_object_refs,
454 loaded_per_epoch_config_objects,
455 *transaction_digest,
456 lamport_version,
457 object_changes,
458 gas_coin,
459 if inner.events.data.is_empty() {
460 None
461 } else {
462 Some(inner.events.digest())
463 },
464 transaction_dependencies.into_iter().collect(),
465 );
466
467 (inner, effects)
468 }
469
470 #[cfg(debug_assertions)]
472 fn check_invariants(&self) {
473 debug_assert!(
475 {
476 self.execution_results
477 .written_objects
478 .keys()
479 .all(|id| !self.execution_results.deleted_object_ids.contains(id))
480 },
481 "Object both written and deleted."
482 );
483
484 debug_assert!(
486 {
487 self.mutable_input_refs
488 .keys()
489 .all(|id| self.execution_results.modified_objects.contains(id))
490 },
491 "Mutable input not modified."
492 );
493
494 debug_assert!(
495 {
496 self.execution_results
497 .written_objects
498 .values()
499 .all(|obj| obj.previous_transaction == self.tx_digest)
500 },
501 "Object previous transaction not properly set",
502 );
503 }
504
505 pub fn mutate_input_object(&mut self, object: Object) {
507 let id = object.id();
508 debug_assert!(self.input_objects.contains_key(&id));
509 debug_assert!(!object.is_immutable());
510 self.execution_results.modified_objects.insert(id);
511 self.execution_results.written_objects.insert(id, object);
512 }
513
514 pub fn mutate_new_or_input_object(&mut self, object: Object) {
515 let id = object.id();
516 debug_assert!(!object.is_immutable());
517 if self.input_objects.contains_key(&id) {
518 self.execution_results.modified_objects.insert(id);
519 }
520 self.execution_results.written_objects.insert(id, object);
521 }
522
523 pub fn mutate_child_object(&mut self, old_object: Object, new_object: Object) {
527 let id = new_object.id();
528 let old_ref = old_object.compute_object_reference();
529 debug_assert_eq!(old_ref.0, id);
530 self.loaded_runtime_objects.insert(
531 id,
532 DynamicallyLoadedObjectMetadata {
533 version: old_ref.1,
534 digest: old_ref.2,
535 owner: old_object.owner.clone(),
536 storage_rebate: old_object.storage_rebate,
537 previous_transaction: old_object.previous_transaction,
538 },
539 );
540 self.execution_results.modified_objects.insert(id);
541 self.execution_results
542 .written_objects
543 .insert(id, new_object);
544 }
545
546 pub fn upgrade_system_package(&mut self, package: Object) {
550 let id = package.id();
551 assert!(package.is_package() && is_system_package(id));
552 self.execution_results.modified_objects.insert(id);
553 self.execution_results.written_objects.insert(id, package);
554 }
555
556 pub fn create_object(&mut self, object: Object) {
558 debug_assert!(
563 object.is_immutable() || object.version() == SequenceNumber::MIN,
564 "Created mutable objects should not have a version set",
565 );
566 let id = object.id();
567 self.execution_results.created_object_ids.insert(id);
568 self.execution_results.written_objects.insert(id, object);
569 }
570
571 pub fn delete_input_object(&mut self, id: &ObjectID) {
573 debug_assert!(!self.execution_results.written_objects.contains_key(id));
575 debug_assert!(self.input_objects.contains_key(id));
576 self.execution_results.modified_objects.insert(*id);
577 self.execution_results.deleted_object_ids.insert(*id);
578 }
579
580 pub fn drop_writes(&mut self) {
581 self.execution_results.drop_writes();
582 self.ptb_emitted_accumulator_event_ranges.clear();
584 }
585
586 pub fn read_object(&self, id: &ObjectID) -> Option<&Object> {
587 debug_assert!(!self.execution_results.deleted_object_ids.contains(id));
589 self.execution_results
590 .written_objects
591 .get(id)
592 .or_else(|| self.input_objects.get(id))
593 }
594
595 pub fn save_loaded_runtime_objects(
596 &mut self,
597 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
598 ) {
599 #[cfg(debug_assertions)]
600 {
601 for (id, v1) in &loaded_runtime_objects {
602 if let Some(v2) = self.loaded_runtime_objects.get(id) {
603 assert_eq!(v1, v2);
604 }
605 }
606 for (id, v1) in &self.loaded_runtime_objects {
607 if let Some(v2) = loaded_runtime_objects.get(id) {
608 assert_eq!(v1, v2);
609 }
610 }
611 }
612 self.loaded_runtime_objects.extend(loaded_runtime_objects);
615 }
616
617 pub fn save_wrapped_object_containers(
618 &mut self,
619 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
620 ) {
621 #[cfg(debug_assertions)]
622 {
623 for (id, container1) in &wrapped_object_containers {
624 if let Some(container2) = self.wrapped_object_containers.get(id) {
625 assert_eq!(container1, container2);
626 }
627 }
628 for (id, container1) in &self.wrapped_object_containers {
629 if let Some(container2) = wrapped_object_containers.get(id) {
630 assert_eq!(container1, container2);
631 }
632 }
633 }
634 self.wrapped_object_containers
637 .extend(wrapped_object_containers);
638 }
639
640 pub fn save_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
641 #[cfg(debug_assertions)]
642 {
643 for id in &self.generated_runtime_ids {
644 assert!(!generated_ids.contains(id))
645 }
646 for id in &generated_ids {
647 assert!(!self.generated_runtime_ids.contains(id));
648 }
649 }
650 self.generated_runtime_ids.extend(generated_ids);
651 }
652
653 pub fn estimate_effects_size_upperbound(&self) -> usize {
654 TransactionEffects::estimate_effects_size_upperbound_v2(
655 self.execution_results.written_objects.len(),
656 self.execution_results.modified_objects.len(),
657 self.input_objects.len(),
658 )
659 }
660
661 pub fn written_objects_size(&self) -> usize {
662 self.execution_results
663 .written_objects
664 .values()
665 .fold(0, |sum, obj| sum + obj.object_size_for_gas_metering())
666 }
667
668 pub fn check_gasless_execution_requirements(
674 &self,
675 withdrawal_reservations: Option<&BTreeMap<(SuiAddress, TypeTag), u64>>,
676 ) -> Result<(), String> {
677 if !self.execution_results.written_objects.is_empty() {
678 return Err("Gasless transactions cannot create or mutate objects".to_string());
679 }
680
681 let input_coin_ids: BTreeSet<ObjectID> = self
682 .input_objects
683 .iter()
684 .filter(|(_, obj)| obj.coin_type_maybe().is_some())
685 .map(|(id, _)| *id)
686 .collect();
687 if self.execution_results.deleted_object_ids != input_coin_ids {
688 return Err(format!(
689 "Gasless transaction must destroy exactly its input Coins. \
690 Expected: {input_coin_ids:?}, deleted: {:?}",
691 self.execution_results.deleted_object_ids
692 ));
693 }
694
695 let allowed_types =
696 sui_types::transaction::get_gasless_allowed_token_types(self.protocol_config);
697
698 let net_totals = sui_types::balance_change::signed_balance_changes_from_events(
701 &self.execution_results.accumulator_events,
702 )
703 .fold(
704 BTreeMap::<(SuiAddress, TypeTag), i128>::new(),
705 |mut totals, (address, token_type, signed_amount)| {
706 *totals.entry((address, token_type)).or_default() += signed_amount;
707 totals
708 },
709 );
710
711 for ((recipient, token_type), net_amount) in &net_totals {
712 if *net_amount <= 0 {
713 continue;
714 }
715 if let Some(&min_amount) = allowed_types.get(token_type)
716 && *net_amount < i128::from(min_amount)
717 {
718 return Err(format!(
719 "Gasless transfer of {net_amount} to {recipient} is below \
720 minimum {min_amount} for token type {token_type}"
721 ));
722 }
723 }
724
725 if let Some(reservations) = withdrawal_reservations {
726 for ((owner, token_type), &reserved) in reservations {
727 let net = net_totals
728 .get(&(*owner, token_type.clone()))
729 .copied()
730 .unwrap_or(0);
731 let remaining = (reserved as i128).saturating_add(net);
732 if remaining > 0
733 && let Some(&min_balance_remaining) = allowed_types.get(token_type)
734 && min_balance_remaining > 0
735 && remaining < min_balance_remaining as i128
736 {
737 return Err(format!(
738 "Gasless withdrawal leaves {remaining} unused for {owner}, \
739 below minimum {min_balance_remaining} for token type {token_type}"
740 ));
741 }
742 }
743 }
744
745 Ok(())
746 }
747
748 pub fn conserve_unmetered_storage_rebate(&mut self, unmetered_storage_rebate: u64) {
753 if unmetered_storage_rebate == 0 {
754 return;
758 }
759 tracing::debug!(
760 "Amount of unmetered storage rebate from system tx: {:?}",
761 unmetered_storage_rebate
762 );
763 let mut system_state_wrapper = self
764 .read_object(&SUI_SYSTEM_STATE_OBJECT_ID)
765 .expect("0x5 object must be mutated in system tx with unmetered storage rebate")
766 .clone();
767 assert_eq!(system_state_wrapper.storage_rebate, 0);
770 system_state_wrapper.storage_rebate = unmetered_storage_rebate;
771 self.mutate_input_object(system_state_wrapper);
772 }
773
774 pub fn add_accumulator_event(&mut self, event: AccumulatorEvent) {
776 self.execution_results.accumulator_events.push(event);
777 }
778
779 fn get_object_modified_at(
785 &self,
786 object_id: &ObjectID,
787 ) -> Option<DynamicallyLoadedObjectMetadata> {
788 if self.execution_results.modified_objects.contains(object_id) {
789 Some(
790 self.mutable_input_refs
791 .get(object_id)
792 .map(
793 |((version, digest), owner)| DynamicallyLoadedObjectMetadata {
794 version: *version,
795 digest: *digest,
796 owner: owner.clone(),
797 storage_rebate: self.input_objects[object_id].storage_rebate,
799 previous_transaction: self.input_objects[object_id]
800 .previous_transaction,
801 },
802 )
803 .or_else(|| self.loaded_runtime_objects.get(object_id).cloned())
804 .unwrap_or_else(|| {
805 debug_assert!(is_system_package(*object_id));
806 let package_obj =
807 self.store.get_package_object(object_id).unwrap().unwrap();
808 let obj = package_obj.object();
809 DynamicallyLoadedObjectMetadata {
810 version: obj.version(),
811 digest: obj.digest(),
812 owner: obj.owner.clone(),
813 storage_rebate: obj.storage_rebate,
814 previous_transaction: obj.previous_transaction,
815 }
816 }),
817 )
818 } else {
819 None
820 }
821 }
822
823 pub fn protocol_config(&self) -> &'backing ProtocolConfig {
824 self.protocol_config
825 }
826}
827
828impl TemporaryStore<'_> {
829 pub fn check_ownership_invariants(
832 &self,
833 sender: &SuiAddress,
834 sponsor: &Option<SuiAddress>,
835 gas_charger: &GasCharger,
836 mutable_inputs: &HashSet<ObjectID>,
837 input_reservations: &BTreeMap<(SuiAddress, TypeTag), u64>,
838 is_epoch_change: bool,
839 ) -> SuiResult<()> {
840 let gas_objs: HashSet<&ObjectID> = gas_charger.used_coins().map(|g| &g.0).collect();
841 let gas_owner = sponsor.as_ref().unwrap_or(sender);
842
843 let objects_authenticated_for_mutation: HashSet<SuiAddress> = self
845 .input_objects
846 .iter()
847 .filter_map(|(id, obj)| {
848 match &obj.owner {
849 Owner::AddressOwner(a) => {
850 if gas_objs.contains(id) {
851 assert!(
853 a == gas_owner,
854 "Gas object must be owned by sender or sponsor"
855 );
856 } else {
857 assert!(sender == a, "Input object must be owned by sender");
858 }
859 Some(id)
860 }
861 Owner::Shared { .. } | Owner::ConsensusAddressOwner { .. } => Some(id),
862 Owner::Immutable => {
863 None
874 }
875 Owner::Party { permissions, .. } => {
876 let sender_permissions = permissions.permissions_for(sender);
877 let sponsor_permissions = sponsor
878 .as_ref()
879 .map(|s| permissions.permissions_for(s))
880 .unwrap_or(ObjectPermissions::NONE);
881 (sender_permissions | sponsor_permissions)
882 .can_use_mutably()
883 .then_some(id)
884 }
885 Owner::ObjectOwner(_parent) => {
886 unreachable!(
887 "Input objects must be address owned, shared, consensus, or immutable"
888 )
889 }
890 }
891 })
892 .filter(|id| {
893 mutable_inputs.contains(id)
896 })
897 .copied()
898 .chain(self.generated_runtime_ids.iter().copied())
901 .map(SuiAddress::from)
902 .collect();
903
904 let mut authenticated_for_mutation = {
906 assert!(
907 !objects_authenticated_for_mutation.contains(sender),
908 "Sender cannot be an object"
909 );
910 assert!(
911 sponsor
912 .is_none_or(|sponsor| !objects_authenticated_for_mutation.contains(&sponsor)),
913 "Sponsor cannot be an object"
914 );
915 let mut s = objects_authenticated_for_mutation.clone();
916 s.insert(*sender);
917 if let Some(sponsor) = sponsor {
918 s.insert(*sponsor);
919 }
920 s
921 };
922
923 let mut objects_to_authenticate = self
925 .execution_results
926 .modified_objects
927 .iter()
928 .copied()
929 .collect::<Vec<_>>();
930
931 while let Some(to_authenticate) = objects_to_authenticate.pop() {
932 if authenticated_for_mutation.contains(&to_authenticate.into()) {
933 continue;
935 }
936
937 let parent = if let Some(container_id) =
938 self.wrapped_object_containers.get(&to_authenticate)
939 {
940 *container_id
942 } else {
943 let Some(old_obj) = self.store.get_object(&to_authenticate) else {
946 panic!(
947 "Failed to load object {to_authenticate:?}.\n \
948 If it cannot be loaded, we would expect it to be in the wrapped object map: {:#?}",
949 &self.wrapped_object_containers
950 )
951 };
952
953 match &old_obj.owner {
954 Owner::ObjectOwner(parent) => ObjectID::from(*parent),
957 Owner::AddressOwner(parent)
962 | Owner::ConsensusAddressOwner { owner: parent, .. } => {
963 ObjectID::from(*parent)
967 }
968 owner @ Owner::Shared { .. } | owner @ Owner::Party { .. } => {
971 panic!(
972 "Unauthenticated root at {to_authenticate:?} with owner {owner:?}\n\
973 Potentially covering objects in: {authenticated_for_mutation:#?}"
974 );
975 }
976
977 Owner::Immutable => {
978 assert!(
979 is_epoch_change,
980 "Immutable objects cannot be written, except for \
981 Sui Framework/Move stdlib upgrades at epoch change boundaries"
982 );
983 assert!(
987 is_system_package(to_authenticate),
988 "Only system packages can be upgraded"
989 );
990 continue;
991 }
992 }
993 };
994
995 authenticated_for_mutation.insert(to_authenticate.into());
997 objects_to_authenticate.push(parent);
998 }
999
1000 let sui_balance_type =
1002 sui_types::balance::Balance::type_tag(sui_types::gas_coin::GAS::type_tag());
1003 let gas_payment_address_balance =
1004 gas_charger
1005 .gas_payment_location()
1006 .and_then(|location| match location {
1007 PaymentLocation::Coin(_) => None,
1008 PaymentLocation::AddressBalance(address) => Some(address),
1009 });
1010 let mut funds_net_changes: BTreeMap<(SuiAddress, TypeTag), i128> = BTreeMap::new();
1011 for event in self.execution_results.accumulator_events.iter() {
1012 let amount = match event.write.value {
1013 AccumulatorValue::Integer(a) => a as i128,
1014 AccumulatorValue::IntegerTuple(_, _) | AccumulatorValue::EventDigest(_) => {
1015 assert!(
1016 !sui_types::balance::Balance::is_balance_type(&event.write.address.ty),
1017 "Non-integer accumulator changes should not be balances"
1018 );
1019 continue;
1020 }
1021 };
1022 let signed = match event.write.operation {
1023 AccumulatorOperation::Split => -amount,
1024 AccumulatorOperation::Merge => amount,
1025 };
1026 let address = event.write.address.address;
1027 let type_tag = &event.write.address.ty;
1028 let key = (address, type_tag.clone());
1029 *funds_net_changes.entry(key.clone()).or_insert(0) += signed;
1030 let authorized = match event.write.operation {
1037 AccumulatorOperation::Merge => true,
1038 AccumulatorOperation::Split => {
1039 input_reservations.contains_key(&key)
1040 || objects_authenticated_for_mutation.contains(&address)
1041 || (*type_tag == sui_balance_type
1042 && gas_payment_address_balance
1043 .is_some_and(|gas_addr| gas_addr == address))
1044 }
1045 };
1046 assert!(
1047 authorized,
1048 "Unauthenticated funds-accumulator Split at address {address} for type \
1049 {type_tag}: no input reservation, address is not an authenticated object, and \
1050 it is not the final gas payment address balance"
1051 );
1052 }
1053
1054 for (key, change) in funds_net_changes {
1059 if change >= 0 || objects_authenticated_for_mutation.contains(&key.0) {
1061 continue;
1062 }
1063 let reservation = input_reservations.get(&key).copied().unwrap_or(0) as u128;
1064 let withdrawn = change.unsigned_abs();
1065 assert!(
1066 withdrawn <= reservation,
1067 "Net withdrawal of {withdrawn} for {key:?} exceeds input reservation of \
1068 {reservation}"
1069 );
1070 }
1071
1072 Ok(())
1073 }
1074}
1075
1076impl TemporaryStore<'_> {
1077 pub(crate) fn collect_storage_and_rebate(&mut self, gas_charger: &mut GasCharger) {
1084 let old_storage_rebates: Vec<_> = self
1086 .execution_results
1087 .written_objects
1088 .keys()
1089 .map(|object_id| {
1090 self.get_object_modified_at(object_id)
1091 .map(|metadata| metadata.storage_rebate)
1092 .unwrap_or_default()
1093 })
1094 .collect();
1095 for (object, old_storage_rebate) in self
1096 .execution_results
1097 .written_objects
1098 .values_mut()
1099 .zip_debug_eq(old_storage_rebates)
1100 {
1101 let new_object_size = object.object_size_for_gas_metering();
1103 let new_storage_rebate = gas_charger.track_storage_mutation(
1105 object.id(),
1106 new_object_size,
1107 old_storage_rebate,
1108 );
1109 object.storage_rebate = new_storage_rebate;
1110 }
1111
1112 self.collect_rebate(gas_charger);
1113 }
1114
1115 pub(crate) fn collect_rebate(&self, gas_charger: &mut GasCharger) {
1116 for object_id in &self.execution_results.modified_objects {
1117 if self
1118 .execution_results
1119 .written_objects
1120 .contains_key(object_id)
1121 {
1122 continue;
1123 }
1124 let storage_rebate = self
1126 .get_object_modified_at(object_id)
1127 .unwrap()
1129 .storage_rebate;
1130 gas_charger.track_storage_mutation(*object_id, 0, storage_rebate);
1131 }
1132 }
1133
1134 pub fn check_execution_results_consistency<Mode: ExecutionMode>(
1135 &self,
1136 ) -> Result<(), Mode::Error> {
1137 assert_invariant!(
1138 self.execution_results
1139 .created_object_ids
1140 .iter()
1141 .all(|id| !self.execution_results.deleted_object_ids.contains(id)
1142 && !self.execution_results.modified_objects.contains(id)),
1143 "Created object IDs cannot also be deleted or modified"
1144 );
1145 assert_invariant!(
1146 self.execution_results.modified_objects.iter().all(|id| {
1147 self.mutable_input_refs.contains_key(id)
1148 || self.loaded_runtime_objects.contains_key(id)
1149 || is_system_package(*id)
1150 }),
1151 "A modified object must be either a mutable input, a loaded child object, or a system package"
1152 );
1153 Ok(())
1154 }
1155}
1156impl TemporaryStore<'_> {
1161 pub fn advance_epoch_safe_mode(
1162 &mut self,
1163 params: &AdvanceEpochParams,
1164 protocol_config: &ProtocolConfig,
1165 ) {
1166 let wrapper = get_sui_system_state_wrapper(self.store.as_object_store())
1167 .expect("System state wrapper object must exist");
1168 let (old_object, new_object) =
1169 wrapper.advance_epoch_safe_mode(params, self.store.as_object_store(), protocol_config);
1170 self.mutate_child_object(old_object, new_object);
1171 }
1172}
1173
1174type ModifiedObjectInfo<'a> = (
1175 ObjectID,
1176 Option<DynamicallyLoadedObjectMetadata>,
1178 Option<&'a Object>,
1179);
1180
1181impl TemporaryStore<'_> {
1182 fn get_input_sui(
1183 &self,
1184 id: &ObjectID,
1185 expected_version: SequenceNumber,
1186 layout_resolver: &mut impl LayoutResolver,
1187 ) -> Result<u64, ExecutionError> {
1188 if let Some(obj) = self.input_objects.get(id) {
1189 if obj.version() != expected_version {
1191 invariant_violation!(
1192 "Version mismatching when resolving input object to check conservation--\
1193 expected {}, got {}",
1194 expected_version,
1195 obj.version(),
1196 );
1197 }
1198 obj.get_total_sui(layout_resolver).map_err(|e| {
1199 make_invariant_violation!(
1200 "Failed looking up input SUI in SUI conservation checking for input with \
1201 type {:?}: {e:#?}",
1202 obj.struct_tag(),
1203 )
1204 })
1205 } else {
1206 let Some(obj) = self.store.get_object_by_key(id, expected_version) else {
1208 invariant_violation!(
1209 "Failed looking up dynamic field {id} in SUI conservation checking"
1210 );
1211 };
1212 obj.get_total_sui(layout_resolver).map_err(|e| {
1213 make_invariant_violation!(
1214 "Failed looking up input SUI in SUI conservation checking for type \
1215 {:?}: {e:#?}",
1216 obj.struct_tag(),
1217 )
1218 })
1219 }
1220 }
1221
1222 fn get_modified_objects(&self) -> Vec<ModifiedObjectInfo<'_>> {
1227 self.execution_results
1228 .modified_objects
1229 .iter()
1230 .map(|id| {
1231 let metadata = self.get_object_modified_at(id);
1232 let output = self.execution_results.written_objects.get(id);
1233 (*id, metadata, output)
1234 })
1235 .chain(
1236 self.execution_results
1237 .written_objects
1238 .iter()
1239 .filter_map(|(id, object)| {
1240 if self.execution_results.modified_objects.contains(id) {
1241 None
1242 } else {
1243 Some((*id, None, Some(object)))
1244 }
1245 }),
1246 )
1247 .collect()
1248 }
1249
1250 pub fn check_sui_conserved(
1264 &self,
1265 simple_conservation_checks: bool,
1266 gas_summary: &GasCostSummary,
1267 ) -> Result<(), ExecutionError> {
1268 if !simple_conservation_checks {
1269 return Ok(());
1270 }
1271 let mut total_input_rebate = 0;
1273 let mut total_output_rebate = 0;
1275 for (_id, input, output) in self.get_modified_objects() {
1276 if let Some(input) = input {
1277 total_input_rebate += input.storage_rebate;
1278 }
1279 if let Some(object) = output {
1280 total_output_rebate += object.storage_rebate;
1281 }
1282 }
1283
1284 if gas_summary.storage_cost == 0 {
1285 if total_input_rebate
1297 != total_output_rebate
1298 + gas_summary.storage_rebate
1299 + gas_summary.non_refundable_storage_fee
1300 {
1301 return Err(ExecutionError::invariant_violation(format!(
1302 "SUI conservation failed -- no storage charges in gas summary \
1303 and total storage input rebate {} not equal \
1304 to total storage output rebate {}",
1305 total_input_rebate, total_output_rebate,
1306 )));
1307 }
1308 } else {
1309 if total_input_rebate
1312 != gas_summary.storage_rebate + gas_summary.non_refundable_storage_fee
1313 {
1314 return Err(ExecutionError::invariant_violation(format!(
1315 "SUI conservation failed -- {} SUI in storage rebate field of input objects, \
1316 {} SUI in tx storage rebate or tx non-refundable storage rebate",
1317 total_input_rebate, gas_summary.non_refundable_storage_fee,
1318 )));
1319 }
1320
1321 if gas_summary.storage_cost != total_output_rebate {
1324 return Err(ExecutionError::invariant_violation(format!(
1325 "SUI conservation failed -- {} SUI charged for storage, \
1326 {} SUI in storage rebate field of output objects",
1327 gas_summary.storage_cost, total_output_rebate
1328 )));
1329 }
1330 }
1331 Ok(())
1332 }
1333
1334 pub fn check_address_balance_changes(
1358 &self,
1359 _protocol_config: &ProtocolConfig,
1360 input_reservations: &BTreeMap<(SuiAddress, TypeTag), u64>,
1361 ) -> Result<(), ExecutionError> {
1362 use sui_types::balance::Balance;
1363
1364 let mut actual_changes: BTreeMap<(SuiAddress, TypeTag), i128> = BTreeMap::new();
1365 let mut has_ptb_withdrawals: BTreeSet<(SuiAddress, TypeTag)> = BTreeSet::new();
1366 let mut has_ptb_deposits: BTreeSet<(SuiAddress, TypeTag)> = BTreeSet::new();
1367 for (idx, event) in self.execution_results.accumulator_events.iter().enumerate() {
1368 let amount = match event.write.value {
1374 AccumulatorValue::Integer(amount) => amount as i128,
1375 AccumulatorValue::IntegerTuple(_, _) | AccumulatorValue::EventDigest(_) => {
1376 assert_invariant!(
1377 !sui_types::balance::Balance::is_balance_type(&event.write.address.ty),
1378 "Non-integer accumulator changes should not be balances"
1379 );
1380 continue;
1381 }
1382 };
1383 if !Balance::is_balance_type(&event.write.address.ty) {
1384 debug_fatal!(
1385 "Integer accumulator value at non-Balance type: {:?}",
1386 event.write.address.ty
1387 );
1388 continue;
1389 }
1390 let is_ptb_emitted = self
1391 .ptb_emitted_accumulator_event_ranges
1392 .iter()
1393 .any(|range| range.contains(&idx));
1394 let key = (event.write.address.address, event.write.address.ty.clone());
1395 let change = match event.write.operation {
1396 AccumulatorOperation::Split => {
1397 if is_ptb_emitted {
1398 has_ptb_withdrawals.insert(key.clone());
1399 }
1400 -amount
1401 }
1402 AccumulatorOperation::Merge => {
1403 if is_ptb_emitted {
1404 has_ptb_deposits.insert(key.clone());
1405 }
1406 amount
1407 }
1408 };
1409 *actual_changes.entry(key.clone()).or_insert(0) += change;
1410 }
1411
1412 for (key, actual) in actual_changes {
1413 let (address, type_tag) = &key;
1414 if let Some(budget) = input_reservations.get(&key).copied() {
1415 let net_withdrawn = -actual.min(0) as u128;
1416 assert_invariant!(
1417 net_withdrawn <= budget as u128,
1418 "Balance accumulator withdrawal exceeds reservation budget at address \
1419 {address} for type {type_tag}: net Split {net_withdrawn}, budget {budget}"
1420 );
1421 } else if has_ptb_withdrawals.contains(&key) {
1422 } else if has_ptb_deposits.contains(&key) {
1426 assert_invariant!(
1431 actual >= 0,
1432 "PTB-emitted Balance accumulator deposits do not cover the runtime \
1433 withdrawal at address {address} for type {type_tag}: net change {actual}"
1434 );
1435 } else {
1436 invariant_violation!(
1437 "Unauthorized runtime Balance accumulator event at address {address} for \
1438 type {type_tag}: net change {actual} (no input reservation, no PTB-emitted \
1439 events)"
1440 );
1441 }
1442 }
1443
1444 Ok(())
1445 }
1446
1447 pub fn check_sui_conserved_expensive(
1460 &self,
1461 gas_summary: &GasCostSummary,
1462 advance_epoch_gas_summary: Option<(u64, u64)>,
1463 layout_resolver: &mut impl LayoutResolver,
1464 ) -> Result<(), ExecutionError> {
1465 let mut total_input_sui: u128 = 0;
1472 let mut total_output_sui: u128 = 0;
1474
1475 total_input_sui += self.execution_results.settlement_input_sui as u128;
1479 total_output_sui += self.execution_results.settlement_output_sui as u128;
1480
1481 for (id, input, output) in self.get_modified_objects() {
1482 if let Some(input) = input {
1483 total_input_sui += self.get_input_sui(&id, input.version, layout_resolver)? as u128;
1484 }
1485 if let Some(object) = output {
1486 total_output_sui += object.get_total_sui(layout_resolver).map_err(|e| {
1487 make_invariant_violation!(
1488 "Failed looking up output SUI in SUI conservation checking for \
1489 mutated type {:?}: {e:#?}",
1490 object.struct_tag(),
1491 )
1492 })? as u128;
1493 }
1494 }
1495
1496 for event in &self.execution_results.accumulator_events {
1497 let (input, output) = event.total_sui_in_event();
1498 total_input_sui += input as u128;
1499 total_output_sui += output as u128;
1500 }
1501
1502 total_output_sui +=
1507 gas_summary.computation_cost as u128 + gas_summary.non_refundable_storage_fee as u128;
1508 if let Some((epoch_fees, epoch_rebates)) = advance_epoch_gas_summary {
1509 total_input_sui += epoch_fees as u128;
1510 total_output_sui += epoch_rebates as u128;
1511 }
1512 if total_input_sui != total_output_sui {
1513 return Err(ExecutionError::invariant_violation(format!(
1514 "SUI conservation failed: input={}, output={}, \
1515 this transaction either mints or burns SUI",
1516 total_input_sui, total_output_sui,
1517 )));
1518 }
1519 Ok(())
1520 }
1521}
1522
1523impl ChildObjectResolver for TemporaryStore<'_> {
1524 fn read_child_object(
1525 &self,
1526 parent: &ObjectID,
1527 child: &ObjectID,
1528 child_version_upper_bound: SequenceNumber,
1529 ) -> SuiResult<Option<Object>> {
1530 let obj_opt = self.execution_results.written_objects.get(child);
1531 if obj_opt.is_some() {
1532 Ok(obj_opt.cloned())
1533 } else {
1534 let _scope = monitored_scope("Execution::read_child_object");
1535 self.store
1536 .read_child_object(parent, child, child_version_upper_bound)
1537 }
1538 }
1539
1540 fn get_object_received_at_version(
1541 &self,
1542 owner: &ObjectID,
1543 receiving_object_id: &ObjectID,
1544 receive_object_at_version: SequenceNumber,
1545 epoch_id: EpochId,
1546 ) -> SuiResult<Option<Object>> {
1547 debug_assert!(
1550 !self
1551 .execution_results
1552 .written_objects
1553 .contains_key(receiving_object_id)
1554 );
1555 debug_assert!(
1556 !self
1557 .execution_results
1558 .deleted_object_ids
1559 .contains(receiving_object_id)
1560 );
1561 self.store.get_object_received_at_version(
1562 owner,
1563 receiving_object_id,
1564 receive_object_at_version,
1565 epoch_id,
1566 )
1567 }
1568}
1569
1570fn was_object_mutated(object: &Object, original: &Object) -> bool {
1573 let data_equal = match (&object.data, &original.data) {
1574 (Data::Move(a), Data::Move(b)) => a.contents_and_type_equal(b),
1575 (Data::Package(a), Data::Package(b)) => a == b,
1578 _ => false,
1579 };
1580
1581 let owner_equal = match (&object.owner, &original.owner) {
1582 (Owner::Shared { .. }, Owner::Shared { .. }) => true,
1586 (
1587 Owner::ConsensusAddressOwner { owner: a, .. },
1588 Owner::ConsensusAddressOwner { owner: b, .. },
1589 ) => a == b,
1590 (Owner::AddressOwner(a), Owner::AddressOwner(b)) => a == b,
1591 (Owner::Immutable, Owner::Immutable) => true,
1592 (Owner::ObjectOwner(a), Owner::ObjectOwner(b)) => a == b,
1593 (
1594 Owner::Party {
1595 permissions: a,
1596 start_version: _,
1597 },
1598 Owner::Party {
1599 permissions: b,
1600 start_version: _,
1601 },
1602 ) => a == b,
1603
1604 (Owner::AddressOwner(_), _)
1607 | (Owner::Immutable, _)
1608 | (Owner::ObjectOwner(_), _)
1609 | (Owner::Shared { .. }, _)
1610 | (Owner::ConsensusAddressOwner { .. }, _)
1611 | (Owner::Party { .. }, _) => false,
1612 };
1613
1614 !data_equal || !owner_equal
1615}
1616
1617impl Storage for TemporaryStore<'_> {
1618 fn reset(&mut self) {
1619 self.drop_writes();
1620 }
1621
1622 fn read_object(&self, id: &ObjectID) -> Option<&Object> {
1623 TemporaryStore::read_object(self, id)
1624 }
1625
1626 fn record_execution_results(
1628 &mut self,
1629 results: ExecutionResults,
1630 ) -> Result<(), ExecutionError> {
1631 let ExecutionResults::V2(mut results) = results else {
1632 panic!("ExecutionResults::V2 expected in sui-execution v1 and above");
1633 };
1634
1635 let mut to_remove = Vec::new();
1637 for (id, original) in &self.non_exclusive_input_original_versions {
1638 if results
1640 .written_objects
1641 .get(id)
1642 .map(|obj| was_object_mutated(obj, original))
1643 .unwrap_or(true)
1644 {
1645 return Err(ExecutionError::new_with_source(
1646 ExecutionErrorKind::NonExclusiveWriteInputObjectModified { id: *id },
1647 "Non-exclusive write input object has been modified or deleted",
1648 ));
1649 }
1650 to_remove.push(*id);
1651 }
1652
1653 for id in to_remove {
1654 results.written_objects.remove(&id);
1655 results.modified_objects.remove(&id);
1656 }
1657
1658 let event_start = self.execution_results.accumulator_events.len();
1664 self.execution_results.merge_results(
1665 results, true, true,
1666 )?;
1667 let event_end = self.execution_results.accumulator_events.len();
1668 debug_assert!(
1669 event_start <= event_end,
1670 "merge_results should not shrink accumulator_events"
1671 );
1672 let (event_start, event_end) = (event_start.min(event_end), event_start.max(event_end));
1673 let range = event_start..event_end;
1674 match self.ptb_emitted_accumulator_event_ranges.last_mut() {
1675 Some(last) if last.end == range.start => last.end = range.end,
1677 _ => self.ptb_emitted_accumulator_event_ranges.push(range),
1678 }
1679
1680 Ok(())
1681 }
1682
1683 fn save_loaded_runtime_objects(
1684 &mut self,
1685 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
1686 ) {
1687 TemporaryStore::save_loaded_runtime_objects(self, loaded_runtime_objects)
1688 }
1689
1690 fn save_wrapped_object_containers(
1691 &mut self,
1692 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
1693 ) {
1694 TemporaryStore::save_wrapped_object_containers(self, wrapped_object_containers)
1695 }
1696
1697 fn check_coin_deny_list(
1698 &self,
1699 receiving_funds_type_and_owners: BTreeMap<TypeTag, BTreeSet<SuiAddress>>,
1700 ) -> DenyListResult {
1701 let result = check_coin_deny_list_v2_during_execution(
1702 receiving_funds_type_and_owners,
1703 self.cur_epoch,
1704 self.store.as_object_store(),
1705 );
1706 if result.num_non_gas_coin_owners > 0
1709 && !self.input_objects.contains_key(&SUI_DENY_LIST_OBJECT_ID)
1710 {
1711 self.loaded_per_epoch_config_objects
1712 .write()
1713 .insert(SUI_DENY_LIST_OBJECT_ID);
1714 }
1715 result
1716 }
1717
1718 fn record_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
1719 TemporaryStore::save_generated_object_ids(self, generated_ids)
1720 }
1721}
1722
1723impl BackingPackageStore for TemporaryStore<'_> {
1724 fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
1725 if let Some(obj) = self.execution_results.written_objects.get(package_id) {
1732 Ok(Some(PackageObject::new(obj.clone())))
1733 } else {
1734 self.store.get_package_object(package_id).inspect(|obj| {
1735 if let Some(v) = obj
1737 && !self
1738 .runtime_packages_loaded_from_db
1739 .read()
1740 .contains_key(package_id)
1741 {
1742 self.runtime_packages_loaded_from_db
1747 .write()
1748 .insert(*package_id, v.clone());
1749 }
1750 })
1751 }
1752 }
1753}
1754
1755impl ParentSync for TemporaryStore<'_> {
1756 fn get_latest_parent_entry_ref_deprecated(&self, _object_id: ObjectID) -> Option<ObjectRef> {
1757 unreachable!("Never called in newer protocol versions")
1758 }
1759}