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