Skip to content

Commit 2b5a0e1

Browse files
Snowbridge: Set default asset claimer to local network (#11919)
The inbound-queue v2 message converter falls back to the Snowbridge sovereign account on AssetHub as the asset claimer when no explicit claimer is supplied. Previously this fallback used `AccountId32 { network: None, .. }`, which did not match the location AssetHub's signed-origin converter produces (it sets `network: Some(LocalNetwork)`). The trap-key hash stored on `AssetsTrapped` therefore could not be matched by a signed `polkadotXcm.claim_assets` call, making default-claimer trapped funds effectively unrecoverable without a runtime upgrade. This PR sets `network: Some(LocalNetwork::get())` on the fallback claimer so its `Location` agrees with what `SignedToAccountId32<_, _, LocalNetwork>` yields on AssetHub, and adds a test covering the no-claimer-supplied path. --------- Co-authored-by: Branislav Kontur <bkontur@gmail.com>
1 parent f2bc410 commit 2b5a0e1

3 files changed

Lines changed: 426 additions & 2 deletions

File tree

bridges/snowbridge/primitives/inbound-queue/src/v2/converter.rs

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,13 @@ where
133133
.and_then(|claimer_bytes| Location::decode(&mut claimer_bytes.as_ref()).ok())
134134
// or use the Snowbridge sovereign on AH as the fallback claimer.
135135
.unwrap_or_else(|| {
136-
Location::new(0, [AccountId32 { network: None, id: bridge_owner.clone().into() }])
136+
Location::new(
137+
0,
138+
[AccountId32 {
139+
network: Some(LocalNetwork::get()),
140+
id: bridge_owner.clone().into(),
141+
}],
142+
)
137143
});
138144

139145
let mut remote_xcm: Xcm<()> = match &message.payload {
@@ -779,6 +785,8 @@ mod tests {
779785
}
780786

781787
// actual claimer should default to Snowbridge sovereign account
788+
// pinned to the local network, so the location remains unambiguous if
789+
// it is reanchored or forwarded across consensus systems.
782790
let bridge_owner = ExternalConsensusLocationsConverterFor::<
783791
AssetHubUniversal<LocalNetwork, AssetHubParaId>,
784792
[u8; 32],
@@ -789,8 +797,73 @@ mod tests {
789797
.unwrap();
790798
assert_eq!(
791799
actual_claimer,
792-
Some(Location::new(0, [AccountId32 { network: None, id: bridge_owner }]))
800+
Some(Location::new(
801+
0,
802+
[AccountId32 { network: Some(LocalNetwork::get()), id: bridge_owner }]
803+
))
804+
);
805+
});
806+
}
807+
808+
#[test]
809+
fn test_missing_claimer_defaults_to_bridge_owner_on_local_network() {
810+
sp_io::TestExternalities::default().execute_with(|| {
811+
let origin: H160 = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf").into();
812+
let native_token_id: H160 = hex!("5615deb798bb3e4dfa0139dfa1b3d433cc23b72f").into();
813+
let beneficiary =
814+
hex!("908783d8cd24c9e02cee1d26ab9c46d458621ad0150b626c536a40b9df3f09c6").into();
815+
let token_value = 3_000_000_000_000u128;
816+
let assets = vec![EthereumAsset::NativeTokenERC20 {
817+
token_id: native_token_id,
818+
value: token_value,
819+
}];
820+
let instructions =
821+
vec![DepositAsset { assets: Wild(AllCounted(1).into()), beneficiary }];
822+
let xcm: Xcm<()> = instructions.into();
823+
let versioned_xcm = VersionedXcm::V5(xcm);
824+
825+
let message = Message {
826+
gateway: H160::zero(),
827+
nonce: 0,
828+
origin,
829+
assets,
830+
payload: Payload::Raw(versioned_xcm.encode()),
831+
// No claimer supplied — fallback path should be used.
832+
claimer: None,
833+
value: 6_000_000_000_000u128,
834+
execution_fee: 1_000_000_000_000u128,
835+
relayer_fee: 5_000_000_000_000u128,
836+
};
837+
838+
let xcm = Converter::convert(message).expect("conversion succeeds");
839+
840+
let bridge_owner = ExternalConsensusLocationsConverterFor::<
841+
AssetHubUniversal<LocalNetwork, AssetHubParaId>,
842+
[u8; 32],
843+
>::convert_location(&Location::new(
844+
2,
845+
[GlobalConsensus(EthereumNetwork::get())],
846+
))
847+
.unwrap();
848+
let expected_claimer = Location::new(
849+
0,
850+
[AccountId32 { network: Some(LocalNetwork::get()), id: bridge_owner }],
793851
);
852+
853+
let claimer = xcm
854+
.into_iter()
855+
.find_map(|instruction| match instruction {
856+
SetHints { hints } => hints
857+
.into_iter()
858+
.map(|hint| match hint {
859+
AssetClaimer { location } => location,
860+
})
861+
.next(),
862+
_ => None,
863+
})
864+
.expect("AssetClaimer hint should be present");
865+
866+
assert_eq!(claimer, expected_claimer);
794867
});
795868
}
796869

0 commit comments

Comments
 (0)