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