fix: Complete snowbridge pezpallet rebrand and critical bug fixes

- snowbridge-pezpallet-* → pezsnowbridge-pezpallet-* (201 refs)
- pallet/ directories → pezpallet/ (4 locations)
- Fixed pezpallet.rs self-include recursion bug
- Fixed sc-chain-spec hardcoded crate name in derive macro
- Reverted .pezpallet_by_name() to .pallet_by_name() (subxt API)
- Added BizinikiwiConfig type alias for zombienet tests
- Deleted obsolete session state files

Verified: pezsnowbridge-pezpallet-*, pezpallet-staking,
pezpallet-staking-async, pezframe-benchmarking-cli all pass cargo check
This commit is contained in:
2025-12-16 09:57:23 +03:00
parent 7fce5a2472
commit 90fd044766
3019 changed files with 41780 additions and 24101 deletions
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
use super::*;
use crate::Pezpallet as InboundQueue;
use pezframe_benchmarking::v2::*;
use pezframe_support::assert_ok;
use pezframe_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();
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 (teyrchain 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.event,
));
}
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 pezsnowbridge_core::ChannelId;
use pezsnowbridge_inbound_queue_primitives::Log;
use pezsp_core::{RuntimeDebug, H160, H256};
use pezsp_std::prelude::*;
use alloy_core::{primitives::B256, sol, sol_types::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_raw_log_validate(topics, &log.data)
.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.into(),
})
}
}
@@ -0,0 +1,384 @@
// 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 teyrchain.
//!
//! The message relayers are rewarded using native currency from the sovereign account of the
//! destination teyrchain.
//!
//! # Extrinsics
//!
//! ## Governance
//!
//! * [`Call::set_operating_mode`]: Set the operating mode of the pezpallet. Can be used to disable
//! processing of inbound messages.
//!
//! ## Message Submission
//!
//! * [`Call::submit`]: Submit a message for verification and dispatch the final destination
//! teyrchain.
#![cfg_attr(not(feature = "std"), no_std)]
mod envelope;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod weights;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod test;
use codec::{Decode, DecodeAll, Encode};
use envelope::Envelope;
use pezframe_support::{
traits::{
fungible::{Inspect, Mutate},
tokens::{Fortitude, Preservation},
},
weights::WeightToFee,
PalletError,
};
use pezframe_system::ensure_signed;
use scale_info::TypeInfo;
use pezsp_core::H160;
use pezsp_runtime::traits::Zero;
use pezsp_std::vec;
use xcm::prelude::{
send_xcm, Junction::*, Location, SendError as XcmpSendError, SendXcm, Xcm, XcmContext, XcmHash,
};
use xcm_executor::traits::TransactAsset;
use pezsnowbridge_core::{
sibling_sovereign_account, BasicOperatingMode, Channel, ChannelId, ParaId, PricingParameters,
StaticLookup,
};
use pezsnowbridge_inbound_queue_primitives::{
v1::{ConvertMessage, ConvertMessageError, VersionedMessage},
EventProof, VerificationError, Verifier,
};
use pezsp_runtime::{traits::Saturating, SaturatedConversion, TokenError};
pub use weights::WeightInfo;
type BalanceOf<T> =
<<T as pezpallet::Config>::Token as Inspect<<T as pezframe_system::Config>::AccountId>>::Balance;
pub use pezpallet::*;
pub const LOG_TARGET: &str = "snowbridge-inbound-queue";
#[pezframe_support::pezpallet]
pub mod pezpallet {
use super::*;
use pezframe_support::pezpallet_prelude::*;
use pezframe_system::pezpallet_prelude::*;
use pezsp_core::H256;
#[cfg(feature = "runtime-benchmarks")]
use pezsnowbridge_inbound_queue_primitives::EventFixture;
#[pezpallet::pezpallet]
pub struct Pezpallet<T>(_);
#[cfg(feature = "runtime-benchmarks")]
pub trait BenchmarkHelper<T> {
fn initialize_storage() -> EventFixture;
}
#[pezpallet::config]
pub trait Config: pezframe_system::Config {
#[allow(deprecated)]
type RuntimeEvent: From<Event<Self>> + IsType<<Self as pezframe_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
#[pezpallet::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>;
/// To withdraw and deposit an asset.
type AssetTransactor: TransactAsset;
}
#[pezpallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pezpallet<T> {}
#[pezpallet::event]
#[pezpallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// 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 teyrchain
message_id: [u8; 32],
/// Fee burned for the teleport
fee_burned: BalanceOf<T>,
},
/// Set OperatingMode
OperatingModeChanged { mode: BasicOperatingMode },
}
#[pezpallet::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,
/// Pezpallet is halted
Halted,
/// Message verification error,
Verification(VerificationError),
/// XCMP send failure
Send(SendError),
/// Message conversion error
ConvertMessage(ConvertMessageError),
}
#[derive(
Clone, Encode, Decode, DecodeWithMemTracking, 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
#[pezpallet::storage]
pub type Nonce<T: Config> = StorageMap<_, Twox64Concat, ChannelId, u64, ValueQuery>;
/// The current operating mode of the pezpallet.
#[pezpallet::storage]
#[pezpallet::getter(fn operating_mode)]
pub type OperatingMode<T: Config> = StorageValue<_, BasicOperatingMode, ValueQuery>;
#[pezpallet::call]
impl<T: Config> Pezpallet<T> {
/// Submit an inbound message originating from the Gateway contract on Ethereum
#[pezpallet::call_index(0)]
#[pezpallet::weight(T::WeightInfo::submit())]
pub fn submit(origin: OriginFor<T>, event: EventProof) -> DispatchResult {
let who = ensure_signed(origin)?;
ensure!(!Self::operating_mode().is_halted(), Error::<T>::Halted);
// submit message to verifier for verification
T::Verifier::verify(&event.event_log, &event.proof)
.map_err(|e| Error::<T>::Verification(e))?;
// Decode event log into an Envelope
let envelope =
Envelope::try_from(&event.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 teyrchain, only if funds
// are available
let sovereign_account = sibling_sovereign_account::<T>(channel.para_id);
let delivery_cost = Self::calculate_delivery_cost(event.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)?;
}
// Decode payload into `VersionedMessage`
let message = VersionedMessage::decode_all(&mut envelope.payload.as_ref())
.map_err(|_| Error::<T>::InvalidPayload)?;
// Decode message into XCM
let (xcm, fee) = Self::do_convert(envelope.message_id, message.clone())?;
tracing::info!(
target: LOG_TARGET,
?xcm,
?fee,
"💫 xcm decoded"
);
// Burning fees for teleport
Self::burn_fees(channel.para_id, fee)?;
// Attempt to send XCM to a dest teyrchain
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,
fee_burned: fee,
});
Ok(())
}
/// Halt or resume all pezpallet operations. May only be called by root.
#[pezpallet::call_index(1)]
#[pezpallet::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> Pezpallet<T> {
pub fn do_convert(
message_id: H256,
message: VersionedMessage,
) -> Result<(Xcm<()>, BalanceOf<T>), Error<T>> {
let (xcm, fee) = T::MessageConverter::convert(message_id, message)
.map_err(|e| Error::<T>::ConvertMessage(e))?;
Ok((xcm, fee))
}
pub fn send_xcm(xcm: Xcm<()>, dest: ParaId) -> Result<XcmHash, Error<T>> {
let dest = Location::new(1, [Teyrchain(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)
}
/// Burn the amount of the fee embedded into the XCM for teleports
pub fn burn_fees(para_id: ParaId, fee: BalanceOf<T>) -> DispatchResult {
let dummy_context =
XcmContext { origin: None, message_id: Default::default(), topic: None };
let dest = Location::new(1, [Teyrchain(para_id.into())]);
let fees = (Location::parent(), fee.saturated_into::<u128>()).into();
T::AssetTransactor::can_check_out(&dest, &fees, &dummy_context).map_err(|error| {
tracing::error!(
target: LOG_TARGET,
?error,
"XCM asset check out failed with error"
);
TokenError::FundsUnavailable
})?;
T::AssetTransactor::check_out(&dest, &fees, &dummy_context);
T::AssetTransactor::withdraw_asset(&fees, &dest, None).map_err(|error| {
tracing::error!(
target: LOG_TARGET,
?error,
"XCM asset withdraw failed with error"
);
TokenError::FundsUnavailable
})?;
Ok(())
}
}
/// API for accessing the delivery cost of a message
impl<T: Config> Get<BalanceOf<T>> for Pezpallet<T> {
fn get() -> BalanceOf<T> {
// Cost here based on MaxMessagePayloadSize(the worst case)
Self::calculate_delivery_cost(T::MaxMessageSize::get())
}
}
}
@@ -0,0 +1,358 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
use super::*;
use pezframe_support::{derive_impl, parameter_types, traits::ConstU32, weights::IdentityFee};
use hex_literal::hex;
use pezsnowbridge_beacon_primitives::{
types::deneb, BeaconHeader, ExecutionProof, Fork, ForkVersions, VersionedExecutionPayloadHeader,
};
use pezsnowbridge_core::{
gwei, meth, Channel, ChannelId, PricingParameters, Rewards, StaticLookup, TokenId,
};
use pezsnowbridge_inbound_queue_primitives::{v1::MessageToXcm, Log, Proof, VerificationError};
use pezsp_core::{H160, H256};
use pezsp_runtime::{
traits::{IdentifyAccount, IdentityLookup, MaybeConvert, Verify},
BuildStorage, FixedU128, MultiSignature,
};
use pezsp_std::{convert::From, default::Default};
use xcm::{
latest::{SendXcm, ZAGROS_GENESIS_HASH},
prelude::*,
};
use xcm_executor::AssetsInHolding;
#[cfg(feature = "runtime-benchmarks")]
use pezsnowbridge_inbound_queue_primitives::EventFixture;
#[cfg(feature = "runtime-benchmarks")]
use pezsnowbridge_pezpallet_inbound_queue_fixtures::register_token::make_register_token_message;
use crate::{self as inbound_queue};
type Block = pezframe_system::mocking::MockBlock<Test>;
pezframe_support::construct_runtime!(
pub enum Test
{
System: pezframe_system::{Pezpallet, Call, Storage, Event<T>},
Balances: pezpallet_balances::{Pezpallet, Call, Storage, Config<T>, Event<T>},
EthereumBeaconClient: pezsnowbridge_pezpallet_ethereum_client::{Pezpallet, Call, Storage, Event<T>},
InboundQueue: inbound_queue::{Pezpallet, Call, Storage, Event<T>},
}
);
pub type Signature = MultiSignature;
pub type AccountId = <<Signature as Verify>::Signer as IdentifyAccount>::AccountId;
type Balance = u128;
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
impl pezframe_system::Config for Test {
type AccountId = AccountId;
type Lookup = IdentityLookup<Self::AccountId>;
type AccountData = pezpallet_balances::AccountData<u128>;
type Block = Block;
}
parameter_types! {
pub const ExistentialDeposit: u128 = 1;
}
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
impl pezpallet_balances::Config for Test {
type Balance = Balance;
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
}
parameter_types! {
pub const ChainForkVersions: ForkVersions = ForkVersions {
genesis: Fork {
version: hex!("00000001"),
epoch: 0,
},
altair: Fork {
version: hex!("01000001"),
epoch: 0,
},
bellatrix: Fork {
version: hex!("02000001"),
epoch: 0,
},
capella: Fork {
version: hex!("03000001"),
epoch: 0,
},
deneb: Fork {
version: hex!("04000001"),
epoch: 0,
},
electra: Fork {
version: hex!("05000000"),
epoch: 80000000000,
},
fulu: Fork {
version: hex!("06000000"),
epoch: 80000000001,
}
};
}
impl pezsnowbridge_pezpallet_ethereum_client::Config for Test {
type RuntimeEvent = RuntimeEvent;
type ForkVersions = ChainForkVersions;
type FreeHeadersInterval = ConstU32<32>;
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;
pub UniversalLocation: InteriorLocation =
[GlobalConsensus(ByGenesis(ZAGROS_GENESIS_HASH)), Teyrchain(1002)].into();
pub AssetHubFromEthereum: Location = Location::new(1,[GlobalConsensus(ByGenesis(ZAGROS_GENESIS_HASH)),Teyrchain(1000)]);
}
#[cfg(feature = "runtime-benchmarks")]
impl<T: pezsnowbridge_pezpallet_ethereum_client::Config> BenchmarkHelper<T> for Test {
fn initialize_storage() -> EventFixture {
make_register_token_message()
}
}
// Mock XCM sender that always succeeds
pub struct MockXcmSender;
impl SendXcm for MockXcmSender {
type Ticket = Xcm<()>;
fn validate(
dest: &mut Option<Location>,
xcm: &mut Option<Xcm<()>>,
) -> SendResult<Self::Ticket> {
if let Some(location) = dest {
match location.unpack() {
(_, [Teyrchain(1001)]) => return Err(XcmpSendError::NotApplicable),
_ => Ok((xcm.clone().unwrap(), Assets::default())),
}
} else {
Ok((xcm.clone().unwrap(), Assets::default()))
}
}
fn deliver(xcm: Self::Ticket) -> core::result::Result<XcmHash, XcmpSendError> {
let hash = xcm.using_encoded(pezsp_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: HEZ, remote: meth(1) },
multiplier: FixedU128::from_rational(1, 1),
};
}
pub const HEZ: 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() })
}
}
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<AssetsInHolding, XcmError> {
Ok(AssetsInHolding::default())
}
fn internal_transfer_asset(
_what: &Asset,
_from: &Location,
_to: &Location,
_context: &XcmContext,
) -> Result<AssetsInHolding, XcmError> {
Ok(AssetsInHolding::default())
}
}
pub struct MockTokenIdConvert;
impl MaybeConvert<TokenId, Location> for MockTokenIdConvert {
fn maybe_convert(_id: TokenId) -> Option<Location> {
Some(Location::parent())
}
}
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<u128>;
type LengthToFee = IdentityFee<u128>;
type MaxMessageSize = ConstU32<1024>;
type AssetTransactor = SuccessfulTransactor;
}
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() -> pezsp_io::TestExternalities {
let storage = pezframe_system::GenesisConfig::<Test>::default().build_storage().unwrap();
let mut ext: pezsp_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 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![],
}
}
pub const ASSET_HUB_PARAID: u32 = 1000u32;
pub const TEMPLATE_PARAID: u32 = 1001u32;
@@ -0,0 +1,245 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
use super::*;
use pezframe_support::{assert_noop, assert_ok};
use hex_literal::hex;
use pezsnowbridge_core::ChannelId;
use pezsnowbridge_inbound_queue_primitives::Proof;
use pezsp_keyring::Sr25519Keyring as Keyring;
use pezsp_runtime::DispatchError;
use pezsp_std::convert::From;
use crate::Error;
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 event proof
let event = EventProof {
event_log: mock_event_log(),
proof: Proof {
receipt_proof: Default::default(),
execution_proof: mock_execution_proof(),
},
};
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(), event.clone()));
let pezpallet_events = pezframe_system::Pezpallet::<Test>::events();
assert!(
pezpallet_events.iter().any(|event| matches!(
event.event,
RuntimeEvent::InboundQueue(Event::MessageReceived { nonce, ..})
if nonce == 1
)),
"no event emit."
);
let delivery_cost = InboundQueue::calculate_delivery_cost(event.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 teyrchain 1001
let sovereign_account = sibling_sovereign_account::<Test>(TEMPLATE_PARAID.into());
println!("account: {}", sovereign_account);
let _ = Balances::mint_into(&sovereign_account, 10000);
// Submit event proof
let event = EventProof {
event_log: mock_event_log_invalid_channel(),
proof: Proof {
receipt_proof: Default::default(),
execution_proof: mock_execution_proof(),
},
};
assert_noop!(
InboundQueue::submit(origin.clone(), event.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 event proof
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(), event.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 event = EventProof {
event_log: mock_event_log(),
proof: Proof {
receipt_proof: Default::default(),
execution_proof: mock_execution_proof(),
},
};
assert_ok!(InboundQueue::submit(origin.clone(), event.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(), event.clone()),
Error::<Test>::InvalidNonce
);
});
}
#[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);
// Reset balance of sovereign_account to zero first
let sovereign_account = sibling_sovereign_account::<Test>(ASSET_HUB_PARAID.into());
Balances::set_balance(&sovereign_account, 0);
// Submit message
let event = EventProof {
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(), event.clone()));
});
}
#[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(),
pezsnowbridge_core::BasicOperatingMode::Halted
));
assert_noop!(InboundQueue::submit(origin, 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()),
pezsnowbridge_core::BasicOperatingMode::Halted
),
DispatchError::BadOrigin
);
});
}
#[test]
fn test_submit_no_funds_to_reward_relayers_and_ed_preserved() {
new_tester().execute_with(|| {
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::<Test>(ASSET_HUB_PARAID.into());
Balances::set_balance(&sovereign_account, ExistentialDeposit::get() + 1);
// Submit message successfully
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(), event.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;
let event = EventProof {
event_log,
proof: Proof {
receipt_proof: Default::default(),
execution_proof: mock_execution_proof(),
},
};
assert_ok!(InboundQueue::submit(origin.clone(), event.clone()));
// Check balance of sovereign account as ED does not change
let amount = Balances::balance(&sovereign_account);
assert_eq!(amount, ExistentialDeposit::get());
});
}
@@ -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 BIZINIKIWI 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 pezframe_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
use pezsp_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))
}
}