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