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