1use crate::execution_mode::ExecutionMode;
5use crate::gas_charger::GasCharger;
6use move_vm_runtime::runtime::MoveRuntime;
7use mysten_common::{ZipDebugEqIteratorExt, debug_fatal};
8use mysten_metrics::monitored_scope;
9use parking_lot::RwLock;
10use std::cell::RefCell;
11use std::collections::{BTreeMap, BTreeSet, HashSet};
12use std::sync::Arc;
13use sui_protocol_config::ProtocolConfig;
14use sui_types::accumulator_event::AccumulatorEvent;
15use sui_types::accumulator_root::AccumulatorObjId;
16use sui_types::base_types::{SystemObjectVersions, VersionDigest};
17use sui_types::coin_reservation::ParsedDigest;
18use sui_types::committee::EpochId;
19use sui_types::deny_list_v2::check_coin_deny_list_v2_during_execution;
20use sui_types::effects::{
21 AccumulatorOperation, AccumulatorValue, AccumulatorWriteV1, TransactionEffects,
22 TransactionEffectsV2, TransactionEvents,
23};
24use sui_types::execution::{
25 DynamicallyLoadedObjectMetadata, ExecutionResults, ExecutionResultsV2, SharedInput,
26};
27use sui_types::execution_status::{ExecutionErrorKind, ExecutionStatus};
28use sui_types::inner_temporary_store::InnerTemporaryStore;
29use sui_types::object::Data;
30use sui_types::storage::{BackingStore, DenyListResult, PackageObject};
31use sui_types::sui_system_state::{AdvanceEpochParams, get_sui_system_state_wrapper};
32use sui_types::transaction::{Command, GasData, TransactionKind, is_gasless_transaction};
33use sui_types::{
34 SUI_DENY_LIST_OBJECT_ID,
35 base_types::{ObjectID, ObjectRef, SequenceNumber, SuiAddress, TransactionDigest},
36 digests::ObjectDigest,
37 effects::EffectsObjectChange,
38 error::{ExecutionError, SuiResult},
39 gas::GasCostSummary,
40 object::Object,
41 object::Owner,
42 storage::{BackingPackageStore, RuntimeObjectResolver, Storage},
43 transaction::InputObjects,
44};
45use sui_types::{SUI_SYSTEM_STATE_OBJECT_ID, TypeTag, is_system_package};
46
47pub(crate) mod invariants;
48use invariants::InvariantChecker;
49
50type AllowanceIds = BTreeMap<(SuiAddress, TypeTag), Vec<ObjectID>>;
52
53#[derive(Default)]
54struct PostExecutionCheckInputs {
55 input_reservations: BTreeMap<(SuiAddress, TypeTag), u64>,
58 allowance_ids: AllowanceIds,
61 advance_epoch_gas_summary: Option<(u64, u64)>,
64 is_genesis: bool,
66 declared_packages: Option<Vec<(usize, BTreeSet<ObjectID>)>>,
69}
70
71impl PostExecutionCheckInputs {
72 fn new(transaction: (&TransactionKind, &GasData, SuiAddress), enable_gasless: bool) -> Self {
73 let (transaction_kind, gas_data, transaction_signer) = transaction;
74 let (input_reservations, allowance_ids) = compute_input_reservations(
75 transaction_kind,
76 gas_data,
77 transaction_signer,
78 enable_gasless,
79 );
80 Self {
81 input_reservations,
82 allowance_ids,
83 advance_epoch_gas_summary: transaction_kind.get_advance_epoch_tx_gas_summary(),
84 is_genesis: matches!(transaction_kind, TransactionKind::Genesis(_)),
85 declared_packages: declared_packages(transaction_kind),
86 }
87 }
88}
89
90pub struct TemporaryStore<'backing> {
91 store: &'backing dyn BackingStore,
97 tx_digest: TransactionDigest,
98 input_objects: BTreeMap<ObjectID, Object>,
99 post_execution_check_inputs: PostExecutionCheckInputs,
102
103 non_exclusive_input_original_versions: BTreeMap<ObjectID, Object>,
106
107 stream_ended_consensus_objects: BTreeMap<ObjectID, SequenceNumber >,
108 lamport_timestamp: SequenceNumber,
110 mutable_input_refs: BTreeMap<ObjectID, (VersionDigest, Owner)>,
113 execution_results: ExecutionResultsV2,
114 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
116 protocol_config: &'backing ProtocolConfig,
117
118 runtime_packages_loaded_from_db: RwLock<BTreeMap<ObjectID, PackageObject>>,
121
122 receiving_objects: Vec<ObjectRef>,
125
126 cur_epoch: EpochId,
129
130 loaded_per_epoch_config_objects: RwLock<BTreeSet<ObjectID>>,
133
134 invariants: InvariantChecker,
136
137 system_object_versions: SystemObjectVersions,
139
140 loaded_system_objects: RefCell<BTreeMap<ObjectID, (SequenceNumber, ObjectDigest)>>,
144}
145
146impl<'backing> TemporaryStore<'backing> {
147 #[allow(clippy::too_many_arguments)]
150 pub(crate) fn new(
151 store: &'backing dyn BackingStore,
152 input_objects: InputObjects,
153 receiving_objects: Vec<ObjectRef>,
154 tx_digest: TransactionDigest,
155 protocol_config: &'backing ProtocolConfig,
156 cur_epoch: EpochId,
157 system_object_versions: SystemObjectVersions,
158 transaction: (&TransactionKind, &GasData, SuiAddress),
159 ) -> Self {
160 let post_execution_check_inputs =
161 PostExecutionCheckInputs::new(transaction, protocol_config.enable_gasless());
162 Self::new_with_input_objects(
163 store,
164 input_objects,
165 receiving_objects,
166 tx_digest,
167 protocol_config,
168 cur_epoch,
169 system_object_versions,
170 post_execution_check_inputs,
171 )
172 }
173
174 pub(crate) fn new_for_genesis_state_update(
175 store: &'backing dyn BackingStore,
176 tx_digest: TransactionDigest,
177 protocol_config: &'backing ProtocolConfig,
178 ) -> Self {
179 Self::new_with_input_objects(
180 store,
181 InputObjects::new(vec![]),
182 vec![],
183 tx_digest,
184 protocol_config,
185 0,
186 SystemObjectVersions::empty(),
187 PostExecutionCheckInputs {
188 is_genesis: true,
189 ..Default::default()
190 },
191 )
192 }
193
194 fn new_with_input_objects(
195 store: &'backing dyn BackingStore,
196 input_objects: InputObjects,
197 receiving_objects: Vec<ObjectRef>,
198 tx_digest: TransactionDigest,
199 protocol_config: &'backing ProtocolConfig,
200 cur_epoch: EpochId,
201 system_object_versions: SystemObjectVersions,
202 post_execution_check_inputs: PostExecutionCheckInputs,
203 ) -> Self {
204 let mutable_input_refs = input_objects.exclusive_mutable_inputs();
205 let non_exclusive_input_original_versions = input_objects.non_exclusive_input_objects();
206
207 let lamport_timestamp = input_objects.lamport_timestamp(&receiving_objects);
208 let stream_ended_consensus_objects = input_objects.consensus_stream_ended_objects();
209 let objects = input_objects.into_object_map();
210 #[cfg(debug_assertions)]
211 {
212 assert!(
214 objects
215 .keys()
216 .collect::<HashSet<_>>()
217 .intersection(
218 &receiving_objects
219 .iter()
220 .map(|oref| &oref.0)
221 .collect::<HashSet<_>>()
222 )
223 .next()
224 .is_none()
225 );
226 }
227 Self {
228 store,
229 tx_digest,
230 input_objects: objects,
231 non_exclusive_input_original_versions,
232 stream_ended_consensus_objects,
233 lamport_timestamp,
234 mutable_input_refs,
235 execution_results: ExecutionResultsV2::default(),
236 protocol_config,
237 loaded_runtime_objects: BTreeMap::new(),
238 runtime_packages_loaded_from_db: RwLock::new(BTreeMap::new()),
239 receiving_objects,
240 cur_epoch,
241 loaded_per_epoch_config_objects: RwLock::new(BTreeSet::new()),
242 post_execution_check_inputs,
243 invariants: InvariantChecker::default(),
244 system_object_versions,
245 loaded_system_objects: RefCell::new(BTreeMap::new()),
246 }
247 }
248
249 pub fn load_implicitly_read_system_object(&self, object_id: &ObjectID) -> Option<Object> {
255 let version = match self.system_object_versions.get(object_id) {
256 Some(version) => version,
257 None => {
258 debug_fatal!(
259 "system_object_versions must contain entry for object_id: {:?}",
260 object_id
261 );
262 return None;
263 }
264 };
265 let object = self
266 .store
267 .load_implicitly_read_system_object(object_id, version)?;
270 self.loaded_system_objects
273 .borrow_mut()
274 .insert(*object_id, (object.version(), object.digest()));
275 Some(object)
276 }
277
278 pub fn objects(&self) -> &BTreeMap<ObjectID, Object> {
280 &self.input_objects
281 }
282
283 pub fn update_object_version_and_prev_tx(&mut self) {
284 self.execution_results.update_version_and_previous_tx(
285 self.lamport_timestamp,
286 self.tx_digest,
287 &self.input_objects,
288 self.protocol_config.reshare_at_same_initial_version(),
289 );
290
291 #[cfg(debug_assertions)]
292 {
293 self.check_invariants();
294 }
295 }
296
297 fn calculate_accumulator_running_max_withdraws(&self) -> BTreeMap<AccumulatorObjId, u128> {
298 let mut running_net_withdraws: BTreeMap<AccumulatorObjId, i128> = BTreeMap::new();
299 let mut running_max_withdraws: BTreeMap<AccumulatorObjId, u128> = BTreeMap::new();
300 for event in &self.execution_results.accumulator_events {
301 match &event.write.value {
302 AccumulatorValue::Integer(amount) => match event.write.operation {
303 AccumulatorOperation::Split => {
304 let entry = running_net_withdraws
305 .entry(event.accumulator_obj)
306 .or_default();
307 *entry += *amount as i128;
308 if *entry > 0 {
309 let max_entry = running_max_withdraws
310 .entry(event.accumulator_obj)
311 .or_default();
312 *max_entry = (*max_entry).max(*entry as u128);
313 }
314 }
315 AccumulatorOperation::Merge => {
316 let entry = running_net_withdraws
317 .entry(event.accumulator_obj)
318 .or_default();
319 *entry -= *amount as i128;
320 }
321 },
322 AccumulatorValue::IntegerTuple(_, _) | AccumulatorValue::EventDigest(_) => {}
323 }
324 }
325 running_max_withdraws
326 }
327
328 pub(crate) fn check_accumulator_amounts_representable(&self) -> Result<(), ExecutionError> {
356 let supply = sui_types::gas_coin::TOTAL_SUPPLY_MIST as u128;
357 let mut merge_totals: BTreeMap<AccumulatorObjId, u128> = BTreeMap::new();
358 let mut split_totals: BTreeMap<AccumulatorObjId, u128> = BTreeMap::new();
359 let mut total_sui_split: u128 = 0;
361 for event in &self.execution_results.accumulator_events {
362 let AccumulatorValue::Integer(amount) = event.write.value else {
363 continue;
364 };
365 let amount = amount as u128;
366 let is_sui = sui_types::gas_coin::GasCoin::is_gas_balance_type(&event.write.address.ty);
369 let limit = if is_sui { supply } else { u64::MAX as u128 };
370 let total = match event.write.operation {
371 AccumulatorOperation::Merge => {
372 merge_totals.entry(event.accumulator_obj).or_default()
373 }
374 AccumulatorOperation::Split => {
375 split_totals.entry(event.accumulator_obj).or_default()
376 }
377 };
378 *total += amount;
379 if *total > limit {
380 return Err(ExecutionError::new_with_source(
381 ExecutionErrorKind::CoinBalanceOverflow,
382 format!(
383 "accumulator balance change for {:?} exceeds the representable limit \
384 (gross total {}, limit {})",
385 event.accumulator_obj, *total, limit
386 ),
387 ));
388 }
389 if is_sui && matches!(event.write.operation, AccumulatorOperation::Split) {
390 total_sui_split += amount;
391 if total_sui_split > supply {
392 return Err(ExecutionError::new_with_source(
393 ExecutionErrorKind::CoinBalanceOverflow,
394 format!(
395 "total SUI withdrawn across all accumulators ({total_sui_split}) \
396 exceeds the total supply ({supply})"
397 ),
398 ));
399 }
400 }
401 }
402 Ok(())
403 }
404
405 fn merge_accumulator_events(&mut self) {
407 self.execution_results.accumulator_events = self
408 .execution_results
409 .accumulator_events
410 .iter()
411 .fold(
412 BTreeMap::<AccumulatorObjId, Vec<AccumulatorWriteV1>>::new(),
413 |mut map, event| {
414 map.entry(event.accumulator_obj)
415 .or_default()
416 .push(event.write.clone());
417 map
418 },
419 )
420 .into_iter()
421 .map(|(obj_id, writes)| {
422 AccumulatorEvent::new(obj_id, AccumulatorWriteV1::merge(writes))
423 })
424 .collect();
425 }
426
427 pub fn into_inner(
429 self,
430 accumulator_running_max_withdraws: BTreeMap<AccumulatorObjId, u128>,
431 ) -> InnerTemporaryStore {
432 let results = self.execution_results;
433 InnerTemporaryStore {
434 input_objects: self.input_objects,
435 stream_ended_consensus_objects: self.stream_ended_consensus_objects,
436 mutable_inputs: self.mutable_input_refs,
437 written: results.written_objects,
438 events: TransactionEvents {
439 data: results.user_events,
440 },
441 accumulator_events: results.accumulator_events,
442 loaded_runtime_objects: self.loaded_runtime_objects,
443 runtime_packages_loaded_from_db: self.runtime_packages_loaded_from_db.into_inner(),
444 lamport_version: self.lamport_timestamp,
445 binary_config: self.protocol_config.binary_config(None),
446 accumulator_running_max_withdraws,
447 }
448 }
449
450 pub(crate) fn ensure_active_inputs_mutated(&mut self) {
454 let mut to_be_updated = vec![];
455 for id in self.mutable_input_refs.keys() {
457 if !self.execution_results.modified_objects.contains(id) {
458 to_be_updated.push(self.input_objects[id].clone());
462 }
463 }
464 for object in to_be_updated {
465 self.mutate_input_object(object.clone());
467 }
468 }
469
470 fn get_object_changes(&self) -> BTreeMap<ObjectID, EffectsObjectChange> {
471 let results = &self.execution_results;
472 let all_ids = results
473 .created_object_ids
474 .iter()
475 .chain(&results.deleted_object_ids)
476 .chain(&results.modified_objects)
477 .chain(results.written_objects.keys())
478 .collect::<BTreeSet<_>>();
479 all_ids
480 .into_iter()
481 .map(|id| {
482 (
483 *id,
484 EffectsObjectChange::new(
485 self.get_object_modified_at(id)
486 .map(|metadata| ((metadata.version, metadata.digest), metadata.owner)),
487 results.written_objects.get(id),
488 results.created_object_ids.contains(id),
489 results.deleted_object_ids.contains(id),
490 ),
491 )
492 })
493 .chain(results.accumulator_events.iter().cloned().map(
494 |AccumulatorEvent {
495 accumulator_obj,
496 write,
497 }| {
498 (
499 *accumulator_obj.inner(),
500 EffectsObjectChange::new_from_accumulator_write(write),
501 )
502 },
503 ))
504 .collect()
505 }
506
507 pub fn into_effects(
508 mut self,
509 shared_object_refs: Vec<SharedInput>,
510 transaction_digest: &TransactionDigest,
511 mut transaction_dependencies: BTreeSet<TransactionDigest>,
512 gas_cost_summary: GasCostSummary,
513 status: ExecutionStatus,
514 gas_coin: Option<ObjectID>,
515 epoch: EpochId,
516 ) -> (InnerTemporaryStore, TransactionEffects) {
517 for (id, obj) in &self.execution_results.written_objects {
520 assert!(
521 !matches!(obj.owner, Owner::Party { .. }),
522 "Party-owned objects are not yet supported (object {id})"
523 );
524 }
525
526 self.update_object_version_and_prev_tx();
527 let accumulator_running_max_withdraws = self.calculate_accumulator_running_max_withdraws();
529 self.merge_accumulator_events();
530
531 for (id, expected_version, expected_digest) in &self.receiving_objects {
534 if let Some(obj_meta) = self.loaded_runtime_objects.get(id) {
538 let loaded_via_receive = obj_meta.version == *expected_version
542 && obj_meta.digest == *expected_digest
543 && obj_meta.owner.is_address_owned();
544 if loaded_via_receive {
545 transaction_dependencies.insert(obj_meta.previous_transaction);
546 }
547 }
548 }
549
550 assert!(self.protocol_config.enable_effects_v2());
551
552 let object_changes = self.get_object_changes();
553
554 let lamport_version = self.lamport_timestamp;
555 let loaded_per_epoch_config_objects = self.loaded_per_epoch_config_objects.read().clone();
557 let loaded_system_objects = self.loaded_system_objects.borrow().clone();
558 let unchanged_consensus_objects = TransactionEffectsV2::compute_unchanged_consensus_objects(
559 shared_object_refs,
560 loaded_per_epoch_config_objects,
561 &object_changes,
562 loaded_system_objects,
563 );
564 let inner = self.into_inner(accumulator_running_max_withdraws);
565
566 let effects = TransactionEffects::new_from_execution_v2(
567 status,
568 epoch,
569 gas_cost_summary,
570 unchanged_consensus_objects,
571 *transaction_digest,
572 lamport_version,
573 object_changes,
574 gas_coin,
575 if inner.events.data.is_empty() {
576 None
577 } else {
578 Some(inner.events.digest())
579 },
580 transaction_dependencies.into_iter().collect(),
581 );
582
583 (inner, effects)
584 }
585
586 #[cfg(debug_assertions)]
588 fn check_invariants(&self) {
589 debug_assert!(
591 {
592 self.execution_results
593 .written_objects
594 .keys()
595 .all(|id| !self.execution_results.deleted_object_ids.contains(id))
596 },
597 "Object both written and deleted."
598 );
599
600 debug_assert!(
602 {
603 self.mutable_input_refs
604 .keys()
605 .all(|id| self.execution_results.modified_objects.contains(id))
606 },
607 "Mutable input not modified."
608 );
609
610 debug_assert!(
611 {
612 self.execution_results
613 .written_objects
614 .values()
615 .all(|obj| obj.previous_transaction == self.tx_digest)
616 },
617 "Object previous transaction not properly set",
618 );
619 }
620
621 pub fn mutate_input_object(&mut self, object: Object) {
623 let id = object.id();
624 debug_assert!(self.input_objects.contains_key(&id));
625 debug_assert!(!object.is_immutable());
626 self.execution_results.modified_objects.insert(id);
627 self.execution_results.written_objects.insert(id, object);
628 }
629
630 pub fn mutate_new_or_input_object(&mut self, object: Object) {
631 let id = object.id();
632 debug_assert!(!object.is_immutable());
633 if self.input_objects.contains_key(&id) {
634 self.execution_results.modified_objects.insert(id);
635 }
636 self.execution_results.written_objects.insert(id, object);
637 }
638
639 pub fn mutate_child_object(&mut self, old_object: Object, new_object: Object) {
643 let id = new_object.id();
644 let old_ref = old_object.compute_object_reference();
645 debug_assert_eq!(old_ref.0, id);
646 self.loaded_runtime_objects.insert(
647 id,
648 DynamicallyLoadedObjectMetadata {
649 version: old_ref.1,
650 digest: old_ref.2,
651 owner: old_object.owner.clone(),
652 storage_rebate: old_object.storage_rebate,
653 previous_transaction: old_object.previous_transaction,
654 },
655 );
656 self.execution_results.modified_objects.insert(id);
657 self.execution_results
658 .written_objects
659 .insert(id, new_object);
660 }
661
662 pub fn upgrade_system_package(&mut self, package: Object) {
666 let id = package.id();
667 assert!(package.is_package() && is_system_package(id));
668 self.execution_results.modified_objects.insert(id);
669 self.execution_results.written_objects.insert(id, package);
670 }
671
672 pub fn create_object(&mut self, object: Object) {
674 debug_assert!(
679 object.is_immutable() || object.version() == SequenceNumber::MIN,
680 "Created mutable objects should not have a version set",
681 );
682 let id = object.id();
683 self.execution_results.created_object_ids.insert(id);
684 self.execution_results.written_objects.insert(id, object);
685 }
686
687 pub fn delete_input_object(&mut self, id: &ObjectID) {
689 debug_assert!(!self.execution_results.written_objects.contains_key(id));
691 debug_assert!(self.input_objects.contains_key(id));
692 self.execution_results.modified_objects.insert(*id);
693 self.execution_results.deleted_object_ids.insert(*id);
694 }
695
696 pub fn drop_writes(&mut self) {
697 self.execution_results.drop_writes();
698 self.invariants = InvariantChecker::default();
699 }
700
701 pub(crate) fn into_bump_only(self) -> Self {
706 let Self {
707 store,
709 tx_digest,
710 input_objects,
711 non_exclusive_input_original_versions,
712 stream_ended_consensus_objects,
713 lamport_timestamp,
714 mutable_input_refs,
715 receiving_objects,
716 cur_epoch,
717 protocol_config,
718 post_execution_check_inputs,
719 system_object_versions,
720 loaded_runtime_objects,
722 runtime_packages_loaded_from_db,
723 loaded_per_epoch_config_objects,
724 loaded_system_objects,
725 execution_results: _,
727 invariants: _,
728 } = self;
729 let mut bump_only = Self {
730 store,
731 tx_digest,
732 input_objects,
733 non_exclusive_input_original_versions,
734 stream_ended_consensus_objects,
735 lamport_timestamp,
736 mutable_input_refs,
737 receiving_objects,
738 cur_epoch,
739 protocol_config,
740 loaded_runtime_objects,
741 runtime_packages_loaded_from_db,
742 loaded_per_epoch_config_objects,
743 post_execution_check_inputs,
744 system_object_versions,
745 loaded_system_objects,
746 execution_results: ExecutionResultsV2::default(),
747 invariants: InvariantChecker::default(),
748 };
749 bump_only.ensure_active_inputs_mutated();
751 bump_only
752 }
753
754 pub fn read_object(&self, id: &ObjectID) -> Option<&Object> {
755 debug_assert!(!self.execution_results.deleted_object_ids.contains(id));
757 self.execution_results
758 .written_objects
759 .get(id)
760 .or_else(|| self.input_objects.get(id))
761 }
762
763 pub fn save_loaded_runtime_objects(
764 &mut self,
765 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
766 ) {
767 #[cfg(debug_assertions)]
768 {
769 for (id, v1) in &loaded_runtime_objects {
770 if let Some(v2) = self.loaded_runtime_objects.get(id) {
771 assert_eq!(v1, v2);
772 }
773 }
774 for (id, v1) in &self.loaded_runtime_objects {
775 if let Some(v2) = loaded_runtime_objects.get(id) {
776 assert_eq!(v1, v2);
777 }
778 }
779 }
780 self.loaded_runtime_objects.extend(loaded_runtime_objects);
783 }
784
785 pub fn save_wrapped_object_containers(
786 &mut self,
787 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
788 ) {
789 self.invariants
790 .save_wrapped_object_containers(wrapped_object_containers);
791 }
792
793 pub fn save_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
794 self.invariants.save_generated_object_ids(generated_ids);
795 }
796
797 pub fn estimate_effects_size_upperbound(&self) -> usize {
798 TransactionEffects::estimate_effects_size_upperbound_v2(
799 self.execution_results.written_objects.len(),
800 self.execution_results.modified_objects.len(),
801 self.input_objects.len(),
802 )
803 }
804
805 pub fn written_objects_size(&self) -> usize {
806 self.execution_results
807 .written_objects
808 .values()
809 .fold(0, |sum, obj| sum + obj.object_size_for_gas_metering())
810 }
811
812 pub(crate) fn check_gasless_execution_requirements(&self) -> Result<(), String> {
815 use sui_types::balance::Balance;
816
817 let withdrawal_reservations = self
820 .post_execution_check_inputs
821 .input_reservations
822 .iter()
823 .filter_map(|((owner, ty), amount)| {
824 Balance::maybe_get_balance_type_param(ty)
825 .map(|coin_type| ((*owner, coin_type), *amount))
826 })
827 .collect();
828 self.check_gasless_execution_requirements_with_reservations(Some(&withdrawal_reservations))
829 }
830
831 pub(crate) fn check_gasless_execution_requirements_with_reservations(
842 &self,
843 withdrawal_reservations: Option<&BTreeMap<(SuiAddress, TypeTag), u64>>,
844 ) -> Result<(), String> {
845 if !self.execution_results.written_objects.is_empty() {
846 return Err("Gasless transactions cannot create or mutate objects".to_string());
847 }
848
849 let input_coin_ids: BTreeSet<ObjectID> = self
850 .input_objects
851 .iter()
852 .filter(|(_, obj)| obj.coin_type_maybe().is_some())
853 .map(|(id, _)| *id)
854 .collect();
855 if self.execution_results.deleted_object_ids != input_coin_ids {
856 return Err(format!(
857 "Gasless transaction must destroy exactly its input Coins. \
858 Expected: {input_coin_ids:?}, deleted: {:?}",
859 self.execution_results.deleted_object_ids
860 ));
861 }
862
863 let allowed_types =
864 sui_types::transaction::get_gasless_allowed_token_types(self.protocol_config);
865
866 let net_totals = sui_types::balance_change::signed_balance_changes_from_events(
869 &self.execution_results.accumulator_events,
870 )
871 .fold(
872 BTreeMap::<(SuiAddress, TypeTag), i128>::new(),
873 |mut totals, (address, token_type, signed_amount)| {
874 *totals.entry((address, token_type)).or_default() += signed_amount;
875 totals
876 },
877 );
878
879 for ((recipient, token_type), net_amount) in &net_totals {
880 if *net_amount <= 0 {
881 continue;
882 }
883 if let Some(&min_amount) = allowed_types.get(token_type)
884 && *net_amount < i128::from(min_amount)
885 {
886 return Err(format!(
887 "Gasless transfer of {net_amount} to {recipient} is below \
888 minimum {min_amount} for token type {token_type}"
889 ));
890 }
891 }
892
893 if let Some(reservations) = withdrawal_reservations {
894 for ((owner, token_type), &reserved) in reservations {
895 let net = net_totals
896 .get(&(*owner, token_type.clone()))
897 .copied()
898 .unwrap_or(0);
899 let remaining = (reserved as i128).saturating_add(net);
900 if remaining > 0
901 && let Some(&min_balance_remaining) = allowed_types.get(token_type)
902 && min_balance_remaining > 0
903 && remaining < min_balance_remaining as i128
904 {
905 return Err(format!(
906 "Gasless withdrawal leaves {remaining} unused for {owner}, \
907 below minimum {min_balance_remaining} for token type {token_type}"
908 ));
909 }
910 }
911 }
912
913 Ok(())
914 }
915
916 pub fn conserve_unmetered_storage_rebate(&mut self, unmetered_storage_rebate: u64) {
921 if unmetered_storage_rebate == 0 {
922 return;
926 }
927 tracing::debug!(
928 "Amount of unmetered storage rebate from system tx: {:?}",
929 unmetered_storage_rebate
930 );
931 let mut system_state_wrapper = self
932 .read_object(&SUI_SYSTEM_STATE_OBJECT_ID)
933 .expect("0x5 object must be mutated in system tx with unmetered storage rebate")
934 .clone();
935 assert_eq!(system_state_wrapper.storage_rebate, 0);
938 system_state_wrapper.storage_rebate = unmetered_storage_rebate;
939 self.mutate_input_object(system_state_wrapper);
940 }
941
942 pub fn add_accumulator_event(&mut self, event: AccumulatorEvent) {
944 self.execution_results.accumulator_events.push(event);
945 }
946
947 fn get_object_modified_at(
953 &self,
954 object_id: &ObjectID,
955 ) -> Option<DynamicallyLoadedObjectMetadata> {
956 if self.execution_results.modified_objects.contains(object_id) {
957 Some(
958 self.mutable_input_refs
959 .get(object_id)
960 .map(
961 |((version, digest), owner)| DynamicallyLoadedObjectMetadata {
962 version: *version,
963 digest: *digest,
964 owner: owner.clone(),
965 storage_rebate: self.input_objects[object_id].storage_rebate,
967 previous_transaction: self.input_objects[object_id]
968 .previous_transaction,
969 },
970 )
971 .or_else(|| self.loaded_runtime_objects.get(object_id).cloned())
972 .unwrap_or_else(|| {
973 debug_assert!(is_system_package(*object_id));
974 let package_obj =
975 self.store.get_package_object(object_id).unwrap().unwrap();
976 let obj = package_obj.object();
977 DynamicallyLoadedObjectMetadata {
978 version: obj.version(),
979 digest: obj.digest(),
980 owner: obj.owner.clone(),
981 storage_rebate: obj.storage_rebate,
982 previous_transaction: obj.previous_transaction,
983 }
984 }),
985 )
986 } else {
987 None
988 }
989 }
990
991 pub fn protocol_config(&self) -> &'backing ProtocolConfig {
992 self.protocol_config
993 }
994
995 pub(crate) fn check_conservation_invariants<Mode: ExecutionMode>(
998 &self,
999 move_vm: &Arc<MoveRuntime>,
1000 enable_expensive_checks: bool,
1001 cost_summary: &GasCostSummary,
1002 ) -> Result<(), ExecutionError> {
1003 self.invariants.check_conservation_invariants::<Mode>(
1004 self,
1005 move_vm,
1006 enable_expensive_checks,
1007 cost_summary,
1008 )
1009 }
1010
1011 pub(crate) fn check_published_packages(&self) -> Result<(), ExecutionError> {
1015 self.invariants.check_published_packages(self)
1016 }
1017
1018 pub(crate) fn check_ownership_invariants(
1019 &self,
1020 sender: &SuiAddress,
1021 sponsor: &Option<SuiAddress>,
1022 gas_charger: &GasCharger,
1023 is_epoch_change: bool,
1024 ) -> SuiResult<()> {
1025 self.invariants.check_ownership_invariants(
1026 self,
1027 sender,
1028 sponsor,
1029 gas_charger,
1030 is_epoch_change,
1031 )
1032 }
1033}
1034
1035impl TemporaryStore<'_> {
1036 pub(crate) fn collect_storage_and_rebate(
1043 &mut self,
1044 gas_charger: &mut GasCharger,
1045 ) -> Result<(), ExecutionError> {
1046 let old_storage_rebates: Vec<_> = self
1048 .execution_results
1049 .written_objects
1050 .keys()
1051 .map(|object_id| {
1052 self.get_object_modified_at(object_id)
1053 .map(|metadata| metadata.storage_rebate)
1054 .unwrap_or_default()
1055 })
1056 .collect();
1057 for (object, old_storage_rebate) in self
1058 .execution_results
1059 .written_objects
1060 .values_mut()
1061 .zip_debug_eq(old_storage_rebates)
1062 {
1063 let new_object_size = object.object_size_for_gas_metering();
1065 let new_storage_rebate = gas_charger
1067 .track_storage_mutation(object.id(), new_object_size, old_storage_rebate)
1068 .ok_or_else(|| ExecutionError::from_kind(ExecutionErrorKind::InvariantViolation))?;
1069 object.storage_rebate = new_storage_rebate;
1070 }
1071
1072 self.collect_rebate(gas_charger)
1073 }
1074
1075 pub(crate) fn collect_rebate(
1076 &self,
1077 gas_charger: &mut GasCharger,
1078 ) -> Result<(), ExecutionError> {
1079 for object_id in &self.execution_results.modified_objects {
1080 if self
1081 .execution_results
1082 .written_objects
1083 .contains_key(object_id)
1084 {
1085 continue;
1086 }
1087 let storage_rebate = self
1089 .get_object_modified_at(object_id)
1090 .unwrap()
1092 .storage_rebate;
1093 gas_charger
1094 .track_storage_mutation(*object_id, 0, storage_rebate)
1095 .ok_or_else(|| ExecutionError::from_kind(ExecutionErrorKind::InvariantViolation))?;
1096 }
1097 Ok(())
1098 }
1099
1100 pub fn check_execution_results_consistency<Mode: ExecutionMode>(
1101 &self,
1102 ) -> Result<(), Mode::Error> {
1103 assert_invariant!(
1104 self.execution_results
1105 .created_object_ids
1106 .iter()
1107 .all(|id| !self.execution_results.deleted_object_ids.contains(id)
1108 && !self.execution_results.modified_objects.contains(id)),
1109 "Created object IDs cannot also be deleted or modified"
1110 );
1111 assert_invariant!(
1112 self.execution_results.modified_objects.iter().all(|id| {
1113 self.mutable_input_refs.contains_key(id)
1114 || self.loaded_runtime_objects.contains_key(id)
1115 || is_system_package(*id)
1116 }),
1117 "A modified object must be either a mutable input, a loaded child object, or a system package"
1118 );
1119 Ok(())
1120 }
1121}
1122impl TemporaryStore<'_> {
1127 pub fn advance_epoch_safe_mode(
1128 &mut self,
1129 params: &AdvanceEpochParams,
1130 protocol_config: &ProtocolConfig,
1131 ) {
1132 let wrapper = get_sui_system_state_wrapper(self.store)
1133 .expect("System state wrapper object must exist");
1134 let (old_object, new_object) =
1135 wrapper.advance_epoch_safe_mode(params, self.store, protocol_config);
1136 self.mutate_child_object(old_object, new_object);
1137 }
1138}
1139
1140impl RuntimeObjectResolver for TemporaryStore<'_> {
1141 fn read_child_object(
1142 &self,
1143 parent: &ObjectID,
1144 child: &ObjectID,
1145 child_version_upper_bound: SequenceNumber,
1146 ) -> SuiResult<Option<Object>> {
1147 let obj_opt = self.execution_results.written_objects.get(child);
1148 if obj_opt.is_some() {
1149 Ok(obj_opt.cloned())
1150 } else {
1151 let _scope = monitored_scope("Execution::read_child_object");
1152 self.store
1153 .read_child_object(parent, child, child_version_upper_bound)
1154 }
1155 }
1156
1157 fn get_object_received_at_version(
1158 &self,
1159 owner: &ObjectID,
1160 receiving_object_id: &ObjectID,
1161 receive_object_at_version: SequenceNumber,
1162 epoch_id: EpochId,
1163 ) -> SuiResult<Option<Object>> {
1164 debug_assert!(
1167 !self
1168 .execution_results
1169 .written_objects
1170 .contains_key(receiving_object_id)
1171 );
1172 debug_assert!(
1173 !self
1174 .execution_results
1175 .deleted_object_ids
1176 .contains(receiving_object_id)
1177 );
1178 self.store.get_object_received_at_version(
1179 owner,
1180 receiving_object_id,
1181 receive_object_at_version,
1182 epoch_id,
1183 )
1184 }
1185}
1186
1187fn compute_input_reservations(
1196 transaction_kind: &TransactionKind,
1197 gas_data: &GasData,
1198 transaction_signer: SuiAddress,
1199 enable_gasless: bool,
1200) -> (BTreeMap<(SuiAddress, TypeTag), u64>, AllowanceIds) {
1201 use sui_types::balance::Balance;
1202 use sui_types::gas_coin::GAS;
1203 use sui_types::transaction::{Reservation, WithdrawFrom, is_gas_paid_from_address_balance};
1204
1205 let is_gasless = enable_gasless && is_gasless_transaction(gas_data, transaction_kind);
1206 let mut reservations: BTreeMap<(SuiAddress, TypeTag), u64> = BTreeMap::new();
1207 let mut allowance_ids = AllowanceIds::new();
1208 let sui_balance_type = Balance::type_tag(GAS::type_tag());
1209
1210 for arg in transaction_kind.get_funds_withdrawals() {
1211 let ty = arg.type_arg.to_type_tag();
1212 let owner = match arg.withdraw_from {
1213 WithdrawFrom::Sender => transaction_signer,
1214 WithdrawFrom::Sponsor => gas_data.owner,
1215 WithdrawFrom::SenderAllowance { funder, allowance } => {
1218 allowance_ids
1219 .entry((funder, ty.clone()))
1220 .or_default()
1221 .push(allowance);
1222 funder
1223 }
1224 };
1225 let Reservation::MaxAmountU64(reservation) = arg.reservation;
1226 let entry = reservations.entry((owner, ty)).or_insert(0);
1227 *entry = entry.saturating_add(reservation);
1228 }
1229
1230 if !is_gasless && is_gas_paid_from_address_balance(gas_data, transaction_kind) {
1233 let entry = reservations
1234 .entry((gas_data.owner, sui_balance_type.clone()))
1235 .or_insert(0);
1236 *entry = entry.saturating_add(gas_data.budget);
1237 }
1238
1239 for entry in &gas_data.payment {
1240 if let Ok(parsed) = ParsedDigest::try_from(entry.2) {
1241 let entry = reservations
1242 .entry((gas_data.owner, sui_balance_type.clone()))
1243 .or_insert(0);
1244 *entry = entry.saturating_add(parsed.reservation_amount());
1245 }
1246 }
1247
1248 (reservations, allowance_ids)
1249}
1250
1251fn declared_packages(
1254 transaction_kind: &TransactionKind,
1255) -> Option<Vec<(usize, BTreeSet<ObjectID>)>> {
1256 let TransactionKind::ProgrammableTransaction(pt) = transaction_kind else {
1257 return None;
1258 };
1259 Some(
1260 pt.commands
1261 .iter()
1262 .filter_map(|command| match command {
1263 Command::Publish(modules, dep_ids) | Command::Upgrade(modules, dep_ids, _, _) => {
1264 Some((modules.len(), dep_ids.iter().copied().collect()))
1265 }
1266 _ => None,
1267 })
1268 .collect(),
1269 )
1270}
1271
1272fn was_object_mutated(object: &Object, original: &Object) -> bool {
1275 let data_equal = match (&object.data, &original.data) {
1276 (Data::Move(a), Data::Move(b)) => a.contents_and_type_equal(b),
1277 (Data::Package(a), Data::Package(b)) => a == b,
1280 _ => false,
1281 };
1282
1283 let owner_equal = match (&object.owner, &original.owner) {
1284 (Owner::Shared { .. }, Owner::Shared { .. }) => true,
1288 (
1289 Owner::ConsensusAddressOwner { owner: a, .. },
1290 Owner::ConsensusAddressOwner { owner: b, .. },
1291 ) => a == b,
1292 (Owner::AddressOwner(a), Owner::AddressOwner(b)) => a == b,
1293 (Owner::Immutable, Owner::Immutable) => true,
1294 (Owner::ObjectOwner(a), Owner::ObjectOwner(b)) => a == b,
1295 (
1296 Owner::Party {
1297 permissions: a,
1298 start_version: _,
1299 },
1300 Owner::Party {
1301 permissions: b,
1302 start_version: _,
1303 },
1304 ) => a == b,
1305
1306 (Owner::AddressOwner(_), _)
1309 | (Owner::Immutable, _)
1310 | (Owner::ObjectOwner(_), _)
1311 | (Owner::Shared { .. }, _)
1312 | (Owner::ConsensusAddressOwner { .. }, _)
1313 | (Owner::Party { .. }, _) => false,
1314 };
1315
1316 !data_equal || !owner_equal
1317}
1318
1319impl Storage for TemporaryStore<'_> {
1320 fn reset(&mut self) {
1321 self.drop_writes();
1322 }
1323
1324 fn read_object(&self, id: &ObjectID) -> Option<&Object> {
1325 TemporaryStore::read_object(self, id)
1326 }
1327
1328 fn record_execution_results(
1330 &mut self,
1331 results: ExecutionResults,
1332 ) -> Result<(), ExecutionError> {
1333 let ExecutionResults::V2(mut results) = results else {
1334 panic!("ExecutionResults::V2 expected in sui-execution v1 and above");
1335 };
1336
1337 let mut to_remove = Vec::new();
1339 for (id, original) in &self.non_exclusive_input_original_versions {
1340 if results
1342 .written_objects
1343 .get(id)
1344 .map(|obj| was_object_mutated(obj, original))
1345 .unwrap_or(true)
1346 {
1347 return Err(ExecutionError::new_with_source(
1348 ExecutionErrorKind::NonExclusiveWriteInputObjectModified { id: *id },
1349 "Non-exclusive write input object has been modified or deleted",
1350 ));
1351 }
1352 to_remove.push(*id);
1353 }
1354
1355 for id in to_remove {
1356 results.written_objects.remove(&id);
1357 results.modified_objects.remove(&id);
1358 }
1359
1360 let event_start = self.execution_results.accumulator_events.len();
1366 self.execution_results.merge_results(
1367 results, true, true,
1368 )?;
1369 let event_end = self.execution_results.accumulator_events.len();
1370 self.invariants
1371 .record_ptb_event_range(event_start, event_end);
1372
1373 Ok(())
1374 }
1375
1376 fn save_loaded_runtime_objects(
1377 &mut self,
1378 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
1379 ) {
1380 TemporaryStore::save_loaded_runtime_objects(self, loaded_runtime_objects)
1381 }
1382
1383 fn save_wrapped_object_containers(
1384 &mut self,
1385 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
1386 ) {
1387 TemporaryStore::save_wrapped_object_containers(self, wrapped_object_containers)
1388 }
1389
1390 fn check_coin_deny_list(
1391 &self,
1392 receiving_funds_type_and_owners: BTreeMap<TypeTag, BTreeSet<SuiAddress>>,
1393 ) -> DenyListResult {
1394 let result = check_coin_deny_list_v2_during_execution(
1395 receiving_funds_type_and_owners,
1396 self.cur_epoch,
1397 self.store,
1398 );
1399 if result.num_non_gas_coin_owners > 0
1402 && !self.input_objects.contains_key(&SUI_DENY_LIST_OBJECT_ID)
1403 {
1404 self.loaded_per_epoch_config_objects
1405 .write()
1406 .insert(SUI_DENY_LIST_OBJECT_ID);
1407 }
1408 result
1409 }
1410
1411 fn record_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
1412 TemporaryStore::save_generated_object_ids(self, generated_ids)
1413 }
1414}
1415
1416impl BackingPackageStore for TemporaryStore<'_> {
1417 fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
1418 if let Some(obj) = self.execution_results.written_objects.get(package_id) {
1425 Ok(Some(PackageObject::new(obj.clone())))
1426 } else {
1427 self.store.get_package_object(package_id).inspect(|obj| {
1428 if let Some(v) = obj
1430 && !self
1431 .runtime_packages_loaded_from_db
1432 .read()
1433 .contains_key(package_id)
1434 {
1435 self.runtime_packages_loaded_from_db
1440 .write()
1441 .insert(*package_id, v.clone());
1442 }
1443 })
1444 }
1445 }
1446}