git.delta.rocks / unique-network / refs/commits / 7e798e96a17d

difftreelog

source

pallets/evm-contract-helpers/src/lib.rs13.0 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};22use evm_coder::AbiCoder;23pub use pallet::*;24pub use eth::*;25use scale_info::TypeInfo;26use frame_support::storage::bounded_btree_map::BoundedBTreeMap;27pub mod eth;2829/// Maximum number of methods per contract that could have fee limit30pub const MAX_FEE_LIMITED_METHODS: u32 = 5;3132#[frame_support::pallet]33pub mod pallet {34	pub use super::*;35	use crate::eth::ContractHelpersEvents;36	use frame_support::{pallet_prelude::*, sp_runtime::DispatchResult};37	use frame_system::{pallet_prelude::OriginFor, ensure_root};38	use sp_core::{H160, U256};39	use sp_std::vec::Vec;40	use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};41	use up_data_structs::SponsorshipState;42	use evm_coder::ToLog;4344	#[pallet::config]45	pub trait Config:46		frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config47	{48		/// Overarching event type.49		type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;5051		/// Address, under which magic contract will be available52		#[pallet::constant]53		type ContractAddress: Get<H160>;5455		/// In case of enabled sponsoring, but no sponsoring rate limit set,56		/// this value will be used implicitly57		type DefaultSponsoringRateLimit: Get<Self::BlockNumber>;58	}5960	#[pallet::error]61	pub enum Error<T> {62		/// This method is only executable by contract owner63		NoPermission,6465		/// No pending sponsor for contract.66		NoPendingSponsor,6768		/// Number of methods that sponsored limit is defined for exceeds maximum.69		TooManyMethodsHaveSponsoredLimit,70	}7172	#[pallet::pallet]73	#[pallet::generate_store(trait Store)]74	pub struct Pallet<T>(_);7576	/// Store owner for contract.77	///78	/// * **Key** - contract address.79	/// * **Value** - owner for contract.80	#[pallet::storage]81	pub(super) type Owner<T: Config> =82		StorageMap<Hasher = Twox128, Key = H160, Value = H160, QueryKind = ValueQuery>;8384	/// Deprecated: this storage is deprecated85	#[pallet::storage]86	type SelfSponsoring<T: Config> =87		StorageMap<Hasher = Twox128, Key = H160, Value = bool, QueryKind = ValueQuery>;8889	/// Store for contract sponsorship state.90	///91	/// * **Key** - contract address.92	/// * **Value** - sponsorship state.93	#[pallet::storage]94	pub(super) type Sponsoring<T: Config> = StorageMap<95		Hasher = Twox64Concat,96		Key = H160,97		Value = SponsorshipState<T::CrossAccountId>,98		QueryKind = ValueQuery,99	>;100101	/// Store for sponsoring mode.102	///103	/// ### Usage104	/// Prefer to delete collection from storage if mode chaged to [`Disabled`](SponsoringModeT::Disabled).105	///106	/// * **Key** - contract address.107	/// * **Value** - [`sponsoring mode`](SponsoringModeT).108	#[pallet::storage]109	pub(super) type SponsoringMode<T: Config> =110		StorageMap<Hasher = Twox128, Key = H160, Value = SponsoringModeT, QueryKind = OptionQuery>;111112	/// Storage for sponsoring rate limit in blocks.113	///114	/// * **Key** - contract address.115	/// * **Value** - amount of sponsored blocks.116	#[pallet::storage]117	pub(super) type SponsoringRateLimit<T: Config> = StorageMap<118		Hasher = Twox128,119		Key = H160,120		Value = T::BlockNumber,121		QueryKind = ValueQuery,122		OnEmpty = T::DefaultSponsoringRateLimit,123	>;124125	/// Storage for last sponsored block.126	///127	/// * **Key1** - contract address.128	/// * **Key2** - sponsored user address.129	/// * **Value** - last sponsored block number.130	#[pallet::storage]131	pub(super) type SponsoringFeeLimit<T: Config> = StorageMap<132		Hasher = Twox128,133		Key = H160,134		Value = BoundedBTreeMap<u32, U256, ConstU32<MAX_FEE_LIMITED_METHODS>>,135		QueryKind = ValueQuery,136	>;137138	#[pallet::storage]139	pub(super) type SponsorBasket<T: Config> = StorageDoubleMap<140		Hasher1 = Twox128,141		Key1 = H160,142		Hasher2 = Twox128,143		Key2 = H160,144		Value = T::BlockNumber,145		QueryKind = OptionQuery,146	>;147148	/// Storege for contracts with [`Allowlisted`](SponsoringModeT::Allowlisted) sponsoring mode.149	///150	/// ### Usage151	/// Prefer to delete collection from storage if mode chaged to non `Allowlisted`, than set **Value** to **false**.152	///153	/// * **Key** - contract address.154	/// * **Value** - is contract in [`Allowlisted`](SponsoringModeT::Allowlisted) mode.155	#[pallet::storage]156	pub(super) type AllowlistEnabled<T: Config> =157		StorageMap<Hasher = Twox128, Key = H160, Value = bool, QueryKind = ValueQuery>;158159	/// Storage for users that allowed for sponsorship.160	///161	/// ### Usage162	/// Prefer to delete record from storage if user no more allowed for sponsorship.163	///164	/// * **Key1** - contract address.165	/// * **Key2** - user that allowed for sponsorship.166	/// * **Value** - allowance for sponsorship.167	#[pallet::storage]168	pub(super) type Allowlist<T: Config> = StorageDoubleMap<169		Hasher1 = Twox128,170		Key1 = H160,171		Hasher2 = Twox128,172		Key2 = H160,173		Value = bool,174		QueryKind = ValueQuery,175	>;176177	#[pallet::event]178	#[pallet::generate_deposit(fn deposit_event)]179	pub enum Event<T: Config> {180		/// Contract sponsor was set.181		ContractSponsorSet(182			/// Contract address of the affected collection.183			H160,184			/// New sponsor address.185			T::AccountId,186		),187188		/// New sponsor was confirm.189		ContractSponsorshipConfirmed(190			/// Contract address of the affected collection.191			H160,192			/// New sponsor address.193			T::AccountId,194		),195196		/// Collection sponsor was removed.197		ContractSponsorRemoved(198			/// Contract address of the affected collection.199			H160,200		),201	}202203	#[pallet::call]204	impl<T: Config> Pallet<T> {205		/// Migrate contract to use `SponsoringMode` storage instead of `SelfSponsoring`206		#[pallet::call_index(0)]207		#[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(208			addresses.len() as u64, addresses.len() as u64 * 2209		))]210		pub fn migrate_from_self_sponsoring(211			origin: OriginFor<T>,212			addresses: Vec<H160>,213		) -> DispatchResult {214			ensure_root(origin)?;215			for address in addresses {216				if <SelfSponsoring<T>>::get(address) {217					<SponsoringMode<T>>::set(address, Some(SponsoringModeT::Allowlisted));218					<SelfSponsoring<T>>::remove(address);219				}220			}221			Ok(())222		}223	}224225	impl<T: Config> Pallet<T> {226		/// Get contract owner.227		pub fn contract_owner(contract: H160) -> H160 {228			<Owner<T>>::get(contract)229		}230231		/// Set `sponsor` for `contract`.232		///233		/// `sender` must be owner of contract.234		pub fn set_sponsor(235			sender: &T::CrossAccountId,236			contract: H160,237			sponsor: &T::CrossAccountId,238		) -> DispatchResult {239			Pallet::<T>::ensure_owner(contract, *sender.as_eth())?;240			Sponsoring::<T>::insert(241				contract,242				SponsorshipState::<T::CrossAccountId>::Unconfirmed(sponsor.clone()),243			);244245			<Pallet<T>>::deposit_event(Event::<T>::ContractSponsorSet(246				contract,247				sponsor.as_sub().clone(),248			));249			<PalletEvm<T>>::deposit_log(250				ContractHelpersEvents::ContractSponsorSet {251					contract_address: contract,252					sponsor: *sponsor.as_eth(),253				}254				.to_log(contract),255			);256			Ok(())257		}258259		/// Force set `sponsor` for `contract`.260		///261		/// Differs from `set_sponsor` in that confirmation262		/// from the sponsor is not required.263		pub fn force_set_sponsor(264			contract_address: H160,265			sponsor: &T::CrossAccountId,266		) -> DispatchResult {267			Sponsoring::<T>::insert(268				contract_address,269				SponsorshipState::<T::CrossAccountId>::Confirmed(sponsor.clone()),270			);271272			let eth_sponsor = *sponsor.as_eth();273			let sub_sponsor = sponsor.as_sub().clone();274275			<Pallet<T>>::deposit_event(Event::<T>::ContractSponsorSet(276				contract_address,277				sub_sponsor.clone(),278			));279			<PalletEvm<T>>::deposit_log(280				ContractHelpersEvents::ContractSponsorSet {281					contract_address,282					sponsor: eth_sponsor,283				}284				.to_log(contract_address),285			);286287			<Pallet<T>>::deposit_event(Event::<T>::ContractSponsorshipConfirmed(288				contract_address,289				sub_sponsor,290			));291			<PalletEvm<T>>::deposit_log(292				ContractHelpersEvents::ContractSponsorshipConfirmed {293					contract_address,294					sponsor: eth_sponsor,295				}296				.to_log(contract_address),297			);298299			Ok(())300		}301302		/// Remove sponsor for `contract`.303		///304		/// `sender` must be owner of contract.305		pub fn remove_sponsor(306			sender: &T::CrossAccountId,307			contract_address: H160,308		) -> DispatchResult {309			Self::ensure_owner(contract_address, *sender.as_eth())?;310			Self::force_remove_sponsor(contract_address)311		}312313		/// Force remove `sponsor` for `contract`.314		///315		/// Differs from `remove_sponsor` in that316		/// it doesn't require consent from the `owner` of the contract.317		pub fn force_remove_sponsor(contract_address: H160) -> DispatchResult {318			Sponsoring::<T>::remove(contract_address);319320			Self::deposit_event(Event::<T>::ContractSponsorRemoved(contract_address));321			<PalletEvm<T>>::deposit_log(322				ContractHelpersEvents::ContractSponsorRemoved { contract_address }323					.to_log(contract_address),324			);325326			Ok(())327		}328329		/// Confirm sponsorship.330		///331		/// `sender` must be same that set via [`set_sponsor`].332		pub fn confirm_sponsorship(333			sender: &T::CrossAccountId,334			contract_address: H160,335		) -> DispatchResult {336			match Sponsoring::<T>::get(contract_address) {337				SponsorshipState::Unconfirmed(sponsor) => {338					ensure!(sponsor == *sender, Error::<T>::NoPermission);339					let eth_sponsor = *sponsor.as_eth();340					let sub_sponsor = sponsor.as_sub().clone();341					Sponsoring::<T>::insert(342						contract_address,343						SponsorshipState::<T::CrossAccountId>::Confirmed(sponsor),344					);345346					<Pallet<T>>::deposit_event(Event::<T>::ContractSponsorshipConfirmed(347						contract_address,348						sub_sponsor,349					));350					<PalletEvm<T>>::deposit_log(351						ContractHelpersEvents::ContractSponsorshipConfirmed {352							contract_address,353							sponsor: eth_sponsor,354						}355						.to_log(contract_address),356					);357358					Ok(())359				}360				SponsorshipState::Disabled | SponsorshipState::Confirmed(_) => {361					Err(Error::<T>::NoPendingSponsor.into())362				}363			}364		}365366		/// Get sponsor.367		pub fn get_sponsor(contract: H160) -> Option<T::CrossAccountId> {368			match Sponsoring::<T>::get(contract) {369				SponsorshipState::Disabled | SponsorshipState::Unconfirmed(_) => None,370				SponsorshipState::Confirmed(sponsor) => Some(sponsor),371			}372		}373374		/// Get current sponsoring mode, performing lazy migration from legacy storage375		/// Deprecated: this method is for deprecated storage376		pub fn sponsoring_mode(contract: H160) -> SponsoringModeT {377			<SponsoringMode<T>>::get(contract)378				.or_else(|| {379					#[allow(deprecated)]380					<SelfSponsoring<T>>::get(contract).then(|| SponsoringModeT::Allowlisted)381				})382				.unwrap_or_default()383		}384385		/// Reconfigure contract sponsoring mode386		/// Deprecated: this method is for deprecated storage387		pub fn set_sponsoring_mode(contract: H160, mode: SponsoringModeT) {388			if mode == SponsoringModeT::Disabled {389				<SponsoringMode<T>>::remove(contract);390			} else {391				<SponsoringMode<T>>::insert(contract, mode);392			}393			#[allow(deprecated)]394			<SelfSponsoring<T>>::remove(contract)395		}396397		/// Set duration between two sponsored contract calls398		pub fn set_sponsoring_rate_limit(contract: H160, rate_limit: T::BlockNumber) {399			<SponsoringRateLimit<T>>::insert(contract, rate_limit);400		}401402		/// Set maximum for gas limit of transaction403		pub fn set_sponsoring_fee_limit(contract: H160, fee_limit: U256) -> DispatchResult {404			<SponsoringFeeLimit<T>>::try_mutate(contract, |limits_map| {405				limits_map406					.try_insert(0xffffffff, fee_limit)407					.map_err(|_| <Error<T>>::TooManyMethodsHaveSponsoredLimit)408			})?;409			Ok(())410		}411412		/// Is user added to allowlist, or he is owner of specified contract413		pub fn allowed(contract: H160, user: H160) -> bool {414			<Allowlist<T>>::get(&contract, &user) || <Owner<T>>::get(&contract) == user415		}416417		/// Toggle contract allowlist access418		pub fn toggle_allowlist(contract: H160, enabled: bool) {419			<AllowlistEnabled<T>>::insert(contract, enabled)420		}421422		/// Toggle user presence in contract's allowlist423		pub fn toggle_allowed(contract: H160, user: H160, allowed: bool) {424			<Allowlist<T>>::insert(contract, user, allowed);425		}426427		/// Throw error if user is not allowed to reconfigure target contract428		pub fn ensure_owner(contract: H160, user: H160) -> DispatchResult {429			ensure!(<Owner<T>>::get(&contract) == user, Error::<T>::NoPermission);430			Ok(())431		}432	}433}434435/// Available contract sponsoring modes436#[derive(437	Encode, Decode, Debug, PartialEq, TypeInfo, MaxEncodedLen, Default, AbiCoder, Clone, Copy,438)]439#[repr(u8)]440pub enum SponsoringModeT {441	/// Sponsoring is disabled442	#[default]443	Disabled,444	/// Only users from allowlist will be sponsored445	Allowlisted,446	/// All users will be sponsored447	Generous,448}