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