use move_binary_format::{
file_format::{AbilitySet, Bytecode, FunctionDefinition, SignatureToken, Visibility},
CompiledModule,
};
use move_bytecode_utils::format_signature_token;
use sui_protocol_config::ProtocolConfig;
use sui_types::{
base_types::{TxContext, TxContextKind, TX_CONTEXT_MODULE_NAME, TX_CONTEXT_STRUCT_NAME},
clock::Clock,
error::ExecutionError,
is_object, is_object_vector, is_primitive,
move_package::{is_test_fun, FnInfoMap},
SUI_FRAMEWORK_ADDRESS,
};
use crate::{verification_failure, INIT_FN_NAME};
pub fn verify_module(
config: &ProtocolConfig,
module: &CompiledModule,
fn_info_map: &FnInfoMap,
) -> Result<(), ExecutionError> {
for func_def in &module.function_defs {
let handle = module.function_handle_at(func_def.function);
let name = module.identifier_at(handle.name);
if !is_test_fun(name, module, fn_info_map) {
verify_init_not_called(module, func_def).map_err(verification_failure)?;
}
if name == INIT_FN_NAME {
verify_init_function(config, module, func_def).map_err(verification_failure)?;
continue;
}
if !func_def.is_entry {
continue;
}
verify_entry_function_impl(module, func_def).map_err(verification_failure)?;
}
Ok(())
}
fn verify_init_not_called(
module: &CompiledModule,
fdef: &FunctionDefinition,
) -> Result<(), String> {
let code = match &fdef.code {
None => return Ok(()),
Some(code) => code,
};
code.code
.iter()
.enumerate()
.filter_map(|(idx, instr)| match instr {
Bytecode::Call(fhandle_idx) => Some((idx, module.function_handle_at(*fhandle_idx))),
Bytecode::CallGeneric(finst_idx) => {
let finst = module.function_instantiation_at(*finst_idx);
Some((idx, module.function_handle_at(finst.handle)))
}
_ => None,
})
.try_for_each(|(idx, fhandle)| {
let name = module.identifier_at(fhandle.name);
if name == INIT_FN_NAME {
Err(format!(
"{}::{} at offset {}. Cannot call a module's '{}' function from another Move function",
module.self_id(),
name,
idx,
INIT_FN_NAME
))
} else {
Ok(())
}
})
}
fn verify_init_function(
config: &ProtocolConfig,
module: &CompiledModule,
fdef: &FunctionDefinition,
) -> Result<(), String> {
if fdef.visibility != Visibility::Private {
return Err(format!(
"{}. '{}' function must be private",
module.self_id(),
INIT_FN_NAME
));
}
if config.ban_entry_init() && fdef.is_entry {
return Err(format!(
"{}. '{}' cannot be 'entry'",
module.self_id(),
INIT_FN_NAME
));
}
let fhandle = module.function_handle_at(fdef.function);
if !fhandle.type_parameters.is_empty() {
return Err(format!(
"{}. '{}' function cannot have type parameters",
module.self_id(),
INIT_FN_NAME
));
}
if !module.signature_at(fhandle.return_).is_empty() {
return Err(format!(
"{}, '{}' function cannot have return values",
module.self_id(),
INIT_FN_NAME
));
}
let parameters = &module.signature_at(fhandle.parameters).0;
if parameters.is_empty() || parameters.len() > 2 {
return Err(format!(
"Expected at least one and at most two parameters for {}::{}",
module.self_id(),
INIT_FN_NAME,
));
}
if TxContext::kind(module, ¶meters[parameters.len() - 1]) != TxContextKind::None {
Ok(())
} else {
Err(format!(
"Expected last parameter for {0}::{1} to be &mut {2}::{3}::{4} or &{2}::{3}::{4}, \
but found {5}",
module.self_id(),
INIT_FN_NAME,
SUI_FRAMEWORK_ADDRESS,
TX_CONTEXT_MODULE_NAME,
TX_CONTEXT_STRUCT_NAME,
format_signature_token(module, ¶meters[0]),
))
}
}
fn verify_entry_function_impl(
module: &CompiledModule,
func_def: &FunctionDefinition,
) -> Result<(), String> {
let handle = module.function_handle_at(func_def.function);
let params = module.signature_at(handle.parameters);
let all_non_ctx_params = match params.0.last() {
Some(last_param) if TxContext::kind(module, last_param) != TxContextKind::None => {
¶ms.0[0..params.0.len() - 1]
}
_ => ¶ms.0,
};
for param in all_non_ctx_params {
verify_param_type(module, &handle.type_parameters, param)?;
}
for return_ty in &module.signature_at(handle.return_).0 {
verify_return_type(module, &handle.type_parameters, return_ty)?;
}
Ok(())
}
fn verify_return_type(
view: &CompiledModule,
type_parameters: &[AbilitySet],
return_ty: &SignatureToken,
) -> Result<(), String> {
if matches!(
return_ty,
SignatureToken::Reference(_) | SignatureToken::MutableReference(_)
) {
return Err("Invalid entry point return type. Expected a non reference type.".to_owned());
}
let abilities = view
.abilities(return_ty, type_parameters)
.map_err(|e| format!("Unexpected CompiledModule error: {}", e))?;
if abilities.has_drop() {
Ok(())
} else {
Err(format!(
"Invalid entry point return type. \
The specified return type does not have the 'drop' ability: {}",
format_signature_token(view, return_ty),
))
}
}
fn verify_param_type(
view: &CompiledModule,
function_type_args: &[AbilitySet],
param: &SignatureToken,
) -> Result<(), String> {
if Clock::is_mutable(view, param) {
return Err(format!(
"Invalid entry point parameter type. Clock must be passed by immutable reference. got: \
{}",
format_signature_token(view, param),
));
}
if is_primitive(view, function_type_args, param)
|| is_object(view, function_type_args, param)?
|| is_object_vector(view, function_type_args, param)?
{
Ok(())
} else {
Err(format!(
"Invalid entry point parameter type. Expected primitive or object type. Got: {}",
format_signature_token(view, param)
))
}
}