Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions bridges/snowbridge/pallets/system-frontend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ frame-benchmarking = { optional = true, workspace = true }
frame-support.workspace = true
frame-system.workspace = true
log = { workspace = true }
pallet-asset-conversion = { workspace = true }
pallet-xcm.workspace = true
scale-info = { features = ["derive"], workspace = true }
snowbridge-core.workspace = true
Expand All @@ -48,6 +49,7 @@ std = [
"frame-support/std",
"frame-system/std",
"log/std",
"pallet-asset-conversion/std",
"pallet-xcm/std",
"scale-info/std",
"snowbridge-core/std",
Expand All @@ -63,6 +65,7 @@ runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-asset-conversion/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"pallet-xcm/runtime-benchmarks",
"snowbridge-core/runtime-benchmarks",
Expand All @@ -74,6 +77,7 @@ runtime-benchmarks = [
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"pallet-asset-conversion/try-runtime",
"pallet-balances/try-runtime",
"pallet-xcm/try-runtime",
"sp-runtime/try-runtime",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,37 @@
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
//! XCM Execution weights for invoking the backend implementation

use frame_support::weights::Weight;
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 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 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))
}
}
13 changes: 10 additions & 3 deletions bridges/snowbridge/pallets/system-frontend/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ use super::*;
use crate::Pallet as SnowbridgeControlFrontend;
use frame_benchmarking::v2::*;
use xcm::prelude::{Location, *};
use xcm_executor::traits::ConvertLocation;

#[benchmarks]
#[benchmarks(where <T as frame_system::Config>::AccountId: Into<Location>)]
mod benchmarks {
use super::*;
#[benchmark]
Expand All @@ -17,16 +18,22 @@ mod benchmarks {

let asset_location: Location = Location::new(1, [Parachain(2000), GeneralIndex(1)]);
let asset_id = Box::new(VersionedLocation::from(asset_location.clone()));
T::Helper::initialize_storage(asset_location, origin_location);
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);
_(origin as T::RuntimeOrigin, asset_id, asset_metadata, fee_asset);

Ok(())
}
Expand Down
147 changes: 129 additions & 18 deletions bridges/snowbridge/pallets/system-frontend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ pub use backend_weights::*;

use frame_support::{pallet_prelude::*, traits::EnsureOriginWithArg};
use frame_system::pallet_prelude::*;
use pallet_asset_conversion::Swap;
use snowbridge_core::{
operating_mode::ExportPausedQuery, AssetMetadata, BasicOperatingMode as OperatingMode,
burn_for_teleport, operating_mode::ExportPausedQuery, AssetMetadata,
BasicOperatingMode as OperatingMode,
};
use sp_std::prelude::*;
use xcm::{
Expand All @@ -40,7 +42,7 @@ use xcm_executor::traits::{FeeManager, FeeReason, TransactAsset};
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`
Expand All @@ -59,21 +61,24 @@ pub enum EthereumSystemCall {
sender: Box<VersionedLocation>,
asset_id: Box<VersionedLocation>,
metadata: AssetMetadata,
amount: u128,
},
}

#[cfg(feature = "runtime-benchmarks")]
pub trait BenchmarkHelper<O>
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>(_);

Expand All @@ -100,6 +105,9 @@ pub mod pallet {
/// 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>;

Expand All @@ -109,6 +117,8 @@ pub mod pallet {
/// 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;

Expand All @@ -117,7 +127,7 @@ pub mod pallet {

/// A set of helper functions for benchmarking.
#[cfg(feature = "runtime-benchmarks")]
type Helper: BenchmarkHelper<Self::RuntimeOrigin>;
type Helper: BenchmarkHelper<Self::RuntimeOrigin, Self::AccountId>;
}

#[pallet::event]
Expand Down Expand Up @@ -151,6 +161,16 @@ pub mod pallet {
/// 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,
}

impl<T: Config> From<SendError> for Error<T> {
Expand All @@ -169,7 +189,10 @@ pub mod pallet {
pub type ExportOperatingMode<T: Config> = StorageValue<_, OperatingMode, ValueQuery>;

#[pallet::call]
impl<T: Config> Pallet<T> {
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))]
Expand All @@ -191,33 +214,37 @@ pub mod pallet {
#[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 dest = T::BridgeHubLocation::get();
let call =
Self::build_register_token_call(origin_location.clone(), asset_location, metadata)?;
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))?;
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)?
};

Self::deposit_event(Event::<T>::MessageSent {
origin: T::PalletLocation::get().into(),
destination: dest,
message: remote_xcm,
message_id,
});
let call = Self::build_register_token_call(
origin_location.clone(),
asset_location,
metadata,
ether_gained,
)?;

Ok(())
Self::send_transact_call(origin_location, call)
}
}

Expand All @@ -232,11 +259,44 @@ pub mod pallet {
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, Error<T>> {
// reanchor locations relative to BH
let sender = Self::reanchored(sender)?;
Expand All @@ -246,6 +306,7 @@ pub mod pallet {
sender: Box::new(VersionedLocation::from(sender)),
asset_id: Box::new(VersionedLocation::from(asset)),
metadata,
amount,
});

Ok(call)
Expand All @@ -269,6 +330,56 @@ pub mod pallet {
.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) -> 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> {
Expand Down
Loading
Loading