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::{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
34pub 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 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 accumulator_id: Value,
96 stream_id: Value,
98 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 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 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 event_emit_cost_params.event_emit_auth_stream_cost.unwrap()
150 );
151 }
152
153 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 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 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 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
230pub 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
246pub 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}