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}
225
226impl AccumulatorSettlementTxBuilder {
227 pub fn new(
228 cache: Option<&dyn TransactionCacheRead>,
229 ckpt_effects: &[TransactionEffects],
230 checkpoint_seq: u64,
231 tx_index_offset: u64,
232 ) -> Self {
233 let mut updates = BTreeMap::<_, _>::new();
234
235 let mut addresses = HashMap::<_, _>::new();
236
237 for (tx_index, effect) in ckpt_effects.iter().enumerate() {
238 let tx = effect.transaction_digest();
239 let events = match cache.and_then(|c| c.take_accumulator_events(tx)) {
244 Some(events) => events,
245 None => effect.accumulator_events(),
246 };
247
248 for event in events {
249 let (event_input_sui, event_output_sui) = event.total_sui_in_event();
252
253 let AccumulatorEvent {
254 accumulator_obj,
255 write:
256 AccumulatorWriteV1 {
257 operation,
258 value,
259 address,
260 },
261 } = event;
262
263 if let Some(prev) = addresses.insert(accumulator_obj, address.clone()) {
264 debug_assert_eq!(prev, address);
265 }
266
267 let entry = updates.entry(accumulator_obj).or_insert_with(|| {
268 let zero = MergedValueIntermediate::zero(&value);
269 Update {
270 merge: zero.clone(),
271 split: zero,
272 input_sui: 0,
273 output_sui: 0,
274 }
275 });
276
277 entry.input_sui += event_output_sui;
279 entry.output_sui += event_input_sui;
280
281 match operation {
282 AccumulatorOperation::Merge => {
283 entry.merge.accumulate_into(
284 value,
285 checkpoint_seq,
286 tx_index as u64 + tx_index_offset,
287 );
288 }
289 AccumulatorOperation::Split => {
290 entry.split.accumulate_into(
291 value,
292 checkpoint_seq,
293 tx_index as u64 + tx_index_offset,
294 );
295 }
296 }
297 }
298 }
299
300 Self { updates, addresses }
301 }
302
303 pub fn num_updates(&self) -> usize {
304 self.updates.len()
305 }
306
307 pub fn collect_funds_changes(&self) -> BTreeMap<AccumulatorObjId, i128> {
310 self.updates
311 .iter()
312 .filter_map(|(object_id, update)| match (&update.merge, &update.split) {
313 (
314 MergedValueIntermediate::SumU128(merge),
315 MergedValueIntermediate::SumU128(split),
316 ) => Some((*object_id, *merge as i128 - *split as i128)),
317 _ => None,
318 })
319 .collect()
320 }
321
322 pub fn build_tx(
324 self,
325 protocol_config: &ProtocolConfig,
326 epoch: u64,
327 accumulator_root_obj_initial_shared_version: SequenceNumber,
328 checkpoint_height: u64,
329 checkpoint_seq: u64,
330 ) -> Vec<TransactionKind> {
331 let Self { updates, addresses } = self;
332
333 let build_one_settlement_txn = |idx: u64, updates: &mut Vec<(AccumulatorObjId, Update)>| {
334 let (total_input_sui, total_output_sui) =
335 updates
336 .iter()
337 .fold((0, 0), |(acc_input, acc_output), (_, update)| {
338 (acc_input + update.input_sui, acc_output + update.output_sui)
339 });
340
341 Self::build_one_settlement_txn(
342 &addresses,
343 epoch,
344 idx,
345 checkpoint_height,
346 accumulator_root_obj_initial_shared_version,
347 updates.drain(..),
348 total_input_sui,
349 total_output_sui,
350 checkpoint_seq,
351 )
352 };
353
354 let chunk_size = protocol_config
355 .max_updates_per_settlement_txn_as_option()
356 .unwrap_or(u32::MAX) as usize;
357
358 updates
359 .into_iter()
360 .chunks(chunk_size)
361 .into_iter()
362 .enumerate()
363 .map(|(idx, chunk)| {
364 build_one_settlement_txn(idx as u64, &mut chunk.collect::<Vec<_>>())
365 })
366 .collect()
367 }
368
369 fn add_prologue(
370 builder: &mut ProgrammableTransactionBuilder,
371 root: Argument,
372 epoch: u64,
373 checkpoint_height: u64,
374 idx: u64,
375 total_input_sui: u64,
376 total_output_sui: u64,
377 ) {
378 let epoch_arg = builder.pure(epoch).unwrap();
379 let checkpoint_height_arg = builder.pure(checkpoint_height).unwrap();
380 let idx_arg = builder.pure(idx).unwrap();
381 let total_input_sui_arg = builder.pure(total_input_sui).unwrap();
382 let total_output_sui_arg = builder.pure(total_output_sui).unwrap();
383
384 builder.programmable_move_call(
385 SUI_FRAMEWORK_PACKAGE_ID,
386 ACCUMULATOR_SETTLEMENT_MODULE.into(),
387 ACCUMULATOR_ROOT_SETTLEMENT_PROLOGUE_FUNC.into(),
388 vec![],
389 vec![
390 root,
391 epoch_arg,
392 checkpoint_height_arg,
393 idx_arg,
394 total_input_sui_arg,
395 total_output_sui_arg,
396 ],
397 );
398 }
399
400 fn build_one_settlement_txn(
401 addresses: &HashMap<AccumulatorObjId, AccumulatorAddress>,
402 epoch: u64,
403 idx: u64,
404 checkpoint_height: u64,
405 accumulator_root_obj_initial_shared_version: SequenceNumber,
406 updates: impl Iterator<Item = (AccumulatorObjId, Update)>,
407 total_input_sui: u64,
408 total_output_sui: u64,
409 checkpoint_seq: u64,
410 ) -> TransactionKind {
411 let mut builder = ProgrammableTransactionBuilder::new();
412
413 let root = builder
414 .input(CallArg::Object(ObjectArg::SharedObject {
415 id: SUI_ACCUMULATOR_ROOT_OBJECT_ID,
416 initial_shared_version: accumulator_root_obj_initial_shared_version,
417 mutability: SharedObjectMutability::NonExclusiveWrite,
418 }))
419 .unwrap();
420
421 Self::add_prologue(
422 &mut builder,
423 root,
424 epoch,
425 checkpoint_height,
426 idx,
427 total_input_sui,
428 total_output_sui,
429 );
430
431 for (accumulator_obj, update) in updates {
432 let Update { merge, split, .. } = update;
433 let address = addresses.get(&accumulator_obj).unwrap();
434 let merged_value = MergedValue::from(merge);
435 let split_value = MergedValue::from(split);
436 MergedValue::add_move_call(
437 merged_value,
438 split_value,
439 root,
440 address,
441 checkpoint_seq,
442 &mut builder,
443 );
444 }
445
446 TransactionKind::ProgrammableSystemTransaction(builder.finish())
447 }
448}
449
450pub fn build_accumulator_barrier_tx(
454 epoch: u64,
455 accumulator_root_obj_initial_shared_version: SequenceNumber,
456 checkpoint_height: u64,
457 settlement_effects: &[TransactionEffects],
458) -> TransactionKind {
459 let num_settlements = settlement_effects.len() as u64;
460
461 let (objects_created, objects_destroyed) = settlement_effects
462 .iter()
463 .flat_map(|effects| effects.object_changes())
464 .fold((0u64, 0u64), |(created, destroyed), change| {
465 match change.id_operation {
466 IDOperation::Created => (created + 1, destroyed),
467 IDOperation::Deleted => (created, destroyed + 1),
468 IDOperation::None => (created, destroyed),
469 }
470 });
471
472 let mut builder = ProgrammableTransactionBuilder::new();
473 let root = builder
474 .input(CallArg::Object(ObjectArg::SharedObject {
475 id: SUI_ACCUMULATOR_ROOT_OBJECT_ID,
476 initial_shared_version: accumulator_root_obj_initial_shared_version,
477 mutability: SharedObjectMutability::Mutable,
478 }))
479 .unwrap();
480
481 AccumulatorSettlementTxBuilder::add_prologue(
482 &mut builder,
483 root,
484 epoch,
485 checkpoint_height,
486 num_settlements,
487 0,
488 0,
489 );
490
491 let objects_created_arg = builder.pure(objects_created).unwrap();
492 let objects_destroyed_arg = builder.pure(objects_destroyed).unwrap();
493 builder.programmable_move_call(
494 SUI_FRAMEWORK_PACKAGE_ID,
495 ACCUMULATOR_METADATA_MODULE.into(),
496 ident_str!("record_accumulator_object_changes").into(),
497 vec![],
498 vec![root, objects_created_arg, objects_destroyed_arg],
499 );
500
501 TransactionKind::ProgrammableSystemTransaction(builder.finish())
502}
503
504#[cfg(test)]
505mod barrier_settlement_key_tests {
506 use super::*;
507 use sui_types::transaction::TransactionKey;
508
509 #[test]
510 fn test_barrier_tx_returns_accumulator_settlement_key() {
511 let epoch = 5u64;
512 let checkpoint_height = 42u64;
513
514 let kind = build_accumulator_barrier_tx(
515 epoch,
516 SequenceNumber::from_u64(1),
517 checkpoint_height,
518 &[], );
520
521 assert_eq!(
522 kind.accumulator_barrier_settlement_key(),
523 Some(TransactionKey::AccumulatorSettlement(
524 epoch,
525 checkpoint_height
526 ))
527 );
528 assert!(kind.is_accumulator_barrier_settle_tx());
529 }
530
531 #[test]
532 fn test_settlement_tx_has_no_barrier_key() {
533 let protocol_config = ProtocolConfig::get_for_max_version_UNSAFE();
536 let builder = AccumulatorSettlementTxBuilder::new(None, &[], 0, 0);
537 let txns = builder.build_tx(&protocol_config, 5, SequenceNumber::from_u64(1), 42, 0);
538 for txn in txns {
539 assert_eq!(txn.accumulator_barrier_settlement_key(), None);
540 assert!(!txn.is_accumulator_barrier_settle_tx());
541 }
542 }
543}