sui_verifier_v0/
private_generics.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use move_binary_format::{
5    file_format::{
6        Bytecode, FunctionDefinition, FunctionHandle, FunctionInstantiation, ModuleHandle,
7        SignatureToken,
8    },
9    CompiledModule,
10};
11use move_bytecode_utils::format_signature_token;
12use move_core_types::{account_address::AccountAddress, ident_str, identifier::IdentStr};
13use sui_types::{error::ExecutionError, SUI_FRAMEWORK_ADDRESS};
14
15use crate::{verification_failure, TEST_SCENARIO_MODULE_NAME};
16
17pub const TRANSFER_MODULE: &IdentStr = ident_str!("transfer");
18pub const EVENT_MODULE: &IdentStr = ident_str!("event");
19pub const EVENT_FUNCTION: &IdentStr = ident_str!("emit");
20pub const PUBLIC_TRANSFER_FUNCTIONS: &[&IdentStr] = &[
21    ident_str!("public_transfer"),
22    ident_str!("public_freeze_object"),
23    ident_str!("public_share_object"),
24];
25pub const PRIVATE_TRANSFER_FUNCTIONS: &[&IdentStr] = &[
26    ident_str!("transfer"),
27    ident_str!("freeze_object"),
28    ident_str!("share_object"),
29];
30pub const TRANSFER_IMPL_FUNCTIONS: &[&IdentStr] = &[
31    ident_str!("transfer_impl"),
32    ident_str!("freeze_object_impl"),
33    ident_str!("share_object_impl"),
34];
35
36/// All transfer functions (the functions in `sui::transfer`) are "private" in that they are
37/// restricted to the module.
38/// For example, with `transfer::transfer<T>(...)`, either:
39/// - `T` must be a type declared in the current module or
40/// - `T` must have `store`
41///
42/// Similarly, `event::emit` is also "private" to the module. Unlike the `transfer` functions, there
43/// is no relaxation for `store`
44/// Concretely, with `event::emit<T>(...)`:
45/// - `T` must be a type declared in the current module
46pub fn verify_module(module: &CompiledModule) -> Result<(), ExecutionError> {
47    if *module.address() == SUI_FRAMEWORK_ADDRESS
48        && module.name() == IdentStr::new(TEST_SCENARIO_MODULE_NAME).unwrap()
49    {
50        // exclude test_module which is a test-only module in the Sui framework which "emulates"
51        // transactional execution and needs to allow test code to bypass private generics
52        return Ok(());
53    }
54    // do not need to check the sui::transfer module itself
55    for func_def in &module.function_defs {
56        verify_function(module, func_def).map_err(|error| {
57            verification_failure(format!(
58                "{}::{}. {}",
59                module.self_id(),
60                module.identifier_at(module.function_handle_at(func_def.function).name),
61                error
62            ))
63        })?;
64    }
65    Ok(())
66}
67
68fn verify_function(view: &CompiledModule, fdef: &FunctionDefinition) -> Result<(), String> {
69    let code = match &fdef.code {
70        None => return Ok(()),
71        Some(code) => code,
72    };
73    for instr in &code.code {
74        if let Bytecode::CallGeneric(finst_idx) = instr {
75            let FunctionInstantiation {
76                handle,
77                type_parameters,
78            } = view.function_instantiation_at(*finst_idx);
79
80            let fhandle = view.function_handle_at(*handle);
81            let mhandle = view.module_handle_at(fhandle.module);
82
83            let type_arguments = &view.signature_at(*type_parameters).0;
84            let ident = addr_module(view, mhandle);
85            if ident == (SUI_FRAMEWORK_ADDRESS, TRANSFER_MODULE) {
86                verify_private_transfer(view, fhandle, type_arguments)?
87            } else if ident == (SUI_FRAMEWORK_ADDRESS, EVENT_MODULE) {
88                verify_private_event_emit(view, fhandle, type_arguments)?
89            }
90        }
91    }
92    Ok(())
93}
94
95fn verify_private_transfer(
96    view: &CompiledModule,
97    fhandle: &FunctionHandle,
98    type_arguments: &[SignatureToken],
99) -> Result<(), String> {
100    let self_handle = view.module_handle_at(view.self_handle_idx());
101    if addr_module(view, self_handle) == (SUI_FRAMEWORK_ADDRESS, TRANSFER_MODULE) {
102        return Ok(());
103    }
104    let fident = view.identifier_at(fhandle.name);
105    // public transfer functions require `store` and have no additional rules
106    if PUBLIC_TRANSFER_FUNCTIONS.contains(&fident) {
107        return Ok(());
108    }
109    if !PRIVATE_TRANSFER_FUNCTIONS.contains(&fident) {
110        // unknown function, so a bug in the implementation here
111        debug_assert!(false, "unknown transfer function {}", fident);
112        return Err(format!("Calling unknown transfer function, {}", fident));
113    };
114
115    if type_arguments.len() != 1 {
116        debug_assert!(false, "Expected 1 type argument for {}", fident);
117        return Err(format!("Expected 1 type argument for {}", fident));
118    }
119
120    let type_arg = &type_arguments[0];
121    if !is_defined_in_current_module(view, type_arg) {
122        return Err(format!(
123            "Invalid call to '{sui}::transfer::{f}' on an object of type '{t}'. \
124            The transferred object's type must be defined in the current module. \
125            If the object has the 'store' type ability, you can use the non-internal variant \
126            instead, i.e. '{sui}::transfer::public_{f}'",
127            sui = SUI_FRAMEWORK_ADDRESS,
128            f = fident,
129            t = format_signature_token(view, type_arg),
130        ));
131    }
132
133    Ok(())
134}
135
136fn verify_private_event_emit(
137    view: &CompiledModule,
138    fhandle: &FunctionHandle,
139    type_arguments: &[SignatureToken],
140) -> Result<(), String> {
141    let fident = view.identifier_at(fhandle.name);
142    if fident != EVENT_FUNCTION {
143        debug_assert!(false, "unknown transfer function {}", fident);
144        return Err(format!("Calling unknown event function, {}", fident));
145    };
146
147    if type_arguments.len() != 1 {
148        debug_assert!(false, "Expected 1 type argument for {}", fident);
149        return Err(format!("Expected 1 type argument for {}", fident));
150    }
151
152    let type_arg = &type_arguments[0];
153    if !is_defined_in_current_module(view, type_arg) {
154        return Err(format!(
155            "Invalid call to '{}::event::{}' with an event type '{}'. \
156                The event's type must be defined in the current module",
157            SUI_FRAMEWORK_ADDRESS,
158            fident,
159            format_signature_token(view, type_arg),
160        ));
161    }
162
163    Ok(())
164}
165
166fn is_defined_in_current_module(view: &CompiledModule, type_arg: &SignatureToken) -> bool {
167    match type_arg {
168        SignatureToken::Datatype(_) | SignatureToken::DatatypeInstantiation(_) => {
169            let idx = match type_arg {
170                SignatureToken::Datatype(idx) => *idx,
171                SignatureToken::DatatypeInstantiation(s) => s.0,
172                _ => unreachable!(),
173            };
174            let shandle = view.datatype_handle_at(idx);
175            view.self_handle_idx() == shandle.module
176        }
177        SignatureToken::TypeParameter(_)
178        | SignatureToken::Bool
179        | SignatureToken::U8
180        | SignatureToken::U16
181        | SignatureToken::U32
182        | SignatureToken::U64
183        | SignatureToken::U128
184        | SignatureToken::U256
185        | SignatureToken::Address
186        | SignatureToken::Vector(_)
187        | SignatureToken::Signer
188        | SignatureToken::Reference(_)
189        | SignatureToken::MutableReference(_) => false,
190    }
191}
192
193fn addr_module<'a>(
194    view: &'a CompiledModule,
195    mhandle: &ModuleHandle,
196) -> (AccountAddress, &'a IdentStr) {
197    let maddr = view.address_identifier_at(mhandle.address);
198    let mident = view.identifier_at(mhandle.name);
199    (*maddr, mident)
200}