Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevents EthereumBlobExporter from consuming dest when returning NotApplicable #5789

Merged
merged 16 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 12 additions & 10 deletions bridges/snowbridge/primitives/router/src/outbound/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,15 @@ where
return Err(SendError::NotApplicable)
}

let dest = destination.take().ok_or(SendError::MissingArgument)?;
// Cloning destination to avoid modifying the value so subsequent exporters can use it.
let dest = destination.clone().take().ok_or(SendError::MissingArgument)?;
if dest != Here {
log::trace!(target: "xcm::ethereum_blob_exporter", "skipped due to unmatched remote destination {dest:?}.");
return Err(SendError::NotApplicable)
}

let (local_net, local_sub) = universal_source
// Cloning universal_source to avoid modifying the value so subsequent exporters can use it.
let (local_net, local_sub) = universal_source.clone()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would maybe also change this:

log::error!(target: "xcm::ethereum_blob_exporter", "could not get global consensus from universal source '{universal_source:?}'.");
				SendError::Unroutable

to:

log::error!(target: "xcm::ethereum_blob_exporter", "could not get global consensus from universal source '{universal_source:?}'.");
				SendError::NotApplicable

to give a chance other routers in the tuple.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and other suggestion change MissingArgument->NotApplicable:

		let para_id = match local_sub.as_slice() {
			[Parachain(para_id)] => *para_id,
			_ => {
				log::error!(target: "xcm::ethereum_blob_exporter", "could not get parachain id from universal source '{local_sub:?}'.");
                                // TODO: why not `NotApplicable`?
				return Err(SendError::MissingArgument)
			},
		};
	/// A needed argument is `None` when it should be `Some`.
	MissingArgument,

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and one more, I would move message.take() as a last step after all the validations pass.
And also Unroutable could be NotApplicable to give other router a chance.

		let message = message.take().ok_or_else(|| {
			log::error!(target: "xcm::ethereum_blob_exporter", "xcm message not provided.");
			SendError::MissingArgument
		})?;
		let source_location = Location::new(1, local_sub.clone());
		let agent_id = match AgentHashedDescription::convert_location(&source_location) {
			Some(id) => id,
			None => {
				log::error!(target: "xcm::ethereum_blob_exporter", "unroutable due to not being able to create agent id. '{source_location:?}'");
				return Err(SendError::Unroutable)
			},
		};

