pub use checked::*;
#[sui_macros::with_checked_arithmetic]
mod checked {
use crate::execution_mode::ExecutionMode;
use crate::execution_value::{
CommandKind, ExecutionState, ObjectContents, ObjectValue, RawValueType, Value,
};
use crate::gas_charger::GasCharger;
use move_binary_format::{
compatibility::{Compatibility, InclusionCheck},
errors::{Location, PartialVMResult, VMResult},
file_format::{AbilitySet, CodeOffset, FunctionDefinitionIndex, LocalIndex, Visibility},
file_format_common::VERSION_6,
normalized, CompiledModule,
};
use move_core_types::{
account_address::AccountAddress,
identifier::{IdentStr, Identifier},
language_storage::{ModuleId, TypeTag},
u256::U256,
};
use move_vm_runtime::{
move_vm::MoveVM,
session::{LoadedFunctionInstantiation, SerializedReturnValues},
};
use move_vm_types::loaded_data::runtime_types::{CachedDatatype, Type};
use serde::{de::DeserializeSeed, Deserialize};
use std::{
collections::{BTreeMap, BTreeSet},
fmt,
sync::Arc,
};
use sui_move_natives::object_runtime::ObjectRuntime;
use sui_protocol_config::ProtocolConfig;
use sui_types::execution_config_utils::to_binary_config;
use sui_types::execution_status::{CommandArgumentError, PackageUpgradeError};
use sui_types::storage::{get_package_objects, PackageObject};
use sui_types::{
base_types::{
MoveLegacyTxContext, MoveObjectType, ObjectID, SuiAddress, TxContext, TxContextKind,
RESOLVED_ASCII_STR, RESOLVED_STD_OPTION, RESOLVED_UTF8_STR, TX_CONTEXT_MODULE_NAME,
TX_CONTEXT_STRUCT_NAME,
},
coin::Coin,
error::{command_argument_error, ExecutionError, ExecutionErrorKind},
id::RESOLVED_SUI_ID,
metrics::LimitsMetrics,
move_package::{
normalize_deserialized_modules, MovePackage, UpgradeCap, UpgradePolicy, UpgradeReceipt,
UpgradeTicket,
},
transaction::{Argument, Command, ProgrammableMoveCall, ProgrammableTransaction},
transfer::RESOLVED_RECEIVING_STRUCT,
SUI_FRAMEWORK_ADDRESS,
};
use sui_verifier::{
private_generics::{EVENT_MODULE, PRIVATE_TRANSFER_FUNCTIONS, TRANSFER_MODULE},
INIT_FN_NAME,
};
use tracing::instrument;
use crate::adapter::substitute_package_id;
use crate::programmable_transactions::context::*;
pub fn execute<Mode: ExecutionMode>(
protocol_config: &ProtocolConfig,
metrics: Arc<LimitsMetrics>,
vm: &MoveVM,
state_view: &mut dyn ExecutionState,
tx_context: &mut TxContext,
gas_charger: &mut GasCharger,
pt: ProgrammableTransaction,
) -> Result<Mode::ExecutionResults, ExecutionError> {
let ProgrammableTransaction { inputs, commands } = pt;
let mut context = ExecutionContext::new(
protocol_config,
metrics,
vm,
state_view,
tx_context,
gas_charger,
inputs,
)?;
let mut mode_results = Mode::empty_results();
for (idx, command) in commands.into_iter().enumerate() {
if let Err(err) = execute_command::<Mode>(&mut context, &mut mode_results, command) {
let object_runtime: &ObjectRuntime = context.object_runtime();
let loaded_runtime_objects = object_runtime.loaded_runtime_objects();
drop(context);
state_view.save_loaded_runtime_objects(loaded_runtime_objects);
return Err(err.with_command_index(idx));
};
}
let object_runtime: &ObjectRuntime = context.object_runtime();
let loaded_runtime_objects = object_runtime.loaded_runtime_objects();
let wrapped_object_containers = object_runtime.wrapped_object_containers();
let finished = context.finish::<Mode>();
state_view.save_loaded_runtime_objects(loaded_runtime_objects);
state_view.save_wrapped_object_containers(wrapped_object_containers);
state_view.record_execution_results(finished?);
Ok(mode_results)
}
#[instrument(level = "trace", skip_all)]
fn execute_command<Mode: ExecutionMode>(
context: &mut ExecutionContext<'_, '_, '_>,
mode_results: &mut Mode::ExecutionResults,
command: Command,
) -> Result<(), ExecutionError> {
let mut argument_updates = Mode::empty_arguments();
let results = match command {
Command::MakeMoveVec(tag_opt, args) if args.is_empty() => {
let Some(tag) = tag_opt else {
invariant_violation!(
"input checker ensures if args are empty, there is a type specified"
);
};
let tag = unsafe { tag.into_type_tag_unchecked() };
let elem_ty = context
.load_type(&tag)
.map_err(|e| context.convert_vm_error(e))?;
let ty = Type::Vector(Box::new(elem_ty));
let abilities = context
.vm
.get_runtime()
.get_type_abilities(&ty)
.map_err(|e| context.convert_vm_error(e))?;
let bytes = bcs::to_bytes::<Vec<u8>>(&vec![]).unwrap();
vec![Value::Raw(
RawValueType::Loaded {
ty,
abilities,
used_in_non_entry_move_call: false,
},
bytes,
)]
}
Command::MakeMoveVec(tag_opt, args) => {
let mut res = vec![];
leb128::write::unsigned(&mut res, args.len() as u64).unwrap();
let mut arg_iter = args.into_iter().enumerate();
let (mut used_in_non_entry_move_call, elem_ty) = match tag_opt {
Some(tag) => {
let tag = unsafe { tag.into_type_tag_unchecked() };
let elem_ty = context
.load_type(&tag)
.map_err(|e| context.convert_vm_error(e))?;
(false, elem_ty)
}
None => {
let (idx, arg) = arg_iter.next().unwrap();
let obj: ObjectValue =
context.by_value_arg(CommandKind::MakeMoveVec, idx, arg)?;
obj.write_bcs_bytes(&mut res);
(obj.used_in_non_entry_move_call, obj.type_)
}
};
for (idx, arg) in arg_iter {
let value: Value = context.by_value_arg(CommandKind::MakeMoveVec, idx, arg)?;
check_param_type::<Mode>(context, idx, &value, &elem_ty)?;
used_in_non_entry_move_call =
used_in_non_entry_move_call || value.was_used_in_non_entry_move_call();
value.write_bcs_bytes(&mut res);
}
let ty = Type::Vector(Box::new(elem_ty));
let abilities = context
.vm
.get_runtime()
.get_type_abilities(&ty)
.map_err(|e| context.convert_vm_error(e))?;
vec![Value::Raw(
RawValueType::Loaded {
ty,
abilities,
used_in_non_entry_move_call,
},
res,
)]
}
Command::TransferObjects(objs, addr_arg) => {
let objs: Vec<ObjectValue> = objs
.into_iter()
.enumerate()
.map(|(idx, arg)| context.by_value_arg(CommandKind::TransferObjects, idx, arg))
.collect::<Result<_, _>>()?;
let addr: SuiAddress =
context.by_value_arg(CommandKind::TransferObjects, objs.len(), addr_arg)?;
for obj in objs {
obj.ensure_public_transfer_eligible()?;
context.transfer_object(obj, addr)?;
}
vec![]
}
Command::SplitCoins(coin_arg, amount_args) => {
let mut obj: ObjectValue = context.borrow_arg_mut(0, coin_arg)?;
let ObjectContents::Coin(coin) = &mut obj.contents else {
let e = ExecutionErrorKind::command_argument_error(
CommandArgumentError::TypeMismatch,
0,
);
let msg = "Expected a coin but got an non coin object".to_owned();
return Err(ExecutionError::new_with_source(e, msg));
};
let split_coins = amount_args
.into_iter()
.map(|amount_arg| {
let amount: u64 =
context.by_value_arg(CommandKind::SplitCoins, 1, amount_arg)?;
let new_coin_id = context.fresh_id()?;
let new_coin = coin.split(amount, new_coin_id)?;
let coin_type = obj.type_.clone();
let new_coin = unsafe { ObjectValue::coin(coin_type, new_coin) };
Ok(Value::Object(new_coin))
})
.collect::<Result<_, ExecutionError>>()?;
context.restore_arg::<Mode>(&mut argument_updates, coin_arg, Value::Object(obj))?;
split_coins
}
Command::MergeCoins(target_arg, coin_args) => {
let mut target: ObjectValue = context.borrow_arg_mut(0, target_arg)?;
let ObjectContents::Coin(target_coin) = &mut target.contents else {
let e = ExecutionErrorKind::command_argument_error(
CommandArgumentError::TypeMismatch,
0,
);
let msg = "Expected a coin but got an non coin object".to_owned();
return Err(ExecutionError::new_with_source(e, msg));
};
let coins: Vec<ObjectValue> = coin_args
.into_iter()
.enumerate()
.map(|(idx, arg)| context.by_value_arg(CommandKind::MergeCoins, idx + 1, arg))
.collect::<Result<_, _>>()?;
for (idx, coin) in coins.into_iter().enumerate() {
if target.type_ != coin.type_ {
let e = ExecutionErrorKind::command_argument_error(
CommandArgumentError::TypeMismatch,
(idx + 1) as u16,
);
let msg = "Coins do not have the same type".to_owned();
return Err(ExecutionError::new_with_source(e, msg));
}
let ObjectContents::Coin(Coin { id, balance }) = coin.contents else {
invariant_violation!(
"Target coin was a coin, and we already checked for the same type. \
This should be a coin"
);
};
context.delete_id(*id.object_id())?;
target_coin.add(balance)?;
}
context.restore_arg::<Mode>(
&mut argument_updates,
target_arg,
Value::Object(target),
)?;
vec![]
}
Command::MoveCall(move_call) => {
let ProgrammableMoveCall {
package,
module,
function,
type_arguments,
arguments,
} = *move_call;
let module = unsafe { Identifier::new_unchecked(module) };
let function = unsafe { Identifier::new_unchecked(function) };
let mut loaded_type_arguments = Vec::with_capacity(type_arguments.len());
for (ix, type_arg) in type_arguments.into_iter().enumerate() {
let type_arg = unsafe { type_arg.into_type_tag_unchecked() };
let ty = context
.load_type(&type_arg)
.map_err(|e| context.convert_type_argument_error(ix, e))?;
loaded_type_arguments.push(ty);
}
let original_address = context.set_link_context(package)?;
let runtime_id = ModuleId::new(original_address, module);
let return_values = execute_move_call::<Mode>(
context,
&mut argument_updates,
&runtime_id,
&function,
loaded_type_arguments,
arguments,
false,
);
context.linkage_view.reset_linkage();
return_values?
}
Command::Publish(modules, dep_ids) => {
execute_move_publish::<Mode>(context, &mut argument_updates, modules, dep_ids)?
}
Command::Upgrade(modules, dep_ids, current_package_id, upgrade_ticket) => {
execute_move_upgrade::<Mode>(
context,
modules,
dep_ids,
current_package_id,
upgrade_ticket,
)?
}
};
Mode::finish_command(context, mode_results, argument_updates, &results)?;
context.push_command_results(results)?;
Ok(())
}
fn execute_move_call<Mode: ExecutionMode>(
context: &mut ExecutionContext<'_, '_, '_>,
argument_updates: &mut Mode::ArgumentUpdates,
module_id: &ModuleId,
function: &IdentStr,
type_arguments: Vec<Type>,
arguments: Vec<Argument>,
is_init: bool,
) -> Result<Vec<Value>, ExecutionError> {
let LoadedFunctionInfo {
kind,
signature,
return_value_kinds,
index,
last_instr,
} = check_visibility_and_signature::<Mode>(
context,
module_id,
function,
&type_arguments,
is_init,
)?;
let (tx_context_kind, by_mut_ref, serialized_arguments) =
build_move_args::<Mode>(context, module_id, function, kind, &signature, &arguments)?;
let SerializedReturnValues {
mutable_reference_outputs,
return_values,
} = vm_move_call(
context,
module_id,
function,
type_arguments,
tx_context_kind,
serialized_arguments,
)?;
assert_invariant!(
by_mut_ref.len() == mutable_reference_outputs.len(),
"lost mutable input"
);
context.take_user_events(module_id, index, last_instr)?;
let saved_linkage = context.linkage_view.steal_linkage();
let used_in_non_entry_move_call = kind == FunctionKind::NonEntry;
let res = write_back_results::<Mode>(
context,
argument_updates,
&arguments,
used_in_non_entry_move_call,
mutable_reference_outputs
.into_iter()
.map(|(i, bytes, _layout)| (i, bytes)),
by_mut_ref,
return_values.into_iter().map(|(bytes, _layout)| bytes),
return_value_kinds,
);
context.linkage_view.restore_linkage(saved_linkage)?;
res
}
fn write_back_results<Mode: ExecutionMode>(
context: &mut ExecutionContext<'_, '_, '_>,
argument_updates: &mut Mode::ArgumentUpdates,
arguments: &[Argument],
non_entry_move_call: bool,
mut_ref_values: impl IntoIterator<Item = (u8, Vec<u8>)>,
mut_ref_kinds: impl IntoIterator<Item = (u8, ValueKind)>,
return_values: impl IntoIterator<Item = Vec<u8>>,
return_value_kinds: impl IntoIterator<Item = ValueKind>,
) -> Result<Vec<Value>, ExecutionError> {
for ((i, bytes), (j, kind)) in mut_ref_values.into_iter().zip(mut_ref_kinds) {
assert_invariant!(i == j, "lost mutable input");
let arg_idx = i as usize;
let value = make_value(context, kind, bytes, non_entry_move_call)?;
context.restore_arg::<Mode>(argument_updates, arguments[arg_idx], value)?;
}
return_values
.into_iter()
.zip(return_value_kinds)
.map(|(bytes, kind)| {
make_value(
context, kind, bytes, true,
)
})
.collect()
}
fn make_value(
context: &mut ExecutionContext<'_, '_, '_>,
value_info: ValueKind,
bytes: Vec<u8>,
used_in_non_entry_move_call: bool,
) -> Result<Value, ExecutionError> {
Ok(match value_info {
ValueKind::Object {
type_,
has_public_transfer,
} => Value::Object(context.make_object_value(
type_,
has_public_transfer,
used_in_non_entry_move_call,
&bytes,
)?),
ValueKind::Raw(ty, abilities) => Value::Raw(
RawValueType::Loaded {
ty,
abilities,
used_in_non_entry_move_call,
},
bytes,
),
})
}
fn execute_move_publish<Mode: ExecutionMode>(
context: &mut ExecutionContext<'_, '_, '_>,
argument_updates: &mut Mode::ArgumentUpdates,
module_bytes: Vec<Vec<u8>>,
dep_ids: Vec<ObjectID>,
) -> Result<Vec<Value>, ExecutionError> {
assert_invariant!(
!module_bytes.is_empty(),
"empty package is checked in transaction input checker"
);
context
.gas_charger
.charge_publish_package(module_bytes.iter().map(|v| v.len()).sum())?;
let mut modules = deserialize_modules::<Mode>(context, &module_bytes)?;
let runtime_id = if Mode::packages_are_predefined() {
(*modules[0].self_id().address()).into()
} else {
let id = context.tx_context.fresh_id();
substitute_package_id(&mut modules, id)?;
id
};
let storage_id = runtime_id;
let dependencies = fetch_packages(context, &dep_ids)?;
let package =
context.new_package(&modules, dependencies.iter().map(|p| p.move_package()))?;
context.linkage_view.set_linkage(&package)?;
context.write_package(package);
let res = publish_and_verify_modules(context, runtime_id, &modules)
.and_then(|_| init_modules::<Mode>(context, argument_updates, &modules));
context.linkage_view.reset_linkage();
if res.is_err() {
context.pop_package();
}
res?;
let values = if Mode::packages_are_predefined() {
vec![]
} else {
let cap = &UpgradeCap::new(context.fresh_id()?, storage_id);
vec![Value::Object(context.make_object_value(
UpgradeCap::type_().into(),
true,
false,
&bcs::to_bytes(cap).unwrap(),
)?)]
};
Ok(values)
}
fn execute_move_upgrade<Mode: ExecutionMode>(
context: &mut ExecutionContext<'_, '_, '_>,
module_bytes: Vec<Vec<u8>>,
dep_ids: Vec<ObjectID>,
current_package_id: ObjectID,
upgrade_ticket_arg: Argument,
) -> Result<Vec<Value>, ExecutionError> {
assert_invariant!(
!module_bytes.is_empty(),
"empty package is checked in transaction input checker"
);
context
.gas_charger
.charge_upgrade_package(module_bytes.iter().map(|v| v.len()).sum())?;
let upgrade_ticket_type = context
.load_type_from_struct(&UpgradeTicket::type_())
.map_err(|e| context.convert_vm_error(e))?;
let upgrade_receipt_type = context
.load_type_from_struct(&UpgradeReceipt::type_())
.map_err(|e| context.convert_vm_error(e))?;
let upgrade_ticket: UpgradeTicket = {
let mut ticket_bytes = Vec::new();
let ticket_val: Value =
context.by_value_arg(CommandKind::Upgrade, 0, upgrade_ticket_arg)?;
check_param_type::<Mode>(context, 0, &ticket_val, &upgrade_ticket_type)?;
ticket_val.write_bcs_bytes(&mut ticket_bytes);
bcs::from_bytes(&ticket_bytes).map_err(|_| {
ExecutionError::from_kind(ExecutionErrorKind::CommandArgumentError {
arg_idx: 0,
kind: CommandArgumentError::InvalidBCSBytes,
})
})?
};
if current_package_id != upgrade_ticket.package.bytes {
return Err(ExecutionError::from_kind(
ExecutionErrorKind::PackageUpgradeError {
upgrade_error: PackageUpgradeError::PackageIDDoesNotMatch {
package_id: current_package_id,
ticket_id: upgrade_ticket.package.bytes,
},
},
));
}
let hash_modules = true;
let computed_digest =
MovePackage::compute_digest_for_modules_and_deps(&module_bytes, &dep_ids, hash_modules)
.to_vec();
if computed_digest != upgrade_ticket.digest {
return Err(ExecutionError::from_kind(
ExecutionErrorKind::PackageUpgradeError {
upgrade_error: PackageUpgradeError::DigestDoesNotMatch {
digest: computed_digest,
},
},
));
}
let current_package = fetch_package(context, &upgrade_ticket.package.bytes)?;
let mut modules = deserialize_modules::<Mode>(context, &module_bytes)?;
let runtime_id = current_package.move_package().original_package_id();
substitute_package_id(&mut modules, runtime_id)?;
let storage_id = context.tx_context.fresh_id();
let dependencies = fetch_packages(context, &dep_ids)?;
let package = context.upgrade_package(
storage_id,
current_package.move_package(),
&modules,
dependencies.iter().map(|p| p.move_package()),
)?;
context.linkage_view.set_linkage(&package)?;
let res = publish_and_verify_modules(context, runtime_id, &modules);
context.linkage_view.reset_linkage();
res?;
check_compatibility(
context,
current_package.move_package(),
&modules,
upgrade_ticket.policy,
)?;
context.write_package(package);
Ok(vec![Value::Raw(
RawValueType::Loaded {
ty: upgrade_receipt_type,
abilities: AbilitySet::EMPTY,
used_in_non_entry_move_call: false,
},
bcs::to_bytes(&UpgradeReceipt::new(upgrade_ticket, storage_id)).unwrap(),
)])
}
fn check_compatibility<'a>(
context: &ExecutionContext,
existing_package: &MovePackage,
upgrading_modules: impl IntoIterator<Item = &'a CompiledModule>,
policy: u8,
) -> Result<(), ExecutionError> {
let Ok(policy) = UpgradePolicy::try_from(policy) else {
return Err(ExecutionError::from_kind(
ExecutionErrorKind::PackageUpgradeError {
upgrade_error: PackageUpgradeError::UnknownUpgradePolicy { policy },
},
));
};
let binary_config = to_binary_config(context.protocol_config);
let Ok(current_normalized) = existing_package.normalize(&binary_config) else {
invariant_violation!("Tried to normalize modules in existing package but failed")
};
let mut new_normalized = normalize_deserialized_modules(upgrading_modules.into_iter());
for (name, cur_module) in current_normalized {
let Some(new_module) = new_normalized.remove(&name) else {
return Err(ExecutionError::new_with_source(
ExecutionErrorKind::PackageUpgradeError {
upgrade_error: PackageUpgradeError::IncompatibleUpgrade,
},
format!("Existing module {name} not found in next version of package"),
));
};
check_module_compatibility(&policy, &cur_module, &new_module)?;
}
Ok(())
}
fn check_module_compatibility(
policy: &UpgradePolicy,
cur_module: &normalized::Module,
new_module: &normalized::Module,
) -> Result<(), ExecutionError> {
match policy {
UpgradePolicy::Additive => InclusionCheck::Subset.check(cur_module, new_module),
UpgradePolicy::DepOnly => InclusionCheck::Equal.check(cur_module, new_module),
UpgradePolicy::Compatible => {
let compatibility = Compatibility::upgrade_check();
compatibility.check(cur_module, new_module)
}
}
.map_err(|e| {
ExecutionError::new_with_source(
ExecutionErrorKind::PackageUpgradeError {
upgrade_error: PackageUpgradeError::IncompatibleUpgrade,
},
e,
)
})
}
fn fetch_package(
context: &ExecutionContext<'_, '_, '_>,
package_id: &ObjectID,
) -> Result<PackageObject, ExecutionError> {
let mut fetched_packages = fetch_packages(context, vec![package_id])?;
assert_invariant!(
fetched_packages.len() == 1,
"Number of fetched packages must match the number of package object IDs if successful."
);
match fetched_packages.pop() {
Some(pkg) => Ok(pkg),
None => invariant_violation!(
"We should always fetch a package for each object or return a dependency error."
),
}
}
fn fetch_packages<'ctx, 'vm, 'state, 'a>(
context: &'ctx ExecutionContext<'vm, 'state, 'a>,
package_ids: impl IntoIterator<Item = &'ctx ObjectID>,
) -> Result<Vec<PackageObject>, ExecutionError> {
let package_ids: BTreeSet<_> = package_ids.into_iter().collect();
match get_package_objects(&context.state_view, package_ids) {
Err(e) => Err(ExecutionError::new_with_source(
ExecutionErrorKind::PublishUpgradeMissingDependency,
e,
)),
Ok(Err(missing_deps)) => {
let msg = format!(
"Missing dependencies: {}",
missing_deps
.into_iter()
.map(|dep| format!("{}", dep))
.collect::<Vec<_>>()
.join(", ")
);
Err(ExecutionError::new_with_source(
ExecutionErrorKind::PublishUpgradeMissingDependency,
msg,
))
}
Ok(Ok(pkgs)) => Ok(pkgs),
}
}
fn vm_move_call(
context: &mut ExecutionContext<'_, '_, '_>,
module_id: &ModuleId,
function: &IdentStr,
type_arguments: Vec<Type>,
tx_context_kind: TxContextKind,
mut serialized_arguments: Vec<Vec<u8>>,
) -> Result<SerializedReturnValues, ExecutionError> {
match tx_context_kind {
TxContextKind::None => (),
TxContextKind::Mutable | TxContextKind::Immutable => {
serialized_arguments.push(context.tx_context.to_bcs_legacy_context());
}
}
let mut result = context
.execute_function_bypass_visibility(
module_id,
function,
type_arguments,
serialized_arguments,
)
.map_err(|e| context.convert_vm_error(e))?;
if tx_context_kind == TxContextKind::Mutable {
let Some((_, ctx_bytes, _)) = result.mutable_reference_outputs.pop() else {
invariant_violation!("Missing TxContext in reference outputs");
};
let updated_ctx: MoveLegacyTxContext = bcs::from_bytes(&ctx_bytes).map_err(|e| {
ExecutionError::invariant_violation(format!(
"Unable to deserialize TxContext bytes. {e}"
))
})?;
context.tx_context.update_state(updated_ctx)?;
}
Ok(result)
}
#[allow(clippy::extra_unused_type_parameters)]
fn deserialize_modules<Mode: ExecutionMode>(
context: &mut ExecutionContext<'_, '_, '_>,
module_bytes: &[Vec<u8>],
) -> Result<Vec<CompiledModule>, ExecutionError> {
let binary_config = to_binary_config(context.protocol_config);
let modules = module_bytes
.iter()
.map(|b| {
CompiledModule::deserialize_with_config(b, &binary_config)
.map_err(|e| e.finish(Location::Undefined))
})
.collect::<VMResult<Vec<CompiledModule>>>()
.map_err(|e| context.convert_vm_error(e))?;
assert_invariant!(
!modules.is_empty(),
"input checker ensures package is not empty"
);
Ok(modules)
}
fn publish_and_verify_modules(
context: &mut ExecutionContext<'_, '_, '_>,
package_id: ObjectID,
modules: &[CompiledModule],
) -> Result<(), ExecutionError> {
let new_module_bytes: Vec<_> = modules
.iter()
.map(|m| {
let mut bytes = Vec::new();
m.serialize_with_version(VERSION_6, &mut bytes).unwrap();
bytes
})
.collect();
context
.publish_module_bundle(new_module_bytes, AccountAddress::from(package_id))
.map_err(|e| context.convert_vm_error(e))?;
for module in modules {
sui_verifier::verifier::sui_verify_module_unmetered(
module,
&BTreeMap::new(),
&context
.protocol_config
.verifier_config(None),
)?;
}
Ok(())
}
fn init_modules<Mode: ExecutionMode>(
context: &mut ExecutionContext<'_, '_, '_>,
argument_updates: &mut Mode::ArgumentUpdates,
modules: &[CompiledModule],
) -> Result<(), ExecutionError> {
let modules_to_init = modules.iter().filter_map(|module| {
for fdef in &module.function_defs {
let fhandle = module.function_handle_at(fdef.function);
let fname = module.identifier_at(fhandle.name);
if fname == INIT_FN_NAME {
return Some(module.self_id());
}
}
None
});
for module_id in modules_to_init {
let return_values = execute_move_call::<Mode>(
context,
argument_updates,
&module_id,
INIT_FN_NAME,
vec![],
vec![],
true,
)?;
assert_invariant!(
return_values.is_empty(),
"init should not have return values"
)
}
Ok(())
}
#[derive(PartialEq, Eq, Clone, Copy)]
enum FunctionKind {
PrivateEntry,
PublicEntry,
NonEntry,
Init,
}
enum ValueKind {
Object {
type_: MoveObjectType,
has_public_transfer: bool,
},
Raw(Type, AbilitySet),
}
struct LoadedFunctionInfo {
kind: FunctionKind,
signature: LoadedFunctionInstantiation,
return_value_kinds: Vec<ValueKind>,
index: FunctionDefinitionIndex,
last_instr: CodeOffset,
}
fn check_visibility_and_signature<Mode: ExecutionMode>(
context: &mut ExecutionContext<'_, '_, '_>,
module_id: &ModuleId,
function: &IdentStr,
type_arguments: &[Type],
from_init: bool,
) -> Result<LoadedFunctionInfo, ExecutionError> {
if from_init {
let result = context.load_function(module_id, function, type_arguments);
assert_invariant!(
result.is_ok(),
"The modules init should be able to be loaded"
);
}
let no_new_packages = vec![];
let data_store = SuiDataStore::new(&context.linkage_view, &no_new_packages);
let module = context
.vm
.get_runtime()
.load_module(module_id, &data_store)
.map_err(|e| context.convert_vm_error(e))?;
let Some((index, fdef)) = module
.function_defs
.iter()
.enumerate()
.find(|(_index, fdef)| {
module.identifier_at(module.function_handle_at(fdef.function).name) == function
})
else {
return Err(ExecutionError::new_with_source(
ExecutionErrorKind::FunctionNotFound,
format!(
"Could not resolve function '{}' in module {}",
function, &module_id,
),
));
};
if !from_init && function == INIT_FN_NAME && context.protocol_config.ban_entry_init() {
return Err(ExecutionError::new_with_source(
ExecutionErrorKind::NonEntryFunctionInvoked,
"Cannot call 'init'",
));
}
let last_instr: CodeOffset = fdef
.code
.as_ref()
.map(|code| code.code.len() - 1)
.unwrap_or(0) as CodeOffset;
let function_kind = match (fdef.visibility, fdef.is_entry) {
(Visibility::Private | Visibility::Friend, true) => FunctionKind::PrivateEntry,
(Visibility::Public, true) => FunctionKind::PublicEntry,
(Visibility::Public, false) => FunctionKind::NonEntry,
(Visibility::Private, false) if from_init => {
assert_invariant!(
function == INIT_FN_NAME,
"module init specified non-init function"
);
FunctionKind::Init
}
(Visibility::Private | Visibility::Friend, false)
if Mode::allow_arbitrary_function_calls() =>
{
FunctionKind::NonEntry
}
(Visibility::Private | Visibility::Friend, false) => {
return Err(ExecutionError::new_with_source(
ExecutionErrorKind::NonEntryFunctionInvoked,
"Can only call `entry` or `public` functions",
));
}
};
let signature = context
.load_function(module_id, function, type_arguments)
.map_err(|e| context.convert_vm_error(e))?;
let signature =
subst_signature(signature, type_arguments).map_err(|e| context.convert_vm_error(e))?;
let return_value_kinds = match function_kind {
FunctionKind::Init => {
assert_invariant!(
signature.return_.is_empty(),
"init functions must have no return values"
);
vec![]
}
FunctionKind::PrivateEntry | FunctionKind::PublicEntry | FunctionKind::NonEntry => {
check_non_entry_signature::<Mode>(context, module_id, function, &signature)?
}
};
check_private_generics(context, module_id, function, type_arguments)?;
Ok(LoadedFunctionInfo {
kind: function_kind,
signature,
return_value_kinds,
index: FunctionDefinitionIndex(index as u16),
last_instr,
})
}
fn subst_signature(
signature: LoadedFunctionInstantiation,
type_arguments: &[Type],
) -> VMResult<LoadedFunctionInstantiation> {
let LoadedFunctionInstantiation {
parameters,
return_,
} = signature;
let parameters = parameters
.into_iter()
.map(|ty| ty.subst(type_arguments))
.collect::<PartialVMResult<Vec<_>>>()
.map_err(|err| err.finish(Location::Undefined))?;
let return_ = return_
.into_iter()
.map(|ty| ty.subst(type_arguments))
.collect::<PartialVMResult<Vec<_>>>()
.map_err(|err| err.finish(Location::Undefined))?;
Ok(LoadedFunctionInstantiation {
parameters,
return_,
})
}
fn check_non_entry_signature<Mode: ExecutionMode>(
context: &mut ExecutionContext<'_, '_, '_>,
_module_id: &ModuleId,
_function: &IdentStr,
signature: &LoadedFunctionInstantiation,
) -> Result<Vec<ValueKind>, ExecutionError> {
signature
.return_
.iter()
.enumerate()
.map(|(idx, return_type)| {
let return_type = match return_type {
Type::Reference(inner) | Type::MutableReference(inner)
if Mode::allow_arbitrary_values() =>
{
inner
}
Type::Reference(_) | Type::MutableReference(_) => {
return Err(ExecutionError::from_kind(
ExecutionErrorKind::InvalidPublicFunctionReturnType { idx: idx as u16 },
))
}
t => t,
};
let abilities = context
.vm
.get_runtime()
.get_type_abilities(return_type)
.map_err(|e| context.convert_vm_error(e))?;
Ok(match return_type {
Type::MutableReference(_) | Type::Reference(_) => unreachable!(),
Type::TyParam(_) => {
invariant_violation!("TyParam should have been substituted")
}
Type::Datatype(_) | Type::DatatypeInstantiation(_) if abilities.has_key() => {
let type_tag = context
.vm
.get_runtime()
.get_type_tag(return_type)
.map_err(|e| context.convert_vm_error(e))?;
let TypeTag::Struct(struct_tag) = type_tag else {
invariant_violation!("Struct type make a non struct type tag")
};
ValueKind::Object {
type_: MoveObjectType::from(*struct_tag),
has_public_transfer: abilities.has_store(),
}
}
Type::Datatype(_)
| Type::DatatypeInstantiation(_)
| Type::Bool
| Type::U8
| Type::U64
| Type::U128
| Type::Address
| Type::Signer
| Type::Vector(_)
| Type::U16
| Type::U32
| Type::U256 => ValueKind::Raw(return_type.clone(), abilities),
})
})
.collect()
}
fn check_private_generics(
_context: &mut ExecutionContext,
module_id: &ModuleId,
function: &IdentStr,
_type_arguments: &[Type],
) -> Result<(), ExecutionError> {
let module_ident = (module_id.address(), module_id.name());
if module_ident == (&SUI_FRAMEWORK_ADDRESS, EVENT_MODULE) {
return Err(ExecutionError::new_with_source(
ExecutionErrorKind::NonEntryFunctionInvoked,
format!("Cannot directly call functions in sui::{}", EVENT_MODULE),
));
}
if module_ident == (&SUI_FRAMEWORK_ADDRESS, TRANSFER_MODULE)
&& PRIVATE_TRANSFER_FUNCTIONS.contains(&function)
{
let msg = format!(
"Cannot directly call sui::{m}::{f}. \
Use the public variant instead, sui::{m}::public_{f}",
m = TRANSFER_MODULE,
f = function
);
return Err(ExecutionError::new_with_source(
ExecutionErrorKind::NonEntryFunctionInvoked,
msg,
));
}
Ok(())
}
type ArgInfo = (
TxContextKind,
Vec<(LocalIndex, ValueKind)>,
Vec<Vec<u8>>,
);
fn build_move_args<Mode: ExecutionMode>(
context: &mut ExecutionContext<'_, '_, '_>,
module_id: &ModuleId,
function: &IdentStr,
function_kind: FunctionKind,
signature: &LoadedFunctionInstantiation,
args: &[Argument],
) -> Result<ArgInfo, ExecutionError> {
let parameters = &signature.parameters;
let tx_ctx_kind = match parameters.last() {
Some(t) => is_tx_context(context, t)?,
None => TxContextKind::None,
};
let has_one_time_witness = function_kind == FunctionKind::Init && parameters.len() == 2;
let has_tx_context = tx_ctx_kind != TxContextKind::None;
let num_args = args.len() + (has_one_time_witness as usize) + (has_tx_context as usize);
if num_args != parameters.len() {
return Err(ExecutionError::new_with_source(
ExecutionErrorKind::ArityMismatch,
format!(
"Expected {:?} argument{} calling function '{}', but found {:?}",
parameters.len(),
if parameters.len() == 1 { "" } else { "s" },
function,
num_args
),
));
}
let mut by_mut_ref = vec![];
let mut serialized_args = Vec::with_capacity(num_args);
let command_kind = CommandKind::MoveCall {
package: (*module_id.address()).into(),
module: module_id.name(),
function,
};
if has_one_time_witness {
let bcs_true_value = bcs::to_bytes(&true).unwrap();
serialized_args.push(bcs_true_value)
}
for ((idx, arg), param_ty) in args.iter().copied().enumerate().zip(parameters) {
let (value, non_ref_param_ty): (Value, &Type) = match param_ty {
Type::MutableReference(inner) => {
let value = context.borrow_arg_mut(idx, arg)?;
let object_info = if let Value::Object(ObjectValue {
type_,
has_public_transfer,
..
}) = &value
{
let type_tag = context
.vm
.get_runtime()
.get_type_tag(type_)
.map_err(|e| context.convert_vm_error(e))?;
let TypeTag::Struct(struct_tag) = type_tag else {
invariant_violation!("Struct type make a non struct type tag")
};
let type_ = (*struct_tag).into();
ValueKind::Object {
type_,
has_public_transfer: *has_public_transfer,
}
} else {
let abilities = context
.vm
.get_runtime()
.get_type_abilities(inner)
.map_err(|e| context.convert_vm_error(e))?;
ValueKind::Raw((**inner).clone(), abilities)
};
by_mut_ref.push((idx as LocalIndex, object_info));
(value, inner)
}
Type::Reference(inner) => (context.borrow_arg(idx, arg, param_ty)?, inner),
t => {
let value = context.by_value_arg(command_kind, idx, arg)?;
(value, t)
}
};
if matches!(
function_kind,
FunctionKind::PrivateEntry | FunctionKind::Init
) && value.was_used_in_non_entry_move_call()
{
return Err(command_argument_error(
CommandArgumentError::InvalidArgumentToPrivateEntryFunction,
idx,
));
}
check_param_type::<Mode>(context, idx, &value, non_ref_param_ty)?;
let bytes = {
let mut v = vec![];
value.write_bcs_bytes(&mut v);
v
};
serialized_args.push(bytes);
}
Ok((tx_ctx_kind, by_mut_ref, serialized_args))
}
fn check_param_type<Mode: ExecutionMode>(
context: &mut ExecutionContext<'_, '_, '_>,
idx: usize,
value: &Value,
param_ty: &Type,
) -> Result<(), ExecutionError> {
match value {
Value::Raw(RawValueType::Any, _) if Mode::allow_arbitrary_values() => return Ok(()),
Value::Raw(RawValueType::Any, bytes) => {
let Some(layout) = primitive_serialization_layout(context, param_ty)? else {
let msg = format!(
"Non-primitive argument at index {}. If it is an object, it must be \
populated by an object",
idx,
);
return Err(ExecutionError::new_with_source(
ExecutionErrorKind::command_argument_error(
CommandArgumentError::InvalidUsageOfPureArg,
idx as u16,
),
msg,
));
};
bcs_argument_validate(bytes, idx as u16, layout)?;
return Ok(());
}
Value::Raw(RawValueType::Loaded { ty, abilities, .. }, _) => {
assert_invariant!(
Mode::allow_arbitrary_values() || !abilities.has_key(),
"Raw value should never be an object"
);
if ty != param_ty {
return Err(command_argument_error(
CommandArgumentError::TypeMismatch,
idx,
));
}
}
Value::Object(obj) => {
let ty = &obj.type_;
if ty != param_ty {
return Err(command_argument_error(
CommandArgumentError::TypeMismatch,
idx,
));
}
}
Value::Receiving(_, _, assigned_type) => {
if let Some(assigned_type) = assigned_type {
if assigned_type != param_ty {
return Err(command_argument_error(
CommandArgumentError::TypeMismatch,
idx,
));
}
}
let Type::DatatypeInstantiation(struct_inst) = param_ty else {
return Err(command_argument_error(
CommandArgumentError::TypeMismatch,
idx,
));
};
let (sidx, targs) = &**struct_inst;
let Some(s) = context.vm.get_runtime().get_struct_type(*sidx) else {
invariant_violation!("sui::transfer::Receiving struct not found in session")
};
let resolved_struct = get_datatype_ident(&s);
if resolved_struct != RESOLVED_RECEIVING_STRUCT || targs.len() != 1 {
return Err(command_argument_error(
CommandArgumentError::TypeMismatch,
idx,
));
}
}
}
Ok(())
}
fn get_datatype_ident(s: &CachedDatatype) -> (&AccountAddress, &IdentStr, &IdentStr) {
let module_id = &s.defining_id;
let struct_name = &s.name;
(
module_id.address(),
module_id.name(),
struct_name.as_ident_str(),
)
}
pub fn is_tx_context(
context: &mut ExecutionContext<'_, '_, '_>,
t: &Type,
) -> Result<TxContextKind, ExecutionError> {
let (is_mut, inner) = match t {
Type::MutableReference(inner) => (true, inner),
Type::Reference(inner) => (false, inner),
_ => return Ok(TxContextKind::None),
};
let Type::Datatype(idx) = &**inner else {
return Ok(TxContextKind::None);
};
let Some(s) = context.vm.get_runtime().get_struct_type(*idx) else {
invariant_violation!("Loaded struct not found")
};
let (module_addr, module_name, struct_name) = get_datatype_ident(&s);
let is_tx_context_type = module_addr == &SUI_FRAMEWORK_ADDRESS
&& module_name == TX_CONTEXT_MODULE_NAME
&& struct_name == TX_CONTEXT_STRUCT_NAME;
Ok(if is_tx_context_type {
if is_mut {
TxContextKind::Mutable
} else {
TxContextKind::Immutable
}
} else {
TxContextKind::None
})
}
fn primitive_serialization_layout(
context: &mut ExecutionContext<'_, '_, '_>,
param_ty: &Type,
) -> Result<Option<PrimitiveArgumentLayout>, ExecutionError> {
Ok(match param_ty {
Type::Signer => return Ok(None),
Type::Reference(_) | Type::MutableReference(_) | Type::TyParam(_) => {
invariant_violation!("references and type parameters should be checked elsewhere")
}
Type::Bool => Some(PrimitiveArgumentLayout::Bool),
Type::U8 => Some(PrimitiveArgumentLayout::U8),
Type::U16 => Some(PrimitiveArgumentLayout::U16),
Type::U32 => Some(PrimitiveArgumentLayout::U32),
Type::U64 => Some(PrimitiveArgumentLayout::U64),
Type::U128 => Some(PrimitiveArgumentLayout::U128),
Type::U256 => Some(PrimitiveArgumentLayout::U256),
Type::Address => Some(PrimitiveArgumentLayout::Address),
Type::Vector(inner) => {
let info_opt = primitive_serialization_layout(context, inner)?;
info_opt.map(|layout| PrimitiveArgumentLayout::Vector(Box::new(layout)))
}
Type::DatatypeInstantiation(struct_inst) => {
let (idx, targs) = &**struct_inst;
let Some(s) = context.vm.get_runtime().get_struct_type(*idx) else {
invariant_violation!("Loaded struct not found")
};
let resolved_struct = get_datatype_ident(&s);
if resolved_struct == RESOLVED_STD_OPTION && targs.len() == 1 {
let info_opt = primitive_serialization_layout(context, &targs[0])?;
info_opt.map(|layout| PrimitiveArgumentLayout::Option(Box::new(layout)))
} else {
None
}
}
Type::Datatype(idx) => {
let Some(s) = context.vm.get_runtime().get_struct_type(*idx) else {
invariant_violation!("Loaded struct not found")
};
let resolved_struct = get_datatype_ident(&s);
if resolved_struct == RESOLVED_SUI_ID {
Some(PrimitiveArgumentLayout::Address)
} else if resolved_struct == RESOLVED_ASCII_STR {
Some(PrimitiveArgumentLayout::Ascii)
} else if resolved_struct == RESOLVED_UTF8_STR {
Some(PrimitiveArgumentLayout::UTF8)
} else {
None
}
}
})
}
#[derive(Debug)]
pub enum PrimitiveArgumentLayout {
Option(Box<PrimitiveArgumentLayout>),
Vector(Box<PrimitiveArgumentLayout>),
Ascii,
UTF8,
Bool,
U8,
U16,
U32,
U64,
U128,
U256,
Address,
}
impl PrimitiveArgumentLayout {
pub fn bcs_only(&self) -> bool {
match self {
PrimitiveArgumentLayout::Option(_)
| PrimitiveArgumentLayout::Ascii
| PrimitiveArgumentLayout::UTF8 => false,
PrimitiveArgumentLayout::Bool
| PrimitiveArgumentLayout::U8
| PrimitiveArgumentLayout::U16
| PrimitiveArgumentLayout::U32
| PrimitiveArgumentLayout::U64
| PrimitiveArgumentLayout::U128
| PrimitiveArgumentLayout::U256
| PrimitiveArgumentLayout::Address => true,
PrimitiveArgumentLayout::Vector(inner) => inner.bcs_only(),
}
}
}
pub fn bcs_argument_validate(
bytes: &[u8],
idx: u16,
layout: PrimitiveArgumentLayout,
) -> Result<(), ExecutionError> {
bcs::from_bytes_seed(&layout, bytes).map_err(|_| {
ExecutionError::new_with_source(
ExecutionErrorKind::command_argument_error(
CommandArgumentError::InvalidBCSBytes,
idx,
),
format!("Function expects {layout} but provided argument's value does not match",),
)
})
}
impl<'d> serde::de::DeserializeSeed<'d> for &PrimitiveArgumentLayout {
type Value = ();
fn deserialize<D: serde::de::Deserializer<'d>>(
self,
deserializer: D,
) -> Result<Self::Value, D::Error> {
use serde::de::Error;
match self {
PrimitiveArgumentLayout::Ascii => {
let s: &str = serde::Deserialize::deserialize(deserializer)?;
if !s.is_ascii() {
Err(D::Error::custom("not an ascii string"))
} else {
Ok(())
}
}
PrimitiveArgumentLayout::UTF8 => {
deserializer.deserialize_string(serde::de::IgnoredAny)?;
Ok(())
}
PrimitiveArgumentLayout::Option(layout) => {
deserializer.deserialize_option(OptionElementVisitor(layout))
}
PrimitiveArgumentLayout::Vector(layout) => {
deserializer.deserialize_seq(VectorElementVisitor(layout))
}
PrimitiveArgumentLayout::Bool => {
deserializer.deserialize_bool(serde::de::IgnoredAny)?;
Ok(())
}
PrimitiveArgumentLayout::U8 => {
deserializer.deserialize_u8(serde::de::IgnoredAny)?;
Ok(())
}
PrimitiveArgumentLayout::U16 => {
deserializer.deserialize_u16(serde::de::IgnoredAny)?;
Ok(())
}
PrimitiveArgumentLayout::U32 => {
deserializer.deserialize_u32(serde::de::IgnoredAny)?;
Ok(())
}
PrimitiveArgumentLayout::U64 => {
deserializer.deserialize_u64(serde::de::IgnoredAny)?;
Ok(())
}
PrimitiveArgumentLayout::U128 => {
deserializer.deserialize_u128(serde::de::IgnoredAny)?;
Ok(())
}
PrimitiveArgumentLayout::U256 => {
U256::deserialize(deserializer)?;
Ok(())
}
PrimitiveArgumentLayout::Address => {
SuiAddress::deserialize(deserializer)?;
Ok(())
}
}
}
}
struct VectorElementVisitor<'a>(&'a PrimitiveArgumentLayout);
impl<'d> serde::de::Visitor<'d> for VectorElementVisitor<'_> {
type Value = ();
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("Vector")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'d>,
{
while seq.next_element_seed(self.0)?.is_some() {}
Ok(())
}
}
struct OptionElementVisitor<'a>(&'a PrimitiveArgumentLayout);
impl<'d> serde::de::Visitor<'d> for OptionElementVisitor<'_> {
type Value = ();
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("Option")
}
fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(())
}
fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: serde::Deserializer<'d>,
{
self.0.deserialize(deserializer)
}
}
impl fmt::Display for PrimitiveArgumentLayout {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PrimitiveArgumentLayout::Vector(inner) => {
write!(f, "vector<{inner}>")
}
PrimitiveArgumentLayout::Option(inner) => {
write!(f, "std::option::Option<{inner}>")
}
PrimitiveArgumentLayout::Ascii => {
write!(f, "std::{}::{}", RESOLVED_ASCII_STR.1, RESOLVED_ASCII_STR.2)
}
PrimitiveArgumentLayout::UTF8 => {
write!(f, "std::{}::{}", RESOLVED_UTF8_STR.1, RESOLVED_UTF8_STR.2)
}
PrimitiveArgumentLayout::Bool => write!(f, "bool"),
PrimitiveArgumentLayout::U8 => write!(f, "u8"),
PrimitiveArgumentLayout::U16 => write!(f, "u16"),
PrimitiveArgumentLayout::U32 => write!(f, "u32"),
PrimitiveArgumentLayout::U64 => write!(f, "u64"),
PrimitiveArgumentLayout::U128 => write!(f, "u128"),
PrimitiveArgumentLayout::U256 => write!(f, "u256"),
PrimitiveArgumentLayout::Address => write!(f, "address"),
}
}
}
}