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 DYNAMIC_FIELD_FIELD_STRUCT_NAME, DYNAMIC_FIELD_MODULE_NAME, DynamicFieldKey,
13 DynamicFieldObject, Field, UnboundedDynamicFieldID, 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 AccumulatorValue {
105 pub fn get_field_id(owner: SuiAddress, type_: &TypeTag) -> SuiResult<AccumulatorObjId> {
106 if !Balance::is_balance_type(type_) {
107 return Err(SuiErrorKind::TypeError {
108 error: "only Balance<T> is supported".to_string(),
109 }
110 .into());
111 }
112
113 let key = AccumulatorKey { owner };
114 Ok(AccumulatorObjId(
115 DynamicFieldKey(
116 SUI_ACCUMULATOR_ROOT_OBJECT_ID,
117 key,
118 AccumulatorKey::get_type_tag(std::slice::from_ref(type_)),
119 )
120 .into_unbounded_id()?
121 .as_object_id(),
122 ))
123 }
124
125 pub fn exists(
126 child_object_resolver: &dyn ChildObjectResolver,
127 version_bound: Option<SequenceNumber>,
128 owner: SuiAddress,
129 type_: &TypeTag,
130 ) -> SuiResult<bool> {
131 if !Balance::is_balance_type(type_) {
132 return Err(SuiErrorKind::TypeError {
133 error: "only Balance<T> is supported".to_string(),
134 }
135 .into());
136 }
137
138 let key = AccumulatorKey { owner };
139 DynamicFieldKey(
140 SUI_ACCUMULATOR_ROOT_OBJECT_ID,
141 key,
142 AccumulatorKey::get_type_tag(std::slice::from_ref(type_)),
143 )
144 .into_id_with_bound(version_bound.unwrap_or(SequenceNumber::MAX))?
145 .exists(child_object_resolver)
146 }
147
148 pub fn load_latest_by_id<T>(
149 object_store: &dyn ObjectStore,
150 id: AccumulatorObjId,
151 ) -> SuiResult<Option<(T, SequenceNumber)>>
152 where
153 T: Serialize + DeserializeOwned,
154 {
155 UnboundedDynamicFieldID::<AccumulatorKey>::new(SUI_ACCUMULATOR_ROOT_OBJECT_ID, id.0)
156 .load_object(object_store)
157 .map(|o| {
158 let version = o.0.version();
159 o.load_value::<T>().map(|v| (v, version))
160 })
161 .transpose()
162 }
163
164 pub fn load(
165 child_object_resolver: &dyn ChildObjectResolver,
166 version_bound: Option<SequenceNumber>,
167 owner: SuiAddress,
168 type_: &TypeTag,
169 ) -> SuiResult<Option<Self>> {
170 if !Balance::is_balance_type(type_) {
171 return Err(SuiErrorKind::TypeError {
172 error: "only Balance<T> is supported".to_string(),
173 }
174 .into());
175 }
176
177 let key = AccumulatorKey { owner };
178 let key_type_tag = AccumulatorKey::get_type_tag(std::slice::from_ref(type_));
179
180 let Some(value) = DynamicFieldKey(SUI_ACCUMULATOR_ROOT_OBJECT_ID, key, key_type_tag)
181 .into_id_with_bound(version_bound.unwrap_or(SequenceNumber::MAX))?
182 .load_object(child_object_resolver)?
183 .map(|o| o.load_value::<U128>())
184 .transpose()?
185 else {
186 return Ok(None);
187 };
188
189 Ok(Some(Self::U128(value)))
190 }
191
192 pub fn load_object(
193 child_object_resolver: &dyn ChildObjectResolver,
194 version_bound: Option<SequenceNumber>,
195 owner: SuiAddress,
196 type_: &TypeTag,
197 ) -> SuiResult<Option<Object>> {
198 let key = AccumulatorKey { owner };
199 let key_type_tag = AccumulatorKey::get_type_tag(std::slice::from_ref(type_));
200
201 Ok(
202 DynamicFieldKey(SUI_ACCUMULATOR_ROOT_OBJECT_ID, key, key_type_tag)
203 .into_id_with_bound(version_bound.unwrap_or(SequenceNumber::MAX))?
204 .load_object(child_object_resolver)?
205 .map(|o| o.as_object()),
206 )
207 }
208
209 pub fn create_for_testing(owner: SuiAddress, type_tag: TypeTag, balance: u64) -> Object {
210 let key = AccumulatorKey { owner };
211 let value = U128 {
212 value: balance as u128,
213 };
214
215 let field_key = DynamicFieldKey(
216 SUI_ACCUMULATOR_ROOT_OBJECT_ID,
217 key,
218 AccumulatorKey::get_type_tag(std::slice::from_ref(&type_tag)),
219 );
220 let field = field_key.into_field(value).unwrap();
221 let move_object = field
222 .into_move_object_unsafe_for_testing(SequenceNumber::new())
223 .unwrap();
224
225 Object::new_move(
226 move_object,
227 Owner::ObjectOwner(SUI_ACCUMULATOR_ROOT_ADDRESS.into()),
228 TransactionDigest::genesis_marker(),
229 )
230 }
231}
232
233pub fn stream_id_from_accumulator_event(ev: &AccumulatorEvent) -> Option<SuiAddress> {
235 if let TypeTag::Struct(tag) = &ev.write.address.ty
236 && tag.address == SUI_FRAMEWORK_ADDRESS
237 && tag.module.as_ident_str() == ACCUMULATOR_SETTLEMENT_MODULE
238 && tag.name.as_ident_str() == ACCUMULATOR_SETTLEMENT_EVENT_STREAM_HEAD
239 {
240 return Some(ev.write.address.address);
241 }
242 None
243}
244
245impl TryFrom<&MoveObject> for AccumulatorValue {
246 type Error = SuiError;
247 fn try_from(value: &MoveObject) -> Result<Self, Self::Error> {
248 value
249 .type_()
250 .is_balance_accumulator_field()
251 .then(|| {
252 value
253 .to_rust::<Field<AccumulatorKey, U128>>()
254 .map(|f| f.value)
255 })
256 .flatten()
257 .map(Self::U128)
258 .ok_or_else(|| {
259 SuiErrorKind::DynamicFieldReadError(format!(
260 "Dynamic field {:?} is not a AccumulatorValue",
261 value.id()
262 ))
263 .into()
264 })
265 }
266}
267
268pub fn update_account_balance_for_testing(account_object: &mut Object, balance_change: i128) {
269 let current_balance_field = DynamicFieldObject::<AccumulatorKey>::new(account_object.clone())
270 .load_field::<U128>()
271 .unwrap();
272
273 let current_balance = current_balance_field.value.value;
274
275 assert!(current_balance <= i128::MAX as u128);
276 assert!(current_balance as i128 >= balance_change.abs());
277
278 let new_balance = U128 {
279 value: (current_balance as i128 + balance_change) as u128,
280 };
281
282 let new_field = serialize_dynamic_field(
283 ¤t_balance_field.id,
284 ¤t_balance_field.name,
285 new_balance,
286 )
287 .unwrap();
288
289 let move_object = account_object.data.try_as_move_mut().unwrap();
290 move_object.set_contents_unsafe(new_field);
291}
292
293pub(crate) fn is_balance_accumulator_field(s: &StructTag) -> bool {
295 s.address == SUI_FRAMEWORK_ADDRESS
296 && s.module.as_ident_str() == DYNAMIC_FIELD_MODULE_NAME
297 && s.name.as_ident_str() == DYNAMIC_FIELD_FIELD_STRUCT_NAME
298 && s.type_params.len() == 2
299 && is_accumulator_key_balance(&s.type_params[0])
300 && is_accumulator_u128(&s.type_params[1])
301}
302
303pub(crate) fn is_accumulator_key_balance(t: &TypeTag) -> bool {
305 if let TypeTag::Struct(s) = t {
306 s.address == SUI_FRAMEWORK_ADDRESS
307 && s.module.as_ident_str() == ACCUMULATOR_ROOT_MODULE
308 && s.name.as_ident_str() == ACCUMULATOR_KEY_TYPE
309 && s.type_params.len() == 1
310 && Balance::is_balance_type(&s.type_params[0])
311 } else {
312 false
313 }
314}
315
316pub(crate) fn is_accumulator_u128(t: &TypeTag) -> bool {
318 if let TypeTag::Struct(s) = t {
319 s.address == SUI_FRAMEWORK_ADDRESS
320 && s.module.as_ident_str() == ACCUMULATOR_ROOT_MODULE
321 && s.name.as_ident_str() == ACCUMULATOR_U128_TYPE
322 && s.type_params.is_empty()
323 } else {
324 false
325 }
326}
327
328pub(crate) fn extract_balance_type_from_field(s: &StructTag) -> Option<TypeTag> {
330 if s.type_params.len() != 2 {
331 return None;
332 }
333
334 if let TypeTag::Struct(key_struct) = &s.type_params[0]
335 && key_struct.type_params.len() == 1
336 && let TypeTag::Struct(balance_struct) = &key_struct.type_params[0]
337 && Balance::is_balance(balance_struct)
338 && balance_struct.type_params.len() == 1
339 {
340 return Some(balance_struct.type_params[0].clone());
341 }
342 None
343}
344
345#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
348pub struct EventStreamHead {
349 pub mmr: Vec<U256>,
351 pub checkpoint_seq: u64,
353 pub num_events: u64,
355}
356
357impl Default for EventStreamHead {
358 fn default() -> Self {
359 Self::new()
360 }
361}
362
363impl EventStreamHead {
364 pub fn new() -> Self {
365 Self {
366 mmr: vec![],
367 checkpoint_seq: 0,
368 num_events: 0,
369 }
370 }
371
372 pub fn num_events(&self) -> u64 {
373 self.num_events
374 }
375
376 pub fn checkpoint_seq(&self) -> u64 {
377 self.checkpoint_seq
378 }
379
380 pub fn mmr(&self) -> &Vec<U256> {
381 &self.mmr
382 }
383}
384
385#[derive(Debug, Serialize, Clone, PartialEq, Eq)]
386pub struct EventCommitment {
387 pub checkpoint_seq: u64,
388 pub transaction_idx: u64,
389 pub event_idx: u64,
390 pub digest: Digest,
391}
392
393impl EventCommitment {
394 pub fn new(checkpoint_seq: u64, transaction_idx: u64, event_idx: u64, digest: Digest) -> Self {
395 Self {
396 checkpoint_seq,
397 transaction_idx,
398 event_idx,
399 digest,
400 }
401 }
402}
403
404impl PartialOrd for EventCommitment {
405 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
406 Some(self.cmp(other))
407 }
408}
409
410impl Ord for EventCommitment {
411 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
412 (self.checkpoint_seq, self.transaction_idx, self.event_idx).cmp(&(
413 other.checkpoint_seq,
414 other.transaction_idx,
415 other.event_idx,
416 ))
417 }
418}
419
420pub fn build_event_merkle_root(events: &[EventCommitment]) -> Digest {
421 use fastcrypto::hash::Blake2b256;
422 use fastcrypto::merkle::MerkleTree;
423
424 debug_assert!(
425 events.windows(2).all(|w| w[0] <= w[1]),
426 "Events must be ordered by (checkpoint_seq, transaction_idx, event_idx)"
427 );
428
429 let merkle_tree = MerkleTree::<Blake2b256>::build_from_unserialized(events.to_vec())
430 .expect("failed to serialize event commitments for merkle root");
431 let root_node = merkle_tree.root();
432 let root_digest = root_node.bytes();
433 Digest::new(root_digest)
434}