sui_verifier_latest/
private_generics.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use move_binary_format::{
5    CompiledModule,
6    file_format::{
7        Bytecode, FunctionDefinition, FunctionHandle, FunctionInstantiation, ModuleHandle,
8        SignatureToken,
9    },
10};
11use move_bytecode_utils::format_signature_token;
12use move_core_types::{account_address::AccountAddress, ident_str, identifier::IdentStr};
13use move_vm_config::verifier::VerifierConfig;
14use sui_types::{SUI_FRAMEWORK_ADDRESS, error::ExecutionError};
15
16use crate::{TEST_SCENARIO_MODULE_NAME, verification_failure};
17
18pub const TRANSFER_MODULE: &IdentStr = ident_str!("transfer");
19pub const EVENT_MODULE: &IdentStr = ident_str!("event");
20pub const EVENT_FUNCTION: &IdentStr = ident_str!("emit");
21pub const EMIT_AUTHENTICATED_FUNCTION: &IdentStr = ident_str!("emit_authenticated");
22pub const EMIT_AUTHENTICATED_IMPL_FUNCTION: &IdentStr = ident_str!("emit_authenticated_impl");
23
24pub const PRIVATE_EVENT_FUNCTIONS: &[&IdentStr] = &[EVENT_FUNCTION, EMIT_AUTHENTICATED_FUNCTION];
25pub const GET_EVENTS_TEST_FUNCTION: &IdentStr = ident_str!("events_by_type");
26pub const COIN_REGISTRY_MODULE: &IdentStr = ident_str!("coin_registry");
27pub const DYNAMIC_COIN_CREATION_FUNCTION: &IdentStr = ident_str!("new_currency");
28pub const PUBLIC_TRANSFER_FUNCTIONS: &[&IdentStr] = &[
29    ident_str!("public_transfer"),
30    ident_str!("public_freeze_object"),
31    ident_str!("public_share_object"),
32    ident_str!("public_receive"),
33    ident_str!("receiving_object_id"),
34    ident_str!("public_party_transfer"),
35];
36pub const PRIVATE_TRANSFER_FUNCTIONS: &[&IdentStr] = &[
37    ident_str!("transfer"),
38    ident_str!("freeze_object"),
39    ident_str!("share_object"),
40    ident_str!("receive"),
41    ident_str!("party_transfer"),
42];
43
44/// All transfer functions (the functions in `sui::transfer`) are "private" in that they are
45/// restricted to the module.
46/// For example, with `transfer::transfer<T>(...)`, either:
47/// - `T` must be a type declared in the current module or
48/// - `T` must have `store`
49///
50/// Similarly, `event::emit` is also "private" to the module. Unlike the `transfer` functions, there
51/// is no relaxation for `store`
52/// Concretely, with `event::emit<T>(...)`:
53/// - `T` must be a type declared in the current module
54pub fn verify_module(
55    module: &CompiledModule,
56    verifier_config: &VerifierConfig,
57) -> Result<(), ExecutionError> {
58    if *module.address() == SUI_FRAMEWORK_ADDRESS
59        && module.name() == IdentStr::new(TEST_SCENARIO_MODULE_NAME).unwrap()
60    {
61        // exclude test_module which is a test-only module in the Sui framework which "emulates"
62        // transactional execution and needs to allow test code to bypass private generics
63        return Ok(());
64    }
65    // do not need to check the sui::transfer module itself
66    for func_def in &module.function_defs {
67        verify_function(module, func_def, verifier_config.allow_receiving_object_id).map_err(
68            |error| {
69                verification_failure(format!(
70                    "{}::{}. {}",
71                    module.self_id(),
72                    module.identifier_at(module.function_handle_at(func_def.function).name),
73                    error
74                ))
75            },
76        )?;
77    }
78    Ok(())
79}
80
81fn verify_function(
82    view: &CompiledModule,
83    fdef: &FunctionDefinition,
84    allow_receiving_object_id: bool,
85) -> Result<(), String> {
86    let code = match &fdef.code {
87        None => return Ok(()),
88        Some(code) => code,
89    };
90    for instr in &code.code {
91        if let Bytecode::CallGeneric(finst_idx) = instr {
92            let FunctionInstantiation {
93                handle,
94                type_parameters,
95            } = view.function_instantiation_at(*finst_idx);
96
97            let fhandle = view.function_handle_at(*handle);
98            let mhandle = view.module_handle_at(fhandle.module);
99
100            let type_arguments = &view.signature_at(*type_parameters).0;
101            let ident = addr_module(view, mhandle);
102            if ident == (SUI_FRAMEWORK_ADDRESS, TRANSFER_MODULE) {
103                verify_private_transfer(view, fhandle, type_arguments, allow_receiving_object_id)?
104            } else if ident == (SUI_FRAMEWORK_ADDRESS, EVENT_MODULE) {
105                verify_private_event_emit(view, fhandle, type_arguments)?
106            } else if ident == (SUI_FRAMEWORK_ADDRESS, COIN_REGISTRY_MODULE) {
107                verify_dynamic_coin_creation(view, fhandle, type_arguments)?
108            }
109        }
110    }
111    Ok(())
112}
113
114fn verify_private_transfer(
115    view: &CompiledModule,
116    fhandle: &FunctionHandle,
117    type_arguments: &[SignatureToken],
118    allow_receiving_object_id: bool,
119) -> Result<(), String> {
120    let public_transfer_functions = if allow_receiving_object_id {
121        PUBLIC_TRANSFER_FUNCTIONS
122    } else {
123        // Before protocol version 33, the `receiving_object_id` function was not public
124        &PUBLIC_TRANSFER_FUNCTIONS[..4]
125    };
126    let self_handle = view.module_handle_at(view.self_handle_idx());
127    if addr_module(view, self_handle) == (SUI_FRAMEWORK_ADDRESS, TRANSFER_MODULE) {
128        return Ok(());
129    }
130    let fident = view.identifier_at(fhandle.name);
131    // public transfer functions require `store` and have no additional rules
132    if public_transfer_functions.contains(&fident) {
133        return Ok(());
134    }
135    if !PRIVATE_TRANSFER_FUNCTIONS.contains(&fident) {
136        // unknown function, so a bug in the implementation here
137        debug_assert!(false, "unknown transfer function {}", fident);
138        return Err(format!("Calling unknown transfer function, {}", fident));
139    };
140
141    if type_arguments.len() != 1 {
142        debug_assert!(false, "Expected 1 type argument for {}", fident);
143        return Err(format!("Expected 1 type argument for {}", fident));
144    }
145
146    let type_arg = &type_arguments[0];
147    if !is_defined_in_current_module(view, type_arg) {
148        return Err(format!(
149            "Invalid call to '{sui}::transfer::{f}' on an object of type '{t}'. \
150            The transferred object's type must be defined in the current module. \
151            If the object has the 'store' type ability, you can use the non-internal variant \
152            instead, i.e. '{sui}::transfer::public_{f}'",
153            sui = SUI_FRAMEWORK_ADDRESS,
154            f = fident,
155            t = format_signature_token(view, type_arg),
156        ));
157    }
158
159    Ok(())
160}
161
162fn verify_private_event_emit(
163    view: &CompiledModule,
164    fhandle: &FunctionHandle,
165    type_arguments: &[SignatureToken],
166) -> Result<(), String> {
167    let fident = view.identifier_at(fhandle.name);
168    if fident == GET_EVENTS_TEST_FUNCTION {
169        // test-only function with no params--no need to verify
170        return Ok(());
171    }
172
173    if fident == EMIT_AUTHENTICATED_IMPL_FUNCTION {
174        let module_id = view.self_id();
175        if (module_id.address(), module_id.name()) != (&SUI_FRAMEWORK_ADDRESS, EVENT_MODULE) {
176            debug_assert!(
177                false,
178                "Calling {} outside of {} module this shouldn't happen",
179                EMIT_AUTHENTICATED_IMPL_FUNCTION, EVENT_MODULE
180            );
181            return Err(format!(
182                "Calling {} outside of {} which is impossible",
183                EMIT_AUTHENTICATED_IMPL_FUNCTION, EVENT_MODULE
184            ));
185        } else {
186            return Ok(());
187        }
188    }
189
190    if !PRIVATE_EVENT_FUNCTIONS.contains(&fident) {
191        debug_assert!(false, "unknown event function {}", fident);
192        return Err(format!("Calling unknown event function, {}", fident));
193    };
194
195    if type_arguments.len() != 1 {
196        debug_assert!(false, "Expected 1 type argument for {}", fident);
197        return Err(format!("Expected 1 type argument for {}", fident));
198    }
199
200    let type_arg = &type_arguments[0];
201    if !is_defined_in_current_module(view, type_arg) {
202        return Err(format!(
203            "Invalid call to '{}::event::{}' with an event type '{}'. \
204                The event's type must be defined in the current module",
205            SUI_FRAMEWORK_ADDRESS,
206            fident,
207            format_signature_token(view, type_arg),
208        ));
209    }
210
211    Ok(())
212}
213
214/// Coin creation using `key` (non OTW coin creation) is only possible with
215/// internal types.
216fn verify_dynamic_coin_creation(
217    view: &CompiledModule,
218    fhandle: &FunctionHandle,
219    type_arguments: &[SignatureToken],
220) -> Result<(), String> {
221    let fident = view.identifier_at(fhandle.name);
222
223    // If we are calling anything besides `coin::new_currency`,
224    // we don't need this check.
225    if fident != DYNAMIC_COIN_CREATION_FUNCTION {
226        return Ok(());
227    }
228
229    // We expect a single type argument (`T: key`)
230    if type_arguments.len() != 1 {
231        debug_assert!(false, "Expected 1 type argument for {}", fident);
232        return Err(format!("Expected 1 type argument for {}", fident));
233    }
234
235    let type_arg = &type_arguments[0];
236    if !is_defined_in_current_module(view, type_arg) {
237        return Err(format!(
238            "Invalid call to '{}::coin_registry::{}' with type '{}'. \
239                The coin's type must be defined in the current module",
240            SUI_FRAMEWORK_ADDRESS,
241            fident,
242            format_signature_token(view, type_arg),
243        ));
244    }
245
246    Ok(())
247}
248
249fn is_defined_in_current_module(view: &CompiledModule, type_arg: &SignatureToken) -> bool {
250    match type_arg {
251        SignatureToken::Datatype(_) | SignatureToken::DatatypeInstantiation(_) => {
252            let idx = match type_arg {
253                SignatureToken::Datatype(idx) => *idx,
254                SignatureToken::DatatypeInstantiation(s) => s.0,
255                _ => unreachable!(),
256            };
257            let shandle = view.datatype_handle_at(idx);
258            view.self_handle_idx() == shandle.module
259        }
260        SignatureToken::TypeParameter(_)
261        | SignatureToken::Bool
262        | SignatureToken::U8
263        | SignatureToken::U16
264        | SignatureToken::U32
265        | SignatureToken::U64
266        | SignatureToken::U128
267        | SignatureToken::U256
268        | SignatureToken::Address
269        | SignatureToken::Vector(_)
270        | SignatureToken::Signer
271        | SignatureToken::Reference(_)
272        | SignatureToken::MutableReference(_) => false,
273    }
274}
275
276fn addr_module<'a>(
277    view: &'a CompiledModule,
278    mhandle: &ModuleHandle,
279) -> (AccountAddress, &'a IdentStr) {
280    let maddr = view.address_identifier_at(mhandle.address);
281    let mident = view.identifier_at(mhandle.name);
282    (*maddr, mident)
283}