From 0e9cca01a38644312ff4285f06c4536f5a9eabc4 Mon Sep 17 00:00:00 2001 From: ron Date: Fri, 25 Oct 2024 00:12:35 +0800 Subject: [PATCH 01/20] Rebase inbound queue --- .../inbound-queue-v2/src/benchmarking/mod.rs | 15 - .../pallets/inbound-queue-v2/src/envelope.rs | 15 +- .../pallets/inbound-queue-v2/src/lib.rs | 203 ++------- .../pallets/inbound-queue-v2/src/mock.rs | 155 ++----- .../pallets/inbound-queue-v2/src/test.rs | 53 --- .../primitives/router/src/inbound/v2.rs | 430 +----------------- .../src/bridge_to_ethereum_config.rs | 19 +- 7 files changed, 101 insertions(+), 789 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/benchmarking/mod.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/benchmarking/mod.rs index 52461a8a7fbe..4c5df07b27ac 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/benchmarking/mod.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/benchmarking/mod.rs @@ -23,21 +23,6 @@ mod benchmarks { create_message.block_roots_root, ); - let sovereign_account = sibling_sovereign_account::(1000u32.into()); - - let minimum_balance = T::Token::minimum_balance(); - - // So that the receiving account exists - assert_ok!(T::Token::mint_into(&caller, minimum_balance)); - // Fund the sovereign account (parachain sovereign account) so it can transfer a reward - // fee to the caller account - assert_ok!(T::Token::mint_into( - &sovereign_account, - 3_000_000_000_000u128 - .try_into() - .unwrap_or_else(|_| panic!("unable to cast sovereign account balance")), - )); - #[block] { assert_ok!(InboundQueue::::submit( diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs index 31a8992442d8..8c9b137c64ba 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs @@ -1,15 +1,15 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023 Snowfork -use snowbridge_core::{inbound::Log, ChannelId}; +use snowbridge_core::inbound::Log; -use sp_core::{RuntimeDebug, H160, H256}; +use sp_core::{RuntimeDebug, H160}; use sp_std::prelude::*; use alloy_primitives::B256; use alloy_sol_types::{sol, SolEvent}; sol! { - event OutboundMessageAccepted(bytes32 indexed channel_id, uint64 nonce, bytes32 indexed message_id, bytes payload); + event OutboundMessageAccepted(uint64 indexed nonce, uint128 fee, bytes payload); } /// An inbound message that has had its outer envelope decoded. @@ -17,12 +17,10 @@ sol! { pub struct Envelope { /// The address of the outbound queue on Ethereum that emitted this message as an event log pub gateway: H160, - /// The message Channel - pub channel_id: ChannelId, /// A nonce for enforcing replay protection and ordering. pub nonce: u64, - /// An id for tracing the message on its route (has no role in bridge consensus) - pub message_id: H256, + /// Total fee paid in Ether on Ethereum, should cover all the cost + pub fee: u128, /// The inner payload generated from the source application. pub payload: Vec, } @@ -41,9 +39,8 @@ impl TryFrom<&Log> for Envelope { Ok(Self { gateway: log.address, - channel_id: ChannelId::from(event.channel_id.as_ref()), nonce: event.nonce, - message_id: H256::from(event.message_id.as_ref()), + fee: event.fee, payload: event.payload, }) } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index c26859dcf5d7..0eaadcb6a3c3 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -38,49 +38,35 @@ mod test; use codec::{Decode, DecodeAll, Encode}; use envelope::Envelope; -use frame_support::{ - traits::{ - fungible::{Inspect, Mutate}, - tokens::{Fortitude, Preservation}, - }, - weights::WeightToFee, - PalletError, -}; +use frame_support::PalletError; use frame_system::ensure_signed; use scale_info::TypeInfo; use sp_core::H160; -use sp_runtime::traits::Zero; use sp_std::vec; -use xcm::prelude::{ - send_xcm, Junction::*, Location, SendError as XcmpSendError, SendXcm, Xcm, XcmContext, XcmHash, +use xcm::{ + prelude::{send_xcm, Junction::*, Location, SendError as XcmpSendError, SendXcm, Xcm}, + VersionedXcm, MAX_XCM_DECODE_DEPTH, }; -use xcm_executor::traits::TransactAsset; use snowbridge_core::{ inbound::{Message, VerificationError, Verifier}, - sibling_sovereign_account, BasicOperatingMode, Channel, ChannelId, ParaId, PricingParameters, - StaticLookup, -}; -use snowbridge_router_primitives::inbound::v2::{ - ConvertMessage, ConvertMessageError, VersionedMessage, + BasicOperatingMode, }; -use sp_runtime::{traits::Saturating, SaturatedConversion, TokenError}; +use snowbridge_router_primitives::inbound::v2::Message as MessageV2; pub use weights::WeightInfo; #[cfg(feature = "runtime-benchmarks")] use snowbridge_beacon_primitives::BeaconHeader; -type BalanceOf = - <::Token as Inspect<::AccountId>>::Balance; - pub use pallet::*; -pub const LOG_TARGET: &str = "snowbridge-inbound-queue"; +pub const LOG_TARGET: &str = "snowbridge-inbound-queue:v2"; #[frame_support::pallet] pub mod pallet { use super::*; + use codec::DecodeLimit; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; @@ -101,44 +87,17 @@ pub mod pallet { /// The verifier for inbound messages from Ethereum type Verifier: Verifier; - /// Message relayers are rewarded with this asset - type Token: Mutate + Inspect; - /// XCM message sender type XcmSender: SendXcm; - // Address of the Gateway contract + /// Address of the Gateway contract #[pallet::constant] type GatewayAddress: Get; - /// Convert inbound message to XCM - type MessageConverter: ConvertMessage< - AccountId = Self::AccountId, - Balance = BalanceOf, - >; - - /// Lookup a channel descriptor - type ChannelLookup: StaticLookup; - - /// Lookup pricing parameters - type PricingParameters: Get>>; - type WeightInfo: WeightInfo; #[cfg(feature = "runtime-benchmarks")] type Helper: BenchmarkHelper; - - /// Convert a weight value into deductible balance type. - type WeightToFee: WeightToFee>; - - /// Convert a length value into deductible balance type - type LengthToFee: WeightToFee>; - - /// The upper limit here only used to estimate delivery cost - type MaxMessageSize: Get; - - /// To withdraw and deposit an asset. - type AssetTransactor: TransactAsset; } #[pallet::hooks] @@ -149,14 +108,10 @@ pub mod pallet { pub enum Event { /// A message was received from Ethereum MessageReceived { - /// The message channel - channel_id: ChannelId, /// The message nonce nonce: u64, /// ID of the XCM message which was forwarded to the final destination parachain message_id: [u8; 32], - /// Fee burned for the teleport - fee_burned: BalanceOf, }, /// Set OperatingMode OperatingModeChanged { mode: BasicOperatingMode }, @@ -184,8 +139,6 @@ pub mod pallet { Verification(VerificationError), /// XCMP send failure Send(SendError), - /// Message conversion error - ConvertMessage(ConvertMessageError), } #[derive(Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo, PalletError)] @@ -215,9 +168,9 @@ pub mod pallet { } } - /// The current nonce for each channel + /// The nonce of the message been processed or not #[pallet::storage] - pub type Nonce = StorageMap<_, Twox64Concat, ChannelId, u64, ValueQuery>; + pub type Nonce = StorageMap<_, Identity, u64, bool, ValueQuery>; /// The current operating mode of the pallet. #[pallet::storage] @@ -230,7 +183,7 @@ pub mod pallet { #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::submit())] pub fn submit(origin: OriginFor, message: Message) -> DispatchResult { - let who = ensure_signed(origin)?; + let _who = ensure_signed(origin)?; ensure!(!Self::operating_mode().is_halted(), Error::::Halted); // submit message to verifier for verification @@ -244,63 +197,46 @@ pub mod pallet { // Verify that the message was submitted from the known Gateway contract ensure!(T::GatewayAddress::get() == envelope.gateway, Error::::InvalidGateway); - // Retrieve the registered channel for this message - let channel = - T::ChannelLookup::lookup(envelope.channel_id).ok_or(Error::::InvalidChannel)?; - - // Verify message nonce - >::try_mutate(envelope.channel_id, |nonce| -> DispatchResult { - if *nonce == u64::MAX { - return Err(Error::::MaxNonceReached.into()) - } - if envelope.nonce != nonce.saturating_add(1) { - Err(Error::::InvalidNonce.into()) - } else { - *nonce = nonce.saturating_add(1); - Ok(()) - } - })?; - - // Reward relayer from the sovereign account of the destination parachain, only if funds - // are available - let sovereign_account = sibling_sovereign_account::(channel.para_id); - let delivery_cost = Self::calculate_delivery_cost(message.encode().len() as u32); - let amount = T::Token::reducible_balance( - &sovereign_account, - Preservation::Preserve, - Fortitude::Polite, - ) - .min(delivery_cost); - if !amount.is_zero() { - T::Token::transfer(&sovereign_account, &who, amount, Preservation::Preserve)?; - } + // Verify the message has not been processed + ensure!(!>::contains_key(envelope.nonce), Error::::InvalidNonce); - // Decode payload into `VersionedMessage` - let message = VersionedMessage::decode_all(&mut envelope.payload.as_ref()) + // Decode payload into `MessageV2` + let message = MessageV2::decode_all(&mut envelope.payload.as_ref()) .map_err(|_| Error::::InvalidPayload)?; - // Decode message into XCM - let (xcm, fee) = Self::do_convert(envelope.message_id, message.clone())?; + // Decode xcm + let versioned_xcm = VersionedXcm::<()>::decode_with_depth_limit( + MAX_XCM_DECODE_DEPTH, + &mut message.xcm.as_ref(), + ) + .map_err(|_| Error::::InvalidPayload)?; + let xcm: Xcm<()> = versioned_xcm.try_into().map_err(|_| >::InvalidPayload)?; log::info!( target: LOG_TARGET, - "💫 xcm decoded as {:?} with fee {:?}", + "💫 xcm decoded as {:?}", xcm, - fee ); - // Burning fees for teleport - Self::burn_fees(channel.para_id, fee)?; + // Set nonce flag to true + >::try_mutate(envelope.nonce, |done| -> DispatchResult { + *done = true; + Ok(()) + })?; + + // Todo: Deposit fee(in Ether) to RewardLeger which should cover all of: + // T::RewardLeger::deposit(who, envelope.fee.into())?; + // a. The submit extrinsic cost on BH + // b. The delivery cost to AH + // c. The execution cost on AH + // d. The execution cost on destination chain(if any) + // e. The reward - // Attempt to send XCM to a dest parachain - let message_id = Self::send_xcm(xcm, channel.para_id)?; + // Attempt to forward XCM to AH + let dest = Location::new(1, [Parachain(1000)]); + let (message_id, _) = send_xcm::(dest, xcm).map_err(Error::::from)?; - Self::deposit_event(Event::MessageReceived { - channel_id: envelope.channel_id, - nonce: envelope.nonce, - message_id, - fee_burned: fee, - }); + Self::deposit_event(Event::MessageReceived { nonce: envelope.nonce, message_id }); Ok(()) } @@ -318,61 +254,4 @@ pub mod pallet { Ok(()) } } - - impl Pallet { - pub fn do_convert( - message_id: H256, - message: VersionedMessage, - ) -> Result<(Xcm<()>, BalanceOf), Error> { - let (xcm, fee) = T::MessageConverter::convert(message_id, message) - .map_err(|e| Error::::ConvertMessage(e))?; - Ok((xcm, fee)) - } - - pub fn send_xcm(xcm: Xcm<()>, dest: ParaId) -> Result> { - let dest = Location::new(1, [Parachain(dest.into())]); - let (xcm_hash, _) = send_xcm::(dest, xcm).map_err(Error::::from)?; - Ok(xcm_hash) - } - - pub fn calculate_delivery_cost(length: u32) -> BalanceOf { - let weight_fee = T::WeightToFee::weight_to_fee(&T::WeightInfo::submit()); - let len_fee = T::LengthToFee::weight_to_fee(&Weight::from_parts(length as u64, 0)); - weight_fee - .saturating_add(len_fee) - .saturating_add(T::PricingParameters::get().rewards.local) - } - - /// Burn the amount of the fee embedded into the XCM for teleports - pub fn burn_fees(para_id: ParaId, fee: BalanceOf) -> DispatchResult { - let dummy_context = - XcmContext { origin: None, message_id: Default::default(), topic: None }; - let dest = Location::new(1, [Parachain(para_id.into())]); - let fees = (Location::parent(), fee.saturated_into::()).into(); - T::AssetTransactor::can_check_out(&dest, &fees, &dummy_context).map_err(|error| { - log::error!( - target: LOG_TARGET, - "XCM asset check out failed with error {:?}", error - ); - TokenError::FundsUnavailable - })?; - T::AssetTransactor::check_out(&dest, &fees, &dummy_context); - T::AssetTransactor::withdraw_asset(&fees, &dest, None).map_err(|error| { - log::error!( - target: LOG_TARGET, - "XCM asset withdraw failed with error {:?}", error - ); - TokenError::FundsUnavailable - })?; - Ok(()) - } - } - - /// API for accessing the delivery cost of a message - impl Get> for Pallet { - fn get() -> BalanceOf { - // Cost here based on MaxMessagePayloadSize(the worst case) - Self::calculate_delivery_cost(T::MaxMessageSize::get()) - } - } } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index fad62628c0f0..f08ce202f9bc 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -8,11 +8,9 @@ use snowbridge_beacon_primitives::{ types::deneb, BeaconHeader, ExecutionProof, Fork, ForkVersions, VersionedExecutionPayloadHeader, }; use snowbridge_core::{ - gwei, inbound::{Log, Proof, VerificationError}, - meth, Channel, ChannelId, PricingParameters, Rewards, StaticLookup, TokenId, + TokenId, }; -use snowbridge_router_primitives::inbound::v2::MessageToXcm; use sp_core::{H160, H256}; use sp_runtime::{ traits::{IdentifyAccount, IdentityLookup, MaybeEquivalence, Verify}, @@ -20,7 +18,6 @@ use sp_runtime::{ }; use sp_std::{convert::From, default::Default}; use xcm::{latest::SendXcm, prelude::*}; -use xcm_executor::AssetsInHolding; use crate::{self as inbound_queue}; @@ -149,65 +146,8 @@ impl SendXcm for MockXcmSender { } } -parameter_types! { - pub const OwnParaId: ParaId = ParaId::new(1013); - pub Parameters: PricingParameters = PricingParameters { - exchange_rate: FixedU128::from_rational(1, 400), - fee_per_gas: gwei(20), - rewards: Rewards { local: DOT, remote: meth(1) }, - multiplier: FixedU128::from_rational(1, 1), - }; -} - pub const DOT: u128 = 10_000_000_000; -pub struct MockChannelLookup; -impl StaticLookup for MockChannelLookup { - type Source = ChannelId; - type Target = Channel; - - fn lookup(channel_id: Self::Source) -> Option { - if channel_id != - hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into() - { - return None - } - Some(Channel { agent_id: H256::zero(), para_id: ASSET_HUB_PARAID.into() }) - } -} - -pub struct SuccessfulTransactor; -impl TransactAsset for SuccessfulTransactor { - fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { - Ok(()) - } - - fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { - Ok(()) - } - - fn deposit_asset(_what: &Asset, _who: &Location, _context: Option<&XcmContext>) -> XcmResult { - Ok(()) - } - - fn withdraw_asset( - _what: &Asset, - _who: &Location, - _context: Option<&XcmContext>, - ) -> Result { - Ok(AssetsInHolding::default()) - } - - fn internal_transfer_asset( - _what: &Asset, - _from: &Location, - _to: &Location, - _context: &XcmContext, - ) -> Result { - Ok(AssetsInHolding::default()) - } -} - pub struct MockTokenIdConvert; impl MaybeEquivalence for MockTokenIdConvert { fn convert(_id: &TokenId) -> Option { @@ -221,28 +161,11 @@ impl MaybeEquivalence for MockTokenIdConvert { impl inbound_queue::Config for Test { type RuntimeEvent = RuntimeEvent; type Verifier = MockVerifier; - type Token = Balances; type XcmSender = MockXcmSender; type WeightInfo = (); type GatewayAddress = GatewayAddress; - type MessageConverter = MessageToXcm< - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - MockTokenIdConvert, - UniversalLocation, - AssetHubFromEthereum, - >; - type PricingParameters = Parameters; - type ChannelLookup = MockChannelLookup; #[cfg(feature = "runtime-benchmarks")] type Helper = Test; - type WeightToFee = IdentityFee; - type LengthToFee = IdentityFee; - type MaxMessageSize = ConstU32<1024>; - type AssetTransactor = SuccessfulTransactor; } pub fn last_events(n: usize) -> Vec { @@ -261,16 +184,6 @@ pub fn expect_events(e: Vec) { pub fn setup() { System::set_block_number(1); - Balances::mint_into( - &sibling_sovereign_account::(ASSET_HUB_PARAID.into()), - InitialFund::get(), - ) - .unwrap(); - Balances::mint_into( - &sibling_sovereign_account::(TEMPLATE_PARAID.into()), - InitialFund::get(), - ) - .unwrap(); } pub fn new_tester() -> sp_io::TestExternalities { @@ -286,47 +199,47 @@ pub fn new_tester() -> sp_io::TestExternalities { // cargo test --test register_token -- --nocapture pub fn mock_event_log() -> Log { Log { - // gateway address - address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), - topics: vec![ - hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), - // channel id - hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into(), - // message id - hex!("5f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0").into(), - ], - // Nonce + Payload - data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002e000f000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d00e40b54020000000000000000000000000000000000000000000000000000000000").into(), - } + // gateway address + address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), + topics: vec![ + hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), + // channel id + hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into(), + // message id + hex!("5f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0").into(), + ], + // Nonce + Payload + data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002e000f000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d00e40b54020000000000000000000000000000000000000000000000000000000000").into(), + } } pub fn mock_event_log_invalid_channel() -> Log { Log { - address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), - topics: vec![ - hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), - // invalid channel id - hex!("0000000000000000000000000000000000000000000000000000000000000000").into(), - hex!("5f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0").into(), - ], - data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001e000f000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d0000").into(), - } + address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), + topics: vec![ + hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), + // invalid channel id + hex!("0000000000000000000000000000000000000000000000000000000000000000").into(), + hex!("5f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0").into(), + ], + data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001e000f000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d0000").into(), + } } pub fn mock_event_log_invalid_gateway() -> Log { Log { - // gateway address - address: H160::zero(), - topics: vec![ - hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), - // channel id - hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into(), - // message id - hex!("5f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0").into(), - ], - // Nonce + Payload - data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001e000f000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d0000").into(), - } + // gateway address + address: H160::zero(), + topics: vec![ + hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), + // channel id + hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into(), + // message id + hex!("5f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0").into(), + ], + // Nonce + Payload + data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001e000f000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d0000").into(), + } } pub fn mock_execution_proof() -> ExecutionProof { diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs index 41c38460aabf..989787af167d 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs @@ -17,7 +17,6 @@ use crate::mock::*; fn test_submit_happy_path() { new_tester().execute_with(|| { let relayer: AccountId = Keyring::Bob.into(); - let channel_sovereign = sibling_sovereign_account::(ASSET_HUB_PARAID.into()); let origin = RuntimeOrigin::signed(relayer.clone()); @@ -30,34 +29,15 @@ fn test_submit_happy_path() { }, }; - let initial_fund = InitialFund::get(); - assert_eq!(Balances::balance(&relayer), 0); - assert_eq!(Balances::balance(&channel_sovereign), initial_fund); - assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); expect_events(vec![InboundQueueEvent::MessageReceived { - channel_id: hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539") - .into(), nonce: 1, message_id: [ 255, 125, 48, 71, 174, 185, 100, 26, 159, 43, 108, 6, 116, 218, 55, 155, 223, 143, 141, 22, 124, 110, 241, 18, 122, 217, 130, 29, 139, 76, 97, 201, ], - fee_burned: 110000000000, } .into()]); - - let delivery_cost = InboundQueue::calculate_delivery_cost(message.encode().len() as u32); - assert!( - Parameters::get().rewards.local < delivery_cost, - "delivery cost exceeds pure reward" - ); - - assert_eq!(Balances::balance(&relayer), delivery_cost, "relayer was rewarded"); - assert!( - Balances::balance(&channel_sovereign) <= initial_fund - delivery_cost, - "sovereign account paid reward" - ); }); } @@ -67,11 +47,6 @@ fn test_submit_xcm_invalid_channel() { let relayer: AccountId = Keyring::Bob.into(); let origin = RuntimeOrigin::signed(relayer); - // Deposit funds into sovereign account of parachain 1001 - let sovereign_account = sibling_sovereign_account::(TEMPLATE_PARAID.into()); - println!("account: {}", sovereign_account); - let _ = Balances::mint_into(&sovereign_account, 10000); - // Submit message let message = Message { event_log: mock_event_log_invalid_channel(), @@ -93,10 +68,6 @@ fn test_submit_with_invalid_gateway() { let relayer: AccountId = Keyring::Bob.into(); let origin = RuntimeOrigin::signed(relayer); - // Deposit funds into sovereign account of Asset Hub (Statemint) - let sovereign_account = sibling_sovereign_account::(ASSET_HUB_PARAID.into()); - let _ = Balances::mint_into(&sovereign_account, 10000); - // Submit message let message = Message { event_log: mock_event_log_invalid_gateway(), @@ -118,10 +89,6 @@ fn test_submit_with_invalid_nonce() { let relayer: AccountId = Keyring::Bob.into(); let origin = RuntimeOrigin::signed(relayer); - // Deposit funds into sovereign account of Asset Hub (Statemint) - let sovereign_account = sibling_sovereign_account::(ASSET_HUB_PARAID.into()); - let _ = Balances::mint_into(&sovereign_account, 10000); - // Submit message let message = Message { event_log: mock_event_log(), @@ -132,11 +99,6 @@ fn test_submit_with_invalid_nonce() { }; assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); - let nonce: u64 = >::get(ChannelId::from(hex!( - "c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539" - ))); - assert_eq!(nonce, 1); - // Submit the same again assert_noop!( InboundQueue::submit(origin.clone(), message.clone()), @@ -151,10 +113,6 @@ fn test_submit_no_funds_to_reward_relayers_just_ignore() { let relayer: AccountId = Keyring::Bob.into(); let origin = RuntimeOrigin::signed(relayer); - // Reset balance of sovereign_account to zero first - let sovereign_account = sibling_sovereign_account::(ASSET_HUB_PARAID.into()); - Balances::set_balance(&sovereign_account, 0); - // Submit message let message = Message { event_log: mock_event_log(), @@ -209,10 +167,6 @@ fn test_submit_no_funds_to_reward_relayers_and_ed_preserved() { let relayer: AccountId = Keyring::Bob.into(); let origin = RuntimeOrigin::signed(relayer); - // Reset balance of sovereign account to (ED+1) first - let sovereign_account = sibling_sovereign_account::(ASSET_HUB_PARAID.into()); - Balances::set_balance(&sovereign_account, ExistentialDeposit::get() + 1); - // Submit message successfully let message = Message { event_log: mock_event_log(), @@ -223,10 +177,6 @@ fn test_submit_no_funds_to_reward_relayers_and_ed_preserved() { }; assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); - // Check balance of sovereign account to ED - let amount = Balances::balance(&sovereign_account); - assert_eq!(amount, ExistentialDeposit::get()); - // Submit another message with nonce set as 2 let mut event_log = mock_event_log(); event_log.data[31] = 2; @@ -238,8 +188,5 @@ fn test_submit_no_funds_to_reward_relayers_and_ed_preserved() { }, }; assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); - // Check balance of sovereign account as ED does not change - let amount = Balances::balance(&sovereign_account); - assert_eq!(amount, ExistentialDeposit::get()); }); } diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 05c054080620..8ab4c98501b4 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -2,16 +2,17 @@ // SPDX-FileCopyrightText: 2023 Snowfork //! Converts messages from Ethereum to XCM messages -use crate::inbound::{CallIndex, GlobalConsensusEthereumConvertsFor}; use codec::{Decode, Encode}; use core::marker::PhantomData; use frame_support::{traits::tokens::Balance as BalanceT, weights::Weight, PalletError}; use scale_info::TypeInfo; use snowbridge_core::TokenId; use sp_core::{Get, RuntimeDebug, H160, H256}; +use sp_io::hashing::blake2_256; use sp_runtime::{traits::MaybeEquivalence, MultiAddress}; use sp_std::prelude::*; use xcm::prelude::{Junction::AccountKey20, *}; +use xcm_executor::traits::ConvertLocation; const MINIMUM_DEPOSIT: u128 = 1; @@ -20,429 +21,36 @@ const MINIMUM_DEPOSIT: u128 = 1; /// Instead having BridgeHub transcode the messages into XCM. #[derive(Clone, Encode, Decode, RuntimeDebug)] pub enum VersionedMessage { - V1(MessageV1), + V2(Message), } -/// For V1, the ethereum side sends messages which are transcoded into XCM. These messages are +/// For V2, the ethereum side sends messages which are transcoded into XCM. These messages are /// self-contained, in that they can be transcoded using only information in the message. #[derive(Clone, Encode, Decode, RuntimeDebug)] -pub struct MessageV1 { - /// EIP-155 chain id of the origin Ethereum network - pub chain_id: u64, +pub struct Message { + /// The origin address + pub origin: H160, /// The command originating from the Gateway contract - pub command: Command, + pub xcm: Vec, } -#[derive(Clone, Encode, Decode, RuntimeDebug)] -pub enum Command { - /// Register a wrapped token on the AssetHub `ForeignAssets` pallet - RegisterToken { - /// The address of the ERC20 token to be bridged over to AssetHub - token: H160, - /// XCM execution fee on AssetHub - fee: u128, - }, - /// Send Ethereum token to AssetHub or another parachain - SendToken { - /// The address of the ERC20 token to be bridged over to AssetHub - token: H160, - /// The destination for the transfer - destination: Destination, - /// Amount to transfer - amount: u128, - /// XCM execution fee on AssetHub - fee: u128, - }, - /// Send Polkadot token back to the original parachain - SendNativeToken { - /// The Id of the token - token_id: TokenId, - /// The destination for the transfer - destination: Destination, - /// Amount to transfer - amount: u128, - /// XCM execution fee on AssetHub - fee: u128, - }, -} - -/// Destination for bridged tokens -#[derive(Clone, Encode, Decode, RuntimeDebug)] -pub enum Destination { - /// The funds will be deposited into account `id` on AssetHub - AccountId32 { id: [u8; 32] }, - /// The funds will deposited into the sovereign account of destination parachain `para_id` on - /// AssetHub, Account `id` on the destination parachain will receive the funds via a - /// reserve-backed transfer. See - ForeignAccountId32 { - para_id: u32, - id: [u8; 32], - /// XCM execution fee on final destination - fee: u128, - }, - /// The funds will deposited into the sovereign account of destination parachain `para_id` on - /// AssetHub, Account `id` on the destination parachain will receive the funds via a - /// reserve-backed transfer. See - ForeignAccountId20 { - para_id: u32, - id: [u8; 20], - /// XCM execution fee on final destination - fee: u128, - }, -} - -pub struct MessageToXcm< - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, -> where - CreateAssetCall: Get, - CreateAssetDeposit: Get, - Balance: BalanceT, - ConvertAssetId: MaybeEquivalence, - EthereumUniversalLocation: Get, - GlobalAssetHubLocation: Get, -{ - _phantom: PhantomData<( - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, - )>, -} - -/// Reason why a message conversion failed. -#[derive(Copy, Clone, TypeInfo, PalletError, Encode, Decode, RuntimeDebug)] -pub enum ConvertMessageError { - /// The message version is not supported for conversion. - UnsupportedVersion, - InvalidDestination, - InvalidToken, - /// The fee asset is not supported for conversion. - UnsupportedFeeAsset, - CannotReanchor, -} - -/// convert the inbound message to xcm which will be forwarded to the destination chain -pub trait ConvertMessage { - type Balance: BalanceT + From; - type AccountId; - /// Converts a versioned message into an XCM message and an optional topicID - fn convert( - message_id: H256, - message: VersionedMessage, - ) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError>; -} - -impl< - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, - > ConvertMessage - for MessageToXcm< - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, - > +pub struct GlobalConsensusEthereumConvertsFor(PhantomData); +impl ConvertLocation for GlobalConsensusEthereumConvertsFor where - CreateAssetCall: Get, - CreateAssetDeposit: Get, - InboundQueuePalletInstance: Get, - Balance: BalanceT + From, - AccountId: Into<[u8; 32]>, - ConvertAssetId: MaybeEquivalence, - EthereumUniversalLocation: Get, - GlobalAssetHubLocation: Get, + AccountId: From<[u8; 32]> + Clone, { - type Balance = Balance; - type AccountId = AccountId; - - fn convert( - message_id: H256, - message: VersionedMessage, - ) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError> { - use Command::*; - use VersionedMessage::*; - match message { - V1(MessageV1 { chain_id, command: RegisterToken { token, fee } }) => - Ok(Self::convert_register_token(message_id, chain_id, token, fee)), - V1(MessageV1 { chain_id, command: SendToken { token, destination, amount, fee } }) => - Ok(Self::convert_send_token(message_id, chain_id, token, destination, amount, fee)), - V1(MessageV1 { - chain_id, - command: SendNativeToken { token_id, destination, amount, fee }, - }) => Self::convert_send_native_token( - message_id, - chain_id, - token_id, - destination, - amount, - fee, - ), + fn convert_location(location: &Location) -> Option { + match location.unpack() { + (_, [GlobalConsensus(Ethereum { chain_id })]) => + Some(Self::from_chain_id(chain_id).into()), + _ => None, } } } -impl< - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, - > - MessageToXcm< - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, - > -where - CreateAssetCall: Get, - CreateAssetDeposit: Get, - InboundQueuePalletInstance: Get, - Balance: BalanceT + From, - AccountId: Into<[u8; 32]>, - ConvertAssetId: MaybeEquivalence, - EthereumUniversalLocation: Get, - GlobalAssetHubLocation: Get, -{ - fn convert_register_token( - message_id: H256, - chain_id: u64, - token: H160, - fee: u128, - ) -> (Xcm<()>, Balance) { - let network = Ethereum { chain_id }; - let xcm_fee: Asset = (Location::parent(), fee).into(); - let deposit: Asset = (Location::parent(), CreateAssetDeposit::get()).into(); - - let total_amount = fee + CreateAssetDeposit::get(); - let total: Asset = (Location::parent(), total_amount).into(); - - let bridge_location = Location::new(2, GlobalConsensus(network)); - - let owner = GlobalConsensusEthereumConvertsFor::<[u8; 32]>::from_chain_id(&chain_id); - let asset_id = Self::convert_token_address(network, token); - let create_call_index: [u8; 2] = CreateAssetCall::get(); - let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); - - let xcm: Xcm<()> = vec![ - // Teleport required fees. - ReceiveTeleportedAsset(total.into()), - // Pay for execution. - BuyExecution { fees: xcm_fee, weight_limit: Unlimited }, - // Fund the snowbridge sovereign with the required deposit for creation. - DepositAsset { assets: Definite(deposit.into()), beneficiary: bridge_location.clone() }, - // This `SetAppendix` ensures that `xcm_fee` not spent by `Transact` will be - // deposited to snowbridge sovereign, instead of being trapped, regardless of - // `Transact` success or not. - SetAppendix(Xcm(vec![ - RefundSurplus, - DepositAsset { assets: AllCounted(1).into(), beneficiary: bridge_location }, - ])), - // Only our inbound-queue pallet is allowed to invoke `UniversalOrigin`. - DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), - // Change origin to the bridge. - UniversalOrigin(GlobalConsensus(network)), - // Call create_asset on foreign assets pallet. - Transact { - origin_kind: OriginKind::Xcm, - require_weight_at_most: Weight::from_parts(400_000_000, 8_000), - call: ( - create_call_index, - asset_id, - MultiAddress::<[u8; 32], ()>::Id(owner), - MINIMUM_DEPOSIT, - ) - .encode() - .into(), - }, - // Forward message id to Asset Hub - SetTopic(message_id.into()), - // Once the program ends here, appendix program will run, which will deposit any - // leftover fee to snowbridge sovereign. - ] - .into(); - - (xcm, total_amount.into()) - } - - fn convert_send_token( - message_id: H256, - chain_id: u64, - token: H160, - destination: Destination, - amount: u128, - asset_hub_fee: u128, - ) -> (Xcm<()>, Balance) { - let network = Ethereum { chain_id }; - let asset_hub_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); - let asset: Asset = (Self::convert_token_address(network, token), amount).into(); - - let (dest_para_id, beneficiary, dest_para_fee) = match destination { - // Final destination is a 32-byte account on AssetHub - Destination::AccountId32 { id } => - (None, Location::new(0, [AccountId32 { network: None, id }]), 0), - // Final destination is a 32-byte account on a sibling of AssetHub - Destination::ForeignAccountId32 { para_id, id, fee } => ( - Some(para_id), - Location::new(0, [AccountId32 { network: None, id }]), - // Total fee needs to cover execution on AssetHub and Sibling - fee, - ), - // Final destination is a 20-byte account on a sibling of AssetHub - Destination::ForeignAccountId20 { para_id, id, fee } => ( - Some(para_id), - Location::new(0, [AccountKey20 { network: None, key: id }]), - // Total fee needs to cover execution on AssetHub and Sibling - fee, - ), - }; - - let total_fees = asset_hub_fee.saturating_add(dest_para_fee); - let total_fee_asset: Asset = (Location::parent(), total_fees).into(); - let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); - - let mut instructions = vec![ - ReceiveTeleportedAsset(total_fee_asset.into()), - BuyExecution { fees: asset_hub_fee_asset, weight_limit: Unlimited }, - DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), - UniversalOrigin(GlobalConsensus(network)), - ReserveAssetDeposited(asset.clone().into()), - ClearOrigin, - ]; - - match dest_para_id { - Some(dest_para_id) => { - let dest_para_fee_asset: Asset = (Location::parent(), dest_para_fee).into(); - let bridge_location = Location::new(2, GlobalConsensus(network)); - - instructions.extend(vec![ - // After program finishes deposit any leftover assets to the snowbridge - // sovereign. - SetAppendix(Xcm(vec![DepositAsset { - assets: Wild(AllCounted(2)), - beneficiary: bridge_location, - }])), - // Perform a deposit reserve to send to destination chain. - DepositReserveAsset { - assets: Definite(vec![dest_para_fee_asset.clone(), asset].into()), - dest: Location::new(1, [Parachain(dest_para_id)]), - xcm: vec![ - // Buy execution on target. - BuyExecution { fees: dest_para_fee_asset, weight_limit: Unlimited }, - // Deposit assets to beneficiary. - DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, - // Forward message id to destination parachain. - SetTopic(message_id.into()), - ] - .into(), - }, - ]); - }, - None => { - instructions.extend(vec![ - // Deposit both asset and fees to beneficiary so the fees will not get - // trapped. Another benefit is when fees left more than ED on AssetHub could be - // used to create the beneficiary account in case it does not exist. - DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, - ]); - }, - } - - // Forward message id to Asset Hub. - instructions.push(SetTopic(message_id.into())); - - // The `instructions` to forward to AssetHub, and the `total_fees` to locally burn (since - // they are teleported within `instructions`). - (instructions.into(), total_fees.into()) - } - - // Convert ERC20 token address to a location that can be understood by Assets Hub. - fn convert_token_address(network: NetworkId, token: H160) -> Location { - Location::new( - 2, - [GlobalConsensus(network), AccountKey20 { network: None, key: token.into() }], - ) - } - - /// Constructs an XCM message destined for AssetHub that withdraws assets from the sovereign - /// account of the Gateway contract and either deposits those assets into a recipient account or - /// forwards the assets to another parachain. - fn convert_send_native_token( - message_id: H256, - chain_id: u64, - token_id: TokenId, - destination: Destination, - amount: u128, - asset_hub_fee: u128, - ) -> Result<(Xcm<()>, Balance), ConvertMessageError> { - let network = Ethereum { chain_id }; - let asset_hub_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); - - let beneficiary = match destination { - // Final destination is a 32-byte account on AssetHub - Destination::AccountId32 { id } => - Ok(Location::new(0, [AccountId32 { network: None, id }])), - _ => Err(ConvertMessageError::InvalidDestination), - }?; - - let total_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); - - let asset_loc = - ConvertAssetId::convert(&token_id).ok_or(ConvertMessageError::InvalidToken)?; - - let mut reanchored_asset_loc = asset_loc.clone(); - reanchored_asset_loc - .reanchor(&GlobalAssetHubLocation::get(), &EthereumUniversalLocation::get()) - .map_err(|_| ConvertMessageError::CannotReanchor)?; - - let asset: Asset = (reanchored_asset_loc, amount).into(); - - let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); - - let instructions = vec![ - ReceiveTeleportedAsset(total_fee_asset.clone().into()), - BuyExecution { fees: asset_hub_fee_asset, weight_limit: Unlimited }, - DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), - UniversalOrigin(GlobalConsensus(network)), - WithdrawAsset(asset.clone().into()), - // Deposit both asset and fees to beneficiary so the fees will not get - // trapped. Another benefit is when fees left more than ED on AssetHub could be - // used to create the beneficiary account in case it does not exist. - DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, - SetTopic(message_id.into()), - ]; - - // `total_fees` to burn on this chain when sending `instructions` to run on AH (which also - // teleport fees) - Ok((instructions.into(), asset_hub_fee.into())) +impl GlobalConsensusEthereumConvertsFor { + pub fn from_chain_id(chain_id: &u64) -> [u8; 32] { + (b"ethereum-chain", chain_id).using_encoded(blake2_256) } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index eb046b7edaa1..970ce143dc6c 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -26,7 +26,7 @@ use parachains_common::{AccountId, Balance}; use snowbridge_beacon_primitives::{Fork, ForkVersions}; use snowbridge_core::{gwei, meth, AllowSiblingsOnly, PricingParameters, Rewards}; use snowbridge_router_primitives::{ - inbound::{v1::MessageToXcm, v2::MessageToXcm as MessageToXcmV2}, + inbound::v1::MessageToXcm, outbound::{v1::EthereumBlobExporter, v2::EthereumBlobExporter as EthereumBlobExporterV2}, }; use sp_core::H160; @@ -116,31 +116,14 @@ impl snowbridge_pallet_inbound_queue::Config for Runtime { impl snowbridge_pallet_inbound_queue_v2::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Verifier = snowbridge_pallet_ethereum_client::Pallet; - type Token = Balances; #[cfg(not(feature = "runtime-benchmarks"))] type XcmSender = XcmRouter; #[cfg(feature = "runtime-benchmarks")] type XcmSender = DoNothingRouter; - type ChannelLookup = EthereumSystem; type GatewayAddress = EthereumGatewayAddress; #[cfg(feature = "runtime-benchmarks")] type Helper = Runtime; - type MessageConverter = MessageToXcmV2< - CreateAssetCall, - CreateAssetDeposit, - ConstU8, - AccountId, - Balance, - EthereumSystem, - EthereumUniversalLocation, - AssetHubFromEthereum, - >; - type WeightToFee = WeightToFee; - type LengthToFee = ConstantMultiplier; - type MaxMessageSize = ConstU32<2048>; type WeightInfo = crate::weights::snowbridge_pallet_inbound_queue_v2::WeightInfo; - type PricingParameters = EthereumSystem; - type AssetTransactor = ::AssetTransactor; } impl snowbridge_pallet_outbound_queue::Config for Runtime { From cf8f536731fbc114ef201ec85105e546b6b55278 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Fri, 1 Nov 2024 10:43:35 +0200 Subject: [PATCH 02/20] inbound v2 --- .../inbound-queue-v2/runtime-api/Cargo.toml | 34 +++++++++++++++++++ .../inbound-queue-v2/runtime-api/README.md | 3 ++ .../inbound-queue-v2/runtime-api/src/lib.rs | 17 ++++++++++ .../primitives/router/src/inbound/v2.rs | 6 ++++ 4 files changed, 60 insertions(+) create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/Cargo.toml create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/README.md create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/src/lib.rs diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/Cargo.toml new file mode 100644 index 000000000000..7745f4e23902 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "snowbridge-inbound-queue-v2-runtime-api" +description = "Snowbridge Inbound Queue V2 Runtime API" +version = "0.2.0" +authors = ["Snowfork "] +edition.workspace = true +repository.workspace = true +license = "Apache-2.0" +categories = ["cryptography::cryptocurrencies"] + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { features = ["derive"], workspace = true } +sp-std = { workspace = true } +sp-api = { workspace = true } +frame-support = { workspace = true } +snowbridge-merkle-tree = { workspace = true } +snowbridge-core = { workspace = true } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "snowbridge-core/std", + "snowbridge-merkle-tree/std", + "sp-api/std", + "sp-std/std", +] diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/README.md b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/README.md new file mode 100644 index 000000000000..89b6b0e157c5 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/README.md @@ -0,0 +1,3 @@ +# Ethereum Inbound Queue V2 Runtime API + +Provides an API to dry-run inbound messages to get the XCM (and its execution cost) that will be executed on AssetHub. diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/src/lib.rs new file mode 100644 index 000000000000..97f76aa62d30 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/src/lib.rs @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::traits::tokens::Balance as BalanceT; +use snowbridge_core::{ + inbound::v2::InboundMessage, + PricingParameters, +}; + +sp_api::decl_runtime_apis! { + pub trait InboundQueueApi where Balance: BalanceT + { + /// Dry runs the provided message on AH to provide the XCM payload and execution cost. + fn dry_run(message: InboundMessage, proof: ) -> (Xcm, u128); + } +} diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 606b1244263a..eb6e7b332d1e 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -30,8 +30,14 @@ pub enum VersionedMessage { pub struct Message { /// The origin address pub origin: H160, + /// The assets + pub assets: Vec>, /// The command originating from the Gateway contract pub xcm: Vec, + /// The claimer in the case that funds get trapped. + pub claimer: MultiAddres, + /// The relayer reward that will be allocated after the delivery of this message. + pub reward: u128 } #[cfg(test)] From 3e9b03dc3765a4306cc9a9e592dd253264a7fdc2 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 4 Nov 2024 15:36:24 +0200 Subject: [PATCH 03/20] send token unit test --- .../pallets/inbound-queue-v2/src/test.rs | 74 +++++++------------ .../primitives/router/src/inbound/v2.rs | 26 +++++-- 2 files changed, 46 insertions(+), 54 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs index afc17420d1b4..5420db36fb47 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs @@ -7,11 +7,9 @@ use hex_literal::hex; use snowbridge_core::{inbound::Proof, ChannelId}; use sp_keyring::AccountKeyring as Keyring; use sp_runtime::DispatchError; -use sp_std::convert::From; -use crate::{Error, Event as InboundQueueEvent}; - -use crate::mock::*; +use crate::{mock::*, Error, Event as InboundQueueEvent}; +use snowbridge_router_primitives::inbound::v2::Asset; #[test] fn test_submit_happy_path() { @@ -107,25 +105,6 @@ fn test_submit_with_invalid_nonce() { }); } -#[test] -fn test_submit_no_funds_to_reward_relayers_just_ignore() { - new_tester().execute_with(|| { - let relayer: AccountId = Keyring::Bob.into(); - let origin = RuntimeOrigin::signed(relayer); - - // Submit message - let message = Message { - event_log: mock_event_log(), - proof: Proof { - receipt_proof: Default::default(), - execution_proof: mock_execution_proof(), - }, - }; - // Check submit successfully in case no funds available - assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); - }); -} - #[test] fn test_set_operating_mode() { new_tester().execute_with(|| { @@ -162,31 +141,30 @@ fn test_set_operating_mode_root_only() { } #[test] -fn test_submit_no_funds_to_reward_relayers_and_ed_preserved() { +fn test_send_native_erc20_token_payload() { new_tester().execute_with(|| { - let relayer: AccountId = Keyring::Bob.into(); - let origin = RuntimeOrigin::signed(relayer); - - // Submit message successfully - let message = Message { - event_log: mock_event_log(), - proof: Proof { - receipt_proof: Default::default(), - execution_proof: mock_execution_proof(), - }, - }; - assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); - - // Submit another message with nonce set as 2 - let mut event_log = mock_event_log(); - event_log.data[31] = 2; - let message = Message { - event_log, - proof: Proof { - receipt_proof: Default::default(), - execution_proof: mock_execution_proof(), - }, - }; - assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); + // To generate test data: forge test --match-test testSendEther -vvvv + let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf04005615deb798bb3e4dfa0139dfa1b3d433cc23b72f0000b2d3595bf00600000000000000000000").to_vec(); + let message = MessageV2::decode(&mut payload.as_ref()); + assert_ok!(message.clone()); + + let inbound_message = message.unwrap(); + + let expected_origin: H160 = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf").into(); + let expected_token_id: H160 = hex!("5615deb798bb3e4dfa0139dfa1b3d433cc23b72f").into(); + let expected_value = 500000000000000000u128; + let expected_xcm: Vec = vec![]; + let expected_claimer: Option> = None; + + assert_eq!(expected_origin, inbound_message.origin); + assert_eq!(1, inbound_message.assets.len()); + if let Asset::NativeTokenERC20 { token_id, value } = &inbound_message.assets[0] { + assert_eq!(expected_token_id, *token_id); + assert_eq!(expected_value, *value); + } else { + panic!("Expected NativeTokenERC20 asset"); + } + assert_eq!(expected_xcm, inbound_message.xcm); + assert_eq!(expected_claimer, inbound_message.claimer); }); } diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index eb6e7b332d1e..7a9cde2ff301 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -31,13 +31,27 @@ pub struct Message { /// The origin address pub origin: H160, /// The assets - pub assets: Vec>, - /// The command originating from the Gateway contract + pub assets: Vec, + // The command originating from the Gateway contract pub xcm: Vec, - /// The claimer in the case that funds get trapped. - pub claimer: MultiAddres, - /// The relayer reward that will be allocated after the delivery of this message. - pub reward: u128 + // The claimer in the case that funds get trapped. + pub claimer: Option>, +} + +#[derive(Clone, Encode, Decode, RuntimeDebug)] +pub enum Asset { + NativeTokenERC20 { + /// The token ID, native or foreign + token_id: H160, + /// The monetary value of the asset + value: u128 + }, + ForeignTokenERC20 { + /// The token ID, native or foreign + token_id: H256, + /// The monetary value of the asset + value: u128 + } } #[cfg(test)] From 5b92bd0689d3a05237f673736a87f50450d7503f Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 4 Nov 2024 16:24:36 +0200 Subject: [PATCH 04/20] xcm decoding --- Cargo.lock | 1 + .../pallets/inbound-queue-v2/Cargo.toml | 1 + .../pallets/inbound-queue-v2/src/test.rs | 111 +++++++++++++++++- 3 files changed, 111 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 80b763792b44..e0fc00e3c524 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21325,6 +21325,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", + "hex", "hex-literal", "log", "pallet-balances", diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue-v2/Cargo.toml index d212b18d2d54..4d589b86e142 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/Cargo.toml +++ b/bridges/snowbridge/pallets/inbound-queue-v2/Cargo.toml @@ -45,6 +45,7 @@ frame-benchmarking = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } snowbridge-pallet-ethereum-client = { workspace = true, default-features = true } hex-literal = { workspace = true, default-features = true } +hex = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs index 5420db36fb47..d195c21f85a2 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs @@ -9,7 +9,12 @@ use sp_keyring::AccountKeyring as Keyring; use sp_runtime::DispatchError; use crate::{mock::*, Error, Event as InboundQueueEvent}; -use snowbridge_router_primitives::inbound::v2::Asset; +use snowbridge_router_primitives::inbound::v2::Asset as InboundAsset; +use sp_core::H256; +use xcm::opaque::latest::prelude::ClearOrigin; +use xcm::opaque::latest::Asset; +use xcm::opaque::latest::prelude::ReceiveTeleportedAsset; +use codec::DecodeLimit; #[test] fn test_submit_happy_path() { @@ -158,7 +163,7 @@ fn test_send_native_erc20_token_payload() { assert_eq!(expected_origin, inbound_message.origin); assert_eq!(1, inbound_message.assets.len()); - if let Asset::NativeTokenERC20 { token_id, value } = &inbound_message.assets[0] { + if let InboundAsset::NativeTokenERC20 { token_id, value } = &inbound_message.assets[0] { assert_eq!(expected_token_id, *token_id); assert_eq!(expected_value, *value); } else { @@ -168,3 +173,105 @@ fn test_send_native_erc20_token_payload() { assert_eq!(expected_claimer, inbound_message.claimer); }); } + +#[test] +fn test_send_foreign_erc20_token_payload() { + new_tester().execute_with(|| { + let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf040197874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f400000b2d3595bf00600000000000000000000").to_vec(); + let message = MessageV2::decode(&mut payload.as_ref()); + assert_ok!(message.clone()); + + let inbound_message = message.unwrap(); + + let expected_origin: H160 = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf").into(); + let expected_token_id: H256 = hex!("97874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f40").into(); + let expected_value = 500000000000000000u128; + let expected_xcm: Vec = vec![]; + let expected_claimer: Option> = None; + + assert_eq!(expected_origin, inbound_message.origin); + assert_eq!(1, inbound_message.assets.len()); + if let InboundAsset::ForeignTokenERC20 { token_id, value } = &inbound_message.assets[0] { + assert_eq!(expected_token_id, *token_id); + assert_eq!(expected_value, *value); + } else { + panic!("Expected ForeignTokenERC20 asset"); + } + assert_eq!(expected_xcm, inbound_message.xcm); + assert_eq!(expected_claimer, inbound_message.claimer); + }); +} + +#[test] +fn test_inbound_message_with_xcm() { + new_tester().execute_with(|| { + let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf040197874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f400000b2d3595bf00600000000000000000c0508020401000002286bee0a00").to_vec(); + let message = MessageV2::decode(&mut payload.as_ref()); + assert_ok!(message.clone()); + + let inbound_message = message.unwrap(); + + let expected_origin: H160 = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf").into(); + let expected_token_id: H256 = hex!("97874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f40").into(); + let expected_value = 500000000000000000u128; + let expected_xcm: Vec = vec![]; + let expected_claimer: Option> = None; + + assert_eq!(expected_origin, inbound_message.origin); + assert_eq!(1, inbound_message.assets.len()); + if let InboundAsset::ForeignTokenERC20 { token_id, value } = &inbound_message.assets[0] { + assert_eq!(expected_token_id, *token_id); + assert_eq!(expected_value, *value); + } else { + panic!("Expected ForeignTokenERC20 asset"); + } + assert_eq!(expected_xcm, inbound_message.xcm); + assert_eq!(expected_claimer, inbound_message.claimer); + }); +} + +#[test] +fn encode_xcm() { + new_tester().execute_with(|| { + let total_fee_asset: Asset = (Location::parent(), 1_000_000_000).into(); + + let mut instructions: Xcm<()> = vec![ + ReceiveTeleportedAsset(total_fee_asset.into()), + ClearOrigin, + ].into(); + + let versioned_xcm_message = VersionedXcm::V5(instructions.clone()); + + let xcm_bytes = VersionedXcm::encode(&versioned_xcm_message); + let hex_string = hex::encode(xcm_bytes.clone()); + + println!("xcm hex: {}", hex_string); + + let versioned_xcm = VersionedXcm::<()>::decode_with_depth_limit( + MAX_XCM_DECODE_DEPTH, + &mut xcm_bytes.as_ref(), + ); + + assert_ok!(versioned_xcm.clone()); + + // Check if decoding was successful + let decoded_instructions = match versioned_xcm.unwrap() { + VersionedXcm::V5(decoded) => decoded, + _ => { + panic!("unexpected xcm version found") + } + }; + + let mut original_instructions = instructions.into_iter(); + let mut decoded_instructions = decoded_instructions.into_iter(); + + let original_first = original_instructions.next().take(); + let decoded_first = decoded_instructions.next().take(); + assert_eq!(original_first, decoded_first, "First instruction (ReceiveTeleportedAsset) does not match."); + + let original_second = original_instructions.next().take(); + let decoded_second = decoded_instructions.next().take(); + assert_eq!(original_second, decoded_second, "Second instruction (ClearOrigin) does not match."); + }); +} + From 41170b38fe4c0300ca6b3290740d1002c2cf5007 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Tue, 5 Nov 2024 11:31:03 +0200 Subject: [PATCH 05/20] adds more tests --- .../pallets/inbound-queue-v2/src/test.rs | 73 +++++++++++++------ 1 file changed, 52 insertions(+), 21 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs index d195c21f85a2..abf9a3f8a823 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs @@ -9,12 +9,13 @@ use sp_keyring::AccountKeyring as Keyring; use sp_runtime::DispatchError; use crate::{mock::*, Error, Event as InboundQueueEvent}; +use codec::DecodeLimit; use snowbridge_router_primitives::inbound::v2::Asset as InboundAsset; use sp_core::H256; -use xcm::opaque::latest::prelude::ClearOrigin; -use xcm::opaque::latest::Asset; -use xcm::opaque::latest::prelude::ReceiveTeleportedAsset; -use codec::DecodeLimit; +use xcm::opaque::latest::{ + prelude::{ClearOrigin, ReceiveTeleportedAsset}, + Asset, AssetId, Assets, +}; #[test] fn test_submit_happy_path() { @@ -202,31 +203,58 @@ fn test_send_foreign_erc20_token_payload() { }); } +use xcm::opaque::latest::Junctions::Here; #[test] -fn test_inbound_message_with_xcm() { +fn test_register_token_inbound_message_with_xcm_and_claimer() { new_tester().execute_with(|| { - let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf040197874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f400000b2d3595bf00600000000000000000c0508020401000002286bee0a00").to_vec(); + let payload = hex!("5991a2df15a8f6a256d3ec51e99254cd3fb576a904005615deb798bb3e4dfa0139dfa1b3d433cc23b72f00000000000000000000000000000000300508020401000002286bee0a015029e3b139f4393adda86303fcdaa35f60bb7092bf").to_vec(); let message = MessageV2::decode(&mut payload.as_ref()); assert_ok!(message.clone()); let inbound_message = message.unwrap(); - let expected_origin: H160 = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf").into(); - let expected_token_id: H256 = hex!("97874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f40").into(); - let expected_value = 500000000000000000u128; - let expected_xcm: Vec = vec![]; - let expected_claimer: Option> = None; + let expected_origin: H160 = hex!("5991a2df15a8f6a256d3ec51e99254cd3fb576a9").into(); + let expected_token_id: H160 = hex!("5615deb798bb3e4dfa0139dfa1b3d433cc23b72f").into(); + let expected_value = 0u128; + let expected_xcm: Vec = hex!("0508020401000002286bee0a").to_vec(); + let expected_claimer: Option> = Some(hex!("29E3b139f4393aDda86303fcdAa35F60Bb7092bF").to_vec()); assert_eq!(expected_origin, inbound_message.origin); assert_eq!(1, inbound_message.assets.len()); - if let InboundAsset::ForeignTokenERC20 { token_id, value } = &inbound_message.assets[0] { + if let InboundAsset::NativeTokenERC20 { token_id, value } = &inbound_message.assets[0] { assert_eq!(expected_token_id, *token_id); assert_eq!(expected_value, *value); } else { - panic!("Expected ForeignTokenERC20 asset"); + panic!("Expected NativeTokenERC20 asset"); } assert_eq!(expected_xcm, inbound_message.xcm); assert_eq!(expected_claimer, inbound_message.claimer); + + // decode xcm + let versioned_xcm = VersionedXcm::<()>::decode_with_depth_limit( + MAX_XCM_DECODE_DEPTH, + &mut inbound_message.xcm.as_ref(), + ); + + assert_ok!(versioned_xcm.clone()); + + // Check if decoding was successful + let decoded_instructions = match versioned_xcm.unwrap() { + VersionedXcm::V5(decoded) => decoded, + _ => { + panic!("unexpected xcm version found") + } + }; + + let total_fee_asset: Asset = (Location::parent(), 1_000_000_000).into(); + let first_instruction = ReceiveTeleportedAsset(total_fee_asset.into()); + + let mut decoded_instructions = decoded_instructions.into_iter(); + let decoded_first = decoded_instructions.next().take(); + assert!(decoded_first.is_some()); + let decoded_second = decoded_instructions.next().take(); + assert!(decoded_second.is_some()); + assert_eq!(ClearOrigin, decoded_second.unwrap(), "Second instruction (ClearOrigin) does not match."); }); } @@ -235,10 +263,8 @@ fn encode_xcm() { new_tester().execute_with(|| { let total_fee_asset: Asset = (Location::parent(), 1_000_000_000).into(); - let mut instructions: Xcm<()> = vec![ - ReceiveTeleportedAsset(total_fee_asset.into()), - ClearOrigin, - ].into(); + let mut instructions: Xcm<()> = + vec![ReceiveTeleportedAsset(total_fee_asset.into()), ClearOrigin].into(); let versioned_xcm_message = VersionedXcm::V5(instructions.clone()); @@ -259,7 +285,7 @@ fn encode_xcm() { VersionedXcm::V5(decoded) => decoded, _ => { panic!("unexpected xcm version found") - } + }, }; let mut original_instructions = instructions.into_iter(); @@ -267,11 +293,16 @@ fn encode_xcm() { let original_first = original_instructions.next().take(); let decoded_first = decoded_instructions.next().take(); - assert_eq!(original_first, decoded_first, "First instruction (ReceiveTeleportedAsset) does not match."); + assert_eq!( + original_first, decoded_first, + "First instruction (ReceiveTeleportedAsset) does not match." + ); let original_second = original_instructions.next().take(); let decoded_second = decoded_instructions.next().take(); - assert_eq!(original_second, decoded_second, "Second instruction (ClearOrigin) does not match."); + assert_eq!( + original_second, decoded_second, + "Second instruction (ClearOrigin) does not match." + ); }); } - From d81affce74fb323bb96c5caa685e4cdaee384ae1 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Tue, 5 Nov 2024 14:46:40 +0200 Subject: [PATCH 06/20] converter --- .../pallets/inbound-queue-v2/src/lib.rs | 33 +++------ .../primitives/router/src/inbound/v2.rs | 74 ++++++++++++++++++- 2 files changed, 82 insertions(+), 25 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index 0eaadcb6a3c3..d923e4211ccb 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -95,7 +95,8 @@ pub mod pallet { type GatewayAddress: Get; type WeightInfo: WeightInfo; - + /// AssetHub parachain ID + type AssetHubParaId: Get; #[cfg(feature = "runtime-benchmarks")] type Helper: BenchmarkHelper; } @@ -139,6 +140,8 @@ pub mod pallet { Verification(VerificationError), /// XCMP send failure Send(SendError), + /// Message conversion error + ConvertMessage(ConvertMessageError), } #[derive(Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo, PalletError)] @@ -204,25 +207,7 @@ pub mod pallet { let message = MessageV2::decode_all(&mut envelope.payload.as_ref()) .map_err(|_| Error::::InvalidPayload)?; - // Decode xcm - let versioned_xcm = VersionedXcm::<()>::decode_with_depth_limit( - MAX_XCM_DECODE_DEPTH, - &mut message.xcm.as_ref(), - ) - .map_err(|_| Error::::InvalidPayload)?; - let xcm: Xcm<()> = versioned_xcm.try_into().map_err(|_| >::InvalidPayload)?; - - log::info!( - target: LOG_TARGET, - "💫 xcm decoded as {:?}", - xcm, - ); - - // Set nonce flag to true - >::try_mutate(envelope.nonce, |done| -> DispatchResult { - *done = true; - Ok(()) - })?; + let xcm = convert_message(message)?; // Todo: Deposit fee(in Ether) to RewardLeger which should cover all of: // T::RewardLeger::deposit(who, envelope.fee.into())?; @@ -233,11 +218,17 @@ pub mod pallet { // e. The reward // Attempt to forward XCM to AH - let dest = Location::new(1, [Parachain(1000)]); + let dest = Location::new(1, [Parachain(T::AssetHubParaId)]); let (message_id, _) = send_xcm::(dest, xcm).map_err(Error::::from)?; Self::deposit_event(Event::MessageReceived { nonce: envelope.nonce, message_id }); + // Set nonce flag to true + >::try_mutate(envelope.nonce, |done| -> DispatchResult { + *done = true; + Ok(()) + })?; + Ok(()) } diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 7a9cde2ff301..08bbae3c7c6e 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -4,17 +4,18 @@ use codec::{Decode, Encode}; use core::marker::PhantomData; -use frame_support::{traits::tokens::Balance as BalanceT, PalletError}; +use frame_support::PalletError; use scale_info::TypeInfo; use snowbridge_core::TokenId; use sp_core::{Get, RuntimeDebug, H160, H256}; -use sp_io::hashing::blake2_256; -use sp_runtime::{traits::MaybeEquivalence, MultiAddress}; +use sp_runtime::MultiAddress; use sp_std::prelude::*; use xcm::prelude::{Junction::AccountKey20, *}; -use xcm_executor::traits::ConvertLocation; +use xcm::MAX_XCM_DECODE_DEPTH; +use codec::DecodeLimit; const MINIMUM_DEPOSIT: u128 = 1; +const LOG_TARGET: &str = "snowbridge-router-primitives"; /// Messages from Ethereum are versioned. This is because in future, /// we may want to evolve the protocol so that the ethereum side sends XCM messages directly. @@ -54,6 +55,71 @@ pub enum Asset { } } +/// Reason why a message conversion failed. +#[derive(Copy, Clone, TypeInfo, PalletError, Encode, Decode, RuntimeDebug)] +pub enum ConvertMessageError { + /// The XCM provided with the message could not be decoded into XCM. + InvalidXCM, + /// Invalid claimer MultiAddress provided in payload. + InvalidClaimer, +} + +pub trait ConvertMessage { + fn convert( + message: Message, + ) -> Result, ConvertMessageError>; +} + +pub struct MessageToXcm< + EthereumUniversalLocation, + AssetHubLocation, +> where + EthereumUniversalLocation: Get, + AssetHubLocation: Get, +{ + _phantom: PhantomData<( + EthereumUniversalLocation, + AssetHubLocation, + )>, +} + +impl< + EthereumUniversalLocation, + AssetHubLocation, +> ConvertMessage +for MessageToXcm< + EthereumUniversalLocation, + AssetHubLocation, +> + where + EthereumUniversalLocation: Get, + AssetHubLocation: Get, +{ + fn convert(message: Message) -> Result, ConvertMessageError> { + // Decode xcm + let versioned_xcm = VersionedXcm::<()>::decode_with_depth_limit( + MAX_XCM_DECODE_DEPTH, + &mut message.xcm.as_ref(), + ).map_err(|_| ConvertMessageError::InvalidXCM)?; + let message_xcm: Xcm<()> = versioned_xcm.try_into().map_err(|_| ConvertMessageError::InvalidXCM)?; + + log::debug!(target: LOG_TARGET,"xcm decoded as {:?}", message_xcm); + + let origin_location: Location = Location::new(2, [EthereumUniversalLocation::get().into(), Junction::AccountKey20{ key: message.origin.into(), network: None}.into()]); + let instructions = vec![ + AliasOrigin(origin_location), + ]; + + if let Some(claimer) = message.claimer { + let claimer = MultiAddress::decode(&mut claimer.as_ref()).map_err(|_| ConvertMessageError::InvalidClaimer)?; + let claimer_location: Location = Location::new(1, [AssetHubLocation::get().into(), claimer.into()]); + instructions.push(SetAssetClaimer { location: claimer_location }); + } + + Ok(instructions.into()) + } +} + #[cfg(test)] mod tests { use crate::inbound::{CallIndex, GlobalConsensusEthereumConvertsFor}; From 163866029c93614476bc20a2abb3a6445a406553 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Wed, 6 Nov 2024 15:15:26 +0200 Subject: [PATCH 07/20] inbound progress --- .../pallets/inbound-queue-v2/src/lib.rs | 13 ++-- .../pallets/inbound-queue-v2/src/mock.rs | 29 ++++--- .../pallets/inbound-queue-v2/src/test.rs | 9 ++- .../primitives/router/src/inbound/v2.rs | 76 ++++++++++++++----- 4 files changed, 84 insertions(+), 43 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index d923e4211ccb..080ee1c3a6bd 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -44,8 +44,7 @@ use scale_info::TypeInfo; use sp_core::H160; use sp_std::vec; use xcm::{ - prelude::{send_xcm, Junction::*, Location, SendError as XcmpSendError, SendXcm, Xcm}, - VersionedXcm, MAX_XCM_DECODE_DEPTH, + prelude::{send_xcm, Junction::*, Location, SendError as XcmpSendError, SendXcm}, }; use snowbridge_core::{ @@ -53,12 +52,15 @@ use snowbridge_core::{ BasicOperatingMode, }; use snowbridge_router_primitives::inbound::v2::Message as MessageV2; +use snowbridge_router_primitives::inbound::v2::ConvertMessage; pub use weights::WeightInfo; #[cfg(feature = "runtime-benchmarks")] use snowbridge_beacon_primitives::BeaconHeader; +use snowbridge_router_primitives::inbound::v2::ConvertMessageError; + pub use pallet::*; pub const LOG_TARGET: &str = "snowbridge-inbound-queue:v2"; @@ -66,11 +68,9 @@ pub const LOG_TARGET: &str = "snowbridge-inbound-queue:v2"; #[frame_support::pallet] pub mod pallet { use super::*; - use codec::DecodeLimit; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; - use sp_core::H256; #[pallet::pallet] pub struct Pallet(_); @@ -97,6 +97,7 @@ pub mod pallet { type WeightInfo: WeightInfo; /// AssetHub parachain ID type AssetHubParaId: Get; + type MessageConverter: ConvertMessage; #[cfg(feature = "runtime-benchmarks")] type Helper: BenchmarkHelper; } @@ -207,7 +208,7 @@ pub mod pallet { let message = MessageV2::decode_all(&mut envelope.payload.as_ref()) .map_err(|_| Error::::InvalidPayload)?; - let xcm = convert_message(message)?; + let xcm = T::MessageConverter::convert(message).map_err(|e| Error::::ConvertMessage(e))?; // Todo: Deposit fee(in Ether) to RewardLeger which should cover all of: // T::RewardLeger::deposit(who, envelope.fee.into())?; @@ -218,7 +219,7 @@ pub mod pallet { // e. The reward // Attempt to forward XCM to AH - let dest = Location::new(1, [Parachain(T::AssetHubParaId)]); + let dest = Location::new(1, [Parachain(T::AssetHubParaId::get())]); let (message_id, _) = send_xcm::(dest, xcm).map_err(Error::::from)?; Self::deposit_event(Event::MessageReceived { nonce: envelope.nonce, message_id }); diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index f08ce202f9bc..7f754fe20874 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -18,7 +18,7 @@ use sp_runtime::{ }; use sp_std::{convert::From, default::Default}; use xcm::{latest::SendXcm, prelude::*}; - +use snowbridge_router_primitives::inbound::v2::MessageToXcm; use crate::{self as inbound_queue}; type Block = frame_system::mocking::MockBlock; @@ -100,20 +100,6 @@ impl Verifier for MockVerifier { const GATEWAY_ADDRESS: [u8; 20] = hex!["eda338e4dc46038493b885327842fd3e301cab39"]; -parameter_types! { - pub const EthereumNetwork: xcm::v3::NetworkId = xcm::v3::NetworkId::Ethereum { chain_id: 11155111 }; - pub const GatewayAddress: H160 = H160(GATEWAY_ADDRESS); - pub const CreateAssetCall: [u8;2] = [53, 0]; - pub const CreateAssetExecutionFee: u128 = 2_000_000_000; - pub const CreateAssetDeposit: u128 = 100_000_000_000; - pub const SendTokenExecutionFee: u128 = 1_000_000_000; - pub const InitialFund: u128 = 1_000_000_000_000; - pub const InboundQueuePalletInstance: u8 = 80; - pub UniversalLocation: InteriorLocation = - [GlobalConsensus(Westend), Parachain(1002)].into(); - pub AssetHubFromEthereum: Location = Location::new(1,[GlobalConsensus(Westend),Parachain(1000)]); -} - #[cfg(feature = "runtime-benchmarks")] impl BenchmarkHelper for Test { // not implemented since the MockVerifier is used for tests @@ -158,12 +144,25 @@ impl MaybeEquivalence for MockTokenIdConvert { } } +parameter_types! { + pub const EthereumNetwork: xcm::v5::NetworkId = xcm::v5::NetworkId::Ethereum { chain_id: 11155111 }; + pub const GatewayAddress: H160 = H160(GATEWAY_ADDRESS); + pub const InboundQueuePalletInstance: u8 = 80; + pub AssetHubLocation: InteriorLocation = Parachain(1000).into(); +} + impl inbound_queue::Config for Test { type RuntimeEvent = RuntimeEvent; type Verifier = MockVerifier; type XcmSender = MockXcmSender; type WeightInfo = (); type GatewayAddress = GatewayAddress; + type AssetHubParaId = ConstU32<1000>; + type MessageConverter = MessageToXcm< + EthereumNetwork, + AssetHubLocation, + InboundQueuePalletInstance, + >; #[cfg(feature = "runtime-benchmarks")] type Helper = Test; } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs index abf9a3f8a823..475539e6e1a9 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs @@ -10,12 +10,16 @@ use sp_runtime::DispatchError; use crate::{mock::*, Error, Event as InboundQueueEvent}; use codec::DecodeLimit; -use snowbridge_router_primitives::inbound::v2::Asset as InboundAsset; +use snowbridge_router_primitives::inbound::v2::InboundAsset; use sp_core::H256; use xcm::opaque::latest::{ prelude::{ClearOrigin, ReceiveTeleportedAsset}, Asset, AssetId, Assets, }; +use xcm::VersionedXcm; +use xcm::MAX_XCM_DECODE_DEPTH; +use snowbridge_router_primitives::inbound::v2::ConvertMessage; +use xcm::prelude::{Junction::AccountKey20, *}; #[test] fn test_submit_happy_path() { @@ -246,9 +250,6 @@ fn test_register_token_inbound_message_with_xcm_and_claimer() { } }; - let total_fee_asset: Asset = (Location::parent(), 1_000_000_000).into(); - let first_instruction = ReceiveTeleportedAsset(total_fee_asset.into()); - let mut decoded_instructions = decoded_instructions.into_iter(); let decoded_first = decoded_instructions.next().take(); assert!(decoded_first.is_some()); diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 08bbae3c7c6e..444b1f9a7ee5 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -6,15 +6,12 @@ use codec::{Decode, Encode}; use core::marker::PhantomData; use frame_support::PalletError; use scale_info::TypeInfo; -use snowbridge_core::TokenId; use sp_core::{Get, RuntimeDebug, H160, H256}; -use sp_runtime::MultiAddress; use sp_std::prelude::*; use xcm::prelude::{Junction::AccountKey20, *}; use xcm::MAX_XCM_DECODE_DEPTH; use codec::DecodeLimit; -const MINIMUM_DEPOSIT: u128 = 1; const LOG_TARGET: &str = "snowbridge-router-primitives"; /// Messages from Ethereum are versioned. This is because in future, @@ -32,7 +29,7 @@ pub struct Message { /// The origin address pub origin: H160, /// The assets - pub assets: Vec, + pub assets: Vec, // The command originating from the Gateway contract pub xcm: Vec, // The claimer in the case that funds get trapped. @@ -40,15 +37,15 @@ pub struct Message { } #[derive(Clone, Encode, Decode, RuntimeDebug)] -pub enum Asset { +pub enum InboundAsset { NativeTokenERC20 { - /// The token ID, native or foreign + /// The native token ID token_id: H160, /// The monetary value of the asset value: u128 }, ForeignTokenERC20 { - /// The token ID, native or foreign + /// The foreign token ID token_id: H256, /// The monetary value of the asset value: u128 @@ -71,29 +68,35 @@ pub trait ConvertMessage { } pub struct MessageToXcm< - EthereumUniversalLocation, + EthereumNetwork, AssetHubLocation, + InboundQueuePalletInstance, > where - EthereumUniversalLocation: Get, + EthereumNetwork: Get, AssetHubLocation: Get, + InboundQueuePalletInstance: Get, { _phantom: PhantomData<( - EthereumUniversalLocation, + EthereumNetwork, AssetHubLocation, + InboundQueuePalletInstance, )>, } impl< - EthereumUniversalLocation, + EthereumNetwork, AssetHubLocation, + InboundQueuePalletInstance, > ConvertMessage for MessageToXcm< - EthereumUniversalLocation, + EthereumNetwork, AssetHubLocation, + InboundQueuePalletInstance, > where - EthereumUniversalLocation: Get, + EthereumNetwork: Get, AssetHubLocation: Get, + InboundQueuePalletInstance: Get, { fn convert(message: Message) -> Result, ConvertMessageError> { // Decode xcm @@ -105,17 +108,54 @@ for MessageToXcm< log::debug!(target: LOG_TARGET,"xcm decoded as {:?}", message_xcm); - let origin_location: Location = Location::new(2, [EthereumUniversalLocation::get().into(), Junction::AccountKey20{ key: message.origin.into(), network: None}.into()]); - let instructions = vec![ - AliasOrigin(origin_location), + let network = EthereumNetwork::get(); + + let mut origin_location = Location::new(2, GlobalConsensus(network)).push_interior(AccountKey20 { + key: message.origin.into(), network: None + }).map_err(|_| ConvertMessageError::InvalidXCM)?; + + let network = EthereumNetwork::get(); + + let fee_asset = Location::new(1, Here); + let fee_value = 1_000_000_000u128; // TODO configure + let fee: Asset = (fee_asset, fee_value).into(); + let mut instructions = vec![ + ReceiveTeleportedAsset(fee.clone().into()), + BuyExecution{fees: fee, weight_limit: Unlimited}, + DescendOrigin(PalletInstance(InboundQueuePalletInstance::get()).into()), + UniversalOrigin(GlobalConsensus(network)), + AliasOrigin(origin_location.into()), ]; + for asset in &message.assets { + match asset { + InboundAsset::NativeTokenERC20 { token_id, value } => { + let token_location: Location = Location::new(2, [GlobalConsensus(EthereumNetwork::get()), AccountKey20{network: None, key: (*token_id).into()}]); + instructions.push(ReserveAssetDeposited((token_location, *value).into())); + } + InboundAsset::ForeignTokenERC20 { token_id, value } => { + // TODO check how token is represented as H256 on AH, assets pallet? + let token_location: Location = Location::new(0, [AccountId32 {network: None, id: (*token_id).into()}]); + // TODO Is this token always on AH? Would probably need to distinguish between tokens on other parachains eventually + instructions.push(WithdrawAsset((token_location, *value).into())); + } + } + } + if let Some(claimer) = message.claimer { - let claimer = MultiAddress::decode(&mut claimer.as_ref()).map_err(|_| ConvertMessageError::InvalidClaimer)?; - let claimer_location: Location = Location::new(1, [AssetHubLocation::get().into(), claimer.into()]); + let claimer = Junction::decode(&mut claimer.as_ref()).map_err(|_| ConvertMessageError::InvalidClaimer)?; + let claimer_location: Location = Location::new(0, [claimer.into()]); instructions.push(SetAssetClaimer { location: claimer_location }); } + // TODO not sure this is correct, should the junction be prefixed with GlobalConsensus(EthereumNetwork::get()? + instructions.push(DescendOrigin(AccountKey20 { + key: message.origin.into(), network: None + }.into())); + + // Add the XCM the user specified to the end of the XCM + instructions.extend(message_xcm.0); + Ok(instructions.into()) } } From 716404b512b15426242b65d3b1f2e31d1a6b0773 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 7 Nov 2024 07:16:01 +0200 Subject: [PATCH 08/20] tests --- .../pallets/inbound-queue-v2/src/mock.rs | 1 - .../primitives/router/src/inbound/v2.rs | 116 ++++++++++-------- 2 files changed, 62 insertions(+), 55 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index 7f754fe20874..6ff547172ee3 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -160,7 +160,6 @@ impl inbound_queue::Config for Test { type AssetHubParaId = ConstU32<1000>; type MessageConverter = MessageToXcm< EthereumNetwork, - AssetHubLocation, InboundQueuePalletInstance, >; #[cfg(feature = "runtime-benchmarks")] diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 444b1f9a7ee5..43e497e29620 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -2,15 +2,16 @@ // SPDX-FileCopyrightText: 2023 Snowfork //! Converts messages from Ethereum to XCM messages -use codec::{Decode, Encode}; +use codec::{Decode, DecodeLimit, Encode}; use core::marker::PhantomData; use frame_support::PalletError; use scale_info::TypeInfo; use sp_core::{Get, RuntimeDebug, H160, H256}; use sp_std::prelude::*; -use xcm::prelude::{Junction::AccountKey20, *}; -use xcm::MAX_XCM_DECODE_DEPTH; -use codec::DecodeLimit; +use xcm::{ + prelude::{Junction::AccountKey20, *}, + MAX_XCM_DECODE_DEPTH, +}; const LOG_TARGET: &str = "snowbridge-router-primitives"; @@ -42,14 +43,14 @@ pub enum InboundAsset { /// The native token ID token_id: H160, /// The monetary value of the asset - value: u128 + value: u128, }, ForeignTokenERC20 { /// The foreign token ID token_id: H256, /// The monetary value of the asset - value: u128 - } + value: u128, + }, } /// Reason why a message conversion failed. @@ -62,96 +63,89 @@ pub enum ConvertMessageError { } pub trait ConvertMessage { - fn convert( - message: Message, - ) -> Result, ConvertMessageError>; + fn convert(message: Message) -> Result, ConvertMessageError>; } -pub struct MessageToXcm< - EthereumNetwork, - AssetHubLocation, - InboundQueuePalletInstance, -> where +pub struct MessageToXcm +where EthereumNetwork: Get, - AssetHubLocation: Get, InboundQueuePalletInstance: Get, { - _phantom: PhantomData<( - EthereumNetwork, - AssetHubLocation, - InboundQueuePalletInstance, - )>, + _phantom: PhantomData<(EthereumNetwork, InboundQueuePalletInstance)>, } -impl< - EthereumNetwork, - AssetHubLocation, - InboundQueuePalletInstance, -> ConvertMessage -for MessageToXcm< - EthereumNetwork, - AssetHubLocation, - InboundQueuePalletInstance, -> - where - EthereumNetwork: Get, - AssetHubLocation: Get, - InboundQueuePalletInstance: Get, +impl ConvertMessage + for MessageToXcm +where + EthereumNetwork: Get, + InboundQueuePalletInstance: Get, { fn convert(message: Message) -> Result, ConvertMessageError> { // Decode xcm let versioned_xcm = VersionedXcm::<()>::decode_with_depth_limit( MAX_XCM_DECODE_DEPTH, &mut message.xcm.as_ref(), - ).map_err(|_| ConvertMessageError::InvalidXCM)?; - let message_xcm: Xcm<()> = versioned_xcm.try_into().map_err(|_| ConvertMessageError::InvalidXCM)?; + ) + .map_err(|_| ConvertMessageError::InvalidXCM)?; + let message_xcm: Xcm<()> = + versioned_xcm.try_into().map_err(|_| ConvertMessageError::InvalidXCM)?; log::debug!(target: LOG_TARGET,"xcm decoded as {:?}", message_xcm); let network = EthereumNetwork::get(); - let mut origin_location = Location::new(2, GlobalConsensus(network)).push_interior(AccountKey20 { - key: message.origin.into(), network: None - }).map_err(|_| ConvertMessageError::InvalidXCM)?; + let origin_location = Location::new(2, GlobalConsensus(network)) + .push_interior(AccountKey20 { key: message.origin.into(), network: None }) + .map_err(|_| ConvertMessageError::InvalidXCM)?; let network = EthereumNetwork::get(); let fee_asset = Location::new(1, Here); - let fee_value = 1_000_000_000u128; // TODO configure + let fee_value = 1_000_000_000u128; // TODO needs to be dry-run to get the fee but also + // need to add a fee here for the dry run... Chicken/egg problem? let fee: Asset = (fee_asset, fee_value).into(); let mut instructions = vec![ ReceiveTeleportedAsset(fee.clone().into()), - BuyExecution{fees: fee, weight_limit: Unlimited}, + BuyExecution { fees: fee, weight_limit: Unlimited }, DescendOrigin(PalletInstance(InboundQueuePalletInstance::get()).into()), - UniversalOrigin(GlobalConsensus(network)), + UniversalOrigin(GlobalConsensus(network)), AliasOrigin(origin_location.into()), ]; for asset in &message.assets { match asset { InboundAsset::NativeTokenERC20 { token_id, value } => { - let token_location: Location = Location::new(2, [GlobalConsensus(EthereumNetwork::get()), AccountKey20{network: None, key: (*token_id).into()}]); + let token_location: Location = Location::new( + 2, + [ + GlobalConsensus(EthereumNetwork::get()), + AccountKey20 { network: None, key: (*token_id).into() }, + ], + ); instructions.push(ReserveAssetDeposited((token_location, *value).into())); - } + }, InboundAsset::ForeignTokenERC20 { token_id, value } => { // TODO check how token is represented as H256 on AH, assets pallet? - let token_location: Location = Location::new(0, [AccountId32 {network: None, id: (*token_id).into()}]); - // TODO Is this token always on AH? Would probably need to distinguish between tokens on other parachains eventually + let token_location: Location = + Location::new(0, [AccountId32 { network: None, id: (*token_id).into() }]); + // TODO Is this token always on AH? Would probably need to distinguish between + // tokens on other parachains eventually instructions.push(WithdrawAsset((token_location, *value).into())); - } + }, } } if let Some(claimer) = message.claimer { - let claimer = Junction::decode(&mut claimer.as_ref()).map_err(|_| ConvertMessageError::InvalidClaimer)?; + let claimer = Junction::decode(&mut claimer.as_ref()) + .map_err(|_| ConvertMessageError::InvalidClaimer)?; let claimer_location: Location = Location::new(0, [claimer.into()]); instructions.push(SetAssetClaimer { location: claimer_location }); } - // TODO not sure this is correct, should the junction be prefixed with GlobalConsensus(EthereumNetwork::get()? - instructions.push(DescendOrigin(AccountKey20 { - key: message.origin.into(), network: None - }.into())); + // TODO not sure this is correct, should the junction be prefixed with + // GlobalConsensus(EthereumNetwork::get()? + instructions + .push(DescendOrigin(AccountKey20 { key: message.origin.into(), network: None }.into())); // Add the XCM the user specified to the end of the XCM instructions.extend(message_xcm.0); @@ -162,9 +156,14 @@ for MessageToXcm< #[cfg(test)] mod tests { - use crate::inbound::{CallIndex, GlobalConsensusEthereumConvertsFor}; + use crate::inbound::{ + v2::{ConvertMessage, Message, MessageToXcm}, + CallIndex, GlobalConsensusEthereumConvertsFor, + }; + use codec::Decode; use frame_support::{assert_ok, parameter_types}; use hex_literal::hex; + use sp_runtime::traits::ConstU8; use xcm::prelude::*; use xcm_executor::traits::ConvertLocation; @@ -232,4 +231,13 @@ mod tests { assert_eq!(reanchored_asset_with_ethereum_context, asset.clone()); } } + + #[test] + fn test_convert_message() { + let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf040197874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f400000b2d3595bf00600000000000000000000").to_vec(); + let message = Message::decode(&mut payload.as_ref()); + assert_ok!(message.clone()); + let result = MessageToXcm::>::convert(message.unwrap()); + assert_ok!(result); + } } From 89a157d654b95e1cd897ce9ff7aaad114795093c Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 7 Nov 2024 11:17:53 +0200 Subject: [PATCH 09/20] tests --- .../primitives/router/src/inbound/v2.rs | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 43e497e29620..99591a0d7d86 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -58,6 +58,8 @@ pub enum InboundAsset { pub enum ConvertMessageError { /// The XCM provided with the message could not be decoded into XCM. InvalidXCM, + /// The XCM provided with the message could not be decoded into versioned XCM. + InvalidVersionedXCM, /// Invalid claimer MultiAddress provided in payload. InvalidClaimer, } @@ -81,14 +83,17 @@ where InboundQueuePalletInstance: Get, { fn convert(message: Message) -> Result, ConvertMessageError> { - // Decode xcm - let versioned_xcm = VersionedXcm::<()>::decode_with_depth_limit( - MAX_XCM_DECODE_DEPTH, - &mut message.xcm.as_ref(), - ) - .map_err(|_| ConvertMessageError::InvalidXCM)?; - let message_xcm: Xcm<()> = - versioned_xcm.try_into().map_err(|_| ConvertMessageError::InvalidXCM)?; + let mut message_xcm : Xcm<()> = Xcm::new(); + if message.xcm.len() > 0{ + // Decode xcm + let versioned_xcm = VersionedXcm::<()>::decode_with_depth_limit( + MAX_XCM_DECODE_DEPTH, + &mut message.xcm.as_ref(), + ) + .map_err(|_| ConvertMessageError::InvalidVersionedXCM)?; + message_xcm = + versioned_xcm.try_into().map_err(|_| ConvertMessageError::InvalidXCM)?; + } log::debug!(target: LOG_TARGET,"xcm decoded as {:?}", message_xcm); @@ -147,7 +152,7 @@ where instructions .push(DescendOrigin(AccountKey20 { key: message.origin.into(), network: None }.into())); - // Add the XCM the user specified to the end of the XCM + // Add the XCM sent in the message to the end of the xcm instruction instructions.extend(message_xcm.0); Ok(instructions.into()) From e814b29f83ac98188b0d7222ceb661eba11b9657 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 7 Nov 2024 11:25:47 +0200 Subject: [PATCH 10/20] fix alias origin --- bridges/snowbridge/primitives/router/src/inbound/v2.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 99591a0d7d86..d159a223b317 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -114,7 +114,6 @@ where BuyExecution { fees: fee, weight_limit: Unlimited }, DescendOrigin(PalletInstance(InboundQueuePalletInstance::get()).into()), UniversalOrigin(GlobalConsensus(network)), - AliasOrigin(origin_location.into()), ]; for asset in &message.assets { @@ -147,10 +146,9 @@ where instructions.push(SetAssetClaimer { location: claimer_location }); } - // TODO not sure this is correct, should the junction be prefixed with - // GlobalConsensus(EthereumNetwork::get()? - instructions - .push(DescendOrigin(AccountKey20 { key: message.origin.into(), network: None }.into())); + // Set the alias origin to the original sender on Ethereum. Important to be before the + // arbitrary XCM that is appended to the message on the next line. + instructions.push(AliasOrigin(origin_location.into())); // Add the XCM sent in the message to the end of the xcm instruction instructions.extend(message_xcm.0); From 697b51ccdd99734ac4d2cc2a87e00ab372d41790 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 7 Nov 2024 11:59:37 +0200 Subject: [PATCH 11/20] cleanup runtime interface --- Cargo.lock | 11 +++++++++++ Cargo.toml | 1 + .../pallets/inbound-queue-v2/runtime-api/Cargo.toml | 10 ++++------ .../pallets/inbound-queue-v2/runtime-api/src/lib.rs | 12 +++++------- .../snowbridge/primitives/router/src/inbound/v2.rs | 4 ++-- 5 files changed, 23 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4b4db98d455c..85f4ab4b2234 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21173,6 +21173,17 @@ dependencies = [ "wasm-bindgen-test", ] +[[package]] +name = "snowbridge-inbound-queue-v2-runtime-api" +version = "0.2.0" +dependencies = [ + "snowbridge-core", + "snowbridge-merkle-tree", + "snowbridge-router-primitives", + "sp-api 26.0.0", + "staging-xcm", +] + [[package]] name = "snowbridge-merkle-tree" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index c85e41de0f32..4c50363fec0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,7 @@ members = [ "bridges/snowbridge/pallets/inbound-queue", "bridges/snowbridge/pallets/inbound-queue-v2", "bridges/snowbridge/pallets/inbound-queue-v2/fixtures", + "bridges/snowbridge/pallets/inbound-queue-v2/runtime-api", "bridges/snowbridge/pallets/inbound-queue/fixtures", "bridges/snowbridge/pallets/outbound-queue", "bridges/snowbridge/pallets/outbound-queue-v2", diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/Cargo.toml index 7745f4e23902..73a381017426 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/Cargo.toml +++ b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/Cargo.toml @@ -15,20 +15,18 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { features = ["derive"], workspace = true } -sp-std = { workspace = true } sp-api = { workspace = true } -frame-support = { workspace = true } snowbridge-merkle-tree = { workspace = true } snowbridge-core = { workspace = true } +snowbridge-router-primitives = { workspace = true } +xcm = { workspace = true } [features] default = ["std"] std = [ - "codec/std", - "frame-support/std", "snowbridge-core/std", "snowbridge-merkle-tree/std", + "snowbridge-router-primitives/std", "sp-api/std", - "sp-std/std", + "xcm/std", ] diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/src/lib.rs index 97f76aa62d30..03720b7ca3d2 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/src/lib.rs @@ -2,16 +2,14 @@ // SPDX-FileCopyrightText: 2023 Snowfork #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::traits::tokens::Balance as BalanceT; -use snowbridge_core::{ - inbound::v2::InboundMessage, - PricingParameters, -}; +use snowbridge_core::inbound::Proof; +use snowbridge_router_primitives::inbound::v2::Message; +use xcm::latest::Xcm; sp_api::decl_runtime_apis! { - pub trait InboundQueueApi where Balance: BalanceT + pub trait InboundQueueApiV2 { /// Dry runs the provided message on AH to provide the XCM payload and execution cost. - fn dry_run(message: InboundMessage, proof: ) -> (Xcm, u128); + fn dry_run(message: Message, proof: Proof) -> (Xcm<()>, u128); } } diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index d159a223b317..a849c3891bfa 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -25,7 +25,7 @@ pub enum VersionedMessage { /// For V2, the ethereum side sends messages which are transcoded into XCM. These messages are /// self-contained, in that they can be transcoded using only information in the message. -#[derive(Clone, Encode, Decode, RuntimeDebug)] +#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct Message { /// The origin address pub origin: H160, @@ -37,7 +37,7 @@ pub struct Message { pub claimer: Option>, } -#[derive(Clone, Encode, Decode, RuntimeDebug)] +#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] pub enum InboundAsset { NativeTokenERC20 { /// The native token ID From b20b72808252cc8c01a837765811d062767229c2 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Fri, 8 Nov 2024 09:34:23 +0200 Subject: [PATCH 12/20] dry run xcm --- Cargo.lock | 2 + .../pallets/inbound-queue-v2/src/api.rs | 17 ++++ .../pallets/inbound-queue-v2/src/lib.rs | 26 ++++-- .../pallets/inbound-queue-v2/src/mock.rs | 9 +- .../pallets/inbound-queue-v2/src/test.rs | 16 ++-- .../snowbridge/primitives/router/Cargo.toml | 3 + .../primitives/router/src/inbound/dry_run.rs | 86 +++++++++++++++++++ .../primitives/router/src/inbound/mod.rs | 1 + .../primitives/router/src/inbound/v2.rs | 9 +- 9 files changed, 145 insertions(+), 24 deletions(-) create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs create mode 100644 bridges/snowbridge/primitives/router/src/inbound/dry_run.rs diff --git a/Cargo.lock b/Cargo.lock index 85f4ab4b2234..486a0b15695f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21342,6 +21342,7 @@ dependencies = [ "hex-literal", "log", "pallet-balances", + "pallet-xcm", "parity-scale-codec", "scale-info", "serde", @@ -21443,6 +21444,7 @@ name = "snowbridge-router-primitives" version = "0.9.0" dependencies = [ "frame-support", + "frame-system", "hex-literal", "log", "parity-scale-codec", diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs new file mode 100644 index 000000000000..803949346634 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Helpers for implementing runtime api + +use crate::{Config, Error}; +use snowbridge_core::inbound::Proof; +use snowbridge_router_primitives::inbound::v2::{ConvertMessage, Message}; +use xcm::{ + latest::Xcm, + prelude::{Junction::*, Location, SendError as XcmpSendError, SendXcm}, +}; +pub fn dry_run(message: Message, proof: Proof) -> Result<(Xcm<()>, u128), Error> +where + T: Config, +{ + Ok((Xcm::<()>::new(), 0)) +} diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index 080ee1c3a6bd..cf435a3f6ad5 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -22,7 +22,7 @@ //! * [`Call::submit`]: Submit a message for verification and dispatch the final destination //! parachain. #![cfg_attr(not(feature = "std"), no_std)] - +pub mod api; mod envelope; #[cfg(feature = "runtime-benchmarks")] @@ -41,9 +41,11 @@ use envelope::Envelope; use frame_support::PalletError; use frame_system::ensure_signed; use scale_info::TypeInfo; +use snowbridge_core::inbound::Proof; use sp_core::H160; use sp_std::vec; use xcm::{ + latest::Xcm, prelude::{send_xcm, Junction::*, Location, SendError as XcmpSendError, SendXcm}, }; @@ -51,8 +53,7 @@ use snowbridge_core::{ inbound::{Message, VerificationError, Verifier}, BasicOperatingMode, }; -use snowbridge_router_primitives::inbound::v2::Message as MessageV2; -use snowbridge_router_primitives::inbound::v2::ConvertMessage; +use snowbridge_router_primitives::inbound::v2::{ConvertMessage, Message as MessageV2}; pub use weights::WeightInfo; @@ -89,7 +90,6 @@ pub mod pallet { /// XCM message sender type XcmSender: SendXcm; - /// Address of the Gateway contract #[pallet::constant] type GatewayAddress: Get; @@ -208,7 +208,8 @@ pub mod pallet { let message = MessageV2::decode_all(&mut envelope.payload.as_ref()) .map_err(|_| Error::::InvalidPayload)?; - let xcm = T::MessageConverter::convert(message).map_err(|e| Error::::ConvertMessage(e))?; + let xcm = + T::MessageConverter::convert(message).map_err(|e| Error::::ConvertMessage(e))?; // Todo: Deposit fee(in Ether) to RewardLeger which should cover all of: // T::RewardLeger::deposit(who, envelope.fee.into())?; @@ -246,4 +247,19 @@ pub mod pallet { Ok(()) } } + + impl Pallet { + pub fn dry_run_message( + message: Message, + proof: Proof, + ) -> Result<(Xcm<()>, u128), Error> { + let xcm = + T::MessageConverter::convert(message).map_err(|e| Error::::ConvertMessage(e))?; + let origin_location = Location::new(1, Parachain(1002).into()); + let dry_run_result = + pallet_xcm::dry_run_xcm(origin_location, xcm).map_err(Error::::from)?; + + Ok((xcm, 0)) + } + } } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index 6ff547172ee3..a3874598ec9c 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -2,6 +2,7 @@ // SPDX-FileCopyrightText: 2023 Snowfork use super::*; +use crate::{self as inbound_queue}; use frame_support::{derive_impl, parameter_types, traits::ConstU32, weights::IdentityFee}; use hex_literal::hex; use snowbridge_beacon_primitives::{ @@ -11,6 +12,7 @@ use snowbridge_core::{ inbound::{Log, Proof, VerificationError}, TokenId, }; +use snowbridge_router_primitives::inbound::v2::MessageToXcm; use sp_core::{H160, H256}; use sp_runtime::{ traits::{IdentifyAccount, IdentityLookup, MaybeEquivalence, Verify}, @@ -18,8 +20,6 @@ use sp_runtime::{ }; use sp_std::{convert::From, default::Default}; use xcm::{latest::SendXcm, prelude::*}; -use snowbridge_router_primitives::inbound::v2::MessageToXcm; -use crate::{self as inbound_queue}; type Block = frame_system::mocking::MockBlock; @@ -158,10 +158,7 @@ impl inbound_queue::Config for Test { type WeightInfo = (); type GatewayAddress = GatewayAddress; type AssetHubParaId = ConstU32<1000>; - type MessageConverter = MessageToXcm< - EthereumNetwork, - InboundQueuePalletInstance, - >; + type MessageConverter = MessageToXcm; #[cfg(feature = "runtime-benchmarks")] type Helper = Test; } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs index 475539e6e1a9..68fcf49b53d7 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs @@ -10,16 +10,16 @@ use sp_runtime::DispatchError; use crate::{mock::*, Error, Event as InboundQueueEvent}; use codec::DecodeLimit; -use snowbridge_router_primitives::inbound::v2::InboundAsset; +use snowbridge_router_primitives::inbound::v2::{ConvertMessage, InboundAsset}; use sp_core::H256; -use xcm::opaque::latest::{ - prelude::{ClearOrigin, ReceiveTeleportedAsset}, - Asset, AssetId, Assets, +use xcm::{ + opaque::latest::{ + prelude::{ClearOrigin, ReceiveTeleportedAsset}, + Asset, AssetId, Assets, + }, + prelude::{Junction::AccountKey20, *}, + VersionedXcm, MAX_XCM_DECODE_DEPTH, }; -use xcm::VersionedXcm; -use xcm::MAX_XCM_DECODE_DEPTH; -use snowbridge_router_primitives::inbound::v2::ConvertMessage; -use xcm::prelude::{Junction::AccountKey20, *}; #[test] fn test_submit_happy_path() { diff --git a/bridges/snowbridge/primitives/router/Cargo.toml b/bridges/snowbridge/primitives/router/Cargo.toml index 664f2dbf7930..aa4b3177c00b 100644 --- a/bridges/snowbridge/primitives/router/Cargo.toml +++ b/bridges/snowbridge/primitives/router/Cargo.toml @@ -17,6 +17,7 @@ scale-info = { features = ["derive"], workspace = true } log = { workspace = true } frame-support = { workspace = true } +frame-system = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } @@ -37,6 +38,7 @@ default = ["std"] std = [ "codec/std", "frame-support/std", + "frame-system/std", "log/std", "scale-info/std", "snowbridge-core/std", @@ -50,6 +52,7 @@ std = [ ] runtime-benchmarks = [ "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", "snowbridge-core/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", diff --git a/bridges/snowbridge/primitives/router/src/inbound/dry_run.rs b/bridges/snowbridge/primitives/router/src/inbound/dry_run.rs new file mode 100644 index 000000000000..4b8c4bc87593 --- /dev/null +++ b/bridges/snowbridge/primitives/router/src/inbound/dry_run.rs @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use crate::inbound::v2::{ConvertMessage, Message}; +use codec::{Decode, Encode}; +use frame_support::{ + dispatch::{GetDispatchInfo, PostDispatchInfo}, + Parameter, +}; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{Dispatchable, PhantomData}, + Weight, +}; +use xcm::{ + latest::Xcm, + opaque::latest::{ExecuteXcm, Junction, Junction::Parachain, Location}, +}; +use xcm_builder::InspectMessageQueues; + +#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo)] +pub enum DryRunError { + /// Message cannot be decoded. + InvalidPayload, + /// An API call is unsupported. + Unimplemented, + /// Converting a versioned data structure from one version to another failed. + VersionedConversionFailed, +} + +pub trait DryRunMessage { + fn dry_run_xcm(message: Message) -> Result, DryRunError>; +} + +pub struct MessageToFeeEstimate +where + Runtime: frame_system::Config, + Router: InspectMessageQueues, + RuntimeCall: Parameter + + GetDispatchInfo + + Dispatchable::RuntimeOrigin, PostInfo = PostDispatchInfo>, + XcmExecutor: ExecuteXcm<::RuntimeCall>, + MessageConverter: ConvertMessage, +{ + _phantom: PhantomData<(Runtime, Router, RuntimeCall, XcmExecutor, MessageConverter)>, +} + +impl DryRunMessage + for MessageToFeeEstimate +where + Runtime: frame_system::Config, + Router: InspectMessageQueues, + RuntimeCall: Parameter + + GetDispatchInfo + + Dispatchable::RuntimeOrigin, PostInfo = PostDispatchInfo>, + XcmExecutor: ExecuteXcm<::RuntimeCall>, + MessageConverter: ConvertMessage, +{ + fn dry_run_xcm(message: Message) -> Result, DryRunError> { + let message_xcm = + MessageConverter::convert(message).map_err(|error| DryRunError::InvalidPayload)?; + let origin_location = Location::new(1, Parachain(1002)); + + let xcm_program = Xcm::::from(message_xcm.clone().try_into().unwrap()); + + let origin_location: Location = origin_location + .try_into() + .map_err(|error| DryRunError::VersionedConversionFailed)?; + let xcm: Xcm = + xcm_program.try_into().map_err(|error| DryRunError::VersionedConversionFailed)?; + let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256); + frame_system::Pallet::::reset_events(); // To make sure we only record events from current call. + let result = XcmExecutor::prepare_and_execute( + origin_location, + xcm, + &mut hash, + Weight::MAX, // Max limit available for execution. + Weight::zero(), + ); + let forwarded_xcms = Router::get_messages(); + let events: Vec<::RuntimeEvent> = + frame_system::Pallet::::read_events_no_consensus() + .map(|record| record.event.clone()) + .collect(); + Ok(vec![].into()) + } +} diff --git a/bridges/snowbridge/primitives/router/src/inbound/mod.rs b/bridges/snowbridge/primitives/router/src/inbound/mod.rs index 37890e878603..1c8e68abf1be 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/mod.rs @@ -2,6 +2,7 @@ // SPDX-FileCopyrightText: 2023 Snowfork // SPDX-FileCopyrightText: 2021-2022 Parity Technologies (UK) Ltd. +pub mod dry_run; pub mod v1; pub mod v2; use codec::Encode; diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index a849c3891bfa..9cc273b01392 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -83,16 +83,15 @@ where InboundQueuePalletInstance: Get, { fn convert(message: Message) -> Result, ConvertMessageError> { - let mut message_xcm : Xcm<()> = Xcm::new(); - if message.xcm.len() > 0{ + let mut message_xcm: Xcm<()> = Xcm::new(); + if message.xcm.len() > 0 { // Decode xcm let versioned_xcm = VersionedXcm::<()>::decode_with_depth_limit( MAX_XCM_DECODE_DEPTH, &mut message.xcm.as_ref(), ) - .map_err(|_| ConvertMessageError::InvalidVersionedXCM)?; - message_xcm = - versioned_xcm.try_into().map_err(|_| ConvertMessageError::InvalidXCM)?; + .map_err(|_| ConvertMessageError::InvalidVersionedXCM)?; + message_xcm = versioned_xcm.try_into().map_err(|_| ConvertMessageError::InvalidXCM)?; } log::debug!(target: LOG_TARGET,"xcm decoded as {:?}", message_xcm); From a0fe8de6591ce51731a25eb719a2913cc49b2c8b Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 18 Nov 2024 09:39:03 +0200 Subject: [PATCH 13/20] cleanup --- Cargo.lock | 1 - .../pallets/inbound-queue-v2/src/api.rs | 9 ++++---- .../pallets/inbound-queue-v2/src/lib.rs | 21 ++----------------- .../pallets/inbound-queue-v2/src/mock.rs | 18 +++++++++++++--- .../pallets/inbound-queue-v2/src/test.rs | 11 +++++----- .../primitives/router/src/inbound/dry_run.rs | 18 +++++++++------- 6 files changed, 37 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 486a0b15695f..e8b5dc6dce42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21342,7 +21342,6 @@ dependencies = [ "hex-literal", "log", "pallet-balances", - "pallet-xcm", "parity-scale-codec", "scale-info", "serde", diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs index 803949346634..d63ef00b034d 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs @@ -1,17 +1,18 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023 Snowfork -//! Helpers for implementing runtime api +//! Implements the dry-run API. use crate::{Config, Error}; use snowbridge_core::inbound::Proof; -use snowbridge_router_primitives::inbound::v2::{ConvertMessage, Message}; +use snowbridge_router_primitives::inbound::v2::Message; use xcm::{ latest::Xcm, - prelude::{Junction::*, Location, SendError as XcmpSendError, SendXcm}, }; -pub fn dry_run(message: Message, proof: Proof) -> Result<(Xcm<()>, u128), Error> +use snowbridge_router_primitives::inbound::dry_run::DryRunMessage; +pub fn dry_run(message: Message, _proof: Proof) -> Result<(Xcm<()>, u128), Error> where T: Config, { + let _dry_run_result = T::XCMDryRunner::dry_run_xcm(message); Ok((Xcm::<()>::new(), 0)) } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index cf435a3f6ad5..dfb850d1c5d3 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -41,11 +41,9 @@ use envelope::Envelope; use frame_support::PalletError; use frame_system::ensure_signed; use scale_info::TypeInfo; -use snowbridge_core::inbound::Proof; use sp_core::H160; use sp_std::vec; use xcm::{ - latest::Xcm, prelude::{send_xcm, Junction::*, Location, SendError as XcmpSendError, SendXcm}, }; @@ -54,7 +52,7 @@ use snowbridge_core::{ BasicOperatingMode, }; use snowbridge_router_primitives::inbound::v2::{ConvertMessage, Message as MessageV2}; - +use snowbridge_router_primitives::inbound::dry_run::DryRunMessage; pub use weights::WeightInfo; #[cfg(feature = "runtime-benchmarks")] @@ -90,10 +88,10 @@ pub mod pallet { /// XCM message sender type XcmSender: SendXcm; + type XCMDryRunner: DryRunMessage; /// Address of the Gateway contract #[pallet::constant] type GatewayAddress: Get; - type WeightInfo: WeightInfo; /// AssetHub parachain ID type AssetHubParaId: Get; @@ -247,19 +245,4 @@ pub mod pallet { Ok(()) } } - - impl Pallet { - pub fn dry_run_message( - message: Message, - proof: Proof, - ) -> Result<(Xcm<()>, u128), Error> { - let xcm = - T::MessageConverter::convert(message).map_err(|e| Error::::ConvertMessage(e))?; - let origin_location = Location::new(1, Parachain(1002).into()); - let dry_run_result = - pallet_xcm::dry_run_xcm(origin_location, xcm).map_err(Error::::from)?; - - Ok((xcm, 0)) - } - } } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index a3874598ec9c..5a898644ef3e 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -3,7 +3,7 @@ use super::*; use crate::{self as inbound_queue}; -use frame_support::{derive_impl, parameter_types, traits::ConstU32, weights::IdentityFee}; +use frame_support::{derive_impl, parameter_types, traits::ConstU32}; use hex_literal::hex; use snowbridge_beacon_primitives::{ types::deneb, BeaconHeader, ExecutionProof, Fork, ForkVersions, VersionedExecutionPayloadHeader, @@ -13,13 +13,15 @@ use snowbridge_core::{ TokenId, }; use snowbridge_router_primitives::inbound::v2::MessageToXcm; -use sp_core::{H160, H256}; +use sp_core::H160; use sp_runtime::{ traits::{IdentifyAccount, IdentityLookup, MaybeEquivalence, Verify}, - BuildStorage, FixedU128, MultiSignature, + BuildStorage, MultiSignature, }; use sp_std::{convert::From, default::Default}; use xcm::{latest::SendXcm, prelude::*}; +use snowbridge_router_primitives::inbound::dry_run::DryRunError; +use snowbridge_router_primitives::inbound::v2::Message; type Block = frame_system::mocking::MockBlock; @@ -132,6 +134,15 @@ impl SendXcm for MockXcmSender { } } +pub struct MockXcmDryRunner; + +impl DryRunMessage for MockXcmDryRunner { + + fn dry_run_xcm(_message: Message) -> Result, DryRunError> { + Ok(Xcm::<()>::new()) + } +} + pub const DOT: u128 = 10_000_000_000; pub struct MockTokenIdConvert; @@ -155,6 +166,7 @@ impl inbound_queue::Config for Test { type RuntimeEvent = RuntimeEvent; type Verifier = MockVerifier; type XcmSender = MockXcmSender; + type XCMDryRunner = MockXcmDryRunner; type WeightInfo = (); type GatewayAddress = GatewayAddress; type AssetHubParaId = ConstU32<1000>; diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs index 68fcf49b53d7..29dacc686b6c 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs @@ -4,20 +4,20 @@ use super::*; use frame_support::{assert_noop, assert_ok}; use hex_literal::hex; -use snowbridge_core::{inbound::Proof, ChannelId}; +use snowbridge_core::inbound::Proof; use sp_keyring::AccountKeyring as Keyring; use sp_runtime::DispatchError; use crate::{mock::*, Error, Event as InboundQueueEvent}; use codec::DecodeLimit; -use snowbridge_router_primitives::inbound::v2::{ConvertMessage, InboundAsset}; +use snowbridge_router_primitives::inbound::v2::InboundAsset; use sp_core::H256; use xcm::{ opaque::latest::{ prelude::{ClearOrigin, ReceiveTeleportedAsset}, - Asset, AssetId, Assets, + Asset }, - prelude::{Junction::AccountKey20, *}, + prelude::*, VersionedXcm, MAX_XCM_DECODE_DEPTH, }; @@ -207,7 +207,6 @@ fn test_send_foreign_erc20_token_payload() { }); } -use xcm::opaque::latest::Junctions::Here; #[test] fn test_register_token_inbound_message_with_xcm_and_claimer() { new_tester().execute_with(|| { @@ -264,7 +263,7 @@ fn encode_xcm() { new_tester().execute_with(|| { let total_fee_asset: Asset = (Location::parent(), 1_000_000_000).into(); - let mut instructions: Xcm<()> = + let instructions: Xcm<()> = vec![ReceiveTeleportedAsset(total_fee_asset.into()), ClearOrigin].into(); let versioned_xcm_message = VersionedXcm::V5(instructions.clone()); diff --git a/bridges/snowbridge/primitives/router/src/inbound/dry_run.rs b/bridges/snowbridge/primitives/router/src/inbound/dry_run.rs index 4b8c4bc87593..c03eb34d628a 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/dry_run.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/dry_run.rs @@ -1,5 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023 Snowfork +extern crate alloc; + use crate::inbound::v2::{ConvertMessage, Message}; use codec::{Decode, Encode}; use frame_support::{ @@ -13,7 +15,7 @@ use sp_runtime::{ }; use xcm::{ latest::Xcm, - opaque::latest::{ExecuteXcm, Junction, Junction::Parachain, Location}, + opaque::latest::{ExecuteXcm, Junction::Parachain, Location}, }; use xcm_builder::InspectMessageQueues; @@ -57,30 +59,30 @@ where { fn dry_run_xcm(message: Message) -> Result, DryRunError> { let message_xcm = - MessageConverter::convert(message).map_err(|error| DryRunError::InvalidPayload)?; + MessageConverter::convert(message).map_err(|_| DryRunError::InvalidPayload)?; let origin_location = Location::new(1, Parachain(1002)); let xcm_program = Xcm::::from(message_xcm.clone().try_into().unwrap()); let origin_location: Location = origin_location .try_into() - .map_err(|error| DryRunError::VersionedConversionFailed)?; + .map_err(|_| DryRunError::VersionedConversionFailed)?; let xcm: Xcm = - xcm_program.try_into().map_err(|error| DryRunError::VersionedConversionFailed)?; + xcm_program.try_into().map_err(|_| DryRunError::VersionedConversionFailed)?; let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256); frame_system::Pallet::::reset_events(); // To make sure we only record events from current call. - let result = XcmExecutor::prepare_and_execute( + let _result = XcmExecutor::prepare_and_execute( origin_location, xcm, &mut hash, Weight::MAX, // Max limit available for execution. Weight::zero(), ); - let forwarded_xcms = Router::get_messages(); - let events: Vec<::RuntimeEvent> = + let _forwarded_xcms = Router::get_messages(); + let _events: alloc::vec::Vec<::RuntimeEvent> = frame_system::Pallet::::read_events_no_consensus() .map(|record| record.event.clone()) .collect(); - Ok(vec![].into()) + Ok(alloc::vec![].into()) } } From 490d091f5c4c65f809bc2523f577037fb6907a22 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 18 Nov 2024 10:24:38 +0200 Subject: [PATCH 14/20] remove unnecessary dry-run logic --- .../pallets/inbound-queue-v2/src/api.rs | 7 +-- .../pallets/inbound-queue-v2/src/lib.rs | 10 ++-- .../pallets/inbound-queue-v2/src/mock.rs | 8 +-- .../pallets/inbound-queue-v2/src/test.rs | 2 +- .../primitives/router/src/inbound/dry_run.rs | 54 +++---------------- 5 files changed, 19 insertions(+), 62 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs index d63ef00b034d..e8c969f389c2 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs @@ -4,11 +4,8 @@ use crate::{Config, Error}; use snowbridge_core::inbound::Proof; -use snowbridge_router_primitives::inbound::v2::Message; -use xcm::{ - latest::Xcm, -}; -use snowbridge_router_primitives::inbound::dry_run::DryRunMessage; +use snowbridge_router_primitives::inbound::{dry_run::DryRunMessage, v2::Message}; +use xcm::latest::Xcm; pub fn dry_run(message: Message, _proof: Proof) -> Result<(Xcm<()>, u128), Error> where T: Config, diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index dfb850d1c5d3..392cb4d0dcf6 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -43,16 +43,16 @@ use frame_system::ensure_signed; use scale_info::TypeInfo; use sp_core::H160; use sp_std::vec; -use xcm::{ - prelude::{send_xcm, Junction::*, Location, SendError as XcmpSendError, SendXcm}, -}; +use xcm::prelude::{send_xcm, Junction::*, Location, SendError as XcmpSendError, SendXcm}; use snowbridge_core::{ inbound::{Message, VerificationError, Verifier}, BasicOperatingMode, }; -use snowbridge_router_primitives::inbound::v2::{ConvertMessage, Message as MessageV2}; -use snowbridge_router_primitives::inbound::dry_run::DryRunMessage; +use snowbridge_router_primitives::inbound::{ + dry_run::DryRunMessage, + v2::{ConvertMessage, Message as MessageV2}, +}; pub use weights::WeightInfo; #[cfg(feature = "runtime-benchmarks")] diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index 5a898644ef3e..f35337da3a61 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -12,7 +12,10 @@ use snowbridge_core::{ inbound::{Log, Proof, VerificationError}, TokenId, }; -use snowbridge_router_primitives::inbound::v2::MessageToXcm; +use snowbridge_router_primitives::inbound::{ + dry_run::DryRunError, + v2::{Message, MessageToXcm}, +}; use sp_core::H160; use sp_runtime::{ traits::{IdentifyAccount, IdentityLookup, MaybeEquivalence, Verify}, @@ -20,8 +23,6 @@ use sp_runtime::{ }; use sp_std::{convert::From, default::Default}; use xcm::{latest::SendXcm, prelude::*}; -use snowbridge_router_primitives::inbound::dry_run::DryRunError; -use snowbridge_router_primitives::inbound::v2::Message; type Block = frame_system::mocking::MockBlock; @@ -137,7 +138,6 @@ impl SendXcm for MockXcmSender { pub struct MockXcmDryRunner; impl DryRunMessage for MockXcmDryRunner { - fn dry_run_xcm(_message: Message) -> Result, DryRunError> { Ok(Xcm::<()>::new()) } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs index 29dacc686b6c..211959c4bd4d 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs @@ -15,7 +15,7 @@ use sp_core::H256; use xcm::{ opaque::latest::{ prelude::{ClearOrigin, ReceiveTeleportedAsset}, - Asset + Asset, }, prelude::*, VersionedXcm, MAX_XCM_DECODE_DEPTH, diff --git a/bridges/snowbridge/primitives/router/src/inbound/dry_run.rs b/bridges/snowbridge/primitives/router/src/inbound/dry_run.rs index c03eb34d628a..a4db78666816 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/dry_run.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/dry_run.rs @@ -4,20 +4,13 @@ extern crate alloc; use crate::inbound::v2::{ConvertMessage, Message}; use codec::{Decode, Encode}; -use frame_support::{ - dispatch::{GetDispatchInfo, PostDispatchInfo}, - Parameter, -}; use scale_info::TypeInfo; use sp_runtime::{ - traits::{Dispatchable, PhantomData}, - Weight, + traits::PhantomData, }; use xcm::{ latest::Xcm, - opaque::latest::{ExecuteXcm, Junction::Parachain, Location}, }; -use xcm_builder::InspectMessageQueues; #[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo)] pub enum DryRunError { @@ -33,56 +26,23 @@ pub trait DryRunMessage { fn dry_run_xcm(message: Message) -> Result, DryRunError>; } -pub struct MessageToFeeEstimate +pub struct MessageToXCM where - Runtime: frame_system::Config, - Router: InspectMessageQueues, - RuntimeCall: Parameter - + GetDispatchInfo - + Dispatchable::RuntimeOrigin, PostInfo = PostDispatchInfo>, - XcmExecutor: ExecuteXcm<::RuntimeCall>, + MessageConverter: ConvertMessage, { - _phantom: PhantomData<(Runtime, Router, RuntimeCall, XcmExecutor, MessageConverter)>, + _phantom: PhantomData, } -impl DryRunMessage - for MessageToFeeEstimate +impl DryRunMessage + for MessageToXCM where - Runtime: frame_system::Config, - Router: InspectMessageQueues, - RuntimeCall: Parameter - + GetDispatchInfo - + Dispatchable::RuntimeOrigin, PostInfo = PostDispatchInfo>, - XcmExecutor: ExecuteXcm<::RuntimeCall>, MessageConverter: ConvertMessage, { fn dry_run_xcm(message: Message) -> Result, DryRunError> { let message_xcm = MessageConverter::convert(message).map_err(|_| DryRunError::InvalidPayload)?; - let origin_location = Location::new(1, Parachain(1002)); - - let xcm_program = Xcm::::from(message_xcm.clone().try_into().unwrap()); - let origin_location: Location = origin_location - .try_into() - .map_err(|_| DryRunError::VersionedConversionFailed)?; - let xcm: Xcm = - xcm_program.try_into().map_err(|_| DryRunError::VersionedConversionFailed)?; - let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256); - frame_system::Pallet::::reset_events(); // To make sure we only record events from current call. - let _result = XcmExecutor::prepare_and_execute( - origin_location, - xcm, - &mut hash, - Weight::MAX, // Max limit available for execution. - Weight::zero(), - ); - let _forwarded_xcms = Router::get_messages(); - let _events: alloc::vec::Vec<::RuntimeEvent> = - frame_system::Pallet::::read_events_no_consensus() - .map(|record| record.event.clone()) - .collect(); - Ok(alloc::vec![].into()) + Ok(message_xcm) } } From bf500d27842262448c8779b5f92a0f4674f2f673 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 18 Nov 2024 10:50:41 +0200 Subject: [PATCH 15/20] cleanup --- .../pallets/inbound-queue-v2/src/api.rs | 9 ++-- .../pallets/inbound-queue-v2/src/lib.rs | 6 +-- .../pallets/inbound-queue-v2/src/mock.rs | 9 ---- .../primitives/router/src/inbound/dry_run.rs | 48 ------------------- .../primitives/router/src/inbound/mod.rs | 1 - .../src/bridge_to_ethereum_config.rs | 5 +- 6 files changed, 9 insertions(+), 69 deletions(-) delete mode 100644 bridges/snowbridge/primitives/router/src/inbound/dry_run.rs diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs index e8c969f389c2..a285a7c5af42 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs @@ -4,12 +4,13 @@ use crate::{Config, Error}; use snowbridge_core::inbound::Proof; -use snowbridge_router_primitives::inbound::{dry_run::DryRunMessage, v2::Message}; +use snowbridge_router_primitives::inbound::v2::{ConvertMessage, Message}; use xcm::latest::Xcm; -pub fn dry_run(message: Message, _proof: Proof) -> Result<(Xcm<()>, u128), Error> + +pub fn dry_run(message: Message, _proof: Proof) -> Result, Error> where T: Config, { - let _dry_run_result = T::XCMDryRunner::dry_run_xcm(message); - Ok((Xcm::<()>::new(), 0)) + let xcm = T::MessageConverter::convert(message).map_err(|e| Error::::ConvertMessage(e))?; + Ok(xcm) } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index 392cb4d0dcf6..144f78603986 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -49,10 +49,7 @@ use snowbridge_core::{ inbound::{Message, VerificationError, Verifier}, BasicOperatingMode, }; -use snowbridge_router_primitives::inbound::{ - dry_run::DryRunMessage, - v2::{ConvertMessage, Message as MessageV2}, -}; +use snowbridge_router_primitives::inbound::v2::{ConvertMessage, Message as MessageV2}; pub use weights::WeightInfo; #[cfg(feature = "runtime-benchmarks")] @@ -88,7 +85,6 @@ pub mod pallet { /// XCM message sender type XcmSender: SendXcm; - type XCMDryRunner: DryRunMessage; /// Address of the Gateway contract #[pallet::constant] type GatewayAddress: Get; diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index f35337da3a61..307dd31479b5 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -135,14 +135,6 @@ impl SendXcm for MockXcmSender { } } -pub struct MockXcmDryRunner; - -impl DryRunMessage for MockXcmDryRunner { - fn dry_run_xcm(_message: Message) -> Result, DryRunError> { - Ok(Xcm::<()>::new()) - } -} - pub const DOT: u128 = 10_000_000_000; pub struct MockTokenIdConvert; @@ -166,7 +158,6 @@ impl inbound_queue::Config for Test { type RuntimeEvent = RuntimeEvent; type Verifier = MockVerifier; type XcmSender = MockXcmSender; - type XCMDryRunner = MockXcmDryRunner; type WeightInfo = (); type GatewayAddress = GatewayAddress; type AssetHubParaId = ConstU32<1000>; diff --git a/bridges/snowbridge/primitives/router/src/inbound/dry_run.rs b/bridges/snowbridge/primitives/router/src/inbound/dry_run.rs deleted file mode 100644 index a4db78666816..000000000000 --- a/bridges/snowbridge/primitives/router/src/inbound/dry_run.rs +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2023 Snowfork -extern crate alloc; - -use crate::inbound::v2::{ConvertMessage, Message}; -use codec::{Decode, Encode}; -use scale_info::TypeInfo; -use sp_runtime::{ - traits::PhantomData, -}; -use xcm::{ - latest::Xcm, -}; - -#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo)] -pub enum DryRunError { - /// Message cannot be decoded. - InvalidPayload, - /// An API call is unsupported. - Unimplemented, - /// Converting a versioned data structure from one version to another failed. - VersionedConversionFailed, -} - -pub trait DryRunMessage { - fn dry_run_xcm(message: Message) -> Result, DryRunError>; -} - -pub struct MessageToXCM -where - - MessageConverter: ConvertMessage, -{ - _phantom: PhantomData, -} - -impl DryRunMessage - for MessageToXCM -where - MessageConverter: ConvertMessage, -{ - fn dry_run_xcm(message: Message) -> Result, DryRunError> { - let message_xcm = - MessageConverter::convert(message).map_err(|_| DryRunError::InvalidPayload)?; - - Ok(message_xcm) - } -} diff --git a/bridges/snowbridge/primitives/router/src/inbound/mod.rs b/bridges/snowbridge/primitives/router/src/inbound/mod.rs index 1c8e68abf1be..37890e878603 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/mod.rs @@ -2,7 +2,6 @@ // SPDX-FileCopyrightText: 2023 Snowfork // SPDX-FileCopyrightText: 2021-2022 Parity Technologies (UK) Ltd. -pub mod dry_run; pub mod v1; pub mod v2; use codec::Encode; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index b0a606552460..efb1364ead4d 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -26,7 +26,6 @@ use parachains_common::{AccountId, Balance}; use snowbridge_beacon_primitives::{Fork, ForkVersions}; use snowbridge_core::{gwei, meth, AllowSiblingsOnly, PricingParameters, Rewards}; use snowbridge_router_primitives::{ - inbound::v1::MessageToXcm, outbound::{v1::EthereumBlobExporter, v2::EthereumBlobExporter as EthereumBlobExporterV2}, }; use sp_core::H160; @@ -95,7 +94,7 @@ impl snowbridge_pallet_inbound_queue::Config for Runtime { type GatewayAddress = EthereumGatewayAddress; #[cfg(feature = "runtime-benchmarks")] type Helper = Runtime; - type MessageConverter = MessageToXcm< + type MessageConverter = snowbridge_router_primitives::inbound::v1::MessageToXcm< CreateAssetCall, CreateAssetDeposit, ConstU8, @@ -124,6 +123,8 @@ impl snowbridge_pallet_inbound_queue_v2::Config for Runtime { #[cfg(feature = "runtime-benchmarks")] type Helper = Runtime; type WeightInfo = crate::weights::snowbridge_pallet_inbound_queue_v2::WeightInfo; + type AssetHubParaId = ConstU32<1000>; + type MessageConverter = snowbridge_router_primitives::inbound::v2::MessageToXcm>; } impl snowbridge_pallet_outbound_queue::Config for Runtime { From cb9111fa8c0a86277be209367185576f667fad2b Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 18 Nov 2024 15:48:12 +0200 Subject: [PATCH 16/20] add foreign erc20 token id lookup --- .../pallets/inbound-queue-v2/src/mock.rs | 13 +++------- .../primitives/router/src/inbound/v2.rs | 26 ++++++++++--------- .../src/bridge_to_ethereum_config.rs | 2 +- 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index 307dd31479b5..6a9279b75686 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -12,10 +12,7 @@ use snowbridge_core::{ inbound::{Log, Proof, VerificationError}, TokenId, }; -use snowbridge_router_primitives::inbound::{ - dry_run::DryRunError, - v2::{Message, MessageToXcm}, -}; +use snowbridge_router_primitives::inbound::v2::MessageToXcm; use sp_core::H160; use sp_runtime::{ traits::{IdentifyAccount, IdentityLookup, MaybeEquivalence, Verify}, @@ -135,8 +132,6 @@ impl SendXcm for MockXcmSender { } } -pub const DOT: u128 = 10_000_000_000; - pub struct MockTokenIdConvert; impl MaybeEquivalence for MockTokenIdConvert { fn convert(_id: &TokenId) -> Option { @@ -161,7 +156,8 @@ impl inbound_queue::Config for Test { type WeightInfo = (); type GatewayAddress = GatewayAddress; type AssetHubParaId = ConstU32<1000>; - type MessageConverter = MessageToXcm; + type MessageConverter = + MessageToXcm; #[cfg(feature = "runtime-benchmarks")] type Helper = Test; } @@ -266,6 +262,3 @@ pub fn mock_execution_proof() -> ExecutionProof { execution_branch: vec![], } } - -pub const ASSET_HUB_PARAID: u32 = 1000u32; -pub const TEMPLATE_PARAID: u32 = 1001u32; diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 9cc273b01392..04826fd6ce93 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -6,7 +6,9 @@ use codec::{Decode, DecodeLimit, Encode}; use core::marker::PhantomData; use frame_support::PalletError; use scale_info::TypeInfo; +use snowbridge_core::TokenId; use sp_core::{Get, RuntimeDebug, H160, H256}; +use sp_runtime::traits::MaybeEquivalence; use sp_std::prelude::*; use xcm::{ prelude::{Junction::AccountKey20, *}, @@ -62,25 +64,29 @@ pub enum ConvertMessageError { InvalidVersionedXCM, /// Invalid claimer MultiAddress provided in payload. InvalidClaimer, + /// Invalid foreign ERC20 token ID + InvalidAsset, } pub trait ConvertMessage { fn convert(message: Message) -> Result, ConvertMessageError>; } -pub struct MessageToXcm +pub struct MessageToXcm where EthereumNetwork: Get, InboundQueuePalletInstance: Get, + ConvertAssetId: MaybeEquivalence, { - _phantom: PhantomData<(EthereumNetwork, InboundQueuePalletInstance)>, + _phantom: PhantomData<(EthereumNetwork, InboundQueuePalletInstance, ConvertAssetId)>, } -impl ConvertMessage - for MessageToXcm +impl ConvertMessage + for MessageToXcm where EthereumNetwork: Get, InboundQueuePalletInstance: Get, + ConvertAssetId: MaybeEquivalence, { fn convert(message: Message) -> Result, ConvertMessageError> { let mut message_xcm: Xcm<()> = Xcm::new(); @@ -105,8 +111,7 @@ where let network = EthereumNetwork::get(); let fee_asset = Location::new(1, Here); - let fee_value = 1_000_000_000u128; // TODO needs to be dry-run to get the fee but also - // need to add a fee here for the dry run... Chicken/egg problem? + let fee_value = 1_000_000_000u128; // TODO get from command let fee: Asset = (fee_asset, fee_value).into(); let mut instructions = vec![ ReceiveTeleportedAsset(fee.clone().into()), @@ -128,12 +133,9 @@ where instructions.push(ReserveAssetDeposited((token_location, *value).into())); }, InboundAsset::ForeignTokenERC20 { token_id, value } => { - // TODO check how token is represented as H256 on AH, assets pallet? - let token_location: Location = - Location::new(0, [AccountId32 { network: None, id: (*token_id).into() }]); - // TODO Is this token always on AH? Would probably need to distinguish between - // tokens on other parachains eventually - instructions.push(WithdrawAsset((token_location, *value).into())); + let asset_id = ConvertAssetId::convert(&token_id) + .ok_or(ConvertMessageError::InvalidAsset)?; + instructions.push(WithdrawAsset((asset_id, *value).into())); }, } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index efb1364ead4d..29d95239c704 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -124,7 +124,7 @@ impl snowbridge_pallet_inbound_queue_v2::Config for Runtime { type Helper = Runtime; type WeightInfo = crate::weights::snowbridge_pallet_inbound_queue_v2::WeightInfo; type AssetHubParaId = ConstU32<1000>; - type MessageConverter = snowbridge_router_primitives::inbound::v2::MessageToXcm>; + type MessageConverter = snowbridge_router_primitives::inbound::v2::MessageToXcm, EthereumSystem>; } impl snowbridge_pallet_outbound_queue::Config for Runtime { From 8372cae30098553333e8c459ff64084f618fffa0 Mon Sep 17 00:00:00 2001 From: Clara van Staden Date: Wed, 20 Nov 2024 13:23:57 +0200 Subject: [PATCH 17/20] Update bridges/snowbridge/primitives/router/src/inbound/v2.rs Co-authored-by: Vincent Geddes <117534+vgeddes@users.noreply.github.com> --- bridges/snowbridge/primitives/router/src/inbound/v2.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 04826fd6ce93..6b65a62129a8 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -25,7 +25,7 @@ pub enum VersionedMessage { V2(Message), } -/// For V2, the ethereum side sends messages which are transcoded into XCM. These messages are +/// The ethereum side sends messages which are transcoded into XCM on BH. These messages are /// self-contained, in that they can be transcoded using only information in the message. #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct Message { From 767b0efa4a2f941f33493871f1f01ace08ad71c9 Mon Sep 17 00:00:00 2001 From: Clara van Staden Date: Wed, 20 Nov 2024 14:52:36 +0200 Subject: [PATCH 18/20] Update bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs Co-authored-by: Vincent Geddes <117534+vgeddes@users.noreply.github.com> --- bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index 144f78603986..14c317c53121 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -196,7 +196,7 @@ pub mod pallet { ensure!(T::GatewayAddress::get() == envelope.gateway, Error::::InvalidGateway); // Verify the message has not been processed - ensure!(!>::contains_key(envelope.nonce), Error::::InvalidNonce); + ensure!(!Nonce::::contains_key(envelope.nonce), Error::::InvalidNonce); // Decode payload into `MessageV2` let message = MessageV2::decode_all(&mut envelope.payload.as_ref()) From 324f0c0c61849e230d541e242c3d9ada0161a3c3 Mon Sep 17 00:00:00 2001 From: Clara van Staden Date: Wed, 20 Nov 2024 14:54:59 +0200 Subject: [PATCH 19/20] Update bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs Co-authored-by: Vincent Geddes <117534+vgeddes@users.noreply.github.com> --- bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index 14c317c53121..b5b917c31a72 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -220,10 +220,7 @@ pub mod pallet { Self::deposit_event(Event::MessageReceived { nonce: envelope.nonce, message_id }); // Set nonce flag to true - >::try_mutate(envelope.nonce, |done| -> DispatchResult { - *done = true; - Ok(()) - })?; + Nonce::::insert(envelope.nonce, ()) Ok(()) } From b9a4e174518b254e73e963a18ce2b793aefb5786 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Wed, 20 Nov 2024 15:07:59 +0200 Subject: [PATCH 20/20] rename InboundAsset to Asset --- bridges/snowbridge/primitives/router/src/inbound/v2.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 04826fd6ce93..03b3c2d94af5 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -32,7 +32,7 @@ pub struct Message { /// The origin address pub origin: H160, /// The assets - pub assets: Vec, + pub assets: Vec, // The command originating from the Gateway contract pub xcm: Vec, // The claimer in the case that funds get trapped. @@ -40,7 +40,7 @@ pub struct Message { } #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] -pub enum InboundAsset { +pub enum Asset { NativeTokenERC20 { /// The native token ID token_id: H160, @@ -112,7 +112,7 @@ where let fee_asset = Location::new(1, Here); let fee_value = 1_000_000_000u128; // TODO get from command - let fee: Asset = (fee_asset, fee_value).into(); + let fee: xcm::prelude::Asset = (fee_asset, fee_value).into(); let mut instructions = vec![ ReceiveTeleportedAsset(fee.clone().into()), BuyExecution { fees: fee, weight_limit: Unlimited }, @@ -122,7 +122,7 @@ where for asset in &message.assets { match asset { - InboundAsset::NativeTokenERC20 { token_id, value } => { + Asset::NativeTokenERC20 { token_id, value } => { let token_location: Location = Location::new( 2, [ @@ -132,7 +132,7 @@ where ); instructions.push(ReserveAssetDeposited((token_location, *value).into())); }, - InboundAsset::ForeignTokenERC20 { token_id, value } => { + Asset::ForeignTokenERC20 { token_id, value } => { let asset_id = ConvertAssetId::convert(&token_id) .ok_or(ConvertMessageError::InvalidAsset)?; instructions.push(WithdrawAsset((asset_id, *value).into()));