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