feat: initialize Kurdistan SDK - independent fork of Polkadot SDK

This commit is contained in:
2025-12-13 15:44:15 +03:00
commit 286de54384
6841 changed files with 1848356 additions and 0 deletions
@@ -0,0 +1,32 @@
// 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::*;
#[benchmark]
fn submit() -> Result<(), BenchmarkError> {
let caller: T::AccountId = whitelisted_caller();
let create_message = T::Helper::initialize_storage();
#[block]
{
assert_ok!(InboundQueue::<T>::submit(
RawOrigin::Signed(caller.clone()).into(),
Box::new(create_message.event),
));
}
Ok(())
}
impl_benchmark_test_suite!(InboundQueue, crate::mock::new_tester(), crate::mock::Test);
}
@@ -0,0 +1,310 @@
// 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 AssetHub for further processing.
//!
//! Message relayers are rewarded in wrapped Ether that is included within the message. This
//! wrapped Ether is derived from Ether that the message origin has locked up on Ethereum.
//!
//! # 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 to the final destination
//! teyrchain.
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod weights;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod test;
pub use crate::weights::WeightInfo;
use bp_relayers::RewardLedger;
use frame_system::ensure_signed;
use snowbridge_core::{
reward::{AddTip, AddTipError},
sparse_bitmap::{SparseBitmap, SparseBitmapImpl},
BasicOperatingMode,
};
use snowbridge_inbound_queue_primitives::{
v2::{ConvertMessage, ConvertMessageError, Message},
EventProof, VerificationError, Verifier,
};
use sp_core::H160;
use sp_runtime::traits::TryConvert;
use sp_std::prelude::*;
use xcm::prelude::{ExecuteXcm, Junction::*, Location, SendXcm, *};
pub use pallet::*;
pub const LOG_TARGET: &str = "snowbridge-pallet-inbound-queue-v2";
pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
pub type Nonce<T> = SparseBitmapImpl<crate::NonceBitmap<T>>;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
#[cfg(feature = "runtime-benchmarks")]
use snowbridge_inbound_queue_primitives::EventFixture;
#[pallet::pallet]
pub struct Pallet<T>(_);
#[cfg(feature = "runtime-benchmarks")]
pub trait BenchmarkHelper<T> {
fn initialize_storage() -> EventFixture;
}
#[pallet::config]
pub trait Config: frame_system::Config {
#[allow(deprecated)]
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// The verifier for inbound messages from Ethereum.
type Verifier: Verifier;
/// XCM message sender.
type XcmSender: SendXcm;
/// Handler for XCM fees.
type XcmExecutor: ExecuteXcm<Self::RuntimeCall>;
/// Address of the Gateway contract.
#[pallet::constant]
type GatewayAddress: Get<H160>;
/// AssetHub teyrchain ID.
type AssetHubParaId: Get<u32>;
/// Convert a command from Ethereum to an XCM message.
type MessageConverter: ConvertMessage;
#[cfg(feature = "runtime-benchmarks")]
type Helper: BenchmarkHelper<Self>;
/// Reward discriminator type.
type RewardKind: Parameter + MaxEncodedLen + Send + Sync + Copy + Clone;
/// The default RewardKind discriminator for rewards allocated to relayers from this pallet.
#[pallet::constant]
type DefaultRewardKind: Get<Self::RewardKind>;
/// Relayer reward payment.
type RewardPayment: RewardLedger<Self::AccountId, Self::RewardKind, u128>;
/// AccountId to Location converter
type AccountToLocation: for<'a> TryConvert<&'a Self::AccountId, Location>;
type WeightInfo: WeightInfo;
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// A message was received from Ethereum
MessageReceived {
/// The message nonce
nonce: u64,
/// ID of the XCM message which was forwarded to the final destination teyrchain
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,
/// Account could not be converted to bytes
InvalidAccount,
/// Message has an invalid envelope.
InvalidMessage,
/// Message has an unexpected nonce.
InvalidNonce,
/// Fee provided is invalid.
InvalidFee,
/// 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,
/// Invalid network specified
InvalidNetwork,
/// Pallet is halted
Halted,
/// The operation required fees to be paid which the initiator could not meet.
FeesNotMet,
/// The desired destination was unreachable, generally because there is a no way of routing
/// to it.
Unreachable,
/// There was some other issue (i.e. not to do with routing) in sending the message.
/// Perhaps a lack of space for buffering the message.
SendFailure,
/// Invalid foreign ERC-20 token ID
InvalidAsset,
/// Cannot reachor a foreign ERC-20 asset location.
CannotReanchor,
/// Message verification error
Verification(VerificationError),
}
impl<T: Config> From<SendError> for Error<T> {
fn from(e: SendError) -> Self {
match e {
SendError::Fees => Error::<T>::FeesNotMet,
SendError::NotApplicable => Error::<T>::Unreachable,
_ => Error::<T>::SendFailure,
}
}
}
impl<T: Config> From<ConvertMessageError> for Error<T> {
fn from(e: ConvertMessageError) -> Self {
match e {
ConvertMessageError::InvalidAsset => Error::<T>::InvalidAsset,
ConvertMessageError::CannotReanchor => Error::<T>::CannotReanchor,
ConvertMessageError::InvalidNetwork => Error::<T>::InvalidNetwork,
}
}
}
/// StorageMap used for encoding a SparseBitmapImpl that tracks whether a specific nonce has
/// been processed or not. Message nonces are unique and never repeated.
#[pallet::storage]
pub type NonceBitmap<T: Config> = StorageMap<_, Twox64Concat, u64, u128, ValueQuery>;
/// The current operating mode of the pallet.
#[pallet::storage]
pub type OperatingMode<T: Config> = StorageValue<_, BasicOperatingMode, ValueQuery>;
/// Keep track of tips added for a message as an additional relayer incentivization. The
/// key for the storage map is the nonce of the message to which the tip should be added.
/// The value is the tip amount, in Ether.
#[pallet::storage]
pub type Tips<T: Config> = StorageMap<_, Blake2_128Concat, u64, u128, OptionQuery>;
#[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>, event: Box<EventProof>) -> DispatchResult {
let who = ensure_signed(origin)?;
ensure!(!OperatingMode::<T>::get().is_halted(), Error::<T>::Halted);
// submit message for verification
T::Verifier::verify(&event.event_log, &event.proof)
.map_err(|e| Error::<T>::Verification(e))?;
// Decode event log into a bridge message
let message =
Message::try_from(&event.event_log).map_err(|_| Error::<T>::InvalidMessage)?;
Self::process_message(who, message)
}
/// 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 process_message(relayer: T::AccountId, message: Message) -> DispatchResult {
// Verify that the message was submitted from the known Gateway contract
ensure!(T::GatewayAddress::get() == message.gateway, Error::<T>::InvalidGateway);
let (nonce, relayer_fee) = (message.nonce, message.relayer_fee);
// Verify the message has not been processed
ensure!(!Nonce::<T>::get(nonce), Error::<T>::InvalidNonce);
let xcm =
T::MessageConverter::convert(message).map_err(|error| Error::<T>::from(error))?;
// Forward XCM to AH
let dest = Location::new(1, [Teyrchain(T::AssetHubParaId::get())]);
// Mark message as received
Nonce::<T>::set(nonce);
let message_id =
Self::send_xcm(dest.clone(), &relayer, xcm.clone()).map_err(|error| {
tracing::error!(target: LOG_TARGET, ?error, ?dest, ?xcm, "XCM send failed with error");
Error::<T>::from(error)
})?;
// Pay relayer reward
let tip = Tips::<T>::take(nonce).unwrap_or_default();
let total_tip = relayer_fee.saturating_add(tip);
if total_tip > 0 {
T::RewardPayment::register_reward(&relayer, T::DefaultRewardKind::get(), total_tip);
}
Self::deposit_event(Event::MessageReceived { nonce, message_id });
Ok(())
}
fn send_xcm(
dest: Location,
fee_payer: &T::AccountId,
xcm: Xcm<()>,
) -> Result<XcmHash, SendError> {
let (ticket, fee) = validate_send::<T::XcmSender>(dest, xcm)?;
let fee_payer = T::AccountToLocation::try_convert(fee_payer).map_err(|err| {
tracing::error!(
target: LOG_TARGET,
?err,
"Failed to convert account to XCM location",
);
SendError::NotApplicable
})?;
T::XcmExecutor::charge_fees(fee_payer.clone(), fee.clone()).map_err(|error| {
tracing::error!(
target: LOG_TARGET,
?error,
"Charging fees failed with error",
);
SendError::Fees
})?;
T::XcmSender::deliver(ticket)
}
}
impl<T: Config> AddTip for Pallet<T> {
fn add_tip(nonce: u64, amount: u128) -> Result<(), AddTipError> {
ensure!(amount > 0, AddTipError::AmountZero);
// If the nonce is already processed, return an error
ensure!(!Nonce::<T>::get(nonce.into()), AddTipError::NonceConsumed);
// Otherwise add the tip.
Tips::<T>::mutate(nonce, |tip| {
*tip = Some(tip.unwrap_or_default().saturating_add(amount));
});
return Ok(());
}
}
}
@@ -0,0 +1,239 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
use super::*;
use crate::{self as inbound_queue_v2};
use frame_support::{derive_impl, parameter_types, traits::ConstU32};
use hex_literal::hex;
use snowbridge_beacon_primitives::{
types::deneb, BeaconHeader, ExecutionProof, VersionedExecutionPayloadHeader,
};
use snowbridge_core::{ParaId, TokenId};
use snowbridge_inbound_queue_primitives::{
v2::{CreateAssetCallInfo, MessageToXcm},
Log, Proof, VerificationError,
};
use sp_core::H160;
use sp_runtime::{
traits::{IdentityLookup, MaybeConvert},
BuildStorage,
};
use sp_std::{convert::From, default::Default, marker::PhantomData};
use xcm::{opaque::latest::ZAGROS_GENESIS_HASH, prelude::*};
type Block = frame_system::mocking::MockBlock<Test>;
use snowbridge_test_utils::mock_rewards::{BridgeReward, MockRewardLedger};
pub use snowbridge_test_utils::mock_xcm::{MockXcmExecutor, MockXcmSender};
#[cfg(feature = "runtime-benchmarks")]
use snowbridge_inbound_queue_primitives::EventFixture;
#[cfg(feature = "runtime-benchmarks")]
use snowbridge_pallet_inbound_queue_v2_fixtures::register_token::make_register_token_message;
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>},
InboundQueue: inbound_queue_v2::{Pallet, Call, Storage, Event<T>},
}
);
pub(crate) const ERROR_ADDRESS: [u8; 20] = hex!("0000000000000000000000000000000000000911");
pub type AccountId = sp_runtime::AccountId32;
type Balance = u128;
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Test {
type AccountId = AccountId;
type Lookup = IdentityLookup<Self::AccountId>;
type AccountData = pallet_balances::AccountData<u128>;
type Block = Block;
}
parameter_types! {
pub const ExistentialDeposit: u128 = 1;
}
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
impl pallet_balances::Config for Test {
type Balance = Balance;
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
}
// Mock verifier
pub struct MockVerifier;
impl Verifier for MockVerifier {
fn verify(log: &Log, _: &Proof) -> Result<(), VerificationError> {
if log.address == ERROR_ADDRESS.into() {
return Err(VerificationError::InvalidProof);
}
Ok(())
}
}
const GATEWAY_ADDRESS: [u8; 20] = hex!["b1185ede04202fe62d38f5db72f71e38ff3e8305"];
#[cfg(feature = "runtime-benchmarks")]
impl<T: Config> BenchmarkHelper<T> for Test {
// not implemented since the MockVerifier is used for tests
fn initialize_storage() -> EventFixture {
make_register_token_message()
}
}
pub struct MockTokenIdConvert;
impl MaybeConvert<TokenId, Location> for MockTokenIdConvert {
fn maybe_convert(_id: TokenId) -> Option<Location> {
Some(Location::parent())
}
}
pub struct MockAccountLocationConverter<AccountId>(PhantomData<AccountId>);
impl<'a, AccountId: Clone + Clone> TryConvert<&'a AccountId, Location>
for MockAccountLocationConverter<AccountId>
{
fn try_convert(_who: &AccountId) -> Result<Location, &AccountId> {
Ok(Location::here())
}
}
parameter_types! {
pub const EthereumNetwork: NetworkId = Ethereum { chain_id: 11155111 };
pub const GatewayAddress: H160 = H160(GATEWAY_ADDRESS);
pub InboundQueueLocation: InteriorLocation = [PalletInstance(84)].into();
pub SnowbridgeReward: BridgeReward = BridgeReward::Snowbridge;
pub const CreateAssetCallIndex: [u8;2] = [53, 0];
pub const SetReservesCallIndex: [u8;2] = [53, 33];
pub const CreateAssetDeposit: u128 = 10_000_000_000u128;
pub const LocalNetwork: NetworkId = ByGenesis(ZAGROS_GENESIS_HASH);
pub CreateAssetCall: CreateAssetCallInfo = CreateAssetCallInfo {
create_call: CreateAssetCallIndex::get(),
deposit: CreateAssetDeposit::get(),
min_balance: 1,
set_reserves_call: SetReservesCallIndex::get(),
};
pub AssetHubParaId: ParaId = ParaId::from(1000);
}
impl inbound_queue_v2::Config for Test {
type RuntimeEvent = RuntimeEvent;
type Verifier = MockVerifier;
type XcmSender = MockXcmSender;
type XcmExecutor = MockXcmExecutor;
type GatewayAddress = GatewayAddress;
type AssetHubParaId = ConstU32<1000>;
type MessageConverter = MessageToXcm<
CreateAssetCall,
EthereumNetwork,
LocalNetwork,
GatewayAddress,
InboundQueueLocation,
AssetHubParaId,
MockTokenIdConvert,
AccountId,
>;
#[cfg(feature = "runtime-benchmarks")]
type Helper = Test;
type WeightInfo = ();
type AccountToLocation = MockAccountLocationConverter<AccountId>;
type RewardKind = BridgeReward;
type DefaultRewardKind = SnowbridgeReward;
type RewardPayment = MockRewardLedger;
}
pub fn setup() {
System::set_block_number(1);
}
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!("b1185ede04202fe62d38f5db72f71e38ff3e8305").into(),
topics: vec![
hex!("550e2067494b1736ea5573f2d19cdc0ac95b410fff161bf16f11c6229655ec9c").into(),
],
// Nonce + Payload
data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000b1185ede04202fe62d38f5db72f71e38ff3e830500000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000009184e72a0000000000000000000000000000000000000000000000000000000015d3ef798000000000000000000000000000000000000000000000000000000015d3ef798000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000b8ea8cb425d85536b158d661da1ef0895bb92f1d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").into(),
}
}
pub fn mock_event_log_invalid_gateway() -> Log {
Log {
// gateway address
address: H160::zero(),
topics: vec![
hex!("550e2067494b1736ea5573f2d19cdc0ac95b410fff161bf16f11c6229655ec9c").into(),
],
// Nonce + Payload
data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000b1185ede04202fe62d38f5db72f71e38ff3e830500000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000009184e72a0000000000000000000000000000000000000000000000000000000015d3ef798000000000000000000000000000000000000000000000000000000015d3ef798000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000b8ea8cb425d85536b158d661da1ef0895bb92f1d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").into(),
}
}
pub fn mock_event_log_invalid_message() -> Log {
Log {
// gateway address
address: hex!("b8ea8cb425d85536b158d661da1ef0895bb92f1d").into(),
topics: vec![
hex!("b61699d45635baed7500944331ea827538a50dbfef79180f2079e9185da627aa").into(),
],
// Nonce + Payload
data: hex!("000000000000000000000000000000000000000000000000000000b8ea8cb425d85536b158d661da1ef0895bb92f1d000000000000000000000000000000000000000000000000001dcd6500000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000059682f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002cdeadbeef774667629726ec1fabebcec0d9139bd1c8f72a23deadbeef0000000000000000000000001dcd650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").into(),
}
}
pub fn mock_execution_proof() -> ExecutionProof {
ExecutionProof {
header: BeaconHeader::default(),
ancestry_proof: None,
execution_header: VersionedExecutionPayloadHeader::Deneb(deneb::ExecutionPayloadHeader {
parent_hash: Default::default(),
fee_recipient: Default::default(),
state_root: Default::default(),
receipts_root: Default::default(),
logs_bloom: vec![],
prev_randao: Default::default(),
block_number: 0,
gas_limit: 0,
gas_used: 0,
timestamp: 0,
extra_data: vec![],
base_fee_per_gas: Default::default(),
block_hash: Default::default(),
transactions_root: Default::default(),
withdrawals_root: Default::default(),
blob_gas_used: 0,
excess_blob_gas: 0,
}),
execution_branch: vec![],
}
}
// Generated from smoketests:
// cd smoketests
// ./make-bindings
// cargo test --test register_token_v2 -- --nocapture
pub fn mock_event_log_v2() -> Log {
Log {
// gateway address
address: hex!("b1185ede04202fe62d38f5db72f71e38ff3e8305").into(),
topics: vec![
hex!("550e2067494b1736ea5573f2d19cdc0ac95b410fff161bf16f11c6229655ec9c").into(),
],
// Nonce + Payload
data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000b1185ede04202fe62d38f5db72f71e38ff3e830500000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000009184e72a0000000000000000000000000000000000000000000000000000000015d3ef798000000000000000000000000000000000000000000000000000000015d3ef798000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000b8ea8cb425d85536b158d661da1ef0895bb92f1d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").into(),
}
}
@@ -0,0 +1,529 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
use super::*;
use crate::{mock::*, Error};
use codec::Encode;
use frame_support::{assert_noop, assert_ok};
use snowbridge_inbound_queue_primitives::{v2::XcmPayload, EventProof, Proof};
use snowbridge_test_utils::{
mock_rewards::{RegisteredRewardAmount, RegisteredRewardsCount},
mock_xcm::{set_charge_fees_override, set_sender_override},
};
use sp_keyring::sr25519::Keyring;
use sp_runtime::DispatchError;
#[test]
fn test_submit_happy_path() {
new_tester().execute_with(|| {
let relayer: AccountId = Keyring::Bob.into();
let origin = RuntimeOrigin::signed(relayer.clone());
// Submit message
let event = EventProof {
event_log: mock_event_log(),
proof: Proof {
receipt_proof: Default::default(),
execution_proof: mock_execution_proof(),
},
};
assert_ok!(InboundQueue::submit(origin.clone(), Box::new(event.clone())));
let events = frame_system::Pallet::<Test>::events();
assert!(
events.iter().any(|event| matches!(
event.event,
RuntimeEvent::InboundQueue(Event::MessageReceived { nonce, ..})
if nonce == 1
)),
"no message received event emitted."
);
assert_eq!(RegisteredRewardsCount::get(), 1, "Relayer reward should have been registered");
});
}
#[test]
fn test_submit_with_invalid_gateway() {
new_tester().execute_with(|| {
let relayer: AccountId = Keyring::Bob.into();
let origin = RuntimeOrigin::signed(relayer);
// Submit message
let event = EventProof {
event_log: mock_event_log_invalid_gateway(),
proof: Proof {
receipt_proof: Default::default(),
execution_proof: mock_execution_proof(),
},
};
assert_noop!(
InboundQueue::submit(origin.clone(), Box::new(event.clone())),
Error::<Test>::InvalidGateway
);
});
}
#[test]
fn test_submit_verification_fails_with_invalid_proof() {
new_tester().execute_with(|| {
let relayer: AccountId = Keyring::Bob.into();
let origin = RuntimeOrigin::signed(relayer.clone());
// Submit message
let mut event = EventProof {
event_log: mock_event_log(),
proof: Proof {
receipt_proof: Default::default(),
execution_proof: mock_execution_proof(),
},
};
// The mock verifier will error once it matches this address.
event.event_log.address = ERROR_ADDRESS.into();
assert_noop!(
InboundQueue::submit(origin.clone(), Box::new(event.clone())),
Error::<Test>::Verification(VerificationError::InvalidProof)
);
});
}
#[test]
fn test_submit_fails_with_malformed_message() {
new_tester().execute_with(|| {
let relayer: AccountId = Keyring::Bob.into();
let origin = RuntimeOrigin::signed(relayer.clone());
// Submit message
let event = EventProof {
event_log: mock_event_log_invalid_message(),
proof: Proof {
receipt_proof: Default::default(),
execution_proof: mock_execution_proof(),
},
};
assert_noop!(
InboundQueue::submit(origin.clone(), Box::new(event.clone())),
Error::<Test>::InvalidMessage
);
});
}
#[test]
fn test_using_same_nonce_fails() {
new_tester().execute_with(|| {
let relayer: AccountId = Keyring::Bob.into();
let origin = RuntimeOrigin::signed(relayer.clone());
// Submit message
let event = EventProof {
event_log: mock_event_log(),
proof: Proof {
receipt_proof: Default::default(),
execution_proof: mock_execution_proof(),
},
};
assert_ok!(InboundQueue::submit(origin.clone(), Box::new(event.clone())));
let events = frame_system::Pallet::<Test>::events();
assert!(
events.iter().any(|event| matches!(
event.event,
RuntimeEvent::InboundQueue(Event::MessageReceived { nonce, ..})
if nonce == 1
)),
"no event emitted."
);
assert_noop!(
InboundQueue::submit(origin.clone(), Box::new(event.clone())),
Error::<Test>::InvalidNonce
);
});
}
#[test]
fn test_set_operating_mode() {
new_tester().execute_with(|| {
let relayer: AccountId = Keyring::Bob.into();
let origin = RuntimeOrigin::signed(relayer);
let event = EventProof {
event_log: mock_event_log(),
proof: Proof {
receipt_proof: Default::default(),
execution_proof: mock_execution_proof(),
},
};
assert_ok!(InboundQueue::set_operating_mode(
RuntimeOrigin::root(),
snowbridge_core::BasicOperatingMode::Halted
));
assert_noop!(InboundQueue::submit(origin, Box::new(event)), 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
);
});
}
#[test]
fn test_xcm_send_failure() {
crate::test::new_tester().execute_with(|| {
set_sender_override(
|dest: &mut Option<Location>, xcm: &mut Option<Xcm<()>>| {
if let Some(location) = dest {
match location.unpack() {
(_, [Teyrchain(1001)]) => return Err(SendError::NotApplicable),
_ => Ok((xcm.clone().unwrap(), Assets::default())),
}
} else {
Ok((xcm.clone().unwrap(), Assets::default()))
}
},
|_| Err(SendError::DestinationUnsupported),
);
let relayer: AccountId = Keyring::Bob.into();
let origin = mock::RuntimeOrigin::signed(relayer.clone());
// Submit message
let event = EventProof {
event_log: mock_event_log(),
proof: Proof {
receipt_proof: Default::default(),
execution_proof: mock_execution_proof(),
},
};
assert_noop!(
crate::test::InboundQueue::submit(origin.clone(), Box::new(event.clone())),
Error::<Test>::SendFailure
);
});
}
#[test]
fn test_xcm_send_validate_failure() {
crate::test::new_tester().execute_with(|| {
set_sender_override(
|_, _| return Err(SendError::NotApplicable),
|xcm| {
let hash = xcm.using_encoded(sp_io::hashing::blake2_256);
Ok(hash)
},
);
let relayer: AccountId = Keyring::Bob.into();
let origin = mock::RuntimeOrigin::signed(relayer.clone());
// Submit message
let event = EventProof {
event_log: mock_event_log(),
proof: Proof {
receipt_proof: Default::default(),
execution_proof: mock_execution_proof(),
},
};
assert_noop!(
crate::test::InboundQueue::submit(origin.clone(), Box::new(event.clone())),
Error::<Test>::Unreachable
);
});
}
#[test]
fn test_xcm_charge_fees_failure() {
crate::test::new_tester().execute_with(|| {
set_charge_fees_override(|_, _| Err(XcmError::FeesNotMet));
let relayer: AccountId = Keyring::Bob.into();
let origin = mock::RuntimeOrigin::signed(relayer.clone());
// Submit message
let event = EventProof {
event_log: mock_event_log(),
proof: Proof {
receipt_proof: Default::default(),
execution_proof: mock_execution_proof(),
},
};
assert_noop!(
crate::test::InboundQueue::submit(origin.clone(), Box::new(event.clone())),
Error::<Test>::FeesNotMet
);
});
}
#[test]
fn test_register_token() {
new_tester().execute_with(|| {
let relayer: AccountId = Keyring::Bob.into();
let origin = RuntimeOrigin::signed(relayer);
let event = EventProof {
event_log: mock_event_log_v2(),
proof: Proof {
receipt_proof: Default::default(),
execution_proof: mock_execution_proof(),
},
};
assert_ok!(InboundQueue::submit(origin, Box::new(event)));
});
}
#[test]
fn test_switch_operating_mode() {
new_tester().execute_with(|| {
let relayer: AccountId = Keyring::Bob.into();
let origin = RuntimeOrigin::signed(relayer);
let event = EventProof {
event_log: mock_event_log(),
proof: Proof {
receipt_proof: Default::default(),
execution_proof: mock_execution_proof(),
},
};
assert_ok!(InboundQueue::set_operating_mode(
RuntimeOrigin::root(),
snowbridge_core::BasicOperatingMode::Halted
));
assert_noop!(
InboundQueue::submit(origin.clone(), Box::new(event.clone())),
Error::<Test>::Halted
);
assert_ok!(InboundQueue::set_operating_mode(
RuntimeOrigin::root(),
snowbridge_core::BasicOperatingMode::Normal
));
assert_ok!(InboundQueue::submit(origin, Box::new(event)));
});
}
#[test]
fn zero_reward_does_not_register_reward() {
new_tester().execute_with(|| {
let relayer: AccountId = Keyring::Bob.into();
let origin = H160::random();
assert_ok!(InboundQueue::process_message(
relayer,
Message {
nonce: 0,
assets: vec![],
xcm: XcmPayload::Raw(vec![]),
claimer: None,
execution_fee: 1_000_000_000,
relayer_fee: 0,
gateway: GatewayAddress::get(),
origin,
value: 3_000_000_000,
}
));
assert_eq!(
RegisteredRewardsCount::get(),
0,
"Zero relayer reward should not be registered"
);
});
}
#[test]
fn test_add_tip_cumulative() {
new_tester().execute_with(|| {
let nonce: u64 = 10;
let amount1: u128 = 500;
let amount2: u128 = 300;
assert_eq!(Tips::<Test>::get(nonce), None);
assert_ok!(InboundQueue::add_tip(nonce, amount1));
assert_eq!(Tips::<Test>::get(nonce), Some(amount1));
assert_ok!(InboundQueue::add_tip(nonce, amount2));
assert_eq!(Tips::<Test>::get(nonce), Some(amount1 + amount2));
});
}
#[test]
fn test_add_tip_nonce_consumed() {
new_tester().execute_with(|| {
let nonce: u64 = 20;
let amount: u128 = 400;
Nonce::<Test>::set(nonce.into());
assert_noop!(InboundQueue::add_tip(nonce, amount), AddTipError::NonceConsumed);
assert_eq!(Tips::<Test>::get(nonce), None);
});
}
#[test]
fn test_add_tip_amount_zero() {
new_tester().execute_with(|| {
let nonce: u64 = 30;
let amount: u128 = 0;
assert_noop!(InboundQueue::add_tip(nonce, amount), AddTipError::AmountZero);
assert_eq!(Tips::<Test>::get(nonce), None);
});
}
#[test]
fn inbound_tip_is_paid_out_to_relayer() {
new_tester().execute_with(|| {
let nonce: u64 = 77;
let tip: u128 = 12_345;
let relayer_fee: u128 = 2_000;
// Add tip for nonce before message is processed
assert_ok!(InboundQueue::add_tip(nonce, tip));
assert_eq!(Tips::<Test>::get(nonce), Some(tip));
// Process inbound message with relayer_fee
let relayer: AccountId = Keyring::Bob.into();
assert_ok!(InboundQueue::process_message(
relayer,
Message {
nonce,
assets: vec![],
xcm: XcmPayload::Raw(vec![]),
claimer: None,
execution_fee: 1_000_000_000,
relayer_fee,
gateway: mock::GatewayAddress::get(),
origin: H160::random(),
value: 3_000_000_000,
},
));
// Reward should be registered from relayer_fee + tip
assert_eq!(
RegisteredRewardsCount::get(),
1,
"Reward should be registered from relayer_fee + tip"
);
// Check the actual reward amount paid out (should be relayer_fee + tip)
assert_eq!(
RegisteredRewardAmount::get(),
relayer_fee + tip,
"Reward amount should equal relayer_fee + tip"
);
// Tip should be consumed from storage
assert_eq!(Tips::<Test>::get(nonce), None);
});
}
#[test]
fn relayer_fee_paid_out_when_no_tip_exists() {
new_tester().execute_with(|| {
let nonce: u64 = 88;
let relayer_fee: u128 = 5_000;
// Ensure no tip exists for this nonce
assert_eq!(Tips::<Test>::get(nonce), None);
// Process inbound message with relayer_fee but no tip
let relayer: AccountId = Keyring::Bob.into();
assert_ok!(InboundQueue::process_message(
relayer,
Message {
nonce,
assets: vec![],
xcm: XcmPayload::Raw(vec![]),
claimer: None,
execution_fee: 1_000_000_000,
relayer_fee,
gateway: mock::GatewayAddress::get(),
origin: H160::random(),
value: 3_000_000_000,
},
));
// Relayer fee should be paid out even without tip
assert_eq!(
RegisteredRewardsCount::get(),
1,
"Relayer fee should be paid out even when no tip exists"
);
// Check the actual reward amount paid out
assert_eq!(
RegisteredRewardAmount::get(),
relayer_fee,
"Reward amount should equal relayer_fee when no tip exists"
);
// Confirm no tip storage was affected
assert_eq!(Tips::<Test>::get(nonce), None);
});
}
#[test]
fn tip_paid_out_when_no_relayer_fee() {
new_tester().execute_with(|| {
let nonce: u64 = 99;
let tip: u128 = 8_500;
// Add tip for nonce before message is processed
assert_ok!(InboundQueue::add_tip(nonce, tip));
assert_eq!(Tips::<Test>::get(nonce), Some(tip));
// Process inbound message with zero relayer_fee but with tip
let relayer: AccountId = Keyring::Bob.into();
assert_ok!(InboundQueue::process_message(
relayer,
Message {
nonce,
assets: vec![],
xcm: XcmPayload::Raw(vec![]),
claimer: None,
execution_fee: 1_000_000_000,
relayer_fee: 0,
gateway: mock::GatewayAddress::get(),
origin: H160::random(),
value: 3_000_000_000,
},
));
// Tip should be paid out even without relayer fee
assert_eq!(
RegisteredRewardsCount::get(),
1,
"Tip should be paid out even when relayer_fee is 0"
);
// Check the actual reward amount paid out (should be just the tip)
assert_eq!(
RegisteredRewardAmount::get(),
tip,
"Reward amount should equal tip when relayer_fee is 0"
);
// Tip should be consumed from storage
assert_eq!(Tips::<Test>::get(nonce), None);
});
}
@@ -0,0 +1,36 @@
// 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-pezkuwichain-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 {
// Proof Size summary in bytes:
// Measured: `309`
// Estimated: `3774`
// Minimum execution time: 59_000_000 picoseconds.
Weight::from_parts(60_000_000, 0)
.saturating_add(Weight::from_parts(0, 3774))
.saturating_add(RocksDbWeight::get().reads(7))
.saturating_add(RocksDbWeight::get().writes(2))
}
}