1use move_binary_format::{
5 file_format::{AbilitySet, Bytecode, FunctionDefinition, SignatureToken, Visibility},
6 CompiledModule,
7};
8use move_bytecode_utils::format_signature_token;
9use sui_protocol_config::ProtocolConfig;
10use sui_types::{
11 base_types::{TxContext, TxContextKind, TX_CONTEXT_MODULE_NAME, TX_CONTEXT_STRUCT_NAME},
12 clock::Clock,
13 error::ExecutionError,
14 is_object, is_object_vector, is_primitive,
15 move_package::{is_test_fun, FnInfoMap},
16 SUI_FRAMEWORK_ADDRESS,
17};
18
19use crate::{verification_failure, INIT_FN_NAME};
20
21pub fn verify_module(
38 config: &ProtocolConfig,
39 module: &CompiledModule,
40 fn_info_map: &FnInfoMap,
41) -> Result<(), ExecutionError> {
42 for func_def in &module.function_defs {
46 let handle = module.function_handle_at(func_def.function);
47 let name = module.identifier_at(handle.name);
48
49 if !is_test_fun(name, module, fn_info_map) {
51 verify_init_not_called(module, func_def).map_err(verification_failure)?;
52 }
53
54 if name == INIT_FN_NAME {
55 verify_init_function(config, module, func_def).map_err(verification_failure)?;
56 continue;
57 }
58
59 if !func_def.is_entry {
62 continue;
64 }
65 verify_entry_function_impl(module, func_def).map_err(verification_failure)?;
66 }
67 Ok(())
68}
69
70fn verify_init_not_called(
71 module: &CompiledModule,
72 fdef: &FunctionDefinition,
73) -> Result<(), String> {
74 let code = match &fdef.code {
75 None => return Ok(()),
76 Some(code) => code,
77 };
78 code.code
79 .iter()
80 .enumerate()
81 .filter_map(|(idx, instr)| match instr {
82 Bytecode::Call(fhandle_idx) => Some((idx, module.function_handle_at(*fhandle_idx))),
83 Bytecode::CallGeneric(finst_idx) => {
84 let finst = module.function_instantiation_at(*finst_idx);
85 Some((idx, module.function_handle_at(finst.handle)))
86 }
87 _ => None,
88 })
89 .try_for_each(|(idx, fhandle)| {
90 let name = module.identifier_at(fhandle.name);
91 if name == INIT_FN_NAME {
92 Err(format!(
93 "{}::{} at offset {}. Cannot call a module's '{}' function from another Move function",
94 module.self_id(),
95 name,
96 idx,
97 INIT_FN_NAME
98 ))
99 } else {
100 Ok(())
101 }
102 })
103}
104
105fn verify_init_function(
107 config: &ProtocolConfig,
108 module: &CompiledModule,
109 fdef: &FunctionDefinition,
110) -> Result<(), String> {
111 if fdef.visibility != Visibility::Private {
112 return Err(format!(
113 "{}. '{}' function must be private",
114 module.self_id(),
115 INIT_FN_NAME
116 ));
117 }
118
119 if config.ban_entry_init() && fdef.is_entry {
120 return Err(format!(
121 "{}. '{}' cannot be 'entry'",
122 module.self_id(),
123 INIT_FN_NAME
124 ));
125 }
126
127 let fhandle = module.function_handle_at(fdef.function);
128 if !fhandle.type_parameters.is_empty() {
129 return Err(format!(
130 "{}. '{}' function cannot have type parameters",
131 module.self_id(),
132 INIT_FN_NAME
133 ));
134 }
135
136 if !module.signature_at(fhandle.return_).is_empty() {
137 return Err(format!(
138 "{}, '{}' function cannot have return values",
139 module.self_id(),
140 INIT_FN_NAME
141 ));
142 }
143
144 let parameters = &module.signature_at(fhandle.parameters).0;
145 if parameters.is_empty() || parameters.len() > 2 {
146 return Err(format!(
147 "Expected at least one and at most two parameters for {}::{}",
148 module.self_id(),
149 INIT_FN_NAME,
150 ));
151 }
152
153 if TxContext::kind(module, ¶meters[parameters.len() - 1]) != TxContextKind::None {
158 Ok(())
159 } else {
160 Err(format!(
161 "Expected last parameter for {0}::{1} to be &mut {2}::{3}::{4} or &{2}::{3}::{4}, \
162 but found {5}",
163 module.self_id(),
164 INIT_FN_NAME,
165 SUI_FRAMEWORK_ADDRESS,
166 TX_CONTEXT_MODULE_NAME,
167 TX_CONTEXT_STRUCT_NAME,
168 format_signature_token(module, ¶meters[0]),
169 ))
170 }
171}
172
173fn verify_entry_function_impl(
174 module: &CompiledModule,
175 func_def: &FunctionDefinition,
176) -> Result<(), String> {
177 let handle = module.function_handle_at(func_def.function);
178 let params = module.signature_at(handle.parameters);
179
180 let all_non_ctx_params = match params.0.last() {
181 Some(last_param) if TxContext::kind(module, last_param) != TxContextKind::None => {
182 ¶ms.0[0..params.0.len() - 1]
183 }
184 _ => ¶ms.0,
185 };
186 for param in all_non_ctx_params {
187 verify_param_type(module, &handle.type_parameters, param)?;
188 }
189
190 for return_ty in &module.signature_at(handle.return_).0 {
191 verify_return_type(module, &handle.type_parameters, return_ty)?;
192 }
193
194 Ok(())
195}
196
197fn verify_return_type(
198 view: &CompiledModule,
199 type_parameters: &[AbilitySet],
200 return_ty: &SignatureToken,
201) -> Result<(), String> {
202 if matches!(
203 return_ty,
204 SignatureToken::Reference(_) | SignatureToken::MutableReference(_)
205 ) {
206 return Err("Invalid entry point return type. Expected a non reference type.".to_owned());
207 }
208 let abilities = view
209 .abilities(return_ty, type_parameters)
210 .map_err(|e| format!("Unexpected CompiledModule error: {}", e))?;
211 if abilities.has_drop() {
212 Ok(())
213 } else {
214 Err(format!(
215 "Invalid entry point return type. \
216 The specified return type does not have the 'drop' ability: {}",
217 format_signature_token(view, return_ty),
218 ))
219 }
220}
221
222fn verify_param_type(
223 view: &CompiledModule,
224 function_type_args: &[AbilitySet],
225 param: &SignatureToken,
226) -> Result<(), String> {
227 if Clock::is_mutable(view, param) {
230 return Err(format!(
231 "Invalid entry point parameter type. Clock must be passed by immutable reference. got: \
232 {}",
233 format_signature_token(view, param),
234 ));
235 }
236
237 if is_primitive(view, function_type_args, param)
238 || is_object(view, function_type_args, param)?
239 || is_object_vector(view, function_type_args, param)?
240 {
241 Ok(())
242 } else {
243 Err(format!(
244 "Invalid entry point parameter type. Expected primitive or object type. Got: {}",
245 format_signature_token(view, param)
246 ))
247 }
248}