sui_verifier_latest/
private_generics_verifier_v2.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use 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
24// Stdlib functions
25pub const MOVE_STDLIB_INTERNAL_PERMIT: FunctionIdent =
26    (MOVE_STDLIB_ADDRESS, INTERNAL_MODULE, ident_str!("permit"));
27
28// Event function
29pub 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
47// Public transfer functions
48pub 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
79// Private transfer functions
80pub 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
106// Coin registry functions
107pub const SUI_COIN_REGISTRY_NEW_CURRENCY: FunctionIdent = (
108    SUI_FRAMEWORK_ADDRESS,
109    COIN_REGISTRY_MODULE,
110    ident_str!("new_currency"),
111);
112
113// Modules that must have all public functions listed in `FUNCTIONS_TO_CHECK`
114pub const EXHAUSTIVE_MODULES: &[(AccountAddress, &IdentStr)] = &[
115    (SUI_FRAMEWORK_ADDRESS, EVENT_MODULE),
116    (SUI_FRAMEWORK_ADDRESS, TRANSFER_MODULE),
117];
118
119// A list of all functions to check for internal rules. A boolean for each type parameter indicates
120// if the type parameter is `internal`
121pub const FUNCTIONS_TO_CHECK: &[(FunctionIdent, &[/* is internal */ bool])] = &[
122    // stdlib functions
123    (MOVE_STDLIB_INTERNAL_PERMIT, &[true]),
124    // event functions
125    (SUI_EVENT_EMIT_EVENT, &[true]),
126    (SUI_EVENT_EMIT_AUTHENTICATED, &[true]),
127    (SUI_EVENT_NUM_EVENTS, &[]),
128    (SUI_EVENT_EVENTS_BY_TYPE, &[false]),
129    // public transfer functions
130    (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    // private transfer functions
137    (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    // coin registry functions
143    (SUI_COIN_REGISTRY_NEW_CURRENCY, &[true]),
144];
145
146enum Error {
147    User(String),
148    InvariantViolation(String),
149}
150
151/// Several functions in the Sui Framework have `internal` type parameters, whose arguments must be
152/// instantiated with types defined in the caller's module.
153/// For example, with `transfer::transfer<T>(...)` `T` must be a type declared in the current
154/// module. Otherwise, `transfer::public_transfer<T>(...)` can be used without restriction, as long
155/// as `T` has `store`. Note thought that the ability constraint is not checked in this verifier,
156/// but rather in the normal bytecode verifier type checking.
157/// To avoid, issues, all `su::transfer` and `sui::event` functions must be configured in `INTERNAL_FUNCTIONS`.
158pub 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    // Skip sui::test_scenario
167    if module_address == SUI_FRAMEWORK_ADDRESS && module_name.as_str() == TEST_SCENARIO_MODULE_NAME
168    {
169        // exclude test_module which is a test-only module in the Sui framework which "emulates"
170        // transactional execution and needs to allow test code to bypass private generics
171        return Ok(());
172    };
173
174    // Check exhaustiveness for sensitive modules
175    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                // The function needs to be added to the FUNCTIONS_TO_CHECK list
186                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    // Check calls
195    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        // This should have been caught by the bytecode verifier
251        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}