Skip to content

Commit bcf4010

Browse files
paritytech-release-backport-bot[bot]karolk91github-actions[bot]bkontur
authored
[stable2506] Backport #9179 (#9304)
Backport #9179 into `stable2506` from karolk91. See the [documentation](https://github.com/paritytech/polkadot-sdk/blob/master/docs/BACKPORT.md) on how to use this bot. <!-- # To be used by other automation, do not modify: original-pr-number: #${pull_number} --> Co-authored-by: Karol Kokoszka <[email protected]> Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Branislav Kontur <[email protected]>
1 parent 6fd693e commit bcf4010

File tree

2 files changed

+185
-35
lines changed

2 files changed

+185
-35
lines changed

polkadot/xcm/xcm-executor/src/assets.rs

Lines changed: 175 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -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

@@ -530,6 +517,21 @@ mod tests {
530517
(Here, amount).into()
531518
}
532519
#[allow(non_snake_case)]
520+
/// Concrete fungible constructor with index for GeneralIndex
521+
fn CFG(index: u128, amount: u128) -> Asset {
522+
(GeneralIndex(index), amount).into()
523+
}
524+
#[allow(non_snake_case)]
525+
/// Concrete fungible constructor (parent=1)
526+
fn CFP(amount: u128) -> Asset {
527+
(Parent, amount).into()
528+
}
529+
#[allow(non_snake_case)]
530+
/// Concrete fungible constructor (parent=2)
531+
fn CFPP(amount: u128) -> Asset {
532+
((Parent, Parent), amount).into()
533+
}
534+
#[allow(non_snake_case)]
533535
/// Concrete non-fungible constructor
534536
fn CNF(instance_id: u8) -> Asset {
535537
(Here, [instance_id; 4]).into()
@@ -543,18 +545,156 @@ mod tests {
543545
}
544546

545547
#[test]
546-
fn subsume_assets_works() {
547-
let t1 = test_assets();
548+
fn assets_in_holding_order_works() {
549+
// populate assets in non-ordered fashion
550+
let mut assets = AssetsInHolding::new();
551+
assets.subsume(CFPP(300));
552+
assets.subsume(CFP(200));
553+
assets.subsume(CNF(2));
554+
assets.subsume(CF(100));
555+
assets.subsume(CNF(1));
556+
assets.subsume(CFG(10, 400));
557+
assets.subsume(CFG(15, 500));
558+
559+
// following is the order we expect from AssetsInHolding
560+
// - fungibles before non-fungibles
561+
// - for fungibles, sort by parent first, if parents match, then by other components like
562+
// general index
563+
// - for non-fungibles, sort by instance_id
564+
let mut iter = assets.clone().into_assets_iter();
565+
// fungible, order by parent, parent=0
566+
assert_eq!(Some(CF(100)), iter.next());
567+
// fungible, order by parent then by general index, parent=0, general index=10
568+
assert_eq!(Some(CFG(10, 400)), iter.next());
569+
// fungible, order by parent then by general index, parent=0, general index=15
570+
assert_eq!(Some(CFG(15, 500)), iter.next());
571+
// fungible, order by parent, parent=1
572+
assert_eq!(Some(CFP(200)), iter.next());
573+
// fungible, order by parent, parent=2
574+
assert_eq!(Some(CFPP(300)), iter.next());
575+
// non-fungible, after fungibles, order by instance id, id=1
576+
assert_eq!(Some(CNF(1)), iter.next());
577+
// non-fungible, after fungibles, order by instance id, id=2
578+
assert_eq!(Some(CNF(2)), iter.next());
579+
// nothing else in the assets
580+
assert_eq!(None, iter.next());
581+
582+
// lets add copy of the assets to the assets itself, just to check if order stays the same
583+
// we also expect 2x amount for every fungible and collapsed non-fungibles
584+
let assets_same = assets.clone();
585+
assets.subsume_assets(assets_same);
586+
587+
let mut iter = assets.into_assets_iter();
588+
assert_eq!(Some(CF(200)), iter.next());
589+
assert_eq!(Some(CFG(10, 800)), iter.next());
590+
assert_eq!(Some(CFG(15, 1000)), iter.next());
591+
assert_eq!(Some(CFP(400)), iter.next());
592+
assert_eq!(Some(CFPP(600)), iter.next());
593+
assert_eq!(Some(CNF(1)), iter.next());
594+
assert_eq!(Some(CNF(2)), iter.next());
595+
assert_eq!(None, iter.next());
596+
}
597+
598+
#[test]
599+
fn subsume_assets_equal_length_holdings() {
600+
let mut t1 = test_assets();
548601
let mut t2 = AssetsInHolding::new();
549602
t2.subsume(CF(300));
550603
t2.subsume(CNF(50));
551-
let mut r1 = t1.clone();
552-
r1.subsume_assets(t2.clone());
553-
let mut r2 = t1.clone();
554-
for a in t2.assets_iter() {
555-
r2.subsume(a)
556-
}
557-
assert_eq!(r1, r2);
604+
605+
let t1_clone = t1.clone();
606+
let mut t2_clone = t2.clone();
607+
608+
// ensure values for same fungibles are summed up together
609+
// and order is also ok (see assets_in_holding_order_works())
610+
t1.subsume_assets(t2.clone());
611+
let mut iter = t1.into_assets_iter();
612+
assert_eq!(Some(CF(600)), iter.next());
613+
assert_eq!(Some(CNF(40)), iter.next());
614+
assert_eq!(Some(CNF(50)), iter.next());
615+
assert_eq!(None, iter.next());
616+
617+
// try the same initial holdings but other way around
618+
// expecting same exact result as above
619+
t2_clone.subsume_assets(t1_clone.clone());
620+
let mut iter = t2_clone.into_assets_iter();
621+
assert_eq!(Some(CF(600)), iter.next());
622+
assert_eq!(Some(CNF(40)), iter.next());
623+
assert_eq!(Some(CNF(50)), iter.next());
624+
assert_eq!(None, iter.next());
625+
}
626+
627+
#[test]
628+
fn subsume_assets_different_length_holdings() {
629+
let mut t1 = AssetsInHolding::new();
630+
t1.subsume(CFP(400));
631+
t1.subsume(CFPP(100));
632+
633+
let mut t2 = AssetsInHolding::new();
634+
t2.subsume(CF(100));
635+
t2.subsume(CNF(50));
636+
t2.subsume(CNF(40));
637+
t2.subsume(CFP(100));
638+
t2.subsume(CFPP(100));
639+
640+
let t1_clone = t1.clone();
641+
let mut t2_clone = t2.clone();
642+
643+
// ensure values for same fungibles are summed up together
644+
// and order is also ok (see assets_in_holding_order_works())
645+
t1.subsume_assets(t2);
646+
let mut iter = t1.into_assets_iter();
647+
assert_eq!(Some(CF(100)), iter.next());
648+
assert_eq!(Some(CFP(500)), iter.next());
649+
assert_eq!(Some(CFPP(200)), iter.next());
650+
assert_eq!(Some(CNF(40)), iter.next());
651+
assert_eq!(Some(CNF(50)), iter.next());
652+
assert_eq!(None, iter.next());
653+
654+
// try the same initial holdings but other way around
655+
// expecting same exact result as above
656+
t2_clone.subsume_assets(t1_clone);
657+
let mut iter = t2_clone.into_assets_iter();
658+
assert_eq!(Some(CF(100)), iter.next());
659+
assert_eq!(Some(CFP(500)), iter.next());
660+
assert_eq!(Some(CFPP(200)), iter.next());
661+
assert_eq!(Some(CNF(40)), iter.next());
662+
assert_eq!(Some(CNF(50)), iter.next());
663+
assert_eq!(None, iter.next());
664+
}
665+
666+
#[test]
667+
fn subsume_assets_empty_holding() {
668+
let mut t1 = AssetsInHolding::new();
669+
let t2 = AssetsInHolding::new();
670+
t1.subsume_assets(t2.clone());
671+
let mut iter = t1.clone().into_assets_iter();
672+
assert_eq!(None, iter.next());
673+
674+
t1.subsume(CFP(400));
675+
t1.subsume(CNF(40));
676+
t1.subsume(CFPP(100));
677+
678+
let t1_clone = t1.clone();
679+
let mut t2_clone = t2.clone();
680+
681+
// ensure values for same fungibles are summed up together
682+
// and order is also ok (see assets_in_holding_order_works())
683+
t1.subsume_assets(t2.clone());
684+
let mut iter = t1.into_assets_iter();
685+
assert_eq!(Some(CFP(400)), iter.next());
686+
assert_eq!(Some(CFPP(100)), iter.next());
687+
assert_eq!(Some(CNF(40)), iter.next());
688+
assert_eq!(None, iter.next());
689+
690+
// try the same initial holdings but other way around
691+
// expecting same exact result as above
692+
t2_clone.subsume_assets(t1_clone.clone());
693+
let mut iter = t2_clone.into_assets_iter();
694+
assert_eq!(Some(CFP(400)), iter.next());
695+
assert_eq!(Some(CFPP(100)), iter.next());
696+
assert_eq!(Some(CNF(40)), iter.next());
697+
assert_eq!(None, iter.next());
558698
}
559699

560700
#[test]

prdoc/pr_9179.prdoc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
title: 'Fix subsume_assets incorrectly merging two AssetsInHolding'
2+
doc:
3+
- audience: Runtime Dev
4+
description: |
5+
Fix subsume_assets incorrectly merging two AssetsInHolding instances under certain conditions,
6+
which caused asset values to be overridden rather than summed.
7+
8+
crates:
9+
- name: staging-xcm-executor
10+
bump: patch

0 commit comments

Comments
 (0)