referrerpolicy=no-referrer-when-downgrade
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
//! # Runtime Common
//!
//! Common traits and types shared by runtimes.
#![cfg_attr(not(feature = "std"), no_std)]

#[cfg(test)]
mod tests;

use codec::FullCodec;
use core::marker::PhantomData;
use frame_support::traits::Get;
use snowbridge_core::outbound::SendMessageFeeProvider;
use sp_arithmetic::traits::{BaseArithmetic, Unsigned};
use sp_std::fmt::Debug;
use xcm::prelude::*;
use xcm_builder::HandleFee;
use xcm_executor::traits::{FeeReason, TransactAsset};

pub const LOG_TARGET: &str = "xcm::export-fee-to-sibling";

/// A `HandleFee` implementation that takes fees from `ExportMessage` XCM instructions
/// to Snowbridge and splits off the remote fee and deposits it to the origin
/// parachain sovereign account. The local fee is then returned back to be handled by
/// the next fee handler in the chain. Most likely the treasury account.
pub struct XcmExportFeeToSibling<
	Balance,
	AccountId,
	FeeAssetLocation,
	EthereumNetwork,
	AssetTransactor,
	FeeProvider,
>(
	PhantomData<(
		Balance,
		AccountId,
		FeeAssetLocation,
		EthereumNetwork,
		AssetTransactor,
		FeeProvider,
	)>,
);

impl<Balance, AccountId, FeeAssetLocation, EthereumNetwork, AssetTransactor, FeeProvider> HandleFee
	for XcmExportFeeToSibling<
		Balance,
		AccountId,
		FeeAssetLocation,
		EthereumNetwork,
		AssetTransactor,
		FeeProvider,
	>
where
	Balance: BaseArithmetic + Unsigned + Copy + From<u128> + Into<u128> + Debug,
	AccountId: Clone + FullCodec,
	FeeAssetLocation: Get<Location>,
	EthereumNetwork: Get<NetworkId>,
	AssetTransactor: TransactAsset,
	FeeProvider: SendMessageFeeProvider<Balance = Balance>,
{
	fn handle_fee(fees: Assets, context: Option<&XcmContext>, reason: FeeReason) -> Assets {
		let token_location = FeeAssetLocation::get();

		// Check the reason to see if this export is for snowbridge.
		if !matches!(
			reason,
			FeeReason::Export { network: bridged_network, ref destination }
				if bridged_network == EthereumNetwork::get() && destination == &Here
		) {
			return fees
		}

		// Get the parachain sovereign from the `context`.
		let maybe_para_id: Option<u32> =
			if let Some(XcmContext { origin: Some(Location { parents: 1, interior }), .. }) =
				context
			{
				if let Some(Parachain(sibling_para_id)) = interior.first() {
					Some(*sibling_para_id)
				} else {
					None
				}
			} else {
				None
			};
		if maybe_para_id.is_none() {
			log::error!(
				target: LOG_TARGET,
				"invalid location in context {:?}",
				context,
			);
			return fees
		}
		let para_id = maybe_para_id.unwrap();

		// Get the total fee offered by export message.
		let maybe_total_supplied_fee: Option<(usize, Balance)> = fees
			.inner()
			.iter()
			.enumerate()
			.filter_map(|(index, asset)| {
				if let Asset { id: location, fun: Fungible(amount) } = asset {
					if location.0 == token_location {
						return Some((index, (*amount).into()))
					}
				}
				None
			})
			.next();
		if maybe_total_supplied_fee.is_none() {
			log::error!(
				target: LOG_TARGET,
				"could not find fee asset item in fees: {:?}",
				fees,
			);
			return fees
		}
		let (fee_index, total_fee) = maybe_total_supplied_fee.unwrap();
		let local_fee = FeeProvider::local_fee();
		let remote_fee = total_fee.saturating_sub(local_fee);
		if local_fee == Balance::zero() || remote_fee == Balance::zero() {
			log::error!(
				target: LOG_TARGET,
				"calculated refund incorrect with local_fee: {:?} and remote_fee: {:?}",
				local_fee,
				remote_fee,
			);
			return fees
		}
		// Refund remote component of fee to physical origin
		let result = AssetTransactor::deposit_asset(
			&Asset { id: AssetId(token_location.clone()), fun: Fungible(remote_fee.into()) },
			&Location::new(1, [Parachain(para_id)]),
			context,
		);
		if result.is_err() {
			log::error!(
				target: LOG_TARGET,
				"transact fee asset failed: {:?}",
				result.unwrap_err()
			);
			return fees
		}

		// Return remaining fee to the next fee handler in the chain.
		let mut modified_fees = fees.inner().clone();
		modified_fees.remove(fee_index);
		modified_fees.push(Asset { id: AssetId(token_location), fun: Fungible(local_fee.into()) });
		modified_fees.into()
	}
}