mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-31 13:21:01 +00:00
Adds Snowbridge to Rococo runtime (#2522)
# Description Adds Snowbridge to the Rococo bridge hub runtime. Includes config changes required in Rococo asset hub. --------- Co-authored-by: Alistair Singh <alistair.singh7@gmail.com> Co-authored-by: ron <yrong1997@gmail.com> Co-authored-by: Vincent Geddes <vincent.geddes@hey.com> Co-authored-by: claravanstaden <Cats 4 life!>
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
[package]
|
||||
name = "snowbridge-inbound-queue"
|
||||
description = "Snowbridge Inbound Queue"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
authors = ["Snowfork <contact@snowfork.com>"]
|
||||
repository = "https://github.com/Snowfork/snowbridge"
|
||||
license = "Apache-2.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.188", optional = true }
|
||||
codec = { version = "3.6.1", package = "parity-scale-codec", default-features = false, features = ["derive"] }
|
||||
scale-info = { version = "2.9.0", default-features = false, features = ["derive"] }
|
||||
hex-literal = { version = "0.4.1", optional = true }
|
||||
log = { version = "0.4.20", default-features = false }
|
||||
alloy-primitives = { version = "0.4.2", default-features = false, features = ["rlp"] }
|
||||
alloy-sol-types = { version = "0.4.2", default-features = false }
|
||||
alloy-rlp = { version = "0.3.3", default-features = false, features = ["derive"] }
|
||||
num-traits = { version = "0.2.16", default-features = false }
|
||||
|
||||
frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true }
|
||||
frame-support = { path = "../../../../../substrate/frame/support", default-features = false }
|
||||
frame-system = { path = "../../../../../substrate/frame/system", default-features = false }
|
||||
pallet-balances = { path = "../../../../../substrate/frame/balances", default-features = false }
|
||||
sp-core = { path = "../../../../../substrate/primitives/core", default-features = false }
|
||||
sp-std = { path = "../../../../../substrate/primitives/std", default-features = false }
|
||||
sp-io = { path = "../../../../../substrate/primitives/io", default-features = false }
|
||||
sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false }
|
||||
|
||||
xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false }
|
||||
xcm-builder = { package = "staging-xcm-builder", path = "../../../../../polkadot/xcm/xcm-builder", default-features = false }
|
||||
|
||||
snowbridge-core = { path = "../../primitives/core", default-features = false }
|
||||
snowbridge-ethereum = { path = "../../primitives/ethereum", default-features = false }
|
||||
snowbridge-router-primitives = { path = "../../primitives/router", default-features = false }
|
||||
snowbridge-beacon-primitives = { path = "../../primitives/beacon", default-features = false, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking" }
|
||||
sp-keyring = { path = "../../../../../substrate/primitives/keyring" }
|
||||
snowbridge-beacon-primitives = { path = "../../primitives/beacon" }
|
||||
snowbridge-ethereum-beacon-client = { path = "../../pallets/ethereum-beacon-client" }
|
||||
hex-literal = { version = "0.4.1" }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"alloy-primitives/std",
|
||||
"alloy-rlp/std",
|
||||
"alloy-sol-types/std",
|
||||
"codec/std",
|
||||
"frame-benchmarking/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
"log/std",
|
||||
"num-traits/std",
|
||||
"pallet-balances/std",
|
||||
"scale-info/std",
|
||||
"serde",
|
||||
"snowbridge-core/std",
|
||||
"snowbridge-ethereum/std",
|
||||
"snowbridge-router-primitives/std",
|
||||
"sp-core/std",
|
||||
"sp-io/std",
|
||||
"sp-runtime/std",
|
||||
"sp-std/std",
|
||||
"xcm-builder/std",
|
||||
"xcm/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"frame-benchmarking",
|
||||
"frame-benchmarking/runtime-benchmarks",
|
||||
"frame-support/runtime-benchmarks",
|
||||
"frame-system/runtime-benchmarks",
|
||||
"hex-literal",
|
||||
"pallet-balances/runtime-benchmarks",
|
||||
"snowbridge-beacon-primitives",
|
||||
"snowbridge-core/runtime-benchmarks",
|
||||
"snowbridge-ethereum-beacon-client/runtime-benchmarks",
|
||||
"snowbridge-router-primitives/runtime-benchmarks",
|
||||
"sp-runtime/runtime-benchmarks",
|
||||
"xcm-builder/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"frame-support/try-runtime",
|
||||
"frame-system/try-runtime",
|
||||
"pallet-balances/try-runtime",
|
||||
"snowbridge-ethereum-beacon-client/try-runtime",
|
||||
"sp-runtime/try-runtime",
|
||||
]
|
||||
@@ -0,0 +1,40 @@
|
||||
use hex_literal::hex;
|
||||
use snowbridge_beacon_primitives::CompactExecutionHeader;
|
||||
use snowbridge_core::inbound::{Log, Message, Proof};
|
||||
use sp_std::vec;
|
||||
|
||||
pub struct InboundQueueTest {
|
||||
pub execution_header: CompactExecutionHeader,
|
||||
pub message: Message,
|
||||
}
|
||||
|
||||
pub fn make_create_message() -> InboundQueueTest {
|
||||
InboundQueueTest{
|
||||
execution_header: CompactExecutionHeader{
|
||||
parent_hash: hex!("b5608f0af7c3b6fe5c593772fc25436b8d6549eb236adb0855c6ad33e0004e04").into(),
|
||||
block_number: 115,
|
||||
state_root: hex!("47ed174789836c622499d9659a4ac32c3b91a7b15642d39b0a11b82ff23995c1").into(),
|
||||
receipts_root: hex!("42c08b5303fcdf9e49c833fe5f1182cdbc8206bf8aec581125fc34aba11e1f1a").into(),
|
||||
},
|
||||
message: Message {
|
||||
event_log: Log {
|
||||
address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(),
|
||||
topics: vec![
|
||||
hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(),
|
||||
hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into(),
|
||||
hex!("5f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0").into(),
|
||||
],
|
||||
data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002e00a736aa00000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d00e40b54020000000000000000000000000000000000000000000000000000000000").into(),
|
||||
},
|
||||
proof: Proof {
|
||||
block_hash: hex!("add15f439c8a57fe375d0a679870b1359921d70cb0e3e44f0dd3e272849f4097").into(),
|
||||
tx_index: 0,
|
||||
data: (vec![
|
||||
hex!("42c08b5303fcdf9e49c833fe5f1182cdbc8206bf8aec581125fc34aba11e1f1a").to_vec(),
|
||||
], vec![
|
||||
hex!("f9028e822080b9028802f90284018301ed20b9010000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000080000000000000000000000000000004000000000080000000000000000000000000000000000010100000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000040004000000000000002000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000200000000000010f90179f85894eda338e4dc46038493b885327842fd3e301cab39e1a0f78bb28d4b1d7da699e5c0bc2be29c2b04b5aab6aacf6298fe5304f9db9c6d7ea000000000000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7df9011c94eda338e4dc46038493b885327842fd3e301cab39f863a07153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84fa0c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539a05f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0b8a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002e00a736aa00000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d00e40b54020000000000000000000000000000000000000000000000000000000000").to_vec(),
|
||||
]),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
mod fixtures;
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
use super::*;
|
||||
|
||||
use crate::Pallet as InboundQueue;
|
||||
use frame_benchmarking::v2::*;
|
||||
use frame_support::assert_ok;
|
||||
use frame_system::RawOrigin;
|
||||
|
||||
#[benchmarks]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
use crate::benchmarking::fixtures::make_create_message;
|
||||
|
||||
#[benchmark]
|
||||
fn submit() -> Result<(), BenchmarkError> {
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
|
||||
let create_message = make_create_message();
|
||||
|
||||
T::Helper::initialize_storage(
|
||||
create_message.message.proof.block_hash,
|
||||
create_message.execution_header,
|
||||
);
|
||||
|
||||
let sovereign_account = sibling_sovereign_account::<T>(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::<T>::submit(
|
||||
RawOrigin::Signed(caller.clone()).into(),
|
||||
create_message.message,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(InboundQueue, crate::mock::new_tester(), crate::mock::Test);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
use snowbridge_core::{inbound::Log, ChannelId};
|
||||
|
||||
use sp_core::{RuntimeDebug, H160, H256};
|
||||
use sp_std::{convert::TryFrom, 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);
|
||||
}
|
||||
|
||||
/// An inbound message that has had its outer envelope decoded.
|
||||
#[derive(Clone, RuntimeDebug)]
|
||||
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,
|
||||
/// The inner payload generated from the source application.
|
||||
pub payload: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, RuntimeDebug)]
|
||||
pub struct EnvelopeDecodeError;
|
||||
|
||||
impl TryFrom<&Log> for Envelope {
|
||||
type Error = EnvelopeDecodeError;
|
||||
|
||||
fn try_from(log: &Log) -> Result<Self, Self::Error> {
|
||||
let topics: Vec<B256> = log.topics.iter().map(|x| B256::from_slice(x.as_ref())).collect();
|
||||
|
||||
let event = OutboundMessageAccepted::decode_log(topics, &log.data, true)
|
||||
.map_err(|_| EnvelopeDecodeError)?;
|
||||
|
||||
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()),
|
||||
payload: event.payload,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
//! Inbound Queue
|
||||
//!
|
||||
//! # Overview
|
||||
//!
|
||||
//! Receives messages emitted by the Gateway contract on Ethereum, whereupon they are verified,
|
||||
//! translated to XCM, and finally sent to their final destination parachain.
|
||||
//!
|
||||
//! The message relayers are rewarded using native currency from the sovereign account of the
|
||||
//! destination parachain.
|
||||
//!
|
||||
//! # Extrinsics
|
||||
//!
|
||||
//! ## Governance
|
||||
//!
|
||||
//! * [`Call::set_operating_mode`]: Set the operating mode of the pallet. Can be used to disable
|
||||
//! processing of inbound messages.
|
||||
//!
|
||||
//! ## Message Submission
|
||||
//!
|
||||
//! * [`Call::submit`]: Submit a message for verification and dispatch the final destination
|
||||
//! parachain.
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
mod envelope;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
mod benchmarking;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
use snowbridge_beacon_primitives::CompactExecutionHeader;
|
||||
|
||||
pub mod weights;
|
||||
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
use codec::{Decode, DecodeAll, Encode};
|
||||
use envelope::Envelope;
|
||||
use frame_support::{
|
||||
traits::{
|
||||
fungible::{Inspect, Mutate},
|
||||
tokens::{Fortitude, Precision, Preservation},
|
||||
},
|
||||
weights::WeightToFee,
|
||||
PalletError,
|
||||
};
|
||||
use frame_system::ensure_signed;
|
||||
use scale_info::TypeInfo;
|
||||
use sp_core::{H160, H256};
|
||||
use sp_std::{convert::TryFrom, vec};
|
||||
use xcm::prelude::{
|
||||
send_xcm, Instruction::SetTopic, Junction::*, Junctions::*, MultiLocation,
|
||||
SendError as XcmpSendError, SendXcm, Xcm, XcmHash,
|
||||
};
|
||||
|
||||
use snowbridge_core::{
|
||||
inbound::{Message, VerificationError, Verifier},
|
||||
sibling_sovereign_account, BasicOperatingMode, Channel, ChannelId, ParaId, StaticLookup,
|
||||
};
|
||||
use snowbridge_router_primitives::{
|
||||
inbound,
|
||||
inbound::{ConvertMessage, ConvertMessageError},
|
||||
};
|
||||
use sp_runtime::traits::Saturating;
|
||||
|
||||
pub use weights::WeightInfo;
|
||||
|
||||
type BalanceOf<T> =
|
||||
<<T as pallet::Config>::Token as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
|
||||
|
||||
pub use pallet::*;
|
||||
|
||||
pub const LOG_TARGET: &str = "snowbridge-inbound-queue";
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
|
||||
use frame_support::pallet_prelude::*;
|
||||
use frame_system::pallet_prelude::*;
|
||||
use snowbridge_core::PricingParameters;
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub trait BenchmarkHelper<T> {
|
||||
fn initialize_storage(block_hash: H256, header: CompactExecutionHeader);
|
||||
}
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config {
|
||||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
||||
|
||||
/// The verifier for inbound messages from Ethereum
|
||||
type Verifier: Verifier;
|
||||
|
||||
/// Message relayers are rewarded with this asset
|
||||
type Token: Mutate<Self::AccountId> + Inspect<Self::AccountId>;
|
||||
|
||||
/// XCM message sender
|
||||
type XcmSender: SendXcm;
|
||||
|
||||
// Address of the Gateway contract
|
||||
#[pallet::constant]
|
||||
type GatewayAddress: Get<H160>;
|
||||
|
||||
/// Convert inbound message to XCM
|
||||
type MessageConverter: ConvertMessage<
|
||||
AccountId = Self::AccountId,
|
||||
Balance = BalanceOf<Self>,
|
||||
>;
|
||||
|
||||
/// Lookup a channel descriptor
|
||||
type ChannelLookup: StaticLookup<Source = ChannelId, Target = Channel>;
|
||||
|
||||
/// Lookup pricing parameters
|
||||
type PricingParameters: Get<PricingParameters<BalanceOf<Self>>>;
|
||||
|
||||
type WeightInfo: WeightInfo;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type Helper: BenchmarkHelper<Self>;
|
||||
|
||||
/// Convert a weight value into deductible balance type.
|
||||
type WeightToFee: WeightToFee<Balance = BalanceOf<Self>>;
|
||||
|
||||
/// Convert a length value into deductible balance type
|
||||
type LengthToFee: WeightToFee<Balance = BalanceOf<Self>>;
|
||||
|
||||
/// The upper limit here only used to estimate delivery cost
|
||||
type MaxMessageSize: Get<u32>;
|
||||
}
|
||||
|
||||
#[pallet::hooks]
|
||||
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
|
||||
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T> {
|
||||
/// 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],
|
||||
},
|
||||
/// Set OperatingMode
|
||||
OperatingModeChanged { mode: BasicOperatingMode },
|
||||
}
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T> {
|
||||
/// Message came from an invalid outbound channel on the Ethereum side.
|
||||
InvalidGateway,
|
||||
/// Message has an invalid envelope.
|
||||
InvalidEnvelope,
|
||||
/// Message has an unexpected nonce.
|
||||
InvalidNonce,
|
||||
/// Message has an invalid payload.
|
||||
InvalidPayload,
|
||||
/// Message channel is invalid
|
||||
InvalidChannel,
|
||||
/// The max nonce for the type has been reached
|
||||
MaxNonceReached,
|
||||
/// Cannot convert location
|
||||
InvalidAccountConversion,
|
||||
/// Pallet is halted
|
||||
Halted,
|
||||
/// Message verification error,
|
||||
Verification(VerificationError),
|
||||
/// XCMP send failure
|
||||
Send(SendError),
|
||||
/// Message conversion error
|
||||
ConvertMessage(ConvertMessageError),
|
||||
}
|
||||
|
||||
#[derive(Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo, PalletError)]
|
||||
pub enum SendError {
|
||||
NotApplicable,
|
||||
NotRoutable,
|
||||
Transport,
|
||||
DestinationUnsupported,
|
||||
ExceedsMaxMessageSize,
|
||||
MissingArgument,
|
||||
Fees,
|
||||
}
|
||||
|
||||
impl<T: Config> From<XcmpSendError> for Error<T> {
|
||||
fn from(e: XcmpSendError) -> Self {
|
||||
match e {
|
||||
XcmpSendError::NotApplicable => Error::<T>::Send(SendError::NotApplicable),
|
||||
XcmpSendError::Unroutable => Error::<T>::Send(SendError::NotRoutable),
|
||||
XcmpSendError::Transport(_) => Error::<T>::Send(SendError::Transport),
|
||||
XcmpSendError::DestinationUnsupported =>
|
||||
Error::<T>::Send(SendError::DestinationUnsupported),
|
||||
XcmpSendError::ExceedsMaxMessageSize =>
|
||||
Error::<T>::Send(SendError::ExceedsMaxMessageSize),
|
||||
XcmpSendError::MissingArgument => Error::<T>::Send(SendError::MissingArgument),
|
||||
XcmpSendError::Fees => Error::<T>::Send(SendError::Fees),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The current nonce for each channel
|
||||
#[pallet::storage]
|
||||
pub type Nonce<T: Config> = StorageMap<_, Twox64Concat, ChannelId, u64, ValueQuery>;
|
||||
|
||||
/// The current operating mode of the pallet.
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn operating_mode)]
|
||||
pub type OperatingMode<T: Config> = StorageValue<_, BasicOperatingMode, ValueQuery>;
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Submit an inbound message originating from the Gateway contract on Ethereum
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight(T::WeightInfo::submit())]
|
||||
pub fn submit(origin: OriginFor<T>, message: Message) -> DispatchResult {
|
||||
let who = ensure_signed(origin)?;
|
||||
ensure!(!Self::operating_mode().is_halted(), Error::<T>::Halted);
|
||||
|
||||
// submit message to verifier for verification
|
||||
T::Verifier::verify(&message.event_log, &message.proof)
|
||||
.map_err(|e| Error::<T>::Verification(e))?;
|
||||
|
||||
// Decode event log into an Envelope
|
||||
let envelope =
|
||||
Envelope::try_from(&message.event_log).map_err(|_| Error::<T>::InvalidEnvelope)?;
|
||||
|
||||
// Verify that the message was submitted from the known Gateway contract
|
||||
ensure!(T::GatewayAddress::get() == envelope.gateway, Error::<T>::InvalidGateway);
|
||||
|
||||
// Retrieve the registered channel for this message
|
||||
let channel =
|
||||
T::ChannelLookup::lookup(envelope.channel_id).ok_or(Error::<T>::InvalidChannel)?;
|
||||
|
||||
// Verify message nonce
|
||||
<Nonce<T>>::try_mutate(envelope.channel_id, |nonce| -> DispatchResult {
|
||||
if *nonce == u64::MAX {
|
||||
return Err(Error::<T>::MaxNonceReached.into())
|
||||
}
|
||||
if envelope.nonce != nonce.saturating_add(1) {
|
||||
Err(Error::<T>::InvalidNonce.into())
|
||||
} else {
|
||||
*nonce = nonce.saturating_add(1);
|
||||
Ok(())
|
||||
}
|
||||
})?;
|
||||
|
||||
// Reward relayer from the sovereign account of the destination parachain
|
||||
// Expected to fail if sovereign account has no funds
|
||||
let sovereign_account = sibling_sovereign_account::<T>(channel.para_id);
|
||||
let delivery_cost = Self::calculate_delivery_cost(message.encode().len() as u32);
|
||||
T::Token::transfer(&sovereign_account, &who, delivery_cost, Preservation::Preserve)?;
|
||||
|
||||
// Decode message into XCM
|
||||
let (xcm, fee) =
|
||||
match inbound::VersionedMessage::decode_all(&mut envelope.payload.as_ref()) {
|
||||
Ok(message) => Self::do_convert(envelope.message_id, message)?,
|
||||
Err(_) => return Err(Error::<T>::InvalidPayload.into()),
|
||||
};
|
||||
|
||||
// We embed fees for xcm execution inside the xcm program using teleports
|
||||
// so we must burn the amount of the fee embedded into the XCM script.
|
||||
T::Token::burn_from(&sovereign_account, fee, Precision::Exact, Fortitude::Polite)?;
|
||||
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"💫 xcm {:?} sent with fee {:?}",
|
||||
xcm,
|
||||
fee
|
||||
);
|
||||
|
||||
// Attempt to send XCM to a dest parachain
|
||||
let message_id = Self::send_xcm(xcm, channel.para_id)?;
|
||||
|
||||
Self::deposit_event(Event::MessageReceived {
|
||||
channel_id: envelope.channel_id,
|
||||
nonce: envelope.nonce,
|
||||
message_id,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Halt or resume all pallet operations. May only be called by root.
|
||||
#[pallet::call_index(1)]
|
||||
#[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
|
||||
pub fn set_operating_mode(
|
||||
origin: OriginFor<T>,
|
||||
mode: BasicOperatingMode,
|
||||
) -> DispatchResult {
|
||||
ensure_root(origin)?;
|
||||
OperatingMode::<T>::set(mode);
|
||||
Self::deposit_event(Event::OperatingModeChanged { mode });
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
pub fn do_convert(
|
||||
message_id: H256,
|
||||
message: inbound::VersionedMessage,
|
||||
) -> Result<(Xcm<()>, BalanceOf<T>), Error<T>> {
|
||||
let (mut xcm, fee) =
|
||||
T::MessageConverter::convert(message).map_err(|e| Error::<T>::ConvertMessage(e))?;
|
||||
// Append the message id as an XCM topic
|
||||
xcm.inner_mut().extend(vec![SetTopic(message_id.into())]);
|
||||
Ok((xcm, fee))
|
||||
}
|
||||
|
||||
pub fn send_xcm(xcm: Xcm<()>, dest: ParaId) -> Result<XcmHash, Error<T>> {
|
||||
let dest = MultiLocation { parents: 1, interior: X1(Parachain(dest.into())) };
|
||||
let (xcm_hash, _) = send_xcm::<T::XcmSender>(dest, xcm).map_err(Error::<T>::from)?;
|
||||
Ok(xcm_hash)
|
||||
}
|
||||
|
||||
pub fn calculate_delivery_cost(length: u32) -> BalanceOf<T> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/// API for accessing the delivery cost of a message
|
||||
impl<T: Config> Get<BalanceOf<T>> for Pallet<T> {
|
||||
fn get() -> BalanceOf<T> {
|
||||
// Cost here based on MaxMessagePayloadSize(the worst case)
|
||||
Self::calculate_delivery_cost(T::MaxMessageSize::get())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
use super::*;
|
||||
|
||||
use frame_support::{
|
||||
parameter_types,
|
||||
traits::{ConstU128, ConstU32, Everything},
|
||||
weights::IdentityFee,
|
||||
};
|
||||
use hex_literal::hex;
|
||||
use snowbridge_beacon_primitives::{Fork, ForkVersions};
|
||||
use snowbridge_core::{
|
||||
gwei,
|
||||
inbound::{Log, Proof, VerificationError},
|
||||
meth, Channel, ChannelId, PricingParameters, Rewards, StaticLookup,
|
||||
};
|
||||
use snowbridge_router_primitives::inbound::MessageToXcm;
|
||||
use sp_core::{H160, H256};
|
||||
use sp_runtime::{
|
||||
traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Verify},
|
||||
BuildStorage, FixedU128, MultiSignature,
|
||||
};
|
||||
use sp_std::convert::From;
|
||||
use xcm::v3::{prelude::*, MultiAssets, SendXcm};
|
||||
|
||||
use crate::{self as inbound_queue};
|
||||
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
|
||||
frame_support::construct_runtime!(
|
||||
pub enum Test
|
||||
{
|
||||
System: frame_system::{Pallet, Call, Storage, Event<T>},
|
||||
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
|
||||
EthereumBeaconClient: snowbridge_ethereum_beacon_client::{Pallet, Call, Storage, Event<T>},
|
||||
InboundQueue: inbound_queue::{Pallet, Call, Storage, Event<T>},
|
||||
}
|
||||
);
|
||||
|
||||
pub type Signature = MultiSignature;
|
||||
pub type AccountId = <<Signature as Verify>::Signer as IdentifyAccount>::AccountId;
|
||||
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
}
|
||||
|
||||
type Balance = u128;
|
||||
|
||||
impl frame_system::Config for Test {
|
||||
type BaseCallFilter = Everything;
|
||||
type BlockWeights = ();
|
||||
type BlockLength = ();
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type RuntimeTask = RuntimeTask;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = AccountId;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type DbWeight = ();
|
||||
type Version = ();
|
||||
type PalletInfo = PalletInfo;
|
||||
type AccountData = pallet_balances::AccountData<u128>;
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type SystemWeightInfo = ();
|
||||
type SS58Prefix = ();
|
||||
type OnSetCode = ();
|
||||
type MaxConsumers = frame_support::traits::ConstU32<16>;
|
||||
type Nonce = u64;
|
||||
type Block = Block;
|
||||
}
|
||||
|
||||
impl pallet_balances::Config for Test {
|
||||
type MaxLocks = ();
|
||||
type MaxReserves = ();
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type Balance = Balance;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type DustRemoval = ();
|
||||
type ExistentialDeposit = ConstU128<1>;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type RuntimeHoldReason = ();
|
||||
type RuntimeFreezeReason = ();
|
||||
type MaxHolds = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const ExecutionHeadersPruneThreshold: u32 = 10;
|
||||
pub const ChainForkVersions: ForkVersions = ForkVersions{
|
||||
genesis: Fork {
|
||||
version: [0, 0, 0, 1], // 0x00000001
|
||||
epoch: 0,
|
||||
},
|
||||
altair: Fork {
|
||||
version: [1, 0, 0, 1], // 0x01000001
|
||||
epoch: 0,
|
||||
},
|
||||
bellatrix: Fork {
|
||||
version: [2, 0, 0, 1], // 0x02000001
|
||||
epoch: 0,
|
||||
},
|
||||
capella: Fork {
|
||||
version: [3, 0, 0, 1], // 0x03000001
|
||||
epoch: 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
impl snowbridge_ethereum_beacon_client::Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type ForkVersions = ChainForkVersions;
|
||||
type MaxExecutionHeadersToKeep = ExecutionHeadersPruneThreshold;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
// Mock verifier
|
||||
pub struct MockVerifier;
|
||||
|
||||
impl Verifier for MockVerifier {
|
||||
fn verify(_: &Log, _: &Proof) -> Result<(), VerificationError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
impl<T: snowbridge_ethereum_beacon_client::Config> BenchmarkHelper<T> for Test {
|
||||
// not implemented since the MockVerifier is used for tests
|
||||
fn initialize_storage(_: H256, _: CompactExecutionHeader) {}
|
||||
}
|
||||
|
||||
// Mock XCM sender that always succeeds
|
||||
pub struct MockXcmSender;
|
||||
|
||||
impl SendXcm for MockXcmSender {
|
||||
type Ticket = Xcm<()>;
|
||||
|
||||
fn validate(
|
||||
dest: &mut Option<MultiLocation>,
|
||||
xcm: &mut Option<xcm::v3::Xcm<()>>,
|
||||
) -> SendResult<Self::Ticket> {
|
||||
match dest {
|
||||
Some(MultiLocation { interior, .. }) => {
|
||||
if let X1(Parachain(1001)) = interior {
|
||||
return Err(XcmpSendError::NotApplicable)
|
||||
}
|
||||
Ok((xcm.clone().unwrap(), MultiAssets::default()))
|
||||
},
|
||||
_ => Ok((xcm.clone().unwrap(), MultiAssets::default())),
|
||||
}
|
||||
}
|
||||
|
||||
fn deliver(xcm: Self::Ticket) -> core::result::Result<XcmHash, XcmpSendError> {
|
||||
let hash = xcm.using_encoded(sp_io::hashing::blake2_256);
|
||||
Ok(hash)
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const OwnParaId: ParaId = ParaId::new(1013);
|
||||
pub Parameters: PricingParameters<u128> = PricingParameters {
|
||||
exchange_rate: FixedU128::from_rational(1, 400),
|
||||
fee_per_gas: gwei(20),
|
||||
rewards: Rewards { local: DOT, remote: meth(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<Self::Target> {
|
||||
if channel_id !=
|
||||
hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into()
|
||||
{
|
||||
return None
|
||||
}
|
||||
Some(Channel { agent_id: H256::zero(), para_id: ASSET_HUB_PARAID.into() })
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
>;
|
||||
type PricingParameters = Parameters;
|
||||
type ChannelLookup = MockChannelLookup;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type Helper = Test;
|
||||
type WeightToFee = IdentityFee<u128>;
|
||||
type LengthToFee = IdentityFee<u128>;
|
||||
type MaxMessageSize = ConstU32<1024>;
|
||||
}
|
||||
|
||||
pub fn last_events(n: usize) -> Vec<RuntimeEvent> {
|
||||
frame_system::Pallet::<Test>::events()
|
||||
.into_iter()
|
||||
.rev()
|
||||
.take(n)
|
||||
.rev()
|
||||
.map(|e| e.event)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn expect_events(e: Vec<RuntimeEvent>) {
|
||||
assert_eq!(last_events(e.len()), e);
|
||||
}
|
||||
|
||||
pub fn setup() {
|
||||
System::set_block_number(1);
|
||||
Balances::mint_into(
|
||||
&sibling_sovereign_account::<Test>(ASSET_HUB_PARAID.into()),
|
||||
InitialFund::get(),
|
||||
)
|
||||
.unwrap();
|
||||
Balances::mint_into(
|
||||
&sibling_sovereign_account::<Test>(TEMPLATE_PARAID.into()),
|
||||
InitialFund::get(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn new_tester() -> sp_io::TestExternalities {
|
||||
let storage = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
|
||||
let mut ext: sp_io::TestExternalities = storage.into();
|
||||
ext.execute_with(setup);
|
||||
ext
|
||||
}
|
||||
|
||||
// Generated from smoketests:
|
||||
// cd smoketests
|
||||
// ./make-bindings
|
||||
// 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(),
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
||||
pub const ASSET_HUB_PARAID: u32 = 1000u32;
|
||||
pub const TEMPLATE_PARAID: u32 = 1001u32;
|
||||
@@ -0,0 +1,211 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
use super::*;
|
||||
|
||||
use frame_support::{assert_noop, assert_ok};
|
||||
use hex_literal::hex;
|
||||
use snowbridge_core::{inbound::Proof, ChannelId};
|
||||
use sp_keyring::AccountKeyring as Keyring;
|
||||
use sp_runtime::{DispatchError, TokenError};
|
||||
use sp_std::convert::From;
|
||||
|
||||
use crate::{Error, Event as InboundQueueEvent};
|
||||
|
||||
use crate::mock::*;
|
||||
|
||||
#[test]
|
||||
fn test_submit_happy_path() {
|
||||
new_tester().execute_with(|| {
|
||||
let relayer: AccountId = Keyring::Bob.into();
|
||||
let channel_sovereign = sibling_sovereign_account::<Test>(ASSET_HUB_PARAID.into());
|
||||
|
||||
let origin = RuntimeOrigin::signed(relayer.clone());
|
||||
|
||||
// Submit message
|
||||
let message = Message {
|
||||
event_log: mock_event_log(),
|
||||
proof: Proof {
|
||||
block_hash: Default::default(),
|
||||
tx_index: Default::default(),
|
||||
data: Default::default(),
|
||||
},
|
||||
};
|
||||
|
||||
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: [
|
||||
27, 217, 88, 127, 46, 143, 199, 70, 236, 66, 212, 244, 85, 221, 153, 104, 175, 37,
|
||||
224, 20, 140, 95, 140, 7, 27, 74, 182, 199, 77, 12, 194, 236,
|
||||
],
|
||||
}
|
||||
.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"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_submit_xcm_invalid_channel() {
|
||||
new_tester().execute_with(|| {
|
||||
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::<Test>(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(),
|
||||
proof: Proof {
|
||||
block_hash: Default::default(),
|
||||
tx_index: Default::default(),
|
||||
data: Default::default(),
|
||||
},
|
||||
};
|
||||
assert_noop!(
|
||||
InboundQueue::submit(origin.clone(), message.clone()),
|
||||
Error::<Test>::InvalidChannel,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_submit_with_invalid_gateway() {
|
||||
new_tester().execute_with(|| {
|
||||
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::<Test>(ASSET_HUB_PARAID.into());
|
||||
let _ = Balances::mint_into(&sovereign_account, 10000);
|
||||
|
||||
// Submit message
|
||||
let message = Message {
|
||||
event_log: mock_event_log_invalid_gateway(),
|
||||
proof: Proof {
|
||||
block_hash: Default::default(),
|
||||
tx_index: Default::default(),
|
||||
data: Default::default(),
|
||||
},
|
||||
};
|
||||
assert_noop!(
|
||||
InboundQueue::submit(origin.clone(), message.clone()),
|
||||
Error::<Test>::InvalidGateway
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_submit_with_invalid_nonce() {
|
||||
new_tester().execute_with(|| {
|
||||
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::<Test>(ASSET_HUB_PARAID.into());
|
||||
let _ = Balances::mint_into(&sovereign_account, 10000);
|
||||
|
||||
// Submit message
|
||||
let message = Message {
|
||||
event_log: mock_event_log(),
|
||||
proof: Proof {
|
||||
block_hash: Default::default(),
|
||||
tx_index: Default::default(),
|
||||
data: Default::default(),
|
||||
},
|
||||
};
|
||||
assert_ok!(InboundQueue::submit(origin.clone(), message.clone()));
|
||||
|
||||
let nonce: u64 = <Nonce<Test>>::get(ChannelId::from(hex!(
|
||||
"c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539"
|
||||
)));
|
||||
assert_eq!(nonce, 1);
|
||||
|
||||
// Submit the same again
|
||||
assert_noop!(
|
||||
InboundQueue::submit(origin.clone(), message.clone()),
|
||||
Error::<Test>::InvalidNonce
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_submit_no_funds_to_reward_relayers() {
|
||||
new_tester().execute_with(|| {
|
||||
let relayer: AccountId = Keyring::Bob.into();
|
||||
let origin = RuntimeOrigin::signed(relayer);
|
||||
|
||||
// Reset balance of sovereign_account to zero so to trigger the FundsUnavailable error
|
||||
let sovereign_account = sibling_sovereign_account::<Test>(ASSET_HUB_PARAID.into());
|
||||
Balances::set_balance(&sovereign_account, 0);
|
||||
|
||||
// Submit message
|
||||
let message = Message {
|
||||
event_log: mock_event_log(),
|
||||
proof: Proof {
|
||||
block_hash: Default::default(),
|
||||
tx_index: Default::default(),
|
||||
data: Default::default(),
|
||||
},
|
||||
};
|
||||
assert_noop!(
|
||||
InboundQueue::submit(origin.clone(), message.clone()),
|
||||
TokenError::FundsUnavailable
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_operating_mode() {
|
||||
new_tester().execute_with(|| {
|
||||
let relayer: AccountId = Keyring::Bob.into();
|
||||
let origin = RuntimeOrigin::signed(relayer);
|
||||
let message = Message {
|
||||
event_log: mock_event_log(),
|
||||
proof: Proof {
|
||||
block_hash: Default::default(),
|
||||
tx_index: Default::default(),
|
||||
data: Default::default(),
|
||||
},
|
||||
};
|
||||
|
||||
assert_ok!(InboundQueue::set_operating_mode(
|
||||
RuntimeOrigin::root(),
|
||||
snowbridge_core::BasicOperatingMode::Halted
|
||||
));
|
||||
|
||||
assert_noop!(InboundQueue::submit(origin, message), Error::<Test>::Halted);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_operating_mode_root_only() {
|
||||
new_tester().execute_with(|| {
|
||||
assert_noop!(
|
||||
InboundQueue::set_operating_mode(
|
||||
RuntimeOrigin::signed(Keyring::Bob.into()),
|
||||
snowbridge_core::BasicOperatingMode::Halted
|
||||
),
|
||||
DispatchError::BadOrigin
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
//! Autogenerated weights for `snowbridge_inbound_queue`
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
|
||||
//! DATE: 2023-07-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! WORST CASE MAP SIZE: `1000000`
|
||||
//! HOSTNAME: `macbook pro 14 m2`, CPU: `m2-arm64`
|
||||
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("bridge-hub-rococo-dev"), DB CACHE: 1024
|
||||
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
|
||||
use sp_std::marker::PhantomData;
|
||||
|
||||
/// Weight functions needed for ethereum_beacon_client.
|
||||
pub trait WeightInfo {
|
||||
fn submit() -> Weight;
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests
|
||||
impl WeightInfo for () {
|
||||
fn submit() -> Weight {
|
||||
Weight::from_parts(70_000_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 3601))
|
||||
.saturating_add(RocksDbWeight::get().reads(2))
|
||||
.saturating_add(RocksDbWeight::get().writes(2))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user