git.delta.rocks / unique-network / refs/commits / 465cfefa64e5

difftreelog

source

pallets/evm-contract-helpers/src/lib.rs12.5 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![doc = include_str!("../README.md")]18#![cfg_attr(not(feature = "std"), no_std)]19#![warn(missing_docs)]2021use codec::{Decode, Encode, MaxEncodedLen};22pub use pallet::*;23pub use eth::*;24use scale_info::TypeInfo;25use frame_support::storage::bounded_btree_map::BoundedBTreeMap;26pub mod eth;2728/// Maximum number of methods per contract that could have fee limit29pub const MAX_FEE_LIMITED_METHODS: u32 = 5;3031#[frame_support::pallet]32pub mod pallet {33	pub use super::*;34	use crate::eth::ContractHelpersEvents;35	use frame_support::pallet_prelude::*;36	use pallet_evm_coder_substrate::DispatchResult;37	use sp_core::{H160, U256};38	use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};39	use up_data_structs::SponsorshipState;40	use evm_coder::ToLog;4142	#[pallet::config]43	pub trait Config:44		frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::account::Config45	{46		/// Overarching event type.47		type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;4849		/// Address, under which magic contract will be available50		type ContractAddress: Get<H160>;5152		/// In case of enabled sponsoring, but no sponsoring rate limit set,53		/// this value will be used implicitly54		type DefaultSponsoringRateLimit: Get<Self::BlockNumber>;55	}5657	#[pallet::error]58	pub enum Error<T> {59		/// This method is only executable by contract owner60		NoPermission,6162		/// No pending sponsor for contract.63		NoPendingSponsor,6465		/// Number of methods that sponsored limit is defined for exceeds maximum.66		TooManyMethodsHaveSponsoredLimit,67	}6869	#[pallet::pallet]70	#[pallet::generate_store(pub(super) trait Store)]71	pub struct Pallet<T>(_);7273	/// Store owner for contract.74	///75	/// * **Key** - contract address.76	/// * **Value** - owner for contract.77	#[pallet::storage]78	pub(super) type Owner<T: Config> =79		StorageMap<Hasher = Twox128, Key = H160, Value = H160, QueryKind = ValueQuery>;8081	#[pallet::storage]82	#[deprecated]83	pub(super) type SelfSponsoring<T: Config> =84		StorageMap<Hasher = Twox128, Key = H160, Value = bool, QueryKind = ValueQuery>;8586	/// Store for contract sponsorship state.87	///88	/// * **Key** - contract address.89	/// * **Value** - sponsorship state.90	#[pallet::storage]91	pub(super) type Sponsoring<T: Config> = StorageMap<92		Hasher = Twox64Concat,93		Key = H160,94		Value = SponsorshipState<T::CrossAccountId>,95		QueryKind = ValueQuery,96	>;9798	/// Store for sponsoring mode.99	///100	/// ### Usage101	/// Prefer to delete collection from storage if mode chaged to [`Disabled`](SponsoringModeT::Disabled).102	///103	/// * **Key** - contract address.104	/// * **Value** - [`sponsoring mode`](SponsoringModeT).105	#[pallet::storage]106	pub(super) type SponsoringMode<T: Config> =107		StorageMap<Hasher = Twox128, Key = H160, Value = SponsoringModeT, QueryKind = OptionQuery>;108109	/// Storage for sponsoring rate limit in blocks.110	///111	/// * **Key** - contract address.112	/// * **Value** - amount of sponsored blocks.113	#[pallet::storage]114	pub(super) type SponsoringRateLimit<T: Config> = StorageMap<115		Hasher = Twox128,116		Key = H160,117		Value = T::BlockNumber,118		QueryKind = ValueQuery,119		OnEmpty = T::DefaultSponsoringRateLimit,120	>;121122	/// Storage for last sponsored block.123	///124	/// * **Key1** - contract address.125	/// * **Key2** - sponsored user address.126	/// * **Value** - last sponsored block number.127	#[pallet::storage]128	pub(super) type SponsoringFeeLimit<T: Config> = StorageMap<129		Hasher = Twox128,130		Key = H160,131		Value = BoundedBTreeMap<u32, U256, ConstU32<MAX_FEE_LIMITED_METHODS>>,132		QueryKind = ValueQuery,133	>;134135	#[pallet::storage]136	pub(super) type SponsorBasket<T: Config> = StorageDoubleMap<137		Hasher1 = Twox128,138		Key1 = H160,139		Hasher2 = Twox128,140		Key2 = H160,141		Value = T::BlockNumber,142		QueryKind = OptionQuery,143	>;144145	/// Storege for contracts with [`Allowlisted`](SponsoringModeT::Allowlisted) sponsoring mode.146	///147	/// ### Usage148	/// Prefer to delete collection from storage if mode chaged to non `Allowlisted`, than set **Value** to **false**.149	///150	/// * **Key** - contract address.151	/// * **Value** - is contract in [`Allowlisted`](SponsoringModeT::Allowlisted) mode.152	#[pallet::storage]153	pub(super) type AllowlistEnabled<T: Config> =154		StorageMap<Hasher = Twox128, Key = H160, Value = bool, QueryKind = ValueQuery>;155156	/// Storage for users that allowed for sponsorship.157	///158	/// ### Usage159	/// Prefer to delete record from storage if user no more allowed for sponsorship.160	///161	/// * **Key1** - contract address.162	/// * **Key2** - user that allowed for sponsorship.163	/// * **Value** - allowance for sponsorship.164	#[pallet::storage]165	pub(super) type Allowlist<T: Config> = StorageDoubleMap<166		Hasher1 = Twox128,167		Key1 = H160,168		Hasher2 = Twox128,169		Key2 = H160,170		Value = bool,171		QueryKind = ValueQuery,172	>;173174	#[pallet::event]175	#[pallet::generate_deposit(fn deposit_event)]176	pub enum Event<T: Config> {177		/// Contract sponsor was set.178		ContractSponsorSet(179			/// Contract address of the affected collection.180			H160,181			/// New sponsor address.182			T::AccountId,183		),184185		/// New sponsor was confirm.186		ContractSponsorshipConfirmed(187			/// Contract address of the affected collection.188			H160,189			/// New sponsor address.190			T::AccountId,191		),192193		/// Collection sponsor was removed.194		ContractSponsorRemoved(195			/// Contract address of the affected collection.196			H160,197		),198	}199200	impl<T: Config> Pallet<T> {201		/// Get contract owner.202		pub fn contract_owner(contract: H160) -> H160 {203			<Owner<T>>::get(contract)204		}205206		/// Set `sponsor` for `contract`.207		///208		/// `sender` must be owner of contract.209		pub fn set_sponsor(210			sender: &T::CrossAccountId,211			contract: H160,212			sponsor: &T::CrossAccountId,213		) -> DispatchResult {214			Pallet::<T>::ensure_owner(contract, *sender.as_eth())?;215			Sponsoring::<T>::insert(216				contract,217				SponsorshipState::<T::CrossAccountId>::Unconfirmed(sponsor.clone()),218			);219220			<Pallet<T>>::deposit_event(Event::<T>::ContractSponsorSet(221				contract,222				sponsor.as_sub().clone(),223			));224			<PalletEvm<T>>::deposit_log(225				ContractHelpersEvents::ContractSponsorSet {226					contract_address: contract,227					sponsor: *sponsor.as_eth(),228				}229				.to_log(contract),230			);231			Ok(())232		}233234		/// Force set `sponsor` for `contract`.235		///236		/// Differs from `set_sponsor` in that confirmation237		/// from the sponsor is not required.238		pub fn force_set_sponsor(239			contract_address: H160,240			sponsor: &T::CrossAccountId,241		) -> DispatchResult {242			Sponsoring::<T>::insert(243				contract_address,244				SponsorshipState::<T::CrossAccountId>::Confirmed(sponsor.clone()),245			);246247			let eth_sponsor = *sponsor.as_eth();248			let sub_sponsor = sponsor.as_sub().clone();249250			<Pallet<T>>::deposit_event(Event::<T>::ContractSponsorSet(251				contract_address,252				sub_sponsor.clone(),253			));254			<PalletEvm<T>>::deposit_log(255				ContractHelpersEvents::ContractSponsorSet {256					contract_address,257					sponsor: eth_sponsor,258				}259				.to_log(contract_address),260			);261262			<Pallet<T>>::deposit_event(Event::<T>::ContractSponsorshipConfirmed(263				contract_address,264				sub_sponsor,265			));266			<PalletEvm<T>>::deposit_log(267				ContractHelpersEvents::ContractSponsorshipConfirmed {268					contract_address,269					sponsor: eth_sponsor,270				}271				.to_log(contract_address),272			);273274			Ok(())275		}276277		/// Remove sponsor for `contract`.278		///279		/// `sender` must be owner of contract.280		pub fn remove_sponsor(281			sender: &T::CrossAccountId,282			contract_address: H160,283		) -> DispatchResult {284			Self::ensure_owner(contract_address, *sender.as_eth())?;285			Self::force_remove_sponsor(contract_address)286		}287288		/// Force remove `sponsor` for `contract`.289		///290		/// Differs from `remove_sponsor` in that291		/// it doesn't require consent from the `owner` of the contract.292		pub fn force_remove_sponsor(contract_address: H160) -> DispatchResult {293			Sponsoring::<T>::remove(contract_address);294295			Self::deposit_event(Event::<T>::ContractSponsorRemoved(contract_address));296			<PalletEvm<T>>::deposit_log(297				ContractHelpersEvents::ContractSponsorRemoved { contract_address }298					.to_log(contract_address),299			);300301			Ok(())302		}303304		/// Confirm sponsorship.305		///306		/// `sender` must be same that set via [`set_sponsor`].307		pub fn confirm_sponsorship(308			sender: &T::CrossAccountId,309			contract_address: H160,310		) -> DispatchResult {311			match Sponsoring::<T>::get(contract_address) {312				SponsorshipState::Unconfirmed(sponsor) => {313					ensure!(sponsor == *sender, Error::<T>::NoPermission);314					let eth_sponsor = *sponsor.as_eth();315					let sub_sponsor = sponsor.as_sub().clone();316					Sponsoring::<T>::insert(317						contract_address,318						SponsorshipState::<T::CrossAccountId>::Confirmed(sponsor),319					);320321					<Pallet<T>>::deposit_event(Event::<T>::ContractSponsorshipConfirmed(322						contract_address,323						sub_sponsor,324					));325					<PalletEvm<T>>::deposit_log(326						ContractHelpersEvents::ContractSponsorshipConfirmed {327							contract_address,328							sponsor: eth_sponsor,329						}330						.to_log(contract_address),331					);332333					Ok(())334				}335				SponsorshipState::Disabled | SponsorshipState::Confirmed(_) => {336					Err(Error::<T>::NoPendingSponsor.into())337				}338			}339		}340341		/// Get sponsor.342		pub fn get_sponsor(contract: H160) -> Option<T::CrossAccountId> {343			match Sponsoring::<T>::get(contract) {344				SponsorshipState::Disabled | SponsorshipState::Unconfirmed(_) => None,345				SponsorshipState::Confirmed(sponsor) => Some(sponsor),346			}347		}348349		/// Get current sponsoring mode, performing lazy migration from legacy storage350		pub fn sponsoring_mode(contract: H160) -> SponsoringModeT {351			<SponsoringMode<T>>::get(contract)352				.or_else(|| {353					#[allow(deprecated)]354					<SelfSponsoring<T>>::get(contract).then(|| SponsoringModeT::Allowlisted)355				})356				.unwrap_or_default()357		}358359		/// Reconfigure contract sponsoring mode360		pub fn set_sponsoring_mode(contract: H160, mode: SponsoringModeT) {361			if mode == SponsoringModeT::Disabled {362				<SponsoringMode<T>>::remove(contract);363			} else {364				<SponsoringMode<T>>::insert(contract, mode);365			}366			#[allow(deprecated)]367			<SelfSponsoring<T>>::remove(contract)368		}369370		/// Set duration between two sponsored contract calls371		pub fn set_sponsoring_rate_limit(contract: H160, rate_limit: T::BlockNumber) {372			<SponsoringRateLimit<T>>::insert(contract, rate_limit);373		}374375		/// Set maximum for gas limit of transaction376		pub fn set_sponsoring_fee_limit(contract: H160, fee_limit: U256) -> DispatchResult {377			<SponsoringFeeLimit<T>>::try_mutate(contract, |limits_map| {378				limits_map379					.try_insert(0xffffffff, fee_limit)380					.map_err(|_| <Error<T>>::TooManyMethodsHaveSponsoredLimit)381			})?;382			Ok(())383		}384385		/// Is user added to allowlist, or he is owner of specified contract386		pub fn allowed(contract: H160, user: H160) -> bool {387			<Allowlist<T>>::get(&contract, &user) || <Owner<T>>::get(&contract) == user388		}389390		/// Toggle contract allowlist access391		pub fn toggle_allowlist(contract: H160, enabled: bool) {392			<AllowlistEnabled<T>>::insert(contract, enabled)393		}394395		/// Toggle user presence in contract's allowlist396		pub fn toggle_allowed(contract: H160, user: H160, allowed: bool) {397			<Allowlist<T>>::insert(contract, user, allowed);398		}399400		/// Throw error if user is not allowed to reconfigure target contract401		pub fn ensure_owner(contract: H160, user: H160) -> DispatchResult {402			ensure!(<Owner<T>>::get(&contract) == user, Error::<T>::NoPermission);403			Ok(())404		}405	}406}407408/// Available contract sponsoring modes409#[derive(Encode, Decode, PartialEq, TypeInfo, MaxEncodedLen, Default)]410pub enum SponsoringModeT {411	/// Sponsoring is disabled412	#[default]413	Disabled,414	/// Only users from allowlist will be sponsored415	Allowlisted,416	/// All users will be sponsored417	Generous,418}419420impl SponsoringModeT {421	fn from_eth(v: u8) -> Option<Self> {422		Some(match v {423			0 => Self::Disabled,424			1 => Self::Allowlisted,425			2 => Self::Generous,426			_ => return None,427		})428	}429	#[allow(dead_code)]430	fn to_eth(self) -> u8 {431		match self {432			SponsoringModeT::Disabled => 0,433			SponsoringModeT::Allowlisted => 1,434			SponsoringModeT::Generous => 2,435		}436	}437}