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