Skip to main content

sui_adapter_v0/programmable_transactions/
context.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4pub use checked::*;
5
6#[sui_macros::with_checked_arithmetic]
7mod checked {
8
9    use sui_types::storage::StorageView;
10
11    use std::{
12        collections::{BTreeMap, BTreeSet, HashMap},
13        sync::Arc,
14    };
15
16    use crate::adapter::new_native_extensions;
17    use crate::error::convert_vm_error;
18    use crate::execution_mode::ExecutionMode;
19    use crate::execution_value::{
20        CommandKind, InputObjectMetadata, InputValue, ObjectContents, ObjectValue, RawValueType,
21        ResultValue, TryFromValue, UsageKind, Value,
22    };
23    use crate::gas_charger::GasCharger;
24    use crate::programmable_transactions::linkage_view::{LinkageInfo, LinkageView, SavedLinkage};
25    use crate::type_resolver::TypeTagResolver;
26    use move_binary_format::{
27        errors::{Location, VMError, VMResult},
28        file_format::{CodeOffset, FunctionDefinitionIndex, TypeParameterIndex},
29        CompiledModule,
30    };
31    use move_core_types::{
32        account_address::AccountAddress,
33        language_storage::{ModuleId, StructTag, TypeTag},
34    };
35    use move_vm_runtime::{move_vm::MoveVM, session::Session};
36    use move_vm_types::loaded_data::runtime_types::Type;
37    use sui_move_natives::object_runtime::{
38        self, get_all_uids, max_event_error, ObjectRuntime, RuntimeResults,
39    };
40    use sui_protocol_config::ProtocolConfig;
41    use sui_types::execution_status::CommandArgumentError;
42    use sui_types::storage::PackageObject;
43    use sui_types::{
44        balance::Balance,
45        base_types::{MoveObjectType, ObjectID, SequenceNumber, SuiAddress, TxContext},
46        coin::Coin,
47        error::{command_argument_error, ExecutionError},
48        event::Event,
49        execution::{ExecutionResults, ExecutionResultsV1},
50        execution_status::ExecutionErrorKind,
51        metrics::ExecutionMetrics,
52        move_package::MovePackage,
53        object::{Data, MoveObject, Object, ObjectInner, Owner},
54        storage::{
55            BackingPackageStore, DeleteKind, DeleteKindWithOldVersion, ObjectChange,
56            RuntimeObjectResolver, WriteKind,
57        },
58        transaction::{Argument, CallArg, ObjectArg},
59    };
60
61    /// Maintains all runtime state specific to programmable transactions
62    pub struct ExecutionContext<'vm, 'state, 'a> {
63        /// The protocol config
64        pub protocol_config: &'a ProtocolConfig,
65        /// Metrics for reporting exceeded limits
66        pub metrics: Arc<ExecutionMetrics>,
67        /// The MoveVM
68        pub vm: &'vm MoveVM,
69        /// The global state, used for resolving packages
70        pub state_view: &'state dyn StorageView,
71        /// A shared transaction context, contains transaction digest information and manages the
72        /// creation of new object IDs
73        pub tx_context: &'a mut TxContext,
74        /// The gas charger used for metering
75        pub gas_charger: &'a mut GasCharger,
76        /// The session used for interacting with Move types and calls
77        pub session: Session<'state, 'vm, LinkageView<'state>>,
78        /// Additional transfers not from the Move runtime
79        additional_transfers: Vec<(/* new owner */ SuiAddress, ObjectValue)>,
80        /// Newly published packages
81        new_packages: Vec<Object>,
82        /// User events are claimed after each Move call
83        user_events: Vec<(ModuleId, StructTag, Vec<u8>)>,
84        // runtime data
85        /// The runtime value for the Gas coin, None if it has been taken/moved
86        gas: InputValue,
87        /// The runtime value for the inputs/call args, None if it has been taken/moved
88        inputs: Vec<InputValue>,
89        /// The results of a given command. For most commands, the inner vector will have length 1.
90        /// It will only not be 1 for Move calls with multiple return values.
91        /// Inner values are None if taken/moved by-value
92        results: Vec<Vec<ResultValue>>,
93        /// Map of arguments that are currently borrowed in this command, true if the borrow is mutable
94        /// This gets cleared out when new results are pushed, i.e. the end of a command
95        borrowed: HashMap<Argument, /* mut */ bool>,
96    }
97
98    /// A write for an object that was generated outside of the Move ObjectRuntime
99    struct AdditionalWrite {
100        /// The new owner of the object
101        recipient: Owner,
102        /// the type of the object,
103        type_: Type,
104        /// if the object has public transfer or not, i.e. if it has store
105        has_public_transfer: bool,
106        /// contents of the object
107        bytes: Vec<u8>,
108    }
109
110    impl<'vm, 'state, 'a> ExecutionContext<'vm, 'state, 'a> {
111        pub fn new(
112            protocol_config: &'a ProtocolConfig,
113            metrics: Arc<ExecutionMetrics>,
114            vm: &'vm MoveVM,
115            state_view: &'state dyn StorageView,
116            tx_context: &'a mut TxContext,
117            gas_charger: &'a mut GasCharger,
118            inputs: Vec<CallArg>,
119        ) -> Result<Self, ExecutionError> {
120            let init_linkage = if protocol_config.package_upgrades() {
121                LinkageInfo::Unset
122            } else {
123                LinkageInfo::Universal
124            };
125
126            // we need a new session just for loading types, which is sad
127            // TODO remove this
128            let linkage = LinkageView::new(Box::new(state_view), init_linkage);
129            let mut tmp_session = new_session(
130                vm,
131                linkage,
132                state_view,
133                BTreeMap::new(),
134                !gas_charger.is_unmetered(),
135                protocol_config,
136                metrics.clone(),
137            );
138            let mut input_object_map = BTreeMap::new();
139            let inputs = inputs
140                .into_iter()
141                .map(|call_arg| {
142                    load_call_arg(
143                        vm,
144                        state_view,
145                        &mut tmp_session,
146                        &mut input_object_map,
147                        call_arg,
148                    )
149                })
150                .collect::<Result<_, ExecutionError>>()?;
151            let gas = if let Some(gas_coin) = gas_charger.gas_coin() {
152                let mut gas = load_object(
153                    vm,
154                    state_view,
155                    &mut tmp_session,
156                    &mut input_object_map,
157                    /* imm override */ false,
158                    gas_coin,
159                )?;
160                // subtract the max gas budget. This amount is off limits in the programmable transaction,
161                // so to mimic this "off limits" behavior, we act as if the coin has less balance than
162                // it really does
163                let Some(Value::Object(ObjectValue {
164                    contents: ObjectContents::Coin(coin),
165                    ..
166                })) = &mut gas.inner.value
167                else {
168                    invariant_violation!("Gas object should be a populated coin")
169                };
170                let max_gas_in_balance = gas_charger.gas_budget();
171                let Some(new_balance) = coin.balance.value().checked_sub(max_gas_in_balance) else {
172                    invariant_violation!(
173                        "Transaction input checker should check that there is enough gas"
174                    );
175                };
176                coin.balance = Balance::new(new_balance);
177                gas
178            } else {
179                InputValue {
180                    object_metadata: None,
181                    inner: ResultValue {
182                        last_usage_kind: None,
183                        value: None,
184                    },
185                }
186            };
187            // the session was just used for ability and layout metadata fetching, no changes should
188            // exist. Plus, Sui Move does not use these changes or events
189            let (res, linkage) = tmp_session.finish();
190            let change_set = res.map_err(|e| crate::error::convert_vm_error(e, vm, &linkage))?;
191            assert_invariant!(change_set.accounts().is_empty(), "Change set must be empty");
192            // make the real session
193            let session = new_session(
194                vm,
195                linkage,
196                state_view,
197                input_object_map,
198                !gas_charger.is_unmetered(),
199                protocol_config,
200                metrics.clone(),
201            );
202
203            Ok(Self {
204                protocol_config,
205                metrics,
206                vm,
207                state_view,
208                tx_context,
209                gas_charger,
210                session,
211                gas,
212                inputs,
213                results: vec![],
214                additional_transfers: vec![],
215                new_packages: vec![],
216                user_events: vec![],
217                borrowed: HashMap::new(),
218            })
219        }
220
221        /// Create a new ID and update the state
222        pub fn fresh_id(&mut self) -> Result<ObjectID, ExecutionError> {
223            let object_id = self.tx_context.fresh_id();
224            let object_runtime: &mut ObjectRuntime = self.session.get_native_extensions().get_mut();
225            object_runtime
226                .new_id(object_id)
227                .map_err(|e| self.convert_vm_error(e.finish(Location::Undefined)))?;
228            Ok(object_id)
229        }
230
231        /// Delete an ID and update the state
232        pub fn delete_id(&mut self, object_id: ObjectID) -> Result<(), ExecutionError> {
233            let object_runtime: &mut ObjectRuntime = self.session.get_native_extensions().get_mut();
234            object_runtime
235                .delete_id(object_id)
236                .map_err(|e| self.convert_vm_error(e.finish(Location::Undefined)))
237        }
238
239        /// Set the link context for the session from the linkage information in the MovePackage found
240        /// at `package_id`.  Returns the runtime ID of the link context package on success.
241        pub fn set_link_context(
242            &mut self,
243            package_id: ObjectID,
244        ) -> Result<AccountAddress, ExecutionError> {
245            let resolver = self.session.get_resolver();
246            if resolver.has_linkage(package_id) {
247                // Setting same context again, can skip.
248                return Ok(resolver.original_package_id().unwrap_or(*package_id));
249            }
250
251            let package = package_for_linkage(&self.session, package_id)
252                .map_err(|e| self.convert_vm_error(e))?;
253
254            set_linkage(&mut self.session, package.move_package())
255        }
256
257        /// Set the link context for the session from the linkage information in the `package`.  Returns
258        /// the runtime ID of the link context package on success.
259        pub fn set_linkage(
260            &mut self,
261            package: &MovePackage,
262        ) -> Result<AccountAddress, ExecutionError> {
263            set_linkage(&mut self.session, package)
264        }
265
266        /// Turn off linkage information, so that the next use of the session will need to set linkage
267        /// information to succeed.
268        pub fn reset_linkage(&mut self) {
269            reset_linkage(&mut self.session);
270        }
271
272        /// Reset the linkage context, and save it (if one exists)
273        pub fn steal_linkage(&mut self) -> Option<SavedLinkage> {
274            steal_linkage(&mut self.session)
275        }
276
277        /// Restore a previously stolen/saved link context.
278        pub fn restore_linkage(
279            &mut self,
280            saved: Option<SavedLinkage>,
281        ) -> Result<(), ExecutionError> {
282            restore_linkage(&mut self.session, saved)
283        }
284
285        /// Load a type using the context's current session.
286        pub fn load_type(&mut self, type_tag: &TypeTag) -> VMResult<Type> {
287            load_type(&mut self.session, type_tag)
288        }
289
290        /// Takes the user events from the runtime and tags them with the Move module of the function
291        /// that was invoked for the command
292        pub fn take_user_events(
293            &mut self,
294            module_id: &ModuleId,
295            function: FunctionDefinitionIndex,
296            last_offset: CodeOffset,
297        ) -> Result<(), ExecutionError> {
298            let object_runtime: &mut ObjectRuntime = self.session.get_native_extensions().get_mut();
299            let events = object_runtime.take_user_events();
300            let num_events = self.user_events.len() + events.len();
301            let max_events = self.protocol_config.max_num_event_emit();
302            if num_events as u64 > max_events {
303                let err = max_event_error(max_events)
304                    .at_code_offset(function, last_offset)
305                    .finish(Location::Module(module_id.clone()));
306                return Err(self.convert_vm_error(err));
307            }
308            let new_events = events
309                .into_iter()
310                .map(|(ty, tag, value)| {
311                    let layout = self
312                        .session
313                        .type_to_type_layout(&ty)
314                        .map_err(|e| self.convert_vm_error(e))?;
315                    let Some(bytes) = value.simple_serialize(&layout) else {
316                        invariant_violation!("Failed to deserialize already serialized Move value");
317                    };
318                    Ok((module_id.clone(), tag, bytes))
319                })
320                .collect::<Result<Vec<_>, ExecutionError>>()?;
321            self.user_events.extend(new_events);
322            Ok(())
323        }
324
325        /// Get the argument value. Cloning the value if it is copyable, and setting its value to None
326        /// if it is not (making it unavailable).
327        /// Errors if out of bounds, if the argument is borrowed, if it is unavailable (already taken),
328        /// or if it is an object that cannot be taken by value (shared or immutable)
329        pub fn by_value_arg<V: TryFromValue>(
330            &mut self,
331            command_kind: CommandKind<'_>,
332            arg_idx: usize,
333            arg: Argument,
334        ) -> Result<V, ExecutionError> {
335            self.by_value_arg_(command_kind, arg)
336                .map_err(|e| command_argument_error(e, arg_idx))
337        }
338        fn by_value_arg_<V: TryFromValue>(
339            &mut self,
340            command_kind: CommandKind<'_>,
341            arg: Argument,
342        ) -> Result<V, CommandArgumentError> {
343            let is_borrowed = self.arg_is_borrowed(&arg);
344            let (input_metadata_opt, val_opt) = self.borrow_mut(arg, UsageKind::ByValue)?;
345            let is_copyable = if let Some(val) = val_opt {
346                val.is_copyable()
347            } else {
348                return Err(CommandArgumentError::InvalidValueUsage);
349            };
350            // If it was taken, we catch this above.
351            // If it was not copyable and was borrowed, error as it creates a dangling reference in
352            // effect.
353            // We allow copyable values to be copied out even if borrowed, as we do not care about
354            // referential transparency at this level.
355            if !is_copyable && is_borrowed {
356                return Err(CommandArgumentError::InvalidValueUsage);
357            }
358            // Gas coin cannot be taken by value, except in TransferObjects
359            if matches!(arg, Argument::GasCoin)
360                && !matches!(command_kind, CommandKind::TransferObjects)
361            {
362                return Err(CommandArgumentError::InvalidGasCoinUsage);
363            }
364            // Immutable objects and shared objects cannot be taken by value
365            if matches!(
366                input_metadata_opt,
367                Some(InputObjectMetadata::InputObject {
368                    owner: Owner::Immutable | Owner::Shared { .. },
369                    ..
370                })
371            ) {
372                return Err(CommandArgumentError::InvalidObjectByValue);
373            }
374            let val = if is_copyable {
375                val_opt.as_ref().unwrap().clone()
376            } else {
377                val_opt.take().unwrap()
378            };
379            V::try_from_value(val)
380        }
381
382        /// Mimic a mutable borrow by taking the argument value, setting its value to None,
383        /// making it unavailable. The value will be marked as borrowed and must be returned with
384        /// restore_arg
385        /// Errors if out of bounds, if the argument is borrowed, if it is unavailable (already taken),
386        /// or if it is an object that cannot be mutably borrowed (immutable)
387        pub fn borrow_arg_mut<V: TryFromValue>(
388            &mut self,
389            arg_idx: usize,
390            arg: Argument,
391        ) -> Result<V, ExecutionError> {
392            self.borrow_arg_mut_(arg)
393                .map_err(|e| command_argument_error(e, arg_idx))
394        }
395        fn borrow_arg_mut_<V: TryFromValue>(
396            &mut self,
397            arg: Argument,
398        ) -> Result<V, CommandArgumentError> {
399            // mutable borrowing requires unique usage
400            if self.arg_is_borrowed(&arg) {
401                return Err(CommandArgumentError::InvalidValueUsage);
402            }
403            self.borrowed.insert(arg, /* is_mut */ true);
404            let (input_metadata_opt, val_opt) = self.borrow_mut(arg, UsageKind::BorrowMut)?;
405            let is_copyable = if let Some(val) = val_opt {
406                val.is_copyable()
407            } else {
408                // error if taken
409                return Err(CommandArgumentError::InvalidValueUsage);
410            };
411            if let Some(InputObjectMetadata::InputObject {
412                is_mutable_input: false,
413                ..
414            }) = input_metadata_opt
415            {
416                return Err(CommandArgumentError::InvalidObjectByMutRef);
417            }
418            // if it is copyable, don't take it as we allow for the value to be copied even if
419            // mutably borrowed
420            let val = if is_copyable {
421                val_opt.as_ref().unwrap().clone()
422            } else {
423                val_opt.take().unwrap()
424            };
425            V::try_from_value(val)
426        }
427
428        /// Mimics an immutable borrow by cloning the argument value without setting its value to None
429        /// Errors if out of bounds, if the argument is mutably borrowed,
430        /// or if it is unavailable (already taken)
431        pub fn borrow_arg<V: TryFromValue>(
432            &mut self,
433            arg_idx: usize,
434            arg: Argument,
435        ) -> Result<V, ExecutionError> {
436            self.borrow_arg_(arg)
437                .map_err(|e| command_argument_error(e, arg_idx))
438        }
439        fn borrow_arg_<V: TryFromValue>(
440            &mut self,
441            arg: Argument,
442        ) -> Result<V, CommandArgumentError> {
443            // immutable borrowing requires the value was not mutably borrowed.
444            // If it was copied, that is okay.
445            // If it was taken/moved, we will find out below
446            if self.arg_is_mut_borrowed(&arg) {
447                return Err(CommandArgumentError::InvalidValueUsage);
448            }
449            self.borrowed.insert(arg, /* is_mut */ false);
450            let (_input_metadata_opt, val_opt) = self.borrow_mut(arg, UsageKind::BorrowImm)?;
451            if val_opt.is_none() {
452                return Err(CommandArgumentError::InvalidValueUsage);
453            }
454            V::try_from_value(val_opt.as_ref().unwrap().clone())
455        }
456
457        /// Restore an argument after being mutably borrowed
458        pub fn restore_arg<Mode: ExecutionMode>(
459            &mut self,
460            updates: &mut Mode::ArgumentUpdates,
461            arg: Argument,
462            value: Value,
463        ) -> Result<(), ExecutionError> {
464            Mode::add_argument_update(self, updates, arg, &value)?;
465            let was_mut_opt = self.borrowed.remove(&arg);
466            assert_invariant!(
467                was_mut_opt.is_some() && was_mut_opt.unwrap(),
468                "Should never restore a non-mut borrowed value. \
469            The take+restore is an implementation detail of mutable references"
470            );
471            // restore is exclusively used for mut
472            let Ok((_, value_opt)) = self.borrow_mut_impl(arg, None) else {
473                invariant_violation!("Should be able to borrow argument to restore it")
474            };
475            let old_value = value_opt.replace(value);
476            assert_invariant!(
477                old_value.is_none() || old_value.unwrap().is_copyable(),
478                "Should never restore a non-taken value, unless it is copyable. \
479            The take+restore is an implementation detail of mutable references"
480            );
481            Ok(())
482        }
483
484        /// Transfer the object to a new owner
485        pub fn transfer_object(
486            &mut self,
487            obj: ObjectValue,
488            addr: SuiAddress,
489        ) -> Result<(), ExecutionError> {
490            self.additional_transfers.push((addr, obj));
491            Ok(())
492        }
493
494        /// Create a new package
495        pub fn new_package<'p>(
496            &self,
497            modules: &[CompiledModule],
498            dependencies: impl IntoIterator<Item = &'p MovePackage>,
499        ) -> Result<Object, ExecutionError> {
500            Object::new_package(
501                modules,
502                self.tx_context.digest(),
503                self.protocol_config,
504                dependencies,
505            )
506        }
507
508        /// Create a package upgrade from `previous_package` with `new_modules` and `dependencies`
509        pub fn upgrade_package<'p>(
510            &self,
511            storage_id: ObjectID,
512            previous_package: &MovePackage,
513            new_modules: &[CompiledModule],
514            dependencies: impl IntoIterator<Item = &'p MovePackage>,
515        ) -> Result<Object, ExecutionError> {
516            Object::new_upgraded_package(
517                previous_package,
518                storage_id,
519                new_modules,
520                self.tx_context.digest(),
521                self.protocol_config,
522                dependencies,
523            )
524        }
525
526        /// Add a newly created package to write as an effect of the transaction
527        pub fn write_package(&mut self, package: Object) -> Result<(), ExecutionError> {
528            assert_invariant!(package.is_package(), "Must be a package");
529            self.new_packages.push(package);
530            Ok(())
531        }
532
533        /// Finish a command: clearing the borrows and adding the results to the result vector
534        pub fn push_command_results(&mut self, results: Vec<Value>) -> Result<(), ExecutionError> {
535            assert_invariant!(
536                self.borrowed.values().all(|is_mut| !is_mut),
537                "all mut borrows should be restored"
538            );
539            // clear borrow state
540            self.borrowed = HashMap::new();
541            self.results
542                .push(results.into_iter().map(ResultValue::new).collect());
543            Ok(())
544        }
545
546        /// Determine the object changes and collect all user events
547        pub fn finish<Mode: ExecutionMode>(self) -> Result<ExecutionResults, ExecutionError> {
548            let Self {
549                protocol_config,
550                metrics,
551                vm,
552                state_view,
553                tx_context,
554                gas_charger,
555                session,
556                additional_transfers,
557                new_packages,
558                gas,
559                inputs,
560                results,
561                user_events,
562                ..
563            } = self;
564            let tx_digest = tx_context.digest();
565            let mut additional_writes = BTreeMap::new();
566            let mut input_object_metadata = BTreeMap::new();
567            // Any object value that has not been taken (still has `Some` for it's value) needs to
568            // written as it's value might have changed (and eventually it's sequence number will need
569            // to increase)
570            let mut by_value_inputs = BTreeSet::new();
571            let mut add_input_object_write = |input| -> Result<(), ExecutionError> {
572                let InputValue {
573                    object_metadata: object_metadata_opt,
574                    inner: ResultValue { value, .. },
575                } = input;
576                let Some(object_metadata) = object_metadata_opt else {
577                    return Ok(());
578                };
579                let InputObjectMetadata::InputObject {
580                    is_mutable_input,
581                    owner,
582                    id,
583                    ..
584                } = &object_metadata
585                else {
586                    unreachable!("Found non-input object metadata for input object when adding writes to input objects -- impossible in v0");
587                };
588                input_object_metadata.insert(object_metadata.id(), object_metadata.clone());
589                let Some(Value::Object(object_value)) = value else {
590                    by_value_inputs.insert(*id);
591                    return Ok(());
592                };
593                if *is_mutable_input {
594                    add_additional_write(&mut additional_writes, owner.clone(), object_value)?;
595                }
596                Ok(())
597            };
598            let gas_id_opt = gas.object_metadata.as_ref().map(|info| info.id());
599            add_input_object_write(gas)?;
600            for input in inputs {
601                add_input_object_write(input)?
602            }
603            // check for unused values
604            // disable this check for dev inspect
605            if !Mode::allow_arbitrary_values() {
606                for (i, command_result) in results.iter().enumerate() {
607                    for (j, result_value) in command_result.iter().enumerate() {
608                        let ResultValue {
609                            last_usage_kind,
610                            value,
611                        } = result_value;
612                        match value {
613                            None => (),
614                            Some(Value::Object(_)) => {
615                                return Err(ExecutionErrorKind::UnusedValueWithoutDrop {
616                                    result_idx: i as u16,
617                                    secondary_idx: j as u16,
618                                }
619                                .into())
620                            }
621                            Some(Value::Raw(RawValueType::Any, _)) => (),
622                            Some(Value::Raw(RawValueType::Loaded { abilities, .. }, _)) => {
623                                // - nothing to check for drop
624                                // - if it does not have drop, but has copy,
625                                //   the last usage must be by value in order to "lie" and say that the
626                                //   last usage is actually a take instead of a clone
627                                // - Otherwise, an error
628                                if abilities.has_drop()
629                                    || (abilities.has_copy()
630                                        && matches!(last_usage_kind, Some(UsageKind::ByValue)))
631                                {
632                                } else {
633                                    let msg = if abilities.has_copy() {
634                                        "The value has copy, but not drop. \
635                                    Its last usage must be by-value so it can be taken."
636                                    } else {
637                                        "Unused value without drop"
638                                    };
639                                    return Err(ExecutionError::new_with_source(
640                                        ExecutionErrorKind::UnusedValueWithoutDrop {
641                                            result_idx: i as u16,
642                                            secondary_idx: j as u16,
643                                        },
644                                        msg,
645                                    ));
646                                }
647                            }
648                            Some(Value::Receiving(_, _, _)) => {
649                                unreachable!("Impossible to hit Receiving in v0")
650                            }
651                        }
652                    }
653                }
654            }
655            // add transfers from TransferObjects command
656            for (recipient, object_value) in additional_transfers {
657                let owner = Owner::AddressOwner(recipient);
658                add_additional_write(&mut additional_writes, owner, object_value)?;
659            }
660            // Refund unused gas
661            if let Some(gas_id) = gas_id_opt {
662                refund_max_gas_budget(&mut additional_writes, gas_charger, gas_id)?;
663            }
664
665            let (res, linkage) = session.finish_with_extensions();
666            let (_, mut native_context_extensions) =
667                res.map_err(|e| convert_vm_error(e, vm, &linkage))?;
668            let object_runtime: ObjectRuntime = native_context_extensions.remove();
669            let new_ids = object_runtime.new_ids().clone();
670            // tell the object runtime what input objects were taken and which were transferred
671            let external_transfers = additional_writes.keys().copied().collect();
672            let RuntimeResults {
673                writes,
674                deletions,
675                user_events: remaining_events,
676                loaded_child_objects,
677            } = object_runtime.finish(by_value_inputs, external_transfers)?;
678            assert_invariant!(
679                remaining_events.is_empty(),
680                "Events should be taken after every Move call"
681            );
682            let mut object_changes = BTreeMap::new();
683            for package in new_packages {
684                let id = package.id();
685                let change = ObjectChange::Write(package, WriteKind::Create);
686                object_changes.insert(id, change);
687            }
688            // we need a new session just for deserializing and fetching abilities. Which is sad
689            // TODO remove this
690            let tmp_session = new_session(
691                vm,
692                linkage,
693                state_view,
694                BTreeMap::new(),
695                !gas_charger.is_unmetered(),
696                protocol_config,
697                metrics,
698            );
699            for (id, additional_write) in additional_writes {
700                let AdditionalWrite {
701                    recipient,
702                    type_,
703                    has_public_transfer,
704                    bytes,
705                } = additional_write;
706                let write_kind = if input_object_metadata.contains_key(&id)
707                    || loaded_child_objects.contains_key(&id)
708                {
709                    assert_invariant!(
710                        !new_ids.contains_key(&id),
711                        "new id should not be in mutations"
712                    );
713                    WriteKind::Mutate
714                } else if new_ids.contains_key(&id) {
715                    WriteKind::Create
716                } else {
717                    WriteKind::Unwrap
718                };
719                // safe given the invariant that the runtime correctly propagates has_public_transfer
720                let move_object = unsafe {
721                    create_written_object(
722                        vm,
723                        &tmp_session,
724                        protocol_config,
725                        &input_object_metadata,
726                        &loaded_child_objects,
727                        id,
728                        type_,
729                        has_public_transfer,
730                        bytes,
731                        write_kind,
732                    )?
733                };
734                let object = Object::new_move(move_object, recipient, tx_digest);
735                let change = ObjectChange::Write(object, write_kind);
736                object_changes.insert(id, change);
737            }
738
739            for (id, (write_kind, recipient, ty, value)) in writes {
740                let abilities = tmp_session
741                    .get_type_abilities(&ty)
742                    .map_err(|e| convert_vm_error(e, vm, tmp_session.get_resolver()))?;
743                let has_public_transfer = abilities.has_store();
744                let layout = tmp_session
745                    .type_to_type_layout(&ty)
746                    .map_err(|e| convert_vm_error(e, vm, tmp_session.get_resolver()))?;
747                let Some(bytes) = value.simple_serialize(&layout) else {
748                    invariant_violation!("Failed to deserialize already serialized Move value");
749                };
750                // safe because has_public_transfer has been determined by the abilities
751                let move_object = unsafe {
752                    create_written_object(
753                        vm,
754                        &tmp_session,
755                        protocol_config,
756                        &input_object_metadata,
757                        &loaded_child_objects,
758                        id,
759                        ty,
760                        has_public_transfer,
761                        bytes,
762                        write_kind,
763                    )?
764                };
765                let object = Object::new_move(move_object, recipient, tx_digest);
766                let change = ObjectChange::Write(object, write_kind);
767                object_changes.insert(id, change);
768            }
769            for (id, delete_kind) in deletions {
770                // For deleted and wrapped objects, the object must exist either in the input or was
771                // loaded as child object. We can read them to get the previous version.
772                // For unwrap_then_delete, in older protocol versions, we must consult the object store
773                // to see if there exists a tombstone, and if so we include it otherwise we skip it.
774                // In newer protocol versions, we can just skip it.
775                let delete_kind_with_seq = match delete_kind {
776                    DeleteKind::Normal | DeleteKind::Wrap => {
777                        let old_version = match input_object_metadata.get(&id) {
778                        Some(metadata) => {
779                            assert_invariant!(
780                                !matches!(metadata, InputObjectMetadata::InputObject { owner: Owner::Immutable, .. }),
781                                "Attempting to delete immutable object {id} via delete kind {delete_kind}"
782                            );
783                            metadata.version()
784                        }
785                        None => {
786                            match loaded_child_objects.get(&id) {
787                                Some(version) => *version,
788                                None => invariant_violation!("Deleted/wrapped object {id} must be either in input or loaded child objects")
789                            }
790                        }
791                    };
792                        if delete_kind == DeleteKind::Normal {
793                            DeleteKindWithOldVersion::Normal(old_version)
794                        } else {
795                            DeleteKindWithOldVersion::Wrap(old_version)
796                        }
797                    }
798                    DeleteKind::UnwrapThenDelete => {
799                        if protocol_config.simplified_unwrap_then_delete() {
800                            DeleteKindWithOldVersion::UnwrapThenDelete
801                        } else {
802                            let old_version =
803                                match state_view.get_latest_parent_entry_ref_deprecated(id) {
804                                    Some((_, previous_version, _)) => previous_version,
805                                    // This object was not created this transaction but has never existed in
806                                    // storage, skip it.
807                                    None => continue,
808                                };
809                            DeleteKindWithOldVersion::UnwrapThenDeleteDEPRECATED(old_version)
810                        }
811                    }
812                };
813                object_changes.insert(id, ObjectChange::Delete(delete_kind_with_seq));
814            }
815
816            let (res, linkage) = tmp_session.finish();
817            let change_set = res.map_err(|e| convert_vm_error(e, vm, &linkage))?;
818
819            // the session was just used for ability and layout metadata fetching, no changes should
820            // exist. Plus, Sui Move does not use these changes or events
821            assert_invariant!(change_set.accounts().is_empty(), "Change set must be empty");
822
823            Ok(ExecutionResults::V1(ExecutionResultsV1 {
824                object_changes,
825                user_events: user_events
826                    .into_iter()
827                    .map(|(module_id, tag, contents)| {
828                        Event::new(
829                            module_id.address(),
830                            module_id.name(),
831                            tx_context.sender(),
832                            tag,
833                            contents,
834                        )
835                    })
836                    .collect(),
837            }))
838        }
839
840        /// Convert a VM Error to an execution one
841        pub fn convert_vm_error(&self, error: VMError) -> ExecutionError {
842            crate::error::convert_vm_error(error, self.vm, self.session.get_resolver())
843        }
844
845        /// Special case errors for type arguments to Move functions
846        pub fn convert_type_argument_error(&self, idx: usize, error: VMError) -> ExecutionError {
847            use move_core_types::vm_status::StatusCode;
848            use sui_types::execution_status::TypeArgumentError;
849            match error.major_status() {
850                StatusCode::NUMBER_OF_TYPE_ARGUMENTS_MISMATCH => {
851                    ExecutionErrorKind::TypeArityMismatch.into()
852                }
853                StatusCode::TYPE_RESOLUTION_FAILURE => ExecutionErrorKind::TypeArgumentError {
854                    argument_idx: idx as TypeParameterIndex,
855                    kind: TypeArgumentError::TypeNotFound,
856                }
857                .into(),
858                StatusCode::CONSTRAINT_NOT_SATISFIED => ExecutionErrorKind::TypeArgumentError {
859                    argument_idx: idx as TypeParameterIndex,
860                    kind: TypeArgumentError::ConstraintNotSatisfied,
861                }
862                .into(),
863                _ => self.convert_vm_error(error),
864            }
865        }
866
867        /// Returns true if the value at the argument's location is borrowed, mutably or immutably
868        fn arg_is_borrowed(&self, arg: &Argument) -> bool {
869            self.borrowed.contains_key(arg)
870        }
871
872        /// Returns true if the value at the argument's location is mutably borrowed
873        fn arg_is_mut_borrowed(&self, arg: &Argument) -> bool {
874            matches!(self.borrowed.get(arg), Some(/* mut */ true))
875        }
876
877        /// Internal helper to borrow the value for an argument and update the most recent usage
878        fn borrow_mut(
879            &mut self,
880            arg: Argument,
881            usage: UsageKind,
882        ) -> Result<(Option<&InputObjectMetadata>, &mut Option<Value>), CommandArgumentError>
883        {
884            self.borrow_mut_impl(arg, Some(usage))
885        }
886
887        /// Internal helper to borrow the value for an argument
888        /// Updates the most recent usage if specified
889        fn borrow_mut_impl(
890            &mut self,
891            arg: Argument,
892            update_last_usage: Option<UsageKind>,
893        ) -> Result<(Option<&InputObjectMetadata>, &mut Option<Value>), CommandArgumentError>
894        {
895            let (metadata, result_value) = match arg {
896                Argument::GasCoin => (self.gas.object_metadata.as_ref(), &mut self.gas.inner),
897                Argument::Input(i) => {
898                    let Some(input_value) = self.inputs.get_mut(i as usize) else {
899                        return Err(CommandArgumentError::IndexOutOfBounds { idx: i });
900                    };
901                    (input_value.object_metadata.as_ref(), &mut input_value.inner)
902                }
903                Argument::Result(i) => {
904                    let Some(command_result) = self.results.get_mut(i as usize) else {
905                        return Err(CommandArgumentError::IndexOutOfBounds { idx: i });
906                    };
907                    if command_result.len() != 1 {
908                        return Err(CommandArgumentError::InvalidResultArity { result_idx: i });
909                    }
910                    (None, &mut command_result[0])
911                }
912                Argument::NestedResult(i, j) => {
913                    let Some(command_result) = self.results.get_mut(i as usize) else {
914                        return Err(CommandArgumentError::IndexOutOfBounds { idx: i });
915                    };
916                    let Some(result_value) = command_result.get_mut(j as usize) else {
917                        return Err(CommandArgumentError::SecondaryIndexOutOfBounds {
918                            result_idx: i,
919                            secondary_idx: j,
920                        });
921                    };
922                    (None, result_value)
923                }
924            };
925            if let Some(usage) = update_last_usage {
926                result_value.last_usage_kind = Some(usage);
927            }
928            Ok((metadata, &mut result_value.value))
929        }
930    }
931
932    impl TypeTagResolver for ExecutionContext<'_, '_, '_> {
933        fn get_type_tag(&self, type_: &Type) -> Result<TypeTag, ExecutionError> {
934            self.session
935                .get_type_tag(type_)
936                .map_err(|e| self.convert_vm_error(e))
937        }
938    }
939
940    pub(crate) fn new_session<'state, 'vm>(
941        vm: &'vm MoveVM,
942        linkage: LinkageView<'state>,
943        child_resolver: &'state dyn RuntimeObjectResolver,
944        input_objects: BTreeMap<ObjectID, object_runtime::InputObject>,
945        is_metered: bool,
946        protocol_config: &ProtocolConfig,
947        metrics: Arc<ExecutionMetrics>,
948    ) -> Session<'state, 'vm, LinkageView<'state>> {
949        vm.new_session_with_extensions(
950            linkage,
951            new_native_extensions(
952                child_resolver,
953                input_objects,
954                is_metered,
955                protocol_config,
956                metrics,
957            ),
958        )
959    }
960
961    // Create a new Session suitable for resolving type and type operations rather than execution
962    pub(crate) fn new_session_for_linkage<'vm, 'state>(
963        vm: &'vm MoveVM,
964        linkage: LinkageView<'state>,
965    ) -> Session<'state, 'vm, LinkageView<'state>> {
966        vm.new_session(linkage)
967    }
968
969    /// Set the link context for the session from the linkage information in the `package`.
970    pub fn set_linkage(
971        session: &mut Session<LinkageView>,
972        linkage: &MovePackage,
973    ) -> Result<AccountAddress, ExecutionError> {
974        session.get_resolver_mut().set_linkage(linkage)
975    }
976
977    /// Turn off linkage information, so that the next use of the session will need to set linkage
978    /// information to succeed.
979    pub fn reset_linkage(session: &mut Session<LinkageView>) {
980        session.get_resolver_mut().reset_linkage();
981    }
982
983    pub fn steal_linkage(session: &mut Session<LinkageView>) -> Option<SavedLinkage> {
984        session.get_resolver_mut().steal_linkage()
985    }
986
987    pub fn restore_linkage(
988        session: &mut Session<LinkageView>,
989        saved: Option<SavedLinkage>,
990    ) -> Result<(), ExecutionError> {
991        session.get_resolver_mut().restore_linkage(saved)
992    }
993
994    /// Fetch the package at `package_id` with a view to using it as a link context.  Produces an error
995    /// if the object at that ID does not exist, or is not a package.
996    fn package_for_linkage(
997        session: &Session<LinkageView>,
998        package_id: ObjectID,
999    ) -> VMResult<PackageObject> {
1000        use move_binary_format::errors::PartialVMError;
1001        use move_core_types::vm_status::StatusCode;
1002
1003        match session.get_resolver().get_package_object(&package_id) {
1004            Ok(Some(package)) => Ok(package),
1005            Ok(None) => Err(PartialVMError::new(StatusCode::LINKER_ERROR)
1006                .with_message(format!("Cannot find link context {package_id} in store"))
1007                .finish(Location::Undefined)),
1008            Err(err) => Err(PartialVMError::new(StatusCode::LINKER_ERROR)
1009                .with_message(format!(
1010                    "Error loading link context {package_id} from store: {err}"
1011                ))
1012                .finish(Location::Undefined)),
1013        }
1014    }
1015
1016    /// Load `type_tag` to get a `Type` in the provided `session`.  `session`'s linkage context may be
1017    /// reset after this operation, because during the operation, it may change when loading a struct.
1018    pub fn load_type(session: &mut Session<LinkageView>, type_tag: &TypeTag) -> VMResult<Type> {
1019        use move_binary_format::errors::PartialVMError;
1020        use move_core_types::vm_status::StatusCode;
1021
1022        fn verification_error<T>(code: StatusCode) -> VMResult<T> {
1023            Err(PartialVMError::new(code).finish(Location::Undefined))
1024        }
1025
1026        Ok(match type_tag {
1027            TypeTag::Bool => Type::Bool,
1028            TypeTag::U8 => Type::U8,
1029            TypeTag::U16 => Type::U16,
1030            TypeTag::U32 => Type::U32,
1031            TypeTag::U64 => Type::U64,
1032            TypeTag::U128 => Type::U128,
1033            TypeTag::U256 => Type::U256,
1034            TypeTag::Address => Type::Address,
1035            TypeTag::Signer => Type::Signer,
1036
1037            TypeTag::Vector(inner) => Type::Vector(Box::new(load_type(session, inner)?)),
1038            TypeTag::Struct(struct_tag) => {
1039                let StructTag {
1040                    address,
1041                    module,
1042                    name,
1043                    type_params,
1044                } = struct_tag.as_ref();
1045
1046                // Load the package that the struct is defined in, in storage
1047                let defining_id = ObjectID::from_address(*address);
1048                let package = package_for_linkage(session, defining_id)?;
1049
1050                // Set the defining package as the link context on the session while loading the
1051                // struct
1052                let original_address =
1053                    set_linkage(session, package.move_package()).map_err(|e| {
1054                        PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
1055                            .with_message(e.to_string())
1056                            .finish(Location::Undefined)
1057                    })?;
1058
1059                let runtime_id = ModuleId::new(original_address, module.clone());
1060                let res = session.load_struct(&runtime_id, name);
1061                reset_linkage(session);
1062                let (idx, struct_type) = res?;
1063
1064                // Recursively load type parameters, if necessary
1065                let type_param_constraints = struct_type.type_param_constraints();
1066                if type_param_constraints.len() != type_params.len() {
1067                    return verification_error(StatusCode::NUMBER_OF_TYPE_ARGUMENTS_MISMATCH);
1068                }
1069
1070                if type_params.is_empty() {
1071                    Type::Datatype(idx)
1072                } else {
1073                    let loaded_type_params = type_params
1074                        .iter()
1075                        .map(|type_param| load_type(session, type_param))
1076                        .collect::<VMResult<Vec<_>>>()?;
1077
1078                    // Verify that the type parameter constraints on the struct are met
1079                    for (constraint, param) in type_param_constraints.zip(&loaded_type_params) {
1080                        let abilities = session.get_type_abilities(param)?;
1081                        if !constraint.is_subset(abilities) {
1082                            return verification_error(StatusCode::CONSTRAINT_NOT_SATISFIED);
1083                        }
1084                    }
1085
1086                    Type::DatatypeInstantiation(Box::new((idx, loaded_type_params)))
1087                }
1088            }
1089        })
1090    }
1091
1092    pub(crate) fn make_object_value<'vm, 'state>(
1093        vm: &'vm MoveVM,
1094        session: &mut Session<'state, 'vm, LinkageView<'state>>,
1095        type_: MoveObjectType,
1096        has_public_transfer: bool,
1097        used_in_non_entry_move_call: bool,
1098        contents: &[u8],
1099    ) -> Result<ObjectValue, ExecutionError> {
1100        let contents = if type_.is_coin() {
1101            let Ok(coin) = Coin::from_bcs_bytes(contents) else {
1102                invariant_violation!("Could not deserialize a coin")
1103            };
1104            ObjectContents::Coin(coin)
1105        } else {
1106            ObjectContents::Raw(contents.to_vec())
1107        };
1108
1109        let tag: StructTag = type_.into();
1110        let type_ = load_type(session, &TypeTag::Struct(Box::new(tag)))
1111            .map_err(|e| crate::error::convert_vm_error(e, vm, session.get_resolver()))?;
1112        Ok(ObjectValue {
1113            type_,
1114            has_public_transfer,
1115            used_in_non_entry_move_call,
1116            contents,
1117        })
1118    }
1119
1120    pub(crate) fn value_from_object<'vm, 'state>(
1121        vm: &'vm MoveVM,
1122        session: &mut Session<'state, 'vm, LinkageView<'state>>,
1123        object: &Object,
1124    ) -> Result<ObjectValue, ExecutionError> {
1125        let ObjectInner {
1126            data: Data::Move(object),
1127            ..
1128        } = object.as_inner()
1129        else {
1130            invariant_violation!("Expected a Move object");
1131        };
1132
1133        let used_in_non_entry_move_call = false;
1134        make_object_value(
1135            vm,
1136            session,
1137            object.type_().clone(),
1138            object.has_public_transfer(),
1139            used_in_non_entry_move_call,
1140            object.contents(),
1141        )
1142    }
1143
1144    /// Load an input object from the state_view
1145    fn load_object<'vm, 'state>(
1146        vm: &'vm MoveVM,
1147        state_view: &'state dyn StorageView,
1148        session: &mut Session<'state, 'vm, LinkageView<'state>>,
1149        input_object_map: &mut BTreeMap<ObjectID, object_runtime::InputObject>,
1150        override_as_immutable: bool,
1151        id: ObjectID,
1152    ) -> Result<InputValue, ExecutionError> {
1153        let Some(obj) = state_view.read_object(&id) else {
1154            // protected by transaction input checker
1155            invariant_violation!("Object {} does not exist yet", id);
1156        };
1157        // override_as_immutable ==> Owner::Shared
1158        assert_invariant!(
1159            !override_as_immutable || matches!(obj.owner, Owner::Shared { .. }),
1160            "override_as_immutable should only be set for shared objects"
1161        );
1162        let is_mutable_input = match obj.owner {
1163            Owner::AddressOwner(_) => true,
1164            Owner::Shared { .. } => !override_as_immutable,
1165            Owner::Immutable => false,
1166            Owner::ObjectOwner(_) => {
1167                // protected by transaction input checker
1168                invariant_violation!("ObjectOwner objects cannot be input")
1169            }
1170            Owner::ConsensusAddressOwner { .. } => {
1171                unimplemented!("ConsensusAddressOwner does not exist for this execution version")
1172            }
1173            Owner::Party { .. } => {
1174                unimplemented!("Party does not exist for this execution version")
1175            }
1176        };
1177        let owner = obj.owner.clone();
1178        let version = obj.version();
1179        let object_metadata = InputObjectMetadata::InputObject {
1180            id,
1181            is_mutable_input,
1182            owner: owner.clone(),
1183            version,
1184        };
1185        let obj_value = value_from_object(vm, session, obj)?;
1186        let contained_uids = {
1187            let fully_annotated_layout =
1188                session
1189                    .type_to_fully_annotated_layout(&obj_value.type_)
1190                    .map_err(|e| convert_vm_error(e, vm, session.get_resolver()))?;
1191            let mut bytes = vec![];
1192            obj_value.write_bcs_bytes(&mut bytes);
1193            match get_all_uids(&fully_annotated_layout, &bytes) {
1194                Err(e) => {
1195                    invariant_violation!("Unable to retrieve UIDs for object. Got error: {e}")
1196                }
1197                Ok(uids) => uids,
1198            }
1199        };
1200        let runtime_input = object_runtime::InputObject {
1201            contained_uids,
1202            owner,
1203            version,
1204        };
1205        let prev = input_object_map.insert(id, runtime_input);
1206        // protected by transaction input checker
1207        assert_invariant!(prev.is_none(), "Duplicate input object {}", id);
1208        Ok(InputValue::new_object(object_metadata, obj_value))
1209    }
1210
1211    /// Load a CallArg, either an object or a raw set of BCS bytes
1212    fn load_call_arg<'vm, 'state>(
1213        vm: &'vm MoveVM,
1214        state_view: &'state dyn StorageView,
1215        session: &mut Session<'state, 'vm, LinkageView<'state>>,
1216        input_object_map: &mut BTreeMap<ObjectID, object_runtime::InputObject>,
1217        call_arg: CallArg,
1218    ) -> Result<InputValue, ExecutionError> {
1219        Ok(match call_arg {
1220            CallArg::Pure(bytes) => InputValue::new_raw(RawValueType::Any, bytes),
1221            CallArg::Object(obj_arg) => {
1222                load_object_arg(vm, state_view, session, input_object_map, obj_arg)?
1223            }
1224            CallArg::FundsWithdrawal(_) => unreachable!("Impossible to hit BalanceWithdraw in v0"),
1225        })
1226    }
1227
1228    /// Load an ObjectArg from state view, marking if it can be treated as mutable or not
1229    fn load_object_arg<'vm, 'state>(
1230        vm: &'vm MoveVM,
1231        state_view: &'state dyn StorageView,
1232        session: &mut Session<'state, 'vm, LinkageView<'state>>,
1233        input_object_map: &mut BTreeMap<ObjectID, object_runtime::InputObject>,
1234        obj_arg: ObjectArg,
1235    ) -> Result<InputValue, ExecutionError> {
1236        match obj_arg {
1237            ObjectArg::ImmOrOwnedObject((id, _, _)) => load_object(
1238                vm,
1239                state_view,
1240                session,
1241                input_object_map,
1242                /* imm override */ false,
1243                id,
1244            ),
1245            ObjectArg::SharedObject { id, mutability, .. } => load_object(
1246                vm,
1247                state_view,
1248                session,
1249                input_object_map,
1250                /* imm override */ !mutability.is_exclusive(),
1251                id,
1252            ),
1253            ObjectArg::Receiving(_) => unreachable!("Impossible to hit Receiving in v0"),
1254        }
1255    }
1256
1257    /// Generate an additional write for an ObjectValue
1258    fn add_additional_write(
1259        additional_writes: &mut BTreeMap<ObjectID, AdditionalWrite>,
1260        owner: Owner,
1261        object_value: ObjectValue,
1262    ) -> Result<(), ExecutionError> {
1263        let ObjectValue {
1264            type_,
1265            has_public_transfer,
1266            contents,
1267            ..
1268        } = object_value;
1269        let bytes = match contents {
1270            ObjectContents::Coin(coin) => coin.to_bcs_bytes(),
1271            ObjectContents::Raw(bytes) => bytes,
1272        };
1273        let object_id = MoveObject::id_opt(&bytes).map_err(|e| {
1274            ExecutionError::invariant_violation(format!("No id for Raw object bytes. {e}"))
1275        })?;
1276        let additional_write = AdditionalWrite {
1277            recipient: owner,
1278            type_,
1279            has_public_transfer,
1280            bytes,
1281        };
1282        additional_writes.insert(object_id, additional_write);
1283        Ok(())
1284    }
1285
1286    /// The max budget was deducted from the gas coin at the beginning of the transaction,
1287    /// now we return exactly that amount. Gas will be charged by the execution engine
1288    fn refund_max_gas_budget(
1289        additional_writes: &mut BTreeMap<ObjectID, AdditionalWrite>,
1290        gas_charger: &mut GasCharger,
1291        gas_id: ObjectID,
1292    ) -> Result<(), ExecutionError> {
1293        let Some(AdditionalWrite { bytes, .. }) = additional_writes.get_mut(&gas_id) else {
1294            invariant_violation!("Gas object cannot be wrapped or destroyed")
1295        };
1296        let Ok(mut coin) = Coin::from_bcs_bytes(bytes) else {
1297            invariant_violation!("Gas object must be a coin")
1298        };
1299        let Some(new_balance) = coin.balance.value().checked_add(gas_charger.gas_budget()) else {
1300            return Err(ExecutionError::new_with_source(
1301                ExecutionErrorKind::CoinBalanceOverflow,
1302                "Gas coin too large after returning the max gas budget",
1303            ));
1304        };
1305        coin.balance = Balance::new(new_balance);
1306        *bytes = coin.to_bcs_bytes();
1307        Ok(())
1308    }
1309
1310    /// Generate an MoveObject given an updated/written object
1311    /// # Safety
1312    ///
1313    /// This function assumes proper generation of has_public_transfer, either from the abilities of
1314    /// the StructTag, or from the runtime correctly propagating from the inputs
1315    unsafe fn create_written_object<'vm, 'state>(
1316        vm: &'vm MoveVM,
1317        session: &Session<'state, 'vm, LinkageView<'state>>,
1318        protocol_config: &ProtocolConfig,
1319        input_object_metadata: &BTreeMap<ObjectID, InputObjectMetadata>,
1320        loaded_child_objects: &BTreeMap<ObjectID, SequenceNumber>,
1321        id: ObjectID,
1322        type_: Type,
1323        has_public_transfer: bool,
1324        contents: Vec<u8>,
1325        write_kind: WriteKind,
1326    ) -> Result<MoveObject, ExecutionError> {
1327        debug_assert_eq!(
1328            id,
1329            MoveObject::id_opt(&contents).expect("object contents should start with an id")
1330        );
1331        let metadata_opt = input_object_metadata.get(&id);
1332        let loaded_child_version_opt = loaded_child_objects.get(&id);
1333        assert_invariant!(
1334            metadata_opt.is_none() || loaded_child_version_opt.is_none(),
1335            "Loaded {id} as a child, but that object was an input object",
1336        );
1337
1338        let old_obj_ver = metadata_opt
1339            .map(|metadata| metadata.version())
1340            .or_else(|| loaded_child_version_opt.copied());
1341
1342        debug_assert!(
1343            (write_kind == WriteKind::Mutate) == old_obj_ver.is_some(),
1344            "Inconsistent state: write_kind: {write_kind:?}, old ver: {old_obj_ver:?}"
1345        );
1346
1347        let type_tag = session
1348            .get_type_tag(&type_)
1349            .map_err(|e| crate::error::convert_vm_error(e, vm, session.get_resolver()))?;
1350
1351        let struct_tag = match type_tag {
1352            TypeTag::Struct(inner) => *inner,
1353            _ => invariant_violation!("Non struct type for object"),
1354        };
1355        MoveObject::new_from_execution(
1356            struct_tag.into(),
1357            has_public_transfer,
1358            old_obj_ver.unwrap_or_default(),
1359            contents,
1360            protocol_config,
1361            /* system_mutation */ false,
1362        )
1363    }
1364}