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