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