diff --git a/polkadot/xcm/xcm-builder/src/routing.rs b/polkadot/xcm/xcm-builder/src/routing.rs index f4c18adddb37..ce3b4cb95ba3 100644 --- a/polkadot/xcm/xcm-builder/src/routing.rs +++ b/polkadot/xcm/xcm-builder/src/routing.rs @@ -104,3 +104,139 @@ impl SendXcm for WithTopicSource>) { + if let Some(xcm) = message { + let instructions = xcm.inner_mut(); + let mut initial_fund = false; + let mut clear_origin_instructions = 0; + + for item in instructions.iter().enumerate() { + match item { + (0, WithdrawAsset(assets, ..)) if assets.len() > 1 => { + initial_fund = true; + } + (n, ClearOrigin) if n > 0 && n <= 4 => { + clear_origin_instructions += 1; + } + (n, BuyExecution { fees, .. }) if n > 0 && n <= 5 && initial_fund => { + if let Some(WithdrawAsset(assets)) = instructions.first() { + let fee_asset = MultiAssets::from( + assets + .inner() + .iter() + .filter(|asset| asset.id == fees.id) + .cloned() + .collect::>(), + ); + let extra_assets = MultiAssets::from( + assets + .inner() + .iter() + .filter(|asset| asset.id != fees.id) + .cloned() + .collect::>(), + ); + + instructions[0] = WithdrawAsset(fee_asset); + instructions.insert(n + 1, WithdrawAsset(extra_assets)); + if clear_origin_instructions > 0 { + instructions.insert(n + 2, ClearOrigin); + for index in 1..1 + clear_origin_instructions { + instructions.remove(index); + } + } + } + break; + } + _ => { + break; + } + } + } + } +} + +pub struct SplitXcmRouter(InnerRouter); + +impl SendXcm for SplitXcmRouter { + type Ticket = InnerRouter::Ticket; + + fn validate(destination: &mut Option, message: &mut Option>) -> SendResult { + split_message(message); + InnerRouter::validate(destination, message) + } + + fn deliver(ticket: Self::Ticket) -> Result { + InnerRouter::deliver(ticket) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use core::cell::RefCell; + + fn fake_message_hash(message: &Xcm) -> XcmHash { + message.using_encoded(sp_io::hashing::blake2_256) + } + thread_local! { + pub static SENT_XCM: RefCell> = RefCell::new(Vec::new()); + } + pub fn sent_xcm() -> Vec<(MultiLocation, opaque::Xcm, XcmHash)> { + SENT_XCM.with(|q| (*q.borrow()).clone()) + } + pub struct TestSendXcm; + impl SendXcm for TestSendXcm { + type Ticket = (MultiLocation, Xcm<()>, XcmHash); + fn validate( + dest: &mut Option, + msg: &mut Option>, + ) -> SendResult<(MultiLocation, Xcm<()>, XcmHash)> { + let msg = msg.take().unwrap(); + let hash = fake_message_hash(&msg); + let triplet = (dest.take().unwrap(), msg, hash); + Ok((triplet, MultiAssets::new())) + } + fn deliver(triplet: (MultiLocation, Xcm<()>, XcmHash)) -> Result { + let hash = triplet.2; + SENT_XCM.with(|q| q.borrow_mut().push(triplet)); + Ok(hash) + } + } + + #[test] + fn split_xcm_router_works() { + // Split XCM router wrapping a test sender + type Router = SplitXcmRouter; + + let fee_asset: MultiAsset = (GeneralIndex(2), 100u128).into(); + let multiple_assets: MultiAssets = vec![ + (GeneralIndex(1), 100u128).into(), + fee_asset.clone(), + (GeneralIndex(3), 100u128).into(), + ].into(); + let message = Xcm(vec![ + WithdrawAsset(multiple_assets.clone()), + ClearOrigin, + BuyExecution { fees: fee_asset.clone(), weight_limit: Unlimited }, + DepositAsset { assets: AllCounted(3).into(), beneficiary: AccountId32 { id: [0u8; 32], network: None }.into() }, + ]); + let multiple_assets_without_fee: MultiAssets = vec![ + (GeneralIndex(1), 100u128).into(), + (GeneralIndex(3), 100u128).into(), + ].into(); + let expected_message = Xcm(vec![ + WithdrawAsset(fee_asset.clone().into()), + BuyExecution { fees: fee_asset.clone(), weight_limit: Unlimited }, + WithdrawAsset(multiple_assets_without_fee), + ClearOrigin, + DepositAsset { assets: AllCounted(3).into(), beneficiary: AccountId32 { id: [0u8; 32], network: None }.into() }, + ]); + let (ticket, _) = Router::validate(&mut Some(MultiLocation::parent()), &mut Some(message)).unwrap(); + let _ = Router::deliver(ticket).unwrap(); + let sent_xcms = sent_xcm(); + let (_, message_sent, _) = sent_xcms.first().unwrap(); + assert_eq!(message_sent, &expected_message); + } +}