1use crate::{
5 MoveTypeTagTrait, MoveTypeTagTraitGeneric, SUI_ACCUMULATOR_ROOT_ADDRESS,
6 SUI_ACCUMULATOR_ROOT_OBJECT_ID, SUI_FRAMEWORK_ADDRESS, SUI_FRAMEWORK_PACKAGE_ID,
7 accumulator_event::AccumulatorEvent,
8 balance::Balance,
9 base_types::{ObjectID, SequenceNumber, SuiAddress},
10 digests::{Digest, TransactionDigest},
11 dynamic_field::{
12 BoundedDynamicFieldID, DYNAMIC_FIELD_FIELD_STRUCT_NAME, DYNAMIC_FIELD_MODULE_NAME,
13 DynamicFieldKey, DynamicFieldObject, Field, serialize_dynamic_field,
14 },
15 error::{SuiError, SuiErrorKind, SuiResult},
16 object::{MoveObject, Object, Owner},
17 storage::{ObjectStore, RuntimeObjectResolver},
18};
19use move_core_types::{
20 account_address::AccountAddress,
21 ident_str,
22 identifier::IdentStr,
23 language_storage::{StructTag, TypeTag},
24 u256::U256,
25};
26use serde::{Deserialize, Serialize, de::DeserializeOwned};
27use sui_protocol_config::ProtocolConfig;
28
29pub const ACCUMULATOR_ROOT_MODULE: &IdentStr = ident_str!("accumulator");
30pub const ACCUMULATOR_METADATA_MODULE: &IdentStr = ident_str!("accumulator_metadata");
31pub const ACCUMULATOR_SETTLEMENT_MODULE: &IdentStr = ident_str!("accumulator_settlement");
32pub const ACCUMULATOR_SETTLEMENT_EVENT_STREAM_HEAD: &IdentStr = ident_str!("EventStreamHead");
33pub const ACCUMULATOR_ROOT_CREATE_FUNC: &IdentStr = ident_str!("create");
34pub const ACCUMULATOR_ROOT_SETTLE_U128_FUNC: &IdentStr = ident_str!("settle_u128");
35pub const ACCUMULATOR_ROOT_SETTLEMENT_PROLOGUE_FUNC: &IdentStr = ident_str!("settlement_prologue");
36pub const ACCUMULATOR_ROOT_SETTLEMENT_SETTLE_EVENTS_FUNC: &IdentStr = ident_str!("settle_events");
37
38const ACCUMULATOR_KEY_TYPE: &IdentStr = ident_str!("Key");
39const ACCUMULATOR_U128_TYPE: &IdentStr = ident_str!("U128");
40
41pub const SETTLEMENT_MAX_TYPE_INSTANTIATION_NODES: u64 = 512;
42
43pub fn is_settle_u128_call(
44 module_address: &AccountAddress,
45 module: &IdentStr,
46 function: &IdentStr,
47) -> bool {
48 *module_address == SUI_FRAMEWORK_ADDRESS
49 && module == ACCUMULATOR_SETTLEMENT_MODULE
50 && function == ACCUMULATOR_ROOT_SETTLE_U128_FUNC
51}
52
53pub fn check_accumulator_type_bounds(config: &ProtocolConfig, ty: &TypeTag) -> bool {
54 match config.max_accumulator_type_nodes_as_option() {
55 Some(max) => ty.node_count() <= max,
56 None => true,
57 }
58}
59
60pub fn get_accumulator_root_obj_initial_shared_version(
61 object_store: &dyn ObjectStore,
62) -> SuiResult<Option<SequenceNumber>> {
63 Ok(object_store
64 .get_object(&SUI_ACCUMULATOR_ROOT_OBJECT_ID)
65 .map(|obj| match obj.owner {
66 Owner::Shared {
67 initial_shared_version,
68 } => initial_shared_version,
69 _ => unreachable!("Accumulator root object must be shared"),
70 }))
71}
72
73#[derive(Debug, Serialize, Deserialize, Clone)]
76pub struct AccumulatorKey {
77 pub owner: SuiAddress,
78}
79
80impl MoveTypeTagTraitGeneric for AccumulatorKey {
81 fn get_type_tag(type_params: &[TypeTag]) -> TypeTag {
82 TypeTag::Struct(Box::new(StructTag {
83 address: SUI_FRAMEWORK_PACKAGE_ID.into(),
84 module: ACCUMULATOR_ROOT_MODULE.to_owned(),
85 name: ACCUMULATOR_KEY_TYPE.to_owned(),
86 type_params: type_params.to_vec(),
87 }))
88 }
89}
90
91#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
92pub enum AccumulatorValue {
93 U128(U128),
94}
95
96#[derive(Default, Serialize, Deserialize, Debug, Eq, PartialEq)]
97pub struct U128 {
98 pub value: u128,
99}
100
101impl MoveTypeTagTrait for U128 {
102 fn get_type_tag() -> TypeTag {
103 TypeTag::Struct(Box::new(StructTag {
104 address: SUI_FRAMEWORK_ADDRESS,
105 module: ACCUMULATOR_ROOT_MODULE.to_owned(),
106 name: ACCUMULATOR_U128_TYPE.to_owned(),
107 type_params: vec![],
108 }))
109 }
110}
111
112#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash)]
114pub struct AccumulatorObjId(ObjectID);
115
116impl AccumulatorObjId {
117 pub fn new_unchecked(id: ObjectID) -> Self {
118 Self(id)
119 }
120
121 pub fn inner(&self) -> &ObjectID {
122 &self.0
123 }
124}
125
126impl std::fmt::Display for AccumulatorObjId {
127 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128 write!(f, "{}", self.0)
129 }
130}
131
132impl AccumulatorValue {
133 pub fn as_u128(&self) -> Option<u128> {
134 match self {
135 AccumulatorValue::U128(value) => Some(value.value),
136 }
137 }
138
139 pub fn get_field_id(owner: SuiAddress, type_: &TypeTag) -> SuiResult<AccumulatorObjId> {
140 if !Balance::is_balance_type(type_) {
141 return Err(SuiErrorKind::TypeError {
142 error: "only Balance<T> is supported".to_string(),
143 }
144 .into());
145 }
146
147 let key = AccumulatorKey { owner };
148 Ok(AccumulatorObjId(
149 DynamicFieldKey(
150 SUI_ACCUMULATOR_ROOT_OBJECT_ID,
151 key,
152 AccumulatorKey::get_type_tag(std::slice::from_ref(type_)),
153 )
154 .into_unbounded_id()?
155 .as_object_id(),
156 ))
157 }
158
159 pub fn exists(
160 runtime_object_resolver: &dyn RuntimeObjectResolver,
161 version_bound: Option<SequenceNumber>,
162 owner: SuiAddress,
163 type_: &TypeTag,
164 ) -> SuiResult<bool> {
165 if !Balance::is_balance_type(type_) {
166 return Err(SuiErrorKind::TypeError {
167 error: "only Balance<T> is supported".to_string(),
168 }
169 .into());
170 }
171
172 let key = AccumulatorKey { owner };
173 DynamicFieldKey(
174 SUI_ACCUMULATOR_ROOT_OBJECT_ID,
175 key,
176 AccumulatorKey::get_type_tag(std::slice::from_ref(type_)),
177 )
178 .into_id_with_bound(version_bound.unwrap_or(SequenceNumber::MAX))?
179 .exists(runtime_object_resolver)
180 }
181
182 pub fn load_by_id<T>(
183 runtime_object_resolver: &dyn RuntimeObjectResolver,
184 version_bound: Option<SequenceNumber>,
185 id: AccumulatorObjId,
186 ) -> SuiResult<Option<T>>
187 where
188 T: Serialize + DeserializeOwned,
189 {
190 BoundedDynamicFieldID::<AccumulatorKey>::new(
191 SUI_ACCUMULATOR_ROOT_OBJECT_ID,
192 id.0,
193 version_bound.unwrap_or(SequenceNumber::MAX),
194 )
195 .load_object(runtime_object_resolver)?
196 .map(|o| o.load_value::<T>())
197 .transpose()
198 }
199
200 pub fn load(
201 runtime_object_resolver: &dyn RuntimeObjectResolver,
202 version_bound: Option<SequenceNumber>,
203 owner: SuiAddress,
204 type_: &TypeTag,
205 ) -> SuiResult<Option<Self>> {
206 if !Balance::is_balance_type(type_) {
207 return Err(SuiErrorKind::TypeError {
208 error: "only Balance<T> is supported".to_string(),
209 }
210 .into());
211 }
212
213 let key = AccumulatorKey { owner };
214 let key_type_tag = AccumulatorKey::get_type_tag(std::slice::from_ref(type_));
215
216 let Some(value) = DynamicFieldKey(SUI_ACCUMULATOR_ROOT_OBJECT_ID, key, key_type_tag)
217 .into_id_with_bound(version_bound.unwrap_or(SequenceNumber::MAX))?
218 .load_object(runtime_object_resolver)?
219 .map(|o| o.load_value::<U128>())
220 .transpose()?
221 else {
222 return Ok(None);
223 };
224
225 Ok(Some(Self::U128(value)))
226 }
227
228 pub fn load_object(
229 runtime_object_resolver: &dyn RuntimeObjectResolver,
230 version_bound: Option<SequenceNumber>,
231 owner: SuiAddress,
232 type_: &TypeTag,
233 ) -> SuiResult<Option<Object>> {
234 let key = AccumulatorKey { owner };
235 let key_type_tag = AccumulatorKey::get_type_tag(std::slice::from_ref(type_));
236
237 Ok(
238 DynamicFieldKey(SUI_ACCUMULATOR_ROOT_OBJECT_ID, key, key_type_tag)
239 .into_id_with_bound(version_bound.unwrap_or(SequenceNumber::MAX))?
240 .load_object(runtime_object_resolver)?
241 .map(|o| o.into_object()),
242 )
243 }
244
245 pub fn load_object_by_id(
246 runtime_object_resolver: &dyn RuntimeObjectResolver,
247 version_bound: Option<SequenceNumber>,
248 id: ObjectID,
249 ) -> SuiResult<Option<Object>> {
250 Ok(BoundedDynamicFieldID::<AccumulatorKey>::new(
251 SUI_ACCUMULATOR_ROOT_OBJECT_ID,
252 id,
253 version_bound.unwrap_or(SequenceNumber::MAX),
254 )
255 .load_object(runtime_object_resolver)?
256 .map(|o| o.into_object()))
257 }
258
259 pub fn create_for_testing(owner: SuiAddress, type_tag: TypeTag, balance: u64) -> Object {
260 let key = AccumulatorKey { owner };
261 let value = U128 {
262 value: balance as u128,
263 };
264
265 let field_key = DynamicFieldKey(
266 SUI_ACCUMULATOR_ROOT_OBJECT_ID,
267 key,
268 AccumulatorKey::get_type_tag(std::slice::from_ref(&type_tag)),
269 );
270 let field = field_key.into_field(value).unwrap();
271 let move_object = field
272 .into_move_object_unsafe_for_testing(SequenceNumber::new())
273 .unwrap();
274
275 Object::new_move(
276 move_object,
277 Owner::ObjectOwner(SUI_ACCUMULATOR_ROOT_ADDRESS.into()),
278 TransactionDigest::genesis_marker(),
279 )
280 }
281}
282
283pub fn stream_id_from_accumulator_event(ev: &AccumulatorEvent) -> Option<SuiAddress> {
285 if let TypeTag::Struct(tag) = &ev.write.address.ty
286 && tag.address == SUI_FRAMEWORK_ADDRESS
287 && tag.module.as_ident_str() == ACCUMULATOR_SETTLEMENT_MODULE
288 && tag.name.as_ident_str() == ACCUMULATOR_SETTLEMENT_EVENT_STREAM_HEAD
289 {
290 return Some(ev.write.address.address);
291 }
292 None
293}
294
295impl TryFrom<&MoveObject> for AccumulatorValue {
296 type Error = SuiError;
297 fn try_from(value: &MoveObject) -> Result<Self, Self::Error> {
298 let (_key, value): (AccumulatorKey, AccumulatorValue) = value.try_into()?;
299 Ok(value)
300 }
301}
302
303impl TryFrom<&MoveObject> for (AccumulatorKey, AccumulatorValue) {
304 type Error = SuiError;
305 fn try_from(value: &MoveObject) -> Result<Self, Self::Error> {
306 value
307 .type_()
308 .is_balance_accumulator_field()
309 .then(|| value.to_rust::<Field<AccumulatorKey, U128>>())
310 .flatten()
311 .map(|f| (f.name, AccumulatorValue::U128(f.value)))
312 .ok_or_else(|| {
313 SuiErrorKind::DynamicFieldReadError(format!(
314 "Dynamic field {:?} is not a AccumulatorValue",
315 value.id()
316 ))
317 .into()
318 })
319 }
320}
321
322pub fn update_account_balance_for_testing(account_object: &mut Object, balance_change: i128) {
323 let current_balance_field = DynamicFieldObject::<AccumulatorKey>::new(account_object.clone())
324 .load_field::<U128>()
325 .unwrap();
326
327 let current_balance = current_balance_field.value.value;
328
329 assert!(current_balance <= i128::MAX as u128);
330 assert!(current_balance as i128 >= balance_change.abs());
331
332 let new_balance = U128 {
333 value: (current_balance as i128 + balance_change) as u128,
334 };
335
336 let new_field = serialize_dynamic_field(
337 ¤t_balance_field.id,
338 ¤t_balance_field.name,
339 new_balance,
340 )
341 .unwrap();
342
343 let move_object = account_object.data.try_as_move_mut().unwrap();
344 move_object.set_contents_unsafe(new_field);
345}
346
347pub(crate) fn accumulator_value_balance_type_maybe(s: &StructTag) -> Option<TypeTag> {
348 if s.address == SUI_FRAMEWORK_ADDRESS
349 && s.module.as_ident_str() == DYNAMIC_FIELD_MODULE_NAME
350 && s.name.as_ident_str() == DYNAMIC_FIELD_FIELD_STRUCT_NAME
351 && s.type_params.len() == 2
352 && let Some(key_type) = accumulator_key_type_maybe(&s.type_params[0])
353 && is_accumulator_u128(&s.type_params[1])
354 {
355 Balance::maybe_get_balance_type_param(&key_type)
356 } else {
357 None
358 }
359}
360
361pub(crate) fn accumulator_key_type_maybe(t: &TypeTag) -> Option<TypeTag> {
363 if let TypeTag::Struct(s) = t
364 && s.address == SUI_FRAMEWORK_ADDRESS
365 && s.module.as_ident_str() == ACCUMULATOR_ROOT_MODULE
366 && s.name.as_ident_str() == ACCUMULATOR_KEY_TYPE
367 && s.type_params.len() == 1
368 {
369 Some(s.type_params[0].clone())
370 } else {
371 None
372 }
373}
374
375pub(crate) fn is_accumulator_u128(t: &TypeTag) -> bool {
377 if let TypeTag::Struct(s) = t {
378 s.address == SUI_FRAMEWORK_ADDRESS
379 && s.module.as_ident_str() == ACCUMULATOR_ROOT_MODULE
380 && s.name.as_ident_str() == ACCUMULATOR_U128_TYPE
381 && s.type_params.is_empty()
382 } else {
383 false
384 }
385}
386
387#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
390pub struct EventStreamHead {
391 pub mmr: Vec<U256>,
393 pub checkpoint_seq: u64,
395 pub num_events: u64,
397}
398
399impl Default for EventStreamHead {
400 fn default() -> Self {
401 Self::new()
402 }
403}
404
405impl EventStreamHead {
406 pub fn new() -> Self {
407 Self {
408 mmr: vec![],
409 checkpoint_seq: 0,
410 num_events: 0,
411 }
412 }
413
414 pub fn num_events(&self) -> u64 {
415 self.num_events
416 }
417
418 pub fn checkpoint_seq(&self) -> u64 {
419 self.checkpoint_seq
420 }
421
422 pub fn mmr(&self) -> &Vec<U256> {
423 &self.mmr
424 }
425}
426
427pub fn derive_event_stream_head_object_id(stream_id: SuiAddress) -> SuiResult<ObjectID> {
428 let key = AccumulatorKey { owner: stream_id };
429
430 let value_type_tag = TypeTag::Struct(Box::new(StructTag {
431 address: SUI_FRAMEWORK_ADDRESS,
432 module: ACCUMULATOR_SETTLEMENT_MODULE.to_owned(),
433 name: ACCUMULATOR_SETTLEMENT_EVENT_STREAM_HEAD.to_owned(),
434 type_params: vec![],
435 }));
436
437 let key_type_tag = AccumulatorKey::get_type_tag(&[value_type_tag]);
438
439 DynamicFieldKey(SUI_ACCUMULATOR_ROOT_OBJECT_ID, key, key_type_tag)
440 .into_unbounded_id()
441 .map(|id| id.as_object_id())
442}
443
444#[derive(Debug, Serialize, Clone, PartialEq, Eq)]
445pub struct EventCommitment {
446 pub checkpoint_seq: u64,
447 pub transaction_idx: u64,
448 pub event_idx: u64,
449 pub digest: Digest,
450}
451
452impl EventCommitment {
453 pub fn new(checkpoint_seq: u64, transaction_idx: u64, event_idx: u64, digest: Digest) -> Self {
454 Self {
455 checkpoint_seq,
456 transaction_idx,
457 event_idx,
458 digest,
459 }
460 }
461}
462
463impl PartialOrd for EventCommitment {
464 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
465 Some(self.cmp(other))
466 }
467}
468
469impl Ord for EventCommitment {
470 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
471 (self.checkpoint_seq, self.transaction_idx, self.event_idx).cmp(&(
472 other.checkpoint_seq,
473 other.transaction_idx,
474 other.event_idx,
475 ))
476 }
477}
478
479pub fn build_event_merkle_root(events: &[EventCommitment]) -> Digest {
480 use fastcrypto::hash::Blake2b256;
481 use fastcrypto::merkle::MerkleTree;
482
483 debug_assert!(
484 events.windows(2).all(|w| w[0] <= w[1]),
485 "Events must be ordered by (checkpoint_seq, transaction_idx, event_idx)"
486 );
487
488 let merkle_tree = MerkleTree::<Blake2b256>::build_from_unserialized(events.to_vec())
489 .expect("failed to serialize event commitments for merkle root");
490 let root_node = merkle_tree.root();
491 let root_digest = root_node.bytes();
492 Digest::new(root_digest)
493}