1use core::marker::PhantomData;
20use sp_core::{H160, U256};
21use sp_std::collections::btree_map::BTreeMap;
22use sp_std::vec::Vec;
23use xcm::prelude::*;
24use xcm_executor::traits::{FeeManager, FeeReason, XcmAssetTransfers};
25
26environmental::environmental!(XCM_HOLDING_ERC20_ORIGINS: XcmHoldingErc20sOrigins);
27
28#[cfg_attr(test, derive(PartialEq, Debug))]
29pub(crate) enum DrainError {
30 AssetNotFound,
31 NotEnoughFounds,
32 SplitError,
33}
34
35#[derive(Default)]
38pub(crate) struct XcmHoldingErc20sOrigins {
39 map: BTreeMap<H160, Vec<(H160, U256)>>,
40}
41impl XcmHoldingErc20sOrigins {
42 pub(crate) fn drain(
46 &mut self,
47 contract_address: H160,
48 amount: U256,
49 ) -> Result<Vec<(H160, U256)>, DrainError> {
50 let tokens_to_transfer = self.drain_inner(&contract_address, amount)?;
51
52 self.map
53 .entry(contract_address)
54 .and_modify(|erc20_origins| {
55 *erc20_origins = erc20_origins.split_off(tokens_to_transfer.len());
56 });
57
58 Ok(tokens_to_transfer)
59 }
60 fn drain_inner(
61 &self,
62 contract_address: &H160,
63 mut amount: U256,
64 ) -> Result<Vec<(H160, U256)>, DrainError> {
65 let mut tokens_to_transfer = Vec::new();
66 if let Some(erc20_origins) = self.map.get(contract_address) {
67 for (from, subamount) in erc20_origins {
68 if &amount > subamount {
69 tokens_to_transfer.push((*from, *subamount));
70 amount -= *subamount;
71 } else if &amount == subamount {
72 tokens_to_transfer.push((*from, *subamount));
73 return Ok(tokens_to_transfer);
74 } else {
75 return Err(DrainError::SplitError);
77 }
78 }
79 Err(DrainError::NotEnoughFounds)
81 } else {
82 Err(DrainError::AssetNotFound)
83 }
84 }
85 pub(crate) fn insert(&mut self, contract_address: H160, who: H160, amount: U256) {
86 self.map
87 .entry(contract_address)
88 .or_default()
89 .push((who, amount));
90 }
91 pub(crate) fn with<R, F>(f: F) -> Option<R>
92 where
93 F: FnOnce(&mut Self) -> R,
94 {
95 XCM_HOLDING_ERC20_ORIGINS::with(|erc20s_origins| f(erc20s_origins))
96 }
97}
98
99pub struct XcmExecutorWrapper<Config, InnerXcmExecutor>(PhantomData<(Config, InnerXcmExecutor)>);
101impl<Config, InnerXcmExecutor> xcm::latest::ExecuteXcm<Config::RuntimeCall>
102 for XcmExecutorWrapper<Config, InnerXcmExecutor>
103where
104 Config: xcm_executor::Config,
105 InnerXcmExecutor: xcm::latest::ExecuteXcm<Config::RuntimeCall>,
106{
107 type Prepared = InnerXcmExecutor::Prepared;
108
109 fn prepare(
110 message: xcm::latest::Xcm<Config::RuntimeCall>,
111 weight_limit: Weight,
112 ) -> Result<Self::Prepared, xcm::latest::InstructionError> {
113 InnerXcmExecutor::prepare(message, weight_limit)
114 }
115
116 fn execute(
117 origin: impl Into<xcm::latest::Location>,
118 pre: Self::Prepared,
119 hash: &mut xcm::latest::XcmHash,
120 weight_credit: xcm::latest::Weight,
121 ) -> xcm::latest::Outcome {
122 let mut erc20s_origins = Default::default();
123 XCM_HOLDING_ERC20_ORIGINS::using(&mut erc20s_origins, || {
124 InnerXcmExecutor::execute(origin, pre, hash, weight_credit)
125 })
126 }
127
128 fn charge_fees(
129 location: impl Into<xcm::latest::Location>,
130 fees: xcm::latest::Assets,
131 ) -> Result<(), xcm::latest::Error> {
132 InnerXcmExecutor::charge_fees(location, fees)
133 }
134}
135
136impl<Config, InnerXcmExecutor> XcmAssetTransfers for XcmExecutorWrapper<Config, InnerXcmExecutor>
137where
138 Config: xcm_executor::Config,
139{
140 type IsReserve = Config::IsReserve;
141 type IsTeleporter = Config::IsTeleporter;
142 type AssetTransactor = Config::AssetTransactor;
143}
144
145impl<Config, InnerXcmExecutor> FeeManager for XcmExecutorWrapper<Config, InnerXcmExecutor>
146where
147 Config: xcm_executor::Config,
148 InnerXcmExecutor: FeeManager,
149{
150 fn is_waived(origin: Option<&Location>, r: FeeReason) -> bool {
151 InnerXcmExecutor::is_waived(origin, r)
152 }
153
154 fn handle_fee(fee: Assets, context: Option<&XcmContext>, r: FeeReason) {
155 InnerXcmExecutor::handle_fee(fee, context, r)
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 #[test]
164 fn test_xcm_holding_ext_erc20s_origins() {
165 const TOKEN1: H160 = H160([1; 20]);
166 const TOKEN2: H160 = H160([2; 20]);
167 const USER1: H160 = H160([3; 20]);
168 const USER2: H160 = H160([4; 20]);
169
170 let mut erc20s_origins_ = Default::default();
172 XCM_HOLDING_ERC20_ORIGINS::using(&mut erc20s_origins_, || {
173 XcmHoldingErc20sOrigins::with(|erc20s_origins| {
174 erc20s_origins.insert(TOKEN1, USER1, U256::from(100));
175 assert_eq!(
176 erc20s_origins.drain(TOKEN2, U256::from(1)),
177 Err(DrainError::AssetNotFound)
178 );
179 assert_eq!(
180 erc20s_origins.drain(TOKEN1, U256::from(100)),
181 Ok(vec![(USER1, U256::from(100))])
182 );
183 })
184 });
185
186 let mut erc20s_origins_ = Default::default();
188 XCM_HOLDING_ERC20_ORIGINS::using(&mut erc20s_origins_, || {
189 XcmHoldingErc20sOrigins::with(|erc20s_origins| {
190 erc20s_origins.insert(TOKEN1, USER1, U256::from(100));
191 erc20s_origins.insert(TOKEN1, USER2, U256::from(200));
192 assert_eq!(
193 erc20s_origins.drain(TOKEN1, U256::from(100)),
194 Ok(vec![(USER1, U256::from(100))])
195 );
196 assert_eq!(
197 erc20s_origins.drain(TOKEN1, U256::from(201)),
198 Err(DrainError::NotEnoughFounds)
199 );
200 assert_eq!(
201 erc20s_origins.drain(TOKEN1, U256::from(199)),
202 Err(DrainError::SplitError)
203 );
204 assert_eq!(
205 erc20s_origins.drain(TOKEN1, U256::from(200)),
206 Ok(vec![(USER2, U256::from(200))])
207 );
208 })
209 });
210 }
211}