1use std::collections::{BTreeMap, HashMap};
5
6use itertools::Itertools;
7use move_core_types::ident_str;
8use move_core_types::u256::U256;
9use mysten_common::fatal;
10use sui_protocol_config::ProtocolConfig;
11use sui_types::accumulator_event::AccumulatorEvent;
12use sui_types::accumulator_root::{
13 ACCUMULATOR_ROOT_SETTLE_U128_FUNC, ACCUMULATOR_ROOT_SETTLEMENT_PROLOGUE_FUNC,
14 ACCUMULATOR_SETTLEMENT_MODULE, AccumulatorObjId, EventCommitment, build_event_merkle_root,
15};
16use sui_types::balance::{BALANCE_MODULE_NAME, BALANCE_STRUCT_NAME};
17use sui_types::base_types::SequenceNumber;
18
19use sui_types::accumulator_root::ACCUMULATOR_METADATA_MODULE;
20use sui_types::digests::Digest;
21use sui_types::effects::{
22 AccumulatorAddress, AccumulatorOperation, AccumulatorValue, AccumulatorWriteV1, IDOperation,
23 TransactionEffects, TransactionEffectsAPI,
24};
25use sui_types::programmable_transaction_builder::ProgrammableTransactionBuilder;
26use sui_types::transaction::{
27 Argument, CallArg, ObjectArg, SharedObjectMutability, TransactionKind,
28};
29use sui_types::{
30 SUI_ACCUMULATOR_ROOT_OBJECT_ID, SUI_FRAMEWORK_ADDRESS, SUI_FRAMEWORK_PACKAGE_ID, TypeTag,
31};
32
33use crate::execution_cache::TransactionCacheRead;
34
35pub mod funds_read;
37pub mod balances;
39pub mod coin_reservations;
40pub mod object_funds_checker;
41
42#[derive(Debug, Copy, Clone)]
49enum MergedValue {
50 SumU128(u128),
51 SumU128U128(u128, u128),
52 EventDigest(Digest, u64),
54}
55
56enum ClassifiedType {
57 Balance,
58 Unknown,
59}
60
61impl ClassifiedType {
62 fn classify(ty: &TypeTag) -> Self {
63 let TypeTag::Struct(struct_tag) = ty else {
64 return Self::Unknown;
65 };
66
67 if struct_tag.address == SUI_FRAMEWORK_ADDRESS
68 && struct_tag.module.as_ident_str() == BALANCE_MODULE_NAME
69 && struct_tag.name.as_ident_str() == BALANCE_STRUCT_NAME
70 {
71 return Self::Balance;
72 }
73
74 Self::Unknown
75 }
76}
77
78impl MergedValue {
79 fn add_move_call(
80 merge: Self,
81 split: Self,
82 root: Argument,
83 address: &AccumulatorAddress,
84 checkpoint_seq: u64,
85 builder: &mut ProgrammableTransactionBuilder,
86 ) {
87 let ty = ClassifiedType::classify(&address.ty);
88 let address_arg = builder.pure(address.address).unwrap();
89
90 match (ty, merge, split) {
91 (
92 ClassifiedType::Balance,
93 MergedValue::SumU128(merge_amount),
94 MergedValue::SumU128(split_amount),
95 ) => {
96 let (merge_amount, split_amount) = if merge_amount >= split_amount {
98 (merge_amount - split_amount, 0)
99 } else {
100 (0, split_amount - merge_amount)
101 };
102
103 if merge_amount != 0 || split_amount != 0 {
104 let merge_amount = builder.pure(merge_amount).unwrap();
105 let split_amount = builder.pure(split_amount).unwrap();
106 builder.programmable_move_call(
107 SUI_FRAMEWORK_PACKAGE_ID,
108 ACCUMULATOR_SETTLEMENT_MODULE.into(),
109 ACCUMULATOR_ROOT_SETTLE_U128_FUNC.into(),
110 vec![address.ty.clone()],
111 vec![root, address_arg, merge_amount, split_amount],
112 );
113 }
114 }
115 (_, MergedValue::SumU128U128(_v1, _v2), MergedValue::SumU128U128(_w1, _w2)) => todo!(),
116 (_, MergedValue::EventDigest(digest, event_count), MergedValue::EventDigest(_, _)) => {
117 let args = vec![
118 root,
119 builder.pure(address.address).unwrap(),
120 builder
121 .pure(U256::from_le_bytes(&digest.into_inner()))
122 .unwrap(),
123 builder.pure(event_count).unwrap(),
124 builder.pure(checkpoint_seq).unwrap(),
125 ];
126 builder.programmable_move_call(
127 SUI_FRAMEWORK_PACKAGE_ID,
128 ACCUMULATOR_SETTLEMENT_MODULE.into(),
129 sui_types::accumulator_root::ACCUMULATOR_ROOT_SETTLEMENT_SETTLE_EVENTS_FUNC
130 .into(),
131 vec![],
132 args,
133 );
134 }
135 _ => fatal!("invalid merge {:?} {:?}", merge, split),
136 }
137 }
138}
139
140impl From<MergedValueIntermediate> for MergedValue {
141 fn from(value: MergedValueIntermediate) -> Self {
142 match value {
143 MergedValueIntermediate::SumU128(v) => MergedValue::SumU128(v),
144 MergedValueIntermediate::SumU128U128(v1, v2) => MergedValue::SumU128U128(v1, v2),
145 MergedValueIntermediate::Events(events) => {
146 let event_count = events.len() as u64;
147 MergedValue::EventDigest(build_event_merkle_root(&events), event_count)
148 }
149 }
150 }
151}
152
153#[derive(Debug, Clone)]
163enum MergedValueIntermediate {
164 SumU128(u128),
165 SumU128U128(u128, u128),
166 Events(Vec<EventCommitment>),
167}
168
169impl MergedValueIntermediate {
170 fn zero(value: &AccumulatorValue) -> Self {
172 match value {
173 AccumulatorValue::Integer(_) => Self::SumU128(0),
174 AccumulatorValue::IntegerTuple(_, _) => Self::SumU128U128(0, 0),
175 AccumulatorValue::EventDigest(_) => Self::Events(vec![]),
176 }
177 }
178
179 fn accumulate_into(
180 &mut self,
181 value: AccumulatorValue,
182 checkpoint_seq: u64,
183 transaction_idx: u64,
184 ) {
185 match (self, value) {
186 (Self::SumU128(v1), AccumulatorValue::Integer(v2)) => *v1 += v2 as u128,
187 (Self::SumU128U128(v1, v2), AccumulatorValue::IntegerTuple(w1, w2)) => {
188 *v1 += w1 as u128;
189 *v2 += w2 as u128;
190 }
191 (Self::Events(commitments), AccumulatorValue::EventDigest(event_digests)) => {
192 for (event_idx, digest) in event_digests {
193 commitments.push(EventCommitment::new(
194 checkpoint_seq,
195 transaction_idx,
196 event_idx,
197 digest,
198 ));
199 }
200 }
201 _ => {
202 fatal!("invalid merge");
203 }
204 }
205 }
206}
207
208struct Update {
209 merge: MergedValueIntermediate,
210 split: MergedValueIntermediate,
211 input_sui: u64,
215 output_sui: u64,
216}
217
218pub(crate) struct AccumulatorSettlementTxBuilder {
219 updates: BTreeMap<AccumulatorObjId, Update>,
221 addresses: HashMap<AccumulatorObjId, AccumulatorAddress>,
223}
224
225impl AccumulatorSettlementTxBuilder {
226 pub fn new(
227 cache: Option<&dyn TransactionCacheRead>,
228 ckpt_effects: &[TransactionEffects],
229 checkpoint_seq: u64,
230 tx_index_offset: u64,
231 ) -> Self {
232 let mut updates = BTreeMap::<_, _>::new();
233
234 let mut addresses = HashMap::<_, _>::new();
235
236 for (tx_index, effect) in ckpt_effects.iter().enumerate() {
237 let tx = effect.transaction_digest();
238 let events = match cache.and_then(|c| c.take_accumulator_events(tx)) {
243 Some(events) => events,
244 None => effect.accumulator_events(),
245 };
246
247 for event in events {
248 let (event_input_sui, event_output_sui) = event.total_sui_in_event();
251
252 let AccumulatorEvent {
253 accumulator_obj,
254 write:
255 AccumulatorWriteV1 {
256 operation,
257 value,
258 address,
259 },
260 } = event;
261
262 if let Some(prev) = addresses.insert(accumulator_obj, address.clone()) {
263 debug_assert_eq!(prev, address);
264 }
265
266 let entry = updates.entry(accumulator_obj).or_insert_with(|| {
267 let zero = MergedValueIntermediate::zero(&value);
268 Update {
269 merge: zero.clone(),
270 split: zero,
271 input_sui: 0,
272 output_sui: 0,
273 }
274 });
275
276 entry.input_sui += event_output_sui;
278 entry.output_sui += event_input_sui;
279
280 match operation {
281 AccumulatorOperation::Merge => {
282 entry.merge.accumulate_into(
283 value,
284 checkpoint_seq,
285 tx_index as u64 + tx_index_offset,
286 );
287 }
288 AccumulatorOperation::Split => {
289 entry.split.accumulate_into(
290 value,
291 checkpoint_seq,
292 tx_index as u64 + tx_index_offset,
293 );
294 }
295 }
296 }
297 }
298
299 Self { updates, addresses }
300 }
301
302 pub fn num_updates(&self) -> usize {
303 self.updates.len()
304 }
305
306 pub fn collect_funds_changes(&self) -> BTreeMap<AccumulatorObjId, i128> {
309 self.updates
310 .iter()
311 .filter_map(|(object_id, update)| match (&update.merge, &update.split) {
312 (
313 MergedValueIntermediate::SumU128(merge),
314 MergedValueIntermediate::SumU128(split),
315 ) => Some((*object_id, *merge as i128 - *split as i128)),
316 _ => None,
317 })
318 .collect()
319 }
320
321 pub fn build_tx(
323 self,
324 protocol_config: &ProtocolConfig,
325 epoch: u64,
326 accumulator_root_obj_initial_shared_version: SequenceNumber,
327 checkpoint_height: u64,
328 checkpoint_seq: u64,
329 ) -> Vec<TransactionKind> {
330 let Self { updates, addresses } = self;
331
332 let build_one_settlement_txn = |idx: u64, updates: &mut Vec<(AccumulatorObjId, Update)>| {
333 let (total_input_sui, total_output_sui) =
334 updates
335 .iter()
336 .fold((0, 0), |(acc_input, acc_output), (_, update)| {
337 (acc_input + update.input_sui, acc_output + update.output_sui)
338 });
339
340 Self::build_one_settlement_txn(
341 &addresses,
342 epoch,
343 idx,
344 checkpoint_height,
345 accumulator_root_obj_initial_shared_version,
346 updates.drain(..),
347 total_input_sui,
348 total_output_sui,
349 checkpoint_seq,
350 )
351 };
352
353 let chunk_size = protocol_config
354 .max_updates_per_settlement_txn_as_option()
355 .unwrap_or(u32::MAX) as usize;
356
357 updates
358 .into_iter()
359 .chunks(chunk_size)
360 .into_iter()
361 .enumerate()
362 .map(|(idx, chunk)| {
363 build_one_settlement_txn(idx as u64, &mut chunk.collect::<Vec<_>>())
364 })
365 .collect()
366 }
367
368 fn add_prologue(
369 builder: &mut ProgrammableTransactionBuilder,
370 root: Argument,
371 epoch: u64,
372 checkpoint_height: u64,
373 idx: u64,
374 total_input_sui: u64,
375 total_output_sui: u64,
376 ) {
377 let epoch_arg = builder.pure(epoch).unwrap();
378 let checkpoint_height_arg = builder.pure(checkpoint_height).unwrap();
379 let idx_arg = builder.pure(idx).unwrap();
380 let total_input_sui_arg = builder.pure(total_input_sui).unwrap();
381 let total_output_sui_arg = builder.pure(total_output_sui).unwrap();
382
383 builder.programmable_move_call(
384 SUI_FRAMEWORK_PACKAGE_ID,
385 ACCUMULATOR_SETTLEMENT_MODULE.into(),
386 ACCUMULATOR_ROOT_SETTLEMENT_PROLOGUE_FUNC.into(),
387 vec![],
388 vec![
389 root,
390 epoch_arg,
391 checkpoint_height_arg,
392 idx_arg,
393 total_input_sui_arg,
394 total_output_sui_arg,
395 ],
396 );
397 }
398
399 fn build_one_settlement_txn(
400 addresses: &HashMap<AccumulatorObjId, AccumulatorAddress>,
401 epoch: u64,
402 idx: u64,
403 checkpoint_height: u64,
404 accumulator_root_obj_initial_shared_version: SequenceNumber,
405 updates: impl Iterator<Item = (AccumulatorObjId, Update)>,
406 total_input_sui: u64,
407 total_output_sui: u64,
408 checkpoint_seq: u64,
409 ) -> TransactionKind {
410 let mut builder = ProgrammableTransactionBuilder::new();
411
412 let root = builder
413 .input(CallArg::Object(ObjectArg::SharedObject {
414 id: SUI_ACCUMULATOR_ROOT_OBJECT_ID,
415 initial_shared_version: accumulator_root_obj_initial_shared_version,
416 mutability: SharedObjectMutability::NonExclusiveWrite,
417 }))
418 .unwrap();
419
420 Self::add_prologue(
421 &mut builder,
422 root,
423 epoch,
424 checkpoint_height,
425 idx,
426 total_input_sui,
427 total_output_sui,
428 );
429
430 for (accumulator_obj, update) in updates {
431 let Update { merge, split, .. } = update;
432 let address = addresses.get(&accumulator_obj).unwrap();
433 let merged_value = MergedValue::from(merge);
434 let split_value = MergedValue::from(split);
435 MergedValue::add_move_call(
436 merged_value,
437 split_value,
438 root,
439 address,
440 checkpoint_seq,
441 &mut builder,
442 );
443 }
444
445 TransactionKind::ProgrammableSystemTransaction(builder.finish())
446 }
447}
448
449pub fn build_accumulator_barrier_tx(
453 epoch: u64,
454 accumulator_root_obj_initial_shared_version: SequenceNumber,
455 checkpoint_height: u64,
456 settlement_effects: &[TransactionEffects],
457) -> TransactionKind {
458 let num_settlements = settlement_effects.len() as u64;
459
460 let (objects_created, objects_destroyed) = settlement_effects
461 .iter()
462 .flat_map(|effects| effects.object_changes())
463 .fold((0u64, 0u64), |(created, destroyed), change| {
464 match change.id_operation {
465 IDOperation::Created => (created + 1, destroyed),
466 IDOperation::Deleted => (created, destroyed + 1),
467 IDOperation::None => (created, destroyed),
468 }
469 });
470
471 let mut builder = ProgrammableTransactionBuilder::new();
472 let root = builder
473 .input(CallArg::Object(ObjectArg::SharedObject {
474 id: SUI_ACCUMULATOR_ROOT_OBJECT_ID,
475 initial_shared_version: accumulator_root_obj_initial_shared_version,
476 mutability: SharedObjectMutability::Mutable,
477 }))
478 .unwrap();
479
480 AccumulatorSettlementTxBuilder::add_prologue(
481 &mut builder,
482 root,
483 epoch,
484 checkpoint_height,
485 num_settlements,
486 0,
487 0,
488 );
489
490 let objects_created_arg = builder.pure(objects_created).unwrap();
491 let objects_destroyed_arg = builder.pure(objects_destroyed).unwrap();
492 builder.programmable_move_call(
493 SUI_FRAMEWORK_PACKAGE_ID,
494 ACCUMULATOR_METADATA_MODULE.into(),
495 ident_str!("record_accumulator_object_changes").into(),
496 vec![],
497 vec![root, objects_created_arg, objects_destroyed_arg],
498 );
499
500 TransactionKind::ProgrammableSystemTransaction(builder.finish())
501}
502
503#[cfg(test)]
504mod barrier_settlement_key_tests {
505 use super::*;
506 use sui_types::transaction::TransactionKey;
507
508 #[test]
509 fn test_barrier_tx_returns_accumulator_settlement_key() {
510 let epoch = 5u64;
511 let checkpoint_height = 42u64;
512
513 let kind = build_accumulator_barrier_tx(
514 epoch,
515 SequenceNumber::from_u64(1),
516 checkpoint_height,
517 &[], );
519
520 assert_eq!(
521 kind.accumulator_barrier_settlement_key(),
522 Some(TransactionKey::AccumulatorSettlement(
523 epoch,
524 checkpoint_height
525 ))
526 );
527 assert!(kind.is_accumulator_barrier_settle_tx());
528 }
529
530 #[test]
531 fn test_settlement_tx_has_no_barrier_key() {
532 let protocol_config = ProtocolConfig::get_for_max_version_UNSAFE();
535 let builder = AccumulatorSettlementTxBuilder::new(None, &[], 0, 0);
536 let txns = builder.build_tx(&protocol_config, 5, SequenceNumber::from_u64(1), 42, 0);
537 for txn in txns {
538 assert_eq!(txn.accumulator_barrier_settlement_key(), None);
539 assert!(!txn.is_accumulator_barrier_settle_tx());
540 }
541 }
542}