1use crate::traits::{AccountIdFor, MaybeIntoEthCall, MaybeIntoEvmCall};
4use crate::weights::pallet_evm_tracker::WeightInfo as SubstrateWeightInfo;
5use crate::{MAXIMUM_NUMBER_OF_CALLS, WeightInfo};
6use domain_runtime_primitives::{ERR_CONTRACT_CREATION_NOT_ALLOWED, EthereumAccountId};
7use frame_support::RuntimeDebugNoBound;
8use frame_support::dispatch::PostDispatchInfo;
9use frame_support::pallet_prelude::{DispatchResult, PhantomData, TypeInfo};
10use frame_system::pallet_prelude::{OriginFor, RuntimeCallFor};
11use pallet_ethereum::{Transaction as EthereumTransaction, TransactionAction};
12use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode};
13use scale_info::prelude::fmt;
14use sp_runtime::traits::{
15 AsSystemOriginSigner, DispatchInfoOf, DispatchOriginOf, Dispatchable, PostDispatchInfoOf,
16 RefundWeight, TransactionExtension, ValidateResult,
17};
18use sp_runtime::transaction_validity::{
19 InvalidTransaction, TransactionSource, TransactionValidity, TransactionValidityError,
20 ValidTransaction,
21};
22use sp_weights::Weight;
23use subspace_runtime_primitives::utility::{MaybeNestedCall, nested_call_iter};
24
25pub fn is_create_contract_allowed<Runtime>(
29 call: &RuntimeCallFor<Runtime>,
30 signer: &EthereumAccountId,
31) -> (bool, u32)
32where
33 Runtime: frame_system::Config<AccountId = EthereumAccountId>
34 + pallet_ethereum::Config
35 + pallet_evm::Config
36 + crate::Config,
37 RuntimeCallFor<Runtime>:
38 MaybeIntoEthCall<Runtime> + MaybeIntoEvmCall<Runtime> + MaybeNestedCall<Runtime>,
39 Result<pallet_ethereum::RawOrigin, OriginFor<Runtime>>: From<OriginFor<Runtime>>,
40{
41 if crate::Pallet::<Runtime>::is_allowed_to_create_contracts(signer) {
44 return (true, 0);
45 }
46
47 let (is_create, call_count) = is_create_contract::<Runtime>(call);
48 (!is_create, call_count)
49}
50
51pub fn is_create_unsigned_contract_allowed<Runtime>(call: &RuntimeCallFor<Runtime>) -> (bool, u32)
55where
56 Runtime: frame_system::Config + pallet_ethereum::Config + pallet_evm::Config + crate::Config,
57 RuntimeCallFor<Runtime>:
58 MaybeIntoEthCall<Runtime> + MaybeIntoEvmCall<Runtime> + MaybeNestedCall<Runtime>,
59 Result<pallet_ethereum::RawOrigin, OriginFor<Runtime>>: From<OriginFor<Runtime>>,
60{
61 if crate::Pallet::<Runtime>::is_allowed_to_create_unsigned_contracts() {
64 return (true, 0);
65 }
66
67 let (is_create, call_count) = is_create_contract::<Runtime>(call);
68 (!is_create, call_count)
69}
70
71pub fn is_create_contract<Runtime>(call: &RuntimeCallFor<Runtime>) -> (bool, u32)
73where
74 Runtime: frame_system::Config + pallet_ethereum::Config + pallet_evm::Config,
75 RuntimeCallFor<Runtime>:
76 MaybeIntoEthCall<Runtime> + MaybeIntoEvmCall<Runtime> + MaybeNestedCall<Runtime>,
77 Result<pallet_ethereum::RawOrigin, OriginFor<Runtime>>: From<OriginFor<Runtime>>,
78{
79 let mut call_count = 0;
80 for call in nested_call_iter::<Runtime>(call) {
81 call_count += 1;
82
83 if let Some(call) = call.maybe_into_eth_call() {
84 match call {
85 pallet_ethereum::Call::transact {
86 transaction: EthereumTransaction::Legacy(transaction),
87 ..
88 } if transaction.action == TransactionAction::Create => {
89 return (true, call_count);
90 }
91 pallet_ethereum::Call::transact {
92 transaction: EthereumTransaction::EIP2930(transaction),
93 ..
94 } if transaction.action == TransactionAction::Create => {
95 return (true, call_count);
96 }
97 pallet_ethereum::Call::transact {
98 transaction: EthereumTransaction::EIP1559(transaction),
99 ..
100 } if transaction.action == TransactionAction::Create => {
101 return (true, call_count);
102 }
103 _ => {}
105 }
106 }
107
108 if let Some(pallet_evm::Call::create { .. } | pallet_evm::Call::create2 { .. }) =
109 call.maybe_into_evm_call()
110 {
111 return (true, call_count);
112 }
113 }
114
115 (false, call_count)
116}
117
118#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo, DecodeWithMemTracking)]
120pub struct CheckContractCreation<Runtime>(PhantomData<Runtime>);
121
122impl<Runtime> CheckContractCreation<Runtime> {
123 pub fn new() -> Self {
124 Self(PhantomData)
125 }
126}
127
128impl<Runtime> Default for CheckContractCreation<Runtime> {
129 fn default() -> Self {
130 Self::new()
131 }
132}
133
134impl<Runtime> CheckContractCreation<Runtime>
135where
136 Runtime: frame_system::Config<AccountId = EthereumAccountId>
137 + pallet_ethereum::Config
138 + pallet_evm::Config
139 + crate::Config
140 + scale_info::TypeInfo
141 + fmt::Debug
142 + Send
143 + Sync,
144 RuntimeCallFor<Runtime>:
145 MaybeIntoEthCall<Runtime> + MaybeIntoEvmCall<Runtime> + MaybeNestedCall<Runtime>,
146 Result<pallet_ethereum::RawOrigin, OriginFor<Runtime>>: From<OriginFor<Runtime>>,
147 <RuntimeCallFor<Runtime> as Dispatchable>::RuntimeOrigin:
148 AsSystemOriginSigner<AccountIdFor<Runtime>> + Clone,
149{
150 pub(crate) fn do_validate_unsigned(
151 call: &RuntimeCallFor<Runtime>,
152 ) -> Result<(ValidTransaction, u32), TransactionValidityError> {
153 let (is_allowed, call_count) = is_create_unsigned_contract_allowed::<Runtime>(call);
154 if !is_allowed {
155 Err(InvalidTransaction::Custom(ERR_CONTRACT_CREATION_NOT_ALLOWED).into())
156 } else {
157 Ok((ValidTransaction::default(), call_count))
158 }
159 }
160
161 pub(crate) fn do_validate_signed(
162 origin: &OriginFor<Runtime>,
163 call: &RuntimeCallFor<Runtime>,
164 ) -> Result<(ValidTransaction, u32), TransactionValidityError> {
165 let Some(who) = origin.as_system_origin_signer() else {
166 return Self::do_validate_unsigned(call);
168 };
169
170 let (is_allowed, call_count) = is_create_contract_allowed::<Runtime>(call, who);
172 if !is_allowed {
173 Err(InvalidTransaction::Custom(ERR_CONTRACT_CREATION_NOT_ALLOWED).into())
174 } else {
175 Ok((ValidTransaction::default(), call_count))
176 }
177 }
178
179 pub fn get_weights(n: u32) -> Weight {
180 SubstrateWeightInfo::<Runtime>::evm_contract_check_multiple(n)
181 .max(SubstrateWeightInfo::<Runtime>::evm_contract_check_nested(n))
182 }
183}
184
185#[derive(RuntimeDebugNoBound)]
187pub enum Pre {
188 Refund(Weight),
190}
191
192#[derive(RuntimeDebugNoBound)]
194pub enum Val {
195 PartialRefund(u32),
197}
198
199impl<Runtime> TransactionExtension<RuntimeCallFor<Runtime>> for CheckContractCreation<Runtime>
203where
204 Runtime: frame_system::Config<AccountId = EthereumAccountId>
205 + pallet_ethereum::Config
206 + pallet_evm::Config
207 + crate::Config
208 + scale_info::TypeInfo
209 + fmt::Debug
210 + Send
211 + Sync,
212 RuntimeCallFor<Runtime>:
213 MaybeIntoEthCall<Runtime> + MaybeIntoEvmCall<Runtime> + MaybeNestedCall<Runtime>,
214 Result<pallet_ethereum::RawOrigin, OriginFor<Runtime>>: From<OriginFor<Runtime>>,
215 <RuntimeCallFor<Runtime> as Dispatchable>::RuntimeOrigin:
216 AsSystemOriginSigner<AccountIdFor<Runtime>> + Clone,
217 for<'a> &'a mut PostDispatchInfoOf<RuntimeCallFor<Runtime>>: Into<&'a mut PostDispatchInfo>,
218{
219 const IDENTIFIER: &'static str = "CheckContractCreation";
220 type Implicit = ();
221 type Val = Val;
222 type Pre = Pre;
223
224 fn weight(&self, _: &RuntimeCallFor<Runtime>) -> Weight {
225 Self::get_weights(MAXIMUM_NUMBER_OF_CALLS)
226 }
227
228 fn validate(
229 &self,
230 origin: OriginFor<Runtime>,
231 call: &RuntimeCallFor<Runtime>,
232 _info: &DispatchInfoOf<RuntimeCallFor<Runtime>>,
233 _len: usize,
234 _self_implicit: Self::Implicit,
235 _inherited_implication: &impl Encode,
236 _source: TransactionSource,
237 ) -> ValidateResult<Self::Val, RuntimeCallFor<Runtime>> {
238 let (validity, val) = if origin.as_system_origin_signer().is_some() {
239 let (valid, call_count) = Self::do_validate_signed(&origin, call)?;
240 (valid, Val::PartialRefund(call_count))
241 } else {
242 let (valid, call_count) = Self::do_validate_unsigned(call)?;
243 (valid, Val::PartialRefund(call_count))
244 };
245
246 Ok((validity, val, origin))
247 }
248
249 fn prepare(
250 self,
251 val: Self::Val,
252 _origin: &DispatchOriginOf<RuntimeCallFor<Runtime>>,
253 _call: &RuntimeCallFor<Runtime>,
254 _info: &DispatchInfoOf<RuntimeCallFor<Runtime>>,
255 _len: usize,
256 ) -> Result<Self::Pre, TransactionValidityError> {
257 let pre_dispatch_weights = Self::get_weights(MAXIMUM_NUMBER_OF_CALLS);
258 match val {
259 Val::PartialRefund(calls) => {
263 let actual_weights = Self::get_weights(calls);
264 Ok(Pre::Refund(
265 pre_dispatch_weights.saturating_sub(actual_weights),
266 ))
267 }
268 }
269 }
270
271 fn post_dispatch_details(
272 pre: Self::Pre,
273 _info: &DispatchInfoOf<RuntimeCallFor<Runtime>>,
274 _post_info: &PostDispatchInfoOf<RuntimeCallFor<Runtime>>,
275 _len: usize,
276 _result: &DispatchResult,
277 ) -> Result<Weight, TransactionValidityError> {
278 let Pre::Refund(weight) = pre;
279 Ok(weight)
280 }
281
282 fn bare_validate(
283 call: &RuntimeCallFor<Runtime>,
284 _info: &DispatchInfoOf<RuntimeCallFor<Runtime>>,
285 _len: usize,
286 ) -> TransactionValidity {
287 Self::do_validate_unsigned(call).map(|(validity, _call_count)| validity)
288 }
289
290 fn bare_validate_and_prepare(
291 call: &RuntimeCallFor<Runtime>,
292 _info: &DispatchInfoOf<RuntimeCallFor<Runtime>>,
293 _len: usize,
294 ) -> Result<(), TransactionValidityError> {
295 Self::do_validate_unsigned(call)?;
296 Ok(())
297 }
298
299 fn bare_post_dispatch(
301 _info: &DispatchInfoOf<RuntimeCallFor<Runtime>>,
302 post_info: &mut PostDispatchInfoOf<RuntimeCallFor<Runtime>>,
303 _len: usize,
304 _result: &DispatchResult,
305 ) -> Result<(), TransactionValidityError> {
306 let pre_dispatch_weights = Self::get_weights(MAXIMUM_NUMBER_OF_CALLS);
307 let actual_weights = Self::get_weights(1);
310
311 let unspent = pre_dispatch_weights.saturating_sub(actual_weights);
313
314 let post_info = Into::<&mut PostDispatchInfo>::into(post_info);
316 if let Some(actual_weight) = post_info.actual_weight
317 && actual_weight.ref_time() >= pre_dispatch_weights.ref_time()
318 {
319 post_info.refund(unspent);
320 }
321
322 Ok(())
323 }
324}