Skip to main content

pallet_evm_tracker/
lib.rs

1// Copyright (C) 2023 Subspace Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// 	http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! Pallet EVM Account nonce tracker
17
18#![cfg_attr(not(feature = "std"), no_std)]
19// TODO: Remove once FRAME macros stop generating `.clone()` calls on `Copy` types with user spans
20#![expect(clippy::clone_on_copy, reason = "Comes from Substrate")]
21
22#[cfg(not(feature = "std"))]
23extern crate alloc;
24
25#[cfg(feature = "runtime-benchmarks")]
26mod benchmarking;
27
28pub mod check_nonce;
29pub mod create_contract;
30pub mod fees;
31pub mod traits;
32pub mod weights;
33
34pub use check_nonce::CheckNonce;
35use domain_runtime_primitives::EthereumAccountId;
36pub use pallet::*;
37use sp_core::U256;
38use sp_domains::PermissionedActionAllowedBy;
39use sp_weights::Weight;
40
41/// Maximum number of calls we benchmarked for.
42const MAXIMUM_NUMBER_OF_CALLS: u32 = 5_000;
43
44/// Weight functions needed for pallet_evm_tracker.
45pub trait WeightInfo {
46    fn evm_contract_check_multiple(c: u32) -> Weight;
47    fn evm_contract_check_nested(c: u32) -> Weight;
48}
49
50#[frame_support::pallet]
51mod pallet {
52    use domain_runtime_primitives::EthereumAccountId;
53    use frame_support::pallet_prelude::*;
54    use frame_system::pallet_prelude::*;
55    use sp_core::U256;
56    use sp_domains::PermissionedActionAllowedBy;
57    use sp_evm_tracker::{INHERENT_IDENTIFIER, InherentError, InherentType};
58
59    #[pallet::config]
60    pub trait Config: frame_system::Config {}
61
62    /// Storage to hold evm account nonces.
63    /// This is only used for pre_dispatch since EVM pre_dispatch does not
64    /// increment account nonce.
65    #[pallet::storage]
66    pub(super) type AccountNonce<T: Config> =
67        StorageMap<_, Identity, T::AccountId, U256, OptionQuery>;
68
69    /// Storage to hold EVM contract creation allow list accounts.
70    /// Unlike domain instantiation, no storage value means "anyone can create contracts".
71    ///
72    /// At genesis, this is set via DomainConfigParams, DomainRuntimeInfo::Evm, and
73    /// RawGenesis::set_evm_contract_creation_allowed_by().
74    // When this type name is changed, evm_contract_creation_allowed_by_storage_key() also needs to
75    // be updated.
76    #[pallet::storage]
77    pub(super) type ContractCreationAllowedBy<T: Config> = StorageValue<
78        _,
79        PermissionedActionAllowedBy<EthereumAccountId>,
80        ValueQuery,
81        DefaultToAnyone,
82    >;
83
84    /// Default value for ContractCreationAllowedBy if it is not set.
85    pub struct DefaultToAnyone;
86
87    impl Get<PermissionedActionAllowedBy<EthereumAccountId>> for DefaultToAnyone {
88        fn get() -> PermissionedActionAllowedBy<EthereumAccountId> {
89            PermissionedActionAllowedBy::Anyone
90        }
91    }
92
93    /// Pallet EVM account nonce tracker.
94    #[pallet::pallet]
95    #[pallet::without_storage_info]
96    pub struct Pallet<T>(_);
97
98    #[pallet::call]
99    impl<T: Config> Pallet<T> {
100        /// An inherent call to set ContractCreationAllowedBy.
101        #[pallet::call_index(0)]
102        #[pallet::weight((T::DbWeight::get().reads_writes(0, 1), DispatchClass::Mandatory))]
103        pub fn set_contract_creation_allowed_by(
104            origin: OriginFor<T>,
105            contract_creation_allowed_by: PermissionedActionAllowedBy<EthereumAccountId>,
106        ) -> DispatchResult {
107            ensure_none(origin)?;
108
109            // signer and is_private_evm_domain() were already checked by pallet-domains.
110
111            ContractCreationAllowedBy::<T>::put(contract_creation_allowed_by);
112
113            Ok(())
114        }
115    }
116
117    #[pallet::inherent]
118    impl<T: Config> ProvideInherent for Pallet<T> {
119        type Call = Call<T>;
120        type Error = InherentError;
121        const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER;
122
123        fn create_inherent(data: &InherentData) -> Option<Self::Call> {
124            let inherent_data = data
125                .get_data::<InherentType>(&INHERENT_IDENTIFIER)
126                .expect("EVM tracker inherent data not correctly encoded")
127                .expect("EVM tracker inherent data must be provided");
128
129            inherent_data
130                .maybe_call
131                .map(
132                    |contract_creation_allowed_by| Call::set_contract_creation_allowed_by {
133                        contract_creation_allowed_by,
134                    },
135                )
136        }
137
138        fn is_inherent_required(data: &InherentData) -> Result<Option<Self::Error>, Self::Error> {
139            let inherent_data = data
140                .get_data::<InherentType>(&INHERENT_IDENTIFIER)
141                .expect("EVM tracker inherent data not correctly encoded")
142                .expect("EVM tracker inherent data must be provided");
143
144            Ok(inherent_data
145                .maybe_call
146                .map(|_encoded_call| InherentError::MissingRuntimeCall))
147        }
148
149        fn check_inherent(call: &Self::Call, data: &InherentData) -> Result<(), Self::Error> {
150            let maybe_provided_call = Self::create_inherent(data);
151
152            if let Some(provided_call) = maybe_provided_call {
153                if Self::is_inherent(call) && call != &provided_call {
154                    return Err(InherentError::IncorrectRuntimeCall);
155                }
156            } else {
157                return Err(InherentError::MissingRuntimeCall);
158            }
159
160            Ok(())
161        }
162
163        fn is_inherent(call: &Self::Call) -> bool {
164            matches!(call, Call::set_contract_creation_allowed_by { .. })
165        }
166    }
167
168    #[pallet::hooks]
169    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
170        fn on_finalize(_now: BlockNumberFor<T>) {
171            // clear the nonce storage, since we would start with updated nonce
172            // during the pre_dispatch in next block
173            let _ = AccountNonce::<T>::clear(u32::MAX, None);
174        }
175    }
176}
177
178impl<T: Config> Pallet<T> {
179    /// Returns current nonce for the given account.
180    pub fn account_nonce(account: T::AccountId) -> Option<U256> {
181        AccountNonce::<T>::get(account)
182    }
183
184    /// Set nonce to the account.
185    pub fn set_account_nonce(account: T::AccountId, nonce: U256) {
186        AccountNonce::<T>::set(account, Some(nonce))
187    }
188
189    /// Returns true if the supplied account is allowed to create contracts.
190    pub fn is_allowed_to_create_contracts(signer: &EthereumAccountId) -> bool {
191        ContractCreationAllowedBy::<T>::get().is_allowed(signer)
192    }
193
194    /// Returns true if any account is allowed to create contracts.
195    pub fn is_allowed_to_create_unsigned_contracts() -> bool {
196        ContractCreationAllowedBy::<T>::get().is_anyone_allowed()
197    }
198
199    /// Returns the current contract creation allow list.
200    /// Mainly used in tests.
201    pub fn contract_creation_allowed_by() -> PermissionedActionAllowedBy<EthereumAccountId> {
202        ContractCreationAllowedBy::<T>::get()
203    }
204}