1use 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
37pub 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 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 accumulator_id: Value,
99 stream_id: Value,
101 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 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 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 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 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 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 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
232pub 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
248pub 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}