1use std::borrow::Cow;
5
6use move_binary_format::{
7 CompiledModule,
8 file_format::{Bytecode, FunctionDefinition, FunctionHandle, SignatureToken, Visibility},
9};
10use move_bytecode_utils::format_signature_token;
11use move_core_types::{account_address::AccountAddress, ident_str, identifier::IdentStr};
12use move_vm_config::verifier::VerifierConfig;
13use mysten_common::ZipDebugEqIteratorExt;
14use sui_types::{
15 MOVE_STDLIB_ADDRESS, SUI_FRAMEWORK_ADDRESS, error::ExecutionError, make_invariant_violation,
16};
17
18use crate::{FunctionIdent, TEST_SCENARIO_MODULE_NAME, verification_failure};
19
20pub const INTERNAL_MODULE: &IdentStr = ident_str!("internal");
21pub const TRANSFER_MODULE: &IdentStr = ident_str!("transfer");
22pub const EVENT_MODULE: &IdentStr = ident_str!("event");
23pub const COIN_REGISTRY_MODULE: &IdentStr = ident_str!("coin_registry");
24
25pub const MOVE_STDLIB_INTERNAL_PERMIT: FunctionIdent =
27 (MOVE_STDLIB_ADDRESS, INTERNAL_MODULE, ident_str!("permit"));
28
29pub const SUI_EVENT_EMIT_EVENT: FunctionIdent =
31 (SUI_FRAMEWORK_ADDRESS, EVENT_MODULE, ident_str!("emit"));
32pub const SUI_EVENT_EMIT_AUTHENTICATED: FunctionIdent = (
33 SUI_FRAMEWORK_ADDRESS,
34 EVENT_MODULE,
35 ident_str!("emit_authenticated"),
36);
37pub const SUI_EVENT_NUM_EVENTS: FunctionIdent = (
38 SUI_FRAMEWORK_ADDRESS,
39 EVENT_MODULE,
40 ident_str!("num_events"),
41);
42pub const SUI_EVENT_EVENTS_BY_TYPE: FunctionIdent = (
43 SUI_FRAMEWORK_ADDRESS,
44 EVENT_MODULE,
45 ident_str!("events_by_type"),
46);
47
48pub const SUI_TRANSFER_PUBLIC_TRANSFER: FunctionIdent = (
50 SUI_FRAMEWORK_ADDRESS,
51 TRANSFER_MODULE,
52 ident_str!("public_transfer"),
53);
54pub const SUI_TRANSFER_PUBLIC_FREEZE_OBJECT: FunctionIdent = (
55 SUI_FRAMEWORK_ADDRESS,
56 TRANSFER_MODULE,
57 ident_str!("public_freeze_object"),
58);
59pub const SUI_TRANSFER_PUBLIC_SHARE_OBJECT: FunctionIdent = (
60 SUI_FRAMEWORK_ADDRESS,
61 TRANSFER_MODULE,
62 ident_str!("public_share_object"),
63);
64pub const SUI_TRANSFER_PUBLIC_RECEIVE: FunctionIdent = (
65 SUI_FRAMEWORK_ADDRESS,
66 TRANSFER_MODULE,
67 ident_str!("public_receive"),
68);
69pub const SUI_TRANSFER_RECEIVING_OBJECT_ID: FunctionIdent = (
70 SUI_FRAMEWORK_ADDRESS,
71 TRANSFER_MODULE,
72 ident_str!("receiving_object_id"),
73);
74pub const SUI_TRANSFER_PUBLIC_PARTY_TRANSFER: FunctionIdent = (
75 SUI_FRAMEWORK_ADDRESS,
76 TRANSFER_MODULE,
77 ident_str!("public_party_transfer"),
78);
79
80pub const SUI_TRANSFER_TRANSFER: FunctionIdent = (
82 SUI_FRAMEWORK_ADDRESS,
83 TRANSFER_MODULE,
84 ident_str!("transfer"),
85);
86pub const SUI_TRANSFER_FREEZE_OBJECT: FunctionIdent = (
87 SUI_FRAMEWORK_ADDRESS,
88 TRANSFER_MODULE,
89 ident_str!("freeze_object"),
90);
91pub const SUI_TRANSFER_SHARE_OBJECT: FunctionIdent = (
92 SUI_FRAMEWORK_ADDRESS,
93 TRANSFER_MODULE,
94 ident_str!("share_object"),
95);
96pub const SUI_TRANSFER_RECEIVE: FunctionIdent = (
97 SUI_FRAMEWORK_ADDRESS,
98 TRANSFER_MODULE,
99 ident_str!("receive"),
100);
101pub const SUI_TRANSFER_PARTY_TRANSFER: FunctionIdent = (
102 SUI_FRAMEWORK_ADDRESS,
103 TRANSFER_MODULE,
104 ident_str!("party_transfer"),
105);
106
107pub const SUI_COIN_REGISTRY_NEW_CURRENCY: FunctionIdent = (
109 SUI_FRAMEWORK_ADDRESS,
110 COIN_REGISTRY_MODULE,
111 ident_str!("new_currency"),
112);
113
114pub const EXHAUSTIVE_MODULES: &[(AccountAddress, &IdentStr)] = &[
116 (SUI_FRAMEWORK_ADDRESS, EVENT_MODULE),
117 (SUI_FRAMEWORK_ADDRESS, TRANSFER_MODULE),
118];
119
120pub const FUNCTIONS_TO_CHECK: &[(FunctionIdent, &[bool])] = &[
123 (MOVE_STDLIB_INTERNAL_PERMIT, &[true]),
125 (SUI_EVENT_EMIT_EVENT, &[true]),
127 (SUI_EVENT_EMIT_AUTHENTICATED, &[true]),
128 (SUI_EVENT_NUM_EVENTS, &[]),
129 (SUI_EVENT_EVENTS_BY_TYPE, &[false]),
130 (SUI_TRANSFER_PUBLIC_TRANSFER, &[false]),
132 (SUI_TRANSFER_PUBLIC_FREEZE_OBJECT, &[false]),
133 (SUI_TRANSFER_PUBLIC_SHARE_OBJECT, &[false]),
134 (SUI_TRANSFER_PUBLIC_RECEIVE, &[false]),
135 (SUI_TRANSFER_RECEIVING_OBJECT_ID, &[false]),
136 (SUI_TRANSFER_PUBLIC_PARTY_TRANSFER, &[false]),
137 (SUI_TRANSFER_TRANSFER, &[true]),
139 (SUI_TRANSFER_FREEZE_OBJECT, &[true]),
140 (SUI_TRANSFER_SHARE_OBJECT, &[true]),
141 (SUI_TRANSFER_RECEIVE, &[true]),
142 (SUI_TRANSFER_PARTY_TRANSFER, &[true]),
143 (SUI_COIN_REGISTRY_NEW_CURRENCY, &[true]),
145];
146
147enum Error {
148 User(String),
149 InvariantViolation(String),
150}
151
152pub fn verify_module(
160 module: &CompiledModule,
161 _verifier_config: &VerifierConfig,
162) -> Result<(), ExecutionError> {
163 let module_id = module.self_id();
164 let module_address = *module_id.address();
165 let module_name = module_id.name();
166
167 if module_address == SUI_FRAMEWORK_ADDRESS && module_name.as_str() == TEST_SCENARIO_MODULE_NAME
169 {
170 return Ok(());
173 };
174
175 if EXHAUSTIVE_MODULES.contains(&(module_address, module_name)) {
177 for fdef in module
178 .function_defs
179 .iter()
180 .filter(|fdef| fdef.visibility == Visibility::Public)
181 {
182 let function_name = module.identifier_at(module.function_handle_at(fdef.function).name);
183 let resolved = &(module_address, module_name, function_name);
184 let rules_opt = FUNCTIONS_TO_CHECK.iter().find(|(f, _)| f == resolved);
185 if rules_opt.is_none() {
186 return Err(make_invariant_violation!(
188 "Unknown function '{module_id}::{function_name}'. \
189 All functions in '{module_id}' must be listed in FUNCTIONS_TO_CHECK",
190 ));
191 }
192 }
193 }
194
195 for func_def in &module.function_defs {
197 verify_function(module, func_def).map_err(|error| match error {
198 Error::User(error) => verification_failure(format!(
199 "{}::{}. {}",
200 module.self_id(),
201 module.identifier_at(module.function_handle_at(func_def.function).name),
202 error
203 )),
204 Error::InvariantViolation(error) => {
205 make_invariant_violation!(
206 "{}::{}. {}",
207 module.self_id(),
208 module.identifier_at(module.function_handle_at(func_def.function).name),
209 error
210 )
211 }
212 })?;
213 }
214 Ok(())
215}
216
217fn verify_function(module: &CompiledModule, fdef: &FunctionDefinition) -> Result<(), Error> {
218 let code = match &fdef.code {
219 None => return Ok(()),
220 Some(code) => code,
221 };
222 for instr in &code.code {
223 let (callee, ty_args): (FunctionIdent<'_>, &[SignatureToken]) = match instr {
224 Bytecode::Call(fhandle_idx) => {
225 let fhandle = module.function_handle_at(*fhandle_idx);
226 (resolve_function(module, fhandle), &[])
227 }
228 Bytecode::CallGeneric(finst_idx) => {
229 let finst = module.function_instantiation_at(*finst_idx);
230 let fhandle = module.function_handle_at(finst.handle);
231 let type_arguments = &module.signature_at(finst.type_parameters).0;
232 (resolve_function(module, fhandle), type_arguments)
233 }
234 _ => continue,
235 };
236 verify_call(module, callee, ty_args)?;
237 }
238 Ok(())
239}
240
241fn verify_call(
242 module: &CompiledModule,
243 callee @ (callee_addr, callee_module, callee_function): FunctionIdent<'_>,
244 ty_args: &[SignatureToken],
245) -> Result<(), Error> {
246 let Some((_, internal_flags)) = FUNCTIONS_TO_CHECK.iter().find(|(f, _)| &callee == f) else {
247 return Ok(());
248 };
249 let internal_flags = *internal_flags;
250 if ty_args.len() != internal_flags.len() {
251 return Err(Error::InvariantViolation(format!(
253 "'{callee_addr}::{callee_module}::{callee_function}' \
254 expects {} type arguments found {}",
255 internal_flags.len(),
256 ty_args.len()
257 )));
258 }
259 for (idx, (ty_arg, &is_internal)) in ty_args.iter().zip_debug_eq(internal_flags).enumerate() {
260 if !is_internal {
261 continue;
262 }
263 if !is_defined_in_current_module(module, ty_arg) {
264 let callee_package_name = callee_package_name(&callee_addr);
265 let help = help_message(&callee_addr, callee_module, callee_function);
266 return Err(Error::User(format!(
267 "Invalid call to '{callee_package_name}::{callee_module}::{callee_function}'. \
268 Type argument #{idx} must be a type defined in the current module, found '{}'.\
269 {help}",
270 format_signature_token(module, ty_arg),
271 )));
272 }
273 }
274
275 Ok(())
276}
277
278fn resolve_function<'a>(
279 module: &'a CompiledModule,
280 callee_handle: &FunctionHandle,
281) -> FunctionIdent<'a> {
282 let mh = module.module_handle_at(callee_handle.module);
283 let a = *module.address_identifier_at(mh.address);
284 let m = module.identifier_at(mh.name);
285 let f = module.identifier_at(callee_handle.name);
286 (a, m, f)
287}
288
289fn is_defined_in_current_module(module: &CompiledModule, type_arg: &SignatureToken) -> bool {
290 match type_arg {
291 SignatureToken::Datatype(_) | SignatureToken::DatatypeInstantiation(_) => {
292 let idx = match type_arg {
293 SignatureToken::Datatype(idx) => *idx,
294 SignatureToken::DatatypeInstantiation(s) => s.0,
295 _ => unreachable!(),
296 };
297 let shandle = module.datatype_handle_at(idx);
298 module.self_handle_idx() == shandle.module
299 }
300 SignatureToken::TypeParameter(_)
301 | SignatureToken::Bool
302 | SignatureToken::U8
303 | SignatureToken::U16
304 | SignatureToken::U32
305 | SignatureToken::U64
306 | SignatureToken::U128
307 | SignatureToken::U256
308 | SignatureToken::Address
309 | SignatureToken::Vector(_)
310 | SignatureToken::Signer
311 | SignatureToken::Reference(_)
312 | SignatureToken::MutableReference(_) => false,
313 }
314}
315
316pub fn callee_package_name(callee_addr: &AccountAddress) -> Cow<'static, str> {
317 match *callee_addr {
318 SUI_FRAMEWORK_ADDRESS => Cow::Borrowed("sui"),
319 MOVE_STDLIB_ADDRESS => Cow::Borrowed("std"),
320 a => {
321 debug_assert!(
322 false,
323 "unknown package in private generics verifier. \
324 Please improve this error message"
325 );
326 Cow::Owned(format!("{a}"))
327 }
328 }
329}
330
331pub fn help_message(
332 callee_addr: &AccountAddress,
333 callee_module: &IdentStr,
334 callee_function: &IdentStr,
335) -> String {
336 if *callee_addr == SUI_FRAMEWORK_ADDRESS && callee_module == TRANSFER_MODULE {
337 format!(
338 " If the type has the 'store' ability, use the public variant instead: 'sui::transfer::public_{}'.",
339 callee_function
340 )
341 } else {
342 String::new()
343 }
344}