sui_verifier_v0/
entry_points_verifier.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use move_binary_format::{
5    file_format::{AbilitySet, Bytecode, FunctionDefinition, SignatureToken, Visibility},
6    CompiledModule,
7};
8use move_bytecode_utils::format_signature_token;
9use sui_protocol_config::ProtocolConfig;
10use sui_types::{
11    base_types::{TxContext, TxContextKind, TX_CONTEXT_MODULE_NAME, TX_CONTEXT_STRUCT_NAME},
12    clock::Clock,
13    error::ExecutionError,
14    is_object, is_object_vector, is_primitive,
15    move_package::{is_test_fun, FnInfoMap},
16    SUI_FRAMEWORK_ADDRESS,
17};
18
19use crate::{verification_failure, INIT_FN_NAME};
20
21/// Checks valid rules for entry points, both for module initialization and transactions
22///
23/// For module initialization
24/// - The existence of the function is optional
25/// - The function must have the name specified by `INIT_FN_NAME`
26/// - The function must have `Visibility::Private`
27/// - The function can have at most two parameters:
28///   - mandatory &mut TxContext or &TxContext (see `is_tx_context`) in the last position
29///   - optional one-time witness type (see one_time_witness verifier pass) passed by value in the first
30///     position
31///
32/// For transaction entry points
33/// - The function must have `is_entry` true
34/// - The function may have a &mut TxContext or &TxContext (see `is_tx_context`) parameter
35///   - The transaction context parameter must be the last parameter
36/// - The function cannot have any return values
37pub fn verify_module(
38    config: &ProtocolConfig,
39    module: &CompiledModule,
40    fn_info_map: &FnInfoMap,
41) -> Result<(), ExecutionError> {
42    // When verifying test functions, a check preventing explicit calls to init functions is
43    // disabled.
44
45    for func_def in &module.function_defs {
46        let handle = module.function_handle_at(func_def.function);
47        let name = module.identifier_at(handle.name);
48
49        // allow calling init function in the test code
50        if !is_test_fun(name, module, fn_info_map) {
51            verify_init_not_called(module, func_def).map_err(verification_failure)?;
52        }
53
54        if name == INIT_FN_NAME {
55            verify_init_function(config, module, func_def).map_err(verification_failure)?;
56            continue;
57        }
58
59        // find candidate entry functions and check their parameters
60        // (ignore other functions)
61        if !func_def.is_entry {
62            // it's not an entry function
63            continue;
64        }
65        verify_entry_function_impl(module, func_def).map_err(verification_failure)?;
66    }
67    Ok(())
68}
69
70fn verify_init_not_called(
71    module: &CompiledModule,
72    fdef: &FunctionDefinition,
73) -> Result<(), String> {
74    let code = match &fdef.code {
75        None => return Ok(()),
76        Some(code) => code,
77    };
78    code.code
79        .iter()
80        .enumerate()
81        .filter_map(|(idx, instr)| match instr {
82            Bytecode::Call(fhandle_idx) => Some((idx, module.function_handle_at(*fhandle_idx))),
83            Bytecode::CallGeneric(finst_idx) => {
84                let finst = module.function_instantiation_at(*finst_idx);
85                Some((idx, module.function_handle_at(finst.handle)))
86            }
87            _ => None,
88        })
89        .try_for_each(|(idx, fhandle)| {
90            let name = module.identifier_at(fhandle.name);
91            if name == INIT_FN_NAME {
92                Err(format!(
93                    "{}::{} at offset {}. Cannot call a module's '{}' function from another Move function",
94                    module.self_id(),
95                    name,
96                    idx,
97                    INIT_FN_NAME
98                ))
99            } else {
100                Ok(())
101            }
102        })
103}
104
105/// Checks if this module has a conformant `init`
106fn verify_init_function(
107    config: &ProtocolConfig,
108    module: &CompiledModule,
109    fdef: &FunctionDefinition,
110) -> Result<(), String> {
111    if fdef.visibility != Visibility::Private {
112        return Err(format!(
113            "{}. '{}' function must be private",
114            module.self_id(),
115            INIT_FN_NAME
116        ));
117    }
118
119    if config.ban_entry_init() && fdef.is_entry {
120        return Err(format!(
121            "{}. '{}' cannot be 'entry'",
122            module.self_id(),
123            INIT_FN_NAME
124        ));
125    }
126
127    let fhandle = module.function_handle_at(fdef.function);
128    if !fhandle.type_parameters.is_empty() {
129        return Err(format!(
130            "{}. '{}' function cannot have type parameters",
131            module.self_id(),
132            INIT_FN_NAME
133        ));
134    }
135
136    if !module.signature_at(fhandle.return_).is_empty() {
137        return Err(format!(
138            "{}, '{}' function cannot have return values",
139            module.self_id(),
140            INIT_FN_NAME
141        ));
142    }
143
144    let parameters = &module.signature_at(fhandle.parameters).0;
145    if parameters.is_empty() || parameters.len() > 2 {
146        return Err(format!(
147            "Expected at least one and at most two parameters for {}::{}",
148            module.self_id(),
149            INIT_FN_NAME,
150        ));
151    }
152
153    // Checking only the last (and possibly the only) parameter here. If there are two parameters,
154    // then the first parameter must be of a one-time witness type and must be passed by value. This
155    // is checked by the verifier for pass one-time witness value (one_time_witness_verifier) -
156    // please see the description of this pass for additional details.
157    if TxContext::kind(module, &parameters[parameters.len() - 1]) != TxContextKind::None {
158        Ok(())
159    } else {
160        Err(format!(
161            "Expected last parameter for {0}::{1} to be &mut {2}::{3}::{4} or &{2}::{3}::{4}, \
162            but found {5}",
163            module.self_id(),
164            INIT_FN_NAME,
165            SUI_FRAMEWORK_ADDRESS,
166            TX_CONTEXT_MODULE_NAME,
167            TX_CONTEXT_STRUCT_NAME,
168            format_signature_token(module, &parameters[0]),
169        ))
170    }
171}
172
173fn verify_entry_function_impl(
174    module: &CompiledModule,
175    func_def: &FunctionDefinition,
176) -> Result<(), String> {
177    let handle = module.function_handle_at(func_def.function);
178    let params = module.signature_at(handle.parameters);
179
180    let all_non_ctx_params = match params.0.last() {
181        Some(last_param) if TxContext::kind(module, last_param) != TxContextKind::None => {
182            &params.0[0..params.0.len() - 1]
183        }
184        _ => &params.0,
185    };
186    for param in all_non_ctx_params {
187        verify_param_type(module, &handle.type_parameters, param)?;
188    }
189
190    for return_ty in &module.signature_at(handle.return_).0 {
191        verify_return_type(module, &handle.type_parameters, return_ty)?;
192    }
193
194    Ok(())
195}
196
197fn verify_return_type(
198    view: &CompiledModule,
199    type_parameters: &[AbilitySet],
200    return_ty: &SignatureToken,
201) -> Result<(), String> {
202    if matches!(
203        return_ty,
204        SignatureToken::Reference(_) | SignatureToken::MutableReference(_)
205    ) {
206        return Err("Invalid entry point return type. Expected a non reference type.".to_owned());
207    }
208    let abilities = view
209        .abilities(return_ty, type_parameters)
210        .map_err(|e| format!("Unexpected CompiledModule error: {}", e))?;
211    if abilities.has_drop() {
212        Ok(())
213    } else {
214        Err(format!(
215            "Invalid entry point return type. \
216            The specified return type does not have the 'drop' ability: {}",
217            format_signature_token(view, return_ty),
218        ))
219    }
220}
221
222fn verify_param_type(
223    view: &CompiledModule,
224    function_type_args: &[AbilitySet],
225    param: &SignatureToken,
226) -> Result<(), String> {
227    // Only `sui::sui_system` is allowed to expose entry functions that accept a mutable clock
228    // parameter.
229    if Clock::is_mutable(view, param) {
230        return Err(format!(
231            "Invalid entry point parameter type. Clock must be passed by immutable reference. got: \
232             {}",
233            format_signature_token(view, param),
234        ));
235    }
236
237    if is_primitive(view, function_type_args, param)
238        || is_object(view, function_type_args, param)?
239        || is_object_vector(view, function_type_args, param)?
240    {
241        Ok(())
242    } else {
243        Err(format!(
244            "Invalid entry point parameter type. Expected primitive or object type. Got: {}",
245            format_signature_token(view, param)
246        ))
247    }
248}