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 extend xcm holding.
18

            
19
use core::marker::PhantomData;
20
use sp_core::{H160, U256};
21
use sp_std::collections::btree_map::BTreeMap;
22
use sp_std::vec::Vec;
23
use xcm_executor::traits::XcmAssetTransfers;
24

            
25
environmental::environmental!(XCM_HOLDING_ERC20_ORIGINS: XcmHoldingErc20sOrigins);
26

            
27
#[cfg_attr(test, derive(PartialEq, Debug))]
28
pub(crate) enum DrainError {
29
	AssetNotFound,
30
	NotEnoughFounds,
31
	SplitError,
32
}
33

            
34
/// Xcm holding erc20 origins extension.
35
/// This extension track down the origin of alls erc20 tokens in the xcm holding.
36
#[derive(Default)]
37
pub(crate) struct XcmHoldingErc20sOrigins {
38
	map: BTreeMap<H160, Vec<(H160, U256)>>,
39
}
40
impl XcmHoldingErc20sOrigins {
41
	/// Take and remove a given amounts of erc20 tokens from the XCM holding.
42
	/// These tokens can come from one or more holders that we had tracked earlier in the XCM
43
	/// execution, so we return an array of (holder, balance).
44
6
	pub(crate) fn drain(
45
6
		&mut self,
46
6
		contract_address: H160,
47
6
		amount: U256,
48
6
	) -> Result<Vec<(H160, U256)>, DrainError> {
49
6
		let tokens_to_transfer = self.drain_inner(&contract_address, amount)?;
50

            
51
3
		self.map
52
3
			.entry(contract_address)
53
3
			.and_modify(|erc20_origins| {
54
3
				*erc20_origins = erc20_origins.split_off(tokens_to_transfer.len());
55
3
			});
56
3

            
57
3
		Ok(tokens_to_transfer)
58
6
	}
59
6
	fn drain_inner(
60
6
		&self,
61
6
		contract_address: &H160,
62
6
		mut amount: U256,
63
6
	) -> Result<Vec<(H160, U256)>, DrainError> {
64
6
		let mut tokens_to_transfer = Vec::new();
65
6
		if let Some(erc20_origins) = self.map.get(contract_address) {
66
6
			for (from, subamount) in erc20_origins {
67
5
				if &amount > subamount {
68
1
					tokens_to_transfer.push((*from, *subamount));
69
1
					amount -= *subamount;
70
4
				} else if &amount == subamount {
71
3
					tokens_to_transfer.push((*from, *subamount));
72
3
					return Ok(tokens_to_transfer);
73
				} else {
74
					// Each insertion of tokens must be drain at once
75
1
					return Err(DrainError::SplitError);
76
				}
77
			}
78
			// If there were enough tokens, we had to return in the for loop
79
1
			Err(DrainError::NotEnoughFounds)
80
		} else {
81
1
			Err(DrainError::AssetNotFound)
82
		}
83
6
	}
84
3
	pub(crate) fn insert(&mut self, contract_address: H160, who: H160, amount: U256) {
85
3
		self.map
86
3
			.entry(contract_address)
87
3
			.or_default()
88
3
			.push((who, amount));
89
3
	}
90
2
	pub(crate) fn with<R, F>(f: F) -> Option<R>
91
2
	where
92
2
		F: FnOnce(&mut Self) -> R,
93
2
	{
94
2
		XCM_HOLDING_ERC20_ORIGINS::with(|erc20s_origins| f(erc20s_origins))
95
2
	}
96
}
97

            
98
/// Xcm executor wrapper that inject xcm holding extension "XcmHoldingErc20sOrigins"
99
pub struct XcmExecutorWrapper<Config, InnerXcmExecutor>(PhantomData<(Config, InnerXcmExecutor)>);
100
impl<Config, InnerXcmExecutor> xcm::latest::ExecuteXcm<Config::RuntimeCall>
101
	for XcmExecutorWrapper<Config, InnerXcmExecutor>
102
where
103
	Config: xcm_executor::Config,
104
	InnerXcmExecutor: xcm::latest::ExecuteXcm<Config::RuntimeCall>,
