pallet_evm_tracker/
create_contract.rs

1//! Contract creation allow list implementations
2
3use crate::traits::{AccountIdFor, MaybeIntoEthCall, MaybeIntoEvmCall};
4use domain_runtime_primitives::{EthereumAccountId, ERR_CONTRACT_CREATION_NOT_ALLOWED};
5use frame_support::pallet_prelude::{PhantomData, TypeInfo};
6use frame_system::pallet_prelude::{OriginFor, RuntimeCallFor};
7use pallet_ethereum::{Transaction as EthereumTransaction, TransactionAction};
8use parity_scale_codec::{Decode, Encode};
9use scale_info::prelude::fmt;
10use sp_core::Get;
11use sp_runtime::impl_tx_ext_default;
12use sp_runtime::traits::{
13    AsSystemOriginSigner, DispatchInfoOf, Dispatchable, TransactionExtension, ValidateResult,
14};
15use sp_runtime::transaction_validity::{
16    InvalidTransaction, TransactionSource, TransactionValidity, TransactionValidityError,
17    ValidTransaction,
18};
19use sp_weights::Weight;
20use subspace_runtime_primitives::utility::{nested_call_iter, MaybeNestedCall};
21
22/// Rejects contracts that can't be created under the current allow list.
23/// Returns false if the call is a contract call, and the account is *not* allowed to call it.
24/// Otherwise, returns true.
25pub fn is_create_contract_allowed<Runtime>(
26    call: &RuntimeCallFor<Runtime>,
27    signer: &EthereumAccountId,
28) -> bool
29where
30    Runtime: frame_system::Config<AccountId = EthereumAccountId>
31        + pallet_ethereum::Config
32        + pallet_evm::Config
33        + crate::Config,
34    RuntimeCallFor<Runtime>:
35        MaybeIntoEthCall<Runtime> + MaybeIntoEvmCall<Runtime> + MaybeNestedCall<Runtime>,
36    Result<pallet_ethereum::RawOrigin, OriginFor<Runtime>>: From<OriginFor<Runtime>>,
37{
38    // If the account is allowed to create contracts, or it's not a contract call, return true.
39    // Only enters allocating code if this account can't create contracts.
40    crate::Pallet::<Runtime>::is_allowed_to_create_contracts(signer)
41        || !is_create_contract::<Runtime>(call)
42}
43
44/// If anyone is allowed to create contracts, allows contracts. Otherwise, rejects contracts.
45/// Returns false if the call is a contract call, and there is a specific (possibly empty) allow
46/// list. Otherwise, returns true.
47pub fn is_create_unsigned_contract_allowed<Runtime>(call: &RuntimeCallFor<Runtime>) -> bool
48where
49    Runtime: frame_system::Config + pallet_ethereum::Config + pallet_evm::Config + crate::Config,
50    RuntimeCallFor<Runtime>:
51        MaybeIntoEthCall<Runtime> + MaybeIntoEvmCall<Runtime> + MaybeNestedCall<Runtime>,
52    Result<pallet_ethereum::RawOrigin, OriginFor<Runtime>>: From<OriginFor<Runtime>>,
53{
54    // If any account is allowed to create contracts, or it's not a contract call, return true.
55    // Only enters allocating code if there is a contract creation filter.
56    crate::Pallet::<Runtime>::is_allowed_to_create_unsigned_contracts()
57        || !is_create_contract::<Runtime>(call)
58}
59
60/// Returns true if the call is a contract creation call.
61pub fn is_create_contract<Runtime>(call: &RuntimeCallFor<Runtime>) -> bool
62where
63    Runtime: frame_system::Config + pallet_ethereum::Config + pallet_evm::Config,
64    RuntimeCallFor<Runtime>:
65        MaybeIntoEthCall<Runtime> + MaybeIntoEvmCall<Runtime> + MaybeNestedCall<Runtime>,
66    Result<pallet_ethereum::RawOrigin, OriginFor<Runtime>>: From<OriginFor<Runtime>>,
67{
68    for call in nested_call_iter::<Runtime>(call) {
69        if let Some(call) = call.maybe_into_eth_call() {
70            match call {
71                pallet_ethereum::Call::transact {
72                    transaction: EthereumTransaction::Legacy(transaction),
73                    ..
74                } => {
75                    if transaction.action == TransactionAction::Create {
76                        return true;
77                    }
78                }
79                pallet_ethereum::Call::transact {
80                    transaction: EthereumTransaction::EIP2930(transaction),
81                    ..
82                } => {
83                    if transaction.action == TransactionAction::Create {
84                        return true;
85                    }
86                }
87                pallet_ethereum::Call::transact {
88                    transaction: EthereumTransaction::EIP1559(transaction),
89                    ..
90                } => {
91                    if transaction.action == TransactionAction::Create {
92                        return true;
93                    }
94                }
95                // Inconclusive, other calls might create contracts.
96                _ => {}
97            }
98        }
99
100        if let Some(pallet_evm::Call::create { .. } | pallet_evm::Call::create2 { .. }) =
101            call.maybe_into_evm_call()
102        {
103            return true;
104        }
105    }
106
107    false
108}
109
110/// Reject contract creation, unless the account is in the current evm contract allow list.
111#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
112pub struct CheckContractCreation<Runtime>(PhantomData<Runtime>);
113
114impl<Runtime> CheckContractCreation<Runtime> {
115    pub fn new() -> Self {
116        Self(PhantomData)
117    }
118}
119
120impl<Runtime> Default for CheckContractCreation<Runtime> {
121    fn default() -> Self {
122        Self::new()
123    }
124}
125
126impl<Runtime> CheckContractCreation<Runtime>
127where
128    Runtime: frame_system::Config<AccountId = EthereumAccountId>
129        + pallet_ethereum::Config
130        + pallet_evm::Config
131        + crate::Config
132        + scale_info::TypeInfo
133        + fmt::Debug
134        + Send
135        + Sync,
136    RuntimeCallFor<Runtime>:
137        MaybeIntoEthCall<Runtime> + MaybeIntoEvmCall<Runtime> + MaybeNestedCall<Runtime>,
138    Result<pallet_ethereum::RawOrigin, OriginFor<Runtime>>: From<OriginFor<Runtime>>,
139    <RuntimeCallFor<Runtime> as Dispatchable>::RuntimeOrigin:
140        AsSystemOriginSigner<AccountIdFor<Runtime>> + Clone,
141{
142    fn do_validate_unsigned(call: &RuntimeCallFor<Runtime>) -> TransactionValidity {
143        if !is_create_unsigned_contract_allowed::<Runtime>(call) {
144            Err(InvalidTransaction::Custom(ERR_CONTRACT_CREATION_NOT_ALLOWED).into())
145        } else {
146            Ok(ValidTransaction::default())
147        }
148    }
149
150    fn do_validate(
151        origin: &OriginFor<Runtime>,
152        call: &RuntimeCallFor<Runtime>,
153    ) -> TransactionValidity {
154        let Some(who) = origin.as_system_origin_signer() else {
155            // Reject unsigned contract creation unless anyone is allowed to create them.
156            return Self::do_validate_unsigned(call);
157        };
158        // Reject contract creation unless the account is in the allow list.
159        if !is_create_contract_allowed::<Runtime>(call, who) {
160            Err(InvalidTransaction::Custom(ERR_CONTRACT_CREATION_NOT_ALLOWED).into())
161        } else {
162            Ok(ValidTransaction::default())
163        }
164    }
165}
166
167// Unsigned calls can't create contracts. Only pallet-evm and pallet-ethereum can create contracts.
168// For pallet-evm all contracts are signed extrinsics, for pallet-ethereum there is only one
169// extrinsic that is self-contained.
170impl<Runtime> TransactionExtension<RuntimeCallFor<Runtime>> for CheckContractCreation<Runtime>
171where
172    Runtime: frame_system::Config<AccountId = EthereumAccountId>
173        + pallet_ethereum::Config
174        + pallet_evm::Config
175        + crate::Config
176        + scale_info::TypeInfo
177        + fmt::Debug
178        + Send
179        + Sync,
180    RuntimeCallFor<Runtime>:
181        MaybeIntoEthCall<Runtime> + MaybeIntoEvmCall<Runtime> + MaybeNestedCall<Runtime>,
182    Result<pallet_ethereum::RawOrigin, OriginFor<Runtime>>: From<OriginFor<Runtime>>,
183    <RuntimeCallFor<Runtime> as Dispatchable>::RuntimeOrigin:
184        AsSystemOriginSigner<AccountIdFor<Runtime>> + Clone,
185{
186    const IDENTIFIER: &'static str = "CheckContractCreation";
187    type Implicit = ();
188    type Val = ();
189    type Pre = ();
190
191    // TODO: calculate proper weight for this extension
192    //  Currently only accounts for storage read
193    fn weight(&self, _: &RuntimeCallFor<Runtime>) -> Weight {
194        // there will always be one storage read for this call
195        <Runtime as frame_system::Config>::DbWeight::get().reads(1)
196    }
197
198    fn validate(
199        &self,
200        origin: OriginFor<Runtime>,
201        call: &RuntimeCallFor<Runtime>,
202        _info: &DispatchInfoOf<RuntimeCallFor<Runtime>>,
203        _len: usize,
204        _self_implicit: Self::Implicit,
205        _inherited_implication: &impl Encode,
206        _source: TransactionSource,
207    ) -> ValidateResult<Self::Val, RuntimeCallFor<Runtime>> {
208        let validity = Self::do_validate(&origin, call)?;
209        Ok((validity, (), origin))
210    }
211
212    impl_tx_ext_default!(RuntimeCallFor<Runtime>; prepare);
213
214    fn bare_validate(
215        call: &RuntimeCallFor<Runtime>,
216        _info: &DispatchInfoOf<RuntimeCallFor<Runtime>>,
217        _len: usize,
218    ) -> TransactionValidity {
219        Self::do_validate_unsigned(call)
220    }
221
222    fn bare_validate_and_prepare(
223        call: &RuntimeCallFor<Runtime>,
224        _info: &DispatchInfoOf<RuntimeCallFor<Runtime>>,
225        _len: usize,
226    ) -> Result<(), TransactionValidityError> {
227        Self::do_validate_unsigned(call)?;
228        Ok(())
229    }
230}