1use crate::execution_mode::ExecutionMode;
5use crate::gas_charger::{GasCharger, PaymentLocation};
6use move_vm_runtime::runtime::MoveRuntime;
7use mysten_common::ZipDebugEqIteratorExt;
8use mysten_metrics::monitored_scope;
9use parking_lot::RwLock;
10use std::collections::{BTreeMap, BTreeSet, HashSet};
11use std::sync::Arc;
12use sui_protocol_config::ProtocolConfig;
13use sui_types::accumulator_event::AccumulatorEvent;
14use sui_types::accumulator_root::AccumulatorObjId;
15use sui_types::base_types::VersionDigest;
16use sui_types::committee::EpochId;
17use sui_types::deny_list_v2::check_coin_deny_list_v2_during_execution;
18use sui_types::effects::{
19 AccumulatorOperation, AccumulatorValue, AccumulatorWriteV1, TransactionEffects,
20 TransactionEffectsV2, TransactionEvents,
21};
22use sui_types::execution::{
23 DynamicallyLoadedObjectMetadata, ExecutionResults, ExecutionResultsV2, SharedInput,
24};
25use sui_types::execution_status::{ExecutionErrorKind, ExecutionStatus};
26use sui_types::inner_temporary_store::InnerTemporaryStore;
27use sui_types::object::Data;
28use sui_types::storage::{BackingStore, DenyListResult, PackageObject};
29use sui_types::sui_system_state::{AdvanceEpochParams, get_sui_system_state_wrapper};
30use sui_types::transaction::{GasData, TransactionKind};
31use sui_types::{
32 SUI_DENY_LIST_OBJECT_ID,
33 base_types::{ObjectID, ObjectRef, SequenceNumber, SuiAddress, TransactionDigest},
34 effects::EffectsObjectChange,
35 error::{ExecutionError, SuiResult},
36 gas::GasCostSummary,
37 object::Object,
38 object::Owner,
39 storage::{BackingPackageStore, ChildObjectResolver, Storage},
40 transaction::InputObjects,
41};
42use sui_types::{SUI_SYSTEM_STATE_OBJECT_ID, TypeTag, is_system_package};
43
44pub(crate) mod invariants;
45use invariants::InvariantChecker;
46
47pub struct TemporaryStore<'backing> {
48 store: &'backing dyn BackingStore,
54 tx_digest: TransactionDigest,
55 input_objects: BTreeMap<ObjectID, Object>,
56
57 non_exclusive_input_original_versions: BTreeMap<ObjectID, Object>,
60
61 stream_ended_consensus_objects: BTreeMap<ObjectID, SequenceNumber >,
62 lamport_timestamp: SequenceNumber,
64 mutable_input_refs: BTreeMap<ObjectID, (VersionDigest, Owner)>,
67 execution_results: ExecutionResultsV2,
68 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
70 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
72 protocol_config: &'backing ProtocolConfig,
73
74 runtime_packages_loaded_from_db: RwLock<BTreeMap<ObjectID, PackageObject>>,
77
78 receiving_objects: Vec<ObjectRef>,
81
82 generated_runtime_ids: BTreeSet<ObjectID>,
86
87 cur_epoch: EpochId,
90
91 loaded_per_epoch_config_objects: RwLock<BTreeSet<ObjectID>>,
94
95 invariants: InvariantChecker,
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 invariants: InvariantChecker::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(crate) 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 unchanged_consensus_objects = TransactionEffectsV2::compute_unchanged_consensus_objects(
447 shared_object_refs,
448 loaded_per_epoch_config_objects,
449 &object_changes,
450 );
451 let inner = self.into_inner(accumulator_running_max_withdraws);
452
453 let effects = TransactionEffects::new_from_execution_v2(
454 status,
455 epoch,
456 gas_cost_summary,
457 unchanged_consensus_objects,
458 *transaction_digest,
459 lamport_version,
460 object_changes,
461 gas_coin,
462 if inner.events.data.is_empty() {
463 None
464 } else {
465 Some(inner.events.digest())
466 },
467 transaction_dependencies.into_iter().collect(),
468 );
469
470 (inner, effects)
471 }
472
473 #[cfg(debug_assertions)]
475 fn check_invariants(&self) {
476 debug_assert!(
478 {
479 self.execution_results
480 .written_objects
481 .keys()
482 .all(|id| !self.execution_results.deleted_object_ids.contains(id))
483 },
484 "Object both written and deleted."
485 );
486
487 debug_assert!(
489 {
490 self.mutable_input_refs
491 .keys()
492 .all(|id| self.execution_results.modified_objects.contains(id))
493 },
494 "Mutable input not modified."
495 );
496
497 debug_assert!(
498 {
499 self.execution_results
500 .written_objects
501 .values()
502 .all(|obj| obj.previous_transaction == self.tx_digest)
503 },
504 "Object previous transaction not properly set",
505 );
506 }
507
508 pub fn mutate_input_object(&mut self, object: Object) {
510 let id = object.id();
511 debug_assert!(self.input_objects.contains_key(&id));
512 debug_assert!(!object.is_immutable());
513 self.execution_results.modified_objects.insert(id);
514 self.execution_results.written_objects.insert(id, object);
515 }
516
517 pub fn mutate_new_or_input_object(&mut self, object: Object) {
518 let id = object.id();
519 debug_assert!(!object.is_immutable());
520 if self.input_objects.contains_key(&id) {
521 self.execution_results.modified_objects.insert(id);
522 }
523 self.execution_results.written_objects.insert(id, object);
524 }
525
526 pub fn mutate_child_object(&mut self, old_object: Object, new_object: Object) {
530 let id = new_object.id();
531 let old_ref = old_object.compute_object_reference();
532 debug_assert_eq!(old_ref.0, id);
533 self.loaded_runtime_objects.insert(
534 id,
535 DynamicallyLoadedObjectMetadata {
536 version: old_ref.1,
537 digest: old_ref.2,
538 owner: old_object.owner.clone(),
539 storage_rebate: old_object.storage_rebate,
540 previous_transaction: old_object.previous_transaction,
541 },
542 );
543 self.execution_results.modified_objects.insert(id);
544 self.execution_results
545 .written_objects
546 .insert(id, new_object);
547 }
548
549 pub fn upgrade_system_package(&mut self, package: Object) {
553 let id = package.id();
554 assert!(package.is_package() && is_system_package(id));
555 self.execution_results.modified_objects.insert(id);
556 self.execution_results.written_objects.insert(id, package);
557 }
558
559 pub fn create_object(&mut self, object: Object) {
561 debug_assert!(
566 object.is_immutable() || object.version() == SequenceNumber::MIN,
567 "Created mutable objects should not have a version set",
568 );
569 let id = object.id();
570 self.execution_results.created_object_ids.insert(id);
571 self.execution_results.written_objects.insert(id, object);
572 }
573
574 pub fn delete_input_object(&mut self, id: &ObjectID) {
576 debug_assert!(!self.execution_results.written_objects.contains_key(id));
578 debug_assert!(self.input_objects.contains_key(id));
579 self.execution_results.modified_objects.insert(*id);
580 self.execution_results.deleted_object_ids.insert(*id);
581 }
582
583 pub fn drop_writes(&mut self) {
584 self.execution_results.drop_writes();
585 self.invariants.clear();
587 }
588
589 pub fn read_object(&self, id: &ObjectID) -> Option<&Object> {
590 debug_assert!(!self.execution_results.deleted_object_ids.contains(id));
592 self.execution_results
593 .written_objects
594 .get(id)
595 .or_else(|| self.input_objects.get(id))
596 }
597
598 pub fn save_loaded_runtime_objects(
599 &mut self,
600 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
601 ) {
602 #[cfg(debug_assertions)]
603 {
604 for (id, v1) in &loaded_runtime_objects {
605 if let Some(v2) = self.loaded_runtime_objects.get(id) {
606 assert_eq!(v1, v2);
607 }
608 }
609 for (id, v1) in &self.loaded_runtime_objects {
610 if let Some(v2) = loaded_runtime_objects.get(id) {
611 assert_eq!(v1, v2);
612 }
613 }
614 }
615 self.loaded_runtime_objects.extend(loaded_runtime_objects);
618 }
619
620 pub fn save_wrapped_object_containers(
621 &mut self,
622 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
623 ) {
624 #[cfg(debug_assertions)]
625 {
626 for (id, container1) in &wrapped_object_containers {
627 if let Some(container2) = self.wrapped_object_containers.get(id) {
628 assert_eq!(container1, container2);
629 }
630 }
631 for (id, container1) in &self.wrapped_object_containers {
632 if let Some(container2) = wrapped_object_containers.get(id) {
633 assert_eq!(container1, container2);
634 }
635 }
636 }
637 self.wrapped_object_containers
640 .extend(wrapped_object_containers);
641 }
642
643 pub fn save_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
644 #[cfg(debug_assertions)]
645 {
646 for id in &self.generated_runtime_ids {
647 assert!(!generated_ids.contains(id))
648 }
649 for id in &generated_ids {
650 assert!(!self.generated_runtime_ids.contains(id));
651 }
652 }
653 self.generated_runtime_ids.extend(generated_ids);
654 }
655
656 pub fn estimate_effects_size_upperbound(&self) -> usize {
657 TransactionEffects::estimate_effects_size_upperbound_v2(
658 self.execution_results.written_objects.len(),
659 self.execution_results.modified_objects.len(),
660 self.input_objects.len(),
661 )
662 }
663
664 pub fn written_objects_size(&self) -> usize {
665 self.execution_results
666 .written_objects
667 .values()
668 .fold(0, |sum, obj| sum + obj.object_size_for_gas_metering())
669 }
670
671 pub fn check_gasless_execution_requirements(
677 &self,
678 withdrawal_reservations: Option<&BTreeMap<(SuiAddress, TypeTag), u64>>,
679 ) -> Result<(), String> {
680 if !self.execution_results.written_objects.is_empty() {
681 return Err("Gasless transactions cannot create or mutate objects".to_string());
682 }
683
684 let input_coin_ids: BTreeSet<ObjectID> = self
685 .input_objects
686 .iter()
687 .filter(|(_, obj)| obj.coin_type_maybe().is_some())
688 .map(|(id, _)| *id)
689 .collect();
690 if self.execution_results.deleted_object_ids != input_coin_ids {
691 return Err(format!(
692 "Gasless transaction must destroy exactly its input Coins. \
693 Expected: {input_coin_ids:?}, deleted: {:?}",
694 self.execution_results.deleted_object_ids
695 ));
696 }
697
698 let allowed_types =
699 sui_types::transaction::get_gasless_allowed_token_types(self.protocol_config);
700
701 let net_totals = sui_types::balance_change::signed_balance_changes_from_events(
704 &self.execution_results.accumulator_events,
705 )
706 .fold(
707 BTreeMap::<(SuiAddress, TypeTag), i128>::new(),
708 |mut totals, (address, token_type, signed_amount)| {
709 *totals.entry((address, token_type)).or_default() += signed_amount;
710 totals
711 },
712 );
713
714 for ((recipient, token_type), net_amount) in &net_totals {
715 if *net_amount <= 0 {
716 continue;
717 }
718 if let Some(&min_amount) = allowed_types.get(token_type)
719 && *net_amount < i128::from(min_amount)
720 {
721 return Err(format!(
722 "Gasless transfer of {net_amount} to {recipient} is below \
723 minimum {min_amount} for token type {token_type}"
724 ));
725 }
726 }
727
728 if let Some(reservations) = withdrawal_reservations {
729 for ((owner, token_type), &reserved) in reservations {
730 let net = net_totals
731 .get(&(*owner, token_type.clone()))
732 .copied()
733 .unwrap_or(0);
734 let remaining = (reserved as i128).saturating_add(net);
735 if remaining > 0
736 && let Some(&min_balance_remaining) = allowed_types.get(token_type)
737 && min_balance_remaining > 0
738 && remaining < min_balance_remaining as i128
739 {
740 return Err(format!(
741 "Gasless withdrawal leaves {remaining} unused for {owner}, \
742 below minimum {min_balance_remaining} for token type {token_type}"
743 ));
744 }
745 }
746 }
747
748 Ok(())
749 }
750
751 pub fn conserve_unmetered_storage_rebate(&mut self, unmetered_storage_rebate: u64) {
756 if unmetered_storage_rebate == 0 {
757 return;
761 }
762 tracing::debug!(
763 "Amount of unmetered storage rebate from system tx: {:?}",
764 unmetered_storage_rebate
765 );
766 let mut system_state_wrapper = self
767 .read_object(&SUI_SYSTEM_STATE_OBJECT_ID)
768 .expect("0x5 object must be mutated in system tx with unmetered storage rebate")
769 .clone();
770 assert_eq!(system_state_wrapper.storage_rebate, 0);
773 system_state_wrapper.storage_rebate = unmetered_storage_rebate;
774 self.mutate_input_object(system_state_wrapper);
775 }
776
777 pub fn add_accumulator_event(&mut self, event: AccumulatorEvent) {
779 self.execution_results.accumulator_events.push(event);
780 }
781
782 fn get_object_modified_at(
788 &self,
789 object_id: &ObjectID,
790 ) -> Option<DynamicallyLoadedObjectMetadata> {
791 if self.execution_results.modified_objects.contains(object_id) {
792 Some(
793 self.mutable_input_refs
794 .get(object_id)
795 .map(
796 |((version, digest), owner)| DynamicallyLoadedObjectMetadata {
797 version: *version,
798 digest: *digest,
799 owner: owner.clone(),
800 storage_rebate: self.input_objects[object_id].storage_rebate,
802 previous_transaction: self.input_objects[object_id]
803 .previous_transaction,
804 },
805 )
806 .or_else(|| self.loaded_runtime_objects.get(object_id).cloned())
807 .unwrap_or_else(|| {
808 debug_assert!(is_system_package(*object_id));
809 let package_obj =
810 self.store.get_package_object(object_id).unwrap().unwrap();
811 let obj = package_obj.object();
812 DynamicallyLoadedObjectMetadata {
813 version: obj.version(),
814 digest: obj.digest(),
815 owner: obj.owner.clone(),
816 storage_rebate: obj.storage_rebate,
817 previous_transaction: obj.previous_transaction,
818 }
819 }),
820 )
821 } else {
822 None
823 }
824 }
825
826 pub fn protocol_config(&self) -> &'backing ProtocolConfig {
827 self.protocol_config
828 }
829
830 pub(crate) fn set_invariant_inputs(
835 &mut self,
836 transaction_kind: &TransactionKind,
837 gas_data: &GasData,
838 transaction_signer: SuiAddress,
839 ) {
840 self.invariants
841 .set_transaction_inputs(transaction_kind, gas_data, transaction_signer);
842 }
843
844 pub(crate) fn check_conservation_invariants<Mode: ExecutionMode>(
847 &self,
848 move_vm: &Arc<MoveRuntime>,
849 enable_expensive_checks: bool,
850 cost_summary: &GasCostSummary,
851 ) -> Result<(), ExecutionError> {
852 self.invariants.check_conservation_invariants::<Mode>(
853 self,
854 move_vm,
855 enable_expensive_checks,
856 cost_summary,
857 )
858 }
859
860 pub(crate) fn check_ownership_invariants(
863 &self,
864 sender: &SuiAddress,
865 sponsor: &Option<SuiAddress>,
866 gas_charger: &GasCharger,
867 mutable_inputs: &HashSet<ObjectID>,
868 is_epoch_change: bool,
869 ) -> SuiResult<()> {
870 self.invariants.check_ownership_invariants(
871 self,
872 sender,
873 sponsor,
874 gas_charger,
875 mutable_inputs,
876 is_epoch_change,
877 )
878 }
879}
880
881impl TemporaryStore<'_> {
882 pub(crate) fn collect_storage_and_rebate(&mut self, gas_charger: &mut GasCharger) {
889 let old_storage_rebates: Vec<_> = self
891 .execution_results
892 .written_objects
893 .keys()
894 .map(|object_id| {
895 self.get_object_modified_at(object_id)
896 .map(|metadata| metadata.storage_rebate)
897 .unwrap_or_default()
898 })
899 .collect();
900 for (object, old_storage_rebate) in self
901 .execution_results
902 .written_objects
903 .values_mut()
904 .zip_debug_eq(old_storage_rebates)
905 {
906 let new_object_size = object.object_size_for_gas_metering();
908 let new_storage_rebate = gas_charger.track_storage_mutation(
910 object.id(),
911 new_object_size,
912 old_storage_rebate,
913 );
914 object.storage_rebate = new_storage_rebate;
915 }
916
917 self.collect_rebate(gas_charger);
918 }
919
920 pub(crate) fn collect_rebate(&self, gas_charger: &mut GasCharger) {
921 for object_id in &self.execution_results.modified_objects {
922 if self
923 .execution_results
924 .written_objects
925 .contains_key(object_id)
926 {
927 continue;
928 }
929 let storage_rebate = self
931 .get_object_modified_at(object_id)
932 .unwrap()
934 .storage_rebate;
935 gas_charger.track_storage_mutation(*object_id, 0, storage_rebate);
936 }
937 }
938
939 pub fn check_execution_results_consistency<Mode: ExecutionMode>(
940 &self,
941 ) -> Result<(), Mode::Error> {
942 assert_invariant!(
943 self.execution_results
944 .created_object_ids
945 .iter()
946 .all(|id| !self.execution_results.deleted_object_ids.contains(id)
947 && !self.execution_results.modified_objects.contains(id)),
948 "Created object IDs cannot also be deleted or modified"
949 );
950 assert_invariant!(
951 self.execution_results.modified_objects.iter().all(|id| {
952 self.mutable_input_refs.contains_key(id)
953 || self.loaded_runtime_objects.contains_key(id)
954 || is_system_package(*id)
955 }),
956 "A modified object must be either a mutable input, a loaded child object, or a system package"
957 );
958 Ok(())
959 }
960}
961impl TemporaryStore<'_> {
966 pub fn advance_epoch_safe_mode(
967 &mut self,
968 params: &AdvanceEpochParams,
969 protocol_config: &ProtocolConfig,
970 ) {
971 let wrapper = get_sui_system_state_wrapper(self.store.as_object_store())
972 .expect("System state wrapper object must exist");
973 let (old_object, new_object) =
974 wrapper.advance_epoch_safe_mode(params, self.store.as_object_store(), protocol_config);
975 self.mutate_child_object(old_object, new_object);
976 }
977}
978
979impl ChildObjectResolver for TemporaryStore<'_> {
980 fn read_child_object(
981 &self,
982 parent: &ObjectID,
983 child: &ObjectID,
984 child_version_upper_bound: SequenceNumber,
985 ) -> SuiResult<Option<Object>> {
986 let obj_opt = self.execution_results.written_objects.get(child);
987 if obj_opt.is_some() {
988 Ok(obj_opt.cloned())
989 } else {
990 let _scope = monitored_scope("Execution::read_child_object");
991 self.store
992 .read_child_object(parent, child, child_version_upper_bound)
993 }
994 }
995
996 fn get_object_received_at_version(
997 &self,
998 owner: &ObjectID,
999 receiving_object_id: &ObjectID,
1000 receive_object_at_version: SequenceNumber,
1001 epoch_id: EpochId,
1002 ) -> SuiResult<Option<Object>> {
1003 debug_assert!(
1006 !self
1007 .execution_results
1008 .written_objects
1009 .contains_key(receiving_object_id)
1010 );
1011 debug_assert!(
1012 !self
1013 .execution_results
1014 .deleted_object_ids
1015 .contains(receiving_object_id)
1016 );
1017 self.store.get_object_received_at_version(
1018 owner,
1019 receiving_object_id,
1020 receive_object_at_version,
1021 epoch_id,
1022 )
1023 }
1024}
1025
1026fn was_object_mutated(object: &Object, original: &Object) -> bool {
1029 let data_equal = match (&object.data, &original.data) {
1030 (Data::Move(a), Data::Move(b)) => a.contents_and_type_equal(b),
1031 (Data::Package(a), Data::Package(b)) => a == b,
1034 _ => false,
1035 };
1036
1037 let owner_equal = match (&object.owner, &original.owner) {
1038 (Owner::Shared { .. }, Owner::Shared { .. }) => true,
1042 (
1043 Owner::ConsensusAddressOwner { owner: a, .. },
1044 Owner::ConsensusAddressOwner { owner: b, .. },
1045 ) => a == b,
1046 (Owner::AddressOwner(a), Owner::AddressOwner(b)) => a == b,
1047 (Owner::Immutable, Owner::Immutable) => true,
1048 (Owner::ObjectOwner(a), Owner::ObjectOwner(b)) => a == b,
1049 (
1050 Owner::Party {
1051 permissions: a,
1052 start_version: _,
1053 },
1054 Owner::Party {
1055 permissions: b,
1056 start_version: _,
1057 },
1058 ) => a == b,
1059
1060 (Owner::AddressOwner(_), _)
1063 | (Owner::Immutable, _)
1064 | (Owner::ObjectOwner(_), _)
1065 | (Owner::Shared { .. }, _)
1066 | (Owner::ConsensusAddressOwner { .. }, _)
1067 | (Owner::Party { .. }, _) => false,
1068 };
1069
1070 !data_equal || !owner_equal
1071}
1072
1073impl Storage for TemporaryStore<'_> {
1074 fn reset(&mut self) {
1075 self.drop_writes();
1076 }
1077
1078 fn read_object(&self, id: &ObjectID) -> Option<&Object> {
1079 TemporaryStore::read_object(self, id)
1080 }
1081
1082 fn record_execution_results(
1084 &mut self,
1085 results: ExecutionResults,
1086 ) -> Result<(), ExecutionError> {
1087 let ExecutionResults::V2(mut results) = results else {
1088 panic!("ExecutionResults::V2 expected in sui-execution v1 and above");
1089 };
1090
1091 let mut to_remove = Vec::new();
1093 for (id, original) in &self.non_exclusive_input_original_versions {
1094 if results
1096 .written_objects
1097 .get(id)
1098 .map(|obj| was_object_mutated(obj, original))
1099 .unwrap_or(true)
1100 {
1101 return Err(ExecutionError::new_with_source(
1102 ExecutionErrorKind::NonExclusiveWriteInputObjectModified { id: *id },
1103 "Non-exclusive write input object has been modified or deleted",
1104 ));
1105 }
1106 to_remove.push(*id);
1107 }
1108
1109 for id in to_remove {
1110 results.written_objects.remove(&id);
1111 results.modified_objects.remove(&id);
1112 }
1113
1114 let event_start = self.execution_results.accumulator_events.len();
1120 self.execution_results.merge_results(
1121 results, true, true,
1122 )?;
1123 let event_end = self.execution_results.accumulator_events.len();
1124 self.invariants
1125 .record_ptb_event_range(event_start, event_end);
1126
1127 Ok(())
1128 }
1129
1130 fn save_loaded_runtime_objects(
1131 &mut self,
1132 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
1133 ) {
1134 TemporaryStore::save_loaded_runtime_objects(self, loaded_runtime_objects)
1135 }
1136
1137 fn save_wrapped_object_containers(
1138 &mut self,
1139 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
1140 ) {
1141 TemporaryStore::save_wrapped_object_containers(self, wrapped_object_containers)
1142 }
1143
1144 fn check_coin_deny_list(
1145 &self,
1146 receiving_funds_type_and_owners: BTreeMap<TypeTag, BTreeSet<SuiAddress>>,
1147 ) -> DenyListResult {
1148 let result = check_coin_deny_list_v2_during_execution(
1149 receiving_funds_type_and_owners,
1150 self.cur_epoch,
1151 self.store.as_object_store(),
1152 );
1153 if result.num_non_gas_coin_owners > 0
1156 && !self.input_objects.contains_key(&SUI_DENY_LIST_OBJECT_ID)
1157 {
1158 self.loaded_per_epoch_config_objects
1159 .write()
1160 .insert(SUI_DENY_LIST_OBJECT_ID);
1161 }
1162 result
1163 }
1164
1165 fn record_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
1166 TemporaryStore::save_generated_object_ids(self, generated_ids)
1167 }
1168}
1169
1170impl BackingPackageStore for TemporaryStore<'_> {
1171 fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
1172 if let Some(obj) = self.execution_results.written_objects.get(package_id) {
1179 Ok(Some(PackageObject::new(obj.clone())))
1180 } else {
1181 self.store.get_package_object(package_id).inspect(|obj| {
1182 if let Some(v) = obj
1184 && !self
1185 .runtime_packages_loaded_from_db
1186 .read()
1187 .contains_key(package_id)
1188 {
1189 self.runtime_packages_loaded_from_db
1194 .write()
1195 .insert(*package_id, v.clone());
1196 }
1197 })
1198 }
1199 }
1200}