astar_primitives/
evm.rs

1// This file is part of Astar.
2
3// Copyright (C) Stake Technologies Pte.Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later
5
6// Astar is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// Astar is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with Astar. If not, see <http://www.gnu.org/licenses/>.
18
19use crate::{AccountId, AssetId};
20
21use fp_evm::AccountProvider;
22use frame_support::{
23    ensure,
24    traits::{
25        fungible::{Balanced, Credit},
26        tokens::{fungible::Inspect, imbalance::OnUnbalanced},
27    },
28};
29use pallet_evm::{AddressMapping, HashedAddressMapping, OnChargeEVMTransaction};
30use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
31use scale_info::TypeInfo;
32pub use sp_core::{Hasher, H160, H256, U256};
33use sp_runtime::traits::UniqueSaturatedInto;
34use sp_std::marker::PhantomData;
35
36use pallet_assets::AssetsCallback;
37use pallet_evm_precompile_assets_erc20::AddressToAssetId;
38
39pub type EvmAddress = H160;
40
41/// Revert opt code. It's inserted at the precompile addresses, to make them functional in EVM.
42pub const EVM_REVERT_CODE: &[u8] = &[0x60, 0x00, 0x60, 0x00, 0xfd];
43
44/// Handler for automatic revert code registration.
45///
46/// When an asset is created, it automatically becomes available to the EVM via an `ERC20-like` interface.
47/// In order for the precompile to work, dedicated asset address needs to have the revert code registered, otherwise the call will fail.
48///
49/// It is important to note that if the dedicated asset EVM address is already taken, asset creation should fail.
50/// After asset has been destroyed, it is also safe to remove the revert code and free the address for future usage.
51pub struct EvmRevertCodeHandler<A, R>(PhantomData<(A, R)>);
52impl<A, R> AssetsCallback<AssetId, AccountId> for EvmRevertCodeHandler<A, R>
53where
54    A: AddressToAssetId<AssetId>,
55    R: pallet_evm::Config,
56{
57    fn created(id: &AssetId, _: &AccountId) -> Result<(), ()> {
58        let address = A::asset_id_to_address(*id);
59        // In case of collision, we need to cancel the asset creation.
60        ensure!(!pallet_evm::AccountCodes::<R>::contains_key(&address), ());
61        pallet_evm::AccountCodes::<R>::insert(address, EVM_REVERT_CODE.to_vec());
62        Ok(())
63    }
64
65    fn destroyed(id: &AssetId) -> Result<(), ()> {
66        let address = A::asset_id_to_address(*id);
67        pallet_evm::AccountCodes::<R>::remove(address);
68        Ok(())
69    }
70}
71
72/// Mapping between Native and EVM Addresses
73pub trait UnifiedAddressMapper<AccountId> {
74    /// Gets the account id associated with given evm address, if mapped else None.
75    fn to_account_id(evm_address: &EvmAddress) -> Option<AccountId>;
76
77    /// Gets the account id associated with given evm address.
78    /// If no mapping exists, then return the default evm address.
79    /// Returns `UnifiedAddress` enum which wraps the inner account id
80    fn to_account_id_or_default(evm_address: &EvmAddress) -> UnifiedAddress<AccountId> {
81        Self::to_account_id(evm_address).map_or_else(
82            // fallback to default account_id
83            || UnifiedAddress::Default(Self::to_default_account_id(evm_address)),
84            |a| UnifiedAddress::Mapped(a),
85        )
86    }
87    /// Gets the default account id which is associated with given evm address.
88    fn to_default_account_id(evm_address: &EvmAddress) -> AccountId;
89
90    /// Gets the evm address associated with given account id, if mapped else None.
91    fn to_h160(account_id: &AccountId) -> Option<EvmAddress>;
92
93    /// Gets the evm address associated with given account id.
94    /// If no mapping exists, then return the default account id.
95    /// Returns `UnifiedAddress` enum which wraps the inner evm address
96    fn to_h160_or_default(account_id: &AccountId) -> UnifiedAddress<H160> {
97        Self::to_h160(account_id).map_or_else(
98            // fallback to default account_id
99            || UnifiedAddress::Default(Self::to_default_h160(account_id)),
100            |a| UnifiedAddress::Mapped(a),
101        )
102    }
103
104    /// Gets the default evm address which is associated with given account id.
105    fn to_default_h160(account_id: &AccountId) -> EvmAddress;
106}
107
108/// Mappings derieved from hashing the original address
109pub struct HashedDefaultMappings<H>(PhantomData<H>);
110impl<H: Hasher<Out = H256>> UnifiedAddressMapper<AccountId> for HashedDefaultMappings<H> {
111    fn to_default_account_id(evm_address: &EvmAddress) -> AccountId {
112        HashedAddressMapping::<H>::into_account_id(*evm_address)
113    }
114
115    fn to_default_h160(account_id: &AccountId) -> EvmAddress {
116        let payload = (b"evm:", account_id);
117        H160::from_slice(&payload.using_encoded(H::hash)[0..20])
118    }
119
120    fn to_account_id(_: &EvmAddress) -> Option<AccountId> {
121        None
122    }
123
124    fn to_h160(_: &AccountId) -> Option<EvmAddress> {
125        None
126    }
127}
128
129#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)]
130pub enum UnifiedAddress<Address> {
131    /// The address fetched from the mappings and the account
132    /// is unified
133    #[codec(index = 0)]
134    Mapped(Address),
135    /// The default address associated with account as there
136    /// is no mapping found and accounts are not unified
137    #[codec(index = 1)]
138    Default(Address),
139}
140
141impl<Address> UnifiedAddress<Address> {
142    /// Get the underlying address
143    pub fn into_address(self) -> Address {
144        match self {
145            Self::Default(a) => a,
146            Self::Mapped(a) => a,
147        }
148    }
149}
150
151/// Wrapper around the `EvmFungibleAdapter` from the `pallet-evm`.
152///
153/// While it provides most of the functionality we need,
154/// it doesn't allow the tip to be deposited into an arbitrary account.
155/// This adapter allows us to do that.
156///
157/// Two separate `OnUnbalanced` handers are used:
158/// - `UOF` for the fee
159/// - `OUT` for the tip
160pub struct EVMFungibleAdapterWrapper<F, FeeHandler, TipHandler>(
161    core::marker::PhantomData<(F, FeeHandler, TipHandler)>,
162);
163impl<T, F, FeeHandler, TipHandler> OnChargeEVMTransaction<T>
164    for EVMFungibleAdapterWrapper<F, FeeHandler, TipHandler>
165where
166    T: pallet_evm::Config,
167    F: Balanced<<T::AccountProvider as AccountProvider>::AccountId>,
168    FeeHandler: OnUnbalanced<Credit<<T::AccountProvider as AccountProvider>::AccountId, F>>,
169    TipHandler: OnUnbalanced<Credit<<T::AccountProvider as AccountProvider>::AccountId, F>>,
170    U256: UniqueSaturatedInto<
171        <F as Inspect<<T::AccountProvider as AccountProvider>::AccountId>>::Balance,
172    >,
173{
174    // Kept type as Option to satisfy bound of Default
175    type LiquidityInfo = Option<Credit<<T::AccountProvider as AccountProvider>::AccountId, F>>;
176
177    fn withdraw_fee(who: &H160, fee: U256) -> Result<Self::LiquidityInfo, pallet_evm::Error<T>> {
178        pallet_evm::EVMFungibleAdapter::<F, FeeHandler>::withdraw_fee(who, fee)
179    }
180
181    fn can_withdraw(who: &H160, amount: U256) -> Result<(), pallet_evm::Error<T>> {
182        pallet_evm::EVMFungibleAdapter::<F, FeeHandler>::can_withdraw(who, amount)
183    }
184
185    fn correct_and_deposit_fee(
186        who: &H160,
187        corrected_fee: U256,
188        base_fee: U256,
189        already_withdrawn: Self::LiquidityInfo,
190    ) -> Self::LiquidityInfo {
191        <pallet_evm::EVMFungibleAdapter::<F, FeeHandler> as OnChargeEVMTransaction<T>>::correct_and_deposit_fee(
192            who,
193            corrected_fee,
194            base_fee,
195            already_withdrawn,
196        )
197    }
198
199    fn pay_priority_fee(tip: Self::LiquidityInfo) {
200        if let Some(tip) = tip {
201            TipHandler::on_unbalanceds(Some(tip).into_iter());
202        }
203    }
204}