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