pezpallet_revive/
primitives.rs

1// This file is part of Bizinikiwi.
2
3// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
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 pezpallet-revive.
19
20use crate::{
21	evm::DryRunConfig, mock::MockHandler, storage::WriteOutcome, BalanceOf, Config, Time, H160,
22	U256,
23};
24use alloc::{boxed::Box, fmt::Debug, string::String, vec::Vec};
25use codec::{Decode, Encode, MaxEncodedLen};
26use pezframe_support::weights::Weight;
27use pezpallet_revive_uapi::ReturnFlags;
28use pezsp_core::Get;
29use pezsp_runtime::{
30	traits::{One, Saturating, Zero},
31	DispatchError, RuntimeDebug,
32};
33use scale_info::TypeInfo;
34
35/// Result type of a `bare_call` or `bare_instantiate` call as well as `ContractsApi::call` and
36/// `ContractsApi::instantiate`.
37///
38/// It contains the execution result together with some auxiliary information.
39///
40/// #Note
41///
42/// It has been extended to include `events` at the end of the struct while not bumping the
43/// `ContractsApi` version. Therefore when SCALE decoding a `ContractResult` its trailing data
44/// should be ignored to avoid any potential compatibility issues.
45#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
46pub struct ContractResult<R, Balance> {
47	/// How much weight was consumed during execution.
48	pub gas_consumed: Weight,
49	/// How much weight is required as gas limit in order to execute this call.
50	///
51	/// This value should be used to determine the weight limit for on-chain execution.
52	///
53	/// # Note
54	///
55	/// This can only be different from [`Self::gas_consumed`] when weight pre charging
56	/// is used. Currently, only `seal_call_runtime` makes use of pre charging.
57	/// Additionally, any `seal_call` or `seal_instantiate` makes use of pre-charging
58	/// when a non-zero `gas_limit` argument is supplied.
59	pub gas_required: Weight,
60	/// How much balance was paid by the origin into the contract's deposit account in order to
61	/// pay for storage.
62	///
63	/// The storage deposit is never actually charged from the origin in case of [`Self::result`]
64	/// is `Err`. This is because on error all storage changes are rolled back including the
65	/// payment of the deposit.
66	pub storage_deposit: StorageDeposit<Balance>,
67	/// The execution result of the vm binary code.
68	pub result: Result<R, DispatchError>,
69}
70
71/// The result of the execution of a `eth_transact` call.
72#[derive(Clone, Eq, PartialEq, Default, Encode, Decode, RuntimeDebug, TypeInfo)]
73pub struct EthTransactInfo<Balance> {
74	/// The amount of gas that was necessary to execute the transaction.
75	pub gas_required: Weight,
76	/// Storage deposit charged.
77	pub storage_deposit: Balance,
78	/// The weight and deposit equivalent in EVM Gas.
79	pub eth_gas: U256,
80	/// The execution return value.
81	pub data: Vec<u8>,
82}
83
84/// Error type of a `eth_transact` call.
85#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
86pub enum EthTransactError {
87	Data(Vec<u8>),
88	Message(String),
89}
90
91#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
92/// Error encountered while creating a BalanceWithDust from a U256 balance.
93pub enum BalanceConversionError {
94	/// Error encountered while creating the main balance value.
95	Value,
96	/// Error encountered while creating the dust value.
97	Dust,
98}
99
100/// A Balance amount along with some "dust" to represent the lowest decimals that can't be expressed
101/// in the native currency
102#[derive(Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug)]
103pub struct BalanceWithDust<Balance> {
104	/// The value expressed in the native currency
105	value: Balance,
106	/// The dust, representing up to 1 unit of the native currency.
107	/// The dust is bounded between 0 and `crate::Config::NativeToEthRatio`
108	dust: u32,
109}
110
111impl<Balance> From<Balance> for BalanceWithDust<Balance> {
112	fn from(value: Balance) -> Self {
113		Self { value, dust: 0 }
114	}
115}
116
117impl<Balance> BalanceWithDust<Balance> {
118	/// Deconstructs the `BalanceWithDust` into its components.
119	pub fn deconstruct(self) -> (Balance, u32) {
120		(self.value, self.dust)
121	}
122
123	/// Creates a new `BalanceWithDust` with the given value and dust.
124	pub fn new_unchecked<T: Config>(value: Balance, dust: u32) -> Self {
125		debug_assert!(dust < T::NativeToEthRatio::get());
126		Self { value, dust }
127	}
128
129	/// Creates a new `BalanceWithDust` from the given EVM value.
130	pub fn from_value<T: Config>(
131		value: U256,
132	) -> Result<BalanceWithDust<BalanceOf<T>>, BalanceConversionError> {
133		if value.is_zero() {
134			return Ok(Default::default());
135		}
136
137		let (quotient, remainder) = value.div_mod(T::NativeToEthRatio::get().into());
138		let value = quotient.try_into().map_err(|_| BalanceConversionError::Value)?;
139		let dust = remainder.try_into().map_err(|_| BalanceConversionError::Dust)?;
140
141		Ok(BalanceWithDust { value, dust })
142	}
143}
144
145impl<Balance: Zero + One + Saturating> BalanceWithDust<Balance> {
146	/// Returns true if both the value and dust are zero.
147	pub fn is_zero(&self) -> bool {
148		self.value.is_zero() && self.dust == 0
149	}
150
151	/// Returns the Balance rounded to the nearest whole unit if the dust is non-zero.
152	pub fn into_rounded_balance(self) -> Balance {
153		if self.dust == 0 {
154			self.value
155		} else {
156			self.value.saturating_add(Balance::one())
157		}
158	}
159}
160
161/// Result type of a `bare_code_upload` call.
162pub type CodeUploadResult<Balance> = Result<CodeUploadReturnValue<Balance>, DispatchError>;
163
164/// Result type of a `get_storage` call.
165pub type GetStorageResult = Result<Option<Vec<u8>>, ContractAccessError>;
166
167/// Result type of a `set_storage` call.
168pub type SetStorageResult = Result<WriteOutcome, ContractAccessError>;
169
170/// The possible errors that can happen querying the storage of a contract.
171#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, RuntimeDebug, TypeInfo)]
172pub enum ContractAccessError {
173	/// The given address doesn't point to a contract.
174	DoesntExist,
175	/// Storage key cannot be decoded from the provided input data.
176	KeyDecodingFailed,
177	/// Writing to storage failed.
178	StorageWriteFailed(DispatchError),
179}
180
181/// Output of a contract call or instantiation which ran to completion.
182#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo, Default)]
183pub struct ExecReturnValue {
184	/// Flags passed along by `seal_return`. Empty when `seal_return` was never called.
185	pub flags: ReturnFlags,
186	/// Buffer passed along by `seal_return`. Empty when `seal_return` was never called.
187	pub data: Vec<u8>,
188}
189
190impl ExecReturnValue {
191	/// The contract did revert all storage changes.
192	pub fn did_revert(&self) -> bool {
193		self.flags.contains(ReturnFlags::REVERT)
194	}
195}
196
197/// The result of a successful contract instantiation.
198#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)]
199pub struct InstantiateReturnValue {
200	/// The output of the called constructor.
201	pub result: ExecReturnValue,
202	/// The address of the new contract.
203	pub addr: H160,
204}
205
206/// The result of successfully uploading a contract.
207#[derive(Clone, PartialEq, Eq, Encode, Decode, MaxEncodedLen, RuntimeDebug, TypeInfo)]
208pub struct CodeUploadReturnValue<Balance> {
209	/// The key under which the new code is stored.
210	pub code_hash: pezsp_core::H256,
211	/// The deposit that was reserved at the caller. Is zero when the code already existed.
212	pub deposit: Balance,
213}
214
215/// Reference to an existing code hash or a new vm module.
216#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
217pub enum Code {
218	/// A vm module as raw bytes.
219	Upload(Vec<u8>),
220	/// The code hash of an on-chain vm binary blob.
221	Existing(pezsp_core::H256),
222}
223
224/// The amount of balance that was either charged or refunded in order to pay for storage.
225#[derive(
226	Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, MaxEncodedLen, RuntimeDebug, TypeInfo,
227)]
228pub enum StorageDeposit<Balance> {
229	/// The transaction reduced storage consumption.
230	///
231	/// This means that the specified amount of balance was transferred from the involved
232	/// deposit accounts to the origin.
233	Refund(Balance),
234	/// The transaction increased storage consumption.
235	///
236	/// This means that the specified amount of balance was transferred from the origin
237	/// to the involved deposit accounts.
238	Charge(Balance),
239}
240
241impl<T, Balance> ContractResult<T, Balance> {
242	pub fn map_result<V>(self, map_fn: impl FnOnce(T) -> V) -> ContractResult<V, Balance> {
243		ContractResult {
244			gas_consumed: self.gas_consumed,
245			gas_required: self.gas_required,
246			storage_deposit: self.storage_deposit,
247			result: self.result.map(map_fn),
248		}
249	}
250}
251
252impl<Balance: Zero> Default for StorageDeposit<Balance> {
253	fn default() -> Self {
254		Self::Charge(Zero::zero())
255	}
256}
257
258impl<Balance: Zero + Copy> StorageDeposit<Balance> {
259	/// Returns how much balance is charged or `0` in case of a refund.
260	pub fn charge_or_zero(&self) -> Balance {
261		match self {
262			Self::Charge(amount) => *amount,
263			Self::Refund(_) => Zero::zero(),
264		}
265	}
266
267	pub fn is_zero(&self) -> bool {
268		match self {
269			Self::Charge(amount) => amount.is_zero(),
270			Self::Refund(amount) => amount.is_zero(),
271		}
272	}
273}
274
275impl<Balance> StorageDeposit<Balance>
276where
277	Balance: Saturating + Ord + Copy,
278{
279	/// This is essentially a saturating signed add.
280	pub fn saturating_add(&self, rhs: &Self) -> Self {
281		use StorageDeposit::*;
282		match (self, rhs) {
283			(Charge(lhs), Charge(rhs)) => Charge(lhs.saturating_add(*rhs)),
284			(Refund(lhs), Refund(rhs)) => Refund(lhs.saturating_add(*rhs)),
285			(Charge(lhs), Refund(rhs)) => {
286				if lhs >= rhs {
287					Charge(lhs.saturating_sub(*rhs))
288				} else {
289					Refund(rhs.saturating_sub(*lhs))
290				}
291			},
292			(Refund(lhs), Charge(rhs)) => {
293				if lhs > rhs {
294					Refund(lhs.saturating_sub(*rhs))
295				} else {
296					Charge(rhs.saturating_sub(*lhs))
297				}
298			},
299		}
300	}
301
302	/// This is essentially a saturating signed sub.
303	pub fn saturating_sub(&self, rhs: &Self) -> Self {
304		use StorageDeposit::*;
305		match (self, rhs) {
306			(Charge(lhs), Refund(rhs)) => Charge(lhs.saturating_add(*rhs)),
307			(Refund(lhs), Charge(rhs)) => Refund(lhs.saturating_add(*rhs)),
308			(Charge(lhs), Charge(rhs)) => {
309				if lhs >= rhs {
310					Charge(lhs.saturating_sub(*rhs))
311				} else {
312					Refund(rhs.saturating_sub(*lhs))
313				}
314			},
315			(Refund(lhs), Refund(rhs)) => {
316				if lhs > rhs {
317					Refund(lhs.saturating_sub(*rhs))
318				} else {
319					Charge(rhs.saturating_sub(*lhs))
320				}
321			},
322		}
323	}
324
325	/// If the amount of deposit (this type) is constrained by a `limit` this calculates how
326	/// much balance (if any) is still available from this limit.
327	///
328	/// # Note
329	///
330	/// In case of a refund the return value can be larger than `limit`.
331	pub fn available(&self, limit: &Balance) -> Balance {
332		use StorageDeposit::*;
333		match self {
334			Charge(amount) => limit.saturating_sub(*amount),
335			Refund(amount) => limit.saturating_add(*amount),
336		}
337	}
338}
339
340/// `Stack` wide configuration options.
341pub struct ExecConfig<T: Config> {
342	/// Indicates whether the account nonce should be incremented after instantiating a new
343	/// contract.
344	///
345	/// In Bizinikiwi, where transactions can be batched, the account's nonce should be incremented
346	/// after each instantiation, ensuring that each instantiation uses a unique nonce.
347	///
348	/// For transactions sent from Ethereum wallets, which cannot be batched, the nonce should only
349	/// be incremented once. In these cases, set this to `false` to suppress an extra nonce
350	/// increment.
351	///
352	/// Note:
353	/// The origin's nonce is already incremented pre-dispatch by the `CheckNonce` transaction
354	/// extension.
355	///
356	/// This does not apply to contract initiated instantatiations. Those will always bump the
357	/// instantiating contract's nonce.
358	pub bump_nonce: bool,
359	/// Whether deposits will be withdrawn from the pezpallet_transaction_payment credit (`Some`)
360	/// free balance (`None`).
361	///
362	/// Contains the encoded_len + base weight.
363	pub collect_deposit_from_hold: Option<(u32, Weight)>,
364	/// The gas price that was chosen for this transaction.
365	///
366	/// It is determined when transforming `eth_transact` into a proper extrinsic.
367	pub effective_gas_price: Option<U256>,
368	/// Whether this configuration was created for a dry-run execution.
369	/// Use to enable logic that should only run in dry-run mode.
370	pub is_dry_run: Option<DryRunConfig<<<T as Config>::Time as Time>::Moment>>,
371	/// An optional mock handler that can be used to override certain behaviors.
372	/// This is primarily used for testing purposes and should be `None` in production
373	/// environments.
374	pub mock_handler: Option<Box<dyn MockHandler<T>>>,
375}
376
377impl<T: Config> ExecConfig<T> {
378	/// Create a default config appropriate when the call originated from a bizinikiwi tx.
379	pub fn new_bizinikiwi_tx() -> Self {
380		Self {
381			bump_nonce: true,
382			collect_deposit_from_hold: None,
383			effective_gas_price: None,
384			is_dry_run: None,
385			mock_handler: None,
386		}
387	}
388
389	pub fn new_bizinikiwi_tx_without_bump() -> Self {
390		Self {
391			bump_nonce: false,
392			collect_deposit_from_hold: None,
393			effective_gas_price: None,
394			mock_handler: None,
395			is_dry_run: None,
396		}
397	}
398
399	/// Create a default config appropriate when the call originated from a ethereum tx.
400	pub fn new_eth_tx(effective_gas_price: U256, encoded_len: u32, base_weight: Weight) -> Self {
401		Self {
402			bump_nonce: false,
403			collect_deposit_from_hold: Some((encoded_len, base_weight)),
404			effective_gas_price: Some(effective_gas_price),
405			mock_handler: None,
406			is_dry_run: None,
407		}
408	}
409
410	/// Set this config to be a dry-run.
411	pub fn with_dry_run(
412		mut self,
413		dry_run_config: DryRunConfig<<<T as Config>::Time as Time>::Moment>,
414	) -> Self {
415		self.is_dry_run = Some(dry_run_config);
416		self
417	}
418}
419
420/// Indicates whether the code was removed after the last refcount was decremented.
421#[must_use = "You must handle whether the code was removed or not."]
422pub enum CodeRemoved {
423	/// The code was not removed. (refcount > 0)
424	No,
425	/// The code was removed. (refcount == 0)
426	Yes,
427}