@@ -132,34 +132,21 @@ impl AssetsInHolding {
132132
133133 /// Mutate `self` to contain all given `assets`, saturating if necessary.
134134 ///
135- /// NOTE: [`AssetsInHolding`] are always sorted, allowing us to optimize this function from
136- /// `O(n^2)` to `O(n)`.
135+ /// NOTE: [`AssetsInHolding`] are always sorted
137136 pub fn subsume_assets ( & mut self , mut assets : AssetsInHolding ) {
138- let mut f_iter = assets. fungible . iter_mut ( ) ;
139- let mut g_iter = self . fungible . iter_mut ( ) ;
140- if let ( Some ( mut f) , Some ( mut g) ) = ( f_iter. next ( ) , g_iter. next ( ) ) {
141- loop {
142- if f. 0 == g. 0 {
143- // keys are equal. in this case, we add `self`'s balance for the asset onto
144- // `assets`, balance, knowing that the `append` operation which follows will
145- // clobber `self`'s value and only use `assets`'s.
146- ( * f. 1 ) . saturating_accrue ( * g. 1 ) ;
147- }
148- if f. 0 <= g. 0 {
149- f = match f_iter. next ( ) {
150- Some ( x) => x,
151- None => break ,
152- } ;
153- }
154- if f. 0 >= g. 0 {
155- g = match g_iter. next ( ) {
156- Some ( x) => x,
157- None => break ,
158- } ;
159- }
160- }
137+ // for fungibles, find matching fungibles and sum their amounts so we end-up having just
138+ // single such fungible but with increased amount inside
139+ for ( asset_id, asset_amount) in assets. fungible {
140+ self . fungible
141+ . entry ( asset_id)
142+ . and_modify ( |current_asset_amount| {
143+ current_asset_amount. saturating_accrue ( asset_amount)
144+ } )
145+ . or_insert ( asset_amount) ;
161146 }
162- self . fungible . append ( & mut assets. fungible ) ;
147+ // for non-fungibles, every entry is unique so there is no notion of amount to sum-up
148+ // together if there is the same non-fungible in both holdings (same instance_id) these
149+ // will be collapsed into just single one
163150 self . non_fungible . append ( & mut assets. non_fungible ) ;
164151 }
165152
@@ -529,6 +516,21 @@ mod tests {
529516 ( Here , amount) . into ( )
530517 }
531518 #[ allow( non_snake_case) ]
519+ /// Concrete fungible constructor with index for GeneralIndex
520+ fn CFG ( index : u128 , amount : u128 ) -> Asset {
521+ ( GeneralIndex ( index) , amount) . into ( )
522+ }
523+ #[ allow( non_snake_case) ]
524+ /// Concrete fungible constructor (parent=1)
525+ fn CFP ( amount : u128 ) -> Asset {
526+ ( Parent , amount) . into ( )
527+ }
528+ #[ allow( non_snake_case) ]
529+ /// Concrete fungible constructor (parent=2)
530+ fn CFPP ( amount : u128 ) -> Asset {
531+ ( ( Parent , Parent ) , amount) . into ( )
532+ }
533+ #[ allow( non_snake_case) ]
532534 /// Concrete non-fungible constructor
533535 fn CNF ( instance_id : u8 ) -> Asset {
534536 ( Here , [ instance_id; 4 ] ) . into ( )
@@ -542,18 +544,156 @@ mod tests {
542544 }
543545
544546 #[ test]
545- fn subsume_assets_works ( ) {
546- let t1 = test_assets ( ) ;
547+ fn assets_in_holding_order_works ( ) {
548+ // populate assets in non-ordered fashion
549+ let mut assets = AssetsInHolding :: new ( ) ;
550+ assets. subsume ( CFPP ( 300 ) ) ;
551+ assets. subsume ( CFP ( 200 ) ) ;
552+ assets. subsume ( CNF ( 2 ) ) ;
553+ assets. subsume ( CF ( 100 ) ) ;
554+ assets. subsume ( CNF ( 1 ) ) ;
555+ assets. subsume ( CFG ( 10 , 400 ) ) ;
556+ assets. subsume ( CFG ( 15 , 500 ) ) ;
557+
558+ // following is the order we expect from AssetsInHolding
559+ // - fungibles before non-fungibles
560+ // - for fungibles, sort by parent first, if parents match, then by other components like
561+ // general index
562+ // - for non-fungibles, sort by instance_id
563+ let mut iter = assets. clone ( ) . into_assets_iter ( ) ;
564+ // fungible, order by parent, parent=0
565+ assert_eq ! ( Some ( CF ( 100 ) ) , iter. next( ) ) ;
566+ // fungible, order by parent then by general index, parent=0, general index=10
567+ assert_eq ! ( Some ( CFG ( 10 , 400 ) ) , iter. next( ) ) ;
568+ // fungible, order by parent then by general index, parent=0, general index=15
569+ assert_eq ! ( Some ( CFG ( 15 , 500 ) ) , iter. next( ) ) ;
570+ // fungible, order by parent, parent=1
571+ assert_eq ! ( Some ( CFP ( 200 ) ) , iter. next( ) ) ;
572+ // fungible, order by parent, parent=2
573+ assert_eq ! ( Some ( CFPP ( 300 ) ) , iter. next( ) ) ;
574+ // non-fungible, after fungibles, order by instance id, id=1
575+ assert_eq ! ( Some ( CNF ( 1 ) ) , iter. next( ) ) ;
576+ // non-fungible, after fungibles, order by instance id, id=2
577+ assert_eq ! ( Some ( CNF ( 2 ) ) , iter. next( ) ) ;
578+ // nothing else in the assets
579+ assert_eq ! ( None , iter. next( ) ) ;
580+
581+ // lets add copy of the assets to the assets itself, just to check if order stays the same
582+ // we also expect 2x amount for every fungible and collapsed non-fungibles
583+ let assets_same = assets. clone ( ) ;
584+ assets. subsume_assets ( assets_same) ;
585+
586+ let mut iter = assets. into_assets_iter ( ) ;
587+ assert_eq ! ( Some ( CF ( 200 ) ) , iter. next( ) ) ;
588+ assert_eq ! ( Some ( CFG ( 10 , 800 ) ) , iter. next( ) ) ;
589+ assert_eq ! ( Some ( CFG ( 15 , 1000 ) ) , iter. next( ) ) ;
590+ assert_eq ! ( Some ( CFP ( 400 ) ) , iter. next( ) ) ;
591+ assert_eq ! ( Some ( CFPP ( 600 ) ) , iter. next( ) ) ;
592+ assert_eq ! ( Some ( CNF ( 1 ) ) , iter. next( ) ) ;
593+ assert_eq ! ( Some ( CNF ( 2 ) ) , iter. next( ) ) ;
594+ assert_eq ! ( None , iter. next( ) ) ;
595+ }
596+
597+ #[ test]
598+ fn subsume_assets_equal_length_holdings ( ) {
599+ let mut t1 = test_assets ( ) ;
547600 let mut t2 = AssetsInHolding :: new ( ) ;
548601 t2. subsume ( CF ( 300 ) ) ;
549602 t2. subsume ( CNF ( 50 ) ) ;
550- let mut r1 = t1. clone ( ) ;
551- r1. subsume_assets ( t2. clone ( ) ) ;
552- let mut r2 = t1. clone ( ) ;
553- for a in t2. assets_iter ( ) {
554- r2. subsume ( a)
555- }
556- assert_eq ! ( r1, r2) ;
603+
604+ let t1_clone = t1. clone ( ) ;
605+ let mut t2_clone = t2. clone ( ) ;
606+
607+ // ensure values for same fungibles are summed up together
608+ // and order is also ok (see assets_in_holding_order_works())
609+ t1. subsume_assets ( t2. clone ( ) ) ;
610+ let mut iter = t1. into_assets_iter ( ) ;
611+ assert_eq ! ( Some ( CF ( 600 ) ) , iter. next( ) ) ;
612+ assert_eq ! ( Some ( CNF ( 40 ) ) , iter. next( ) ) ;
613+ assert_eq ! ( Some ( CNF ( 50 ) ) , iter. next( ) ) ;
614+ assert_eq ! ( None , iter. next( ) ) ;
615+
616+ // try the same initial holdings but other way around
617+ // expecting same exact result as above
618+ t2_clone. subsume_assets ( t1_clone. clone ( ) ) ;
619+ let mut iter = t2_clone. into_assets_iter ( ) ;
620+ assert_eq ! ( Some ( CF ( 600 ) ) , iter. next( ) ) ;
621+ assert_eq ! ( Some ( CNF ( 40 ) ) , iter. next( ) ) ;
622+ assert_eq ! ( Some ( CNF ( 50 ) ) , iter. next( ) ) ;
623+ assert_eq ! ( None , iter. next( ) ) ;
624+ }
625+
626+ #[ test]
627+ fn subsume_assets_different_length_holdings ( ) {
628+ let mut t1 = AssetsInHolding :: new ( ) ;
629+ t1. subsume ( CFP ( 400 ) ) ;
630+ t1. subsume ( CFPP ( 100 ) ) ;
631+
632+ let mut t2 = AssetsInHolding :: new ( ) ;
633+ t2. subsume ( CF ( 100 ) ) ;
634+ t2. subsume ( CNF ( 50 ) ) ;
635+ t2. subsume ( CNF ( 40 ) ) ;
636+ t2. subsume ( CFP ( 100 ) ) ;
637+ t2. subsume ( CFPP ( 100 ) ) ;
638+
639+ let t1_clone = t1. clone ( ) ;
640+ let mut t2_clone = t2. clone ( ) ;
641+
642+ // ensure values for same fungibles are summed up together
643+ // and order is also ok (see assets_in_holding_order_works())
644+ t1. subsume_assets ( t2) ;
645+ let mut iter = t1. into_assets_iter ( ) ;
646+ assert_eq ! ( Some ( CF ( 100 ) ) , iter. next( ) ) ;
647+ assert_eq ! ( Some ( CFP ( 500 ) ) , iter. next( ) ) ;
648+ assert_eq ! ( Some ( CFPP ( 200 ) ) , iter. next( ) ) ;
649+ assert_eq ! ( Some ( CNF ( 40 ) ) , iter. next( ) ) ;
650+ assert_eq ! ( Some ( CNF ( 50 ) ) , iter. next( ) ) ;
651+ assert_eq ! ( None , iter. next( ) ) ;
652+
653+ // try the same initial holdings but other way around
654+ // expecting same exact result as above
655+ t2_clone. subsume_assets ( t1_clone) ;
656+ let mut iter = t2_clone. into_assets_iter ( ) ;
657+ assert_eq ! ( Some ( CF ( 100 ) ) , iter. next( ) ) ;
658+ assert_eq ! ( Some ( CFP ( 500 ) ) , iter. next( ) ) ;
659+ assert_eq ! ( Some ( CFPP ( 200 ) ) , iter. next( ) ) ;
660+ assert_eq ! ( Some ( CNF ( 40 ) ) , iter. next( ) ) ;
661+ assert_eq ! ( Some ( CNF ( 50 ) ) , iter. next( ) ) ;
662+ assert_eq ! ( None , iter. next( ) ) ;
663+ }
664+
665+ #[ test]
666+ fn subsume_assets_empty_holding ( ) {
667+ let mut t1 = AssetsInHolding :: new ( ) ;
668+ let t2 = AssetsInHolding :: new ( ) ;
669+ t1. subsume_assets ( t2. clone ( ) ) ;
670+ let mut iter = t1. clone ( ) . into_assets_iter ( ) ;
671+ assert_eq ! ( None , iter. next( ) ) ;
672+
673+ t1. subsume ( CFP ( 400 ) ) ;
674+ t1. subsume ( CNF ( 40 ) ) ;
675+ t1. subsume ( CFPP ( 100 ) ) ;
676+
677+ let t1_clone = t1. clone ( ) ;
678+ let mut t2_clone = t2. clone ( ) ;
679+
680+ // ensure values for same fungibles are summed up together
681+ // and order is also ok (see assets_in_holding_order_works())
682+ t1. subsume_assets ( t2. clone ( ) ) ;
683+ let mut iter = t1. into_assets_iter ( ) ;
684+ assert_eq ! ( Some ( CFP ( 400 ) ) , iter. next( ) ) ;
685+ assert_eq ! ( Some ( CFPP ( 100 ) ) , iter. next( ) ) ;
686+ assert_eq ! ( Some ( CNF ( 40 ) ) , iter. next( ) ) ;
687+ assert_eq ! ( None , iter. next( ) ) ;
688+
689+ // try the same initial holdings but other way around
690+ // expecting same exact result as above
691+ t2_clone. subsume_assets ( t1_clone. clone ( ) ) ;
692+ let mut iter = t2_clone. into_assets_iter ( ) ;
693+ assert_eq ! ( Some ( CFP ( 400 ) ) , iter. next( ) ) ;
694+ assert_eq ! ( Some ( CFPP ( 100 ) ) , iter. next( ) ) ;
695+ assert_eq ! ( Some ( CNF ( 40 ) ) , iter. next( ) ) ;
696+ assert_eq ! ( None , iter. next( ) ) ;
557697 }
558698
559699 #[ test]
0 commit comments