pallet_erc20_xcm_bridge/
erc20_matcher.rs

1// Copyright 2019-2025 PureStake Inc.
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//! Module that provides types to match erc20 assets.
18
19use sp_core::{Get, H160, U256};
20use xcm::latest::prelude::*;
21use xcm::latest::{Junction, Location};
22use xcm_executor::traits::{Error as MatchError, MatchesFungibles};
23
24pub(crate) struct Erc20Matcher<Erc20MultilocationPrefix>(
25	core::marker::PhantomData<Erc20MultilocationPrefix>,
26);
27
28impl<Erc20MultilocationPrefix: Get<Location>> MatchesFungibles<H160, U256>
29	for Erc20Matcher<Erc20MultilocationPrefix>
30{
31	fn matches_fungibles(multiasset: &Asset) -> Result<(H160, U256), MatchError> {
32		let (amount, id) = match (&multiasset.fun, &multiasset.id) {
33			(Fungible(ref amount), AssetId(ref id)) => (amount, id),
34			_ => return Err(MatchError::AssetNotHandled),
35		};
36		let contract_address = Self::matches_erc20_multilocation(id)
37			.map_err(|_| MatchError::AssetIdConversionFailed)?;
38		let amount = U256::from(*amount);
39
40		Ok((contract_address, amount))
41	}
42}
43
44impl<Erc20MultilocationPrefix: Get<Location>> Erc20Matcher<Erc20MultilocationPrefix> {
45	pub(crate) fn is_erc20_asset(multiasset: &Asset) -> bool {
46		match (&multiasset.fun, &multiasset.id) {
47			(Fungible(_), AssetId(ref id)) => Self::matches_erc20_multilocation(id).is_ok(),
48			_ => false,
49		}
50	}
51	fn matches_erc20_multilocation(multilocation: &Location) -> Result<H160, ()> {
52		let prefix = Erc20MultilocationPrefix::get();
53		if prefix.parent_count() != multilocation.parent_count()
54			|| prefix
55				.interior()
56				.iter()
57				.enumerate()
58				.any(|(index, junction)| multilocation.interior().at(index) != Some(junction))
59		{
60			return Err(());
61		}
62		match multilocation.interior().at(prefix.interior().len()) {
63			Some(Junction::AccountKey20 {
64				key: contract_address,
65				..
66			}) => Ok(H160(*contract_address)),
67			_ => Err(()),
68		}
69	}
70}
71
72#[cfg(test)]
73mod tests {
74	use super::*;
75
76	macro_rules! assert_ok {
77		( $x:expr, $y:expr $(,)? ) => {
78			let is = $x;
79			match is {
80				Ok(ok) => assert_eq!(ok, $y),
81				_ => assert!(false, "Expected Ok(_). Got Err(_)"),
82			}
83		};
84	}
85
86	frame_support::parameter_types! {
87		pub Erc20MultilocationPrefix: Location = Location {
88			parents:0,
89			interior: [PalletInstance(42u8)].into()
90		};
91	}
92
93	#[test]
94	fn should_match_valid_erc20_location() {
95		let location = Location {
96			parents: 0,
97			interior: [
98				PalletInstance(42u8),
99				AccountKey20 {
100					key: [0; 20],
101					network: None,
102				},
103			]
104			.into(),
105		};
106
107		assert_ok!(
108			Erc20Matcher::<Erc20MultilocationPrefix>::matches_fungibles(&Asset::from((
109				location, 100u128
110			))),
111			(H160([0; 20]), U256([100, 0, 0, 0]))
112		);
113	}
114
115	#[test]
116	fn should_match_valid_erc20_location_with_amount_greater_than_u64() {
117		let location = Location {
118			parents: 0,
119			interior: [
120				PalletInstance(42u8),
121				AccountKey20 {
122					key: [0; 20],
123					network: None,
124				},
125			]
126			.into(),
127		};
128
129		assert_ok!(
130			Erc20Matcher::<Erc20MultilocationPrefix>::matches_fungibles(&Asset::from((
131				location,
132				100000000000000000u128
133			))),
134			(H160([0; 20]), U256::from(100000000000000000u128))
135		);
136	}
137
138	#[test]
139	fn should_not_match_invalid_erc20_location() {
140		let invalid_location = Location {
141			parents: 0,
142			interior: [PalletInstance(42u8), GeneralIndex(0)].into(),
143		};
144
145		assert!(
146			Erc20Matcher::<Erc20MultilocationPrefix>::matches_fungibles(&Asset::from((
147				invalid_location,
148				100u128
149			)))
150			.is_err()
151		);
152	}
153}