1use crate::gas_charger::GasCharger;
5use mysten_metrics::monitored_scope;
6use parking_lot::RwLock;
7use std::collections::{BTreeMap, BTreeSet, HashSet};
8use sui_protocol_config::ProtocolConfig;
9use sui_types::accumulator_event::AccumulatorEvent;
10use sui_types::accumulator_root::AccumulatorObjId;
11use sui_types::base_types::VersionDigest;
12use sui_types::committee::EpochId;
13use sui_types::deny_list_v2::check_coin_deny_list_v2_during_execution;
14use sui_types::effects::{
15 AccumulatorOperation, AccumulatorValue, AccumulatorWriteV1, TransactionEffects,
16 TransactionEffectsV2, TransactionEvents,
17};
18use sui_types::execution::{
19 DynamicallyLoadedObjectMetadata, ExecutionResults, ExecutionResultsV2, SharedInput,
20};
21use sui_types::execution_status::{ExecutionErrorKind, ExecutionStatus};
22use sui_types::inner_temporary_store::InnerTemporaryStore;
23use sui_types::layout_resolver::LayoutResolver;
24use sui_types::object::Data;
25use sui_types::storage::{BackingStore, DenyListResult, PackageObject};
26use sui_types::sui_system_state::{AdvanceEpochParams, get_sui_system_state_wrapper};
27use sui_types::{
28 SUI_DENY_LIST_OBJECT_ID,
29 base_types::{ObjectID, ObjectRef, SequenceNumber, SuiAddress, TransactionDigest},
30 effects::EffectsObjectChange,
31 error::{ExecutionError, SuiResult},
32 gas::GasCostSummary,
33 object::Object,
34 object::Owner,
35 storage::{BackingPackageStore, ChildObjectResolver, ParentSync, Storage},
36 transaction::InputObjects,
37};
38use sui_types::{SUI_SYSTEM_STATE_OBJECT_ID, TypeTag, is_system_package};
39
40pub struct TemporaryStore<'backing> {
41 store: &'backing dyn BackingStore,
47 tx_digest: TransactionDigest,
48 input_objects: BTreeMap<ObjectID, Object>,
49
50 non_exclusive_input_original_versions: BTreeMap<ObjectID, Object>,
53
54 stream_ended_consensus_objects: BTreeMap<ObjectID, SequenceNumber >,
55 lamport_timestamp: SequenceNumber,
57 mutable_input_refs: BTreeMap<ObjectID, (VersionDigest, Owner)>,
60 execution_results: ExecutionResultsV2,
61 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
63 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
65 protocol_config: &'backing ProtocolConfig,
66
67 runtime_packages_loaded_from_db: RwLock<BTreeMap<ObjectID, PackageObject>>,
70
71 receiving_objects: Vec<ObjectRef>,
74
75 generated_runtime_ids: BTreeSet<ObjectID>,
79
80 cur_epoch: EpochId,
83
84 loaded_per_epoch_config_objects: RwLock<BTreeSet<ObjectID>>,
87}
88
89impl<'backing> TemporaryStore<'backing> {
90 pub fn new(
93 store: &'backing dyn BackingStore,
94 input_objects: InputObjects,
95 receiving_objects: Vec<ObjectRef>,
96 tx_digest: TransactionDigest,
97 protocol_config: &'backing ProtocolConfig,
98 cur_epoch: EpochId,
99 ) -> Self {
100 let mutable_input_refs = input_objects.exclusive_mutable_inputs();
101 let non_exclusive_input_original_versions = input_objects.non_exclusive_input_objects();
102
103 let lamport_timestamp = input_objects.lamport_timestamp(&receiving_objects);
104 let stream_ended_consensus_objects = input_objects.consensus_stream_ended_objects();
105 let objects = input_objects.into_object_map();
106 #[cfg(debug_assertions)]
107 {
108 assert!(
110 objects
111 .keys()
112 .collect::<HashSet<_>>()
113 .intersection(
114 &receiving_objects
115 .iter()
116 .map(|oref| &oref.0)
117 .collect::<HashSet<_>>()
118 )
119 .next()
120 .is_none()
121 );
122 }
123 Self {
124 store,
125 tx_digest,
126 input_objects: objects,
127 non_exclusive_input_original_versions,
128 stream_ended_consensus_objects,
129 lamport_timestamp,
130 mutable_input_refs,
131 execution_results: ExecutionResultsV2::default(),
132 protocol_config,
133 loaded_runtime_objects: BTreeMap::new(),
134 wrapped_object_containers: BTreeMap::new(),
135 runtime_packages_loaded_from_db: RwLock::new(BTreeMap::new()),
136 receiving_objects,
137 generated_runtime_ids: BTreeSet::new(),
138 cur_epoch,
139 loaded_per_epoch_config_objects: RwLock::new(BTreeSet::new()),
140 }
141 }
142
143 pub fn objects(&self) -> &BTreeMap<ObjectID, Object> {
145 &self.input_objects
146 }
147
148 pub fn update_object_version_and_prev_tx(&mut self) {
149 self.execution_results.update_version_and_previous_tx(
150 self.lamport_timestamp,
151 self.tx_digest,
152 &self.input_objects,
153 self.protocol_config.reshare_at_same_initial_version(),
154 );
155
156 #[cfg(debug_assertions)]
157 {
158 self.check_invariants();
159 }
160 }
161
162 fn calculate_accumulator_running_max_withdraws(&self) -> BTreeMap<AccumulatorObjId, u128> {
163 let mut running_net_withdraws: BTreeMap<AccumulatorObjId, i128> = BTreeMap::new();
164 let mut running_max_withdraws: BTreeMap<AccumulatorObjId, u128> = BTreeMap::new();
165 for event in &self.execution_results.accumulator_events {
166 match &event.write.value {
167 AccumulatorValue::Integer(amount) => match event.write.operation {
168 AccumulatorOperation::Split => {
169 let entry = running_net_withdraws
170 .entry(event.accumulator_obj)
171 .or_default();
172 *entry += *amount as i128;
173 if *entry > 0 {
174 let max_entry = running_max_withdraws
175 .entry(event.accumulator_obj)
176 .or_default();
177 *max_entry = (*max_entry).max(*entry as u128);
178 }
179 }
180 AccumulatorOperation::Merge => {
181 let entry = running_net_withdraws
182 .entry(event.accumulator_obj)
183 .or_default();
184 *entry -= *amount as i128;
185 }
186 },
187 AccumulatorValue::IntegerTuple(_, _) | AccumulatorValue::EventDigest(_) => {}
188 }
189 }
190 running_max_withdraws
191 }
192
193 fn merge_accumulator_events(&mut self) {
195 self.execution_results.accumulator_events = self
196 .execution_results
197 .accumulator_events
198 .iter()
199 .fold(
200 BTreeMap::<AccumulatorObjId, Vec<AccumulatorWriteV1>>::new(),
201 |mut map, event| {
202 map.entry(event.accumulator_obj)
203 .or_default()
204 .push(event.write.clone());
205 map
206 },
207 )
208 .into_iter()
209 .map(|(obj_id, writes)| {
210 AccumulatorEvent::new(obj_id, AccumulatorWriteV1::merge(writes))
211 })
212 .collect();
213 }
214
215 pub fn into_inner(
217 self,
218 accumulator_running_max_withdraws: BTreeMap<AccumulatorObjId, u128>,
219 ) -> InnerTemporaryStore {
220 let results = self.execution_results;
221 InnerTemporaryStore {
222 input_objects: self.input_objects,
223 stream_ended_consensus_objects: self.stream_ended_consensus_objects,
224 mutable_inputs: self.mutable_input_refs,
225 written: results.written_objects,
226 events: TransactionEvents {
227 data: results.user_events,
228 },
229 accumulator_events: results.accumulator_events,
230 loaded_runtime_objects: self.loaded_runtime_objects,
231 runtime_packages_loaded_from_db: self.runtime_packages_loaded_from_db.into_inner(),
232 lamport_version: self.lamport_timestamp,
233 binary_config: self.protocol_config.binary_config(None),
234 accumulator_running_max_withdraws,
235 }
236 }
237
238 pub(crate) fn ensure_active_inputs_mutated(&mut self) {
242 let mut to_be_updated = vec![];
243 for id in self.mutable_input_refs.keys() {
245 if !self.execution_results.modified_objects.contains(id) {
246 to_be_updated.push(self.input_objects[id].clone());
250 }
251 }
252 for object in to_be_updated {
253 self.mutate_input_object(object.clone());
255 }
256 }
257
258 fn get_object_changes(&self) -> BTreeMap<ObjectID, EffectsObjectChange> {
259 let results = &self.execution_results;
260 let all_ids = results
261 .created_object_ids
262 .iter()
263 .chain(&results.deleted_object_ids)
264 .chain(&results.modified_objects)
265 .chain(results.written_objects.keys())
266 .collect::<BTreeSet<_>>();
267 all_ids
268 .into_iter()
269 .map(|id| {
270 (
271 *id,
272 EffectsObjectChange::new(
273 self.get_object_modified_at(id)
274 .map(|metadata| ((metadata.version, metadata.digest), metadata.owner)),
275 results.written_objects.get(id),
276 results.created_object_ids.contains(id),
277 results.deleted_object_ids.contains(id),
278 ),
279 )
280 })
281 .chain(results.accumulator_events.iter().cloned().map(
282 |AccumulatorEvent {
283 accumulator_obj,
284 write,
285 }| {
286 (
287 *accumulator_obj.inner(),
288 EffectsObjectChange::new_from_accumulator_write(write),
289 )
290 },
291 ))
292 .collect()
293 }
294
295 pub fn into_effects(
296 mut self,
297 shared_object_refs: Vec<SharedInput>,
298 transaction_digest: &TransactionDigest,
299 mut transaction_dependencies: BTreeSet<TransactionDigest>,
300 gas_cost_summary: GasCostSummary,
301 status: ExecutionStatus,
302 gas_charger: &mut GasCharger,
303 epoch: EpochId,
304 ) -> (InnerTemporaryStore, TransactionEffects) {
305 self.update_object_version_and_prev_tx();
306 let accumulator_running_max_withdraws = self.calculate_accumulator_running_max_withdraws();
308 self.merge_accumulator_events();
309
310 for (id, expected_version, expected_digest) in &self.receiving_objects {
313 if let Some(obj_meta) = self.loaded_runtime_objects.get(id) {
317 let loaded_via_receive = obj_meta.version == *expected_version
321 && obj_meta.digest == *expected_digest
322 && obj_meta.owner.is_address_owned();
323 if loaded_via_receive {
324 transaction_dependencies.insert(obj_meta.previous_transaction);
325 }
326 }
327 }
328
329 assert!(self.protocol_config.enable_effects_v2());
330
331 let gas_coin = gas_charger.gas_coin();
336
337 let object_changes = self.get_object_changes();
338
339 let lamport_version = self.lamport_timestamp;
340 let loaded_per_epoch_config_objects = self.loaded_per_epoch_config_objects.read().clone();
342 let unchanged_consensus_objects = TransactionEffectsV2::compute_unchanged_consensus_objects(
343 shared_object_refs,
344 loaded_per_epoch_config_objects,
345 &object_changes,
346 );
347 let inner = self.into_inner(accumulator_running_max_withdraws);
348
349 let effects = TransactionEffects::new_from_execution_v2(
350 status,
351 epoch,
352 gas_cost_summary,
353 unchanged_consensus_objects,
354 *transaction_digest,
355 lamport_version,
356 object_changes,
357 gas_coin,
358 if inner.events.data.is_empty() {
359 None
360 } else {
361 Some(inner.events.digest())
362 },
363 transaction_dependencies.into_iter().collect(),
364 );
365
366 (inner, effects)
367 }
368
369 #[cfg(debug_assertions)]
371 fn check_invariants(&self) {
372 debug_assert!(
374 {
375 self.execution_results
376 .written_objects
377 .keys()
378 .all(|id| !self.execution_results.deleted_object_ids.contains(id))
379 },
380 "Object both written and deleted."
381 );
382
383 debug_assert!(
385 {
386 self.mutable_input_refs
387 .keys()
388 .all(|id| self.execution_results.modified_objects.contains(id))
389 },
390 "Mutable input not modified."
391 );
392
393 debug_assert!(
394 {
395 self.execution_results
396 .written_objects
397 .values()
398 .all(|obj| obj.previous_transaction == self.tx_digest)
399 },
400 "Object previous transaction not properly set",
401 );
402 }
403
404 pub fn mutate_input_object(&mut self, object: Object) {
406 let id = object.id();
407 debug_assert!(self.input_objects.contains_key(&id));
408 debug_assert!(!object.is_immutable());
409 self.execution_results.modified_objects.insert(id);
410 self.execution_results.written_objects.insert(id, object);
411 }
412
413 pub fn mutate_child_object(&mut self, old_object: Object, new_object: Object) {
417 let id = new_object.id();
418 let old_ref = old_object.compute_object_reference();
419 debug_assert_eq!(old_ref.0, id);
420 self.loaded_runtime_objects.insert(
421 id,
422 DynamicallyLoadedObjectMetadata {
423 version: old_ref.1,
424 digest: old_ref.2,
425 owner: old_object.owner.clone(),
426 storage_rebate: old_object.storage_rebate,
427 previous_transaction: old_object.previous_transaction,
428 },
429 );
430 self.execution_results.modified_objects.insert(id);
431 self.execution_results
432 .written_objects
433 .insert(id, new_object);
434 }
435
436 pub fn upgrade_system_package(&mut self, package: Object) {
440 let id = package.id();
441 assert!(package.is_package() && is_system_package(id));
442 self.execution_results.modified_objects.insert(id);
443 self.execution_results.written_objects.insert(id, package);
444 }
445
446 pub fn create_object(&mut self, object: Object) {
448 debug_assert!(
453 object.is_immutable() || object.version() == SequenceNumber::MIN,
454 "Created mutable objects should not have a version set",
455 );
456 let id = object.id();
457 self.execution_results.created_object_ids.insert(id);
458 self.execution_results.written_objects.insert(id, object);
459 }
460
461 pub fn delete_input_object(&mut self, id: &ObjectID) {
463 debug_assert!(!self.execution_results.written_objects.contains_key(id));
465 debug_assert!(self.input_objects.contains_key(id));
466 self.execution_results.modified_objects.insert(*id);
467 self.execution_results.deleted_object_ids.insert(*id);
468 }
469
470 pub fn drop_writes(&mut self) {
471 self.execution_results.drop_writes();
472 }
473
474 pub fn read_object(&self, id: &ObjectID) -> Option<&Object> {
475 debug_assert!(!self.execution_results.deleted_object_ids.contains(id));
477 self.execution_results
478 .written_objects
479 .get(id)
480 .or_else(|| self.input_objects.get(id))
481 }
482
483 pub fn save_loaded_runtime_objects(
484 &mut self,
485 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
486 ) {
487 #[cfg(debug_assertions)]
488 {
489 for (id, v1) in &loaded_runtime_objects {
490 if let Some(v2) = self.loaded_runtime_objects.get(id) {
491 assert_eq!(v1, v2);
492 }
493 }
494 for (id, v1) in &self.loaded_runtime_objects {
495 if let Some(v2) = loaded_runtime_objects.get(id) {
496 assert_eq!(v1, v2);
497 }
498 }
499 }
500 self.loaded_runtime_objects.extend(loaded_runtime_objects);
503 }
504
505 pub fn save_wrapped_object_containers(
506 &mut self,
507 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
508 ) {
509 #[cfg(debug_assertions)]
510 {
511 for (id, container1) in &wrapped_object_containers {
512 if let Some(container2) = self.wrapped_object_containers.get(id) {
513 assert_eq!(container1, container2);
514 }
515 }
516 for (id, container1) in &self.wrapped_object_containers {
517 if let Some(container2) = wrapped_object_containers.get(id) {
518 assert_eq!(container1, container2);
519 }
520 }
521 }
522 self.wrapped_object_containers
525 .extend(wrapped_object_containers);
526 }
527
528 pub fn save_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
529 #[cfg(debug_assertions)]
530 {
531 for id in &self.generated_runtime_ids {
532 assert!(!generated_ids.contains(id))
533 }
534 for id in &generated_ids {
535 assert!(!self.generated_runtime_ids.contains(id));
536 }
537 }
538 self.generated_runtime_ids.extend(generated_ids);
539 }
540
541 pub fn estimate_effects_size_upperbound(&self) -> usize {
542 TransactionEffects::estimate_effects_size_upperbound_v2(
543 self.execution_results.written_objects.len(),
544 self.execution_results.modified_objects.len(),
545 self.input_objects.len(),
546 )
547 }
548
549 pub fn written_objects_size(&self) -> usize {
550 self.execution_results
551 .written_objects
552 .values()
553 .fold(0, |sum, obj| sum + obj.object_size_for_gas_metering())
554 }
555
556 pub fn conserve_unmetered_storage_rebate(&mut self, unmetered_storage_rebate: u64) {
561 if unmetered_storage_rebate == 0 {
562 return;
566 }
567 tracing::debug!(
568 "Amount of unmetered storage rebate from system tx: {:?}",
569 unmetered_storage_rebate
570 );
571 let mut system_state_wrapper = self
572 .read_object(&SUI_SYSTEM_STATE_OBJECT_ID)
573 .expect("0x5 object must be mutated in system tx with unmetered storage rebate")
574 .clone();
575 assert_eq!(system_state_wrapper.storage_rebate, 0);
578 system_state_wrapper.storage_rebate = unmetered_storage_rebate;
579 self.mutate_input_object(system_state_wrapper);
580 }
581
582 pub fn add_accumulator_event(&mut self, event: AccumulatorEvent) {
584 self.execution_results.accumulator_events.push(event);
585 }
586
587 fn get_object_modified_at(
593 &self,
594 object_id: &ObjectID,
595 ) -> Option<DynamicallyLoadedObjectMetadata> {
596 if self.execution_results.modified_objects.contains(object_id) {
597 Some(
598 self.mutable_input_refs
599 .get(object_id)
600 .map(
601 |((version, digest), owner)| DynamicallyLoadedObjectMetadata {
602 version: *version,
603 digest: *digest,
604 owner: owner.clone(),
605 storage_rebate: self.input_objects[object_id].storage_rebate,
607 previous_transaction: self.input_objects[object_id]
608 .previous_transaction,
609 },
610 )
611 .or_else(|| self.loaded_runtime_objects.get(object_id).cloned())
612 .unwrap_or_else(|| {
613 debug_assert!(is_system_package(*object_id));
614 let package_obj =
615 self.store.get_package_object(object_id).unwrap().unwrap();
616 let obj = package_obj.object();
617 DynamicallyLoadedObjectMetadata {
618 version: obj.version(),
619 digest: obj.digest(),
620 owner: obj.owner.clone(),
621 storage_rebate: obj.storage_rebate,
622 previous_transaction: obj.previous_transaction,
623 }
624 }),
625 )
626 } else {
627 None
628 }
629 }
630}
631
632impl TemporaryStore<'_> {
633 pub fn check_ownership_invariants(
636 &self,
637 sender: &SuiAddress,
638 sponsor: &Option<SuiAddress>,
639 gas_charger: &mut GasCharger,
640 mutable_inputs: &HashSet<ObjectID>,
641 is_epoch_change: bool,
642 ) -> SuiResult<()> {
643 let gas_objs: HashSet<&ObjectID> = gas_charger.gas_coins().map(|g| &g.0).collect();
644 let gas_owner = sponsor.as_ref().unwrap_or(sender);
645
646 let mut authenticated_for_mutation: HashSet<_> = self
648 .input_objects
649 .iter()
650 .filter_map(|(id, obj)| {
651 match &obj.owner {
652 Owner::AddressOwner(a) => {
653 if gas_objs.contains(id) {
654 assert!(
656 a == gas_owner,
657 "Gas object must be owned by sender or sponsor"
658 );
659 } else {
660 assert!(sender == a, "Input object must be owned by sender");
661 }
662 Some(id)
663 }
664 Owner::Shared { .. } | Owner::ConsensusAddressOwner { .. } => Some(id),
665 Owner::Immutable => {
666 None
677 }
678 Owner::ObjectOwner(_parent) => {
679 unreachable!(
680 "Input objects must be address owned, shared, consensus, or immutable"
681 )
682 }
683 Owner::Party { .. } => {
684 unimplemented!("Party does not exist for this execution version")
685 }
686 }
687 })
688 .filter(|id| {
689 mutable_inputs.contains(id)
692 })
693 .copied()
694 .chain(self.generated_runtime_ids.iter().copied())
697 .collect();
698
699 authenticated_for_mutation.insert((*sender).into());
701 if let Some(sponsor) = sponsor {
702 authenticated_for_mutation.insert((*sponsor).into());
703 }
704
705 let mut objects_to_authenticate = self
707 .execution_results
708 .modified_objects
709 .iter()
710 .copied()
711 .collect::<Vec<_>>();
712
713 while let Some(to_authenticate) = objects_to_authenticate.pop() {
714 if authenticated_for_mutation.contains(&to_authenticate) {
715 continue;
717 }
718
719 let parent = if let Some(container_id) =
720 self.wrapped_object_containers.get(&to_authenticate)
721 {
722 *container_id
724 } else {
725 let Some(old_obj) = self.store.get_object(&to_authenticate) else {
728 panic!(
729 "Failed to load object {to_authenticate:?}.\n \
730 If it cannot be loaded, we would expect it to be in the wrapped object map: {:#?}",
731 &self.wrapped_object_containers
732 )
733 };
734
735 match &old_obj.owner {
736 Owner::ObjectOwner(parent) => ObjectID::from(*parent),
739 Owner::AddressOwner(parent)
744 | Owner::ConsensusAddressOwner { owner: parent, .. } => {
745 ObjectID::from(*parent)
749 }
750 owner @ Owner::Shared { .. } => {
753 panic!(
754 "Unauthenticated root at {to_authenticate:?} with owner {owner:?}\n\
755 Potentially covering objects in: {authenticated_for_mutation:#?}"
756 );
757 }
758 Owner::Immutable => {
759 assert!(
760 is_epoch_change,
761 "Immutable objects cannot be written, except for \
762 Sui Framework/Move stdlib upgrades at epoch change boundaries"
763 );
764 assert!(
768 is_system_package(to_authenticate),
769 "Only system packages can be upgraded"
770 );
771 continue;
772 }
773 Owner::Party { .. } => {
774 unimplemented!("Party does not exist for this execution version")
775 }
776 }
777 };
778
779 authenticated_for_mutation.insert(to_authenticate);
781 objects_to_authenticate.push(parent);
782 }
783 Ok(())
784 }
785}
786
787impl TemporaryStore<'_> {
788 pub(crate) fn collect_storage_and_rebate(&mut self, gas_charger: &mut GasCharger) {
795 let old_storage_rebates: Vec<_> = self
797 .execution_results
798 .written_objects
799 .keys()
800 .map(|object_id| {
801 self.get_object_modified_at(object_id)
802 .map(|metadata| metadata.storage_rebate)
803 .unwrap_or_default()
804 })
805 .collect();
806 for (object, old_storage_rebate) in self
807 .execution_results
808 .written_objects
809 .values_mut()
810 .zip(old_storage_rebates)
811 {
812 let new_object_size = object.object_size_for_gas_metering();
814 let new_storage_rebate = gas_charger.track_storage_mutation(
816 object.id(),
817 new_object_size,
818 old_storage_rebate,
819 );
820 object.storage_rebate = new_storage_rebate;
821 }
822
823 self.collect_rebate(gas_charger);
824 }
825
826 pub(crate) fn collect_rebate(&self, gas_charger: &mut GasCharger) {
827 for object_id in &self.execution_results.modified_objects {
828 if self
829 .execution_results
830 .written_objects
831 .contains_key(object_id)
832 {
833 continue;
834 }
835 let storage_rebate = self
837 .get_object_modified_at(object_id)
838 .unwrap()
840 .storage_rebate;
841 gas_charger.track_storage_mutation(*object_id, 0, storage_rebate);
842 }
843 }
844
845 pub fn check_execution_results_consistency(&self) -> Result<(), ExecutionError> {
846 assert_invariant!(
847 self.execution_results
848 .created_object_ids
849 .iter()
850 .all(|id| !self.execution_results.deleted_object_ids.contains(id)
851 && !self.execution_results.modified_objects.contains(id)),
852 "Created object IDs cannot also be deleted or modified"
853 );
854 assert_invariant!(
855 self.execution_results.modified_objects.iter().all(|id| {
856 self.mutable_input_refs.contains_key(id)
857 || self.loaded_runtime_objects.contains_key(id)
858 || is_system_package(*id)
859 }),
860 "A modified object must be either a mutable input, a loaded child object, or a system package"
861 );
862 Ok(())
863 }
864}
865impl TemporaryStore<'_> {
870 pub fn advance_epoch_safe_mode(
871 &mut self,
872 params: &AdvanceEpochParams,
873 protocol_config: &ProtocolConfig,
874 ) {
875 let wrapper = get_sui_system_state_wrapper(self.store.as_object_store())
876 .expect("System state wrapper object must exist");
877 let (old_object, new_object) =
878 wrapper.advance_epoch_safe_mode(params, self.store.as_object_store(), protocol_config);
879 self.mutate_child_object(old_object, new_object);
880 }
881}
882
883type ModifiedObjectInfo<'a> = (
884 ObjectID,
885 Option<DynamicallyLoadedObjectMetadata>,
887 Option<&'a Object>,
888);
889
890impl TemporaryStore<'_> {
891 fn get_input_sui(
892 &self,
893 id: &ObjectID,
894 expected_version: SequenceNumber,
895 layout_resolver: &mut impl LayoutResolver,
896 ) -> Result<u64, ExecutionError> {
897 if let Some(obj) = self.input_objects.get(id) {
898 if obj.version() != expected_version {
900 invariant_violation!(
901 "Version mismatching when resolving input object to check conservation--\
902 expected {}, got {}",
903 expected_version,
904 obj.version(),
905 );
906 }
907 obj.get_total_sui(layout_resolver).map_err(|e| {
908 make_invariant_violation!(
909 "Failed looking up input SUI in SUI conservation checking for input with \
910 type {:?}: {e:#?}",
911 obj.struct_tag(),
912 )
913 })
914 } else {
915 let Some(obj) = self.store.get_object_by_key(id, expected_version) else {
917 invariant_violation!(
918 "Failed looking up dynamic field {id} in SUI conservation checking"
919 );
920 };
921 obj.get_total_sui(layout_resolver).map_err(|e| {
922 make_invariant_violation!(
923 "Failed looking up input SUI in SUI conservation checking for type \
924 {:?}: {e:#?}",
925 obj.struct_tag(),
926 )
927 })
928 }
929 }
930
931 fn get_modified_objects(&self) -> Vec<ModifiedObjectInfo<'_>> {
936 self.execution_results
937 .modified_objects
938 .iter()
939 .map(|id| {
940 let metadata = self.get_object_modified_at(id);
941 let output = self.execution_results.written_objects.get(id);
942 (*id, metadata, output)
943 })
944 .chain(
945 self.execution_results
946 .written_objects
947 .iter()
948 .filter_map(|(id, object)| {
949 if self.execution_results.modified_objects.contains(id) {
950 None
951 } else {
952 Some((*id, None, Some(object)))
953 }
954 }),
955 )
956 .collect()
957 }
958
959 pub fn check_sui_conserved(
973 &self,
974 simple_conservation_checks: bool,
975 gas_summary: &GasCostSummary,
976 ) -> Result<(), ExecutionError> {
977 if !simple_conservation_checks {
978 return Ok(());
979 }
980 let mut total_input_rebate = 0;
982 let mut total_output_rebate = 0;
984 for (_, input, output) in self.get_modified_objects() {
985 if let Some(input) = input {
986 total_input_rebate += input.storage_rebate;
987 }
988 if let Some(object) = output {
989 total_output_rebate += object.storage_rebate;
990 }
991 }
992
993 if gas_summary.storage_cost == 0 {
994 if total_input_rebate
1006 != total_output_rebate
1007 + gas_summary.storage_rebate
1008 + gas_summary.non_refundable_storage_fee
1009 {
1010 return Err(ExecutionError::invariant_violation(format!(
1011 "SUI conservation failed -- no storage charges in gas summary \
1012 and total storage input rebate {} not equal \
1013 to total storage output rebate {}",
1014 total_input_rebate, total_output_rebate,
1015 )));
1016 }
1017 } else {
1018 if total_input_rebate
1021 != gas_summary.storage_rebate + gas_summary.non_refundable_storage_fee
1022 {
1023 return Err(ExecutionError::invariant_violation(format!(
1024 "SUI conservation failed -- {} SUI in storage rebate field of input objects, \
1025 {} SUI in tx storage rebate or tx non-refundable storage rebate",
1026 total_input_rebate, gas_summary.non_refundable_storage_fee,
1027 )));
1028 }
1029
1030 if gas_summary.storage_cost != total_output_rebate {
1033 return Err(ExecutionError::invariant_violation(format!(
1034 "SUI conservation failed -- {} SUI charged for storage, \
1035 {} SUI in storage rebate field of output objects",
1036 gas_summary.storage_cost, total_output_rebate
1037 )));
1038 }
1039 }
1040 Ok(())
1041 }
1042
1043 pub fn check_sui_conserved_expensive(
1056 &self,
1057 gas_summary: &GasCostSummary,
1058 advance_epoch_gas_summary: Option<(u64, u64)>,
1059 layout_resolver: &mut impl LayoutResolver,
1060 ) -> Result<(), ExecutionError> {
1061 let mut total_input_sui = 0;
1063 let mut total_output_sui = 0;
1065
1066 total_input_sui += self.execution_results.settlement_input_sui;
1070 total_output_sui += self.execution_results.settlement_output_sui;
1071
1072 for (id, input, output) in self.get_modified_objects() {
1073 if let Some(input) = input {
1074 total_input_sui += self.get_input_sui(&id, input.version, layout_resolver)?;
1075 }
1076 if let Some(object) = output {
1077 total_output_sui += object.get_total_sui(layout_resolver).map_err(|e| {
1078 make_invariant_violation!(
1079 "Failed looking up output SUI in SUI conservation checking for \
1080 mutated type {:?}: {e:#?}",
1081 object.struct_tag(),
1082 )
1083 })?;
1084 }
1085 }
1086
1087 for event in &self.execution_results.accumulator_events {
1088 let (input, output) = event.total_sui_in_event();
1089 total_input_sui += input;
1090 total_output_sui += output;
1091 }
1092
1093 total_output_sui += gas_summary.computation_cost + gas_summary.non_refundable_storage_fee;
1098 if let Some((epoch_fees, epoch_rebates)) = advance_epoch_gas_summary {
1099 total_input_sui += epoch_fees;
1100 total_output_sui += epoch_rebates;
1101 }
1102 if total_input_sui != total_output_sui {
1103 return Err(ExecutionError::invariant_violation(format!(
1104 "SUI conservation failed: input={}, output={}, \
1105 this transaction either mints or burns SUI",
1106 total_input_sui, total_output_sui,
1107 )));
1108 }
1109 Ok(())
1110 }
1111}
1112
1113impl ChildObjectResolver for TemporaryStore<'_> {
1114 fn read_child_object(
1115 &self,
1116 parent: &ObjectID,
1117 child: &ObjectID,
1118 child_version_upper_bound: SequenceNumber,
1119 ) -> SuiResult<Option<Object>> {
1120 let obj_opt = self.execution_results.written_objects.get(child);
1121 if obj_opt.is_some() {
1122 Ok(obj_opt.cloned())
1123 } else {
1124 let _scope = monitored_scope("Execution::read_child_object");
1125 self.store
1126 .read_child_object(parent, child, child_version_upper_bound)
1127 }
1128 }
1129
1130 fn get_object_received_at_version(
1131 &self,
1132 owner: &ObjectID,
1133 receiving_object_id: &ObjectID,
1134 receive_object_at_version: SequenceNumber,
1135 epoch_id: EpochId,
1136 ) -> SuiResult<Option<Object>> {
1137 debug_assert!(
1140 !self
1141 .execution_results
1142 .written_objects
1143 .contains_key(receiving_object_id)
1144 );
1145 debug_assert!(
1146 !self
1147 .execution_results
1148 .deleted_object_ids
1149 .contains(receiving_object_id)
1150 );
1151 self.store.get_object_received_at_version(
1152 owner,
1153 receiving_object_id,
1154 receive_object_at_version,
1155 epoch_id,
1156 )
1157 }
1158}
1159
1160fn was_object_mutated(object: &Object, original: &Object) -> bool {
1163 let data_equal = match (&object.data, &original.data) {
1164 (Data::Move(a), Data::Move(b)) => a.contents_and_type_equal(b),
1165 (Data::Package(a), Data::Package(b)) => a == b,
1168 _ => false,
1169 };
1170
1171 let owner_equal = match (&object.owner, &original.owner) {
1172 (Owner::Shared { .. }, Owner::Shared { .. }) => true,
1176 (
1177 Owner::ConsensusAddressOwner { owner: a, .. },
1178 Owner::ConsensusAddressOwner { owner: b, .. },
1179 ) => a == b,
1180 (Owner::AddressOwner(a), Owner::AddressOwner(b)) => a == b,
1181 (Owner::Immutable, Owner::Immutable) => true,
1182 (Owner::ObjectOwner(a), Owner::ObjectOwner(b)) => a == b,
1183
1184 (Owner::AddressOwner(_), _)
1187 | (Owner::Immutable, _)
1188 | (Owner::ObjectOwner(_), _)
1189 | (Owner::Shared { .. }, _)
1190 | (Owner::ConsensusAddressOwner { .. }, _) => false,
1191 (Owner::Party { .. }, _) => {
1192 unimplemented!("Party does not exist for this execution version")
1193 }
1194 };
1195
1196 !data_equal || !owner_equal
1197}
1198
1199impl Storage for TemporaryStore<'_> {
1200 fn reset(&mut self) {
1201 self.drop_writes();
1202 }
1203
1204 fn read_object(&self, id: &ObjectID) -> Option<&Object> {
1205 TemporaryStore::read_object(self, id)
1206 }
1207
1208 fn record_execution_results(
1210 &mut self,
1211 results: ExecutionResults,
1212 ) -> Result<(), ExecutionError> {
1213 let ExecutionResults::V2(mut results) = results else {
1214 panic!("ExecutionResults::V2 expected in sui-execution v1 and above");
1215 };
1216
1217 let mut to_remove = Vec::new();
1219 for (id, original) in &self.non_exclusive_input_original_versions {
1220 if results
1222 .written_objects
1223 .get(id)
1224 .map(|obj| was_object_mutated(obj, original))
1225 .unwrap_or(true)
1226 {
1227 return Err(ExecutionError::new_with_source(
1228 ExecutionErrorKind::NonExclusiveWriteInputObjectModified { id: *id },
1229 "Non-exclusive write input object has been modified or deleted",
1230 ));
1231 }
1232 to_remove.push(*id);
1233 }
1234
1235 for id in to_remove {
1236 results.written_objects.remove(&id);
1237 results.modified_objects.remove(&id);
1238 }
1239
1240 self.execution_results
1243 .merge_results(
1244 results, false, false,
1245 )
1246 .unwrap();
1247
1248 Ok(())
1249 }
1250
1251 fn save_loaded_runtime_objects(
1252 &mut self,
1253 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
1254 ) {
1255 TemporaryStore::save_loaded_runtime_objects(self, loaded_runtime_objects)
1256 }
1257
1258 fn save_wrapped_object_containers(
1259 &mut self,
1260 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
1261 ) {
1262 TemporaryStore::save_wrapped_object_containers(self, wrapped_object_containers)
1263 }
1264
1265 fn check_coin_deny_list(
1266 &self,
1267 receiving_funds_type_and_owners: BTreeMap<TypeTag, BTreeSet<SuiAddress>>,
1268 ) -> DenyListResult {
1269 let result = check_coin_deny_list_v2_during_execution(
1270 receiving_funds_type_and_owners,
1271 self.cur_epoch,
1272 self.store.as_object_store(),
1273 );
1274 if result.num_non_gas_coin_owners > 0
1277 && !self.input_objects.contains_key(&SUI_DENY_LIST_OBJECT_ID)
1278 {
1279 self.loaded_per_epoch_config_objects
1280 .write()
1281 .insert(SUI_DENY_LIST_OBJECT_ID);
1282 }
1283 result
1284 }
1285
1286 fn record_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
1287 TemporaryStore::save_generated_object_ids(self, generated_ids)
1288 }
1289}
1290
1291impl BackingPackageStore for TemporaryStore<'_> {
1292 fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
1293 if let Some(obj) = self.execution_results.written_objects.get(package_id) {
1300 Ok(Some(PackageObject::new(obj.clone())))
1301 } else {
1302 self.store.get_package_object(package_id).inspect(|obj| {
1303 if let Some(v) = obj
1305 && !self
1306 .runtime_packages_loaded_from_db
1307 .read()
1308 .contains_key(package_id)
1309 {
1310 self.runtime_packages_loaded_from_db
1315 .write()
1316 .insert(*package_id, v.clone());
1317 }
1318 })
1319 }
1320 }
1321}
1322
1323impl ParentSync for TemporaryStore<'_> {
1324 fn get_latest_parent_entry_ref_deprecated(&self, _object_id: ObjectID) -> Option<ObjectRef> {
1325 unreachable!("Never called in newer protocol versions")
1326 }
1327}