1use crate::balance::Balance;
5use crate::base_types::SuiAddress;
6use crate::coin::Coin;
7use crate::effects::{
8 AccumulatorOperation, AccumulatorValue, TransactionEffects, TransactionEffectsAPI,
9};
10use crate::full_checkpoint_content::ObjectSet;
11use crate::object::Object;
12use crate::object::Owner;
13use crate::storage::ObjectKey;
14use move_core_types::language_storage::TypeTag;
15
16#[derive(Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, PartialOrd, Ord)]
17pub struct BalanceChange {
18 pub address: SuiAddress,
20
21 pub coin_type: TypeTag,
23
24 pub amount: i128,
28}
29
30#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
31pub struct DetailedBalanceChange {
32 pub address: SuiAddress,
34
35 pub coin_type: TypeTag,
37
38 pub coin_amount: i128,
42
43 pub address_amount: i128,
47}
48
49impl From<DetailedBalanceChange> for BalanceChange {
50 fn from(value: DetailedBalanceChange) -> Self {
51 Self {
52 address: value.address,
53 coin_type: value.coin_type,
54 amount: value.coin_amount + value.address_amount,
55 }
56 }
57}
58
59impl std::fmt::Debug for BalanceChange {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 f.debug_struct("BalanceChange")
62 .field("address", &self.address)
63 .field("coin_type", &self.coin_type.to_canonical_string(true))
64 .field("amount", &self.amount)
65 .finish()
66 }
67}
68
69fn coins<'a, I>(objects: I) -> impl Iterator<Item = (&'a SuiAddress, TypeTag, u64)> + 'a
70where
71 I: IntoIterator<Item = &'a Object> + 'a,
72{
73 objects.into_iter().filter_map(|object| {
74 let address = match object.owner() {
86 Owner::AddressOwner(sui_address)
87 | Owner::ConsensusAddressOwner {
88 owner: sui_address, ..
89 } => sui_address,
90 Owner::Party { .. } => todo!("Party WIP"),
92 Owner::ObjectOwner(_) | Owner::Shared { .. } | Owner::Immutable => return None,
93 };
94 let (coin_type, balance) = Coin::extract_balance_if_coin(object).ok().flatten()?;
95 Some((address, coin_type, balance))
96 })
97}
98
99fn signed_balance_change_from_event(
102 event: &crate::accumulator_event::AccumulatorEvent,
103) -> Option<(SuiAddress, TypeTag, i128)> {
104 let ty = &event.write.address.ty;
105 let coin_type = Balance::maybe_get_balance_type_param(ty)?;
107
108 let amount = match &event.write.value {
109 AccumulatorValue::Integer(v) => *v as i128,
110 AccumulatorValue::IntegerTuple(_, _) | AccumulatorValue::EventDigest(_) => {
112 return None;
113 }
114 };
115
116 let signed_amount = match event.write.operation {
118 AccumulatorOperation::Split => -amount,
119 AccumulatorOperation::Merge => amount,
120 };
121
122 Some((event.write.address.address, coin_type, signed_amount))
123}
124
125pub fn signed_balance_changes_from_events(
127 events: &[crate::accumulator_event::AccumulatorEvent],
128) -> impl Iterator<Item = (SuiAddress, TypeTag, i128)> + '_ {
129 events.iter().filter_map(signed_balance_change_from_event)
130}
131
132pub fn address_balance_changes_from_accumulator_events(
134 effects: &TransactionEffects,
135) -> impl Iterator<Item = (SuiAddress, TypeTag, i128)> {
136 effects
137 .accumulator_events()
138 .into_iter()
139 .filter_map(|ref event| signed_balance_change_from_event(event))
140}
141
142pub fn derive_balance_changes(
143 effects: &TransactionEffects,
144 input_objects: &[Object],
145 output_objects: &[Object],
146) -> Vec<BalanceChange> {
147 derive_detailed_balance_changes(effects, input_objects, output_objects)
148 .into_iter()
149 .filter_map(|detailed_change| {
151 let change = BalanceChange::from(detailed_change);
152 if change.amount == 0 {
153 None
154 } else {
155 Some(change)
156 }
157 })
158 .collect()
159}
160
161pub fn derive_detailed_balance_changes(
162 effects: &TransactionEffects,
163 input_objects: &[Object],
164 output_objects: &[Object],
165) -> Vec<DetailedBalanceChange> {
166 derive_detailed_balance_changes_inner(effects, input_objects, output_objects)
167}
168
169pub fn derive_detailed_balance_changes_2(
174 effects: &TransactionEffects,
175 objects: &ObjectSet,
176) -> Vec<DetailedBalanceChange> {
177 let input_objects = effects
178 .modified_at_versions()
179 .into_iter()
180 .filter_map(|(object_id, version)| objects.get(&ObjectKey(object_id, version)));
181 let output_objects = effects
182 .all_changed_objects()
183 .into_iter()
184 .filter_map(|(object_ref, _owner, _kind)| objects.get(&object_ref.into()));
185
186 derive_detailed_balance_changes_inner(effects, input_objects, output_objects)
187}
188
189fn derive_detailed_balance_changes_inner<'a, I, O>(
194 effects: &TransactionEffects,
195 input_objects: I,
196 output_objects: O,
197) -> Vec<DetailedBalanceChange>
198where
199 I: IntoIterator<Item = &'a Object> + 'a,
200 O: IntoIterator<Item = &'a Object> + 'a,
201{
202 let balances = coins(input_objects).fold(
204 std::collections::BTreeMap::<_, (i128, i128)>::new(),
205 |mut acc, (address, coin_type, balance)| {
206 acc.entry((*address, coin_type)).or_default().0 -= balance as i128;
207 acc
208 },
209 );
210
211 let balances =
213 coins(output_objects).fold(balances, |mut acc, (address, coin_type, balance)| {
214 acc.entry((*address, coin_type)).or_default().0 += balance as i128;
215 acc
216 });
217
218 let balances = address_balance_changes_from_accumulator_events(effects).fold(
220 balances,
221 |mut acc, (address, coin_type, signed_amount)| {
222 acc.entry((address, coin_type)).or_default().1 += signed_amount;
223 acc
224 },
225 );
226
227 balances
228 .into_iter()
229 .filter_map(|((address, coin_type), (coin_amount, address_amount))| {
230 if coin_amount == 0 && address_amount == 0 {
231 return None;
232 }
233
234 Some(DetailedBalanceChange {
235 address,
236 coin_type,
237 coin_amount,
238 address_amount,
239 })
240 })
241 .collect()
242}
243
244pub fn derive_balance_changes_2(
245 effects: &TransactionEffects,
246 objects: &ObjectSet,
247) -> Vec<BalanceChange> {
248 derive_detailed_balance_changes_2(effects, objects)
249 .into_iter()
250 .filter_map(|detailed_change| {
252 let change = BalanceChange::from(detailed_change);
253 if change.amount == 0 {
254 None
255 } else {
256 Some(change)
257 }
258 })
259 .collect()
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265 use crate::accumulator_root::AccumulatorValue as AccumulatorValueRoot;
266 use crate::balance::Balance;
267 use crate::base_types::ObjectID;
268 use crate::digests::TransactionDigest;
269 use crate::effects::{
270 AccumulatorAddress, AccumulatorOperation, AccumulatorValue, AccumulatorWriteV1,
271 EffectsObjectChange, IDOperation, ObjectIn, ObjectOut, TransactionEffects,
272 };
273 use crate::execution_status::ExecutionStatus;
274 use crate::gas::GasCostSummary;
275 use move_core_types::language_storage::TypeTag;
276
277 fn create_effects_with_accumulator_writes(
278 writes: Vec<(ObjectID, AccumulatorWriteV1)>,
279 ) -> TransactionEffects {
280 let changed_objects = writes
281 .into_iter()
282 .map(|(id, write)| {
283 (
284 id,
285 EffectsObjectChange {
286 input_state: ObjectIn::NotExist,
287 output_state: ObjectOut::AccumulatorWriteV1(write),
288 id_operation: IDOperation::None,
289 },
290 )
291 })
292 .collect();
293
294 TransactionEffects::new_from_execution_v2(
295 ExecutionStatus::Success,
296 0,
297 GasCostSummary::default(),
298 vec![],
299 TransactionDigest::random(),
300 crate::base_types::SequenceNumber::new(),
301 changed_objects,
302 None,
303 None,
304 vec![],
305 )
306 }
307
308 fn sui_balance_type() -> TypeTag {
309 Balance::type_tag("0x2::sui::SUI".parse().unwrap())
310 }
311
312 fn custom_coin_type() -> TypeTag {
313 "0xabc::my_coin::MY_COIN".parse().unwrap()
314 }
315
316 fn custom_balance_type() -> TypeTag {
317 Balance::type_tag(custom_coin_type())
318 }
319
320 fn get_accumulator_obj_id(address: SuiAddress, balance_type: &TypeTag) -> ObjectID {
321 *AccumulatorValueRoot::get_field_id(address, balance_type)
322 .unwrap()
323 .inner()
324 }
325
326 #[test]
327 fn test_derive_balance_changes_with_no_accumulator_events() {
328 let effects = create_effects_with_accumulator_writes(vec![]);
329 let result = derive_balance_changes(&effects, &[], &[]);
330 assert!(result.is_empty());
331 }
332
333 #[test]
334 fn test_derive_balance_changes_with_split_accumulator_event() {
335 let address = SuiAddress::random_for_testing_only();
336 let balance_type = sui_balance_type();
337 let obj_id = get_accumulator_obj_id(address, &balance_type);
338 let write = AccumulatorWriteV1 {
339 address: AccumulatorAddress::new(address, balance_type),
340 operation: AccumulatorOperation::Split,
341 value: AccumulatorValue::Integer(1000),
342 };
343 let effects = create_effects_with_accumulator_writes(vec![(obj_id, write)]);
344
345 let result = derive_balance_changes(&effects, &[], &[]);
346
347 assert_eq!(result.len(), 1);
348 assert_eq!(result[0].address, address);
349 assert_eq!(
350 result[0].coin_type,
351 "0x2::sui::SUI".parse::<TypeTag>().unwrap()
352 );
353 assert_eq!(result[0].amount, -1000);
354 }
355
356 #[test]
357 fn test_derive_balance_changes_with_merge_accumulator_event() {
358 let address = SuiAddress::random_for_testing_only();
359 let balance_type = sui_balance_type();
360 let obj_id = get_accumulator_obj_id(address, &balance_type);
361 let write = AccumulatorWriteV1 {
362 address: AccumulatorAddress::new(address, balance_type),
363 operation: AccumulatorOperation::Merge,
364 value: AccumulatorValue::Integer(500),
365 };
366 let effects = create_effects_with_accumulator_writes(vec![(obj_id, write)]);
367
368 let result = derive_balance_changes(&effects, &[], &[]);
369
370 assert_eq!(result.len(), 1);
371 assert_eq!(result[0].address, address);
372 assert_eq!(result[0].amount, 500);
373 }
374
375 #[test]
376 fn test_derive_balance_changes_with_multiple_addresses() {
377 let address1 = SuiAddress::random_for_testing_only();
378 let address2 = SuiAddress::random_for_testing_only();
379 let balance_type = sui_balance_type();
380
381 let obj_id1 = get_accumulator_obj_id(address1, &balance_type);
382 let obj_id2 = get_accumulator_obj_id(address2, &balance_type);
383
384 let write1 = AccumulatorWriteV1 {
385 address: AccumulatorAddress::new(address1, balance_type.clone()),
386 operation: AccumulatorOperation::Split,
387 value: AccumulatorValue::Integer(1000),
388 };
389 let write2 = AccumulatorWriteV1 {
390 address: AccumulatorAddress::new(address2, balance_type),
391 operation: AccumulatorOperation::Merge,
392 value: AccumulatorValue::Integer(1000),
393 };
394
395 let effects =
396 create_effects_with_accumulator_writes(vec![(obj_id1, write1), (obj_id2, write2)]);
397
398 let result = derive_balance_changes(&effects, &[], &[]);
399
400 assert_eq!(result.len(), 2);
401 let addr1_change = result.iter().find(|c| c.address == address1).unwrap();
402 let addr2_change = result.iter().find(|c| c.address == address2).unwrap();
403 assert_eq!(addr1_change.amount, -1000);
404 assert_eq!(addr2_change.amount, 1000);
405 }
406
407 #[test]
408 fn test_derive_balance_changes_with_custom_coin_type() {
409 let address = SuiAddress::random_for_testing_only();
410 let balance_type = custom_balance_type();
411 let obj_id = get_accumulator_obj_id(address, &balance_type);
412 let write = AccumulatorWriteV1 {
413 address: AccumulatorAddress::new(address, balance_type),
414 operation: AccumulatorOperation::Split,
415 value: AccumulatorValue::Integer(2000),
416 };
417 let effects = create_effects_with_accumulator_writes(vec![(obj_id, write)]);
418
419 let result = derive_balance_changes(&effects, &[], &[]);
420
421 assert_eq!(result.len(), 1);
422 assert_eq!(result[0].address, address);
423 assert_eq!(result[0].coin_type, custom_coin_type());
424 assert_eq!(result[0].amount, -2000);
425 }
426
427 #[test]
428 fn test_derive_balance_changes_ignores_non_balance_types() {
429 let address = SuiAddress::random_for_testing_only();
430 let non_balance_type: TypeTag = "0x2::accumulator_settlement::EventStreamHead"
432 .parse()
433 .unwrap();
434 let write = AccumulatorWriteV1 {
435 address: AccumulatorAddress::new(address, non_balance_type),
436 operation: AccumulatorOperation::Split,
437 value: AccumulatorValue::Integer(1000),
438 };
439 let effects = create_effects_with_accumulator_writes(vec![(ObjectID::random(), write)]);
440
441 let result = derive_balance_changes(&effects, &[], &[]);
442
443 assert!(result.is_empty());
444 }
445
446 #[test]
447 fn test_derive_balance_changes_ignores_event_digest_values() {
448 use crate::digests::Digest;
449 use nonempty::nonempty;
450
451 let address = SuiAddress::random_for_testing_only();
452 let balance_type = sui_balance_type();
453 let obj_id = get_accumulator_obj_id(address, &balance_type);
454 let write = AccumulatorWriteV1 {
455 address: AccumulatorAddress::new(address, balance_type),
456 operation: AccumulatorOperation::Merge,
457 value: AccumulatorValue::EventDigest(nonempty![(0, Digest::random())]),
458 };
459 let effects = create_effects_with_accumulator_writes(vec![(obj_id, write)]);
460
461 let result = derive_balance_changes(&effects, &[], &[]);
462
463 assert!(result.is_empty());
464 }
465
466 #[test]
467 fn test_derive_balance_changes_accumulator_zero_amount_filtered() {
468 let address = SuiAddress::random_for_testing_only();
470 let balance_type = sui_balance_type();
471 let obj_id = get_accumulator_obj_id(address, &balance_type);
472
473 let write = AccumulatorWriteV1 {
474 address: AccumulatorAddress::new(address, balance_type),
475 operation: AccumulatorOperation::Split,
476 value: AccumulatorValue::Integer(0),
477 };
478 let effects = create_effects_with_accumulator_writes(vec![(obj_id, write)]);
479
480 let result = derive_balance_changes(&effects, &[], &[]);
481
482 assert!(result.is_empty());
484 }
485
486 #[test]
487 fn test_derive_balance_changes_2_with_accumulator_events() {
488 let address = SuiAddress::random_for_testing_only();
489 let balance_type = sui_balance_type();
490 let obj_id = get_accumulator_obj_id(address, &balance_type);
491 let write = AccumulatorWriteV1 {
492 address: AccumulatorAddress::new(address, balance_type),
493 operation: AccumulatorOperation::Split,
494 value: AccumulatorValue::Integer(1000),
495 };
496 let effects = create_effects_with_accumulator_writes(vec![(obj_id, write)]);
497
498 let objects = crate::full_checkpoint_content::ObjectSet::default();
499 let result = derive_balance_changes_2(&effects, &objects);
500
501 assert_eq!(result.len(), 1);
502 assert_eq!(result[0].address, address);
503 assert_eq!(
504 result[0].coin_type,
505 "0x2::sui::SUI".parse::<TypeTag>().unwrap()
506 );
507 assert_eq!(result[0].amount, -1000);
508 }
509
510 fn create_gas_coin_object(owner: SuiAddress, value: u64) -> Object {
513 create_gas_coin_object_with_owner(Owner::AddressOwner(owner), value)
514 }
515
516 fn create_gas_coin_object_with_owner(owner: Owner, value: u64) -> Object {
517 use crate::base_types::SequenceNumber;
518 use crate::object::MoveObject;
519
520 let obj_id = ObjectID::random();
521 let move_obj = MoveObject::new_gas_coin(SequenceNumber::new(), obj_id, value);
522 Object::new_move(move_obj, owner, TransactionDigest::random())
523 }
524
525 fn create_custom_coin_object(owner: SuiAddress, coin_type: TypeTag, value: u64) -> Object {
526 use crate::base_types::SequenceNumber;
527 use crate::object::MoveObject;
528
529 let obj_id = ObjectID::random();
530 let move_obj = MoveObject::new_coin(coin_type, SequenceNumber::new(), obj_id, value);
531 Object::new_move(
532 move_obj,
533 Owner::AddressOwner(owner),
534 TransactionDigest::random(),
535 )
536 }
537
538 #[test]
539 fn test_derive_balance_changes_with_coin_objects_only() {
540 let address = SuiAddress::random_for_testing_only();
541
542 let input_coin = create_gas_coin_object(address, 5000);
544 let output_coin = create_gas_coin_object(address, 3000);
546
547 let effects = create_effects_with_accumulator_writes(vec![]);
548
549 let result = derive_balance_changes(&effects, &[input_coin], &[output_coin]);
550
551 assert_eq!(result.len(), 1);
552 assert_eq!(result[0].address, address);
553 assert_eq!(result[0].amount, -2000); }
555
556 #[test]
563 fn test_derive_balance_changes_excludes_object_owned_coins() {
564 let sender = SuiAddress::random_for_testing_only();
565 let parent = ObjectID::random();
566
567 let input_coin = create_gas_coin_object(sender, 3000);
569 let output_coin =
570 create_gas_coin_object_with_owner(Owner::ObjectOwner(parent.into()), 3000);
571
572 let effects = create_effects_with_accumulator_writes(vec![]);
573 let result = derive_balance_changes(&effects, &[input_coin], &[output_coin]);
574
575 assert_eq!(result.len(), 1);
576 assert_eq!(result[0].address, sender);
577 assert_eq!(result[0].amount, -3000);
578 }
579
580 #[test]
584 fn test_derive_balance_changes_combines_consensus_address_owner() {
585 use crate::base_types::SequenceNumber;
586
587 let address = SuiAddress::random_for_testing_only();
588
589 let input_coin = create_gas_coin_object(address, 4000);
590 let output_coin = create_gas_coin_object_with_owner(
591 Owner::ConsensusAddressOwner {
592 start_version: SequenceNumber::new(),
593 owner: address,
594 },
595 4000,
596 );
597
598 let effects = create_effects_with_accumulator_writes(vec![]);
599 let result = derive_balance_changes(&effects, &[input_coin], &[output_coin]);
600
601 assert!(
602 result.is_empty(),
603 "moving a coin between fastpath and consensus address custody \
604 is not a balance change"
605 );
606 }
607
608 #[test]
609 fn test_derive_balance_changes_coin_transfer_between_addresses() {
610 let sender = SuiAddress::random_for_testing_only();
611 let receiver = SuiAddress::random_for_testing_only();
612
613 let input_coin = create_gas_coin_object(sender, 10000);
615 let output_coin_sender = create_gas_coin_object(sender, 7000);
617 let output_coin_receiver = create_gas_coin_object(receiver, 3000);
618
619 let effects = create_effects_with_accumulator_writes(vec![]);
620
621 let result = derive_balance_changes(
622 &effects,
623 &[input_coin],
624 &[output_coin_sender, output_coin_receiver],
625 );
626
627 assert_eq!(result.len(), 2);
628 let sender_change = result.iter().find(|c| c.address == sender).unwrap();
629 let receiver_change = result.iter().find(|c| c.address == receiver).unwrap();
630 assert_eq!(sender_change.amount, -3000); assert_eq!(receiver_change.amount, 3000); }
633
634 #[test]
635 fn test_derive_balance_changes_combines_coins_and_accumulator_events() {
636 let address = SuiAddress::random_for_testing_only();
637 let balance_type = sui_balance_type();
638 let obj_id = get_accumulator_obj_id(address, &balance_type);
639
640 let input_coin = create_gas_coin_object(address, 5000);
642 let output_coin = create_gas_coin_object(address, 3000);
643
644 let write = AccumulatorWriteV1 {
646 address: AccumulatorAddress::new(address, balance_type),
647 operation: AccumulatorOperation::Merge,
648 value: AccumulatorValue::Integer(500),
649 };
650 let effects = create_effects_with_accumulator_writes(vec![(obj_id, write)]);
651
652 let result = derive_balance_changes(&effects, &[input_coin], &[output_coin]);
653
654 assert_eq!(result.len(), 1);
656 assert_eq!(result[0].address, address);
657 assert_eq!(result[0].amount, -1500);
658 }
659
660 #[test]
661 fn test_derive_balance_changes_coins_and_accumulator_different_addresses() {
662 let coin_owner = SuiAddress::random_for_testing_only();
663 let accumulator_owner = SuiAddress::random_for_testing_only();
664 let balance_type = sui_balance_type();
665 let obj_id = get_accumulator_obj_id(accumulator_owner, &balance_type);
666
667 let input_coin = create_gas_coin_object(coin_owner, 5000);
669 let output_coin = create_gas_coin_object(coin_owner, 4000);
670
671 let write = AccumulatorWriteV1 {
673 address: AccumulatorAddress::new(accumulator_owner, balance_type),
674 operation: AccumulatorOperation::Merge,
675 value: AccumulatorValue::Integer(2000),
676 };
677 let effects = create_effects_with_accumulator_writes(vec![(obj_id, write)]);
678
679 let result = derive_balance_changes(&effects, &[input_coin], &[output_coin]);
680
681 assert_eq!(result.len(), 2);
682 let coin_change = result.iter().find(|c| c.address == coin_owner).unwrap();
683 let acc_change = result
684 .iter()
685 .find(|c| c.address == accumulator_owner)
686 .unwrap();
687 assert_eq!(coin_change.amount, -1000);
688 assert_eq!(acc_change.amount, 2000);
689 }
690
691 #[test]
692 fn test_derive_balance_changes_coins_and_accumulator_net_to_zero() {
693 let address = SuiAddress::random_for_testing_only();
694 let balance_type = sui_balance_type();
695 let obj_id = get_accumulator_obj_id(address, &balance_type);
696
697 let input_coin = create_gas_coin_object(address, 5000);
699 let output_coin = create_gas_coin_object(address, 4000);
700
701 let write = AccumulatorWriteV1 {
703 address: AccumulatorAddress::new(address, balance_type),
704 operation: AccumulatorOperation::Merge,
705 value: AccumulatorValue::Integer(1000),
706 };
707 let effects = create_effects_with_accumulator_writes(vec![(obj_id, write)]);
708
709 let result = derive_balance_changes(&effects, &[input_coin], &[output_coin]);
710
711 assert!(result.is_empty());
713 }
714
715 #[test]
716 fn test_derive_balance_changes_different_coin_types() {
717 let address = SuiAddress::random_for_testing_only();
718 let custom_type = custom_coin_type();
719 let custom_balance = custom_balance_type();
720 let obj_id = get_accumulator_obj_id(address, &custom_balance);
721
722 let sui_input = create_gas_coin_object(address, 5000);
724 let sui_output = create_gas_coin_object(address, 4000);
725
726 let custom_output = create_custom_coin_object(address, custom_type.clone(), 500);
728
729 let write = AccumulatorWriteV1 {
731 address: AccumulatorAddress::new(address, custom_balance),
732 operation: AccumulatorOperation::Merge,
733 value: AccumulatorValue::Integer(300),
734 };
735 let effects = create_effects_with_accumulator_writes(vec![(obj_id, write)]);
736
737 let result = derive_balance_changes(&effects, &[sui_input], &[sui_output, custom_output]);
738
739 assert_eq!(result.len(), 2);
740
741 let sui_change = result
742 .iter()
743 .find(|c| c.coin_type == "0x2::sui::SUI".parse::<TypeTag>().unwrap())
744 .unwrap();
745 let custom_change = result.iter().find(|c| c.coin_type == custom_type).unwrap();
746
747 assert_eq!(sui_change.amount, -1000);
748 assert_eq!(custom_change.amount, 800); }
750
751 #[test]
752 fn test_derive_balance_changes_accumulator_split_with_coins() {
753 let sender = SuiAddress::random_for_testing_only();
754 let receiver = SuiAddress::random_for_testing_only();
755 let balance_type = sui_balance_type();
756 let sender_obj_id = get_accumulator_obj_id(sender, &balance_type);
757 let receiver_obj_id = get_accumulator_obj_id(receiver, &balance_type.clone());
758
759 let input_coin = create_gas_coin_object(sender, 5000);
761 let output_coin = create_gas_coin_object(sender, 4000);
762
763 let sender_write = AccumulatorWriteV1 {
765 address: AccumulatorAddress::new(sender, balance_type.clone()),
766 operation: AccumulatorOperation::Split,
767 value: AccumulatorValue::Integer(500),
768 };
769 let receiver_write = AccumulatorWriteV1 {
771 address: AccumulatorAddress::new(receiver, balance_type),
772 operation: AccumulatorOperation::Merge,
773 value: AccumulatorValue::Integer(500),
774 };
775 let effects = create_effects_with_accumulator_writes(vec![
776 (sender_obj_id, sender_write),
777 (receiver_obj_id, receiver_write),
778 ]);
779
780 let result = derive_balance_changes(&effects, &[input_coin], &[output_coin]);
781
782 assert_eq!(result.len(), 2);
783 let sender_change = result.iter().find(|c| c.address == sender).unwrap();
784 let receiver_change = result.iter().find(|c| c.address == receiver).unwrap();
785
786 assert_eq!(sender_change.amount, -1500);
788 assert_eq!(receiver_change.amount, 500);
790 }
791}