snowbridge_runtime_common/
lib.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
3//! # Runtime Common
4//!
5//! Common traits and types shared by runtimes.
6#![cfg_attr(not(feature = "std"), no_std)]
7
8#[cfg(test)]
9mod tests;
10
11use codec::FullCodec;
12use core::marker::PhantomData;
13use frame_support::traits::Get;
14use snowbridge_core::outbound::SendMessageFeeProvider;
15use sp_arithmetic::traits::{BaseArithmetic, Unsigned};
16use sp_std::fmt::Debug;
17use xcm::prelude::*;
18use xcm_builder::HandleFee;
19use xcm_executor::traits::{FeeReason, TransactAsset};
20
21pub const LOG_TARGET: &str = "xcm::export-fee-to-sibling";
22
23/// A `HandleFee` implementation that takes fees from `ExportMessage` XCM instructions
24/// to Snowbridge and splits off the remote fee and deposits it to the origin
25/// parachain sovereign account. The local fee is then returned back to be handled by
26/// the next fee handler in the chain. Most likely the treasury account.
27pub struct XcmExportFeeToSibling<
28	Balance,
29	AccountId,
30	FeeAssetLocation,
31	EthereumNetwork,
32	AssetTransactor,
33	FeeProvider,
34>(
35	PhantomData<(
36		Balance,
37		AccountId,
38		FeeAssetLocation,
39		EthereumNetwork,
40		AssetTransactor,
41		FeeProvider,
42	)>,
43);
44
45impl<Balance, AccountId, FeeAssetLocation, EthereumNetwork, AssetTransactor, FeeProvider> HandleFee
46	for XcmExportFeeToSibling<
47		Balance,
48		AccountId,
49		FeeAssetLocation,
50		EthereumNetwork,
51		AssetTransactor,
52		FeeProvider,
53	>
54where
55	Balance: BaseArithmetic + Unsigned + Copy + From<u128> + Into<u128> + Debug,
56	AccountId: Clone + FullCodec,
57	FeeAssetLocation: Get<Location>,
58	EthereumNetwork: Get<NetworkId>,
59	AssetTransactor: TransactAsset,
60	FeeProvider: SendMessageFeeProvider<Balance = Balance>,
61{
62	fn handle_fee(fees: Assets, context: Option<&XcmContext>, reason: FeeReason) -> Assets {
63		let token_location = FeeAssetLocation::get();
64
65		// Check the reason to see if this export is for snowbridge.
66		if !matches!(
67			reason,
68			FeeReason::Export { network: bridged_network, ref destination }
69				if bridged_network == EthereumNetwork::get() && destination == &Here
70		) {
71			return fees
72		}
73
74		// Get the parachain sovereign from the `context`.
75		let maybe_para_id: Option<u32> =
76			if let Some(XcmContext { origin: Some(Location { parents: 1, interior }), .. }) =
77				context
78			{
79				if let Some(Parachain(sibling_para_id)) = interior.first() {
80					Some(*sibling_para_id)
81				} else {
82					None
83				}
84			} else {
85				None
86			};
87		if maybe_para_id.is_none() {
88			log::error!(
89				target: LOG_TARGET,
90				"invalid location in context {:?}",
91				context,
92			);
93			return fees
94		}
95		let para_id = maybe_para_id.unwrap();
96
97		// Get the total fee offered by export message.
98		let maybe_total_supplied_fee: Option<(usize, Balance)> = fees
99			.inner()
100			.iter()
101			.enumerate()
102			.filter_map(|(index, asset)| {
103				if let Asset { id: location, fun: Fungible(amount) } = asset {
104					if location.0 == token_location {
105						return Some((index, (*amount).into()))
106					}
107				}
108				None
109			})
110			.next();
111		if maybe_total_supplied_fee.is_none() {
112			log::error!(
113				target: LOG_TARGET,
114				"could not find fee asset item in fees: {:?}",
115				fees,
116			);
117			return fees
118		}
119		let (fee_index, total_fee) = maybe_total_supplied_fee.unwrap();
120		let local_fee = FeeProvider::local_fee();
121		let remote_fee = total_fee.saturating_sub(local_fee);
122		if local_fee == Balance::zero() || remote_fee == Balance::zero() {
123			log::error!(
124				target: LOG_TARGET,
125				"calculated refund incorrect with local_fee: {:?} and remote_fee: {:?}",
126				local_fee,
127				remote_fee,
128			);
129			return fees
130		}
131		// Refund remote component of fee to physical origin
132		let result = AssetTransactor::deposit_asset(
133			&Asset { id: AssetId(token_location.clone()), fun: Fungible(remote_fee.into()) },
134			&Location::new(1, [Parachain(para_id)]),
135			context,
136		);
137		if result.is_err() {
138			log::error!(
139				target: LOG_TARGET,
140				"transact fee asset failed: {:?}",
141				result.unwrap_err()
142			);
143			return fees
144		}
145
146		// Return remaining fee to the next fee handler in the chain.
147		let mut modified_fees = fees.inner().clone();
148		modified_fees.remove(fee_index);
149		modified_fees.push(Asset { id: AssetId(token_location), fun: Fungible(local_fee.into()) });
150		modified_fees.into()
151	}
152}