Skip to main content

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