1#![cfg_attr(not(feature = "std"), no_std)]
65
66use astar_primitives::{
67 evm::{EvmAddress, UnifiedAddressMapper},
68 Balance,
69};
70use frame_support::{
71 pallet_prelude::*,
72 traits::{
73 fungible::{Inspect as FungibleInspect, Mutate as FungibleMutate},
74 tokens::{Fortitude::*, Precision::*, Preservation::*},
75 OnKilledAccount,
76 },
77};
78use frame_system::{ensure_signed, pallet_prelude::*};
79use pallet_evm::AddressMapping;
80use precompile_utils::keccak256;
81use sp_core::{H160, H256, U256};
82use sp_io::hashing::keccak_256;
83use sp_runtime::{
84 traits::{LookupError, StaticLookup, Zero},
85 MultiAddress,
86};
87use sp_std::marker::PhantomData;
88
89pub use pallet::*;
90
91pub mod weights;
92pub use weights::WeightInfo;
93
94#[cfg(feature = "runtime-benchmarks")]
95mod benchmarking;
96mod mock;
97mod tests;
98
99type EvmSignature = [u8; 65];
101
102#[frame_support::pallet]
103pub mod pallet {
104 use super::*;
105
106 #[pallet::pallet]
107 pub struct Pallet<T>(PhantomData<T>);
108
109 #[pallet::config]
110 pub trait Config: frame_system::Config {
111 type Currency: FungibleMutate<Self::AccountId, Balance = Balance>;
113 type DefaultMappings: UnifiedAddressMapper<Self::AccountId>;
115 #[pallet::constant]
117 type ChainId: Get<u64>;
118 #[pallet::constant]
122 type AccountMappingStorageFee: Get<Balance>;
123 type WeightInfo: WeightInfo;
125 }
126
127 #[pallet::error]
128 pub enum Error<T> {
129 AlreadyMapped,
131 UnexpectedSignatureFormat,
133 InvalidSignature,
135 FundsUnavailable,
137 }
138
139 #[pallet::event]
140 #[pallet::generate_deposit(pub(super) fn deposit_event)]
141 pub enum Event<T: Config> {
142 AccountClaimed {
145 account_id: T::AccountId,
146 evm_address: EvmAddress,
147 },
148 }
149
150 #[pallet::storage]
153 pub type EvmToNative<T: Config> =
154 StorageMap<_, Blake2_128Concat, EvmAddress, T::AccountId, OptionQuery>;
155
156 #[pallet::storage]
159 pub type NativeToEvm<T: Config> =
160 StorageMap<_, Blake2_128Concat, T::AccountId, EvmAddress, OptionQuery>;
161
162 #[pallet::call]
163 impl<T: Config> Pallet<T> {
164 #[pallet::call_index(0)]
176 #[pallet::weight(T::WeightInfo::claim_evm_address())]
177 pub fn claim_evm_address(
178 origin: OriginFor<T>,
179 evm_address: EvmAddress,
180 signature: EvmSignature,
181 ) -> DispatchResult {
182 let who = ensure_signed(origin)?;
183 ensure!(
185 !NativeToEvm::<T>::contains_key(&who),
186 Error::<T>::AlreadyMapped
187 );
188 ensure!(
189 !EvmToNative::<T>::contains_key(evm_address),
190 Error::<T>::AlreadyMapped
191 );
192
193 let address = Self::verify_signature(&who, &signature)
195 .ok_or(Error::<T>::UnexpectedSignatureFormat)?;
196
197 ensure!(evm_address == address, Error::<T>::InvalidSignature);
198
199 Self::charge_storage_fee(&who)?;
201
202 let default_account_id = T::DefaultMappings::to_default_account_id(&evm_address);
204 if frame_system::Pallet::<T>::account_exists(&default_account_id) {
205 T::Currency::transfer(
210 &default_account_id,
211 &who,
212 T::Currency::reducible_balance(&default_account_id, Expendable, Polite),
213 Expendable,
214 )?;
215 }
216
217 EvmToNative::<T>::insert(&evm_address, &who);
219 NativeToEvm::<T>::insert(&who, &evm_address);
220
221 Self::deposit_event(Event::AccountClaimed {
222 account_id: who,
223 evm_address,
224 });
225 Ok(())
226 }
227
228 #[pallet::call_index(1)]
233 #[pallet::weight(T::WeightInfo::claim_default_evm_address())]
234 pub fn claim_default_evm_address(origin: OriginFor<T>) -> DispatchResult {
235 let who = ensure_signed(origin)?;
236 let _ = Self::do_claim_default_evm_address(who)?;
238 Ok(())
239 }
240 }
241}
242
243impl<T: Config> Pallet<T> {
244 fn do_claim_default_evm_address(account_id: T::AccountId) -> Result<EvmAddress, DispatchError> {
246 ensure!(
247 !NativeToEvm::<T>::contains_key(&account_id),
248 Error::<T>::AlreadyMapped
249 );
250 let evm_address = T::DefaultMappings::to_default_h160(&account_id);
252 ensure!(
255 !EvmToNative::<T>::contains_key(&evm_address),
256 Error::<T>::AlreadyMapped
257 );
258
259 Self::charge_storage_fee(&account_id)?;
260
261 EvmToNative::<T>::insert(&evm_address, &account_id);
263 NativeToEvm::<T>::insert(&account_id, &evm_address);
264
265 Self::deposit_event(Event::AccountClaimed {
266 account_id,
267 evm_address,
268 });
269 Ok(evm_address)
270 }
271
272 fn charge_storage_fee(who: &T::AccountId) -> Result<Balance, DispatchError> {
275 let balance = T::Currency::reducible_balance(who, Preserve, Polite);
276 let fee = T::AccountMappingStorageFee::get();
277 ensure!(balance >= fee, Error::<T>::FundsUnavailable);
278 T::Currency::burn_from(
279 who,
280 T::AccountMappingStorageFee::get(),
281 Preserve,
282 Exact,
283 Polite,
284 )
285 }
286}
287
288impl<T: Config> Pallet<T> {
293 pub fn build_signing_payload(who: &T::AccountId) -> [u8; 32] {
294 let domain_separator = Self::build_domain_separator();
295 let args_hash = Self::build_args_hash(who);
296
297 let mut payload = b"\x19\x01".to_vec();
298 payload.extend_from_slice(&domain_separator);
299 payload.extend_from_slice(&args_hash);
300 keccak_256(&payload)
301 }
302
303 pub fn verify_signature(who: &T::AccountId, sig: &EvmSignature) -> Option<EvmAddress> {
304 let payload_hash = Self::build_signing_payload(who);
305
306 sp_io::crypto::secp256k1_ecdsa_recover(sig, &payload_hash)
307 .map(|pubkey| H160::from(H256::from_slice(&keccak_256(&pubkey))))
308 .ok()
309 }
310
311 fn build_domain_separator() -> [u8; 32] {
312 let mut domain =
313 keccak256!("EIP712Domain(string name,string version,uint256 chainId,bytes32 salt)")
314 .to_vec();
315 domain.extend_from_slice(&keccak256!("Astar EVM Claim")); domain.extend_from_slice(&keccak256!("1")); domain.extend_from_slice(&U256::from(T::ChainId::get()).to_big_endian()); domain.extend_from_slice(
319 frame_system::Pallet::<T>::block_hash(BlockNumberFor::<T>::zero()).as_ref(),
320 ); keccak_256(domain.as_slice())
322 }
323
324 fn build_args_hash(account: &T::AccountId) -> [u8; 32] {
325 let mut args_hash = keccak256!("Claim(bytes substrateAddress)").to_vec();
326 args_hash.extend_from_slice(&keccak_256(&account.encode()));
327 keccak_256(args_hash.as_slice())
328 }
329}
330
331#[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
332impl<T: Config> Pallet<T> {
333 pub fn eth_sign_prehash(prehash: &[u8; 32], secret: &libsecp256k1::SecretKey) -> [u8; 65] {
335 let (sig, recovery_id) = libsecp256k1::sign(&libsecp256k1::Message::parse(prehash), secret);
336 let mut r = [0u8; 65];
337 r[0..64].copy_from_slice(&sig.serialize()[..]);
338 r[64] = recovery_id.serialize();
339 r
340 }
341
342 pub fn eth_address(secret: &libsecp256k1::SecretKey) -> EvmAddress {
344 EvmAddress::from_slice(
345 &sp_io::hashing::keccak_256(
346 &libsecp256k1::PublicKey::from_secret_key(secret).serialize()[1..65],
347 )[12..],
348 )
349 }
350}
351
352impl<T: Config> UnifiedAddressMapper<T::AccountId> for Pallet<T> {
355 fn to_account_id(evm_address: &EvmAddress) -> Option<T::AccountId> {
356 EvmToNative::<T>::get(evm_address)
357 }
358
359 fn to_default_account_id(evm_address: &EvmAddress) -> T::AccountId {
360 T::DefaultMappings::to_default_account_id(evm_address)
361 }
362
363 fn to_h160(account_id: &T::AccountId) -> Option<EvmAddress> {
364 NativeToEvm::<T>::get(account_id)
365 }
366
367 fn to_default_h160(account_id: &T::AccountId) -> EvmAddress {
368 T::DefaultMappings::to_default_h160(account_id)
369 }
370}
371
372impl<T: Config> AddressMapping<T::AccountId> for Pallet<T> {
374 fn into_account_id(evm_address: H160) -> T::AccountId {
375 <Self as UnifiedAddressMapper<T::AccountId>>::to_account_id_or_default(&evm_address)
376 .into_address()
377 }
378}
379
380pub struct KillAccountMapping<T>(PhantomData<T>);
383impl<T: Config> OnKilledAccount<T::AccountId> for KillAccountMapping<T> {
384 fn on_killed_account(who: &T::AccountId) {
385 if let Some(evm_addr) = NativeToEvm::<T>::take(who) {
387 EvmToNative::<T>::remove(evm_addr);
388 NativeToEvm::<T>::remove(who);
389 }
390 }
391}
392
393impl<T: Config> StaticLookup for Pallet<T> {
395 type Source = MultiAddress<T::AccountId, ()>;
396 type Target = T::AccountId;
397
398 fn lookup(a: Self::Source) -> Result<Self::Target, LookupError> {
399 match a {
400 MultiAddress::Address20(i) => Ok(
401 <Self as UnifiedAddressMapper<T::AccountId>>::to_account_id_or_default(
402 &EvmAddress::from_slice(&i),
403 )
404 .into_address(),
405 ),
406 _ => Err(LookupError),
407 }
408 }
409
410 fn unlookup(a: Self::Target) -> Self::Source {
411 MultiAddress::Id(a)
412 }
413}