feat: initialize Kurdistan SDK - independent fork of Polkadot SDK

This commit is contained in:
2025-12-13 15:44:15 +03:00
commit e4778b4576
6838 changed files with 1847450 additions and 0 deletions
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
//! XCM Execution weights for invoking the backend implementation
use frame_support::weights::{constants::RocksDbWeight, Weight};
/// XCM Execution weights for invoking the backend implementation
pub trait BackendWeightInfo {
/// Execution weight for remote xcm that dispatches `EthereumSystemCall::RegisterToken`
/// using `Transact`.
fn transact_register_token() -> Weight;
fn transact_add_tip() -> Weight;
fn do_process_message() -> Weight;
fn commit_single() -> Weight;
fn submit_delivery_receipt() -> Weight;
}
impl BackendWeightInfo for () {
fn transact_register_token() -> Weight {
Weight::from_parts(100_000_000, 10000)
}
fn transact_add_tip() -> Weight {
Weight::from_parts(100_000_000, 10000)
}
fn do_process_message() -> Weight {
Weight::from_parts(39_000_000, 3485)
.saturating_add(RocksDbWeight::get().reads(4_u64))
.saturating_add(RocksDbWeight::get().writes(4_u64))
}
fn commit_single() -> Weight {
Weight::from_parts(9_000_000, 1586)
.saturating_add(RocksDbWeight::get().reads(2_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
fn submit_delivery_receipt() -> 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))
}
}
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
//! Benchmarking setup for pallet-template
use super::*;
#[allow(unused)]
use crate::Pallet as SnowbridgeControlFrontend;
use frame_benchmarking::v2::*;
use frame_system::RawOrigin;
use xcm::prelude::{Location, *};
use xcm_executor::traits::ConvertLocation;
#[benchmarks(where <T as frame_system::Config>::AccountId: Into<Location>)]
mod benchmarks {
use super::*;
#[benchmark]
fn register_token() -> Result<(), BenchmarkError> {
let origin_location = Location::new(1, [Teyrchain(2000)]);
let origin = T::Helper::make_xcm_origin(origin_location.clone());
let asset_location: Location = Location::new(1, [Teyrchain(2000), GeneralIndex(1)]);
let asset_id = Box::new(VersionedLocation::from(asset_location.clone()));
T::Helper::initialize_storage(asset_location, origin_location.clone());
let ether = T::EthereumLocation::get();
let asset_owner = T::AccountIdConverter::convert_location(&origin_location).unwrap();
T::Helper::setup_pools(asset_owner, ether.clone());
let asset_metadata = AssetMetadata {
name: "pal".as_bytes().to_vec().try_into().unwrap(),
symbol: "pal".as_bytes().to_vec().try_into().unwrap(),
decimals: 12,
};
let fee_asset = Asset::from((Location::parent(), 1_000_000u128));
#[extrinsic_call]
_(origin as T::RuntimeOrigin, asset_id, asset_metadata, fee_asset);
Ok(())
}
#[benchmark]
fn add_tip() -> Result<(), BenchmarkError> {
let caller: T::AccountId = whitelisted_caller();
let ether = T::EthereumLocation::get();
T::Helper::setup_pools(caller.clone(), ether.clone());
let message_id = MessageId::Inbound(1);
let dot = Location::new(1, Here);
let asset = Asset::from((dot, 1_000_000_00u128));
#[extrinsic_call]
_(RawOrigin::Signed(caller.clone()), message_id, asset);
Ok(())
}
impl_benchmark_test_suite!(
SnowbridgeControlFrontend,
crate::mock::new_test_ext(),
crate::mock::Test
);
}
@@ -0,0 +1,432 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
//!
//! System frontend pallet that acts as the user-facing control-plane for Snowbridge.
//!
//! Some operations are delegated to a backend pallet installed on a remote teyrchain.
//!
//! # Extrinsics
//!
//! * [`Call::register_token`]: Register Pezkuwi native asset as a wrapped ERC20 token on Ethereum.
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod weights;
pub use weights::*;
pub mod backend_weights;
pub use backend_weights::*;
use frame_support::{pallet_prelude::*, traits::EnsureOriginWithArg};
use frame_system::pallet_prelude::*;
use pallet_asset_conversion::Swap;
use snowbridge_core::{
burn_for_teleport, operating_mode::ExportPausedQuery, reward::MessageId, AssetMetadata,
BasicOperatingMode as OperatingMode,
};
use sp_std::prelude::*;
use xcm::{
latest::{validate_send, XcmHash},
prelude::*,
};
use xcm_executor::traits::{FeeManager, FeeReason, TransactAsset};
#[cfg(feature = "runtime-benchmarks")]
use frame_support::traits::OriginTrait;
pub use pallet::*;
pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
pub const LOG_TARGET: &str = "snowbridge-system-frontend";
/// Call indices within BridgeHub runtime for dispatchables within `snowbridge-pallet-system-v2`
#[allow(clippy::large_enum_variant)]
#[derive(Encode, Decode, Debug, PartialEq, Clone, TypeInfo)]
pub enum BridgeHubRuntime<T: frame_system::Config> {
#[codec(index = 90)]
EthereumSystem(EthereumSystemCall<T>),
}
/// Call indices for dispatchables within `snowbridge-pallet-system-v2`
#[derive(Encode, Decode, Debug, PartialEq, Clone, TypeInfo)]
pub enum EthereumSystemCall<T: frame_system::Config> {
#[codec(index = 2)]
RegisterToken {
sender: Box<VersionedLocation>,
asset_id: Box<VersionedLocation>,
metadata: AssetMetadata,
amount: u128,
},
#[codec(index = 3)]
AddTip { sender: AccountIdOf<T>, message_id: MessageId, amount: u128 },
}
#[cfg(feature = "runtime-benchmarks")]
pub trait BenchmarkHelper<O, AccountId>
where
O: OriginTrait,
{
fn make_xcm_origin(location: Location) -> O;
fn initialize_storage(asset_location: Location, asset_owner: Location);
fn setup_pools(caller: AccountId, asset: Location);
}
#[frame_support::pallet]
pub mod pallet {
use super::*;
use xcm_executor::traits::ConvertLocation;
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config: frame_system::Config {
#[allow(deprecated)]
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// Origin check for XCM locations that can register token
type RegisterTokenOrigin: EnsureOriginWithArg<
Self::RuntimeOrigin,
Location,
Success = Location,
>;
/// XCM message sender
type XcmSender: SendXcm;
/// To withdraw and deposit an asset.
type AssetTransactor: TransactAsset;
/// To charge XCM delivery fees
type XcmExecutor: ExecuteXcm<Self::RuntimeCall> + FeeManager;
/// Fee asset for the execution cost on ethereum
type EthereumLocation: Get<Location>;
/// To swap the provided tip asset for
type Swap: Swap<Self::AccountId, AssetKind = Location, Balance = u128>;
/// Location of bridge hub
type BridgeHubLocation: Get<Location>;
/// Universal location of this runtime.
type UniversalLocation: Get<InteriorLocation>;
/// InteriorLocation of this pallet.
type PalletLocation: Get<InteriorLocation>;
type AccountIdConverter: ConvertLocation<Self::AccountId>;
/// Weights for dispatching XCM to backend implementation of `register_token`
type BackendWeightInfo: BackendWeightInfo;
/// Weights for pallet dispatchables
type WeightInfo: WeightInfo;
/// A set of helper functions for benchmarking.
#[cfg(feature = "runtime-benchmarks")]
type Helper: BenchmarkHelper<Self::RuntimeOrigin, Self::AccountId>;
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// An XCM was sent
MessageSent {
origin: Location,
destination: Location,
message: Xcm<()>,
message_id: XcmHash,
},
/// Set OperatingMode
ExportOperatingModeChanged { mode: OperatingMode },
}
#[pallet::error]
pub enum Error<T> {
/// Convert versioned location failure
UnsupportedLocationVersion,
/// Check location failure, should start from the dispatch origin as owner
InvalidAssetOwner,
/// Send xcm message failure
SendFailure,
/// Withdraw fee asset failure
FeesNotMet,
/// Convert to reanchored location failure
LocationConversionFailed,
/// Message export is halted
Halted,
/// The desired destination was unreachable, generally because there is a no way of routing
/// to it.
Unreachable,
/// The asset provided for the tip is unsupported.
UnsupportedAsset,
/// Unable to withdraw asset.
WithdrawError,
/// Account could not be converted to a location.
InvalidAccount,
/// Provided tip asset could not be swapped for ether.
SwapError,
/// Ether could not be burned.
BurnError,
/// The tip provided is zero.
TipAmountZero,
}
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,
}
}
}
/// The current operating mode for exporting to Ethereum.
#[pallet::storage]
#[pallet::getter(fn export_operating_mode)]
pub type ExportOperatingMode<T: Config> = StorageValue<_, OperatingMode, ValueQuery>;
#[pallet::call]
impl<T: Config> Pallet<T>
where
<T as frame_system::Config>::AccountId: Into<Location>,
{
/// Set the operating mode for exporting messages to Ethereum.
#[pallet::call_index(0)]
#[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
pub fn set_operating_mode(origin: OriginFor<T>, mode: OperatingMode) -> DispatchResult {
ensure_root(origin)?;
ExportOperatingMode::<T>::put(mode);
Self::deposit_event(Event::ExportOperatingModeChanged { mode });
Ok(())
}
/// Initiates the registration for a Pezkuwi-native token as a wrapped ERC20 token on
/// Ethereum.
/// - `asset_id`: Location of the asset
/// - `metadata`: Metadata to include in the instantiated ERC20 contract on Ethereum
///
/// All origins are allowed, however `asset_id` must be a location nested within the origin
/// consensus system.
#[pallet::call_index(1)]
#[pallet::weight(
T::WeightInfo::register_token()
.saturating_add(T::BackendWeightInfo::transact_register_token())
.saturating_add(T::BackendWeightInfo::do_process_message())
.saturating_add(T::BackendWeightInfo::commit_single())
.saturating_add(T::BackendWeightInfo::submit_delivery_receipt())
)]
pub fn register_token(
origin: OriginFor<T>,
asset_id: Box<VersionedLocation>,
metadata: AssetMetadata,
fee_asset: Asset,
) -> DispatchResult {
ensure!(!Self::export_operating_mode().is_halted(), Error::<T>::Halted);
let asset_location: Location =
(*asset_id).try_into().map_err(|_| Error::<T>::UnsupportedLocationVersion)?;
let origin_location = T::RegisterTokenOrigin::ensure_origin(origin, &asset_location)?;
let ether_gained = if origin_location.is_here() {
// Root origin/location does not pay any fees/tip.
0
} else {
Self::swap_fee_asset_and_burn(origin_location.clone(), fee_asset)?
};
let call = Self::build_register_token_call(
origin_location.clone(),
asset_location,
metadata,
ether_gained,
)?;
Self::send_transact_call(origin_location, call)
}
/// Add an additional relayer tip for a committed message identified by `message_id`.
/// The tip asset will be swapped for ether.
#[pallet::call_index(2)]
#[pallet::weight(
T::WeightInfo::add_tip()
.saturating_add(T::BackendWeightInfo::transact_add_tip())
)]
pub fn add_tip(origin: OriginFor<T>, message_id: MessageId, asset: Asset) -> DispatchResult
where
<T as frame_system::Config>::AccountId: Into<Location>,
{
let who = ensure_signed(origin)?;
let ether_gained = Self::swap_fee_asset_and_burn(who.clone().into(), asset)?;
// Send the tip details to BH to be allocated to the reward in the Inbound/Outbound
// pallet
let call = Self::build_add_tip_call(who.clone(), message_id.clone(), ether_gained);
Self::send_transact_call(who.into(), call)
}
}
impl<T: Config> Pallet<T> {
fn send_xcm(origin: Location, dest: Location, xcm: Xcm<()>) -> Result<XcmHash, SendError> {
let is_waived =
<T::XcmExecutor as FeeManager>::is_waived(Some(&origin), FeeReason::ChargeFees);
let (ticket, price) = validate_send::<T::XcmSender>(dest, xcm.clone())?;
if !is_waived {
T::XcmExecutor::charge_fees(origin, price).map_err(|_| SendError::Fees)?;
}
T::XcmSender::deliver(ticket)
}
/// Swaps a specified tip asset to Ether and then burns the resulting ether for
/// teleportation. Returns the amount of Ether gained if successful, or a DispatchError if
/// any step fails.
fn swap_and_burn(
origin: Location,
tip_asset_location: Location,
ether_location: Location,
tip_amount: u128,
) -> Result<u128, DispatchError> {
// Swap tip asset to ether
let swap_path = vec![tip_asset_location.clone(), ether_location.clone()];
let who = T::AccountIdConverter::convert_location(&origin)
.ok_or(Error::<T>::LocationConversionFailed)?;
let ether_gained = T::Swap::swap_exact_tokens_for_tokens(
who.clone(),
swap_path,
tip_amount,
None, // No minimum amount required
who,
true,
)?;
// Burn the ether
let ether_asset = Asset::from((ether_location.clone(), ether_gained));
burn_for_teleport::<T::AssetTransactor>(&origin, &ether_asset)
.map_err(|_| Error::<T>::BurnError)?;
Ok(ether_gained)
}
// Build the call to dispatch the `EthereumSystem::register_token` extrinsic on BH
fn build_register_token_call(
sender: Location,
asset: Location,
metadata: AssetMetadata,
amount: u128,
) -> Result<BridgeHubRuntime<T>, Error<T>> {
// reanchor locations relative to BH
let sender = Self::reanchored(sender)?;
let asset = Self::reanchored(asset)?;
let call = BridgeHubRuntime::EthereumSystem(EthereumSystemCall::RegisterToken {
sender: Box::new(VersionedLocation::from(sender)),
asset_id: Box::new(VersionedLocation::from(asset)),
metadata,
amount,
});
Ok(call)
}
// Build the call to dispatch the `EthereumSystem::add_tip` extrinsic on BH
fn build_add_tip_call(
sender: AccountIdOf<T>,
message_id: MessageId,
amount: u128,
) -> BridgeHubRuntime<T> {
BridgeHubRuntime::EthereumSystem(EthereumSystemCall::AddTip {
sender,
message_id,
amount,
})
}
fn build_remote_xcm(call: &impl Encode) -> Xcm<()> {
Xcm(vec![
DescendOrigin(T::PalletLocation::get()),
UnpaidExecution { weight_limit: Unlimited, check_origin: None },
Transact {
origin_kind: OriginKind::Xcm,
call: call.encode().into(),
fallback_max_weight: None,
},
])
}
/// Reanchors `location` relative to BridgeHub.
fn reanchored(location: Location) -> Result<Location, Error<T>> {
location
.reanchored(&T::BridgeHubLocation::get(), &T::UniversalLocation::get())
.map_err(|_| Error::<T>::LocationConversionFailed)
}
fn swap_fee_asset_and_burn(
origin: Location,
fee_asset: Asset,
) -> Result<u128, DispatchError> {
let ether_location = T::EthereumLocation::get();
let (fee_asset_location, fee_amount) = match fee_asset {
Asset { id: AssetId(ref loc), fun: Fungible(amount) } => (loc, amount),
_ => {
tracing::debug!(target: LOG_TARGET, ?fee_asset, "error matching fee asset");
return Err(Error::<T>::UnsupportedAsset.into());
},
};
if fee_amount == 0 {
return Ok(0);
}
let ether_gained = if *fee_asset_location != ether_location {
Self::swap_and_burn(
origin.clone(),
fee_asset_location.clone(),
ether_location,
fee_amount,
)
.inspect_err(|&e| {
tracing::debug!(target: LOG_TARGET, ?e, "error swapping asset");
})?
} else {
burn_for_teleport::<T::AssetTransactor>(&origin, &fee_asset)
.map_err(|_| Error::<T>::BurnError)?;
fee_amount
};
Ok(ether_gained)
}
fn send_transact_call(
origin_location: Location,
call: BridgeHubRuntime<T>,
) -> DispatchResult {
let dest = T::BridgeHubLocation::get();
let remote_xcm = Self::build_remote_xcm(&call);
let message_id = Self::send_xcm(origin_location, dest.clone(), remote_xcm.clone())
.map_err(|error| Error::<T>::from(error))?;
Self::deposit_event(Event::<T>::MessageSent {
origin: T::PalletLocation::get().into(),
destination: dest,
message: remote_xcm,
message_id,
});
Ok(())
}
}
impl<T: Config> ExportPausedQuery for Pallet<T> {
fn is_paused() -> bool {
Self::export_operating_mode().is_halted()
}
}
}
@@ -0,0 +1,121 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
use crate as snowbridge_system_frontend;
#[cfg(feature = "runtime-benchmarks")]
use crate::BenchmarkHelper;
use frame_support::{
derive_impl, parameter_types,
traits::{AsEnsureOriginWithArg, Everything},
};
use snowbridge_core::ParaId;
use snowbridge_test_utils::mock_swap_executor::SwapExecutor;
pub use snowbridge_test_utils::{mock_origin::pallet_xcm_origin, mock_xcm::*};
use sp_core::H256;
use sp_runtime::{
traits::{AccountIdConversion, BlakeTwo256, IdentityLookup},
AccountId32, BuildStorage,
};
use xcm::prelude::*;
type Block = frame_system::mocking::MockBlock<Test>;
pub type AccountId = AccountId32;
// Configure a mock runtime to test the pallet.
frame_support::construct_runtime!(
pub enum Test
{
System: frame_system,
XcmOrigin: pallet_xcm_origin::{Pallet, Origin},
EthereumSystemFrontend: snowbridge_system_frontend,
}
);
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Test {
type BaseCallFilter = frame_support::traits::Everything;
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 PalletInfo = PalletInfo;
type AccountData = pallet_balances::AccountData<u128>;
type Nonce = u64;
type Block = Block;
}
impl pallet_xcm_origin::Config for Test {
type RuntimeOrigin = RuntimeOrigin;
}
#[cfg(feature = "runtime-benchmarks")]
impl BenchmarkHelper<RuntimeOrigin, AccountId> for () {
fn make_xcm_origin(location: Location) -> RuntimeOrigin {
RuntimeOrigin::from(pallet_xcm_origin::Origin(location))
}
fn initialize_storage(_: Location, _: Location) {}
fn setup_pools(_: AccountId, _: Location) {}
}
parameter_types! {
pub storage Ether: Location = Location::new(
2,
[
GlobalConsensus(Ethereum { chain_id: 11155111 }),
],
);
pub storage DeliveryFee: Asset = (Location::parent(), 80_000_000_000u128).into();
pub BridgeHubLocation: Location = Location::new(1, [Teyrchain(1002)]);
pub UniversalLocation: InteriorLocation =
[GlobalConsensus(Pezkuwi), Teyrchain(1000)].into();
pub PalletLocation: InteriorLocation = [PalletInstance(36)].into();
}
pub struct AccountIdConverter;
impl xcm_executor::traits::ConvertLocation<AccountId> for AccountIdConverter {
fn convert_location(ml: &Location) -> Option<AccountId> {
match ml.unpack() {
(0, [Junction::AccountId32 { id, .. }]) =>
Some(<AccountId as codec::Decode>::decode(&mut &*id.to_vec()).unwrap()),
(1, [Teyrchain(id)]) => Some(ParaId::from(*id).into_account_truncating()),
_ => None,
}
}
}
impl crate::Config for Test {
type RuntimeEvent = RuntimeEvent;
type RegisterTokenOrigin = AsEnsureOriginWithArg<pallet_xcm_origin::EnsureXcm<Everything>>;
type XcmSender = MockXcmSender;
type AssetTransactor = SuccessfulTransactor;
type EthereumLocation = Ether;
type XcmExecutor = MockXcmExecutor;
type BridgeHubLocation = BridgeHubLocation;
type UniversalLocation = UniversalLocation;
type PalletLocation = PalletLocation;
type BackendWeightInfo = ();
type Swap = SwapExecutor;
type WeightInfo = ();
#[cfg(feature = "runtime-benchmarks")]
type Helper = ();
type AccountIdConverter = AccountIdConverter;
}
// Build genesis storage according to the mock runtime.
pub fn new_test_ext() -> sp_io::TestExternalities {
let storage = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
let mut ext: sp_io::TestExternalities = storage.into();
ext.execute_with(|| {
System::set_block_number(1);
});
ext
}
pub fn make_xcm_origin(location: Location) -> RuntimeOrigin {
pallet_xcm_origin::Origin(location).into()
}
@@ -0,0 +1,292 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
use crate::{mock::*, DispatchError::Other, Error};
use frame_support::{assert_err, assert_noop, assert_ok};
use frame_system::RawOrigin;
use snowbridge_core::{reward::MessageId, AssetMetadata, BasicOperatingMode};
use snowbridge_test_utils::mock_swap_executor::TRIGGER_SWAP_ERROR_AMOUNT;
use sp_keyring::sr25519::Keyring;
use xcm::{
latest::{Assets, Error as XcmError, Location},
opaque::latest::{Asset, AssetId, AssetInstance, Fungibility},
prelude::{GeneralIndex, SendError, Teyrchain},
VersionedLocation,
};
#[test]
fn register_token() {
new_test_ext().execute_with(|| {
let origin_location = Location::new(1, [Teyrchain(2000)]);
let origin = make_xcm_origin(origin_location.clone());
let asset_location: Location = Location::new(1, [Teyrchain(2000), GeneralIndex(1)]);
let asset_id = Box::new(VersionedLocation::from(asset_location));
let asset_metadata = AssetMetadata {
name: "pal".as_bytes().to_vec().try_into().unwrap(),
symbol: "pal".as_bytes().to_vec().try_into().unwrap(),
decimals: 12,
};
let ether_location = Ether::get();
let fee_amount = 1000;
let asset = Asset::from((ether_location.clone(), fee_amount));
assert_ok!(EthereumSystemFrontend::register_token(
origin.clone(),
asset_id.clone(),
asset_metadata.clone(),
asset.clone(),
));
});
}
#[test]
fn register_token_fails_delivery_fees_not_met() {
new_test_ext().execute_with(|| {
let origin_location = Location::new(1, [Teyrchain(2000)]);
let origin = make_xcm_origin(origin_location);
let asset_location: Location = Location::new(1, [Teyrchain(2000), GeneralIndex(1)]);
let asset_id = Box::new(VersionedLocation::from(asset_location));
let asset_metadata = AssetMetadata {
name: "pal".as_bytes().to_vec().try_into().unwrap(),
symbol: "pal".as_bytes().to_vec().try_into().unwrap(),
decimals: 12,
};
let ether_location = Ether::get();
let fee_amount = 1000;
let asset = Asset::from((ether_location.clone(), fee_amount));
set_charge_fees_override(|_, _| Err(XcmError::FeesNotMet));
assert_err!(
EthereumSystemFrontend::register_token(origin, asset_id, asset_metadata, asset.clone()),
Error::<Test>::FeesNotMet,
);
});
}
#[test]
fn register_token_fails_unroutable() {
new_test_ext().execute_with(|| {
let origin_location = Location::new(1, [Teyrchain(2000)]);
let origin = make_xcm_origin(origin_location);
let asset_location: Location = Location::new(1, [Teyrchain(2000), GeneralIndex(1)]);
let asset_id = Box::new(VersionedLocation::from(asset_location));
let asset_metadata = AssetMetadata {
name: "pal".as_bytes().to_vec().try_into().unwrap(),
symbol: "pal".as_bytes().to_vec().try_into().unwrap(),
decimals: 12,
};
let ether_location = Ether::get();
let fee_amount = 1000;
let asset = Asset::from((ether_location.clone(), fee_amount));
// Send XCM with overrides for `SendXcm` behavior to return `Unroutable` error on
// validate
set_sender_override(
|_, _| Err(SendError::Unroutable),
|_| Err(SendError::Transport("not allowed to call here")),
);
assert_err!(
EthereumSystemFrontend::register_token(
origin.clone(),
asset_id.clone(),
asset_metadata.clone(),
asset.clone(),
),
Error::<Test>::SendFailure
);
// Send XCM with overrides for `SendXcm` behavior to return `Unroutable` error on
// deliver
set_sender_override(
|_, y| Ok((y.take().unwrap(), Assets::default())),
|_| Err(SendError::Unroutable),
);
assert_err!(
EthereumSystemFrontend::register_token(origin, asset_id, asset_metadata, asset.clone()),
Error::<Test>::SendFailure
);
});
}
#[test]
fn test_switch_operating_mode() {
new_test_ext().execute_with(|| {
assert_ok!(EthereumSystemFrontend::set_operating_mode(
RawOrigin::Root.into(),
BasicOperatingMode::Halted,
));
let origin_location = Location::new(1, [Teyrchain(2000)]);
let origin = make_xcm_origin(origin_location);
let asset_location: Location = Location::new(1, [Teyrchain(2000), GeneralIndex(1)]);
let asset_id = Box::new(VersionedLocation::from(asset_location));
let asset_metadata = AssetMetadata {
name: "pal".as_bytes().to_vec().try_into().unwrap(),
symbol: "pal".as_bytes().to_vec().try_into().unwrap(),
decimals: 12,
};
let ether_location = Ether::get();
let fee_amount = 1000;
let asset = Asset::from((ether_location.clone(), fee_amount));
assert_noop!(
EthereumSystemFrontend::register_token(
origin.clone(),
asset_id.clone(),
asset_metadata.clone(),
asset.clone(),
),
crate::Error::<Test>::Halted
);
assert_ok!(EthereumSystemFrontend::set_operating_mode(
RawOrigin::Root.into(),
BasicOperatingMode::Normal,
));
assert_ok!(EthereumSystemFrontend::register_token(origin, asset_id, asset_metadata, asset));
});
}
#[test]
fn add_tip_ether_asset_succeeds() {
new_test_ext().execute_with(|| {
let who: AccountId = Keyring::Alice.into();
let message_id = MessageId::Inbound(1);
let ether_location = Ether::get();
let tip_amount = 1000;
let asset = Asset::from((ether_location.clone(), tip_amount));
assert_ok!(EthereumSystemFrontend::add_tip(
RuntimeOrigin::signed(who.clone()),
message_id.clone(),
asset.clone()
));
let events = System::events();
let event_record = events.last().expect("Expected at least one event").event.clone();
if !matches!(
event_record,
RuntimeEvent::EthereumSystemFrontend(crate::Event::MessageSent { .. })
) {
panic!("Expected MessageSent event, got: {:?}", event_record);
}
});
}
#[test]
fn add_tip_non_ether_asset_succeeds() {
new_test_ext().execute_with(|| {
let who: AccountId = Keyring::Alice.into();
let message_id = MessageId::Outbound(2);
let non_ether_location = Location::new(1, [Teyrchain(3000)]);
let tip_amount = 2000;
let asset = Asset::from((non_ether_location.clone(), tip_amount));
assert_ok!(EthereumSystemFrontend::add_tip(
RuntimeOrigin::signed(who.clone()),
message_id.clone(),
asset.clone()
));
let events = System::events();
let event_record = events.last().expect("Expected at least one event").event.clone();
if !matches!(
event_record,
RuntimeEvent::EthereumSystemFrontend(crate::Event::MessageSent { .. })
) {
panic!("Expected MessageSent event, got: {:?}", event_record);
}
});
}
#[test]
fn add_tip_unsupported_asset_fails() {
new_test_ext().execute_with(|| {
let who: AccountId = Keyring::Alice.into();
let message_id = MessageId::Inbound(1);
let asset = Asset {
id: AssetId(Location::new(1, [Teyrchain(4000)])),
fun: Fungibility::NonFungible(AssetInstance::Array4([0u8; 4])),
};
assert_noop!(
EthereumSystemFrontend::add_tip(RuntimeOrigin::signed(who), message_id, asset),
Error::<Test>::UnsupportedAsset
);
});
}
#[test]
fn add_tip_send_xcm_failure() {
new_test_ext().execute_with(|| {
set_sender_override(
|_, _| Ok((Default::default(), Default::default())),
|_| Err(SendError::Unroutable),
);
let who: AccountId = Keyring::Alice.into();
let message_id = MessageId::Outbound(4);
let ether_location = Ether::get();
let tip_amount = 3000;
let asset = Asset::from((ether_location.clone(), tip_amount));
assert_noop!(
EthereumSystemFrontend::add_tip(RuntimeOrigin::signed(who), message_id, asset),
Error::<Test>::SendFailure
);
});
}
#[test]
fn add_tip_origin_not_signed_fails() {
new_test_ext().execute_with(|| {
let message_id = MessageId::Inbound(5);
let ether_location = Ether::get();
let tip_amount = 1500;
let asset = Asset::from((ether_location, tip_amount));
assert_noop!(
EthereumSystemFrontend::add_tip(RuntimeOrigin::root(), message_id, asset),
sp_runtime::DispatchError::BadOrigin
);
});
}
#[test]
fn tip_fails_due_to_swap_error() {
new_test_ext().execute_with(|| {
let who: AccountId = Keyring::Alice.into();
let message_id = MessageId::Inbound(6);
let non_ether_location = Location::new(1, [Teyrchain(3000)]);
// Use the special amount 12345 that will trigger a swap error in mock_swap_executor
let tip_amount = TRIGGER_SWAP_ERROR_AMOUNT;
let asset = Asset::from((non_ether_location.clone(), tip_amount));
assert_noop!(
EthereumSystemFrontend::add_tip(RuntimeOrigin::signed(who), message_id, asset),
Other("Swap failed for test")
);
});
}
#[test]
fn register_token_with_non_ether_fee_asset_succeeds() {
new_test_ext().execute_with(|| {
let origin_location = Location::new(1, [Teyrchain(2000)]);
let origin = make_xcm_origin(origin_location.clone());
let asset_location: Location = Location::new(1, [Teyrchain(2000), GeneralIndex(1)]);
let asset_id = Box::new(VersionedLocation::from(asset_location));
let asset_metadata = AssetMetadata {
name: "pal".as_bytes().to_vec().try_into().unwrap(),
symbol: "pal".as_bytes().to_vec().try_into().unwrap(),
decimals: 12,
};
let fee_amount = 1000;
let fee_asset = Asset::from((Location::parent(), fee_amount));
assert_ok!(EthereumSystemFrontend::register_token(
origin.clone(),
asset_id.clone(),
asset_metadata.clone(),
fee_asset.clone(),
));
});
}
@@ -0,0 +1,60 @@
//! Autogenerated weights for `snowbridge_system`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2023-10-09, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `crake.local`, CPU: `<UNKNOWN>`
//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-pezkuwichain-dev")`, DB CACHE: `1024`
// Executed Command:
// target/release/pezkuwi-teyrchain
// benchmark
// pallet
// --chain
// bridge-hub-pezkuwichain-dev
// --pallet=snowbridge_system
// --extrinsic=*
// --execution=wasm
// --wasm-execution=compiled
// --template
// ../teyrchain/templates/module-weight-template.hbs
// --output
// ../teyrchain/pallets/control/src/weights.rs
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(missing_docs)]
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
use core::marker::PhantomData;
/// Weight functions needed for `snowbridge_system`.
pub trait WeightInfo {
fn register_token() -> Weight;
fn add_tip() -> Weight;
}
// For backwards compatibility and tests.
impl WeightInfo for () {
fn register_token() -> Weight {
// Proof Size summary in bytes:
// Measured: `256`
// Estimated: `6044`
// Minimum execution time: 45_000_000 picoseconds.
Weight::from_parts(45_000_000, 6044)
.saturating_add(RocksDbWeight::get().reads(5_u64))
.saturating_add(RocksDbWeight::get().writes(3_u64))
}
fn add_tip() -> Weight {
// Proof Size summary in bytes:
// Measured: `256`
// Estimated: `6044`
// Minimum execution time: 45_000_000 picoseconds.
Weight::from_parts(45_000_000, 6044)
.saturating_add(RocksDbWeight::get().reads(5_u64))
.saturating_add(RocksDbWeight::get().writes(3_u64))
}
}