Skip to main content

sui_move_natives_latest/
event.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::{
5    NativesCostTable, abstract_size, get_extension, get_extension_mut, legacy_test_cost,
6    object_runtime::{MoveAccumulatorAction, MoveAccumulatorValue, ObjectRuntime},
7};
8use move_binary_format::errors::{PartialVMError, PartialVMResult};
9use move_binary_format::{safe_assert, safe_assert_eq, safe_unwrap};
10use move_core_types::{
11    account_address::AccountAddress, gas_algebra::InternalGas, language_storage::TypeTag,
12    vm_status::StatusCode,
13};
14use move_vm_runtime::{
15    execution::{
16        Type,
17        values::{Value, Vector, VectorSpecialization},
18    },
19    natives::functions::NativeResult,
20};
21use move_vm_runtime::{native_charge_gas_early_exit, natives::functions::NativeContext};
22use smallvec::smallvec;
23use std::collections::VecDeque;
24use sui_types::{base_types::ObjectID, error::VMMemoryLimitExceededSubStatusCode};
25
26pub const NOT_SUPPORTED: u64 = 0;
27
28#[derive(Clone, Debug)]
29pub struct EventEmitCostParams {
30    pub event_emit_cost_base: InternalGas,
31    pub event_emit_value_size_derivation_cost_per_byte: InternalGas,
32    pub event_emit_tag_size_derivation_cost_per_byte: InternalGas,
33    pub event_emit_output_cost_per_byte: InternalGas,
34    pub event_emit_auth_stream_cost: Option<InternalGas>,
35}
36
37/***************************************************************************************************
38 * native fun emit
39 * Implementation of the Move native function `event::emit<T: copy + drop>(event: T)`
40 * Adds an event to the transaction's event log
41 *   gas cost: event_emit_cost_base                  |  covers various fixed costs in the oper
42 *              + event_emit_value_size_derivation_cost_per_byte * event_size     | derivation of size
43 *              + event_emit_tag_size_derivation_cost_per_byte * tag_size         | converting type
44 *              + event_emit_output_cost_per_byte * (tag_size + event_size)       | emitting the actual event
45 **************************************************************************************************/
46pub fn emit(
47    context: &mut NativeContext,
48    mut ty_args: Vec<Type>,
49    mut args: VecDeque<Value>,
50) -> PartialVMResult<NativeResult> {
51    debug_assert!(ty_args.len() == 1);
52    debug_assert!(args.len() == 1);
53
54    let ty = safe_unwrap!(ty_args.pop());
55    let event_value = safe_unwrap!(args.pop_back());
56    emit_impl(context, ty, event_value, None)
57}
58
59pub fn emit_authenticated_impl(
60    context: &mut NativeContext,
61    mut ty_args: Vec<Type>,
62    mut args: VecDeque<Value>,
63) -> PartialVMResult<NativeResult> {
64    debug_assert!(ty_args.len() == 2);
65    debug_assert!(args.len() == 3);
66
67    let cost = context.gas_used();
68    if !get_extension!(context, ObjectRuntime)?
69        .protocol_config
70        .enable_authenticated_event_streams()
71    {
72        return Ok(NativeResult::err(cost, NOT_SUPPORTED));
73    }
74
75    let event_ty = safe_unwrap!(ty_args.pop());
76    // This type is always sui::event::EventStreamHead
77    let stream_head_ty = safe_unwrap!(ty_args.pop());
78
79    let event_value = safe_unwrap!(args.pop_back());
80    let stream_id = safe_unwrap!(args.pop_back());
81    let accumulator_id = safe_unwrap!(args.pop_back());
82
83    emit_impl(
84        context,
85        event_ty,
86        event_value,
87        Some(StreamRef {
88            accumulator_id,
89            stream_id,
90            stream_head_ty,
91        }),
92    )
93}
94
95struct StreamRef {
96    // The pre-computed id of the accumulator object. This is a hash of
97    // stream_id + ty
98    accumulator_id: Value,
99    // The stream ID (the `stream_id` field of some EventStreamCap)
100    stream_id: Value,
101    // The type of the stream head. Should always be `sui::event::EventStreamHead`
102    stream_head_ty: Type,
103}
104
105fn emit_impl(
106    context: &mut NativeContext,
107    ty: Type,
108    event_value: Value,
109    stream_ref: Option<StreamRef>,
110) -> PartialVMResult<NativeResult> {
111    let event_emit_cost_params = get_extension!(context, NativesCostTable)?
112        .event_emit_cost_params
113        .clone();
114
115    native_charge_gas_early_exit!(context, event_emit_cost_params.event_emit_cost_base);
116
117    let event_value_size = abstract_size(
118        get_extension!(context, ObjectRuntime)?.protocol_config,
119        &event_value,
120    )?;
121
122    // Deriving event value size can be expensive due to recursion overhead
123    native_charge_gas_early_exit!(
124        context,
125        event_emit_cost_params.event_emit_value_size_derivation_cost_per_byte
126            * u64::from(event_value_size).into()
127    );
128
129    let tag = match context.type_to_type_tag(&ty)? {
130        TypeTag::Struct(s) => s,
131        _ => {
132            return Err(
133                PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
134                    .with_message("Sui verifier guarantees this is a struct".to_string()),
135            );
136        }
137    };
138    let tag_size = tag.abstract_size_for_gas_metering();
139
140    // Converting type to typetag be expensive due to recursion overhead
141    native_charge_gas_early_exit!(
142        context,
143        event_emit_cost_params.event_emit_tag_size_derivation_cost_per_byte
144            * u64::from(tag_size).into()
145    );
146
147    if stream_ref.is_some() {
148        native_charge_gas_early_exit!(
149            context,
150            safe_unwrap!(event_emit_cost_params.event_emit_auth_stream_cost)
151        );
152    }
153
154    // Get the type tag before getting the mutable reference to avoid borrowing issues
155    let stream_head_type_tag = if let Some(stream_ref) = &stream_ref {
156        Some(context.type_to_type_tag(&stream_ref.stream_head_ty)?)
157    } else {
158        None
159    };
160
161    let obj_runtime: &mut ObjectRuntime = get_extension_mut!(context)?;
162    let max_event_emit_size = obj_runtime.protocol_config.max_event_emit_size();
163    let ev_size = u64::from(tag_size + event_value_size);
164    // Check if the event size is within the limit
165    if ev_size > max_event_emit_size {
166        return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
167            .with_message(format!(
168                "Emitting event of size {ev_size} bytes. Limit is {max_event_emit_size} bytes."
169            ))
170            .with_sub_status(
171                VMMemoryLimitExceededSubStatusCode::EVENT_SIZE_LIMIT_EXCEEDED as u64,
172            ));
173    }
174
175    // Check that the size contribution of the event is within the total size limit
176    // This feature is guarded as its only present in some versions
177    if let Some(max_event_emit_size_total) = obj_runtime
178        .protocol_config
179        .max_event_emit_size_total_as_option()
180    {
181        let total_events_size = obj_runtime.state.total_events_size() + ev_size;
182        if total_events_size > max_event_emit_size_total {
183            return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
184                .with_message(format!(
185                    "Reached total event size of size {total_events_size} bytes. Limit is {max_event_emit_size_total} bytes."
186                ))
187                .with_sub_status(
188                    VMMemoryLimitExceededSubStatusCode::TOTAL_EVENT_SIZE_LIMIT_EXCEEDED as u64,
189                ));
190        }
191        obj_runtime.state.incr_total_events_size(ev_size);
192    }
193    // Emitting an event is cheap since its a vector push
194    native_charge_gas_early_exit!(
195        context,
196        event_emit_cost_params.event_emit_output_cost_per_byte * ev_size.into()
197    );
198
199    let obj_runtime: &mut ObjectRuntime = get_extension_mut!(context)?;
200
201    obj_runtime.emit_event(*tag, event_value)?;
202
203    if let Some(StreamRef {
204        accumulator_id,
205        stream_id,
206        stream_head_ty: _,
207    }) = stream_ref
208    {
209        let stream_id_addr: AccountAddress = safe_unwrap!(stream_id.value_as::<AccountAddress>());
210        let accumulator_id: ObjectID =
211            safe_unwrap!(accumulator_id.value_as::<AccountAddress>()).into();
212        let event_idx = obj_runtime
213            .state
214            .total_events_emitted()
215            .checked_sub(1)
216            .ok_or_else(|| {
217                PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
218                    .with_message("No events found after emitting authenticated event".to_string())
219            })?;
220        obj_runtime.emit_accumulator_event(
221            accumulator_id,
222            MoveAccumulatorAction::Merge,
223            stream_id_addr,
224            safe_unwrap!(stream_head_type_tag),
225            MoveAccumulatorValue::EventRef(event_idx),
226        )?;
227    }
228
229    Ok(NativeResult::ok(context.gas_used(), smallvec![]))
230}
231
232/// Get the all emitted events of type `T`, starting at the specified index
233pub fn num_events(
234    context: &mut NativeContext,
235    ty_args: Vec<Type>,
236    args: VecDeque<Value>,
237) -> PartialVMResult<NativeResult> {
238    safe_assert!(ty_args.is_empty());
239    safe_assert!(args.is_empty());
240    let object_runtime_ref: &ObjectRuntime = get_extension!(context)?;
241    let num_events = object_runtime_ref.state.events().len();
242    Ok(NativeResult::ok(
243        legacy_test_cost(),
244        smallvec![Value::u32(num_events as u32)],
245    ))
246}
247
248/// Get the all emitted events of type `T`, starting at the specified index
249pub fn get_events_by_type(
250    context: &mut NativeContext,
251    mut ty_args: Vec<Type>,
252    args: VecDeque<Value>,
253) -> PartialVMResult<NativeResult> {
254    safe_assert_eq!(ty_args.len(), 1);
255    let specified_ty = safe_unwrap!(ty_args.pop());
256    let specialization: VectorSpecialization = (&specified_ty).try_into()?;
257    safe_assert!(args.is_empty());
258    let object_runtime_ref: &ObjectRuntime = get_extension!(context)?;
259    let specified_type_tag = match context.type_to_type_tag(&specified_ty)? {
260        TypeTag::Struct(s) => *s,
261        _ => return Ok(NativeResult::ok(legacy_test_cost(), smallvec![])),
262    };
263    let matched_events = object_runtime_ref
264        .state
265        .events()
266        .iter()
267        .filter_map(|(tag, event)| {
268            if &specified_type_tag == tag {
269                Some(event.copy_value())
270            } else {
271                None
272            }
273        })
274        .collect::<Vec<_>>();
275    Ok(NativeResult::ok(
276        legacy_test_cost(),
277        smallvec![Vector::pack(specialization, matched_events)?],
278    ))
279}