Skip to content

Commit d5dda01

Browse files
paritytech-release-backport-bot[bot]karolk91github-actions[bot]bkontur
authored
[stable2409] Backport #9179 (#9301)
Backport #9179 into `stable2409` 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 3a7b924 commit d5dda01

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

@@ -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]

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)