1use crate::{Config, Error, HoldReason, OriginalAccount, ensure};
21use alloc::vec::Vec;
22use core::marker::PhantomData;
23use frame_support::traits::{
24 OnKilledAccount, OnNewAccount, fungible::MutateHold, tokens::Precision,
25};
26use sp_core::{Get, H160};
27use sp_io::hashing::keccak_256;
28use sp_runtime::{AccountId32, DispatchResult, Saturating};
29
30pub trait AddressMapper<T: Config>: private::Sealed {
47 fn to_address(account_id: &T::AccountId) -> H160;
49
50 fn to_account_id(address: &H160) -> T::AccountId;
52
53 fn to_fallback_account_id(address: &H160) -> T::AccountId;
60
61 fn map(account_id: &T::AccountId) -> DispatchResult;
66
67 fn map_no_deposit(account_id: &T::AccountId) -> DispatchResult {
70 Self::map(account_id)
71 }
72
73 fn unmap(account_id: &T::AccountId) -> DispatchResult;
78
79 fn is_mapped(account_id: &T::AccountId) -> bool;
84
85 fn is_eth_derived(account_id: &T::AccountId) -> bool;
89}
90
91mod private {
92 pub trait Sealed {}
93 impl<T> Sealed for super::AccountId32Mapper<T> {}
94 impl<T> Sealed for super::H160Mapper<T> {}
95 impl<T> Sealed for super::TestAccountMapper<T> {}
96}
97
98pub struct AccountId32Mapper<T>(PhantomData<T>);
105
106#[allow(dead_code)]
110pub struct H160Mapper<T>(PhantomData<T>);
111
112pub struct TestAccountMapper<T>(PhantomData<T>);
114
115impl<T> AddressMapper<T> for AccountId32Mapper<T>
116where
117 T: Config<AccountId = AccountId32>,
118{
119 fn to_address(account_id: &AccountId32) -> H160 {
120 let account_bytes: &[u8; 32] = account_id.as_ref();
121 if Self::is_eth_derived(account_id) {
122 H160::from_slice(&account_bytes[..20])
125 } else {
126 let account_hash = keccak_256(account_bytes);
129 H160::from_slice(&account_hash[12..])
130 }
131 }
132
133 fn to_account_id(address: &H160) -> AccountId32 {
134 <OriginalAccount<T>>::get(address).unwrap_or_else(|| Self::to_fallback_account_id(address))
135 }
136
137 fn to_fallback_account_id(address: &H160) -> AccountId32 {
138 let mut account_id = AccountId32::new([0xEE; 32]);
139 let account_bytes: &mut [u8; 32] = account_id.as_mut();
140 account_bytes[..20].copy_from_slice(address.as_bytes());
141 account_id
142 }
143
144 fn map(account_id: &T::AccountId) -> DispatchResult {
145 ensure!(!Self::is_mapped(account_id), <Error<T>>::AccountAlreadyMapped);
146
147 let deposit = T::DepositPerByte::get()
149 .saturating_mul(52u32.into())
150 .saturating_add(T::DepositPerItem::get());
151 T::Currency::hold(&HoldReason::AddressMapping.into(), account_id, deposit)?;
152
153 <OriginalAccount<T>>::insert(Self::to_address(account_id), account_id);
154 Ok(())
155 }
156
157 fn map_no_deposit(account_id: &T::AccountId) -> DispatchResult {
158 ensure!(!Self::is_mapped(account_id), <Error<T>>::AccountAlreadyMapped);
159 <OriginalAccount<T>>::insert(Self::to_address(account_id), account_id);
160 Ok(())
161 }
162
163 fn unmap(account_id: &T::AccountId) -> DispatchResult {
164 <OriginalAccount<T>>::remove(Self::to_address(account_id));
166 T::Currency::release_all(
167 &HoldReason::AddressMapping.into(),
168 account_id,
169 Precision::BestEffort,
170 )?;
171 Ok(())
172 }
173
174 fn is_mapped(account_id: &T::AccountId) -> bool {
175 Self::is_eth_derived(account_id) ||
176 <OriginalAccount<T>>::contains_key(Self::to_address(account_id))
177 }
178
179 fn is_eth_derived(account_id: &T::AccountId) -> bool {
184 let account_bytes: &[u8; 32] = account_id.as_ref();
185 &account_bytes[20..] == &[0xEE; 12]
186 }
187}
188
189impl<T> AddressMapper<T> for TestAccountMapper<T>
190where
191 T: Config<AccountId = u64>,
192{
193 fn to_address(account_id: &T::AccountId) -> H160 {
194 let mut bytes = [0u8; 20];
195 bytes[12..].copy_from_slice(&account_id.to_be_bytes());
196 H160::from(bytes)
197 }
198
199 fn to_account_id(address: &H160) -> T::AccountId {
200 Self::to_fallback_account_id(address)
201 }
202
203 fn to_fallback_account_id(address: &H160) -> T::AccountId {
204 u64::from_be_bytes(address.as_ref()[12..].try_into().unwrap())
205 }
206
207 fn map(_account_id: &T::AccountId) -> DispatchResult {
208 Ok(())
209 }
210
211 fn unmap(_account_id: &T::AccountId) -> DispatchResult {
212 Ok(())
213 }
214
215 fn is_mapped(_account_id: &T::AccountId) -> bool {
216 true
217 }
218
219 fn is_eth_derived(_account_id: &T::AccountId) -> bool {
220 false
221 }
222}
223
224impl<T> AddressMapper<T> for H160Mapper<T>
225where
226 T: Config,
227 crate::AccountIdOf<T>: AsRef<[u8; 20]> + From<H160>,
228{
229 fn to_address(account_id: &T::AccountId) -> H160 {
230 H160::from_slice(account_id.as_ref())
231 }
232
233 fn to_account_id(address: &H160) -> T::AccountId {
234 Self::to_fallback_account_id(address)
235 }
236
237 fn to_fallback_account_id(address: &H160) -> T::AccountId {
238 (*address).into()
239 }
240
241 fn map(_account_id: &T::AccountId) -> DispatchResult {
242 Ok(())
243 }
244
245 fn unmap(_account_id: &T::AccountId) -> DispatchResult {
246 Ok(())
247 }
248
249 fn is_mapped(_account_id: &T::AccountId) -> bool {
250 true
251 }
252
253 fn is_eth_derived(_account_id: &T::AccountId) -> bool {
254 true
255 }
256}
257
258pub fn create1(deployer: &H160, nonce: u64) -> H160 {
260 let mut list = rlp::RlpStream::new_list(2);
261 list.append(&deployer.as_bytes());
262 list.append(&nonce);
263 let hash = keccak_256(&list.out());
264 H160::from_slice(&hash[12..])
265}
266
267pub fn create2(deployer: &H160, code: &[u8], input_data: &[u8], salt: &[u8; 32]) -> H160 {
269 let init_code_hash = {
270 let init_code: Vec<u8> = code.into_iter().chain(input_data).cloned().collect();
271 keccak_256(init_code.as_ref())
272 };
273 let mut bytes = [0; 85];
274 bytes[0] = 0xff;
275 bytes[1..21].copy_from_slice(deployer.as_bytes());
276 bytes[21..53].copy_from_slice(salt);
277 bytes[53..85].copy_from_slice(&init_code_hash);
278 let hash = keccak_256(&bytes);
279 H160::from_slice(&hash[12..])
280}
281
282pub struct AutoMapper<T>(PhantomData<T>);
283
284impl<T: Config> OnNewAccount<T::AccountId> for AutoMapper<T> {
285 fn on_new_account(who: &T::AccountId) {
286 if T::AutoMap::get() &&
287 !T::AddressMapper::is_eth_derived(who) &&
288 let Err(err) = T::AddressMapper::map_no_deposit(who)
289 {
290 log::warn!(
291 target: crate::LOG_TARGET,
292 "Failed to auto-map account {who:?}: {err:?}",
293 );
294 }
295 }
296}
297
298impl<T: Config> OnKilledAccount<T::AccountId> for AutoMapper<T> {
299 fn on_killed_account(who: &T::AccountId) {
300 if T::AutoMap::get() &&
301 !T::AddressMapper::is_eth_derived(who) &&
302 let Err(err) = T::AddressMapper::unmap(who)
303 {
304 log::warn!(
305 target: crate::LOG_TARGET,
306 "Failed to auto-unmap account {who:?}: {err:?}",
307 );
308 }
309 }
310}
311
312#[cfg(test)]
313mod test {
314 use super::*;
315 use crate::{
316 AddressMapper, Error, Pallet,
317 test_utils::*,
318 tests::{AutoMapFlag, ExtBuilder, RuntimeOrigin, Test},
319 };
320 use frame_support::{
321 assert_err,
322 dispatch::Pays,
323 traits::fungible::{InspectHold, Mutate},
324 };
325 use pretty_assertions::assert_eq;
326 use sp_core::{H160, hex2array};
327
328 #[test]
329 fn create1_works() {
330 assert_eq!(
331 create1(&ALICE_ADDR, 1u64),
332 H160(hex2array!("c851da37e4e8d3a20d8d56be2963934b4ad71c3b")),
333 )
334 }
335
336 #[test]
337 fn create2_works() {
338 assert_eq!(
339 create2(
340 &ALICE_ADDR,
341 &hex2array!("600060005560016000"),
342 &hex2array!("55"),
343 &hex2array!("1234567890123456789012345678901234567890123456789012345678901234")
344 ),
345 H160(hex2array!("7f31e795e5836a19a8f919ab5a9de9a197ecd2b6")),
346 )
347 }
348
349 #[test]
350 fn fallback_map_works() {
351 assert!(<Test as Config>::AddressMapper::is_mapped(&ALICE));
352 assert_eq!(
353 ALICE_FALLBACK,
354 <Test as Config>::AddressMapper::to_fallback_account_id(&ALICE_ADDR)
355 );
356 assert_eq!(ALICE_ADDR, <Test as Config>::AddressMapper::to_address(&ALICE_FALLBACK));
357 }
358
359 #[test]
360 fn map_works() {
361 ExtBuilder::default().build().execute_with(|| {
362 <Test as Config>::Currency::set_balance(&EVE, 1_000_000);
363 assert!(!<Test as Config>::AddressMapper::is_mapped(&EVE));
365 assert_eq!(EVE_FALLBACK, <Test as Config>::AddressMapper::to_account_id(&EVE_ADDR));
366 assert_eq!(
367 <Test as Config>::Currency::balance_on_hold(
368 &HoldReason::AddressMapping.into(),
369 &EVE
370 ),
371 0
372 );
373
374 <Test as Config>::AddressMapper::map(&EVE).unwrap();
376 assert!(<Test as Config>::AddressMapper::is_mapped(&EVE));
377 assert_eq!(EVE, <Test as Config>::AddressMapper::to_account_id(&EVE_ADDR));
378 assert!(
379 <Test as Config>::Currency::balance_on_hold(
380 &HoldReason::AddressMapping.into(),
381 &EVE
382 ) > 0
383 );
384 });
385 }
386
387 #[test]
388 fn map_fallback_account_fails() {
389 ExtBuilder::default().build().execute_with(|| {
390 assert!(<Test as Config>::AddressMapper::is_mapped(&ALICE));
391 assert_err!(
393 <Test as Config>::AddressMapper::map(&ALICE),
394 <Error<Test>>::AccountAlreadyMapped,
395 );
396 assert_eq!(
397 <Test as Config>::Currency::balance_on_hold(
398 &HoldReason::AddressMapping.into(),
399 &ALICE
400 ),
401 0
402 );
403 });
404 }
405
406 #[test]
407 fn double_map_fails() {
408 ExtBuilder::default().build().execute_with(|| {
409 assert!(!<Test as Config>::AddressMapper::is_mapped(&EVE));
410 <Test as Config>::Currency::set_balance(&EVE, 1_000_000);
411 <Test as Config>::AddressMapper::map(&EVE).unwrap();
412 assert!(<Test as Config>::AddressMapper::is_mapped(&EVE));
413 let deposit = <Test as Config>::Currency::balance_on_hold(
414 &HoldReason::AddressMapping.into(),
415 &EVE,
416 );
417 assert_err!(
418 <Test as Config>::AddressMapper::map(&EVE),
419 <Error<Test>>::AccountAlreadyMapped,
420 );
421 assert!(<Test as Config>::AddressMapper::is_mapped(&EVE));
422 assert_eq!(
423 <Test as Config>::Currency::balance_on_hold(
424 &HoldReason::AddressMapping.into(),
425 &EVE
426 ),
427 deposit
428 );
429 });
430 }
431
432 #[test]
433 fn unmap_works() {
434 ExtBuilder::default().build().execute_with(|| {
435 <Test as Config>::Currency::set_balance(&EVE, 1_000_000);
436 <Test as Config>::AddressMapper::map(&EVE).unwrap();
437 assert!(<Test as Config>::AddressMapper::is_mapped(&EVE));
438 assert!(
439 <Test as Config>::Currency::balance_on_hold(
440 &HoldReason::AddressMapping.into(),
441 &EVE
442 ) > 0
443 );
444
445 <Test as Config>::AddressMapper::unmap(&EVE).unwrap();
446 assert!(!<Test as Config>::AddressMapper::is_mapped(&EVE));
447 assert_eq!(
448 <Test as Config>::Currency::balance_on_hold(
449 &HoldReason::AddressMapping.into(),
450 &EVE
451 ),
452 0
453 );
454
455 <Test as Config>::AddressMapper::unmap(&EVE).unwrap();
457 assert!(!<Test as Config>::AddressMapper::is_mapped(&EVE));
458 assert_eq!(
459 <Test as Config>::Currency::balance_on_hold(
460 &HoldReason::AddressMapping.into(),
461 &EVE
462 ),
463 0
464 );
465 });
466 }
467
468 #[test]
469 fn auto_mapper_maps_on_new_account() {
470 ExtBuilder::default().build().execute_with(|| {
471 AutoMapFlag::set(true);
472
473 assert!(!frame_system::Pallet::<Test>::account_exists(&EVE));
474 assert!(!<Test as Config>::AddressMapper::is_mapped(&EVE));
475 <Test as Config>::Currency::set_balance(&EVE, 1_000_000);
477 assert!(<Test as Config>::AddressMapper::is_mapped(&EVE));
478 assert_eq!(
480 <Test as Config>::Currency::balance_on_hold(
481 &HoldReason::AddressMapping.into(),
482 &EVE
483 ),
484 0
485 );
486 });
487 }
488
489 #[test]
490 fn auto_mapper_unmaps_on_killed_account() {
491 ExtBuilder::default().build().execute_with(|| {
492 AutoMapFlag::set(true);
493 <Test as Config>::Currency::set_balance(&EVE, 1_000_000);
494 assert!(<Test as Config>::AddressMapper::is_mapped(&EVE));
495
496 <Test as Config>::Currency::set_balance(&EVE, 0);
498 assert!(!<Test as Config>::AddressMapper::is_mapped(&EVE));
499 });
500 }
501
502 #[test]
503 fn auto_mapper_noop_when_disabled() {
504 ExtBuilder::default().build().execute_with(|| {
505 AutoMapFlag::set(false);
506
507 assert!(!<Test as Config>::AddressMapper::is_mapped(&EVE));
508 <Test as Config>::Currency::set_balance(&EVE, 1_000_000);
509 assert!(!<Test as Config>::AddressMapper::is_mapped(&EVE));
510 });
511 }
512
513 #[test]
514 fn auto_mapper_ignores_eth_derived_accounts() {
515 ExtBuilder::default().build().execute_with(|| {
516 AutoMapFlag::set(true);
517
518 assert!(<Test as Config>::AddressMapper::is_mapped(&ALICE));
520 <Test as Config>::Currency::set_balance(&ALICE, 1_000_000);
522 assert!(<Test as Config>::AddressMapper::is_mapped(&ALICE));
523 });
524 }
525
526 #[test]
527 #[cfg(not(feature = "runtime-benchmarks"))]
528 fn unmap_account_dispatchable_blocked_when_auto_map_enabled() {
529 use frame_support::assert_noop;
530 ExtBuilder::default().build().execute_with(|| {
531 AutoMapFlag::set(true);
532
533 assert_noop!(
534 Pallet::<Test>::unmap_account(RuntimeOrigin::signed(EVE)),
535 <Error<Test>>::AutoMappingEnabled,
536 );
537 });
538 }
539
540 #[test]
541 fn batch_map_accounts_empty_pays_yes() {
542 ExtBuilder::default().build().execute_with(|| {
543 let info =
544 Pallet::<Test>::batch_map_accounts(RuntimeOrigin::signed(ALICE), alloc::vec![])
545 .unwrap();
546
547 assert_eq!(info.pays_fee, Pays::Yes);
548 });
549 }
550
551 #[test]
552 fn batch_map_accounts_all_eth_derived_pays_yes() {
553 ExtBuilder::default().build().execute_with(|| {
554 let info = Pallet::<Test>::batch_map_accounts(
557 RuntimeOrigin::signed(ALICE),
558 alloc::vec![ALICE, BOB, CHARLIE, DJANGO],
559 )
560 .unwrap();
561
562 assert_eq!(info.pays_fee, Pays::Yes);
563 });
564 }
565
566 #[test]
567 fn batch_map_accounts_pays_no_when_mostly_unmapped() {
568 ExtBuilder::default().build().execute_with(|| {
569 let unmapped: Vec<AccountId32> =
570 (10u8..19u8).map(|i| AccountId32::new([i; 32])).collect();
571 let mut accounts = unmapped.clone();
572 accounts.push(ALICE); let info =
576 Pallet::<Test>::batch_map_accounts(RuntimeOrigin::signed(ALICE), accounts).unwrap();
577
578 assert_eq!(info.pays_fee, Pays::No);
579
580 for a in &unmapped {
581 assert!(<Test as Config>::AddressMapper::is_mapped(a));
582
583 assert_eq!(
585 <Test as Config>::Currency::balance_on_hold(
586 &HoldReason::AddressMapping.into(),
587 a,
588 ),
589 0
590 );
591 }
592 });
593 }
594
595 #[test]
596 fn batch_map_accounts_already_mapped_no_hold_pays_yes() {
597 ExtBuilder::default().build().execute_with(|| {
598 <Test as Config>::AddressMapper::map_no_deposit(&EVE).unwrap();
599 assert!(<Test as Config>::AddressMapper::is_mapped(&EVE));
600
601 assert_eq!(
602 <Test as Config>::Currency::balance_on_hold(
603 &HoldReason::AddressMapping.into(),
604 &EVE,
605 ),
606 0
607 );
608
609 let info = Pallet::<Test>::batch_map_accounts(
610 RuntimeOrigin::signed(ALICE),
611 alloc::vec![EVE; 10],
612 )
613 .unwrap();
614
615 assert_eq!(info.pays_fee, Pays::Yes);
616 });
617 }
618
619 #[test]
620 fn batch_map_accounts_pays_yes_below_threshold() {
621 ExtBuilder::default().build().execute_with(|| {
622 let mut accounts: Vec<AccountId32> = alloc::vec![AccountId32::new([10u8; 32])];
624 for _ in 0..9 {
625 accounts.push(ALICE);
626 }
627
628 let info =
629 Pallet::<Test>::batch_map_accounts(RuntimeOrigin::signed(ALICE), accounts).unwrap();
630
631 assert_eq!(info.pays_fee, Pays::Yes);
632 });
633 }
634}