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