1use frame_support::traits::tokens::{Fortitude, Precision, Preservation, Provenance};
2use frame_support::traits::{
3 fungible, fungibles,
4 tokens::{Balance as BalanceT, DepositConsequence, WithdrawConsequence},
5 Contains, Get,
6};
7use sp_arithmetic::{traits::Bounded, ArithmeticError};
8use sp_runtime::DispatchError;
9
10pub struct Combiner<AccountId, TestKey, A, B>(sp_std::marker::PhantomData<(AccountId, TestKey, A, B)>);
11
12impl<AccountId, TestKey, A, B> fungibles::Inspect<AccountId> for Combiner<AccountId, TestKey, A, B>
13where
14 TestKey: Contains<<B as fungibles::Inspect<AccountId>>::AssetId>,
15 A: fungible::Inspect<AccountId, Balance = <B as fungibles::Inspect<AccountId>>::Balance>,
16 B: fungibles::Inspect<AccountId>,
17{
18 type AssetId = <B as fungibles::Inspect<AccountId>>::AssetId;
19 type Balance = <B as fungibles::Inspect<AccountId>>::Balance;
20
21 fn total_issuance(asset: Self::AssetId) -> Self::Balance {
22 if TestKey::contains(&asset) {
23 A::total_issuance()
24 } else {
25 B::total_issuance(asset)
26 }
27 }
28
29 fn minimum_balance(asset: Self::AssetId) -> Self::Balance {
30 if TestKey::contains(&asset) {
31 A::minimum_balance()
32 } else {
33 B::minimum_balance(asset)
34 }
35 }
36
37 fn balance(asset: Self::AssetId, who: &AccountId) -> Self::Balance {
38 if TestKey::contains(&asset) {
39 A::balance(who)
40 } else {
41 B::balance(asset, who)
42 }
43 }
44
45 fn total_balance(asset: Self::AssetId, who: &AccountId) -> Self::Balance {
46 if TestKey::contains(&asset) {
47 A::total_balance(who)
48 } else {
49 B::total_balance(asset, who)
50 }
51 }
52
53 fn reducible_balance(
54 asset: Self::AssetId,
55 who: &AccountId,
56 preservation: Preservation,
57 fortitude: Fortitude,
58 ) -> Self::Balance {
59 if TestKey::contains(&asset) {
60 A::reducible_balance(who, preservation, fortitude)
61 } else {
62 B::reducible_balance(asset, who, preservation, fortitude)
63 }
64 }
65
66 fn can_deposit(
67 asset: Self::AssetId,
68 who: &AccountId,
69 amount: Self::Balance,
70 provenance: Provenance,
71 ) -> DepositConsequence {
72 if TestKey::contains(&asset) {
73 A::can_deposit(who, amount, provenance)
74 } else {
75 B::can_deposit(asset, who, amount, provenance)
76 }
77 }
78
79 fn can_withdraw(
80 asset: Self::AssetId,
81 who: &AccountId,
82 amount: Self::Balance,
83 ) -> WithdrawConsequence<Self::Balance> {
84 if TestKey::contains(&asset) {
85 A::can_withdraw(who, amount)
86 } else {
87 B::can_withdraw(asset, who, amount)
88 }
89 }
90
91 fn asset_exists(asset: Self::AssetId) -> bool {
92 if TestKey::contains(&asset) {
93 true
94 } else {
95 B::asset_exists(asset)
96 }
97 }
98}
99
100impl<AccountId, TestKey, A, B> fungibles::Mutate<AccountId> for Combiner<AccountId, TestKey, A, B>
101where
102 TestKey: Contains<<B as fungibles::Inspect<AccountId>>::AssetId>,
103 A: fungible::Mutate<AccountId, Balance = <B as fungibles::Inspect<AccountId>>::Balance>,
104 B: fungibles::Mutate<AccountId>,
105 AccountId: Eq,
106{
107 fn mint_into(
108 asset: Self::AssetId,
109 dest: &AccountId,
110 amount: Self::Balance,
111 ) -> Result<Self::Balance, DispatchError> {
112 if TestKey::contains(&asset) {
113 A::mint_into(dest, amount)
114 } else {
115 B::mint_into(asset, dest, amount)
116 }
117 }
118
119 fn burn_from(
120 asset: Self::AssetId,
121 dest: &AccountId,
122 amount: Self::Balance,
123 preservation: Preservation,
124 precision: Precision,
125 fortitude: Fortitude,
126 ) -> Result<Self::Balance, DispatchError> {
127 if TestKey::contains(&asset) {
128 A::burn_from(dest, amount, preservation, precision, fortitude)
129 } else {
130 B::burn_from(asset, dest, amount, preservation, precision, fortitude)
131 }
132 }
133
134 fn transfer(
135 asset: Self::AssetId,
136 source: &AccountId,
137 dest: &AccountId,
138 amount: Self::Balance,
139 preservation: Preservation,
140 ) -> Result<Self::Balance, DispatchError> {
141 if TestKey::contains(&asset) {
142 A::transfer(source, dest, amount, preservation)
143 } else {
144 B::transfer(asset, source, dest, amount, preservation)
145 }
146 }
147}
148
149impl<AccountId, TestKey, A, B> fungibles::Unbalanced<AccountId> for Combiner<AccountId, TestKey, A, B>
150where
151 TestKey: Contains<<B as fungibles::Inspect<AccountId>>::AssetId>,
152 A: fungible::Mutate<AccountId, Balance = <B as fungibles::Inspect<AccountId>>::Balance>,
153 B: fungibles::Mutate<AccountId>,
154 AccountId: Eq,
155{
156 fn handle_dust(_dust: fungibles::Dust<AccountId, Self>) {
157 }
160
161 fn write_balance(
162 asset: Self::AssetId,
163 who: &AccountId,
164 amount: Self::Balance,
165 ) -> Result<Option<Self::Balance>, DispatchError> {
166 if TestKey::contains(&asset) {
167 A::write_balance(who, amount)
168 } else {
169 B::write_balance(asset, who, amount)
170 }
171 }
172
173 fn set_total_issuance(asset: Self::AssetId, amount: Self::Balance) {
174 if TestKey::contains(&asset) {
175 A::set_total_issuance(amount)
176 } else {
177 B::set_total_issuance(asset, amount)
178 }
179 }
180}
181
182pub trait ConvertBalance<A: Bounded, B: Bounded> {
183 type AssetId;
184 fn convert_balance(amount: A, asset_id: Self::AssetId) -> Result<B, ArithmeticError>;
185 fn convert_balance_back(amount: B, asset_id: Self::AssetId) -> Result<A, ArithmeticError>;
186
187 fn convert_balance_saturated(amount: A, asset_id: Self::AssetId) -> B {
188 Self::convert_balance(amount, asset_id).unwrap_or_else(|e| match e {
189 ArithmeticError::Overflow => B::max_value(),
190 ArithmeticError::Underflow => B::min_value(),
191 ArithmeticError::DivisionByZero => B::max_value(),
192 })
193 }
194 fn convert_balance_back_saturated(amount: B, asset_id: Self::AssetId) -> A {
195 Self::convert_balance_back(amount, asset_id).unwrap_or_else(|e| match e {
196 ArithmeticError::Overflow => A::max_value(),
197 ArithmeticError::Underflow => A::min_value(),
198 ArithmeticError::DivisionByZero => A::max_value(),
199 })
200 }
201}
202
203pub struct Mapper<AccountId, T, C, B, GetCurrencyId>(sp_std::marker::PhantomData<(AccountId, T, C, B, GetCurrencyId)>);
204impl<AccountId, T, C, B, GetCurrencyId> fungible::Inspect<AccountId> for Mapper<AccountId, T, C, B, GetCurrencyId>
205where
206 T: fungibles::Inspect<AccountId>,
207 C: ConvertBalance<
208 <T as fungibles::Inspect<AccountId>>::Balance,
209 B,
210 AssetId = <T as fungibles::Inspect<AccountId>>::AssetId,
211 >,
212 B: BalanceT,
213 GetCurrencyId: Get<<T as fungibles::Inspect<AccountId>>::AssetId>,
214{
215 type Balance = B;
216
217 fn total_issuance() -> Self::Balance {
218 C::convert_balance_saturated(T::total_issuance(GetCurrencyId::get()), GetCurrencyId::get())
219 }
220
221 fn minimum_balance() -> Self::Balance {
222 C::convert_balance_saturated(T::minimum_balance(GetCurrencyId::get()), GetCurrencyId::get())
223 }
224
225 fn balance(who: &AccountId) -> Self::Balance {
226 C::convert_balance_saturated(T::balance(GetCurrencyId::get(), who), GetCurrencyId::get())
227 }
228
229 fn total_balance(who: &AccountId) -> Self::Balance {
230 C::convert_balance_saturated(T::total_balance(GetCurrencyId::get(), who), GetCurrencyId::get())
231 }
232
233 fn reducible_balance(who: &AccountId, preservation: Preservation, fortitude: Fortitude) -> Self::Balance {
234 C::convert_balance_saturated(
235 T::reducible_balance(GetCurrencyId::get(), who, preservation, fortitude),
236 GetCurrencyId::get(),
237 )
238 }
239
240 fn can_deposit(who: &AccountId, amount: Self::Balance, provenance: Provenance) -> DepositConsequence {
241 let amount = C::convert_balance_back(amount, GetCurrencyId::get());
242 let amount = match amount {
243 Ok(amount) => amount,
244 Err(_) => return DepositConsequence::Overflow,
245 };
246 T::can_deposit(GetCurrencyId::get(), who, amount, provenance)
247 }
248
249 fn can_withdraw(who: &AccountId, amount: Self::Balance) -> WithdrawConsequence<Self::Balance> {
250 use WithdrawConsequence::*;
251
252 let amount = C::convert_balance_back(amount, GetCurrencyId::get());
253 let amount = match amount {
254 Ok(amount) => amount,
255 Err(ArithmeticError::Overflow) => return Overflow,
256 Err(ArithmeticError::Underflow) => return Underflow,
257 Err(ArithmeticError::DivisionByZero) => return Overflow,
258 };
259
260 let res = T::can_withdraw(GetCurrencyId::get(), who, amount);
261 match res {
262 WithdrawConsequence::ReducedToZero(b) => {
263 WithdrawConsequence::ReducedToZero(C::convert_balance_saturated(b, GetCurrencyId::get()))
264 }
265 BalanceLow => BalanceLow,
266 WouldDie => WouldDie,
267 UnknownAsset => UnknownAsset,
268 Underflow => Underflow,
269 Overflow => Overflow,
270 Frozen => Frozen,
271 Success => Success,
272 }
273 }
274}
275
276impl<AccountId, T, C, B, GetCurrencyId> fungible::Mutate<AccountId> for Mapper<AccountId, T, C, B, GetCurrencyId>
277where
278 T: fungibles::Mutate<AccountId, Balance = B>,
279 C: ConvertBalance<
280 <T as fungibles::Inspect<AccountId>>::Balance,
281 B,
282 AssetId = <T as fungibles::Inspect<AccountId>>::AssetId,
283 >,
284 B: BalanceT,
285 GetCurrencyId: Get<<T as fungibles::Inspect<AccountId>>::AssetId>,
286 AccountId: Eq,
287{
288 fn mint_into(dest: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
289 T::mint_into(
290 GetCurrencyId::get(),
291 dest,
292 C::convert_balance_back(amount, GetCurrencyId::get())?,
293 )
294 }
295
296 fn burn_from(
297 dest: &AccountId,
298 amount: Self::Balance,
299 preservation: Preservation,
300 precision: Precision,
301 fortitude: Fortitude,
302 ) -> Result<Self::Balance, DispatchError> {
303 T::burn_from(
304 GetCurrencyId::get(),
305 dest,
306 C::convert_balance_back(amount, GetCurrencyId::get())?,
307 preservation,
308 precision,
309 fortitude,
310 )
311 }
312
313 fn transfer(
314 source: &AccountId,
315 dest: &AccountId,
316 amount: B,
317 preservation: Preservation,
318 ) -> Result<B, DispatchError> {
319 T::transfer(
320 GetCurrencyId::get(),
321 source,
322 dest,
323 C::convert_balance_back(amount, GetCurrencyId::get())?,
324 preservation,
325 )
326 }
327}
328
329impl<AccountId, T, C, B, GetCurrencyId> fungible::Unbalanced<AccountId> for Mapper<AccountId, T, C, B, GetCurrencyId>
330where
331 T: fungibles::Unbalanced<AccountId, Balance = B>,
332 C: ConvertBalance<
333 <T as fungibles::Inspect<AccountId>>::Balance,
334 B,
335 AssetId = <T as fungibles::Inspect<AccountId>>::AssetId,
336 >,
337 B: BalanceT,
338 GetCurrencyId: Get<<T as fungibles::Inspect<AccountId>>::AssetId>,
339{
340 fn handle_dust(_dust: fungible::Dust<AccountId, Self>) {
341 }
344
345 fn write_balance(who: &AccountId, amount: Self::Balance) -> Result<Option<Self::Balance>, DispatchError> {
346 T::write_balance(GetCurrencyId::get(), who, amount)
347 }
348
349 fn set_total_issuance(amount: Self::Balance) {
350 T::set_total_issuance(GetCurrencyId::get(), amount)
351 }
352}