iota_move_natives_latest/
event.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::collections::VecDeque;
6
7use iota_types::error::VMMemoryLimitExceededSubStatusCode;
8use move_binary_format::errors::{PartialVMError, PartialVMResult};
9use move_core_types::{gas_algebra::InternalGas, language_storage::TypeTag, vm_status::StatusCode};
10use move_vm_runtime::{native_charge_gas_early_exit, native_functions::NativeContext};
11use move_vm_types::{
12    loaded_data::runtime_types::Type, natives::function::NativeResult, values::Value,
13};
14use smallvec::smallvec;
15
16use crate::{NativesCostTable, legacy_test_cost, object_runtime::ObjectRuntime};
17
18#[derive(Clone, Debug)]
19pub struct EventEmitCostParams {
20    pub event_emit_cost_base: InternalGas,
21    pub event_emit_value_size_derivation_cost_per_byte: InternalGas,
22    pub event_emit_tag_size_derivation_cost_per_byte: InternalGas,
23    pub event_emit_output_cost_per_byte: InternalGas,
24}
25/// ****************************************************************************
26/// ********************* native fun emit
27/// Implementation of the Move native function `event::emit<T: copy +
28/// drop>(event: T)` Adds an event to the transaction's event log
29///   gas cost: event_emit_cost_base                  |  covers various fixed
30/// costs in the oper
31///              + event_emit_value_size_derivation_cost_per_byte * event_size |
32///                derivation of size
33///              + event_emit_tag_size_derivation_cost_per_byte * tag_size |
34///                converting type
35///              + event_emit_output_cost_per_byte * (tag_size + event_size) |
36///                emitting the actual event
37/// ****************************************************************************
38/// *******************
39pub fn emit(
40    context: &mut NativeContext,
41    mut ty_args: Vec<Type>,
42    mut args: VecDeque<Value>,
43) -> PartialVMResult<NativeResult> {
44    debug_assert!(ty_args.len() == 1);
45    debug_assert!(args.len() == 1);
46
47    let event_emit_cost_params = context
48        .extensions_mut()
49        .get::<NativesCostTable>()
50        .event_emit_cost_params
51        .clone();
52
53    native_charge_gas_early_exit!(context, event_emit_cost_params.event_emit_cost_base);
54
55    let ty = ty_args.pop().unwrap();
56    let event_value = args.pop_back().unwrap();
57
58    let event_value_size = event_value.legacy_size();
59
60    // Deriving event value size can be expensive due to recursion overhead
61    native_charge_gas_early_exit!(
62        context,
63        event_emit_cost_params.event_emit_value_size_derivation_cost_per_byte
64            * u64::from(event_value_size).into()
65    );
66
67    let tag = match context.type_to_type_tag(&ty)? {
68        TypeTag::Struct(s) => s,
69        _ => {
70            return Err(
71                PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
72                    .with_message("IOTA verifier guarantees this is a struct".to_string()),
73            );
74        }
75    };
76    let tag_size = tag.abstract_size_for_gas_metering();
77
78    // Converting type to typetag be expensive due to recursion overhead
79    native_charge_gas_early_exit!(
80        context,
81        event_emit_cost_params.event_emit_tag_size_derivation_cost_per_byte
82            * u64::from(tag_size).into()
83    );
84
85    let obj_runtime: &mut ObjectRuntime = context.extensions_mut().get_mut();
86    let max_event_emit_size = obj_runtime.protocol_config.max_event_emit_size();
87    let ev_size = u64::from(tag_size + event_value_size);
88    // Check if the event size is within the limit
89    if ev_size > max_event_emit_size {
90        return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
91            .with_message(format!(
92                "Emitting event of size {ev_size} bytes. Limit is {max_event_emit_size} bytes."
93            ))
94            .with_sub_status(
95                VMMemoryLimitExceededSubStatusCode::EVENT_SIZE_LIMIT_EXCEEDED as u64,
96            ));
97    }
98
99    // Check that the size contribution of the event is within the total size limit
100    // This feature is guarded as its only present in some versions
101    if let Some(max_event_emit_size_total) = obj_runtime
102        .protocol_config
103        .max_event_emit_size_total_as_option()
104    {
105        let total_events_size = obj_runtime.state.total_events_size() + ev_size;
106        if total_events_size > max_event_emit_size_total {
107            return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
108                .with_message(format!(
109                    "Reached total event size of size {total_events_size} bytes. Limit is {max_event_emit_size_total} bytes."
110                ))
111                .with_sub_status(
112                    VMMemoryLimitExceededSubStatusCode::TOTAL_EVENT_SIZE_LIMIT_EXCEEDED as u64,
113                ));
114        }
115        obj_runtime.state.incr_total_events_size(ev_size);
116    }
117    // Emitting an event is cheap since its a vector push
118    native_charge_gas_early_exit!(
119        context,
120        event_emit_cost_params.event_emit_output_cost_per_byte * ev_size.into()
121    );
122
123    let obj_runtime: &mut ObjectRuntime = context.extensions_mut().get_mut();
124
125    obj_runtime.emit_event(ty, *tag, event_value)?;
126    Ok(NativeResult::ok(context.gas_used(), smallvec![]))
127}
128
129/// Get the all emitted events of type `T`, starting at the specified index
130pub fn num_events(
131    context: &mut NativeContext,
132    ty_args: Vec<Type>,
133    args: VecDeque<Value>,
134) -> PartialVMResult<NativeResult> {
135    assert!(ty_args.is_empty());
136    assert!(args.is_empty());
137    let object_runtime_ref: &ObjectRuntime = context.extensions().get();
138    let num_events = object_runtime_ref.state.events().len();
139    Ok(NativeResult::ok(
140        legacy_test_cost(),
141        smallvec![Value::u32(num_events as u32)],
142    ))
143}
144
145/// Get the all emitted events of type `T`, starting at the specified index
146pub fn get_events_by_type(
147    context: &mut NativeContext,
148    mut ty_args: Vec<Type>,
149    args: VecDeque<Value>,
150) -> PartialVMResult<NativeResult> {
151    assert_eq!(ty_args.len(), 1);
152    let specified_ty = ty_args.pop().unwrap();
153    assert!(args.is_empty());
154    let object_runtime_ref: &ObjectRuntime = context.extensions().get();
155    let matched_events = object_runtime_ref
156        .state
157        .events()
158        .iter()
159        .filter_map(|(ty, _, event)| {
160            if specified_ty == *ty {
161                Some(event.copy_value().unwrap())
162            } else {
163                None
164            }
165        })
166        .collect::<Vec<_>>();
167    Ok(NativeResult::ok(
168        legacy_test_cost(),
169        smallvec![Value::vector_for_testing_only(matched_events)],
170    ))
171}