git.delta.rocks / unique-network / refs/commits / bce2cc1e9ab8

difftreelog

source

pallets/evm-contract-helpers/src/eth.rs14.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//! Implementation of magic contract1819extern crate alloc;20use core::marker::PhantomData;21use evm_coder::{22	abi::{AbiType, AbiEncode},23	generate_stubgen, solidity_interface,24	types::*,25	ToLog,26};27use pallet_common::eth;28use pallet_evm::{29	ExitRevert, OnCreate, OnMethodCall, PrecompileResult, PrecompileFailure, PrecompileHandle,30	account::CrossAccountId,31};32use pallet_evm_coder_substrate::{33	SubstrateRecorder, WithRecorder, dispatch_to_evm,34	execution::{Result, PreDispatch},35	frontier_contract,36};37use pallet_evm_transaction_payment::CallContext;38use sp_core::{H160, U256};39use up_data_structs::SponsorshipState;40use crate::{41	AllowlistEnabled, Config, Owner, Pallet, SponsorBasket, SponsoringFeeLimit,42	SponsoringRateLimit, SponsoringModeT, Sponsoring,43};44use frame_support::traits::Get;45use up_sponsorship::SponsorshipHandler;46use sp_std::vec::Vec;4748frontier_contract! {49	macro_rules! ContractHelpers_result {...}50	impl<T: Config> Contract for ContractHelpers<T> {...}51}5253/// Pallet events.54#[derive(ToLog)]55pub enum ContractHelpersEvents {56	/// Contract sponsor was set.57	ContractSponsorSet {58		/// Contract address of the affected collection.59		#[indexed]60		contract_address: Address,61		/// New sponsor address.62		sponsor: Address,63	},6465	/// New sponsor was confirm.66	ContractSponsorshipConfirmed {67		/// Contract address of the affected collection.68		#[indexed]69		contract_address: Address,70		/// New sponsor address.71		sponsor: Address,72	},7374	/// Collection sponsor was removed.75	ContractSponsorRemoved {76		/// Contract address of the affected collection.77		#[indexed]78		contract_address: Address,79	},80}8182/// See [`ContractHelpersCall`]83pub struct ContractHelpers<T: Config>(SubstrateRecorder<T>);84impl<T: Config> WithRecorder<T> for ContractHelpers<T> {85	fn recorder(&self) -> &SubstrateRecorder<T> {86		&self.087	}8889	fn into_recorder(self) -> SubstrateRecorder<T> {90		self.091	}92}9394/// @title Magic contract, which allows users to reconfigure other contracts95#[solidity_interface(name = ContractHelpers, events(ContractHelpersEvents), enum(derive(PreDispatch)))]96impl<T: Config> ContractHelpers<T>97where98	T::AccountId: AsRef<[u8; 32]>,99{100	/// Get user, which deployed specified contract101	/// @dev May return zero address in case if contract is deployed102	///  using uniquenetwork evm-migration pallet, or using other terms not103	///  intended by pallet-evm104	/// @dev Returns zero address if contract does not exists105	/// @param contractAddress Contract to get owner of106	/// @return address Owner of contract107	fn contract_owner(&self, contract_address: Address) -> Result<Address> {108		Ok(<Owner<T>>::get(contract_address))109	}110111	/// Set sponsor.112	/// @param contractAddress Contract for which a sponsor is being established.113	/// @param sponsor User address who set as pending sponsor.114	fn set_sponsor(115		&mut self,116		caller: Caller,117		contract_address: Address,118		sponsor: Address,119	) -> Result<()> {120		self.recorder().consume_sload()?;121		self.recorder().consume_sstore()?;122123		Pallet::<T>::set_sponsor(124			&T::CrossAccountId::from_eth(caller),125			contract_address,126			&T::CrossAccountId::from_eth(sponsor),127		)128		.map_err(dispatch_to_evm::<T>)?;129130		Ok(())131	}132133	/// Set contract as self sponsored.134	///135	/// @param contractAddress Contract for which a self sponsoring is being enabled.136	fn self_sponsored_enable(&mut self, caller: Caller, contract_address: Address) -> Result<()> {137		self.recorder().consume_sload()?;138		self.recorder().consume_sstore()?;139140		let caller = T::CrossAccountId::from_eth(caller);141142		Pallet::<T>::ensure_owner(contract_address, *caller.as_eth())143			.map_err(dispatch_to_evm::<T>)?;144145		Pallet::<T>::force_set_sponsor(146			contract_address,147			&T::CrossAccountId::from_eth(contract_address),148		)149		.map_err(dispatch_to_evm::<T>)?;150151		Ok(())152	}153154	/// Remove sponsor.155	///156	/// @param contractAddress Contract for which a sponsorship is being removed.157	fn remove_sponsor(&mut self, caller: Caller, contract_address: Address) -> Result<()> {158		self.recorder().consume_sload()?;159		self.recorder().consume_sstore()?;160161		Pallet::<T>::remove_sponsor(&T::CrossAccountId::from_eth(caller), contract_address)162			.map_err(dispatch_to_evm::<T>)?;163164		Ok(())165	}166167	/// Confirm sponsorship.168	///169	/// @dev Caller must be same that set via [`setSponsor`].170	///171	/// @param contractAddress Сontract for which need to confirm sponsorship.172	fn confirm_sponsorship(&mut self, caller: Caller, contract_address: Address) -> Result<()> {173		self.recorder().consume_sload()?;174		self.recorder().consume_sstore()?;175176		Pallet::<T>::confirm_sponsorship(&T::CrossAccountId::from_eth(caller), contract_address)177			.map_err(dispatch_to_evm::<T>)?;178179		Ok(())180	}181182	/// Get current sponsor.183	///184	/// @param contractAddress The contract for which a sponsor is requested.185	/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.186	fn sponsor(&self, contract_address: Address) -> Result<Option<eth::CrossAddress>> {187		Ok(Pallet::<T>::get_sponsor(contract_address)188			.as_ref()189			.map(eth::CrossAddress::from_sub_cross_account::<T>))190	}191192	/// Check tat contract has confirmed sponsor.193	///194	/// @param contractAddress The contract for which the presence of a confirmed sponsor is checked.195	/// @return **true** if contract has confirmed sponsor.196	fn has_sponsor(&self, contract_address: Address) -> Result<bool> {197		Ok(Pallet::<T>::get_sponsor(contract_address).is_some())198	}199200	/// Check tat contract has pending sponsor.201	///202	/// @param contractAddress The contract for which the presence of a pending sponsor is checked.203	/// @return **true** if contract has pending sponsor.204	fn has_pending_sponsor(&self, contract_address: Address) -> Result<bool> {205		Ok(match Sponsoring::<T>::get(contract_address) {206			SponsorshipState::Disabled | SponsorshipState::Confirmed(_) => false,207			SponsorshipState::Unconfirmed(_) => true,208		})209	}210211	fn sponsoring_enabled(&self, contract_address: Address) -> Result<bool> {212		Ok(<Pallet<T>>::sponsoring_mode(contract_address) != SponsoringModeT::Disabled)213	}214215	fn set_sponsoring_mode(216		&mut self,217		caller: Caller,218		contract_address: Address,219		mode: SponsoringModeT,220	) -> Result<()> {221		self.recorder().consume_sload()?;222		self.recorder().consume_sstore()?;223224		<Pallet<T>>::ensure_owner(contract_address, caller).map_err(dispatch_to_evm::<T>)?;225		<Pallet<T>>::set_sponsoring_mode(contract_address, mode);226227		Ok(())228	}229230	/// Get current contract sponsoring rate limit231	/// @param contractAddress Contract to get sponsoring rate limit of232	/// @return uint32 Amount of blocks between two sponsored transactions233	fn sponsoring_rate_limit(&self, contract_address: Address) -> Result<u32> {234		self.recorder().consume_sload()?;235236		Ok(<SponsoringRateLimit<T>>::get(contract_address)237			.try_into()238			.map_err(|_| "rate limit > u32::MAX")?)239	}240241	/// Set contract sponsoring rate limit242	/// @dev Sponsoring rate limit - is a minimum amount of blocks that should243	///  pass between two sponsored transactions244	/// @param contractAddress Contract to change sponsoring rate limit of245	/// @param rateLimit Target rate limit246	/// @dev Only contract owner can change this setting247	fn set_sponsoring_rate_limit(248		&mut self,249		caller: Caller,250		contract_address: Address,251		rate_limit: u32,252	) -> Result<()> {253		self.recorder().consume_sload()?;254		self.recorder().consume_sstore()?;255256		<Pallet<T>>::ensure_owner(contract_address, caller).map_err(dispatch_to_evm::<T>)?;257		<Pallet<T>>::set_sponsoring_rate_limit(contract_address, rate_limit.into());258		Ok(())259	}260261	/// Set contract sponsoring fee limit262	/// @dev Sponsoring fee limit - is maximum fee that could be spent by263	///  single transaction264	/// @param contractAddress Contract to change sponsoring fee limit of265	/// @param feeLimit Fee limit266	/// @dev Only contract owner can change this setting267	fn set_sponsoring_fee_limit(268		&mut self,269		caller: Caller,270		contract_address: Address,271		fee_limit: U256,272	) -> Result<()> {273		self.recorder().consume_sload()?;274		self.recorder().consume_sstore()?;275276		<Pallet<T>>::ensure_owner(contract_address, caller).map_err(dispatch_to_evm::<T>)?;277		<Pallet<T>>::set_sponsoring_fee_limit(contract_address, fee_limit)278			.map_err(dispatch_to_evm::<T>)?;279		Ok(())280	}281282	/// Get current contract sponsoring fee limit283	/// @param contractAddress Contract to get sponsoring fee limit of284	/// @return uint256 Maximum amount of fee that could be spent by single285	///  transaction286	fn sponsoring_fee_limit(&self, contract_address: Address) -> Result<U256> {287		self.recorder().consume_sload()?;288289		Ok(get_sponsoring_fee_limit::<T>(contract_address))290	}291292	/// Is specified user present in contract allow list293	/// @dev Contract owner always implicitly included294	/// @param contractAddress Contract to check allowlist of295	/// @param user User to check296	/// @return bool Is specified users exists in contract allowlist297	fn allowed(&self, contract_address: Address, user: Address) -> Result<bool> {298		self.0.consume_sload()?;299		Ok(<Pallet<T>>::allowed(contract_address, user))300	}301302	/// Toggle user presence in contract allowlist303	/// @param contractAddress Contract to change allowlist of304	/// @param user Which user presence should be toggled305	/// @param isAllowed `true` if user should be allowed to be sponsored306	///  or call this contract, `false` otherwise307	/// @dev Only contract owner can change this setting308	fn toggle_allowed(309		&mut self,310		caller: Caller,311		contract_address: Address,312		user: Address,313		is_allowed: bool,314	) -> Result<()> {315		self.recorder().consume_sload()?;316		self.recorder().consume_sstore()?;317318		<Pallet<T>>::ensure_owner(contract_address, caller).map_err(dispatch_to_evm::<T>)?;319		<Pallet<T>>::toggle_allowed(contract_address, user, is_allowed);320321		Ok(())322	}323324	/// Is this contract has allowlist access enabled325	/// @dev Allowlist always can have users, and it is used for two purposes:326	///  in case of allowlist sponsoring mode, users will be sponsored if they exist in allowlist327	///  in case of allowlist access enabled, only users from allowlist may call this contract328	/// @param contractAddress Contract to get allowlist access of329	/// @return bool Is specified contract has allowlist access enabled330	fn allowlist_enabled(&self, contract_address: Address) -> Result<bool> {331		Ok(<AllowlistEnabled<T>>::get(contract_address))332	}333334	/// Toggle contract allowlist access335	/// @param contractAddress Contract to change allowlist access of336	/// @param enabled Should allowlist access to be enabled?337	fn toggle_allowlist(338		&mut self,339		caller: Caller,340		contract_address: Address,341		enabled: bool,342	) -> Result<()> {343		self.recorder().consume_sload()?;344		self.recorder().consume_sstore()?;345346		<Pallet<T>>::ensure_owner(contract_address, caller).map_err(dispatch_to_evm::<T>)?;347		<Pallet<T>>::toggle_allowlist(contract_address, enabled);348		Ok(())349	}350}351352/// Implements [`OnMethodCall`], which delegates call to [`ContractHelpers`]353pub struct HelpersOnMethodCall<T: Config>(PhantomData<*const T>);354impl<T: Config> OnMethodCall<T> for HelpersOnMethodCall<T>355where356	T::AccountId: AsRef<[u8; 32]>,357{358	fn is_reserved(contract: &sp_core::H160) -> bool {359		contract == &T::ContractAddress::get()360	}361362	fn is_used(contract: &sp_core::H160) -> bool {363		contract == &T::ContractAddress::get()364	}365366	fn call(handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {367		// TODO: Extract to another OnMethodCall handler368		if <AllowlistEnabled<T>>::get(handle.code_address())369			&& !<Pallet<T>>::allowed(handle.code_address(), handle.context().caller)370		{371			return Some(Err(PrecompileFailure::Revert {372				exit_status: ExitRevert::Reverted,373				output: ("target contract is allowlisted",)374					.abi_encode_call(evm_coder::fn_selector!(Error(string))),375			}));376		}377378		if handle.code_address() != T::ContractAddress::get() {379			return None;380		}381382		let helpers = ContractHelpers::<T>(SubstrateRecorder::<T>::new(handle.remaining_gas()));383		pallet_evm_coder_substrate::call(handle, helpers)384	}385386	fn get_code(contract: &sp_core::H160) -> Option<Vec<u8>> {387		(contract == &T::ContractAddress::get())388			.then(|| include_bytes!("./stubs/ContractHelpers.raw").to_vec())389	}390}391392/// Hooks into contract creation, storing owner of newly deployed contract393pub struct HelpersOnCreate<T: Config>(PhantomData<*const T>);394impl<T: Config> OnCreate<T> for HelpersOnCreate<T> {395	fn on_create(owner: H160, contract: H160) {396		<Owner<T>>::insert(contract, owner);397	}398}399400/// Bridge to pallet-sponsoring401pub struct HelpersContractSponsoring<T: Config>(PhantomData<*const T>);402impl<T: Config> SponsorshipHandler<T::CrossAccountId, CallContext>403	for HelpersContractSponsoring<T>404{405	fn get_sponsor(406		who: &T::CrossAccountId,407		call_context: &CallContext,408	) -> Option<T::CrossAccountId> {409		let contract_address = call_context.contract_address;410		let mode = <Pallet<T>>::sponsoring_mode(contract_address);411		if mode == SponsoringModeT::Disabled {412			return None;413		}414415		let sponsor = match <Pallet<T>>::get_sponsor(contract_address) {416			Some(sponsor) => sponsor,417			None => return None,418		};419420		if mode == SponsoringModeT::Allowlisted421			&& !<Pallet<T>>::allowed(contract_address, *who.as_eth())422		{423			return None;424		}425		let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;426427		if let Some(last_tx_block) = <SponsorBasket<T>>::get(contract_address, who.as_eth()) {428			let limit = <SponsoringRateLimit<T>>::get(contract_address);429430			let timeout = last_tx_block + limit;431			if block_number < timeout {432				return None;433			}434		}435436		let sponsored_fee_limit = get_sponsoring_fee_limit::<T>(contract_address);437438		if call_context.max_fee > sponsored_fee_limit {439			return None;440		}441442		<SponsorBasket<T>>::insert(contract_address, who.as_eth(), block_number);443444		Some(sponsor)445	}446}447448fn get_sponsoring_fee_limit<T: Config>(contract_address: Address) -> U256 {449	<SponsoringFeeLimit<T>>::get(contract_address)450		.get(&0xffffffff)451		.cloned()452		.unwrap_or(U256::MAX)453}454455generate_stubgen!(contract_helpers_impl, ContractHelpersCall<()>, true);456generate_stubgen!(contract_helpers_iface, ContractHelpersCall<()>, false);