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