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 fn merge_accumulator_events(&mut self) {
208 self.execution_results.accumulator_events = self
209 .execution_results
210 .accumulator_events
211 .iter()
212 .fold(
213 BTreeMap::<AccumulatorObjId, Vec<AccumulatorWriteV1>>::new(),
214 |mut map, event| {
215 map.entry(event.accumulator_obj)
216 .or_default()
217 .push(event.write.clone());
218 map
219 },
220 )
221 .into_iter()
222 .map(|(obj_id, writes)| {
223 AccumulatorEvent::new(obj_id, AccumulatorWriteV1::merge(writes))
224 })
225 .collect();
226 }
227
228 pub fn into_inner(
230 self,
231 accumulator_running_max_withdraws: BTreeMap<AccumulatorObjId, u128>,
232 ) -> InnerTemporaryStore {
233 let results = self.execution_results;
234 InnerTemporaryStore {
235 input_objects: self.input_objects,
236 stream_ended_consensus_objects: self.stream_ended_consensus_objects,
237 mutable_inputs: self.mutable_input_refs,
238 written: results.written_objects,
239 events: TransactionEvents {
240 data: results.user_events,
241 },
242 accumulator_events: results.accumulator_events,
243 loaded_runtime_objects: self.loaded_runtime_objects,
244 runtime_packages_loaded_from_db: self.runtime_packages_loaded_from_db.into_inner(),
245 lamport_version: self.lamport_timestamp,
246 binary_config: self.protocol_config.binary_config(None),
247 accumulator_running_max_withdraws,
248 }
249 }
250
251 pub(crate) fn ensure_active_inputs_mutated(&mut self) {
255 let mut to_be_updated = vec![];
256 for id in self.mutable_input_refs.keys() {
258 if !self.execution_results.modified_objects.contains(id) {
259 to_be_updated.push(self.input_objects[id].clone());
263 }
264 }
265 for object in to_be_updated {
266 self.mutate_input_object(object.clone());
268 }
269 }
270
271 fn get_object_changes(&self) -> BTreeMap<ObjectID, EffectsObjectChange> {
272 let results = &self.execution_results;
273 let all_ids = results
274 .created_object_ids
275 .iter()
276 .chain(&results.deleted_object_ids)
277 .chain(&results.modified_objects)
278 .chain(results.written_objects.keys())
279 .collect::<BTreeSet<_>>();
280 all_ids
281 .into_iter()
282 .map(|id| {
283 (
284 *id,
285 EffectsObjectChange::new(
286 self.get_object_modified_at(id)
287 .map(|metadata| ((metadata.version, metadata.digest), metadata.owner)),
288 results.written_objects.get(id),
289 results.created_object_ids.contains(id),
290 results.deleted_object_ids.contains(id),
291 ),
292 )
293 })
294 .chain(results.accumulator_events.iter().cloned().map(
295 |AccumulatorEvent {
296 accumulator_obj,
297 write,
298 }| {
299 (
300 *accumulator_obj.inner(),
301 EffectsObjectChange::new_from_accumulator_write(write),
302 )
303 },
304 ))
305 .collect()
306 }
307
308 pub fn into_effects(
309 mut self,
310 shared_object_refs: Vec<SharedInput>,
311 transaction_digest: &TransactionDigest,
312 mut transaction_dependencies: BTreeSet<TransactionDigest>,
313 gas_cost_summary: GasCostSummary,
314 status: ExecutionStatus,
315 gas_charger: &mut GasCharger,
316 epoch: EpochId,
317 ) -> (InnerTemporaryStore, TransactionEffects) {
318 for (id, obj) in &self.execution_results.written_objects {
321 assert!(
322 !matches!(obj.owner, Owner::Party { .. }),
323 "Party-owned objects are not yet supported (object {id})"
324 );
325 }
326
327 self.update_object_version_and_prev_tx();
328 let accumulator_running_max_withdraws = self.calculate_accumulator_running_max_withdraws();
330 self.merge_accumulator_events();
331
332 for (id, expected_version, expected_digest) in &self.receiving_objects {
335 if let Some(obj_meta) = self.loaded_runtime_objects.get(id) {
339 let loaded_via_receive = obj_meta.version == *expected_version
343 && obj_meta.digest == *expected_digest
344 && obj_meta.owner.is_address_owned();
345 if loaded_via_receive {
346 transaction_dependencies.insert(obj_meta.previous_transaction);
347 }
348 }
349 }
350
351 assert!(self.protocol_config.enable_effects_v2());
352
353 let gas_coin = gas_charger
358 .gas_payment_amount()
359 .and_then(|gp| match gp.location {
360 PaymentLocation::Coin(coin_id) => Some(coin_id),
361 PaymentLocation::AddressBalance(_) => None,
362 });
363
364 let object_changes = self.get_object_changes();
365
366 let lamport_version = self.lamport_timestamp;
367 let loaded_per_epoch_config_objects = self.loaded_per_epoch_config_objects.read().clone();
369 let inner = self.into_inner(accumulator_running_max_withdraws);
370
371 let effects = TransactionEffects::new_from_execution_v2(
372 status,
373 epoch,
374 gas_cost_summary,
375 shared_object_refs,
377 loaded_per_epoch_config_objects,
378 *transaction_digest,
379 lamport_version,
380 object_changes,
381 gas_coin,
382 if inner.events.data.is_empty() {
383 None
384 } else {
385 Some(inner.events.digest())
386 },
387 transaction_dependencies.into_iter().collect(),
388 );
389
390 (inner, effects)
391 }
392
393 #[cfg(debug_assertions)]
395 fn check_invariants(&self) {
396 debug_assert!(
398 {
399 self.execution_results
400 .written_objects
401 .keys()
402 .all(|id| !self.execution_results.deleted_object_ids.contains(id))
403 },
404 "Object both written and deleted."
405 );
406
407 debug_assert!(
409 {
410 self.mutable_input_refs
411 .keys()
412 .all(|id| self.execution_results.modified_objects.contains(id))
413 },
414 "Mutable input not modified."
415 );
416
417 debug_assert!(
418 {
419 self.execution_results
420 .written_objects
421 .values()
422 .all(|obj| obj.previous_transaction == self.tx_digest)
423 },
424 "Object previous transaction not properly set",
425 );
426 }
427
428 pub fn mutate_input_object(&mut self, object: Object) {
430 let id = object.id();
431 debug_assert!(self.input_objects.contains_key(&id));
432 debug_assert!(!object.is_immutable());
433 self.execution_results.modified_objects.insert(id);
434 self.execution_results.written_objects.insert(id, object);
435 }
436
437 pub fn mutate_new_or_input_object(&mut self, object: Object) {
438 let id = object.id();
439 debug_assert!(!object.is_immutable());
440 if self.input_objects.contains_key(&id) {
441 self.execution_results.modified_objects.insert(id);
442 }
443 self.execution_results.written_objects.insert(id, object);
444 }
445
446 pub fn mutate_child_object(&mut self, old_object: Object, new_object: Object) {
450 let id = new_object.id();
451 let old_ref = old_object.compute_object_reference();
452 debug_assert_eq!(old_ref.0, id);
453 self.loaded_runtime_objects.insert(
454 id,
455 DynamicallyLoadedObjectMetadata {
456 version: old_ref.1,
457 digest: old_ref.2,
458 owner: old_object.owner.clone(),
459 storage_rebate: old_object.storage_rebate,
460 previous_transaction: old_object.previous_transaction,
461 },
462 );
463 self.execution_results.modified_objects.insert(id);
464 self.execution_results
465 .written_objects
466 .insert(id, new_object);
467 }
468
469 pub fn upgrade_system_package(&mut self, package: Object) {
473 let id = package.id();
474 assert!(package.is_package() && is_system_package(id));
475 self.execution_results.modified_objects.insert(id);
476 self.execution_results.written_objects.insert(id, package);
477 }
478
479 pub fn create_object(&mut self, object: Object) {
481 debug_assert!(
486 object.is_immutable() || object.version() == SequenceNumber::MIN,
487 "Created mutable objects should not have a version set",
488 );
489 let id = object.id();
490 self.execution_results.created_object_ids.insert(id);
491 self.execution_results.written_objects.insert(id, object);
492 }
493
494 pub fn delete_input_object(&mut self, id: &ObjectID) {
496 debug_assert!(!self.execution_results.written_objects.contains_key(id));
498 debug_assert!(self.input_objects.contains_key(id));
499 self.execution_results.modified_objects.insert(*id);
500 self.execution_results.deleted_object_ids.insert(*id);
501 }
502
503 pub fn drop_writes(&mut self) {
504 self.execution_results.drop_writes();
505 self.ptb_emitted_accumulator_event_ranges.clear();
507 }
508
509 pub fn read_object(&self, id: &ObjectID) -> Option<&Object> {
510 debug_assert!(!self.execution_results.deleted_object_ids.contains(id));
512 self.execution_results
513 .written_objects
514 .get(id)
515 .or_else(|| self.input_objects.get(id))
516 }
517
518 pub fn save_loaded_runtime_objects(
519 &mut self,
520 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
521 ) {
522 #[cfg(debug_assertions)]
523 {
524 for (id, v1) in &loaded_runtime_objects {
525 if let Some(v2) = self.loaded_runtime_objects.get(id) {
526 assert_eq!(v1, v2);
527 }
528 }
529 for (id, v1) in &self.loaded_runtime_objects {
530 if let Some(v2) = loaded_runtime_objects.get(id) {
531 assert_eq!(v1, v2);
532 }
533 }
534 }
535 self.loaded_runtime_objects.extend(loaded_runtime_objects);
538 }
539
540 pub fn save_wrapped_object_containers(
541 &mut self,
542 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
543 ) {
544 #[cfg(debug_assertions)]
545 {
546 for (id, container1) in &wrapped_object_containers {
547 if let Some(container2) = self.wrapped_object_containers.get(id) {
548 assert_eq!(container1, container2);
549 }
550 }
551 for (id, container1) in &self.wrapped_object_containers {
552 if let Some(container2) = wrapped_object_containers.get(id) {
553 assert_eq!(container1, container2);
554 }
555 }
556 }
557 self.wrapped_object_containers
560 .extend(wrapped_object_containers);
561 }
562
563 pub fn save_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
564 #[cfg(debug_assertions)]
565 {
566 for id in &self.generated_runtime_ids {
567 assert!(!generated_ids.contains(id))
568 }
569 for id in &generated_ids {
570 assert!(!self.generated_runtime_ids.contains(id));
571 }
572 }
573 self.generated_runtime_ids.extend(generated_ids);
574 }
575
576 pub fn estimate_effects_size_upperbound(&self) -> usize {
577 TransactionEffects::estimate_effects_size_upperbound_v2(
578 self.execution_results.written_objects.len(),
579 self.execution_results.modified_objects.len(),
580 self.input_objects.len(),
581 )
582 }
583
584 pub fn written_objects_size(&self) -> usize {
585 self.execution_results
586 .written_objects
587 .values()
588 .fold(0, |sum, obj| sum + obj.object_size_for_gas_metering())
589 }
590
591 pub fn check_gasless_execution_requirements(
597 &self,
598 withdrawal_reservations: Option<&BTreeMap<(SuiAddress, TypeTag), u64>>,
599 ) -> Result<(), String> {
600 if !self.execution_results.written_objects.is_empty() {
601 return Err("Gasless transactions cannot create or mutate objects".to_string());
602 }
603
604 let input_coin_ids: BTreeSet<ObjectID> = self
605 .input_objects
606 .iter()
607 .filter(|(_, obj)| obj.coin_type_maybe().is_some())
608 .map(|(id, _)| *id)
609 .collect();
610 if self.execution_results.deleted_object_ids != input_coin_ids {
611 return Err(format!(
612 "Gasless transaction must destroy exactly its input Coins. \
613 Expected: {input_coin_ids:?}, deleted: {:?}",
614 self.execution_results.deleted_object_ids
615 ));
616 }
617
618 let allowed_types =
619 sui_types::transaction::get_gasless_allowed_token_types(self.protocol_config);
620
621 let net_totals = sui_types::balance_change::signed_balance_changes_from_events(
624 &self.execution_results.accumulator_events,
625 )
626 .fold(
627 BTreeMap::<(SuiAddress, TypeTag), i128>::new(),
628 |mut totals, (address, token_type, signed_amount)| {
629 *totals.entry((address, token_type)).or_default() += signed_amount;
630 totals
631 },
632 );
633
634 for ((recipient, token_type), net_amount) in &net_totals {
635 if *net_amount <= 0 {
636 continue;
637 }
638 if let Some(&min_amount) = allowed_types.get(token_type)
639 && *net_amount < i128::from(min_amount)
640 {
641 return Err(format!(
642 "Gasless transfer of {net_amount} to {recipient} is below \
643 minimum {min_amount} for token type {token_type}"
644 ));
645 }
646 }
647
648 if let Some(reservations) = withdrawal_reservations {
649 for ((owner, token_type), &reserved) in reservations {
650 let net = net_totals
651 .get(&(*owner, token_type.clone()))
652 .copied()
653 .unwrap_or(0);
654 let remaining = (reserved as i128).saturating_add(net);
655 if remaining > 0
656 && let Some(&min_balance_remaining) = allowed_types.get(token_type)
657 && min_balance_remaining > 0
658 && remaining < min_balance_remaining as i128
659 {
660 return Err(format!(
661 "Gasless withdrawal leaves {remaining} unused for {owner}, \
662 below minimum {min_balance_remaining} for token type {token_type}"
663 ));
664 }
665 }
666 }
667
668 Ok(())
669 }
670
671 pub fn conserve_unmetered_storage_rebate(&mut self, unmetered_storage_rebate: u64) {
676 if unmetered_storage_rebate == 0 {
677 return;
681 }
682 tracing::debug!(
683 "Amount of unmetered storage rebate from system tx: {:?}",
684 unmetered_storage_rebate
685 );
686 let mut system_state_wrapper = self
687 .read_object(&SUI_SYSTEM_STATE_OBJECT_ID)
688 .expect("0x5 object must be mutated in system tx with unmetered storage rebate")
689 .clone();
690 assert_eq!(system_state_wrapper.storage_rebate, 0);
693 system_state_wrapper.storage_rebate = unmetered_storage_rebate;
694 self.mutate_input_object(system_state_wrapper);
695 }
696
697 pub fn add_accumulator_event(&mut self, event: AccumulatorEvent) {
699 self.execution_results.accumulator_events.push(event);
700 }
701
702 fn get_object_modified_at(
708 &self,
709 object_id: &ObjectID,
710 ) -> Option<DynamicallyLoadedObjectMetadata> {
711 if self.execution_results.modified_objects.contains(object_id) {
712 Some(
713 self.mutable_input_refs
714 .get(object_id)
715 .map(
716 |((version, digest), owner)| DynamicallyLoadedObjectMetadata {
717 version: *version,
718 digest: *digest,
719 owner: owner.clone(),
720 storage_rebate: self.input_objects[object_id].storage_rebate,
722 previous_transaction: self.input_objects[object_id]
723 .previous_transaction,
724 },
725 )
726 .or_else(|| self.loaded_runtime_objects.get(object_id).cloned())
727 .unwrap_or_else(|| {
728 debug_assert!(is_system_package(*object_id));
729 let package_obj =
730 self.store.get_package_object(object_id).unwrap().unwrap();
731 let obj = package_obj.object();
732 DynamicallyLoadedObjectMetadata {
733 version: obj.version(),
734 digest: obj.digest(),
735 owner: obj.owner.clone(),
736 storage_rebate: obj.storage_rebate,
737 previous_transaction: obj.previous_transaction,
738 }
739 }),
740 )
741 } else {
742 None
743 }
744 }
745
746 pub fn protocol_config(&self) -> &'backing ProtocolConfig {
747 self.protocol_config
748 }
749}
750
751impl TemporaryStore<'_> {
752 pub fn check_ownership_invariants(
755 &self,
756 sender: &SuiAddress,
757 sponsor: &Option<SuiAddress>,
758 gas_charger: &GasCharger,
759 mutable_inputs: &HashSet<ObjectID>,
760 input_reservations: &BTreeMap<(SuiAddress, TypeTag), u64>,
761 is_epoch_change: bool,
762 ) -> SuiResult<()> {
763 let gas_objs: HashSet<&ObjectID> = gas_charger.used_coins().map(|g| &g.0).collect();
764 let gas_owner = sponsor.as_ref().unwrap_or(sender);
765
766 let objects_authenticated_for_mutation: HashSet<SuiAddress> = self
768 .input_objects
769 .iter()
770 .filter_map(|(id, obj)| {
771 match &obj.owner {
772 Owner::AddressOwner(a) => {
773 if gas_objs.contains(id) {
774 assert!(
776 a == gas_owner,
777 "Gas object must be owned by sender or sponsor"
778 );
779 } else {
780 assert!(sender == a, "Input object must be owned by sender");
781 }
782 Some(id)
783 }
784 Owner::Shared { .. } | Owner::ConsensusAddressOwner { .. } => Some(id),
785 Owner::Immutable => {
786 None
797 }
798 Owner::Party { permissions, .. } => {
799 let sender_permissions = permissions.permissions_for(sender);
800 let sponsor_permissions = sponsor
801 .as_ref()
802 .map(|s| permissions.permissions_for(s))
803 .unwrap_or(ObjectPermissions::NONE);
804 (sender_permissions | sponsor_permissions)
805 .can_use_mutably()
806 .then_some(id)
807 }
808 Owner::ObjectOwner(_parent) => {
809 unreachable!(
810 "Input objects must be address owned, shared, consensus, or immutable"
811 )
812 }
813 }
814 })
815 .filter(|id| {
816 mutable_inputs.contains(id)
819 })
820 .copied()
821 .chain(self.generated_runtime_ids.iter().copied())
824 .map(SuiAddress::from)
825 .collect();
826
827 let mut authenticated_for_mutation = {
829 assert!(
830 !objects_authenticated_for_mutation.contains(sender),
831 "Sender cannot be an object"
832 );
833 assert!(
834 sponsor
835 .is_none_or(|sponsor| !objects_authenticated_for_mutation.contains(&sponsor)),
836 "Sponsor cannot be an object"
837 );
838 let mut s = objects_authenticated_for_mutation.clone();
839 s.insert(*sender);
840 if let Some(sponsor) = sponsor {
841 s.insert(*sponsor);
842 }
843 s
844 };
845
846 let mut objects_to_authenticate = self
848 .execution_results
849 .modified_objects
850 .iter()
851 .copied()
852 .collect::<Vec<_>>();
853
854 while let Some(to_authenticate) = objects_to_authenticate.pop() {
855 if authenticated_for_mutation.contains(&to_authenticate.into()) {
856 continue;
858 }
859
860 let parent = if let Some(container_id) =
861 self.wrapped_object_containers.get(&to_authenticate)
862 {
863 *container_id
865 } else {
866 let Some(old_obj) = self.store.get_object(&to_authenticate) else {
869 panic!(
870 "Failed to load object {to_authenticate:?}.\n \
871 If it cannot be loaded, we would expect it to be in the wrapped object map: {:#?}",
872 &self.wrapped_object_containers
873 )
874 };
875
876 match &old_obj.owner {
877 Owner::ObjectOwner(parent) => ObjectID::from(*parent),
880 Owner::AddressOwner(parent)
885 | Owner::ConsensusAddressOwner { owner: parent, .. } => {
886 ObjectID::from(*parent)
890 }
891 owner @ Owner::Shared { .. } | owner @ Owner::Party { .. } => {
894 panic!(
895 "Unauthenticated root at {to_authenticate:?} with owner {owner:?}\n\
896 Potentially covering objects in: {authenticated_for_mutation:#?}"
897 );
898 }
899
900 Owner::Immutable => {
901 assert!(
902 is_epoch_change,
903 "Immutable objects cannot be written, except for \
904 Sui Framework/Move stdlib upgrades at epoch change boundaries"
905 );
906 assert!(
910 is_system_package(to_authenticate),
911 "Only system packages can be upgraded"
912 );
913 continue;
914 }
915 }
916 };
917
918 authenticated_for_mutation.insert(to_authenticate.into());
920 objects_to_authenticate.push(parent);
921 }
922
923 let sui_balance_type =
925 sui_types::balance::Balance::type_tag(sui_types::gas_coin::GAS::type_tag());
926 let gas_payment_address_balance =
927 gas_charger
928 .gas_payment_location()
929 .and_then(|location| match location {
930 PaymentLocation::Coin(_) => None,
931 PaymentLocation::AddressBalance(address) => Some(address),
932 });
933 let mut funds_net_changes: BTreeMap<(SuiAddress, TypeTag), i128> = BTreeMap::new();
934 for event in self.execution_results.accumulator_events.iter() {
935 let amount = match event.write.value {
936 AccumulatorValue::Integer(a) => a as i128,
937 AccumulatorValue::IntegerTuple(_, _) | AccumulatorValue::EventDigest(_) => {
938 assert!(
939 !sui_types::balance::Balance::is_balance_type(&event.write.address.ty),
940 "Non-integer accumulator changes should not be balances"
941 );
942 continue;
943 }
944 };
945 let signed = match event.write.operation {
946 AccumulatorOperation::Split => -amount,
947 AccumulatorOperation::Merge => amount,
948 };
949 let address = event.write.address.address;
950 let type_tag = &event.write.address.ty;
951 let key = (address, type_tag.clone());
952 *funds_net_changes.entry(key.clone()).or_insert(0) += signed;
953 let authorized = match event.write.operation {
960 AccumulatorOperation::Merge => true,
961 AccumulatorOperation::Split => {
962 input_reservations.contains_key(&key)
963 || objects_authenticated_for_mutation.contains(&address)
964 || (*type_tag == sui_balance_type
965 && gas_payment_address_balance
966 .is_some_and(|gas_addr| gas_addr == address))
967 }
968 };
969 assert!(
970 authorized,
971 "Unauthenticated funds-accumulator Split at address {address} for type \
972 {type_tag}: no input reservation, address is not an authenticated object, and \
973 it is not the final gas payment address balance"
974 );
975 }
976
977 for (key, change) in funds_net_changes {
982 if change >= 0 || objects_authenticated_for_mutation.contains(&key.0) {
984 continue;
985 }
986 let reservation = input_reservations.get(&key).copied().unwrap_or(0) as u128;
987 let withdrawn = change.unsigned_abs();
988 assert!(
989 withdrawn <= reservation,
990 "Net withdrawal of {withdrawn} for {key:?} exceeds input reservation of \
991 {reservation}"
992 );
993 }
994
995 Ok(())
996 }
997}
998
999impl TemporaryStore<'_> {
1000 pub(crate) fn collect_storage_and_rebate(&mut self, gas_charger: &mut GasCharger) {
1007 let old_storage_rebates: Vec<_> = self
1009 .execution_results
1010 .written_objects
1011 .keys()
1012 .map(|object_id| {
1013 self.get_object_modified_at(object_id)
1014 .map(|metadata| metadata.storage_rebate)
1015 .unwrap_or_default()
1016 })
1017 .collect();
1018 for (object, old_storage_rebate) in self
1019 .execution_results
1020 .written_objects
1021 .values_mut()
1022 .zip_debug_eq(old_storage_rebates)
1023 {
1024 let new_object_size = object.object_size_for_gas_metering();
1026 let new_storage_rebate = gas_charger.track_storage_mutation(
1028 object.id(),
1029 new_object_size,
1030 old_storage_rebate,
1031 );
1032 object.storage_rebate = new_storage_rebate;
1033 }
1034
1035 self.collect_rebate(gas_charger);
1036 }
1037
1038 pub(crate) fn collect_rebate(&self, gas_charger: &mut GasCharger) {
1039 for object_id in &self.execution_results.modified_objects {
1040 if self
1041 .execution_results
1042 .written_objects
1043 .contains_key(object_id)
1044 {
1045 continue;
1046 }
1047 let storage_rebate = self
1049 .get_object_modified_at(object_id)
1050 .unwrap()
1052 .storage_rebate;
1053 gas_charger.track_storage_mutation(*object_id, 0, storage_rebate);
1054 }
1055 }
1056
1057 pub fn check_execution_results_consistency<Mode: ExecutionMode>(
1058 &self,
1059 ) -> Result<(), Mode::Error> {
1060 assert_invariant!(
1061 self.execution_results
1062 .created_object_ids
1063 .iter()
1064 .all(|id| !self.execution_results.deleted_object_ids.contains(id)
1065 && !self.execution_results.modified_objects.contains(id)),
1066 "Created object IDs cannot also be deleted or modified"
1067 );
1068 assert_invariant!(
1069 self.execution_results.modified_objects.iter().all(|id| {
1070 self.mutable_input_refs.contains_key(id)
1071 || self.loaded_runtime_objects.contains_key(id)
1072 || is_system_package(*id)
1073 }),
1074 "A modified object must be either a mutable input, a loaded child object, or a system package"
1075 );
1076 Ok(())
1077 }
1078}
1079impl TemporaryStore<'_> {
1084 pub fn advance_epoch_safe_mode(
1085 &mut self,
1086 params: &AdvanceEpochParams,
1087 protocol_config: &ProtocolConfig,
1088 ) {
1089 let wrapper = get_sui_system_state_wrapper(self.store.as_object_store())
1090 .expect("System state wrapper object must exist");
1091 let (old_object, new_object) =
1092 wrapper.advance_epoch_safe_mode(params, self.store.as_object_store(), protocol_config);
1093 self.mutate_child_object(old_object, new_object);
1094 }
1095}
1096
1097type ModifiedObjectInfo<'a> = (
1098 ObjectID,
1099 Option<DynamicallyLoadedObjectMetadata>,
1101 Option<&'a Object>,
1102);
1103
1104impl TemporaryStore<'_> {
1105 fn get_input_sui(
1106 &self,
1107 id: &ObjectID,
1108 expected_version: SequenceNumber,
1109 layout_resolver: &mut impl LayoutResolver,
1110 ) -> Result<u64, ExecutionError> {
1111 if let Some(obj) = self.input_objects.get(id) {
1112 if obj.version() != expected_version {
1114 invariant_violation!(
1115 "Version mismatching when resolving input object to check conservation--\
1116 expected {}, got {}",
1117 expected_version,
1118 obj.version(),
1119 );
1120 }
1121 obj.get_total_sui(layout_resolver).map_err(|e| {
1122 make_invariant_violation!(
1123 "Failed looking up input SUI in SUI conservation checking for input with \
1124 type {:?}: {e:#?}",
1125 obj.struct_tag(),
1126 )
1127 })
1128 } else {
1129 let Some(obj) = self.store.get_object_by_key(id, expected_version) else {
1131 invariant_violation!(
1132 "Failed looking up dynamic field {id} in SUI conservation checking"
1133 );
1134 };
1135 obj.get_total_sui(layout_resolver).map_err(|e| {
1136 make_invariant_violation!(
1137 "Failed looking up input SUI in SUI conservation checking for type \
1138 {:?}: {e:#?}",
1139 obj.struct_tag(),
1140 )
1141 })
1142 }
1143 }
1144
1145 fn get_modified_objects(&self) -> Vec<ModifiedObjectInfo<'_>> {
1150 self.execution_results
1151 .modified_objects
1152 .iter()
1153 .map(|id| {
1154 let metadata = self.get_object_modified_at(id);
1155 let output = self.execution_results.written_objects.get(id);
1156 (*id, metadata, output)
1157 })
1158 .chain(
1159 self.execution_results
1160 .written_objects
1161 .iter()
1162 .filter_map(|(id, object)| {
1163 if self.execution_results.modified_objects.contains(id) {
1164 None
1165 } else {
1166 Some((*id, None, Some(object)))
1167 }
1168 }),
1169 )
1170 .collect()
1171 }
1172
1173 pub fn check_sui_conserved(
1187 &self,
1188 simple_conservation_checks: bool,
1189 gas_summary: &GasCostSummary,
1190 ) -> Result<(), ExecutionError> {
1191 if !simple_conservation_checks {
1192 return Ok(());
1193 }
1194 let mut total_input_rebate = 0;
1196 let mut total_output_rebate = 0;
1198 for (_id, input, output) in self.get_modified_objects() {
1199 if let Some(input) = input {
1200 total_input_rebate += input.storage_rebate;
1201 }
1202 if let Some(object) = output {
1203 total_output_rebate += object.storage_rebate;
1204 }
1205 }
1206
1207 if gas_summary.storage_cost == 0 {
1208 if total_input_rebate
1220 != total_output_rebate
1221 + gas_summary.storage_rebate
1222 + gas_summary.non_refundable_storage_fee
1223 {
1224 return Err(ExecutionError::invariant_violation(format!(
1225 "SUI conservation failed -- no storage charges in gas summary \
1226 and total storage input rebate {} not equal \
1227 to total storage output rebate {}",
1228 total_input_rebate, total_output_rebate,
1229 )));
1230 }
1231 } else {
1232 if total_input_rebate
1235 != gas_summary.storage_rebate + gas_summary.non_refundable_storage_fee
1236 {
1237 return Err(ExecutionError::invariant_violation(format!(
1238 "SUI conservation failed -- {} SUI in storage rebate field of input objects, \
1239 {} SUI in tx storage rebate or tx non-refundable storage rebate",
1240 total_input_rebate, gas_summary.non_refundable_storage_fee,
1241 )));
1242 }
1243
1244 if gas_summary.storage_cost != total_output_rebate {
1247 return Err(ExecutionError::invariant_violation(format!(
1248 "SUI conservation failed -- {} SUI charged for storage, \
1249 {} SUI in storage rebate field of output objects",
1250 gas_summary.storage_cost, total_output_rebate
1251 )));
1252 }
1253 }
1254 Ok(())
1255 }
1256
1257 pub fn check_address_balance_changes(
1277 &self,
1278 protocol_config: &ProtocolConfig,
1279 input_reservations: &BTreeMap<(SuiAddress, TypeTag), u64>,
1280 ) -> Result<(), ExecutionError> {
1281 let result = self.check_address_balance_changes_impl(input_reservations);
1282 if protocol_config.enforce_address_balance_change_invariant() {
1283 result
1284 } else {
1285 if let Err(e) = result {
1286 panic!("address-balance-change invariant violated pre-flag: {e}");
1287 }
1288 Ok(())
1289 }
1290 }
1291
1292 fn check_address_balance_changes_impl(
1293 &self,
1294 input_reservations: &BTreeMap<(SuiAddress, TypeTag), u64>,
1295 ) -> Result<(), ExecutionError> {
1296 use sui_types::balance::Balance;
1297
1298 let mut actual_changes: BTreeMap<(SuiAddress, TypeTag), i128> = BTreeMap::new();
1299 let mut ptb_changes: BTreeMap<(SuiAddress, TypeTag), i128> = BTreeMap::new();
1300 for (idx, event) in self.execution_results.accumulator_events.iter().enumerate() {
1301 let amount = match event.write.value {
1307 AccumulatorValue::Integer(amount) => amount as i128,
1308 AccumulatorValue::IntegerTuple(_, _) | AccumulatorValue::EventDigest(_) => {
1309 assert_invariant!(
1310 !sui_types::balance::Balance::is_balance_type(&event.write.address.ty),
1311 "Non-integer accumulator changes should not be balances"
1312 );
1313 continue;
1314 }
1315 };
1316 if !Balance::is_balance_type(&event.write.address.ty) {
1317 debug_fatal!(
1318 "Integer accumulator value at non-Balance type: {:?}",
1319 event.write.address.ty
1320 );
1321 continue;
1322 }
1323 let is_ptb_emitted = self
1324 .ptb_emitted_accumulator_event_ranges
1325 .iter()
1326 .any(|range| range.contains(&idx));
1327 let key = (event.write.address.address, event.write.address.ty.clone());
1328 let change = match event.write.operation {
1329 AccumulatorOperation::Split => -amount,
1330 AccumulatorOperation::Merge => amount,
1331 };
1332 *actual_changes.entry(key.clone()).or_insert(0) += change;
1333 if is_ptb_emitted {
1334 *ptb_changes.entry(key).or_insert(0) += change;
1335 }
1336 }
1337
1338 for (key, actual) in actual_changes {
1339 let (address, type_tag) = &key;
1340 if let Some(budget) = input_reservations.get(&key).copied() {
1341 let net_withdrawn = -actual.min(0) as u128;
1342 assert_invariant!(
1343 net_withdrawn <= budget as u128,
1344 "Balance accumulator withdrawal exceeds reservation budget at address \
1345 {address} for type {type_tag}: net Split {net_withdrawn}, budget {budget}"
1346 );
1347 } else if let Some(ptb_change) = ptb_changes.get(&key).copied() {
1348 assert_invariant!(
1353 actual >= ptb_change.min(0),
1354 "PTB-emitted Balance accumulator events do not cover runtime withdrawals \
1355 at address {address} for type {type_tag}: PTB change {ptb_change}, net \
1356 change {actual}"
1357 );
1358 } else {
1359 invariant_violation!(
1360 "Unauthorized runtime Balance accumulator event at address {address} for \
1361 type {type_tag}: net change {actual} (no input reservation, no PTB-emitted \
1362 events)"
1363 );
1364 }
1365 }
1366
1367 Ok(())
1368 }
1369
1370 pub fn check_sui_conserved_expensive(
1383 &self,
1384 gas_summary: &GasCostSummary,
1385 advance_epoch_gas_summary: Option<(u64, u64)>,
1386 layout_resolver: &mut impl LayoutResolver,
1387 ) -> Result<(), ExecutionError> {
1388 let mut total_input_sui = 0;
1390 let mut total_output_sui = 0;
1392
1393 total_input_sui += self.execution_results.settlement_input_sui;
1397 total_output_sui += self.execution_results.settlement_output_sui;
1398
1399 for (id, input, output) in self.get_modified_objects() {
1400 if let Some(input) = input {
1401 total_input_sui += self.get_input_sui(&id, input.version, layout_resolver)?;
1402 }
1403 if let Some(object) = output {
1404 total_output_sui += object.get_total_sui(layout_resolver).map_err(|e| {
1405 make_invariant_violation!(
1406 "Failed looking up output SUI in SUI conservation checking for \
1407 mutated type {:?}: {e:#?}",
1408 object.struct_tag(),
1409 )
1410 })?;
1411 }
1412 }
1413
1414 for event in &self.execution_results.accumulator_events {
1415 let (input, output) = event.total_sui_in_event();
1416 total_input_sui += input;
1417 total_output_sui += output;
1418 }
1419
1420 total_output_sui += gas_summary.computation_cost + gas_summary.non_refundable_storage_fee;
1425 if let Some((epoch_fees, epoch_rebates)) = advance_epoch_gas_summary {
1426 total_input_sui += epoch_fees;
1427 total_output_sui += epoch_rebates;
1428 }
1429 if total_input_sui != total_output_sui {
1430 return Err(ExecutionError::invariant_violation(format!(
1431 "SUI conservation failed: input={}, output={}, \
1432 this transaction either mints or burns SUI",
1433 total_input_sui, total_output_sui,
1434 )));
1435 }
1436 Ok(())
1437 }
1438}
1439
1440impl ChildObjectResolver for TemporaryStore<'_> {
1441 fn read_child_object(
1442 &self,
1443 parent: &ObjectID,
1444 child: &ObjectID,
1445 child_version_upper_bound: SequenceNumber,
1446 ) -> SuiResult<Option<Object>> {
1447 let obj_opt = self.execution_results.written_objects.get(child);
1448 if obj_opt.is_some() {
1449 Ok(obj_opt.cloned())
1450 } else {
1451 let _scope = monitored_scope("Execution::read_child_object");
1452 self.store
1453 .read_child_object(parent, child, child_version_upper_bound)
1454 }
1455 }
1456
1457 fn get_object_received_at_version(
1458 &self,
1459 owner: &ObjectID,
1460 receiving_object_id: &ObjectID,
1461 receive_object_at_version: SequenceNumber,
1462 epoch_id: EpochId,
1463 ) -> SuiResult<Option<Object>> {
1464 debug_assert!(
1467 !self
1468 .execution_results
1469 .written_objects
1470 .contains_key(receiving_object_id)
1471 );
1472 debug_assert!(
1473 !self
1474 .execution_results
1475 .deleted_object_ids
1476 .contains(receiving_object_id)
1477 );
1478 self.store.get_object_received_at_version(
1479 owner,
1480 receiving_object_id,
1481 receive_object_at_version,
1482 epoch_id,
1483 )
1484 }
1485}
1486
1487fn was_object_mutated(object: &Object, original: &Object) -> bool {
1490 let data_equal = match (&object.data, &original.data) {
1491 (Data::Move(a), Data::Move(b)) => a.contents_and_type_equal(b),
1492 (Data::Package(a), Data::Package(b)) => a == b,
1495 _ => false,
1496 };
1497
1498 let owner_equal = match (&object.owner, &original.owner) {
1499 (Owner::Shared { .. }, Owner::Shared { .. }) => true,
1503 (
1504 Owner::ConsensusAddressOwner { owner: a, .. },
1505 Owner::ConsensusAddressOwner { owner: b, .. },
1506 ) => a == b,
1507 (Owner::AddressOwner(a), Owner::AddressOwner(b)) => a == b,
1508 (Owner::Immutable, Owner::Immutable) => true,
1509 (Owner::ObjectOwner(a), Owner::ObjectOwner(b)) => a == b,
1510 (
1511 Owner::Party {
1512 permissions: a,
1513 start_version: _,
1514 },
1515 Owner::Party {
1516 permissions: b,
1517 start_version: _,
1518 },
1519 ) => a == b,
1520
1521 (Owner::AddressOwner(_), _)
1524 | (Owner::Immutable, _)
1525 | (Owner::ObjectOwner(_), _)
1526 | (Owner::Shared { .. }, _)
1527 | (Owner::ConsensusAddressOwner { .. }, _)
1528 | (Owner::Party { .. }, _) => false,
1529 };
1530
1531 !data_equal || !owner_equal
1532}
1533
1534impl Storage for TemporaryStore<'_> {
1535 fn reset(&mut self) {
1536 self.drop_writes();
1537 }
1538
1539 fn read_object(&self, id: &ObjectID) -> Option<&Object> {
1540 TemporaryStore::read_object(self, id)
1541 }
1542
1543 fn record_execution_results(
1545 &mut self,
1546 results: ExecutionResults,
1547 ) -> Result<(), ExecutionError> {
1548 let ExecutionResults::V2(mut results) = results else {
1549 panic!("ExecutionResults::V2 expected in sui-execution v1 and above");
1550 };
1551
1552 let mut to_remove = Vec::new();
1554 for (id, original) in &self.non_exclusive_input_original_versions {
1555 if results
1557 .written_objects
1558 .get(id)
1559 .map(|obj| was_object_mutated(obj, original))
1560 .unwrap_or(true)
1561 {
1562 return Err(ExecutionError::new_with_source(
1563 ExecutionErrorKind::NonExclusiveWriteInputObjectModified { id: *id },
1564 "Non-exclusive write input object has been modified or deleted",
1565 ));
1566 }
1567 to_remove.push(*id);
1568 }
1569
1570 for id in to_remove {
1571 results.written_objects.remove(&id);
1572 results.modified_objects.remove(&id);
1573 }
1574
1575 let event_start = self.execution_results.accumulator_events.len();
1581 self.execution_results.merge_results(
1582 results, true, true,
1583 )?;
1584 let event_end = self.execution_results.accumulator_events.len();
1585 debug_assert!(
1586 event_start <= event_end,
1587 "merge_results should not shrink accumulator_events"
1588 );
1589 let (event_start, event_end) = (event_start.min(event_end), event_start.max(event_end));
1590 let range = event_start..event_end;
1591 match self.ptb_emitted_accumulator_event_ranges.last_mut() {
1592 Some(last) if last.end == range.start => last.end = range.end,
1594 _ => self.ptb_emitted_accumulator_event_ranges.push(range),
1595 }
1596
1597 Ok(())
1598 }
1599
1600 fn save_loaded_runtime_objects(
1601 &mut self,
1602 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
1603 ) {
1604 TemporaryStore::save_loaded_runtime_objects(self, loaded_runtime_objects)
1605 }
1606
1607 fn save_wrapped_object_containers(
1608 &mut self,
1609 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
1610 ) {
1611 TemporaryStore::save_wrapped_object_containers(self, wrapped_object_containers)
1612 }
1613
1614 fn check_coin_deny_list(
1615 &self,
1616 receiving_funds_type_and_owners: BTreeMap<TypeTag, BTreeSet<SuiAddress>>,
1617 ) -> DenyListResult {
1618 let result = check_coin_deny_list_v2_during_execution(
1619 receiving_funds_type_and_owners,
1620 self.cur_epoch,
1621 self.store.as_object_store(),
1622 );
1623 if result.num_non_gas_coin_owners > 0
1626 && !self.input_objects.contains_key(&SUI_DENY_LIST_OBJECT_ID)
1627 {
1628 self.loaded_per_epoch_config_objects
1629 .write()
1630 .insert(SUI_DENY_LIST_OBJECT_ID);
1631 }
1632 result
1633 }
1634
1635 fn record_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
1636 TemporaryStore::save_generated_object_ids(self, generated_ids)
1637 }
1638}
1639
1640impl BackingPackageStore for TemporaryStore<'_> {
1641 fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
1642 if let Some(obj) = self.execution_results.written_objects.get(package_id) {
1649 Ok(Some(PackageObject::new(obj.clone())))
1650 } else {
1651 self.store.get_package_object(package_id).inspect(|obj| {
1652 if let Some(v) = obj
1654 && !self
1655 .runtime_packages_loaded_from_db
1656 .read()
1657 .contains_key(package_id)
1658 {
1659 self.runtime_packages_loaded_from_db
1664 .write()
1665 .insert(*package_id, v.clone());
1666 }
1667 })
1668 }
1669 }
1670}
1671
1672impl ParentSync for TemporaryStore<'_> {
1673 fn get_latest_parent_entry_ref_deprecated(&self, _object_id: ObjectID) -> Option<ObjectRef> {
1674 unreachable!("Never called in newer protocol versions")
1675 }
1676}