pub use checked::*;
#[sui_macros::with_checked_arithmetic]
mod checked {
use std::{
collections::{BTreeMap, BTreeSet, HashMap},
sync::Arc,
};
use crate::adapter::new_native_extensions;
use crate::error::convert_vm_error;
use crate::execution_mode::ExecutionMode;
use crate::execution_value::{
CommandKind, ExecutionState, InputObjectMetadata, InputValue, ObjectContents, ObjectValue,
RawValueType, ResultValue, TryFromValue, UsageKind, Value,
};
use crate::gas_charger::GasCharger;
use crate::programmable_transactions::linkage_view::{LinkageInfo, LinkageView, SavedLinkage};
use crate::type_resolver::TypeTagResolver;
use move_binary_format::{
errors::{Location, VMError, VMResult},
file_format::{CodeOffset, FunctionDefinitionIndex, TypeParameterIndex},
CompiledModule,
};
use move_core_types::{
account_address::AccountAddress,
language_storage::{ModuleId, StructTag, TypeTag},
};
use move_vm_runtime::{move_vm::MoveVM, session::Session};
use move_vm_types::loaded_data::runtime_types::Type;
use sui_move_natives::object_runtime::{
self, get_all_uids, max_event_error, ObjectRuntime, RuntimeResults,
};
use sui_protocol_config::ProtocolConfig;
use sui_types::execution_status::CommandArgumentError;
use sui_types::storage::PackageObject;
use sui_types::{
balance::Balance,
base_types::{MoveObjectType, ObjectID, SequenceNumber, SuiAddress, TxContext},
coin::Coin,
error::{command_argument_error, ExecutionError, ExecutionErrorKind},
event::Event,
execution::{ExecutionResults, ExecutionResultsV1},
metrics::LimitsMetrics,
move_package::MovePackage,
object::{Data, MoveObject, Object, ObjectInner, Owner},
storage::{
BackingPackageStore, ChildObjectResolver, DeleteKind, DeleteKindWithOldVersion,
ObjectChange, WriteKind,
},
transaction::{Argument, CallArg, ObjectArg},
};
pub struct ExecutionContext<'vm, 'state, 'a> {
pub protocol_config: &'a ProtocolConfig,
pub metrics: Arc<LimitsMetrics>,
pub vm: &'vm MoveVM,
pub state_view: &'state dyn ExecutionState,
pub tx_context: &'a mut TxContext,
pub gas_charger: &'a mut GasCharger,
pub session: Session<'state, 'vm, LinkageView<'state>>,
additional_transfers: Vec<(SuiAddress, ObjectValue)>,
new_packages: Vec<Object>,
user_events: Vec<(ModuleId, StructTag, Vec<u8>)>,
gas: InputValue,
inputs: Vec<InputValue>,
results: Vec<Vec<ResultValue>>,
borrowed: HashMap<Argument, bool>,
}
struct AdditionalWrite {
recipient: Owner,
type_: Type,
has_public_transfer: bool,
bytes: Vec<u8>,
}
impl<'vm, 'state, 'a> ExecutionContext<'vm, 'state, 'a> {
pub fn new(
protocol_config: &'a ProtocolConfig,
metrics: Arc<LimitsMetrics>,
vm: &'vm MoveVM,
state_view: &'state dyn ExecutionState,
tx_context: &'a mut TxContext,
gas_charger: &'a mut GasCharger,
inputs: Vec<CallArg>,
) -> Result<Self, ExecutionError> {
let init_linkage = if protocol_config.package_upgrades_supported() {
LinkageInfo::Unset
} else {
LinkageInfo::Universal
};
let linkage = LinkageView::new(Box::new(state_view.as_sui_resolver()), init_linkage);
let mut tmp_session = new_session(
vm,
linkage,
state_view.as_child_resolver(),
BTreeMap::new(),
!gas_charger.is_unmetered(),
protocol_config,
metrics.clone(),
);
let mut input_object_map = BTreeMap::new();
let inputs = inputs
.into_iter()
.map(|call_arg| {
load_call_arg(
vm,
state_view,
&mut tmp_session,
&mut input_object_map,
call_arg,
)
})
.collect::<Result<_, ExecutionError>>()?;
let gas = if let Some(gas_coin) = gas_charger.gas_coin() {
let mut gas = load_object(
vm,
state_view,
&mut tmp_session,
&mut input_object_map,
false,
gas_coin,
)?;
let Some(Value::Object(ObjectValue {
contents: ObjectContents::Coin(coin),
..
})) = &mut gas.inner.value
else {
invariant_violation!("Gas object should be a populated coin")
};
let max_gas_in_balance = gas_charger.gas_budget();
let Some(new_balance) = coin.balance.value().checked_sub(max_gas_in_balance) else {
invariant_violation!(
"Transaction input checker should check that there is enough gas"
);
};
coin.balance = Balance::new(new_balance);
gas
} else {
InputValue {
object_metadata: None,
inner: ResultValue {
last_usage_kind: None,
value: None,
},
}
};
let (res, linkage) = tmp_session.finish();
let change_set = res.map_err(|e| crate::error::convert_vm_error(e, vm, &linkage))?;
assert_invariant!(change_set.accounts().is_empty(), "Change set must be empty");
let session = new_session(
vm,
linkage,
state_view.as_child_resolver(),
input_object_map,
!gas_charger.is_unmetered(),
protocol_config,
metrics.clone(),
);
#[skip_checked_arithmetic]
move_vm_profiler::tracing_feature_enabled! {
use move_vm_profiler::GasProfiler;
use move_vm_types::gas::GasMeter;
use crate::gas_meter::SuiGasMeter;
let tx_digest = tx_context.digest();
let remaining_gas: u64 = move_vm_types::gas::GasMeter::remaining_gas(&SuiGasMeter(
gas_charger.move_gas_status_mut(),
))
.into();
SuiGasMeter(gas_charger.move_gas_status_mut()).set_profiler(GasProfiler::init(
&vm.config().profiler_config,
format!("{}", tx_digest),
remaining_gas,
));
}
Ok(Self {
protocol_config,
metrics,
vm,
state_view,
tx_context,
gas_charger,
session,
gas,
inputs,
results: vec![],
additional_transfers: vec![],
new_packages: vec![],
user_events: vec![],
borrowed: HashMap::new(),
})
}
pub fn fresh_id(&mut self) -> Result<ObjectID, ExecutionError> {
let object_id = self.tx_context.fresh_id();
let object_runtime: &mut ObjectRuntime = self.session.get_native_extensions().get_mut();
object_runtime
.new_id(object_id)
.map_err(|e| self.convert_vm_error(e.finish(Location::Undefined)))?;
Ok(object_id)
}
pub fn delete_id(&mut self, object_id: ObjectID) -> Result<(), ExecutionError> {
let object_runtime: &mut ObjectRuntime = self.session.get_native_extensions().get_mut();
object_runtime
.delete_id(object_id)
.map_err(|e| self.convert_vm_error(e.finish(Location::Undefined)))
}
pub fn set_link_context(
&mut self,
package_id: ObjectID,
) -> Result<AccountAddress, ExecutionError> {
let resolver = self.session.get_resolver();
if resolver.has_linkage(package_id) {
return Ok(resolver.original_package_id().unwrap_or(*package_id));
}
let package = package_for_linkage(&self.session, package_id)
.map_err(|e| self.convert_vm_error(e))?;
set_linkage(&mut self.session, package.move_package())
}
pub fn set_linkage(
&mut self,
package: &MovePackage,
) -> Result<AccountAddress, ExecutionError> {
set_linkage(&mut self.session, package)
}
pub fn reset_linkage(&mut self) {
reset_linkage(&mut self.session);
}
pub fn steal_linkage(&mut self) -> Option<SavedLinkage> {
steal_linkage(&mut self.session)
}
pub fn restore_linkage(
&mut self,
saved: Option<SavedLinkage>,
) -> Result<(), ExecutionError> {
restore_linkage(&mut self.session, saved)
}
pub fn load_type(&mut self, type_tag: &TypeTag) -> VMResult<Type> {
load_type(&mut self.session, type_tag)
}
pub fn take_user_events(
&mut self,
module_id: &ModuleId,
function: FunctionDefinitionIndex,
last_offset: CodeOffset,
) -> Result<(), ExecutionError> {
let object_runtime: &mut ObjectRuntime = self.session.get_native_extensions().get_mut();
let events = object_runtime.take_user_events();
let num_events = self.user_events.len() + events.len();
let max_events = self.protocol_config.max_num_event_emit();
if num_events as u64 > max_events {
let err = max_event_error(max_events)
.at_code_offset(function, last_offset)
.finish(Location::Module(module_id.clone()));
return Err(self.convert_vm_error(err));
}
let new_events = events
.into_iter()
.map(|(ty, tag, value)| {
let layout = self
.session
.type_to_type_layout(&ty)
.map_err(|e| self.convert_vm_error(e))?;
let Some(bytes) = value.simple_serialize(&layout) else {
invariant_violation!("Failed to deserialize already serialized Move value");
};
Ok((module_id.clone(), tag, bytes))
})
.collect::<Result<Vec<_>, ExecutionError>>()?;
self.user_events.extend(new_events);
Ok(())
}
pub fn by_value_arg<V: TryFromValue>(
&mut self,
command_kind: CommandKind<'_>,
arg_idx: usize,
arg: Argument,
) -> Result<V, ExecutionError> {
self.by_value_arg_(command_kind, arg)
.map_err(|e| command_argument_error(e, arg_idx))
}
fn by_value_arg_<V: TryFromValue>(
&mut self,
command_kind: CommandKind<'_>,
arg: Argument,
) -> Result<V, CommandArgumentError> {
let is_borrowed = self.arg_is_borrowed(&arg);
let (input_metadata_opt, val_opt) = self.borrow_mut(arg, UsageKind::ByValue)?;
let is_copyable = if let Some(val) = val_opt {
val.is_copyable()
} else {
return Err(CommandArgumentError::InvalidValueUsage);
};
if !is_copyable && is_borrowed {
return Err(CommandArgumentError::InvalidValueUsage);
}
if matches!(arg, Argument::GasCoin)
&& !matches!(command_kind, CommandKind::TransferObjects)
{
return Err(CommandArgumentError::InvalidGasCoinUsage);
}
if matches!(
input_metadata_opt,
Some(InputObjectMetadata::InputObject {
owner: Owner::Immutable | Owner::Shared { .. },
..
})
) {
return Err(CommandArgumentError::InvalidObjectByValue);
}
let val = if is_copyable {
val_opt.as_ref().unwrap().clone()
} else {
val_opt.take().unwrap()
};
V::try_from_value(val)
}
pub fn borrow_arg_mut<V: TryFromValue>(
&mut self,
arg_idx: usize,
arg: Argument,
) -> Result<V, ExecutionError> {
self.borrow_arg_mut_(arg)
.map_err(|e| command_argument_error(e, arg_idx))
}
fn borrow_arg_mut_<V: TryFromValue>(
&mut self,
arg: Argument,
) -> Result<V, CommandArgumentError> {
if self.arg_is_borrowed(&arg) {
return Err(CommandArgumentError::InvalidValueUsage);
}
self.borrowed.insert(arg, true);
let (input_metadata_opt, val_opt) = self.borrow_mut(arg, UsageKind::BorrowMut)?;
let is_copyable = if let Some(val) = val_opt {
val.is_copyable()
} else {
return Err(CommandArgumentError::InvalidValueUsage);
};
if let Some(InputObjectMetadata::InputObject {
is_mutable_input: false,
..
}) = input_metadata_opt
{
return Err(CommandArgumentError::InvalidObjectByMutRef);
}
let val = if is_copyable {
val_opt.as_ref().unwrap().clone()
} else {
val_opt.take().unwrap()
};
V::try_from_value(val)
}
pub fn borrow_arg<V: TryFromValue>(
&mut self,
arg_idx: usize,
arg: Argument,
) -> Result<V, ExecutionError> {
self.borrow_arg_(arg)
.map_err(|e| command_argument_error(e, arg_idx))
}
fn borrow_arg_<V: TryFromValue>(
&mut self,
arg: Argument,
) -> Result<V, CommandArgumentError> {
if self.arg_is_mut_borrowed(&arg) {
return Err(CommandArgumentError::InvalidValueUsage);
}
self.borrowed.insert(arg, false);
let (_input_metadata_opt, val_opt) = self.borrow_mut(arg, UsageKind::BorrowImm)?;
if val_opt.is_none() {
return Err(CommandArgumentError::InvalidValueUsage);
}
V::try_from_value(val_opt.as_ref().unwrap().clone())
}
pub fn restore_arg<Mode: ExecutionMode>(
&mut self,
updates: &mut Mode::ArgumentUpdates,
arg: Argument,
value: Value,
) -> Result<(), ExecutionError> {
Mode::add_argument_update(self, updates, arg, &value)?;
let was_mut_opt = self.borrowed.remove(&arg);
assert_invariant!(
was_mut_opt.is_some() && was_mut_opt.unwrap(),
"Should never restore a non-mut borrowed value. \
The take+restore is an implementation detail of mutable references"
);
let Ok((_, value_opt)) = self.borrow_mut_impl(arg, None) else {
invariant_violation!("Should be able to borrow argument to restore it")
};
let old_value = value_opt.replace(value);
assert_invariant!(
old_value.is_none() || old_value.unwrap().is_copyable(),
"Should never restore a non-taken value, unless it is copyable. \
The take+restore is an implementation detail of mutable references"
);
Ok(())
}
pub fn transfer_object(
&mut self,
obj: ObjectValue,
addr: SuiAddress,
) -> Result<(), ExecutionError> {
self.additional_transfers.push((addr, obj));
Ok(())
}
pub fn new_package<'p>(
&self,
modules: &[CompiledModule],
dependencies: impl IntoIterator<Item = &'p MovePackage>,
) -> Result<Object, ExecutionError> {
Object::new_package(
modules,
self.tx_context.digest(),
self.protocol_config.max_move_package_size(),
self.protocol_config.move_binary_format_version(),
dependencies,
)
}
pub fn upgrade_package<'p>(
&self,
storage_id: ObjectID,
previous_package: &MovePackage,
new_modules: &[CompiledModule],
dependencies: impl IntoIterator<Item = &'p MovePackage>,
) -> Result<Object, ExecutionError> {
Object::new_upgraded_package(
previous_package,
storage_id,
new_modules,
self.tx_context.digest(),
self.protocol_config,
dependencies,
)
}
pub fn write_package(&mut self, package: Object) -> Result<(), ExecutionError> {
assert_invariant!(package.is_package(), "Must be a package");
self.new_packages.push(package);
Ok(())
}
pub fn push_command_results(&mut self, results: Vec<Value>) -> Result<(), ExecutionError> {
assert_invariant!(
self.borrowed.values().all(|is_mut| !is_mut),
"all mut borrows should be restored"
);
self.borrowed = HashMap::new();
self.results
.push(results.into_iter().map(ResultValue::new).collect());
Ok(())
}
pub fn finish<Mode: ExecutionMode>(self) -> Result<ExecutionResults, ExecutionError> {
let Self {
protocol_config,
metrics,
vm,
state_view,
tx_context,
gas_charger,
session,
additional_transfers,
new_packages,
gas,
inputs,
results,
user_events,
..
} = self;
let tx_digest = tx_context.digest();
let mut additional_writes = BTreeMap::new();
let mut input_object_metadata = BTreeMap::new();
let mut by_value_inputs = BTreeSet::new();
let mut add_input_object_write = |input| -> Result<(), ExecutionError> {
let InputValue {
object_metadata: object_metadata_opt,
inner: ResultValue { value, .. },
} = input;
let Some(object_metadata) = object_metadata_opt else {
return Ok(());
};
let InputObjectMetadata::InputObject {
is_mutable_input,
owner,
id,
..
} = &object_metadata
else {
unreachable!("Found non-input object metadata for input object when adding writes to input objects -- impossible in v0");
};
input_object_metadata.insert(object_metadata.id(), object_metadata.clone());
let Some(Value::Object(object_value)) = value else {
by_value_inputs.insert(*id);
return Ok(());
};
if *is_mutable_input {
add_additional_write(&mut additional_writes, owner.clone(), object_value)?;
}
Ok(())
};
let gas_id_opt = gas.object_metadata.as_ref().map(|info| info.id());
add_input_object_write(gas)?;
for input in inputs {
add_input_object_write(input)?
}
if !Mode::allow_arbitrary_values() {
for (i, command_result) in results.iter().enumerate() {
for (j, result_value) in command_result.iter().enumerate() {
let ResultValue {
last_usage_kind,
value,
} = result_value;
match value {
None => (),
Some(Value::Object(_)) => {
return Err(ExecutionErrorKind::UnusedValueWithoutDrop {
result_idx: i as u16,
secondary_idx: j as u16,
}
.into())
}
Some(Value::Raw(RawValueType::Any, _)) => (),
Some(Value::Raw(RawValueType::Loaded { abilities, .. }, _)) => {
if abilities.has_drop()
|| (abilities.has_copy()
&& matches!(last_usage_kind, Some(UsageKind::ByValue)))
{
} else {
let msg = if abilities.has_copy() {
"The value has copy, but not drop. \
Its last usage must be by-value so it can be taken."
} else {
"Unused value without drop"
};
return Err(ExecutionError::new_with_source(
ExecutionErrorKind::UnusedValueWithoutDrop {
result_idx: i as u16,
secondary_idx: j as u16,
},
msg,
));
}
}
Some(Value::Receiving(_, _, _)) => {
unreachable!("Impossible to hit Receiving in v0")
}
}
}
}
}
for (recipient, object_value) in additional_transfers {
let owner = Owner::AddressOwner(recipient);
add_additional_write(&mut additional_writes, owner, object_value)?;
}
if let Some(gas_id) = gas_id_opt {
refund_max_gas_budget(&mut additional_writes, gas_charger, gas_id)?;
}
let (res, linkage) = session.finish_with_extensions();
let (_, mut native_context_extensions) =
res.map_err(|e| convert_vm_error(e, vm, &linkage))?;
let object_runtime: ObjectRuntime = native_context_extensions.remove();
let new_ids = object_runtime.new_ids().clone();
let external_transfers = additional_writes.keys().copied().collect();
let RuntimeResults {
writes,
deletions,
user_events: remaining_events,
loaded_child_objects,
} = object_runtime.finish(by_value_inputs, external_transfers)?;
assert_invariant!(
remaining_events.is_empty(),
"Events should be taken after every Move call"
);
let mut object_changes = BTreeMap::new();
for package in new_packages {
let id = package.id();
let change = ObjectChange::Write(package, WriteKind::Create);
object_changes.insert(id, change);
}
let tmp_session = new_session(
vm,
linkage,
state_view.as_child_resolver(),
BTreeMap::new(),
!gas_charger.is_unmetered(),
protocol_config,
metrics,
);
for (id, additional_write) in additional_writes {
let AdditionalWrite {
recipient,
type_,
has_public_transfer,
bytes,
} = additional_write;
let write_kind = if input_object_metadata.contains_key(&id)
|| loaded_child_objects.contains_key(&id)
{
assert_invariant!(
!new_ids.contains_key(&id),
"new id should not be in mutations"
);
WriteKind::Mutate
} else if new_ids.contains_key(&id) {
WriteKind::Create
} else {
WriteKind::Unwrap
};
let move_object = unsafe {
create_written_object(
vm,
&tmp_session,
protocol_config,
&input_object_metadata,
&loaded_child_objects,
id,
type_,
has_public_transfer,
bytes,
write_kind,
)?
};
let object = Object::new_move(move_object, recipient, tx_digest);
let change = ObjectChange::Write(object, write_kind);
object_changes.insert(id, change);
}
for (id, (write_kind, recipient, ty, value)) in writes {
let abilities = tmp_session
.get_type_abilities(&ty)
.map_err(|e| convert_vm_error(e, vm, tmp_session.get_resolver()))?;
let has_public_transfer = abilities.has_store();
let layout = tmp_session
.type_to_type_layout(&ty)
.map_err(|e| convert_vm_error(e, vm, tmp_session.get_resolver()))?;
let Some(bytes) = value.simple_serialize(&layout) else {
invariant_violation!("Failed to deserialize already serialized Move value");
};
let move_object = unsafe {
create_written_object(
vm,
&tmp_session,
protocol_config,
&input_object_metadata,
&loaded_child_objects,
id,
ty,
has_public_transfer,
bytes,
write_kind,
)?
};
let object = Object::new_move(move_object, recipient, tx_digest);
let change = ObjectChange::Write(object, write_kind);
object_changes.insert(id, change);
}
for (id, delete_kind) in deletions {
let delete_kind_with_seq = match delete_kind {
DeleteKind::Normal | DeleteKind::Wrap => {
let old_version = match input_object_metadata.get(&id) {
Some(metadata) => {
assert_invariant!(
!matches!(metadata, InputObjectMetadata::InputObject { owner: Owner::Immutable, .. }),
"Attempting to delete immutable object {id} via delete kind {delete_kind}"
);
metadata.version()
}
None => {
match loaded_child_objects.get(&id) {
Some(version) => *version,
None => invariant_violation!("Deleted/wrapped object {id} must be either in input or loaded child objects")
}
}
};
if delete_kind == DeleteKind::Normal {
DeleteKindWithOldVersion::Normal(old_version)
} else {
DeleteKindWithOldVersion::Wrap(old_version)
}
}
DeleteKind::UnwrapThenDelete => {
if protocol_config.simplified_unwrap_then_delete() {
DeleteKindWithOldVersion::UnwrapThenDelete
} else {
let old_version =
match state_view.get_latest_parent_entry_ref_deprecated(id) {
Some((_, previous_version, _)) => previous_version,
None => continue,
};
DeleteKindWithOldVersion::UnwrapThenDeleteDEPRECATED(old_version)
}
}
};
object_changes.insert(id, ObjectChange::Delete(delete_kind_with_seq));
}
let (res, linkage) = tmp_session.finish();
let change_set = res.map_err(|e| convert_vm_error(e, vm, &linkage))?;
assert_invariant!(change_set.accounts().is_empty(), "Change set must be empty");
Ok(ExecutionResults::V1(ExecutionResultsV1 {
object_changes,
user_events: user_events
.into_iter()
.map(|(module_id, tag, contents)| {
Event::new(
module_id.address(),
module_id.name(),
tx_context.sender(),
tag,
contents,
)
})
.collect(),
}))
}
pub fn convert_vm_error(&self, error: VMError) -> ExecutionError {
crate::error::convert_vm_error(error, self.vm, self.session.get_resolver())
}
pub fn convert_type_argument_error(&self, idx: usize, error: VMError) -> ExecutionError {
use move_core_types::vm_status::StatusCode;
use sui_types::execution_status::TypeArgumentError;
match error.major_status() {
StatusCode::NUMBER_OF_TYPE_ARGUMENTS_MISMATCH => {
ExecutionErrorKind::TypeArityMismatch.into()
}
StatusCode::TYPE_RESOLUTION_FAILURE => ExecutionErrorKind::TypeArgumentError {
argument_idx: idx as TypeParameterIndex,
kind: TypeArgumentError::TypeNotFound,
}
.into(),
StatusCode::CONSTRAINT_NOT_SATISFIED => ExecutionErrorKind::TypeArgumentError {
argument_idx: idx as TypeParameterIndex,
kind: TypeArgumentError::ConstraintNotSatisfied,
}
.into(),
_ => self.convert_vm_error(error),
}
}
fn arg_is_borrowed(&self, arg: &Argument) -> bool {
self.borrowed.contains_key(arg)
}
fn arg_is_mut_borrowed(&self, arg: &Argument) -> bool {
matches!(self.borrowed.get(arg), Some(true))
}
fn borrow_mut(
&mut self,
arg: Argument,
usage: UsageKind,
) -> Result<(Option<&InputObjectMetadata>, &mut Option<Value>), CommandArgumentError>
{
self.borrow_mut_impl(arg, Some(usage))
}
fn borrow_mut_impl(
&mut self,
arg: Argument,
update_last_usage: Option<UsageKind>,
) -> Result<(Option<&InputObjectMetadata>, &mut Option<Value>), CommandArgumentError>
{
let (metadata, result_value) = match arg {
Argument::GasCoin => (self.gas.object_metadata.as_ref(), &mut self.gas.inner),
Argument::Input(i) => {
let Some(input_value) = self.inputs.get_mut(i as usize) else {
return Err(CommandArgumentError::IndexOutOfBounds { idx: i });
};
(input_value.object_metadata.as_ref(), &mut input_value.inner)
}
Argument::Result(i) => {
let Some(command_result) = self.results.get_mut(i as usize) else {
return Err(CommandArgumentError::IndexOutOfBounds { idx: i });
};
if command_result.len() != 1 {
return Err(CommandArgumentError::InvalidResultArity { result_idx: i });
}
(None, &mut command_result[0])
}
Argument::NestedResult(i, j) => {
let Some(command_result) = self.results.get_mut(i as usize) else {
return Err(CommandArgumentError::IndexOutOfBounds { idx: i });
};
let Some(result_value) = command_result.get_mut(j as usize) else {
return Err(CommandArgumentError::SecondaryIndexOutOfBounds {
result_idx: i,
secondary_idx: j,
});
};
(None, result_value)
}
};
if let Some(usage) = update_last_usage {
result_value.last_usage_kind = Some(usage);
}
Ok((metadata, &mut result_value.value))
}
}
impl TypeTagResolver for ExecutionContext<'_, '_, '_> {
fn get_type_tag(&self, type_: &Type) -> Result<TypeTag, ExecutionError> {
self.session
.get_type_tag(type_)
.map_err(|e| self.convert_vm_error(e))
}
}
pub(crate) fn new_session<'state, 'vm>(
vm: &'vm MoveVM,
linkage: LinkageView<'state>,
child_resolver: &'state dyn ChildObjectResolver,
input_objects: BTreeMap<ObjectID, object_runtime::InputObject>,
is_metered: bool,
protocol_config: &ProtocolConfig,
metrics: Arc<LimitsMetrics>,
) -> Session<'state, 'vm, LinkageView<'state>> {
vm.new_session_with_extensions(
linkage,
new_native_extensions(
child_resolver,
input_objects,
is_metered,
protocol_config,
metrics,
),
)
}
pub(crate) fn new_session_for_linkage<'vm, 'state>(
vm: &'vm MoveVM,
linkage: LinkageView<'state>,
) -> Session<'state, 'vm, LinkageView<'state>> {
vm.new_session(linkage)
}
pub fn set_linkage(
session: &mut Session<LinkageView>,
linkage: &MovePackage,
) -> Result<AccountAddress, ExecutionError> {
session.get_resolver_mut().set_linkage(linkage)
}
pub fn reset_linkage(session: &mut Session<LinkageView>) {
session.get_resolver_mut().reset_linkage();
}
pub fn steal_linkage(session: &mut Session<LinkageView>) -> Option<SavedLinkage> {
session.get_resolver_mut().steal_linkage()
}
pub fn restore_linkage(
session: &mut Session<LinkageView>,
saved: Option<SavedLinkage>,
) -> Result<(), ExecutionError> {
session.get_resolver_mut().restore_linkage(saved)
}
fn package_for_linkage(
session: &Session<LinkageView>,
package_id: ObjectID,
) -> VMResult<PackageObject> {
use move_binary_format::errors::PartialVMError;
use move_core_types::vm_status::StatusCode;
match session.get_resolver().get_package_object(&package_id) {
Ok(Some(package)) => Ok(package),
Ok(None) => Err(PartialVMError::new(StatusCode::LINKER_ERROR)
.with_message(format!("Cannot find link context {package_id} in store"))
.finish(Location::Undefined)),
Err(err) => Err(PartialVMError::new(StatusCode::LINKER_ERROR)
.with_message(format!(
"Error loading link context {package_id} from store: {err}"
))
.finish(Location::Undefined)),
}
}
pub fn load_type(session: &mut Session<LinkageView>, type_tag: &TypeTag) -> VMResult<Type> {
use move_binary_format::errors::PartialVMError;
use move_core_types::vm_status::StatusCode;
fn verification_error<T>(code: StatusCode) -> VMResult<T> {
Err(PartialVMError::new(code).finish(Location::Undefined))
}
Ok(match type_tag {
TypeTag::Bool => Type::Bool,
TypeTag::U8 => Type::U8,
TypeTag::U16 => Type::U16,
TypeTag::U32 => Type::U32,
TypeTag::U64 => Type::U64,
TypeTag::U128 => Type::U128,
TypeTag::U256 => Type::U256,
TypeTag::Address => Type::Address,
TypeTag::Signer => Type::Signer,
TypeTag::Vector(inner) => Type::Vector(Box::new(load_type(session, inner)?)),
TypeTag::Struct(struct_tag) => {
let StructTag {
address,
module,
name,
type_params,
} = struct_tag.as_ref();
let defining_id = ObjectID::from_address(*address);
let package = package_for_linkage(session, defining_id)?;
let original_address =
set_linkage(session, package.move_package()).map_err(|e| {
PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
.with_message(e.to_string())
.finish(Location::Undefined)
})?;
let runtime_id = ModuleId::new(original_address, module.clone());
let res = session.load_struct(&runtime_id, name);
reset_linkage(session);
let (idx, struct_type) = res?;
let type_param_constraints = struct_type.type_param_constraints();
if type_param_constraints.len() != type_params.len() {
return verification_error(StatusCode::NUMBER_OF_TYPE_ARGUMENTS_MISMATCH);
}
if type_params.is_empty() {
Type::Datatype(idx)
} else {
let loaded_type_params = type_params
.iter()
.map(|type_param| load_type(session, type_param))
.collect::<VMResult<Vec<_>>>()?;
for (constraint, param) in type_param_constraints.zip(&loaded_type_params) {
let abilities = session.get_type_abilities(param)?;
if !constraint.is_subset(abilities) {
return verification_error(StatusCode::CONSTRAINT_NOT_SATISFIED);
}
}
Type::DatatypeInstantiation(Box::new((idx, loaded_type_params)))
}
}
})
}
pub(crate) fn make_object_value<'vm, 'state>(
vm: &'vm MoveVM,
session: &mut Session<'state, 'vm, LinkageView<'state>>,
type_: MoveObjectType,
has_public_transfer: bool,
used_in_non_entry_move_call: bool,
contents: &[u8],
) -> Result<ObjectValue, ExecutionError> {
let contents = if type_.is_coin() {
let Ok(coin) = Coin::from_bcs_bytes(contents) else {
invariant_violation!("Could not deserialize a coin")
};
ObjectContents::Coin(coin)
} else {
ObjectContents::Raw(contents.to_vec())
};
let tag: StructTag = type_.into();
let type_ = load_type(session, &TypeTag::Struct(Box::new(tag)))
.map_err(|e| crate::error::convert_vm_error(e, vm, session.get_resolver()))?;
Ok(ObjectValue {
type_,
has_public_transfer,
used_in_non_entry_move_call,
contents,
})
}
pub(crate) fn value_from_object<'vm, 'state>(
vm: &'vm MoveVM,
session: &mut Session<'state, 'vm, LinkageView<'state>>,
object: &Object,
) -> Result<ObjectValue, ExecutionError> {
let ObjectInner {
data: Data::Move(object),
..
} = object.as_inner()
else {
invariant_violation!("Expected a Move object");
};
let used_in_non_entry_move_call = false;
make_object_value(
vm,
session,
object.type_().clone(),
object.has_public_transfer(),
used_in_non_entry_move_call,
object.contents(),
)
}
fn load_object<'vm, 'state>(
vm: &'vm MoveVM,
state_view: &'state dyn ExecutionState,
session: &mut Session<'state, 'vm, LinkageView<'state>>,
input_object_map: &mut BTreeMap<ObjectID, object_runtime::InputObject>,
override_as_immutable: bool,
id: ObjectID,
) -> Result<InputValue, ExecutionError> {
let Some(obj) = state_view.read_object(&id) else {
invariant_violation!("Object {} does not exist yet", id);
};
assert_invariant!(
!override_as_immutable || matches!(obj.owner, Owner::Shared { .. }),
"override_as_immutable should only be set for shared objects"
);
let is_mutable_input = match obj.owner {
Owner::AddressOwner(_) => true,
Owner::Shared { .. } => !override_as_immutable,
Owner::Immutable => false,
Owner::ObjectOwner(_) => {
invariant_violation!("ObjectOwner objects cannot be input")
}
Owner::ConsensusV2 { .. } => {
unimplemented!("ConsensusV2 does not exist for this execution version")
}
};
let owner = obj.owner.clone();
let version = obj.version();
let object_metadata = InputObjectMetadata::InputObject {
id,
is_mutable_input,
owner: owner.clone(),
version,
};
let obj_value = value_from_object(vm, session, obj)?;
let contained_uids = {
let fully_annotated_layout =
session
.type_to_fully_annotated_layout(&obj_value.type_)
.map_err(|e| convert_vm_error(e, vm, session.get_resolver()))?;
let mut bytes = vec![];
obj_value.write_bcs_bytes(&mut bytes);
match get_all_uids(&fully_annotated_layout, &bytes) {
Err(e) => {
invariant_violation!("Unable to retrieve UIDs for object. Got error: {e}")
}
Ok(uids) => uids,
}
};
let runtime_input = object_runtime::InputObject {
contained_uids,
owner,
version,
};
let prev = input_object_map.insert(id, runtime_input);
assert_invariant!(prev.is_none(), "Duplicate input object {}", id);
Ok(InputValue::new_object(object_metadata, obj_value))
}
fn load_call_arg<'vm, 'state>(
vm: &'vm MoveVM,
state_view: &'state dyn ExecutionState,
session: &mut Session<'state, 'vm, LinkageView<'state>>,
input_object_map: &mut BTreeMap<ObjectID, object_runtime::InputObject>,
call_arg: CallArg,
) -> Result<InputValue, ExecutionError> {
Ok(match call_arg {
CallArg::Pure(bytes) => InputValue::new_raw(RawValueType::Any, bytes),
CallArg::Object(obj_arg) => {
load_object_arg(vm, state_view, session, input_object_map, obj_arg)?
}
})
}
fn load_object_arg<'vm, 'state>(
vm: &'vm MoveVM,
state_view: &'state dyn ExecutionState,
session: &mut Session<'state, 'vm, LinkageView<'state>>,
input_object_map: &mut BTreeMap<ObjectID, object_runtime::InputObject>,
obj_arg: ObjectArg,
) -> Result<InputValue, ExecutionError> {
match obj_arg {
ObjectArg::ImmOrOwnedObject((id, _, _)) => load_object(
vm,
state_view,
session,
input_object_map,
false,
id,
),
ObjectArg::SharedObject { id, mutable, .. } => load_object(
vm,
state_view,
session,
input_object_map,
!mutable,
id,
),
ObjectArg::Receiving(_) => unreachable!("Impossible to hit Receiving in v0"),
}
}
fn add_additional_write(
additional_writes: &mut BTreeMap<ObjectID, AdditionalWrite>,
owner: Owner,
object_value: ObjectValue,
) -> Result<(), ExecutionError> {
let ObjectValue {
type_,
has_public_transfer,
contents,
..
} = object_value;
let bytes = match contents {
ObjectContents::Coin(coin) => coin.to_bcs_bytes(),
ObjectContents::Raw(bytes) => bytes,
};
let object_id = MoveObject::id_opt(&bytes).map_err(|e| {
ExecutionError::invariant_violation(format!("No id for Raw object bytes. {e}"))
})?;
let additional_write = AdditionalWrite {
recipient: owner,
type_,
has_public_transfer,
bytes,
};
additional_writes.insert(object_id, additional_write);
Ok(())
}
fn refund_max_gas_budget(
additional_writes: &mut BTreeMap<ObjectID, AdditionalWrite>,
gas_charger: &mut GasCharger,
gas_id: ObjectID,
) -> Result<(), ExecutionError> {
let Some(AdditionalWrite { bytes, .. }) = additional_writes.get_mut(&gas_id) else {
invariant_violation!("Gas object cannot be wrapped or destroyed")
};
let Ok(mut coin) = Coin::from_bcs_bytes(bytes) else {
invariant_violation!("Gas object must be a coin")
};
let Some(new_balance) = coin.balance.value().checked_add(gas_charger.gas_budget()) else {
return Err(ExecutionError::new_with_source(
ExecutionErrorKind::CoinBalanceOverflow,
"Gas coin too large after returning the max gas budget",
));
};
coin.balance = Balance::new(new_balance);
*bytes = coin.to_bcs_bytes();
Ok(())
}
unsafe fn create_written_object<'vm, 'state>(
vm: &'vm MoveVM,
session: &Session<'state, 'vm, LinkageView<'state>>,
protocol_config: &ProtocolConfig,
input_object_metadata: &BTreeMap<ObjectID, InputObjectMetadata>,
loaded_child_objects: &BTreeMap<ObjectID, SequenceNumber>,
id: ObjectID,
type_: Type,
has_public_transfer: bool,
contents: Vec<u8>,
write_kind: WriteKind,
) -> Result<MoveObject, ExecutionError> {
debug_assert_eq!(
id,
MoveObject::id_opt(&contents).expect("object contents should start with an id")
);
let metadata_opt = input_object_metadata.get(&id);
let loaded_child_version_opt = loaded_child_objects.get(&id);
assert_invariant!(
metadata_opt.is_none() || loaded_child_version_opt.is_none(),
"Loaded {id} as a child, but that object was an input object",
);
let old_obj_ver = metadata_opt
.map(|metadata| metadata.version())
.or_else(|| loaded_child_version_opt.copied());
debug_assert!(
(write_kind == WriteKind::Mutate) == old_obj_ver.is_some(),
"Inconsistent state: write_kind: {write_kind:?}, old ver: {old_obj_ver:?}"
);
let type_tag = session
.get_type_tag(&type_)
.map_err(|e| crate::error::convert_vm_error(e, vm, session.get_resolver()))?;
let struct_tag = match type_tag {
TypeTag::Struct(inner) => *inner,
_ => invariant_violation!("Non struct type for object"),
};
MoveObject::new_from_execution(
struct_tag.into(),
has_public_transfer,
old_obj_ver.unwrap_or_default(),
contents,
protocol_config,
)
}
}