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