iota_verifier_latest/
entry_points_verifier.rs

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