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