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