99//!
1010//! The derivation path follows DIP-17: `m/9'/coin_type'/17'/account'/key_class'/index`
1111
12- use super :: address_pool:: AddressPool ;
12+ use super :: address_pool:: { AddressPool , KeySource } ;
1313use super :: metadata:: AccountMetadata ;
1414use super :: platform_address:: PlatformP2PKHAddress ;
1515use crate :: error:: { Error , Result } ;
@@ -98,30 +98,63 @@ impl ManagedPlatformAccount {
9898
9999 /// Set the credit balance for a specific address
100100 ///
101- /// This also updates the total balance by recalculating from all address balances.
101+ /// This also updates the total balance by applying the delta.
102+ /// If the address was previously unfunded (balance 0) and becomes funded,
103+ /// and a `KeySource` is provided, the address will be marked as used
104+ /// and the gap limit will be maintained.
102105 pub fn set_address_credit_balance (
103106 & mut self ,
104107 address : PlatformP2PKHAddress ,
105108 credit_balance : u64 ,
109+ key_source : Option < & KeySource > ,
106110 ) {
111+ let old_balance = self . address_balances . get ( & address) . copied ( ) . unwrap_or ( 0 ) ;
112+ let was_unfunded = old_balance == 0 ;
113+ let is_now_funded = credit_balance > 0 ;
114+
107115 self . address_balances . insert ( address, credit_balance) ;
108- self . recalculate_credit_balance ( ) ;
116+ // Apply delta to total: subtract old, add new
117+ self . credit_balance =
118+ self . credit_balance . saturating_sub ( old_balance) . saturating_add ( credit_balance) ;
109119 self . metadata . last_used = Some ( Self :: current_timestamp ( ) ) ;
120+
121+ // If address became funded and we have a key source, update address pool
122+ if was_unfunded && is_now_funded {
123+ if let Some ( ks) = key_source {
124+ self . mark_and_maintain_gap_limit ( & address, ks) ;
125+ }
126+ }
110127 }
111128
112129 /// Add credits to a specific address balance
113130 ///
114131 /// Returns the new credit balance for the address.
132+ /// If the address was previously unfunded (balance 0) and becomes funded,
133+ /// and a `KeySource` is provided, the address will be marked as used
134+ /// and the gap limit will be maintained.
115135 pub fn add_address_credit_balance (
116136 & mut self ,
117137 address : PlatformP2PKHAddress ,
118138 amount : u64 ,
139+ key_source : Option < & KeySource > ,
119140 ) -> u64 {
120141 let current = self . address_balances . get ( & address) . copied ( ) . unwrap_or ( 0 ) ;
142+ let was_unfunded = current == 0 ;
121143 let new_balance = current. saturating_add ( amount) ;
144+ let is_now_funded = new_balance > 0 ;
145+
122146 self . address_balances . insert ( address, new_balance) ;
123- self . recalculate_credit_balance ( ) ;
147+ // Add the amount to the total (saturating to handle overflow)
148+ self . credit_balance = self . credit_balance . saturating_add ( amount) ;
124149 self . metadata . last_used = Some ( Self :: current_timestamp ( ) ) ;
150+
151+ // If address became funded and we have a key source, update address pool
152+ if was_unfunded && is_now_funded {
153+ if let Some ( ks) = key_source {
154+ self . mark_and_maintain_gap_limit ( & address, ks) ;
155+ }
156+ }
157+
125158 new_balance
126159 }
127160
@@ -135,9 +168,12 @@ impl ManagedPlatformAccount {
135168 amount : u64 ,
136169 ) -> u64 {
137170 let current = self . address_balances . get ( & address) . copied ( ) . unwrap_or ( 0 ) ;
171+ // Only subtract what was actually removed (may be less due to saturating)
172+ let actual_removed = current. min ( amount) ;
138173 let new_balance = current. saturating_sub ( amount) ;
139174 self . address_balances . insert ( address, new_balance) ;
140- self . recalculate_credit_balance ( ) ;
175+ // Subtract only what was actually removed from the total
176+ self . credit_balance = self . credit_balance . saturating_sub ( actual_removed) ;
141177 self . metadata . last_used = Some ( Self :: current_timestamp ( ) ) ;
142178 new_balance
143179 }
@@ -234,6 +270,34 @@ impl ManagedPlatformAccount {
234270 self . mark_address_used ( & dashcore_addr)
235271 }
236272
273+ /// Mark a platform address as used and maintain the gap limit
274+ ///
275+ /// This is called internally when an address receives funds for the first time.
276+ /// It marks the address as used in the address pool and generates new addresses
277+ /// if needed to maintain the gap limit.
278+ fn mark_and_maintain_gap_limit (
279+ & mut self ,
280+ address : & PlatformP2PKHAddress ,
281+ key_source : & KeySource ,
282+ ) {
283+ // Mark the address as used
284+ self . mark_platform_address_used ( address) ;
285+
286+ // Maintain gap limit - generate new addresses if needed
287+ // We ignore errors here since this is a best-effort operation
288+ let _ = self . addresses . maintain_gap_limit ( key_source) ;
289+ }
290+
291+ /// Maintain the gap limit for the address pool
292+ ///
293+ /// This generates new addresses if needed to maintain the gap limit.
294+ /// Returns the newly generated addresses.
295+ pub fn maintain_gap_limit ( & mut self , key_source : & KeySource ) -> Result < Vec < Address > > {
296+ self . addresses
297+ . maintain_gap_limit ( key_source)
298+ . map_err ( |e| Error :: InvalidParameter ( format ! ( "Failed to maintain gap limit: {}" , e) ) )
299+ }
300+
237301 /// Get address info for a given address
238302 pub fn get_address_info ( & self , address : & Address ) -> Option < super :: address_pool:: AddressInfo > {
239303 self . addresses . address_info ( address) . cloned ( )
@@ -382,28 +446,51 @@ mod tests {
382446 let pool = create_test_pool ( ) ;
383447 let mut account = ManagedPlatformAccount :: new ( 0 , 0 , Network :: Testnet , pool, false ) ;
384448
385- // Set total credit balance
386- account. set_credit_balance ( 1000 ) ;
387- assert_eq ! ( account. total_credit_balance( ) , 1000 ) ;
388- assert_eq ! ( account. duff_balance( ) , 1 ) ; // 1000 credits = 1 duff
389-
390- // Set address credit balance
449+ // Set address credit balance (this also updates total)
391450 let addr = PlatformP2PKHAddress :: new ( [ 0x11 ; 20 ] ) ;
392- account. set_address_credit_balance ( addr, 500 ) ;
451+ account. set_address_credit_balance ( addr, 500 , None ) ;
393452 assert_eq ! ( account. address_credit_balance( & addr) , 500 ) ;
394- assert_eq ! ( account. total_credit_balance( ) , 500 ) ; // Recalculated from address balances
453+ assert_eq ! ( account. total_credit_balance( ) , 500 ) ;
454+ assert_eq ! ( account. duff_balance( ) , 0 ) ; // 500 credits = 0 duffs (integer division)
395455
396456 // Add to address credit balance
397- let new_balance = account. add_address_credit_balance ( addr, 200 ) ;
398- assert_eq ! ( new_balance, 700 ) ;
399- assert_eq ! ( account. total_credit_balance( ) , 700 ) ;
457+ let new_balance = account. add_address_credit_balance ( addr, 500 , None ) ;
458+ assert_eq ! ( new_balance, 1000 ) ;
459+ assert_eq ! ( account. total_credit_balance( ) , 1000 ) ;
460+ assert_eq ! ( account. duff_balance( ) , 1 ) ; // 1000 credits = 1 duff
461+
462+ // Add more
463+ let new_balance = account. add_address_credit_balance ( addr, 200 , None ) ;
464+ assert_eq ! ( new_balance, 1200 ) ;
465+ assert_eq ! ( account. total_credit_balance( ) , 1200 ) ;
400466
401467 // Remove from address credit balance
402468 let new_balance = account. remove_address_credit_balance ( addr, 100 ) ;
403- assert_eq ! ( new_balance, 600 ) ;
469+ assert_eq ! ( new_balance, 1100 ) ;
470+ assert_eq ! ( account. total_credit_balance( ) , 1100 ) ;
471+
472+ // Update address balance directly (replacing existing)
473+ account. set_address_credit_balance ( addr, 600 , None ) ;
474+ assert_eq ! ( account. address_credit_balance( & addr) , 600 ) ;
404475 assert_eq ! ( account. total_credit_balance( ) , 600 ) ;
405476 }
406477
478+ #[ test]
479+ fn test_set_credit_balance_directly ( ) {
480+ let pool = create_test_pool ( ) ;
481+ let mut account = ManagedPlatformAccount :: new ( 0 , 0 , Network :: Testnet , pool, false ) ;
482+
483+ // set_credit_balance is for direct manipulation (e.g., deserialization)
484+ // When used alone (no address balances), it just sets the total
485+ account. set_credit_balance ( 1000 ) ;
486+ assert_eq ! ( account. total_credit_balance( ) , 1000 ) ;
487+ assert_eq ! ( account. duff_balance( ) , 1 ) ;
488+
489+ account. set_credit_balance ( 2500 ) ;
490+ assert_eq ! ( account. total_credit_balance( ) , 2500 ) ;
491+ assert_eq ! ( account. duff_balance( ) , 2 ) ;
492+ }
493+
407494 #[ test]
408495 fn test_duff_balance_conversion ( ) {
409496 let pool = create_test_pool ( ) ;
@@ -438,15 +525,15 @@ mod tests {
438525 let addr2 = PlatformP2PKHAddress :: new ( [ 0x22 ; 20 ] ) ;
439526 let addr3 = PlatformP2PKHAddress :: new ( [ 0x33 ; 20 ] ) ;
440527
441- account. set_address_credit_balance ( addr1, 100 ) ;
442- account. set_address_credit_balance ( addr2, 200 ) ;
443- account. set_address_credit_balance ( addr3, 300 ) ;
528+ account. set_address_credit_balance ( addr1, 100 , None ) ;
529+ account. set_address_credit_balance ( addr2, 200 , None ) ;
530+ account. set_address_credit_balance ( addr3, 300 , None ) ;
444531
445532 assert_eq ! ( account. total_credit_balance( ) , 600 ) ;
446533 assert_eq ! ( account. funded_address_count( ) , 3 ) ;
447534
448535 // Set one to zero
449- account. set_address_credit_balance ( addr2, 0 ) ;
536+ account. set_address_credit_balance ( addr2, 0 , None ) ;
450537 assert_eq ! ( account. total_credit_balance( ) , 400 ) ;
451538 assert_eq ! ( account. funded_address_count( ) , 2 ) ;
452539 }
@@ -457,7 +544,7 @@ mod tests {
457544 let mut account = ManagedPlatformAccount :: new ( 0 , 0 , Network :: Testnet , pool, false ) ;
458545
459546 let addr = PlatformP2PKHAddress :: new ( [ 0x11 ; 20 ] ) ;
460- account. set_address_credit_balance ( addr, 1000 ) ;
547+ account. set_address_credit_balance ( addr, 1000 , None ) ;
461548
462549 account. clear_balances ( ) ;
463550 assert_eq ! ( account. total_credit_balance( ) , 0 ) ;
@@ -473,9 +560,9 @@ mod tests {
473560 let addr2 = PlatformP2PKHAddress :: new ( [ 0x22 ; 20 ] ) ;
474561 let addr3 = PlatformP2PKHAddress :: new ( [ 0x33 ; 20 ] ) ;
475562
476- account. set_address_credit_balance ( addr1, 100 ) ;
477- account. set_address_credit_balance ( addr2, 0 ) ; // Zero balance
478- account. set_address_credit_balance ( addr3, 300 ) ;
563+ account. set_address_credit_balance ( addr1, 100 , None ) ;
564+ account. set_address_credit_balance ( addr2, 0 , None ) ; // Zero balance
565+ account. set_address_credit_balance ( addr3, 300 , None ) ;
479566
480567 let funded = account. funded_addresses ( ) ;
481568 assert_eq ! ( funded. len( ) , 2 ) ;
@@ -495,7 +582,7 @@ mod tests {
495582 assert ! ( !account. address_balances. contains_key( & addr) ) ;
496583
497584 // Add to balances
498- account. set_address_credit_balance ( addr, 100 ) ;
585+ account. set_address_credit_balance ( addr, 100 , None ) ;
499586 assert ! ( account. contains_platform_address( & addr) ) ;
500587 }
501588}
0 commit comments