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