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