mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 19:21:13 +00:00
Removes Snowbridge parachain directory (#3186)
Removes the `bridges/snowbridge/parachain` directory and moves everything up to under `snowbridge` directly. We are cleaning up our local dev env after merging our crates into the polkadot-sdk. --------- Co-authored-by: claravanstaden <Cats 4 life!>
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
// 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;
|
||||
use snowbridge_pallet_inbound_queue_fixtures::register_token::make_register_token_message;
|
||||
|
||||
#[benchmarks]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn submit() -> Result<(), BenchmarkError> {
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
|
||||
let create_message = make_register_token_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,372 @@
|
||||
// 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::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::*, Location, SendError as XcmpSendError, SendXcm,
|
||||
Xcm, XcmContext, XcmHash,
|
||||
};
|
||||
use xcm_executor::traits::TransactAsset;
|
||||
|
||||
use snowbridge_core::{
|
||||
inbound::{Message, VerificationError, Verifier},
|
||||
sibling_sovereign_account, BasicOperatingMode, Channel, ChannelId, ParaId, PricingParameters,
|
||||
StaticLookup,
|
||||
};
|
||||
use snowbridge_router_primitives::{
|
||||
inbound,
|
||||
inbound::{ConvertMessage, ConvertMessageError},
|
||||
};
|
||||
use sp_runtime::{traits::Saturating, SaturatedConversion, TokenError};
|
||||
|
||||
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::*;
|
||||
|
||||
#[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>;
|
||||
|
||||
/// To withdraw and deposit an asset.
|
||||
type AssetTransactor: TransactAsset;
|
||||
}
|
||||
|
||||
#[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: 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 parachain
|
||||
message_id: [u8; 32],
|
||||
/// Fee burned for the teleport
|
||||
fee_burned: BalanceOf<T>,
|
||||
},
|
||||
/// 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()),
|
||||
};
|
||||
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"💫 xcm decoded as {:?} with fee {:?}",
|
||||
xcm,
|
||||
fee
|
||||
);
|
||||
|
||||
// Burning fees for teleport
|
||||
Self::burn_fees(channel.para_id, 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,
|
||||
fee_burned: fee,
|
||||
});
|
||||
|
||||
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 = Location::new(1, [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)
|
||||
}
|
||||
|
||||
/// 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, [Parachain(para_id.into())]);
|
||||
let fees = (Location::parent(), fee.saturated_into::<u128>()).into();
|
||||
T::AssetTransactor::can_check_out(&dest, &fees, &dummy_context).map_err(|error| {
|
||||
log::error!(
|
||||
target: LOG_TARGET,
|
||||
"XCM asset check out failed with error {:?}", error
|
||||
);
|
||||
TokenError::FundsUnavailable
|
||||
})?;
|
||||
T::AssetTransactor::check_out(&dest, &fees, &dummy_context);
|
||||
T::AssetTransactor::withdraw_asset(&fees, &dest, None).map_err(|error| {
|
||||
log::error!(
|
||||
target: LOG_TARGET,
|
||||
"XCM asset withdraw failed with error {:?}", error
|
||||
);
|
||||
TokenError::FundsUnavailable
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// API for accessing the delivery cost of a message
|
||||
impl<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,347 @@
|
||||
// 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::{latest::SendXcm, prelude::*};
|
||||
use xcm_executor::AssetsInHolding;
|
||||
|
||||
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_pallet_ethereum_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 = ();
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
deneb: Fork {
|
||||
version: [4, 0, 0, 1], // 0x04000001
|
||||
epoch: 4294967295,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl snowbridge_pallet_ethereum_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_pallet_ethereum_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<Location>,
|
||||
xcm: &mut Option<Xcm<()>>,
|
||||
) -> SendResult<Self::Ticket> {
|
||||
if let Some(location) = dest {
|
||||
match location.unpack() {
|
||||
(_, [Parachain(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(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() })
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
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>;
|
||||
type AssetTransactor = SuccessfulTransactor;
|
||||
}
|
||||
|
||||
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,212 @@
|
||||
// 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: [
|
||||
57, 61, 232, 3, 66, 61, 25, 190, 234, 188, 193, 174, 13, 186, 1, 64, 237, 94, 73,
|
||||
83, 14, 18, 209, 213, 78, 121, 43, 108, 251, 245, 107, 67,
|
||||
],
|
||||
fee_burned: 110000000000,
|
||||
}
|
||||
.into()]);
|
||||
|
||||
let delivery_cost = InboundQueue::calculate_delivery_cost(message.encode().len() as u32);
|
||||
assert!(
|
||||
Parameters::get().rewards.local < delivery_cost,
|
||||
"delivery cost exceeds pure reward"
|
||||
);
|
||||
|
||||
assert_eq!(Balances::balance(&relayer), delivery_cost, "relayer was rewarded");
|
||||
assert!(
|
||||
Balances::balance(&channel_sovereign) <= initial_fund - delivery_cost,
|
||||
"sovereign account paid reward"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[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