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, 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 } => {
89 if transaction.action == TransactionAction::Create {
90 return (true, call_count);
91 }
92 }
93 pallet_ethereum::Call::transact {
94 transaction: EthereumTransaction::EIP2930(transaction),
95 ..
96 } => {
97 if transaction.action == TransactionAction::Create {
98 return (true, call_count);
99 }
100 }
101 pallet_ethereum::Call::transact {
102 transaction: EthereumTransaction::EIP1559(transaction),
103 ..
104 } => {
105 if transaction.action == TransactionAction::Create {
106 return (true, call_count);
107 }
108 }
109 _ => {}
111 }
112 }
113
114 if let Some(pallet_evm::Call::create { .. } | pallet_evm::Call::create2 { .. }) =
115 call.maybe_into_evm_call()
116 {
117 return (true, call_count);
118 }
119 }
120
121 (false, call_count)
122}
123
124#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
126pub struct CheckContractCreation<Runtime>(PhantomData<Runtime>);
127
128impl<Runtime> CheckContractCreation<Runtime> {
129 pub fn new() -> Self {
130 Self(PhantomData)
131 }
132}
133
134impl<Runtime> Default for CheckContractCreation<Runtime> {
135 fn default() -> Self {
136 Self::new()
137 }
138}
139
140impl<Runtime> CheckContractCreation<Runtime>
141where
142 Runtime: frame_system::Config<AccountId = EthereumAccountId>
143 + pallet_ethereum::Config
144 + pallet_evm::Config
145 + crate::Config
146 + scale_info::TypeInfo
147 + fmt::Debug
148 + Send
149 + Sync,
150 RuntimeCallFor<Runtime>:
151 MaybeIntoEthCall<Runtime> + MaybeIntoEvmCall<Runtime> + MaybeNestedCall<Runtime>,
152 Result<pallet_ethereum::RawOrigin, OriginFor<Runtime>>: From<OriginFor<Runtime>>,
153 <RuntimeCallFor<Runtime> as Dispatchable>::RuntimeOrigin:
154 AsSystemOriginSigner<AccountIdFor<Runtime>> + Clone,
155{
156 pub(crate) fn do_validate_unsigned(
157 call: &RuntimeCallFor<Runtime>,
158 ) -> Result<(ValidTransaction, u32), TransactionValidityError> {
159 let (is_allowed, call_count) = is_create_unsigned_contract_allowed::<Runtime>(call);
160 if !is_allowed {
161 Err(InvalidTransaction::Custom(ERR_CONTRACT_CREATION_NOT_ALLOWED).into())
162 } else {
163 Ok((ValidTransaction::default(), call_count))
164 }
165 }
166
167 pub(crate) fn do_validate_signed(
168 origin: &OriginFor<Runtime>,
169 call: &RuntimeCallFor<Runtime>,
170 ) -> Result<(ValidTransaction, u32), TransactionValidityError> {
171 let Some(who) = origin.as_system_origin_signer() else {
172 return Self::do_validate_unsigned(call);
174 };
175
176 let (is_allowed, call_count) = is_create_contract_allowed::<Runtime>(call, who);
178 if !is_allowed {
179 Err(InvalidTransaction::Custom(ERR_CONTRACT_CREATION_NOT_ALLOWED).into())
180 } else {
181 Ok((ValidTransaction::default(), call_count))
182 }
183 }
184
185 pub fn get_weights(n: u32) -> Weight {
186 SubstrateWeightInfo::<Runtime>::evm_contract_check_multiple(n)
187 .max(SubstrateWeightInfo::<Runtime>::evm_contract_check_nested(n))
188 }
189}
190
191#[derive(RuntimeDebugNoBound)]
193pub enum Pre {
194 Refund(Weight),
196}
197
198#[derive(RuntimeDebugNoBound)]
200pub enum Val {
201 PartialRefund(u32),
203}
204
205impl<Runtime> TransactionExtension<RuntimeCallFor<Runtime>> for CheckContractCreation<Runtime>
209where
210 Runtime: frame_system::Config<AccountId = EthereumAccountId>
211 + pallet_ethereum::Config
212 + pallet_evm::Config
213 + crate::Config
214 + scale_info::TypeInfo
215 + fmt::Debug
216 + Send
217 + Sync,
218 RuntimeCallFor<Runtime>:
219 MaybeIntoEthCall<Runtime> + MaybeIntoEvmCall<Runtime> + MaybeNestedCall<Runtime>,
220 Result<pallet_ethereum::RawOrigin, OriginFor<Runtime>>: From<OriginFor<Runtime>>,
221 <RuntimeCallFor<Runtime> as Dispatchable>::RuntimeOrigin:
222 AsSystemOriginSigner<AccountIdFor<Runtime>> + Clone,
223 for<'a> &'a mut PostDispatchInfoOf<RuntimeCallFor<Runtime>>: Into<&'a mut PostDispatchInfo>,
224{
225 const IDENTIFIER: &'static str = "CheckContractCreation";
226 type Implicit = ();
227 type Val = Val;
228 type Pre = Pre;
229
230 fn weight(&self, _: &RuntimeCallFor<Runtime>) -> Weight {
231 Self::get_weights(MAXIMUM_NUMBER_OF_CALLS)
232 }
233
234 fn validate(
235 &self,
236 origin: OriginFor<Runtime>,
237 call: &RuntimeCallFor<Runtime>,
238 _info: &DispatchInfoOf<RuntimeCallFor<Runtime>>,
239 _len: usize,
240 _self_implicit: Self::Implicit,
241 _inherited_implication: &impl Encode,
242 _source: TransactionSource,
243 ) -> ValidateResult<Self::Val, RuntimeCallFor<Runtime>> {
244 let (validity, val) = if origin.as_system_origin_signer().is_some() {
245 let (valid, call_count) = Self::do_validate_signed(&origin, call)?;
246 (valid, Val::PartialRefund(call_count))
247 } else {
248 let (valid, call_count) = Self::do_validate_unsigned(call)?;
249 (valid, Val::PartialRefund(call_count))
250 };
251
252 Ok((validity, val, origin))
253 }
254
255 fn prepare(
256 self,
257 val: Self::Val,
258 _origin: &DispatchOriginOf<RuntimeCallFor<Runtime>>,
259 _call: &RuntimeCallFor<Runtime>,
260 _info: &DispatchInfoOf<RuntimeCallFor<Runtime>>,
261 _len: usize,
262 ) -> Result<Self::Pre, TransactionValidityError> {
263 let pre_dispatch_weights = Self::get_weights(MAXIMUM_NUMBER_OF_CALLS);
264 match val {
265 Val::PartialRefund(calls) => {
269 let actual_weights = Self::get_weights(calls);
270 Ok(Pre::Refund(
271 pre_dispatch_weights.saturating_sub(actual_weights),
272 ))
273 }
274 }
275 }
276
277 fn post_dispatch_details(
278 pre: Self::Pre,
279 _info: &DispatchInfoOf<RuntimeCallFor<Runtime>>,
280 _post_info: &PostDispatchInfoOf<RuntimeCallFor<Runtime>>,
281 _len: usize,
282 _result: &DispatchResult,
283 ) -> Result<Weight, TransactionValidityError> {
284 let Pre::Refund(weight) = pre;
285 Ok(weight)
286 }
287
288 fn bare_validate(
289 call: &RuntimeCallFor<Runtime>,
290 _info: &DispatchInfoOf<RuntimeCallFor<Runtime>>,
291 _len: usize,
292 ) -> TransactionValidity {
293 Self::do_validate_unsigned(call).map(|(validity, _call_count)| validity)
294 }
295
296 fn bare_validate_and_prepare(
297 call: &RuntimeCallFor<Runtime>,
298 _info: &DispatchInfoOf<RuntimeCallFor<Runtime>>,
299 _len: usize,
300 ) -> Result<(), TransactionValidityError> {
301 Self::do_validate_unsigned(call)?;
302 Ok(())
303 }
304
305 fn bare_post_dispatch(
307 _info: &DispatchInfoOf<RuntimeCallFor<Runtime>>,
308 post_info: &mut PostDispatchInfoOf<RuntimeCallFor<Runtime>>,
309 _len: usize,
310 _result: &DispatchResult,
311 ) -> Result<(), TransactionValidityError> {
312 let pre_dispatch_weights = Self::get_weights(MAXIMUM_NUMBER_OF_CALLS);
313 let actual_weights = Self::get_weights(1);
316
317 let unspent = pre_dispatch_weights.saturating_sub(actual_weights);
319
320 let post_info = Into::<&mut PostDispatchInfo>::into(post_info);
322 if let Some(actual_weight) = post_info.actual_weight
323 && actual_weight.ref_time() >= pre_dispatch_weights.ref_time()
324 {
325 post_info.refund(unspent);
326 }
327
328 Ok(())
329 }
330}