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