referrerpolicy=no-referrer-when-downgrade

pallet_revive/
primitives.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! A crate that hosts a common definitions that are relevant for the pallet-revive.
19
20use crate::{
21	BalanceOf, Config, H160, U256, deposit_payment::Funds, exec::MomentOf, mock::MockHandler,
22	storage::WriteOutcome, transient_storage::TransientStorage,
23};
24use alloc::{boxed::Box, fmt::Debug, string::String, vec::Vec};
25use codec::{Decode, Encode, MaxEncodedLen};
26use core::cell::RefCell;
27use frame_support::{DefaultNoBound, traits::tokens::Balance, weights::Weight};
28use pallet_revive_types::runtime_api::{
29	CodeV1, ContractResultV1, EthTransactInfoV1, ExecReturnValueV1, InstantiateReturnValueV1,
30	StorageDepositV1,
31};
32use pallet_revive_uapi::ReturnFlags;
33use scale_info::TypeInfo;
34use sp_core::Get;
35use sp_runtime::{
36	DispatchError,
37	traits::{One, Saturating, Zero},
38};
39
40/// Result type of a `bare_call` or `bare_instantiate` call as well as `ContractsApi::call` and
41/// `ContractsApi::instantiate`.
42///
43/// It contains the execution result together with some auxiliary information.
44///
45/// #Note
46///
47/// It has been extended to include `events` at the end of the struct while not bumping the
48/// `ContractsApi` version. Therefore when SCALE decoding a `ContractResult` its trailing data
49/// should be ignored to avoid any potential compatibility issues.
50#[derive(Clone, Eq, PartialEq, Debug)]
51pub struct ContractResult<R, Balance> {
52	/// How much weight was consumed during execution.
53	pub weight_consumed: Weight,
54	/// How much weight is required as weight limit in order to execute this call.
55	///
56	/// This value should be used to determine the weight limit for on-chain execution.
57	///
58	/// # Note
59	///
60	/// This can only be different from [`Self::weight_consumed`] when weight pre charging
61	/// is used. Currently, only `seal_call_runtime` makes use of pre charging.
62	/// Additionally, any `seal_call` or `seal_instantiate` makes use of pre-charging
63	/// when a non-zero `weight_limit` argument is supplied.
64	pub weight_required: Weight,
65	/// How much balance was paid by the origin into the contract's deposit account in order to
66	/// pay for storage.
67	///
68	/// The storage deposit is never actually charged from the origin in case of [`Self::result`]
69	/// is `Err`. This is because on error all storage changes are rolled back including the
70	/// payment of the deposit.
71	pub storage_deposit: StorageDeposit<Balance>,
72	/// The maximal storage deposit amount that occured at any time during the execution.
73	/// This can be higher than the final storage_deposit due to refunds
74	/// This is always a StorageDeposit::Charge(..)
75	pub max_storage_deposit: StorageDeposit<Balance>,
76	/// The amount of Ethereum gas that has been consumed during execution.
77	pub gas_consumed: Balance,
78	/// The execution result of the vm binary code.
79	pub result: Result<R, DispatchError>,
80}
81
82impl<R: Default, B: Balance> Default for ContractResult<R, B> {
83	fn default() -> Self {
84		Self {
85			weight_consumed: Default::default(),
86			weight_required: Default::default(),
87			storage_deposit: Default::default(),
88			max_storage_deposit: Default::default(),
89			gas_consumed: Default::default(),
90			result: Ok(Default::default()),
91		}
92	}
93}
94
95impl<R, RV1, Balance> From<ContractResult<R, Balance>> for ContractResultV1<RV1, Balance>
96where
97	RV1: From<R>,
98{
99	fn from(value: ContractResult<R, Balance>) -> Self {
100		Self {
101			weight_consumed: value.weight_consumed,
102			weight_required: value.weight_required,
103			storage_deposit: value.storage_deposit.into(),
104			max_storage_deposit: value.max_storage_deposit.into(),
105			gas_consumed: value.gas_consumed,
106			result: value.result.map(Into::into),
107		}
108	}
109}
110
111/// The result of the execution of a `eth_transact` call.
112#[derive(Clone, Eq, PartialEq, Default, Debug)]
113pub struct EthTransactInfo<Balance> {
114	/// The amount of weight that was necessary to execute the transaction.
115	pub weight_required: Weight,
116	/// Final storage deposit charged.
117	pub storage_deposit: Balance,
118	/// Maximal storage deposit charged at any time during execution.
119	pub max_storage_deposit: Balance,
120	/// The weight and deposit equivalent in EVM Gas.
121	pub eth_gas: U256,
122	/// The execution return value.
123	pub data: Vec<u8>,
124}
125
126impl<Balance> From<EthTransactInfo<Balance>> for EthTransactInfoV1<Balance> {
127	fn from(value: EthTransactInfo<Balance>) -> Self {
128		Self {
129			weight_required: value.weight_required,
130			storage_deposit: value.storage_deposit,
131			max_storage_deposit: value.max_storage_deposit,
132			eth_gas: value.eth_gas,
133			data: value.data,
134		}
135	}
136}
137
138/// Error type of a `eth_transact` call.
139#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug, TypeInfo)]
140pub enum EthTransactError {
141	Data(Vec<u8>),
142	Message(String),
143}
144
145#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug, TypeInfo)]
146/// Error encountered while creating a BalanceWithDust from a U256 balance.
147pub enum BalanceConversionError {
148	/// Error encountered while creating the main balance value.
149	Value,
150	/// Error encountered while creating the dust value.
151	Dust,
152}
153
154/// A Balance amount along with some "dust" to represent the lowest decimals that can't be expressed
155/// in the native currency
156#[derive(Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug)]
157pub struct BalanceWithDust<Balance> {
158	/// The value expressed in the native currency
159	value: Balance,
160	/// The dust, representing up to 1 unit of the native currency.
161	/// The dust is bounded between 0 and `crate::Config::NativeToEthRatio`
162	dust: u32,
163}
164
165impl<Balance> From<Balance> for BalanceWithDust<Balance> {
166	fn from(value: Balance) -> Self {
167		Self { value, dust: 0 }
168	}
169}
170
171impl<Balance> BalanceWithDust<Balance> {
172	/// Deconstructs the `BalanceWithDust` into its components.
173	pub fn deconstruct(self) -> (Balance, u32) {
174		(self.value, self.dust)
175	}
176
177	/// Creates a new `BalanceWithDust` with the given value and dust.
178	pub fn new_unchecked<T: Config>(value: Balance, dust: u32) -> Self {
179		debug_assert!(dust < T::NativeToEthRatio::get());
180		Self { value, dust }
181	}
182
183	/// Creates a new `BalanceWithDust` from the given EVM value.
184	pub fn from_value<T: Config>(
185		value: U256,
186	) -> Result<BalanceWithDust<BalanceOf<T>>, BalanceConversionError> {
187		if value.is_zero() {
188			return Ok(Default::default());
189		}
190
191		let (quotient, remainder) = value.div_mod(T::NativeToEthRatio::get().into());
192		let value = quotient.try_into().map_err(|_| BalanceConversionError::Value)?;
193		let dust = remainder.try_into().map_err(|_| BalanceConversionError::Dust)?;
194
195		Ok(BalanceWithDust { value, dust })
196	}
197}
198
199impl<Balance: Zero + One + Saturating> BalanceWithDust<Balance> {
200	/// Returns true if both the value and dust are zero.
201	pub fn is_zero(&self) -> bool {
202		self.value.is_zero() && self.dust == 0
203	}
204
205	/// Returns the Balance rounded to the nearest whole unit if the dust is non-zero.
206	pub fn into_rounded_balance(self) -> Balance {
207		if self.dust == 0 { self.value } else { self.value.saturating_add(Balance::one()) }
208	}
209}
210
211/// Result type of a `bare_code_upload` call.
212pub type CodeUploadResult<Balance> = Result<CodeUploadReturnValue<Balance>, DispatchError>;
213
214/// Result type of a `get_storage` call.
215pub type GetStorageResult = Result<Option<Vec<u8>>, ContractAccessError>;
216
217/// Result type of a `set_storage` call.
218pub type SetStorageResult = Result<WriteOutcome, ContractAccessError>;
219
220/// The possible errors that can happen querying the storage of a contract.
221#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, Debug, TypeInfo)]
222pub enum ContractAccessError {
223	/// The given address doesn't point to a contract.
224	DoesntExist,
225	/// Storage key cannot be decoded from the provided input data.
226	KeyDecodingFailed,
227	/// Writing to storage failed.
228	StorageWriteFailed(DispatchError),
229}
230
231/// Output of a contract call or instantiation which ran to completion.
232#[derive(Clone, PartialEq, Eq, Debug, Default)]
233pub struct ExecReturnValue {
234	/// Flags passed along by `seal_return`. Empty when `seal_return` was never called.
235	pub flags: ReturnFlags,
236	/// Buffer passed along by `seal_return`. Empty when `seal_return` was never called.
237	pub data: Vec<u8>,
238}
239
240impl ExecReturnValue {
241	/// The contract did revert all storage changes.
242	pub fn did_revert(&self) -> bool {
243		self.flags.contains(ReturnFlags::REVERT)
244	}
245}
246
247impl From<ExecReturnValue> for ExecReturnValueV1 {
248	fn from(value: ExecReturnValue) -> Self {
249		Self { flags: value.flags, data: value.data }
250	}
251}
252
253/// The result of a successful contract instantiation.
254#[derive(Clone, PartialEq, Eq, Debug, Default)]
255pub struct InstantiateReturnValue {
256	/// The output of the called constructor.
257	pub result: ExecReturnValue,
258	/// The address of the new contract.
259	pub addr: H160,
260}
261
262impl From<InstantiateReturnValue> for InstantiateReturnValueV1 {
263	fn from(value: InstantiateReturnValue) -> Self {
264		Self { result: value.result.into(), addr: value.addr }
265	}
266}
267
268/// The result of successfully uploading a contract.
269#[derive(Clone, PartialEq, Eq, Encode, Decode, MaxEncodedLen, Debug, TypeInfo)]
270pub struct CodeUploadReturnValue<Balance> {
271	/// The key under which the new code is stored.
272	pub code_hash: sp_core::H256,
273	/// The deposit that was reserved at the caller. Is zero when the code already existed.
274	pub deposit: Balance,
275}
276
277impl<Balance> From<CodeUploadReturnValue<Balance>>
278	for pallet_revive_types::runtime_api::CodeUploadReturnValueV1<Balance>
279{
280	fn from(value: CodeUploadReturnValue<Balance>) -> Self {
281		Self { code_hash: value.code_hash, deposit: value.deposit }
282	}
283}
284
285/// Reference to an existing code hash or a new vm module.
286#[derive(Clone, Eq, PartialEq, Debug)]
287pub enum Code {
288	/// A vm module as raw bytes.
289	Upload(Vec<u8>),
290	/// The code hash of an on-chain vm binary blob.
291	Existing(sp_core::H256),
292}
293
294impl From<CodeV1> for Code {
295	fn from(value: CodeV1) -> Self {
296		match value {
297			CodeV1::Upload(code) => Self::Upload(code),
298			CodeV1::Existing(code_hash) => Self::Existing(code_hash),
299		}
300	}
301}
302
303/// The amount of balance that was either charged or refunded in order to pay for storage.
304#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
305pub enum StorageDeposit<Balance> {
306	/// The transaction reduced storage consumption.
307	///
308	/// This means that the specified amount of balance was transferred from the involved
309	/// deposit accounts to the origin.
310	Refund(Balance),
311	/// The transaction increased storage consumption.
312	///
313	/// This means that the specified amount of balance was transferred from the origin
314	/// to the involved deposit accounts.
315	Charge(Balance),
316}
317
318impl<Balance> From<StorageDeposit<Balance>> for StorageDepositV1<Balance> {
319	fn from(value: StorageDeposit<Balance>) -> Self {
320		match value {
321			StorageDeposit::Refund(amount) => Self::Refund(amount),
322			StorageDeposit::Charge(amount) => Self::Charge(amount),
323		}
324	}
325}
326
327impl<T, Balance> ContractResult<T, Balance> {
328	pub fn map_result<V>(self, map_fn: impl FnOnce(T) -> V) -> ContractResult<V, Balance> {
329		ContractResult {
330			weight_consumed: self.weight_consumed,
331			weight_required: self.weight_required,
332			storage_deposit: self.storage_deposit,
333			max_storage_deposit: self.max_storage_deposit,
334			gas_consumed: self.gas_consumed,
335			result: self.result.map(map_fn),
336		}
337	}
338}
339
340impl<Balance: Zero> Default for StorageDeposit<Balance> {
341	fn default() -> Self {
342		Self::Charge(Zero::zero())
343	}
344}
345
346impl<Balance: Zero + Copy> StorageDeposit<Balance> {
347	/// Returns how much balance is charged or `0` in case of a refund.
348	pub fn charge_or_zero(&self) -> Balance {
349		match self {
350			Self::Charge(amount) => *amount,
351			Self::Refund(_) => Zero::zero(),
352		}
353	}
354
355	pub fn is_zero(&self) -> bool {
356		match self {
357			Self::Charge(amount) => amount.is_zero(),
358			Self::Refund(amount) => amount.is_zero(),
359		}
360	}
361}
362
363impl<Balance> StorageDeposit<Balance>
364where
365	Balance: frame_support::traits::tokens::Balance + Saturating + Ord + Copy,
366{
367	/// This is essentially a saturating signed add.
368	pub fn saturating_add(&self, rhs: &Self) -> Self {
369		use StorageDeposit::*;
370		match (self, rhs) {
371			(Charge(lhs), Charge(rhs)) => Charge(lhs.saturating_add(*rhs)),
372			(Refund(lhs), Refund(rhs)) => Refund(lhs.saturating_add(*rhs)),
373			(Charge(lhs), Refund(rhs)) => {
374				if lhs >= rhs {
375					Charge(lhs.saturating_sub(*rhs))
376				} else {
377					Refund(rhs.saturating_sub(*lhs))
378				}
379			},
380			(Refund(lhs), Charge(rhs)) => {
381				if lhs > rhs {
382					Refund(lhs.saturating_sub(*rhs))
383				} else {
384					Charge(rhs.saturating_sub(*lhs))
385				}
386			},
387		}
388	}
389
390	/// This is essentially a saturating signed sub.
391	pub fn saturating_sub(&self, rhs: &Self) -> Self {
392		use StorageDeposit::*;
393		match (self, rhs) {
394			(Charge(lhs), Refund(rhs)) => Charge(lhs.saturating_add(*rhs)),
395			(Refund(lhs), Charge(rhs)) => Refund(lhs.saturating_add(*rhs)),
396			(Charge(lhs), Charge(rhs)) => {
397				if lhs >= rhs {
398					Charge(lhs.saturating_sub(*rhs))
399				} else {
400					Refund(rhs.saturating_sub(*lhs))
401				}
402			},
403			(Refund(lhs), Refund(rhs)) => {
404				if lhs > rhs {
405					Refund(lhs.saturating_sub(*rhs))
406				} else {
407					Charge(rhs.saturating_sub(*lhs))
408				}
409			},
410		}
411	}
412
413	/// If the amount of deposit (this type) is constrained by a `limit` this calculates how
414	/// much balance (if any) is still available from this limit.
415	///
416	/// # Note
417	///
418	/// In case of a refund the return value can be larger than `limit`.
419	pub fn available(&self, limit: &Balance) -> Option<Balance> {
420		use StorageDeposit::*;
421		match self {
422			Charge(amount) => limit.checked_sub(amount),
423			Refund(amount) => Some(limit.saturating_add(*amount)),
424		}
425	}
426}
427
428/// `Stack` wide configuration options.
429#[derive(DefaultNoBound)]
430pub struct ExecConfig<T: Config> {
431	/// Indicates whether the account nonce should be incremented after instantiating a new
432	/// contract.
433	///
434	/// In Substrate, where transactions can be batched, the account's nonce should be incremented
435	/// after each instantiation, ensuring that each instantiation uses a unique nonce.
436	///
437	/// For transactions sent from Ethereum wallets, which cannot be batched, the nonce should only
438	/// be incremented once. In these cases, set this to `false` to suppress an extra nonce
439	/// increment.
440	///
441	/// Note:
442	/// The origin's nonce is already incremented pre-dispatch by the `CheckNonce` transaction
443	/// extension.
444	///
445	/// This does not apply to contract initiated instantatiations. Those will always bump the
446	/// instantiating contract's nonce.
447	pub bump_nonce: bool,
448	/// Whether deposits will be withdrawn from the pallet_transaction_payment credit (`Some`)
449	/// free balance (`None`).
450	///
451	/// Contains the encoded_len + base weight.
452	pub collect_deposit_from_hold: Option<(u32, Weight)>,
453	/// The gas price that was chosen for this transaction.
454	///
455	/// It is determined when transforming `eth_transact` into a proper extrinsic.
456	pub effective_gas_price: Option<U256>,
457	/// Whether this configuration was created for a dry-run execution.
458	/// Use to enable logic that should only run in dry-run mode.
459	pub is_dry_run: Option<DryRunConfigurations<MomentOf<T>>>,
460	/// An optional mock handler that can be used to override certain behaviors.
461	/// This is primarily used for testing purposes and should be `None` in production
462	/// environments.
463	pub mock_handler: Option<Box<dyn MockHandler<T>>>,
464	/// Externally supplied transient storage.
465	///
466	/// This is only used for testing purposes and should be `None` in production
467	/// environments.
468	pub test_env_transient_storage: Option<RefCell<TransientStorage<T>>>,
469}
470
471impl<T: Config> ExecConfig<T> {
472	/// Create a default config appropriate when the call originated from a substrate tx.
473	pub fn new_substrate_tx() -> Self {
474		Self {
475			bump_nonce: true,
476			collect_deposit_from_hold: None,
477			effective_gas_price: None,
478			is_dry_run: None,
479			mock_handler: None,
480			test_env_transient_storage: None,
481		}
482	}
483
484	pub fn new_substrate_tx_without_bump() -> Self {
485		Self {
486			bump_nonce: false,
487			collect_deposit_from_hold: None,
488			effective_gas_price: None,
489			mock_handler: None,
490			is_dry_run: None,
491			test_env_transient_storage: None,
492		}
493	}
494
495	/// Create a default config appropriate when the call originated from a ethereum tx.
496	pub fn new_eth_tx(effective_gas_price: U256, encoded_len: u32, base_weight: Weight) -> Self {
497		Self {
498			bump_nonce: false,
499			collect_deposit_from_hold: Some((encoded_len, base_weight)),
500			effective_gas_price: Some(effective_gas_price),
501			mock_handler: None,
502			is_dry_run: None,
503			test_env_transient_storage: None,
504		}
505	}
506
507	/// Set this config to be a dry-run.
508	pub fn with_dry_run(mut self, timestamp_override: impl Into<Option<MomentOf<T>>>) -> Self {
509		self.is_dry_run =
510			Some(DryRunConfigurations { timestamp_override: timestamp_override.into() });
511		self
512	}
513
514	/// Classify `account` as a deposit source or refund destination based on
515	/// [`Self::collect_deposit_from_hold`]: [`Funds::TxFee`] under eth-tx dispatch (where
516	/// deposits flow through the tx fee pool), otherwise [`Funds::Balance`].
517	pub fn funds<'a>(&self, account: &'a T::AccountId) -> Funds<'a, T::AccountId> {
518		if self.collect_deposit_from_hold.is_some() {
519			Funds::TxFee(account)
520		} else {
521			Funds::Balance(account)
522		}
523	}
524
525	/// Almost clone for testing (does not clone mock_handler)
526	#[cfg(test)]
527	pub fn clone(&self) -> Self {
528		Self {
529			bump_nonce: self.bump_nonce,
530			collect_deposit_from_hold: self.collect_deposit_from_hold,
531			effective_gas_price: self.effective_gas_price,
532			is_dry_run: self.is_dry_run.clone(),
533			mock_handler: None,
534			test_env_transient_storage: None,
535		}
536	}
537}
538
539#[derive(Clone)]
540pub struct DryRunConfigurations<Moment> {
541	pub timestamp_override: Option<Moment>,
542}
543
544/// Indicates whether the code was removed after the last refcount was decremented.
545#[must_use = "You must handle whether the code was removed or not."]
546pub enum CodeRemoved {
547	/// The code was not removed. (refcount > 0)
548	No,
549	/// The code was removed. (refcount == 0)
550	Yes,
551}