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(objects: &[Object]) -> impl Iterator<Item = (&SuiAddress, TypeTag, u64)> + '_ {
70 objects.iter().filter_map(|object| {
71 let address = match object.owner() {
72 Owner::AddressOwner(sui_address)
73 | Owner::ObjectOwner(sui_address)
74 | Owner::ConsensusAddressOwner {
75 owner: sui_address, ..
76 } => sui_address,
77 Owner::Shared { .. } | Owner::Immutable => return None,
78 };
79 let (coin_type, balance) = Coin::extract_balance_if_coin(object).ok().flatten()?;
80 Some((address, coin_type, balance))
81 })
82}
83
84fn signed_balance_change_from_event(
87 event: &crate::accumulator_event::AccumulatorEvent,
88) -> Option<(SuiAddress, TypeTag, i128)> {
89 let ty = &event.write.address.ty;
90 let coin_type = Balance::maybe_get_balance_type_param(ty)?;
92
93 let amount = match &event.write.value {
94 AccumulatorValue::Integer(v) => *v as i128,
95 AccumulatorValue::IntegerTuple(_, _) | AccumulatorValue::EventDigest(_) => {
97 return None;
98 }
99 };
100
101 let signed_amount = match event.write.operation {
103 AccumulatorOperation::Split => -amount,
104 AccumulatorOperation::Merge => amount,
105 };
106
107 Some((event.write.address.address, coin_type, signed_amount))
108}
109
110pub fn signed_balance_changes_from_events(
112 events: &[crate::accumulator_event::AccumulatorEvent],
113) -> impl Iterator<Item = (SuiAddress, TypeTag, i128)> + '_ {
114 events.iter().filter_map(signed_balance_change_from_event)
115}
116
117pub fn address_balance_changes_from_accumulator_events(
119 effects: &TransactionEffects,
120) -> impl Iterator<Item = (SuiAddress, TypeTag, i128)> {
121 effects
122 .accumulator_events()
123 .into_iter()
124 .filter_map(|ref event| signed_balance_change_from_event(event))
125}
126
127pub fn derive_balance_changes(
128 effects: &TransactionEffects,
129 input_objects: &[Object],
130 output_objects: &[Object],
131) -> Vec<BalanceChange> {
132 derive_detailed_balance_changes(effects, input_objects, output_objects)
133 .into_iter()
134 .filter_map(|detailed_change| {
136 let change = BalanceChange::from(detailed_change);
137 if change.amount == 0 {
138 None
139 } else {
140 Some(change)
141 }
142 })
143 .collect()
144}
145
146pub fn derive_detailed_balance_changes(
147 effects: &TransactionEffects,
148 input_objects: &[Object],
149 output_objects: &[Object],
150) -> Vec<DetailedBalanceChange> {
151 let balances = coins(input_objects).fold(
153 std::collections::BTreeMap::<_, (i128, i128)>::new(),
154 |mut acc, (address, coin_type, balance)| {
155 acc.entry((*address, coin_type)).or_default().0 -= balance as i128;
156 acc
157 },
158 );
159
160 let balances =
162 coins(output_objects).fold(balances, |mut acc, (address, coin_type, balance)| {
163 acc.entry((*address, coin_type)).or_default().0 += balance as i128;
164 acc
165 });
166
167 let balances = address_balance_changes_from_accumulator_events(effects).fold(
169 balances,
170 |mut acc, (address, coin_type, signed_amount)| {
171 acc.entry((address, coin_type)).or_default().1 += signed_amount;
172 acc
173 },
174 );
175
176 balances
177 .into_iter()
178 .filter_map(|((address, coin_type), (coin_amount, address_amount))| {
179 if coin_amount == 0 && address_amount == 0 {
180 return None;
181 }
182
183 Some(DetailedBalanceChange {
184 address,
185 coin_type,
186 coin_amount,
187 address_amount,
188 })
189 })
190 .collect()
191}
192
193pub fn derive_balance_changes_2(
194 effects: &TransactionEffects,
195 objects: &ObjectSet,
196) -> Vec<BalanceChange> {
197 let input_objects = effects
198 .modified_at_versions()
199 .into_iter()
200 .filter_map(|(object_id, version)| objects.get(&ObjectKey(object_id, version)).cloned())
201 .collect::<Vec<_>>();
202 let output_objects = effects
203 .all_changed_objects()
204 .into_iter()
205 .filter_map(|(object_ref, _owner, _kind)| objects.get(&object_ref.into()).cloned())
206 .collect::<Vec<_>>();
207
208 let balances = coins(&input_objects).fold(
210 std::collections::BTreeMap::<_, i128>::new(),
211 |mut acc, (address, coin_type, balance)| {
212 *acc.entry((*address, coin_type)).or_default() -= balance as i128;
213 acc
214 },
215 );
216
217 let balances =
219 coins(&output_objects).fold(balances, |mut acc, (address, coin_type, balance)| {
220 *acc.entry((*address, coin_type)).or_default() += balance as i128;
221 acc
222 });
223
224 let balances = address_balance_changes_from_accumulator_events(effects).fold(
226 balances,
227 |mut acc, (address, coin_type, signed_amount)| {
228 *acc.entry((address, coin_type)).or_default() += signed_amount;
229 acc
230 },
231 );
232
233 balances
234 .into_iter()
235 .filter_map(|((address, coin_type), amount)| {
236 if amount == 0 {
237 return None;
238 }
239
240 Some(BalanceChange {
241 address,
242 coin_type,
243 amount,
244 })
245 })
246 .collect()
247}
248
249#[cfg(test)]
250mod tests {
251 use super::*;
252 use crate::accumulator_root::AccumulatorValue as AccumulatorValueRoot;
253 use crate::balance::Balance;
254 use crate::base_types::ObjectID;
255 use crate::digests::TransactionDigest;
256 use crate::effects::{
257 AccumulatorAddress, AccumulatorOperation, AccumulatorValue, AccumulatorWriteV1,
258 EffectsObjectChange, IDOperation, ObjectIn, ObjectOut, TransactionEffects,
259 };
260 use crate::execution_status::ExecutionStatus;
261 use crate::gas::GasCostSummary;
262 use move_core_types::language_storage::TypeTag;
263
264 fn create_effects_with_accumulator_writes(
265 writes: Vec<(ObjectID, AccumulatorWriteV1)>,
266 ) -> TransactionEffects {
267 let changed_objects = writes
268 .into_iter()
269 .map(|(id, write)| {
270 (
271 id,
272 EffectsObjectChange {
273 input_state: ObjectIn::NotExist,
274 output_state: ObjectOut::AccumulatorWriteV1(write),
275 id_operation: IDOperation::None,
276 },
277 )
278 })
279 .collect();
280
281 TransactionEffects::new_from_execution_v2(
282 ExecutionStatus::Success,
283 0,
284 GasCostSummary::default(),
285 vec![],
286 std::collections::BTreeSet::new(),
287 TransactionDigest::random(),
288 crate::base_types::SequenceNumber::new(),
289 changed_objects,
290 None,
291 None,
292 vec![],
293 )
294 }
295
296 fn sui_balance_type() -> TypeTag {
297 Balance::type_tag("0x2::sui::SUI".parse().unwrap())
298 }
299
300 fn custom_coin_type() -> TypeTag {
301 "0xabc::my_coin::MY_COIN".parse().unwrap()
302 }
303
304 fn custom_balance_type() -> TypeTag {
305 Balance::type_tag(custom_coin_type())
306 }
307
308 fn get_accumulator_obj_id(address: SuiAddress, balance_type: &TypeTag) -> ObjectID {
309 *AccumulatorValueRoot::get_field_id(address, balance_type)
310 .unwrap()
311 .inner()
312 }
313
314 #[test]
315 fn test_derive_balance_changes_with_no_accumulator_events() {
316 let effects = create_effects_with_accumulator_writes(vec![]);
317 let result = derive_balance_changes(&effects, &[], &[]);
318 assert!(result.is_empty());
319 }
320
321 #[test]
322 fn test_derive_balance_changes_with_split_accumulator_event() {
323 let address = SuiAddress::random_for_testing_only();
324 let balance_type = sui_balance_type();
325 let obj_id = get_accumulator_obj_id(address, &balance_type);
326 let write = AccumulatorWriteV1 {
327 address: AccumulatorAddress::new(address, balance_type),
328 operation: AccumulatorOperation::Split,
329 value: AccumulatorValue::Integer(1000),
330 };
331 let effects = create_effects_with_accumulator_writes(vec![(obj_id, write)]);
332
333 let result = derive_balance_changes(&effects, &[], &[]);
334
335 assert_eq!(result.len(), 1);
336 assert_eq!(result[0].address, address);
337 assert_eq!(
338 result[0].coin_type,
339 "0x2::sui::SUI".parse::<TypeTag>().unwrap()
340 );
341 assert_eq!(result[0].amount, -1000);
342 }
343
344 #[test]
345 fn test_derive_balance_changes_with_merge_accumulator_event() {
346 let address = SuiAddress::random_for_testing_only();
347 let balance_type = sui_balance_type();
348 let obj_id = get_accumulator_obj_id(address, &balance_type);
349 let write = AccumulatorWriteV1 {
350 address: AccumulatorAddress::new(address, balance_type),
351 operation: AccumulatorOperation::Merge,
352 value: AccumulatorValue::Integer(500),
353 };
354 let effects = create_effects_with_accumulator_writes(vec![(obj_id, write)]);
355
356 let result = derive_balance_changes(&effects, &[], &[]);
357
358 assert_eq!(result.len(), 1);
359 assert_eq!(result[0].address, address);
360 assert_eq!(result[0].amount, 500);
361 }
362
363 #[test]
364 fn test_derive_balance_changes_with_multiple_addresses() {
365 let address1 = SuiAddress::random_for_testing_only();
366 let address2 = SuiAddress::random_for_testing_only();
367 let balance_type = sui_balance_type();
368
369 let obj_id1 = get_accumulator_obj_id(address1, &balance_type);
370 let obj_id2 = get_accumulator_obj_id(address2, &balance_type);
371
372 let write1 = AccumulatorWriteV1 {
373 address: AccumulatorAddress::new(address1, balance_type.clone()),
374 operation: AccumulatorOperation::Split,
375 value: AccumulatorValue::Integer(1000),
376 };
377 let write2 = AccumulatorWriteV1 {
378 address: AccumulatorAddress::new(address2, balance_type),
379 operation: AccumulatorOperation::Merge,
380 value: AccumulatorValue::Integer(1000),
381 };
382
383 let effects =
384 create_effects_with_accumulator_writes(vec![(obj_id1, write1), (obj_id2, write2)]);
385
386 let result = derive_balance_changes(&effects, &[], &[]);
387
388 assert_eq!(result.len(), 2);
389 let addr1_change = result.iter().find(|c| c.address == address1).unwrap();
390 let addr2_change = result.iter().find(|c| c.address == address2).unwrap();
391 assert_eq!(addr1_change.amount, -1000);
392 assert_eq!(addr2_change.amount, 1000);
393 }
394
395 #[test]
396 fn test_derive_balance_changes_with_custom_coin_type() {
397 let address = SuiAddress::random_for_testing_only();
398 let balance_type = custom_balance_type();
399 let obj_id = get_accumulator_obj_id(address, &balance_type);
400 let write = AccumulatorWriteV1 {
401 address: AccumulatorAddress::new(address, balance_type),
402 operation: AccumulatorOperation::Split,
403 value: AccumulatorValue::Integer(2000),
404 };
405 let effects = create_effects_with_accumulator_writes(vec![(obj_id, write)]);
406
407 let result = derive_balance_changes(&effects, &[], &[]);
408
409 assert_eq!(result.len(), 1);
410 assert_eq!(result[0].address, address);
411 assert_eq!(result[0].coin_type, custom_coin_type());
412 assert_eq!(result[0].amount, -2000);
413 }
414
415 #[test]
416 fn test_derive_balance_changes_ignores_non_balance_types() {
417 let address = SuiAddress::random_for_testing_only();
418 let non_balance_type: TypeTag = "0x2::accumulator_settlement::EventStreamHead"
420 .parse()
421 .unwrap();
422 let write = AccumulatorWriteV1 {
423 address: AccumulatorAddress::new(address, non_balance_type),
424 operation: AccumulatorOperation::Split,
425 value: AccumulatorValue::Integer(1000),
426 };
427 let effects = create_effects_with_accumulator_writes(vec![(ObjectID::random(), write)]);
428
429 let result = derive_balance_changes(&effects, &[], &[]);
430
431 assert!(result.is_empty());
432 }
433
434 #[test]
435 fn test_derive_balance_changes_ignores_event_digest_values() {
436 use crate::digests::Digest;
437 use nonempty::nonempty;
438
439 let address = SuiAddress::random_for_testing_only();
440 let balance_type = sui_balance_type();
441 let obj_id = get_accumulator_obj_id(address, &balance_type);
442 let write = AccumulatorWriteV1 {
443 address: AccumulatorAddress::new(address, balance_type),
444 operation: AccumulatorOperation::Merge,
445 value: AccumulatorValue::EventDigest(nonempty![(0, Digest::random())]),
446 };
447 let effects = create_effects_with_accumulator_writes(vec![(obj_id, write)]);
448
449 let result = derive_balance_changes(&effects, &[], &[]);
450
451 assert!(result.is_empty());
452 }
453
454 #[test]
455 fn test_derive_balance_changes_accumulator_zero_amount_filtered() {
456 let address = SuiAddress::random_for_testing_only();
458 let balance_type = sui_balance_type();
459 let obj_id = get_accumulator_obj_id(address, &balance_type);
460
461 let write = AccumulatorWriteV1 {
462 address: AccumulatorAddress::new(address, balance_type),
463 operation: AccumulatorOperation::Split,
464 value: AccumulatorValue::Integer(0),
465 };
466 let effects = create_effects_with_accumulator_writes(vec![(obj_id, write)]);
467
468 let result = derive_balance_changes(&effects, &[], &[]);
469
470 assert!(result.is_empty());
472 }
473
474 #[test]
475 fn test_derive_balance_changes_2_with_accumulator_events() {
476 let address = SuiAddress::random_for_testing_only();
477 let balance_type = sui_balance_type();
478 let obj_id = get_accumulator_obj_id(address, &balance_type);
479 let write = AccumulatorWriteV1 {
480 address: AccumulatorAddress::new(address, balance_type),
481 operation: AccumulatorOperation::Split,
482 value: AccumulatorValue::Integer(1000),
483 };
484 let effects = create_effects_with_accumulator_writes(vec![(obj_id, write)]);
485
486 let objects = crate::full_checkpoint_content::ObjectSet::default();
487 let result = derive_balance_changes_2(&effects, &objects);
488
489 assert_eq!(result.len(), 1);
490 assert_eq!(result[0].address, address);
491 assert_eq!(
492 result[0].coin_type,
493 "0x2::sui::SUI".parse::<TypeTag>().unwrap()
494 );
495 assert_eq!(result[0].amount, -1000);
496 }
497
498 fn create_gas_coin_object(owner: SuiAddress, value: u64) -> Object {
501 use crate::base_types::SequenceNumber;
502 use crate::object::MoveObject;
503
504 let obj_id = ObjectID::random();
505 let move_obj = MoveObject::new_gas_coin(SequenceNumber::new(), obj_id, value);
506 Object::new_move(
507 move_obj,
508 Owner::AddressOwner(owner),
509 TransactionDigest::random(),
510 )
511 }
512
513 fn create_custom_coin_object(owner: SuiAddress, coin_type: TypeTag, value: u64) -> Object {
514 use crate::base_types::SequenceNumber;
515 use crate::object::MoveObject;
516
517 let obj_id = ObjectID::random();
518 let move_obj = MoveObject::new_coin(coin_type, SequenceNumber::new(), obj_id, value);
519 Object::new_move(
520 move_obj,
521 Owner::AddressOwner(owner),
522 TransactionDigest::random(),
523 )
524 }
525
526 #[test]
527 fn test_derive_balance_changes_with_coin_objects_only() {
528 let address = SuiAddress::random_for_testing_only();
529
530 let input_coin = create_gas_coin_object(address, 5000);
532 let output_coin = create_gas_coin_object(address, 3000);
534
535 let effects = create_effects_with_accumulator_writes(vec![]);
536
537 let result = derive_balance_changes(&effects, &[input_coin], &[output_coin]);
538
539 assert_eq!(result.len(), 1);
540 assert_eq!(result[0].address, address);
541 assert_eq!(result[0].amount, -2000); }
543
544 #[test]
545 fn test_derive_balance_changes_coin_transfer_between_addresses() {
546 let sender = SuiAddress::random_for_testing_only();
547 let receiver = SuiAddress::random_for_testing_only();
548
549 let input_coin = create_gas_coin_object(sender, 10000);
551 let output_coin_sender = create_gas_coin_object(sender, 7000);
553 let output_coin_receiver = create_gas_coin_object(receiver, 3000);
554
555 let effects = create_effects_with_accumulator_writes(vec![]);
556
557 let result = derive_balance_changes(
558 &effects,
559 &[input_coin],
560 &[output_coin_sender, output_coin_receiver],
561 );
562
563 assert_eq!(result.len(), 2);
564 let sender_change = result.iter().find(|c| c.address == sender).unwrap();
565 let receiver_change = result.iter().find(|c| c.address == receiver).unwrap();
566 assert_eq!(sender_change.amount, -3000); assert_eq!(receiver_change.amount, 3000); }
569
570 #[test]
571 fn test_derive_balance_changes_combines_coins_and_accumulator_events() {
572 let address = SuiAddress::random_for_testing_only();
573 let balance_type = sui_balance_type();
574 let obj_id = get_accumulator_obj_id(address, &balance_type);
575
576 let input_coin = create_gas_coin_object(address, 5000);
578 let output_coin = create_gas_coin_object(address, 3000);
579
580 let write = AccumulatorWriteV1 {
582 address: AccumulatorAddress::new(address, balance_type),
583 operation: AccumulatorOperation::Merge,
584 value: AccumulatorValue::Integer(500),
585 };
586 let effects = create_effects_with_accumulator_writes(vec![(obj_id, write)]);
587
588 let result = derive_balance_changes(&effects, &[input_coin], &[output_coin]);
589
590 assert_eq!(result.len(), 1);
592 assert_eq!(result[0].address, address);
593 assert_eq!(result[0].amount, -1500);
594 }
595
596 #[test]
597 fn test_derive_balance_changes_coins_and_accumulator_different_addresses() {
598 let coin_owner = SuiAddress::random_for_testing_only();
599 let accumulator_owner = SuiAddress::random_for_testing_only();
600 let balance_type = sui_balance_type();
601 let obj_id = get_accumulator_obj_id(accumulator_owner, &balance_type);
602
603 let input_coin = create_gas_coin_object(coin_owner, 5000);
605 let output_coin = create_gas_coin_object(coin_owner, 4000);
606
607 let write = AccumulatorWriteV1 {
609 address: AccumulatorAddress::new(accumulator_owner, balance_type),
610 operation: AccumulatorOperation::Merge,
611 value: AccumulatorValue::Integer(2000),
612 };
613 let effects = create_effects_with_accumulator_writes(vec![(obj_id, write)]);
614
615 let result = derive_balance_changes(&effects, &[input_coin], &[output_coin]);
616
617 assert_eq!(result.len(), 2);
618 let coin_change = result.iter().find(|c| c.address == coin_owner).unwrap();
619 let acc_change = result
620 .iter()
621 .find(|c| c.address == accumulator_owner)
622 .unwrap();
623 assert_eq!(coin_change.amount, -1000);
624 assert_eq!(acc_change.amount, 2000);
625 }
626
627 #[test]
628 fn test_derive_balance_changes_coins_and_accumulator_net_to_zero() {
629 let address = SuiAddress::random_for_testing_only();
630 let balance_type = sui_balance_type();
631 let obj_id = get_accumulator_obj_id(address, &balance_type);
632
633 let input_coin = create_gas_coin_object(address, 5000);
635 let output_coin = create_gas_coin_object(address, 4000);
636
637 let write = AccumulatorWriteV1 {
639 address: AccumulatorAddress::new(address, balance_type),
640 operation: AccumulatorOperation::Merge,
641 value: AccumulatorValue::Integer(1000),
642 };
643 let effects = create_effects_with_accumulator_writes(vec![(obj_id, write)]);
644
645 let result = derive_balance_changes(&effects, &[input_coin], &[output_coin]);
646
647 assert!(result.is_empty());
649 }
650
651 #[test]
652 fn test_derive_balance_changes_different_coin_types() {
653 let address = SuiAddress::random_for_testing_only();
654 let custom_type = custom_coin_type();
655 let custom_balance = custom_balance_type();
656 let obj_id = get_accumulator_obj_id(address, &custom_balance);
657
658 let sui_input = create_gas_coin_object(address, 5000);
660 let sui_output = create_gas_coin_object(address, 4000);
661
662 let custom_output = create_custom_coin_object(address, custom_type.clone(), 500);
664
665 let write = AccumulatorWriteV1 {
667 address: AccumulatorAddress::new(address, custom_balance),
668 operation: AccumulatorOperation::Merge,
669 value: AccumulatorValue::Integer(300),
670 };
671 let effects = create_effects_with_accumulator_writes(vec![(obj_id, write)]);
672
673 let result = derive_balance_changes(&effects, &[sui_input], &[sui_output, custom_output]);
674
675 assert_eq!(result.len(), 2);
676
677 let sui_change = result
678 .iter()
679 .find(|c| c.coin_type == "0x2::sui::SUI".parse::<TypeTag>().unwrap())
680 .unwrap();
681 let custom_change = result.iter().find(|c| c.coin_type == custom_type).unwrap();
682
683 assert_eq!(sui_change.amount, -1000);
684 assert_eq!(custom_change.amount, 800); }
686
687 #[test]
688 fn test_derive_balance_changes_accumulator_split_with_coins() {
689 let sender = SuiAddress::random_for_testing_only();
690 let receiver = SuiAddress::random_for_testing_only();
691 let balance_type = sui_balance_type();
692 let sender_obj_id = get_accumulator_obj_id(sender, &balance_type);
693 let receiver_obj_id = get_accumulator_obj_id(receiver, &balance_type.clone());
694
695 let input_coin = create_gas_coin_object(sender, 5000);
697 let output_coin = create_gas_coin_object(sender, 4000);
698
699 let sender_write = AccumulatorWriteV1 {
701 address: AccumulatorAddress::new(sender, balance_type.clone()),
702 operation: AccumulatorOperation::Split,
703 value: AccumulatorValue::Integer(500),
704 };
705 let receiver_write = AccumulatorWriteV1 {
707 address: AccumulatorAddress::new(receiver, balance_type),
708 operation: AccumulatorOperation::Merge,
709 value: AccumulatorValue::Integer(500),
710 };
711 let effects = create_effects_with_accumulator_writes(vec![
712 (sender_obj_id, sender_write),
713 (receiver_obj_id, receiver_write),
714 ]);
715
716 let result = derive_balance_changes(&effects, &[input_coin], &[output_coin]);
717
718 assert_eq!(result.len(), 2);
719 let sender_change = result.iter().find(|c| c.address == sender).unwrap();
720 let receiver_change = result.iter().find(|c| c.address == receiver).unwrap();
721
722 assert_eq!(sender_change.amount, -1500);
724 assert_eq!(receiver_change.amount, 500);
726 }
727}