105
{
106
	type Prepared = InnerXcmExecutor::Prepared;
107

            
108
70
	fn prepare(
109
70
		message: xcm::latest::Xcm<Config::RuntimeCall>,
110
70
	) -> Result<Self::Prepared, xcm::latest::Xcm<Config::RuntimeCall>> {
111
70
		InnerXcmExecutor::prepare(message)
112
70
	}
113

            
114
70
	fn execute(
115
70
		origin: impl Into<xcm::latest::Location>,
116
70
		pre: Self::Prepared,
117
70
		hash: &mut xcm::latest::XcmHash,
118
70
		weight_credit: xcm::latest::Weight,
119
70
	) -> xcm::latest::Outcome {
120
70
		let mut erc20s_origins = Default::default();
121
70
		XCM_HOLDING_ERC20_ORIGINS::using(&mut erc20s_origins, || {
122
70
			InnerXcmExecutor::execute(origin, pre, hash, weight_credit)
123
70
		})
124
70
	}
125

            
126
67
	fn charge_fees(
127
67
		location: impl Into<xcm::latest::Location>,
128
67
		fees: xcm::latest::Assets,
129
67
	) -> Result<(), xcm::latest::Error> {
130
67
		InnerXcmExecutor::charge_fees(location, fees)
131
67
	}
132
}
133

            
134
impl<Config, InnerXcmExecutor> XcmAssetTransfers for XcmExecutorWrapper<Config, InnerXcmExecutor>
135
where
136
	Config: xcm_executor::Config,
137
{
138
	type IsReserve = Config::IsReserve;
139
	type IsTeleporter = Config::IsTeleporter;
140
	type AssetTransactor = Config::AssetTransactor;
141
}
142

            
143
#[cfg(test)]
144
mod tests {
145
	use super::*;
146

            
147
	#[test]
148
1
	fn test_xcm_holding_ext_erc20s_origins() {
149
		const TOKEN1: H160 = H160([1; 20]);
150
		const TOKEN2: H160 = H160([2; 20]);
151
		const USER1: H160 = H160([3; 20]);
152
		const USER2: H160 = H160([4; 20]);
153

            
154
		// Simple case
155
1
		let mut erc20s_origins_ = Default::default();
156
1
		XCM_HOLDING_ERC20_ORIGINS::using(&mut erc20s_origins_, || {
157
1
			XcmHoldingErc20sOrigins::with(|erc20s_origins| {
158
1
				erc20s_origins.insert(TOKEN1, USER1, U256::from(100));
159
1
				assert_eq!(
160
1
					erc20s_origins.drain(TOKEN2, U256::from(1)),
161
1
					Err(DrainError::AssetNotFound)
162
1
				);
163
1
				assert_eq!(
164
1
					erc20s_origins.drain(TOKEN1, U256::from(100)),
165
1
					Ok(vec![(USER1, U256::from(100))])
166
1
				);
167
1
			})
168
1
		});
169
1

            
170
1
		// Complex case
171
1
		let mut erc20s_origins_ = Default::default();
172
1
		XCM_HOLDING_ERC20_ORIGINS::using(&mut erc20s_origins_, || {
173
1
			XcmHoldingErc20sOrigins::with(|erc20s_origins| {
174
1
				erc20s_origins.insert(TOKEN1, USER1, U256::from(100));
175
1
				erc20s_origins.insert(TOKEN1, USER2, U256::from(200));
176
1
				assert_eq!(
177
1
					erc20s_origins.drain(TOKEN1, U256::from(100)),
178
1
					Ok(vec![(USER1, U256::from(100))])
179
1
				);
180
1
				assert_eq!(
181
1
					erc20s_origins.drain(TOKEN1, U256::from(201)),
182
1
					Err(DrainError::NotEnoughFounds)
183
1
				);
184
1
				assert_eq!(
185
1
					erc20s_origins.drain(TOKEN1, U256::from(199)),
186
1
					Err(DrainError::SplitError)
187
1
				);
188
1
				assert_eq!(
189
1
					erc20s_origins.drain(TOKEN1, U256::from(200)),
190
1
					Ok(vec![(USER2, U256::from(200))])
191
1
				);
192
1
			})
193
1
		});
194
1
	}
195
}