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 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
23// Event function
24pub 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
42// Public transfer functions
43pub 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
74// Private transfer functions
75pub 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
101// Coin registry functions
102pub const SUI_COIN_REGISTRY_NEW_CURRENCY: FunctionIdent = (
103    SUI_FRAMEWORK_ADDRESS,
104    COIN_REGISTRY_MODULE,
105    ident_str!("new_currency"),
106);
107
108// Modules that must have all public functions listed in `FUNCTIONS_TO_CHECK`
109pub const EXHAUSTIVE_MODULES: &[(AccountAddress, &IdentStr)] = &[
110    (SUI_FRAMEWORK_ADDRESS, EVENT_MODULE),
111    (SUI_FRAMEWORK_ADDRESS, TRANSFER_MODULE),
112];
113
114// A list of all functions to check for internal rules. A boolean for each type parameter indicates
115// if the type parameter is `internal`
116pub const FUNCTIONS_TO_CHECK: &[(FunctionIdent, &[/* is internal */ bool])] = &[
117    // event functions
118    (SUI_EVENT_EMIT_EVENT, &[true]),
119    (SUI_EVENT_EMIT_AUTHENTICATED, &[true]),
120    (SUI_EVENT_NUM_EVENTS, &[]),
121    (SUI_EVENT_EVENTS_BY_TYPE, &[false]),
122    // public transfer functions
123    (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    // private transfer functions
130    (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    // coin registry functions
136    (SUI_COIN_REGISTRY_NEW_CURRENCY, &[true]),
137];
138
139enum Error {
140    User(String),
141    InvariantViolation(String),
142}
143
144/// Several functions in the Sui Framework have `internal` type parameters, whose arguments must be
145/// instantiated with types defined in the caller's module.
146/// For example, with `transfer::transfer<T>(...)` `T` must be a type declared in the current
147/// module. Otherwise, `transfer::public_transfer<T>(...)` can be used without restriction, as long
148/// as `T` has `store`. Note thought that the ability constraint is not checked in this verifier,
149/// but rather in the normal bytecode verifier type checking.
150/// To avoid, issues, all `su::transfer` and `sui::event` functions must be configured in `INTERNAL_FUNCTIONS`.
151pub 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    // Skip sui::test_scenario
160    if module_address == SUI_FRAMEWORK_ADDRESS && module_name.as_str() == TEST_SCENARIO_MODULE_NAME
161    {
162        // exclude test_module which is a test-only module in the Sui framework which "emulates"
163        // transactional execution and needs to allow test code to bypass private generics
164        return Ok(());
165    };
166
167    // Check exhaustiveness for sensitive modules
168    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                // The function needs to be added to the FUNCTIONS_TO_CHECK list
179                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    // Check calls
188    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        // This should have been caught by the bytecode verifier
244        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}