.take()
.ok_or_else(|| {
log::error!(target: "xcm::ethereum_blob_exporter", "universal source not provided.");
Expand All @@ -84,7 +86,7 @@ where
.split_global()
.map_err(|()| {
log::error!(target: "xcm::ethereum_blob_exporter", "could not get global consensus from universal source '{universal_source:?}'.");
SendError::Unroutable
SendError::NotApplicable
})?;

if Ok(local_net) != universal_location.global_consensus() {
Expand All @@ -96,25 +98,25 @@ where
[Parachain(para_id)] => *para_id,
_ => {
log::error!(target: "xcm::ethereum_blob_exporter", "could not get parachain id from universal source '{local_sub:?}'.");
return Err(SendError::MissingArgument)
return Err(SendError::NotApplicable)
},
};

let message = message.take().ok_or_else(|| {
log::error!(target: "xcm::ethereum_blob_exporter", "xcm message not provided.");
SendError::MissingArgument
})?;

let source_location = Location::new(1, local_sub.clone());

let agent_id = match AgentHashedDescription::convert_location(&source_location) {
Some(id) => id,
None => {
log::error!(target: "xcm::ethereum_blob_exporter", "unroutable due to not being able to create agent id. '{source_location:?}'");
return Err(SendError::Unroutable)
return Err(SendError::NotApplicable)
},
};

let message = message.take().ok_or_else(|| {
log::error!(target: "xcm::ethereum_blob_exporter", "xcm message not provided.");
SendError::MissingArgument
})?;

let mut converter =
XcmConverter::<ConvertAssetId, ()>::new(&message, expected_network, agent_id);
let (command, message_id) = converter.convert().map_err(|err|{
Expand Down
116 changes: 110 additions & 6 deletions bridges/snowbridge/primitives/router/src/outbound/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ fn exporter_validate_without_universal_source_yields_missing_argument() {
}

#[test]
fn exporter_validate_without_global_universal_location_yields_unroutable() {
fn exporter_validate_without_global_universal_location_yields_not_applicable() {
let network = BridgedNetwork::get();
let channel: u32 = 0;
let mut universal_source: Option<InteriorLocation> = Here.into();
Expand All @@ -163,7 +163,7 @@ fn exporter_validate_without_global_universal_location_yields_unroutable() {
AgentIdOf,
MockTokenIdConvert,
>::validate(network, channel, &mut universal_source, &mut destination, &mut message);
assert_eq!(result, Err(XcmSendError::Unroutable));
assert_eq!(result, Err(XcmSendError::NotApplicable));
}

#[test]
Expand Down Expand Up @@ -206,7 +206,7 @@ fn exporter_validate_with_remote_universal_source_yields_not_applicable() {
}

#[test]
fn exporter_validate_without_para_id_in_source_yields_missing_argument() {
fn exporter_validate_without_para_id_in_source_yields_not_applicable() {
let network = BridgedNetwork::get();
let channel: u32 = 0;
let mut universal_source: Option<InteriorLocation> = Some(GlobalConsensus(Polkadot).into());
Expand All @@ -221,11 +221,11 @@ fn exporter_validate_without_para_id_in_source_yields_missing_argument() {
AgentIdOf,
MockTokenIdConvert,
>::validate(network, channel, &mut universal_source, &mut destination, &mut message);
assert_eq!(result, Err(XcmSendError::MissingArgument));
assert_eq!(result, Err(XcmSendError::NotApplicable));
}

#[test]
fn exporter_validate_complex_para_id_in_source_yields_missing_argument() {
fn exporter_validate_complex_para_id_in_source_yields_not_applicable() {
let network = BridgedNetwork::get();
let channel: u32 = 0;
let mut universal_source: Option<InteriorLocation> =
Expand All @@ -241,7 +241,7 @@ fn exporter_validate_complex_para_id_in_source_yields_missing_argument() {
AgentIdOf,
MockTokenIdConvert,
>::validate(network, channel, &mut universal_source, &mut destination, &mut message);
assert_eq!(result, Err(XcmSendError::MissingArgument));
assert_eq!(result, Err(XcmSendError::NotApplicable));
}

#[test]
Expand Down Expand Up @@ -1163,3 +1163,107 @@ fn xcm_converter_transfer_native_token_with_invalid_location_will_fail() {
let result = converter.convert();
assert_eq!(result.err(), Some(XcmConverterError::InvalidAsset));
}

#[test]
fn exporter_validate_with_invalid_dest_does_not_alter_destination() {
let network = BridgedNetwork::get();
let destination: InteriorLocation = Parachain(1000).into();

let universal_source: InteriorLocation = [GlobalConsensus(Polkadot), Parachain(1000)].into();

let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000");
let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000");

let channel: u32 = 0;
let assets: Assets = vec![Asset {
id: AssetId([AccountKey20 { network: None, key: token_address }].into()),
fun: Fungible(1000),
}]
.into();
let fee = assets.clone().get(0).unwrap().clone();
let filter: AssetFilter = assets.clone().into();
let msg: Xcm<()> = vec![
WithdrawAsset(assets.clone()),
ClearOrigin,
BuyExecution { fees: fee, weight_limit: Unlimited },
DepositAsset {
assets: filter,
beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(),
},
SetTopic([0; 32]),
]
.into();
let mut msg_wrapper: Option<Xcm<()>> = Some(msg.clone());
let mut dest_wrapper = Some(destination.clone());
let mut universal_source_wrapper = Some(universal_source.clone());

let result =
EthereumBlobExporter::<
UniversalLocation,
BridgedNetwork,
MockOkOutboundQueue,
AgentIdOf,
MockTokenIdConvert,
>::validate(
network, channel, &mut universal_source_wrapper, &mut dest_wrapper, &mut msg_wrapper
);

assert_eq!(result, Err(XcmSendError::NotApplicable));

// ensure mutable variables are not changed
assert_eq!(Some(destination), dest_wrapper);
assert_eq!(Some(msg), msg_wrapper);
assert_eq!(Some(universal_source), universal_source_wrapper);
}

#[test]
fn exporter_validate_with_invalid_universal_source_does_not_alter_universal_source() {
let network = BridgedNetwork::get();
let destination: InteriorLocation = Here.into();

let universal_source: InteriorLocation = [GlobalConsensus(Westend), Parachain(1000)].into();

let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000");
let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000");

let channel: u32 = 0;
let assets: Assets = vec![Asset {
id: AssetId([AccountKey20 { network: None, key: token_address }].into()),
fun: Fungible(1000),
}]
.into();
let fee = assets.clone().get(0).unwrap().clone();
let filter: AssetFilter = assets.clone().into();
let msg: Xcm<()> = vec![
WithdrawAsset(assets.clone()),
ClearOrigin,
BuyExecution { fees: fee, weight_limit: Unlimited },
DepositAsset {
assets: filter,
beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(),
},
SetTopic([0; 32]),
]
.into();
let mut msg_wrapper: Option<Xcm<()>> = Some(msg.clone());
let mut dest_wrapper = Some(destination.clone());
let mut universal_source_wrapper = Some(universal_source.clone());

let result =
EthereumBlobExporter::<
UniversalLocation,
BridgedNetwork,
MockOkOutboundQueue,
AgentIdOf,
MockTokenIdConvert,
>::validate(
network, channel, &mut universal_source_wrapper, &mut dest_wrapper, &mut msg_wrapper
);

assert_eq!(result, Err(XcmSendError::NotApplicable));

// ensure mutable variables are not changed
assert_eq!(Some(destination), dest_wrapper);
assert_eq!(Some(msg), msg_wrapper);
assert_eq!(Some(universal_source), universal_source_wrapper);
}
19 changes: 19 additions & 0 deletions prdoc/pr_5789.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json

title: Prevents EthereumBlobExporter from consuming parameters when returning NotApplicable

doc:
- audience: Node Dev
description: |
When the EthereumBlobExporter returned a NotApplicable error, it consumed parameters `universal_source`,
`destination` and `message`. As a result, subsequent exporters could not use these values. This PR corrects
this incorrect behaviour. It also changes error type from `Unroutable` to `NotApplicable` when the global consensus
system cannot be extracted from the `universal_source`, or when the source location cannot be converted to an agent
ID. Lastly, it changes the error type from `MissingArgument` to `NotApplicable` when the parachain ID cannot be
extracted from the location. These changes should have no effect - it is purely to correct behvaiour should
multiple exporters be used.

crates:
- name: snowbridge-router-primitives
bump: patch