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