1
// Copyright 2025 Moonbeam foundation
2
// This file is part of Moonbeam.
3

            
4
// Moonbeam is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8

            
9
// Moonbeam is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13

            
14
// You should have received a copy of the GNU General Public License
15
// along with Moonbeam.  If not, see <http://www.gnu.org/licenses/>.
16

            
17
//! Test primitives for Moonbeam, including mock implementations for testing.
18

            
19
use frame_support::weights::Weight;
20
use sp_runtime::DispatchError;
21
use sp_std::cell::RefCell;
22
use sp_std::collections::btree_map::BTreeMap;
23
use xcm::latest::Location;
24
use xcm_primitives::XcmFeeTrader;
25

            
26
/// Must match `pallet-xcm-weight-trader`'s `RELATIVE_PRICE_DECIMALS`.
27
pub const RELATIVE_PRICE_DECIMALS: u32 = 18;
28

            
29
thread_local! {
30
	/// Thread-local storage for relative prices keyed by asset location.
31
	///
32
	/// Semantics match `pallet-xcm-weight-trader`:
33
	/// - Price is relative to the chain native asset.
34
	/// - It uses 18 decimals of precision.
35
	/// - Larger values mean the asset is *more valuable* and therefore requires *less* of it
36
	///   to pay for the same amount of weight.
37
	static RELATIVE_PRICE: RefCell<BTreeMap<Location, u128>> = RefCell::new(BTreeMap::new());
38
}
39

            
40
/// Memory-based fee trader for tests that stores relative prices in memory.
41
///
42
/// This is intentionally aligned with `pallet-xcm-weight-trader`'s behavior to make tests and
43
/// benchmarks share the same pricing interpretation:
44
/// - `set_asset_price(location, value)` sets a **relative price** (18 decimals), not a fee-per-second.
45
/// - `compute_fee(weight, asset)` converts `weight` into a "native fee amount" and then converts that
46
///   native amount into the target asset using the stored relative price:
47
///
48
/// \[
49
/// \text{asset\_amount} = \left\lceil \frac{\text{native\_amount} \cdot 10^{18}}{\text{relative\_price}} \right\rceil
50
/// \]
51
///
52
/// For simplicity (and determinism), the "native amount" is derived from `weight.ref_time()`.
53
pub struct MemoryFeeTrader;
54

            
55
impl XcmFeeTrader for MemoryFeeTrader {
56
294
	fn compute_fee(weight: Weight, asset_location: &Location) -> Result<u128, DispatchError> {
57
294
		let relative_price = RELATIVE_PRICE
58
294
			.with(|map| map.borrow().get(asset_location).copied())
59
294
			.ok_or(DispatchError::Other("Asset relative price not set"))?;
60

            
61
		// Stand-in for the runtime's native `WeightToFee`: use ref_time directly.
62
266
		let native_amount: u128 = weight.ref_time() as u128;
63

            
64
266
		let scale: u128 = 10u128.pow(RELATIVE_PRICE_DECIMALS);
65
266
		let numerator = native_amount
66
266
			.checked_mul(scale)
67
266
			.ok_or(DispatchError::Other("Overflow computing fee"))?;
68

            
69
		// Round up (match weight-trader behavior).
70
266
		let amount = numerator
71
266
			.checked_add(relative_price.saturating_sub(1))
72
266
			.ok_or(DispatchError::Other("Overflow computing fee"))?
73
266
			.checked_div(relative_price)
74
266
			.ok_or(DispatchError::Other("Division by zero"))?;
75

            
76
266
		Ok(amount)
77
294
	}
78

            
79
	fn get_asset_price(asset_location: &Location) -> Option<u128> {
80
		RELATIVE_PRICE.with(|map| map.borrow().get(asset_location).copied())
81
	}
82

            
83
315
	fn set_asset_price(asset_location: Location, value: u128) -> Result<(), DispatchError> {
84
315
		if value == 0 {
85
			return Err(DispatchError::Other("Relative price cannot be zero"));
86
315
		}
87

            
88
315
		RELATIVE_PRICE.with(|map| map.borrow_mut().insert(asset_location, value));
89
315
		Ok(())
90
315
	}
91

            
92
	fn remove_asset(asset_location: Location) -> Result<(), DispatchError> {
93
		RELATIVE_PRICE.with(|map| map.borrow_mut().remove(&asset_location));
94
		Ok(())
95
	}
96
}