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_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
36pub 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 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 accumulator_id: Value,
98 stream_id: Value,
100 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 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 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 event_emit_cost_params.event_emit_auth_stream_cost.unwrap()
152 );
153 }
154
155 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 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 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 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
232pub 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
248pub 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}