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

            
19
use sp_core::{Get, H160, U256};
20
use xcm::latest::prelude::*;
21
use xcm::latest::{Junction, Location};
22
use xcm_executor::traits::{Error as MatchError, MatchesFungibles};
23

            
24
pub(crate) struct Erc20Matcher<Erc20MultilocationPrefix>(
25
	core::marker::PhantomData<Erc20MultilocationPrefix>,
26
);
27

            
28
impl<Erc20MultilocationPrefix: Get<Location>> MatchesFungibles<H160, U256>
29
	for Erc20Matcher<Erc20MultilocationPrefix>
30
{
31
3
	fn matches_fungibles(multiasset: &Asset) -> Result<(H160, U256), MatchError> {
32
3
		let (amount, id) = match (&multiasset.fun, &multiasset.id) {
33
3
			(Fungible(ref amount), AssetId(ref id)) => (amount, id),
34
			_ => return Err(MatchError::AssetNotHandled),
35
		};
36
3
		let contract_address = Self::matches_erc20_multilocation(id)
37
3
			.map_err(|_| MatchError::AssetIdConversionFailed)?;
38
2
		let amount = U256::from(*amount);
39
2

            
40
2
		Ok((contract_address, amount))
41
3
	}
42
}
43

            
44
impl<Erc20MultilocationPrefix: Get<Location>> Erc20Matcher<Erc20MultilocationPrefix> {
45
140
	pub(crate) fn is_erc20_asset(multiasset: &Asset) -> bool {
46
140
		match (&multiasset.fun, &multiasset.id) {
47
140
			(Fungible(_), AssetId(ref id)) => Self::matches_erc20_multilocation(id).is_ok(),
48
			_ => false,
49
		}
50
140
	}
51
143
	fn matches_erc20_multilocation(multilocation: &Location) -> Result<H160, ()> {
52
143
		let prefix = Erc20MultilocationPrefix::get();
53
143
		if prefix.parent_count() != multilocation.parent_count()
54
21
			|| prefix
55
21
				.interior()
56
21
				.iter()
57
21
				.enumerate()
58
21
				.any(|(index, junction)| multilocation.interior().at(index) != Some(junction))
59
		{
60
140
			return Err(());
61
3
		}
62
3
		match multilocation.interior().at(prefix.interior().len()) {
63
			Some(Junction::AccountKey20 {
64
2
				key: contract_address,
65
2
				..
66
2
			}) => Ok(H160(*contract_address)),
67
1
			_ => Err(()),
68
		}
69
143
	}
70
}
71

            
72
#[cfg(test)]
73
mod 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
1
	fn should_match_valid_erc20_location() {
95
1
		let location = Location {
96
1
			parents: 0,
97
1
			interior: [
98
1
				PalletInstance(42u8),
99
1
				AccountKey20 {
100
1
					key: [0; 20],
101
1
					network: None,
102
1
				},
103
1
			]
104
1
			.into(),
105
1
		};
106
1

            
107
1
		assert_ok!(
108
1
			Erc20Matcher::<Erc20MultilocationPrefix>::matches_fungibles(&Asset::from((
109
1
				location, 100u128
110
1
			))),
111
1
			(H160([0; 20]), U256([100, 0, 0, 0]))
112
1
		);
113
1
	}
114

            
115
	#[test]
116
1
	fn should_match_valid_erc20_location_with_amount_greater_than_u64() {
117
1
		let location = Location {
118
1
			parents: 0,
119
1
			interior: [
120
1
				PalletInstance(42u8),
121
1
				AccountKey20 {
122
1
					key: [0; 20],
123
1
					network: None,
124
1
				},
125
1
			]
126
1
			.into(),
127
1
		};
128
1

            
129
1
		assert_ok!(
130
1
			Erc20Matcher::<Erc20MultilocationPrefix>::matches_fungibles(&Asset::from((
131
1
				location,
132
1
				100000000000000000u128
133
1
			))),
134
1
			(H160([0; 20]), U256::from(100000000000000000u128))
135
		);
136
1
	}
137

            
138
	#[test]
139
1
	fn should_not_match_invalid_erc20_location() {
140
1
		let invalid_location = Location {
141
1
			parents: 0,
142
1
			interior: [PalletInstance(42u8), GeneralIndex(0)].into(),
143
1
		};
144
1

            
145
1
		assert!(
146
1
			Erc20Matcher::<Erc20MultilocationPrefix>::matches_fungibles(&Asset::from((
147
1
				invalid_location,
148
1
				100u128
149
1
			)))
150
1
			.is_err()
151
1
		);
152
1
	}
153
}