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::{
16 AccumulatorObjId, AccumulatorValue as AccumulatorRootValue, EmptyUnsettledObjectFunds,
17 UnsettledObjectFundsRead,
18};
19use sui_types::base_types::{SystemObjectVersions, VersionDigest};
20use sui_types::coin_reservation::ParsedDigest;
21use sui_types::committee::EpochId;
22use sui_types::deny_list_v2::check_coin_deny_list_v2_during_execution;
23use sui_types::effects::{
24 AccumulatorOperation, AccumulatorValue, AccumulatorWriteV1, TransactionEffects,
25 TransactionEffectsV2, TransactionEvents,
26};
27use sui_types::error::SuiErrorKind;
28use sui_types::execution::{
29 DynamicallyLoadedObjectMetadata, ExecutionResults, ExecutionResultsV2, SharedInput,
30};
31use sui_types::execution_status::{ExecutionErrorKind, ExecutionStatus};
32use sui_types::inner_temporary_store::InnerTemporaryStore;
33use sui_types::object::Data;
34use sui_types::storage::{BackingStore, DenyListResult, ObjectFundsResolver, PackageObject};
35use sui_types::sui_system_state::{AdvanceEpochParams, get_sui_system_state_wrapper};
36use sui_types::transaction::{Command, GasData, TransactionKind, is_gasless_transaction};
37use sui_types::{
38 SUI_ACCUMULATOR_ROOT_OBJECT_ID, SUI_DENY_LIST_OBJECT_ID,
39 base_types::{ObjectID, ObjectRef, SequenceNumber, SuiAddress, TransactionDigest},
40 digests::ObjectDigest,
41 effects::EffectsObjectChange,
42 error::{ExecutionError, SuiResult},
43 gas::GasCostSummary,
44 object::Object,
45 object::Owner,
46 storage::{BackingPackageStore, RuntimeObjectResolver, Storage},
47 transaction::InputObjects,
48};
49use sui_types::{SUI_SYSTEM_STATE_OBJECT_ID, TypeTag, is_system_package};
50
51pub(crate) mod invariants;
52use invariants::InvariantChecker;
53
54type AllowanceIds = BTreeMap<(SuiAddress, TypeTag), Vec<ObjectID>>;
56
57#[derive(Default)]
58struct PostExecutionCheckInputs {
59 input_reservations: BTreeMap<(SuiAddress, TypeTag), u64>,
62 allowance_ids: AllowanceIds,
65 advance_epoch_gas_summary: Option<(u64, u64)>,
68 is_genesis: bool,
70 declared_packages: Option<Vec<(usize, BTreeSet<ObjectID>)>>,
73}
74
75impl PostExecutionCheckInputs {
76 fn new(transaction: (&TransactionKind, &GasData, SuiAddress), enable_gasless: bool) -> Self {
77 let (transaction_kind, gas_data, transaction_signer) = transaction;
78 let (input_reservations, allowance_ids) = compute_input_reservations(
79 transaction_kind,
80 gas_data,
81 transaction_signer,
82 enable_gasless,
83 );
84 Self {
85 input_reservations,
86 allowance_ids,
87 advance_epoch_gas_summary: transaction_kind.get_advance_epoch_tx_gas_summary(),
88 is_genesis: matches!(transaction_kind, TransactionKind::Genesis(_)),
89 declared_packages: declared_packages(transaction_kind),
90 }
91 }
92}
93
94pub struct TemporaryStore<'backing> {
95 store: &'backing dyn BackingStore,
101 tx_digest: TransactionDigest,
102 input_objects: BTreeMap<ObjectID, Object>,
103 post_execution_check_inputs: PostExecutionCheckInputs,
106
107 non_exclusive_input_original_versions: BTreeMap<ObjectID, Object>,
110
111 stream_ended_consensus_objects: BTreeMap<ObjectID, SequenceNumber >,
112 lamport_timestamp: SequenceNumber,
114 mutable_input_refs: BTreeMap<ObjectID, (VersionDigest, Owner)>,
117 execution_results: ExecutionResultsV2,
118 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
120 protocol_config: &'backing ProtocolConfig,
121
122 runtime_packages_loaded_from_db: RwLock<BTreeMap<ObjectID, PackageObject>>,
125
126 receiving_objects: Vec<ObjectRef>,
129
130 cur_epoch: EpochId,
133
134 loaded_per_epoch_config_objects: RwLock<BTreeSet<ObjectID>>,
137
138 invariants: InvariantChecker,
140
141 system_object_versions: SystemObjectVersions,
143
144 loaded_system_objects: RefCell<BTreeMap<ObjectID, (SequenceNumber, ObjectDigest)>>,
148
149 unsettled_object_funds: &'backing dyn UnsettledObjectFundsRead,
150}
151
152impl<'backing> TemporaryStore<'backing> {
153 #[allow(clippy::too_many_arguments)]
156 pub(crate) fn new(
157 store: &'backing dyn BackingStore,
158 input_objects: InputObjects,
159 receiving_objects: Vec<ObjectRef>,
160 tx_digest: TransactionDigest,
161 protocol_config: &'backing ProtocolConfig,
162 cur_epoch: EpochId,
163 system_object_versions: SystemObjectVersions,
164 transaction: (&TransactionKind, &GasData, SuiAddress),
165 unsettled_object_funds: &'backing dyn UnsettledObjectFundsRead,
166 ) -> Self {
167 let post_execution_check_inputs =
168 PostExecutionCheckInputs::new(transaction, protocol_config.enable_gasless());
169 Self::new_with_input_objects(
170 store,
171 input_objects,
172 receiving_objects,
173 tx_digest,
174 protocol_config,
175 cur_epoch,
176 system_object_versions,
177 post_execution_check_inputs,
178 unsettled_object_funds,
179 )
180 }
181
182 pub(crate) fn new_for_genesis_state_update(
183 store: &'backing dyn BackingStore,
184 tx_digest: TransactionDigest,
185 protocol_config: &'backing ProtocolConfig,
186 ) -> Self {
187 Self::new_with_input_objects(
188 store,
189 InputObjects::new(vec![]),
190 vec![],
191 tx_digest,
192 protocol_config,
193 0,
194 SystemObjectVersions::empty(),
195 PostExecutionCheckInputs {
196 is_genesis: true,
197 ..Default::default()
198 },
199 &EmptyUnsettledObjectFunds,
202 )
203 }
204
205 fn new_with_input_objects(
206 store: &'backing dyn BackingStore,
207 input_objects: InputObjects,
208 receiving_objects: Vec<ObjectRef>,
209 tx_digest: TransactionDigest,
210 protocol_config: &'backing ProtocolConfig,
211 cur_epoch: EpochId,
212 system_object_versions: SystemObjectVersions,
213 post_execution_check_inputs: PostExecutionCheckInputs,
214 unsettled_object_funds: &'backing dyn UnsettledObjectFundsRead,
215 ) -> Self {
216 let mutable_input_refs = input_objects.exclusive_mutable_inputs();
217 let non_exclusive_input_original_versions = input_objects.non_exclusive_input_objects();
218
219 let lamport_timestamp = input_objects.lamport_timestamp(&receiving_objects);
220 let stream_ended_consensus_objects = input_objects.consensus_stream_ended_objects();
221 let objects = input_objects.into_object_map();
222 #[cfg(debug_assertions)]
223 {
224 assert!(
226 objects
227 .keys()
228 .collect::<HashSet<_>>()
229 .intersection(
230 &receiving_objects
231 .iter()
232 .map(|oref| &oref.0)
233 .collect::<HashSet<_>>()
234 )
235 .next()
236 .is_none()
237 );
238 }
239 Self {
240 store,
241 tx_digest,
242 input_objects: objects,
243 non_exclusive_input_original_versions,
244 stream_ended_consensus_objects,
245 lamport_timestamp,
246 mutable_input_refs,
247 execution_results: ExecutionResultsV2::default(),
248 protocol_config,
249 loaded_runtime_objects: BTreeMap::new(),
250 runtime_packages_loaded_from_db: RwLock::new(BTreeMap::new()),
251 receiving_objects,
252 cur_epoch,
253 loaded_per_epoch_config_objects: RwLock::new(BTreeSet::new()),
254 post_execution_check_inputs,
255 invariants: InvariantChecker::default(),
256 system_object_versions,
257 loaded_system_objects: RefCell::new(BTreeMap::new()),
258 unsettled_object_funds,
259 }
260 }
261
262 pub fn load_implicitly_read_system_object(&self, object_id: &ObjectID) -> Option<Object> {
268 let version = match self.system_object_versions.get(object_id) {
269 Some(version) => version,
270 None => {
271 debug_fatal!(
272 "system_object_versions must contain entry for object_id: {:?}",
273 object_id
274 );
275 return None;
276 }
277 };
278 let object = self
279 .store
280 .load_implicitly_read_system_object(object_id, version)?;
283 self.loaded_system_objects
286 .borrow_mut()
287 .insert(*object_id, (object.version(), object.digest()));
288 Some(object)
289 }
290
291 pub fn unsettled_object_funds(&self) -> &dyn UnsettledObjectFundsRead {
292 self.unsettled_object_funds
293 }
294
295 pub fn objects(&self) -> &BTreeMap<ObjectID, Object> {
297 &self.input_objects
298 }
299
300 pub fn update_object_version_and_prev_tx(&mut self) {
301 self.execution_results.update_version_and_previous_tx(
302 self.lamport_timestamp,
303 self.tx_digest,
304 &self.input_objects,
305 self.protocol_config.reshare_at_same_initial_version(),
306 );
307
308 #[cfg(debug_assertions)]
309 {
310 self.check_invariants();
311 }
312 }
313
314 fn calculate_accumulator_running_max_withdraws(&self) -> BTreeMap<AccumulatorObjId, u128> {
315 let mut running_net_withdraws: BTreeMap<AccumulatorObjId, i128> = BTreeMap::new();
316 let mut running_max_withdraws: BTreeMap<AccumulatorObjId, u128> = BTreeMap::new();
317 for event in &self.execution_results.accumulator_events {
318 match &event.write.value {
319 AccumulatorValue::Integer(amount) => match event.write.operation {
320 AccumulatorOperation::Split => {
321 let entry = running_net_withdraws
322 .entry(event.accumulator_obj)
323 .or_default();
324 *entry += *amount as i128;
325 if *entry > 0 {
326 let max_entry = running_max_withdraws
327 .entry(event.accumulator_obj)
328 .or_default();
329 *max_entry = (*max_entry).max(*entry as u128);
330 }
331 }
332 AccumulatorOperation::Merge => {
333 let entry = running_net_withdraws
334 .entry(event.accumulator_obj)
335 .or_default();
336 *entry -= *amount as i128;
337 }
338 },
339 AccumulatorValue::IntegerTuple(_, _) | AccumulatorValue::EventDigest(_) => {}
340 }
341 }
342 running_max_withdraws
343 }
344
345 pub(crate) fn check_accumulator_amounts_representable(&self) -> Result<(), ExecutionError> {
373 let supply = sui_types::gas_coin::TOTAL_SUPPLY_MIST as u128;
374 let mut merge_totals: BTreeMap<AccumulatorObjId, u128> = BTreeMap::new();
375 let mut split_totals: BTreeMap<AccumulatorObjId, u128> = BTreeMap::new();
376 let mut total_sui_split: u128 = 0;
378 for event in &self.execution_results.accumulator_events {
379 let AccumulatorValue::Integer(amount) = event.write.value else {
380 continue;
381 };
382 let amount = amount as u128;
383 let is_sui = sui_types::gas_coin::GasCoin::is_gas_balance_type(&event.write.address.ty);
386 let limit = if is_sui { supply } else { u64::MAX as u128 };
387 let total = match event.write.operation {
388 AccumulatorOperation::Merge => {
389 merge_totals.entry(event.accumulator_obj).or_default()
390 }
391 AccumulatorOperation::Split => {
392 split_totals.entry(event.accumulator_obj).or_default()
393 }
394 };
395 *total += amount;
396 if *total > limit {
397 return Err(ExecutionError::new_with_source(
398 ExecutionErrorKind::CoinBalanceOverflow,
399 format!(
400 "accumulator balance change for {:?} exceeds the representable limit \
401 (gross total {}, limit {})",
402 event.accumulator_obj, *total, limit
403 ),
404 ));
405 }
406 if is_sui && matches!(event.write.operation, AccumulatorOperation::Split) {
407 total_sui_split += amount;
408 if total_sui_split > supply {
409 return Err(ExecutionError::new_with_source(
410 ExecutionErrorKind::CoinBalanceOverflow,
411 format!(
412 "total SUI withdrawn across all accumulators ({total_sui_split}) \
413 exceeds the total supply ({supply})"
414 ),
415 ));
416 }
417 }
418 }
419 Ok(())
420 }
421
422 fn merge_accumulator_events(&mut self) {
424 self.execution_results.accumulator_events = self
425 .execution_results
426 .accumulator_events
427 .iter()
428 .fold(
429 BTreeMap::<AccumulatorObjId, Vec<AccumulatorWriteV1>>::new(),
430 |mut map, event| {
431 map.entry(event.accumulator_obj)
432 .or_default()
433 .push(event.write.clone());
434 map
435 },
436 )
437 .into_iter()
438 .map(|(obj_id, writes)| {
439 AccumulatorEvent::new(obj_id, AccumulatorWriteV1::merge(writes))
440 })
441 .collect();
442 }
443
444 pub fn into_inner(
446 self,
447 accumulator_running_max_withdraws: BTreeMap<AccumulatorObjId, u128>,
448 ) -> InnerTemporaryStore {
449 let results = self.execution_results;
450 InnerTemporaryStore {
451 input_objects: self.input_objects,
452 stream_ended_consensus_objects: self.stream_ended_consensus_objects,
453 mutable_inputs: self.mutable_input_refs,
454 written: results.written_objects,
455 events: TransactionEvents {
456 data: results.user_events,
457 },
458 accumulator_events: results.accumulator_events,
459 loaded_runtime_objects: self.loaded_runtime_objects,
460 runtime_packages_loaded_from_db: self.runtime_packages_loaded_from_db.into_inner(),
461 lamport_version: self.lamport_timestamp,
462 binary_config: self.protocol_config.binary_config(None),
463 accumulator_running_max_withdraws,
464 }
465 }
466
467 pub(crate) fn ensure_active_inputs_mutated(&mut self) {
471 let mut to_be_updated = vec![];
472 for id in self.mutable_input_refs.keys() {
474 if !self.execution_results.modified_objects.contains(id) {
475 to_be_updated.push(self.input_objects[id].clone());
479 }
480 }
481 for object in to_be_updated {
482 self.mutate_input_object(object.clone());
484 }
485 }
486
487 fn get_object_changes(&self) -> BTreeMap<ObjectID, EffectsObjectChange> {
488 let results = &self.execution_results;
489 let all_ids = results
490 .created_object_ids
491 .iter()
492 .chain(&results.deleted_object_ids)
493 .chain(&results.modified_objects)
494 .chain(results.written_objects.keys())
495 .collect::<BTreeSet<_>>();
496 all_ids
497 .into_iter()
498 .map(|id| {
499 (
500 *id,
501 EffectsObjectChange::new(
502 self.get_object_modified_at(id)
503 .map(|metadata| ((metadata.version, metadata.digest), metadata.owner)),
504 results.written_objects.get(id),
505 results.created_object_ids.contains(id),
506 results.deleted_object_ids.contains(id),
507 ),
508 )
509 })
510 .chain(results.accumulator_events.iter().cloned().map(
511 |AccumulatorEvent {
512 accumulator_obj,
513 write,
514 }| {
515 (
516 *accumulator_obj.inner(),
517 EffectsObjectChange::new_from_accumulator_write(write),
518 )
519 },
520 ))
521 .collect()
522 }
523
524 pub fn into_effects(
525 mut self,
526 shared_object_refs: Vec<SharedInput>,
527 transaction_digest: &TransactionDigest,
528 mut transaction_dependencies: BTreeSet<TransactionDigest>,
529 gas_cost_summary: GasCostSummary,
530 status: ExecutionStatus,
531 gas_coin: Option<ObjectID>,
532 epoch: EpochId,
533 ) -> (InnerTemporaryStore, TransactionEffects) {
534 for (id, obj) in &self.execution_results.written_objects {
537 assert!(
538 !matches!(obj.owner, Owner::Party { .. }),
539 "Party-owned objects are not yet supported (object {id})"
540 );
541 }
542
543 self.update_object_version_and_prev_tx();
544 let accumulator_running_max_withdraws = self.calculate_accumulator_running_max_withdraws();
546 self.merge_accumulator_events();
547
548 if !self.protocol_config.disable_effects_tx_dependencies() {
549 for (id, expected_version, expected_digest) in &self.receiving_objects {
551 if let Some(obj_meta) = self.loaded_runtime_objects.get(id) {
552 let loaded_via_receive = obj_meta.version == *expected_version
555 && obj_meta.digest == *expected_digest
556 && obj_meta.owner.is_address_owned();
557 if loaded_via_receive {
558 transaction_dependencies.insert(obj_meta.previous_transaction);
559 }
560 }
561 }
562 }
563
564 assert!(self.protocol_config.enable_effects_v2());
565
566 let object_changes = self.get_object_changes();
567
568 let lamport_version = self.lamport_timestamp;
569 let loaded_per_epoch_config_objects = self.loaded_per_epoch_config_objects.read().clone();
571 let loaded_system_objects = self.loaded_system_objects.borrow().clone();
572 let unchanged_consensus_objects = TransactionEffectsV2::compute_unchanged_consensus_objects(
573 shared_object_refs,
574 loaded_per_epoch_config_objects,
575 &object_changes,
576 loaded_system_objects,
577 );
578 let inner = self.into_inner(accumulator_running_max_withdraws);
579
580 let effects = TransactionEffects::new_from_execution_v2(
581 status,
582 epoch,
583 gas_cost_summary,
584 unchanged_consensus_objects,
585 *transaction_digest,
586 lamport_version,
587 object_changes,
588 gas_coin,
589 if inner.events.data.is_empty() {
590 None
591 } else {
592 Some(inner.events.digest())
593 },
594 transaction_dependencies.into_iter().collect(),
595 );
596
597 (inner, effects)
598 }
599
600 #[cfg(debug_assertions)]
602 fn check_invariants(&self) {
603 debug_assert!(
605 {
606 self.execution_results
607 .written_objects
608 .keys()
609 .all(|id| !self.execution_results.deleted_object_ids.contains(id))
610 },
611 "Object both written and deleted."
612 );
613
614 debug_assert!(
616 {
617 self.mutable_input_refs
618 .keys()
619 .all(|id| self.execution_results.modified_objects.contains(id))
620 },
621 "Mutable input not modified."
622 );
623
624 debug_assert!(
625 {
626 self.execution_results
627 .written_objects
628 .values()
629 .all(|obj| obj.previous_transaction == self.tx_digest)
630 },
631 "Object previous transaction not properly set",
632 );
633 }
634
635 pub fn mutate_input_object(&mut self, object: Object) {
637 let id = object.id();
638 debug_assert!(self.input_objects.contains_key(&id));
639 debug_assert!(!object.is_immutable());
640 self.execution_results.modified_objects.insert(id);
641 self.execution_results.written_objects.insert(id, object);
642 }
643
644 pub fn mutate_new_or_input_object(&mut self, object: Object) {
645 let id = object.id();
646 debug_assert!(!object.is_immutable());
647 if self.input_objects.contains_key(&id) {
648 self.execution_results.modified_objects.insert(id);
649 }
650 self.execution_results.written_objects.insert(id, object);
651 }
652
653 pub fn mutate_child_object(&mut self, old_object: Object, new_object: Object) {
657 let id = new_object.id();
658 let old_ref = old_object.compute_object_reference();
659 debug_assert_eq!(old_ref.0, id);
660 self.loaded_runtime_objects.insert(
661 id,
662 DynamicallyLoadedObjectMetadata {
663 version: old_ref.1,
664 digest: old_ref.2,
665 owner: old_object.owner.clone(),
666 storage_rebate: old_object.storage_rebate,
667 previous_transaction: old_object.previous_transaction,
668 },
669 );
670 self.execution_results.modified_objects.insert(id);
671 self.execution_results
672 .written_objects
673 .insert(id, new_object);
674 }
675
676 pub fn upgrade_system_package(&mut self, package: Object) {
680 let id = package.id();
681 assert!(package.is_package() && is_system_package(id));
682 self.execution_results.modified_objects.insert(id);
683 self.execution_results.written_objects.insert(id, package);
684 }
685
686 pub fn create_object(&mut self, object: Object) {
688 debug_assert!(
693 object.is_immutable() || object.version() == SequenceNumber::MIN,
694 "Created mutable objects should not have a version set",
695 );
696 let id = object.id();
697 self.execution_results.created_object_ids.insert(id);
698 self.execution_results.written_objects.insert(id, object);
699 }
700
701 pub fn delete_input_object(&mut self, id: &ObjectID) {
703 debug_assert!(!self.execution_results.written_objects.contains_key(id));
705 debug_assert!(self.input_objects.contains_key(id));
706 self.execution_results.modified_objects.insert(*id);
707 self.execution_results.deleted_object_ids.insert(*id);
708 }
709
710 pub fn drop_writes(&mut self) {
711 self.execution_results.drop_writes();
712 self.invariants = InvariantChecker::default();
713 }
714
715 pub(crate) fn into_bump_only(self) -> Self {
720 let Self {
721 store,
723 tx_digest,
724 input_objects,
725 non_exclusive_input_original_versions,
726 stream_ended_consensus_objects,
727 lamport_timestamp,
728 mutable_input_refs,
729 receiving_objects,
730 cur_epoch,
731 protocol_config,
732 post_execution_check_inputs,
733 system_object_versions,
734 loaded_runtime_objects,
736 runtime_packages_loaded_from_db,
737 loaded_per_epoch_config_objects,
738 loaded_system_objects,
739 unsettled_object_funds,
740 execution_results: _,
742 invariants: _,
743 } = self;
744 let mut bump_only = Self {
745 store,
746 tx_digest,
747 input_objects,
748 non_exclusive_input_original_versions,
749 stream_ended_consensus_objects,
750 lamport_timestamp,
751 mutable_input_refs,
752 receiving_objects,
753 cur_epoch,
754 protocol_config,
755 loaded_runtime_objects,
756 runtime_packages_loaded_from_db,
757 loaded_per_epoch_config_objects,
758 post_execution_check_inputs,
759 system_object_versions,
760 loaded_system_objects,
761 unsettled_object_funds,
762 execution_results: ExecutionResultsV2::default(),
763 invariants: InvariantChecker::default(),
764 };
765 bump_only.ensure_active_inputs_mutated();
767 bump_only
768 }
769
770 pub fn read_object(&self, id: &ObjectID) -> Option<&Object> {
771 debug_assert!(!self.execution_results.deleted_object_ids.contains(id));
773 self.execution_results
774 .written_objects
775 .get(id)
776 .or_else(|| self.input_objects.get(id))
777 }
778
779 pub fn save_loaded_runtime_objects(
780 &mut self,
781 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
782 ) {
783 #[cfg(debug_assertions)]
784 {
785 for (id, v1) in &loaded_runtime_objects {
786 if let Some(v2) = self.loaded_runtime_objects.get(id) {
787 assert_eq!(v1, v2);
788 }
789 }
790 for (id, v1) in &self.loaded_runtime_objects {
791 if let Some(v2) = loaded_runtime_objects.get(id) {
792 assert_eq!(v1, v2);
793 }
794 }
795 }
796 self.loaded_runtime_objects.extend(loaded_runtime_objects);
799 }
800
801 pub fn save_wrapped_object_containers(
802 &mut self,
803 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
804 ) {
805 self.invariants
806 .save_wrapped_object_containers(wrapped_object_containers);
807 }
808
809 pub fn save_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
810 self.invariants.save_generated_object_ids(generated_ids);
811 }
812
813 pub fn estimate_effects_size_upperbound(&self) -> usize {
814 TransactionEffects::estimate_effects_size_upperbound_v2(
815 self.execution_results.written_objects.len(),
816 self.execution_results.modified_objects.len(),
817 self.input_objects.len(),
818 )
819 }
820
821 pub fn written_objects_size(&self) -> usize {
822 self.execution_results
823 .written_objects
824 .values()
825 .fold(0, |sum, obj| sum + obj.object_size_for_gas_metering())
826 }
827
828 pub(crate) fn check_gasless_execution_requirements(&self) -> Result<(), String> {
831 use sui_types::balance::Balance;
832
833 let withdrawal_reservations = self
836 .post_execution_check_inputs
837 .input_reservations
838 .iter()
839 .filter_map(|((owner, ty), amount)| {
840 Balance::maybe_get_balance_type_param(ty)
841 .map(|coin_type| ((*owner, coin_type), *amount))
842 })
843 .collect();
844 self.check_gasless_execution_requirements_with_reservations(Some(&withdrawal_reservations))
845 }
846
847 pub(crate) fn check_gasless_execution_requirements_with_reservations(
858 &self,
859 withdrawal_reservations: Option<&BTreeMap<(SuiAddress, TypeTag), u64>>,
860 ) -> Result<(), String> {
861 if !self.execution_results.written_objects.is_empty() {
862 return Err("Gasless transactions cannot create or mutate objects".to_string());
863 }
864
865 let input_coin_ids: BTreeSet<ObjectID> = self
866 .input_objects
867 .iter()
868 .filter(|(_, obj)| obj.coin_type_maybe().is_some())
869 .map(|(id, _)| *id)
870 .collect();
871 if self.execution_results.deleted_object_ids != input_coin_ids {
872 return Err(format!(
873 "Gasless transaction must destroy exactly its input Coins. \
874 Expected: {input_coin_ids:?}, deleted: {:?}",
875 self.execution_results.deleted_object_ids
876 ));
877 }
878
879 let allowed_types =
880 sui_types::transaction::get_gasless_allowed_token_types(self.protocol_config);
881
882 let net_totals = sui_types::balance_change::signed_balance_changes_from_events(
885 &self.execution_results.accumulator_events,
886 )
887 .fold(
888 BTreeMap::<(SuiAddress, TypeTag), i128>::new(),
889 |mut totals, (address, token_type, signed_amount)| {
890 *totals.entry((address, token_type)).or_default() += signed_amount;
891 totals
892 },
893 );
894
895 for ((recipient, token_type), net_amount) in &net_totals {
896 if *net_amount <= 0 {
897 continue;
898 }
899 if let Some(&min_amount) = allowed_types.get(token_type)
900 && *net_amount < i128::from(min_amount)
901 {
902 return Err(format!(
903 "Gasless transfer of {net_amount} to {recipient} is below \
904 minimum {min_amount} for token type {token_type}"
905 ));
906 }
907 }
908
909 if let Some(reservations) = withdrawal_reservations {
910 for ((owner, token_type), &reserved) in reservations {
911 let net = net_totals
912 .get(&(*owner, token_type.clone()))
913 .copied()
914 .unwrap_or(0);
915 let remaining = (reserved as i128).saturating_add(net);
916 if remaining > 0
917 && let Some(&min_balance_remaining) = allowed_types.get(token_type)
918 && min_balance_remaining > 0
919 && remaining < min_balance_remaining as i128
920 {
921 return Err(format!(
922 "Gasless withdrawal leaves {remaining} unused for {owner}, \
923 below minimum {min_balance_remaining} for token type {token_type}"
924 ));
925 }
926 }
927 }
928
929 Ok(())
930 }
931
932 pub fn conserve_unmetered_storage_rebate(&mut self, unmetered_storage_rebate: u64) {
937 if unmetered_storage_rebate == 0 {
938 return;
942 }
943 tracing::debug!(
944 "Amount of unmetered storage rebate from system tx: {:?}",
945 unmetered_storage_rebate
946 );
947 let mut system_state_wrapper = self
948 .read_object(&SUI_SYSTEM_STATE_OBJECT_ID)
949 .expect("0x5 object must be mutated in system tx with unmetered storage rebate")
950 .clone();
951 assert_eq!(system_state_wrapper.storage_rebate, 0);
954 system_state_wrapper.storage_rebate = unmetered_storage_rebate;
955 self.mutate_input_object(system_state_wrapper);
956 }
957
958 pub fn add_accumulator_event(&mut self, event: AccumulatorEvent) {
960 self.execution_results.accumulator_events.push(event);
961 }
962
963 fn get_object_modified_at(
969 &self,
970 object_id: &ObjectID,
971 ) -> Option<DynamicallyLoadedObjectMetadata> {
972 if self.execution_results.modified_objects.contains(object_id) {
973 Some(
974 self.mutable_input_refs
975 .get(object_id)
976 .map(
977 |((version, digest), owner)| DynamicallyLoadedObjectMetadata {
978 version: *version,
979 digest: *digest,
980 owner: owner.clone(),
981 storage_rebate: self.input_objects[object_id].storage_rebate,
983 previous_transaction: self.input_objects[object_id]
984 .previous_transaction,
985 },
986 )
987 .or_else(|| self.loaded_runtime_objects.get(object_id).cloned())
988 .unwrap_or_else(|| {
989 debug_assert!(is_system_package(*object_id));
990 let package_obj =
991 self.store.get_package_object(object_id).unwrap().unwrap();
992 let obj = package_obj.object();
993 DynamicallyLoadedObjectMetadata {
994 version: obj.version(),
995 digest: obj.digest(),
996 owner: obj.owner.clone(),
997 storage_rebate: obj.storage_rebate,
998 previous_transaction: obj.previous_transaction,
999 }
1000 }),
1001 )
1002 } else {
1003 None
1004 }
1005 }
1006
1007 pub fn protocol_config(&self) -> &'backing ProtocolConfig {
1008 self.protocol_config
1009 }
1010
1011 pub(crate) fn check_conservation_invariants<Mode: ExecutionMode>(
1014 &self,
1015 move_vm: &Arc<MoveRuntime>,
1016 enable_expensive_checks: bool,
1017 cost_summary: &GasCostSummary,
1018 ) -> Result<(), ExecutionError> {
1019 self.invariants.check_conservation_invariants::<Mode>(
1020 self,
1021 move_vm,
1022 enable_expensive_checks,
1023 cost_summary,
1024 )
1025 }
1026
1027 pub(crate) fn check_published_packages(&self) -> Result<(), ExecutionError> {
1031 self.invariants.check_published_packages(self)
1032 }
1033
1034 pub(crate) fn check_ownership_invariants(
1035 &self,
1036 sender: &SuiAddress,
1037 sponsor: &Option<SuiAddress>,
1038 gas_charger: &GasCharger,
1039 is_epoch_change: bool,
1040 ) -> SuiResult<()> {
1041 self.invariants.check_ownership_invariants(
1042 self,
1043 sender,
1044 sponsor,
1045 gas_charger,
1046 is_epoch_change,
1047 )
1048 }
1049}
1050
1051impl TemporaryStore<'_> {
1052 pub(crate) fn collect_storage_and_rebate(
1059 &mut self,
1060 gas_charger: &mut GasCharger,
1061 ) -> Result<(), ExecutionError> {
1062 let old_storage_rebates: Vec<_> = self
1064 .execution_results
1065 .written_objects
1066 .keys()
1067 .map(|object_id| {
1068 self.get_object_modified_at(object_id)
1069 .map(|metadata| metadata.storage_rebate)
1070 .unwrap_or_default()
1071 })
1072 .collect();
1073 for (object, old_storage_rebate) in self
1074 .execution_results
1075 .written_objects
1076 .values_mut()
1077 .zip_debug_eq(old_storage_rebates)
1078 {
1079 let new_object_size = object.object_size_for_gas_metering();
1081 let new_storage_rebate = gas_charger
1083 .track_storage_mutation(object.id(), new_object_size, old_storage_rebate)
1084 .ok_or_else(|| ExecutionError::from_kind(ExecutionErrorKind::InvariantViolation))?;
1085 object.storage_rebate = new_storage_rebate;
1086 }
1087
1088 self.collect_rebate(gas_charger)
1089 }
1090
1091 pub(crate) fn collect_rebate(
1092 &self,
1093 gas_charger: &mut GasCharger,
1094 ) -> Result<(), ExecutionError> {
1095 for object_id in &self.execution_results.modified_objects {
1096 if self
1097 .execution_results
1098 .written_objects
1099 .contains_key(object_id)
1100 {
1101 continue;
1102 }
1103 let storage_rebate = self
1105 .get_object_modified_at(object_id)
1106 .unwrap()
1108 .storage_rebate;
1109 gas_charger
1110 .track_storage_mutation(*object_id, 0, storage_rebate)
1111 .ok_or_else(|| ExecutionError::from_kind(ExecutionErrorKind::InvariantViolation))?;
1112 }
1113 Ok(())
1114 }
1115
1116 pub fn check_execution_results_consistency<Mode: ExecutionMode>(
1117 &self,
1118 ) -> Result<(), Mode::Error> {
1119 assert_invariant!(
1120 self.execution_results
1121 .created_object_ids
1122 .iter()
1123 .all(|id| !self.execution_results.deleted_object_ids.contains(id)
1124 && !self.execution_results.modified_objects.contains(id)),
1125 "Created object IDs cannot also be deleted or modified"
1126 );
1127 assert_invariant!(
1128 self.execution_results.modified_objects.iter().all(|id| {
1129 self.mutable_input_refs.contains_key(id)
1130 || self.loaded_runtime_objects.contains_key(id)
1131 || is_system_package(*id)
1132 }),
1133 "A modified object must be either a mutable input, a loaded child object, or a system package"
1134 );
1135 Ok(())
1136 }
1137}
1138impl TemporaryStore<'_> {
1143 pub fn advance_epoch_safe_mode(
1144 &mut self,
1145 params: &AdvanceEpochParams,
1146 protocol_config: &ProtocolConfig,
1147 ) {
1148 let wrapper = get_sui_system_state_wrapper(self.store)
1149 .expect("System state wrapper object must exist");
1150 let (old_object, new_object) =
1151 wrapper.advance_epoch_safe_mode(params, self.store, protocol_config);
1152 self.mutate_child_object(old_object, new_object);
1153 }
1154}
1155
1156impl RuntimeObjectResolver for TemporaryStore<'_> {
1157 fn read_child_object(
1158 &self,
1159 parent: &ObjectID,
1160 child: &ObjectID,
1161 child_version_upper_bound: SequenceNumber,
1162 ) -> SuiResult<Option<Object>> {
1163 let obj_opt = self.execution_results.written_objects.get(child);
1164 if obj_opt.is_some() {
1165 Ok(obj_opt.cloned())
1166 } else {
1167 let _scope = monitored_scope("Execution::read_child_object");
1168 self.store
1169 .read_child_object(parent, child, child_version_upper_bound)
1170 }
1171 }
1172
1173 fn get_object_received_at_version(
1174 &self,
1175 owner: &ObjectID,
1176 receiving_object_id: &ObjectID,
1177 receive_object_at_version: SequenceNumber,
1178 epoch_id: EpochId,
1179 ) -> SuiResult<Option<Object>> {
1180 debug_assert!(
1183 !self
1184 .execution_results
1185 .written_objects
1186 .contains_key(receiving_object_id)
1187 );
1188 debug_assert!(
1189 !self
1190 .execution_results
1191 .deleted_object_ids
1192 .contains(receiving_object_id)
1193 );
1194 self.store.get_object_received_at_version(
1195 owner,
1196 receiving_object_id,
1197 receive_object_at_version,
1198 epoch_id,
1199 )
1200 }
1201}
1202
1203impl ObjectFundsResolver for TemporaryStore<'_> {
1204 fn object_available_balance(&self, owner: SuiAddress, type_: &TypeTag) -> SuiResult<u128> {
1208 let required_version = self
1209 .load_implicitly_read_system_object(&SUI_ACCUMULATOR_ROOT_OBJECT_ID)
1210 .ok_or(SuiErrorKind::ExecutionInvariantViolation)?
1211 .version();
1212
1213 let settled = AccumulatorRootValue::load(self, Some(required_version), owner, type_)?
1214 .and_then(|value| value.as_u128())
1215 .unwrap_or(0);
1216
1217 let unsettled = self.unsettled_object_funds.get_unsettled_object_withdraw(
1218 &AccumulatorRootValue::get_field_id(owner, type_)?,
1219 required_version,
1220 );
1221 settled
1222 .checked_sub(unsettled)
1223 .ok_or_else(|| SuiErrorKind::ExecutionInvariantViolation.into())
1224 }
1225}
1226
1227fn compute_input_reservations(
1236 transaction_kind: &TransactionKind,
1237 gas_data: &GasData,
1238 transaction_signer: SuiAddress,
1239 enable_gasless: bool,
1240) -> (BTreeMap<(SuiAddress, TypeTag), u64>, AllowanceIds) {
1241 use sui_types::balance::Balance;
1242 use sui_types::gas_coin::GAS;
1243 use sui_types::transaction::{Reservation, WithdrawFrom, is_gas_paid_from_address_balance};
1244
1245 let is_gasless = enable_gasless && is_gasless_transaction(gas_data, transaction_kind);
1246 let mut reservations: BTreeMap<(SuiAddress, TypeTag), u64> = BTreeMap::new();
1247 let mut allowance_ids = AllowanceIds::new();
1248 let sui_balance_type = Balance::type_tag(GAS::type_tag());
1249
1250 for arg in transaction_kind.get_funds_withdrawals() {
1251 let ty = arg.type_arg.to_type_tag();
1252 let owner = match arg.withdraw_from {
1253 WithdrawFrom::Sender => transaction_signer,
1254 WithdrawFrom::Sponsor => gas_data.owner,
1255 WithdrawFrom::SenderAllowance { funder, allowance } => {
1258 allowance_ids
1259 .entry((funder, ty.clone()))
1260 .or_default()
1261 .push(allowance);
1262 funder
1263 }
1264 };
1265 let Reservation::MaxAmountU64(reservation) = arg.reservation;
1266 let entry = reservations.entry((owner, ty)).or_insert(0);
1267 *entry = entry.saturating_add(reservation);
1268 }
1269
1270 if !is_gasless && is_gas_paid_from_address_balance(gas_data, transaction_kind) {
1273 let entry = reservations
1274 .entry((gas_data.owner, sui_balance_type.clone()))
1275 .or_insert(0);
1276 *entry = entry.saturating_add(gas_data.budget);
1277 }
1278
1279 for entry in &gas_data.payment {
1280 if let Ok(parsed) = ParsedDigest::try_from(entry.2) {
1281 let entry = reservations
1282 .entry((gas_data.owner, sui_balance_type.clone()))
1283 .or_insert(0);
1284 *entry = entry.saturating_add(parsed.reservation_amount());
1285 }
1286 }
1287
1288 (reservations, allowance_ids)
1289}
1290
1291fn declared_packages(
1294 transaction_kind: &TransactionKind,
1295) -> Option<Vec<(usize, BTreeSet<ObjectID>)>> {
1296 let TransactionKind::ProgrammableTransaction(pt) = transaction_kind else {
1297 return None;
1298 };
1299 Some(
1300 pt.commands
1301 .iter()
1302 .filter_map(|command| match command {
1303 Command::Publish(modules, dep_ids) | Command::Upgrade(modules, dep_ids, _, _) => {
1304 Some((modules.len(), dep_ids.iter().copied().collect()))
1305 }
1306 _ => None,
1307 })
1308 .collect(),
1309 )
1310}
1311
1312fn was_object_mutated(object: &Object, original: &Object) -> bool {
1315 let data_equal = match (&object.data, &original.data) {
1316 (Data::Move(a), Data::Move(b)) => a.contents_and_type_equal(b),
1317 (Data::Package(a), Data::Package(b)) => a == b,
1320 _ => false,
1321 };
1322
1323 let owner_equal = match (&object.owner, &original.owner) {
1324 (Owner::Shared { .. }, Owner::Shared { .. }) => true,
1328 (
1329 Owner::ConsensusAddressOwner { owner: a, .. },
1330 Owner::ConsensusAddressOwner { owner: b, .. },
1331 ) => a == b,
1332 (Owner::AddressOwner(a), Owner::AddressOwner(b)) => a == b,
1333 (Owner::Immutable, Owner::Immutable) => true,
1334 (Owner::ObjectOwner(a), Owner::ObjectOwner(b)) => a == b,
1335 (
1336 Owner::Party {
1337 permissions: a,
1338 start_version: _,
1339 },
1340 Owner::Party {
1341 permissions: b,
1342 start_version: _,
1343 },
1344 ) => a == b,
1345
1346 (Owner::AddressOwner(_), _)
1349 | (Owner::Immutable, _)
1350 | (Owner::ObjectOwner(_), _)
1351 | (Owner::Shared { .. }, _)
1352 | (Owner::ConsensusAddressOwner { .. }, _)
1353 | (Owner::Party { .. }, _) => false,
1354 };
1355
1356 !data_equal || !owner_equal
1357}
1358
1359impl Storage for TemporaryStore<'_> {
1360 fn reset(&mut self) {
1361 self.drop_writes();
1362 }
1363
1364 fn read_object(&self, id: &ObjectID) -> Option<&Object> {
1365 TemporaryStore::read_object(self, id)
1366 }
1367
1368 fn record_execution_results(
1370 &mut self,
1371 results: ExecutionResults,
1372 ) -> Result<(), ExecutionError> {
1373 let ExecutionResults::V2(mut results) = results else {
1374 panic!("ExecutionResults::V2 expected in sui-execution v1 and above");
1375 };
1376
1377 let mut to_remove = Vec::new();
1379 for (id, original) in &self.non_exclusive_input_original_versions {
1380 if results
1382 .written_objects
1383 .get(id)
1384 .map(|obj| was_object_mutated(obj, original))
1385 .unwrap_or(true)
1386 {
1387 return Err(ExecutionError::new_with_source(
1388 ExecutionErrorKind::NonExclusiveWriteInputObjectModified { id: *id },
1389 "Non-exclusive write input object has been modified or deleted",
1390 ));
1391 }
1392 to_remove.push(*id);
1393 }
1394
1395 for id in to_remove {
1396 results.written_objects.remove(&id);
1397 results.modified_objects.remove(&id);
1398 }
1399
1400 let event_start = self.execution_results.accumulator_events.len();
1406 self.execution_results.merge_results(
1407 results, true, true,
1408 )?;
1409 let event_end = self.execution_results.accumulator_events.len();
1410 self.invariants
1411 .record_ptb_event_range(event_start, event_end);
1412
1413 Ok(())
1414 }
1415
1416 fn save_loaded_runtime_objects(
1417 &mut self,
1418 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
1419 ) {
1420 TemporaryStore::save_loaded_runtime_objects(self, loaded_runtime_objects)
1421 }
1422
1423 fn save_wrapped_object_containers(
1424 &mut self,
1425 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
1426 ) {
1427 TemporaryStore::save_wrapped_object_containers(self, wrapped_object_containers)
1428 }
1429
1430 fn check_coin_deny_list(
1431 &self,
1432 receiving_funds_type_and_owners: BTreeMap<TypeTag, BTreeSet<SuiAddress>>,
1433 ) -> DenyListResult {
1434 let result = check_coin_deny_list_v2_during_execution(
1435 receiving_funds_type_and_owners,
1436 self.cur_epoch,
1437 self.store,
1438 );
1439 if result.num_non_gas_coin_owners > 0
1442 && !self.input_objects.contains_key(&SUI_DENY_LIST_OBJECT_ID)
1443 {
1444 self.loaded_per_epoch_config_objects
1445 .write()
1446 .insert(SUI_DENY_LIST_OBJECT_ID);
1447 }
1448 result
1449 }
1450
1451 fn record_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
1452 TemporaryStore::save_generated_object_ids(self, generated_ids)
1453 }
1454}
1455
1456impl BackingPackageStore for TemporaryStore<'_> {
1457 fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
1458 if let Some(obj) = self.execution_results.written_objects.get(package_id) {
1465 Ok(Some(PackageObject::new(obj.clone())))
1466 } else {
1467 self.store.get_package_object(package_id).inspect(|obj| {
1468 if let Some(v) = obj
1470 && !self
1471 .runtime_packages_loaded_from_db
1472 .read()
1473 .contains_key(package_id)
1474 {
1475 self.runtime_packages_loaded_from_db
1480 .write()
1481 .insert(*package_id, v.clone());
1482 }
1483 })
1484 }
1485 }
1486}