git.delta.rocks / unique-network / refs/commits / 586a87efbd8a

difftreelog

source

pallets/evm-contract-helpers/src/eth.rs14.6 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::{AbiWriter, AbiType},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(match Pallet::<T>::get_sponsor(contract_address) {188			Some(ref value) => Some(eth::CrossAddress::from_sub_cross_account::<T>(value)),189			None => None,190		})191	}192193	/// Check tat contract has confirmed sponsor.194	///195	/// @param contractAddress The contract for which the presence of a confirmed sponsor is checked.196	/// @return **true** if contract has confirmed sponsor.197	fn has_sponsor(&self, contract_address: Address) -> Result<bool> {198		Ok(Pallet::<T>::get_sponsor(contract_address).is_some())199	}200201	/// Check tat contract has pending sponsor.202	///203	/// @param contractAddress The contract for which the presence of a pending sponsor is checked.204	/// @return **true** if contract has pending sponsor.205	fn has_pending_sponsor(&self, contract_address: Address) -> Result<bool> {206		Ok(match Sponsoring::<T>::get(contract_address) {207			SponsorshipState::Disabled | SponsorshipState::Confirmed(_) => false,208			SponsorshipState::Unconfirmed(_) => true,209		})210	}211212	fn sponsoring_enabled(&self, contract_address: Address) -> Result<bool> {213		Ok(<Pallet<T>>::sponsoring_mode(contract_address) != SponsoringModeT::Disabled)214	}215216	fn set_sponsoring_mode(217		&mut self,218		caller: Caller,219		contract_address: Address,220		mode: SponsoringModeT,221	) -> Result<()> {222		self.recorder().consume_sload()?;223		self.recorder().consume_sstore()?;224225		<Pallet<T>>::ensure_owner(contract_address, caller).map_err(dispatch_to_evm::<T>)?;226		<Pallet<T>>::set_sponsoring_mode(contract_address, mode);227228		Ok(())229	}230231	/// Get current contract sponsoring rate limit232	/// @param contractAddress Contract to get sponsoring rate limit of233	/// @return uint32 Amount of blocks between two sponsored transactions234	fn sponsoring_rate_limit(&self, contract_address: Address) -> Result<u32> {235		self.recorder().consume_sload()?;236237		Ok(<SponsoringRateLimit<T>>::get(contract_address)238			.try_into()239			.map_err(|_| "rate limit > u32::MAX")?)240	}241242	/// Set contract sponsoring rate limit243	/// @dev Sponsoring rate limit - is a minimum amount of blocks that should244	///  pass between two sponsored transactions245	/// @param contractAddress Contract to change sponsoring rate limit of246	/// @param rateLimit Target rate limit247	/// @dev Only contract owner can change this setting248	fn set_sponsoring_rate_limit(249		&mut self,250		caller: Caller,251		contract_address: Address,252		rate_limit: u32,253	) -> Result<()> {254		self.recorder().consume_sload()?;255		self.recorder().consume_sstore()?;256257		<Pallet<T>>::ensure_owner(contract_address, caller).map_err(dispatch_to_evm::<T>)?;258		<Pallet<T>>::set_sponsoring_rate_limit(contract_address, rate_limit.into());259		Ok(())260	}261262	/// Set contract sponsoring fee limit263	/// @dev Sponsoring fee limit - is maximum fee that could be spent by264	///  single transaction265	/// @param contractAddress Contract to change sponsoring fee limit of266	/// @param feeLimit Fee limit267	/// @dev Only contract owner can change this setting268	fn set_sponsoring_fee_limit(269		&mut self,270		caller: Caller,271		contract_address: Address,272		fee_limit: U256,273	) -> Result<()> {274		self.recorder().consume_sload()?;275		self.recorder().consume_sstore()?;276277		<Pallet<T>>::ensure_owner(contract_address, caller).map_err(dispatch_to_evm::<T>)?;278		<Pallet<T>>::set_sponsoring_fee_limit(contract_address, fee_limit.into())279			.map_err(dispatch_to_evm::<T>)?;280		Ok(())281	}282283	/// Get current contract sponsoring fee limit284	/// @param contractAddress Contract to get sponsoring fee limit of285	/// @return uint256 Maximum amount of fee that could be spent by single286	///  transaction287	fn sponsoring_fee_limit(&self, contract_address: Address) -> Result<U256> {288		self.recorder().consume_sload()?;289290		Ok(get_sponsoring_fee_limit::<T>(contract_address))291	}292293	/// Is specified user present in contract allow list294	/// @dev Contract owner always implicitly included295	/// @param contractAddress Contract to check allowlist of296	/// @param user User to check297	/// @return bool Is specified users exists in contract allowlist298	fn allowed(&self, contract_address: Address, user: Address) -> Result<bool> {299		self.0.consume_sload()?;300		Ok(<Pallet<T>>::allowed(contract_address, user))301	}302303	/// Toggle user presence in contract allowlist304	/// @param contractAddress Contract to change allowlist of305	/// @param user Which user presence should be toggled306	/// @param isAllowed `true` if user should be allowed to be sponsored307	///  or call this contract, `false` otherwise308	/// @dev Only contract owner can change this setting309	fn toggle_allowed(310		&mut self,311		caller: Caller,312		contract_address: Address,313		user: Address,314		is_allowed: bool,315	) -> Result<()> {316		self.recorder().consume_sload()?;317		self.recorder().consume_sstore()?;318319		<Pallet<T>>::ensure_owner(contract_address, caller).map_err(dispatch_to_evm::<T>)?;320		<Pallet<T>>::toggle_allowed(contract_address, user, is_allowed);321322		Ok(())323	}324325	/// Is this contract has allowlist access enabled326	/// @dev Allowlist always can have users, and it is used for two purposes:327	///  in case of allowlist sponsoring mode, users will be sponsored if they exist in allowlist328	///  in case of allowlist access enabled, only users from allowlist may call this contract329	/// @param contractAddress Contract to get allowlist access of330	/// @return bool Is specified contract has allowlist access enabled331	fn allowlist_enabled(&self, contract_address: Address) -> Result<bool> {332		Ok(<AllowlistEnabled<T>>::get(contract_address))333	}334335	/// Toggle contract allowlist access336	/// @param contractAddress Contract to change allowlist access of337	/// @param enabled Should allowlist access to be enabled?338	fn toggle_allowlist(339		&mut self,340		caller: Caller,341		contract_address: Address,342		enabled: bool,343	) -> Result<()> {344		self.recorder().consume_sload()?;345		self.recorder().consume_sstore()?;346347		<Pallet<T>>::ensure_owner(contract_address, caller).map_err(dispatch_to_evm::<T>)?;348		<Pallet<T>>::toggle_allowlist(contract_address, enabled);349		Ok(())350	}351}352353/// Implements [`OnMethodCall`], which delegates call to [`ContractHelpers`]354pub struct HelpersOnMethodCall<T: Config>(PhantomData<*const T>);355impl<T: Config> OnMethodCall<T> for HelpersOnMethodCall<T>356where357	T::AccountId: AsRef<[u8; 32]>,358{359	fn is_reserved(contract: &sp_core::H160) -> bool {360		contract == &T::ContractAddress::get()361	}362363	fn is_used(contract: &sp_core::H160) -> bool {364		contract == &T::ContractAddress::get()365	}366367	fn call(handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {368		// TODO: Extract to another OnMethodCall handler369		if <AllowlistEnabled<T>>::get(handle.code_address())370			&& !<Pallet<T>>::allowed(handle.code_address(), handle.context().caller)371		{372			return Some(Err(PrecompileFailure::Revert {373				exit_status: ExitRevert::Reverted,374				output: {375					let mut writer = AbiWriter::new_call(evm_coder::fn_selector!(Error(string)));376					writer.string("Target contract is allowlisted");377					writer.finish()378				},379			}));380		}381382		if handle.code_address() != T::ContractAddress::get() {383			return None;384		}385386		let helpers = ContractHelpers::<T>(SubstrateRecorder::<T>::new(handle.remaining_gas()));387		pallet_evm_coder_substrate::call(handle, helpers)388	}389390	fn get_code(contract: &sp_core::H160) -> Option<Vec<u8>> {391		(contract == &T::ContractAddress::get())392			.then(|| include_bytes!("./stubs/ContractHelpers.raw").to_vec())393	}394}395396/// Hooks into contract creation, storing owner of newly deployed contract397pub struct HelpersOnCreate<T: Config>(PhantomData<*const T>);398impl<T: Config> OnCreate<T> for HelpersOnCreate<T> {399	fn on_create(owner: H160, contract: H160) {400		<Owner<T>>::insert(contract, owner);401	}402}403404/// Bridge to pallet-sponsoring405pub struct HelpersContractSponsoring<T: Config>(PhantomData<*const T>);406impl<T: Config> SponsorshipHandler<T::CrossAccountId, CallContext>407	for HelpersContractSponsoring<T>408{409	fn get_sponsor(410		who: &T::CrossAccountId,411		call_context: &CallContext,412	) -> Option<T::CrossAccountId> {413		let contract_address = call_context.contract_address;414		let mode = <Pallet<T>>::sponsoring_mode(contract_address);415		if mode == SponsoringModeT::Disabled {416			return None;417		}418419		let sponsor = match <Pallet<T>>::get_sponsor(contract_address) {420			Some(sponsor) => sponsor,421			None => return None,422		};423424		if mode == SponsoringModeT::Allowlisted425			&& !<Pallet<T>>::allowed(contract_address, *who.as_eth())426		{427			return None;428		}429		let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;430431		if let Some(last_tx_block) = <SponsorBasket<T>>::get(contract_address, who.as_eth()) {432			let limit = <SponsoringRateLimit<T>>::get(contract_address);433434			let timeout = last_tx_block + limit;435			if block_number < timeout {436				return None;437			}438		}439440		let sponsored_fee_limit = get_sponsoring_fee_limit::<T>(contract_address);441442		if call_context.max_fee > sponsored_fee_limit {443			return None;444		}445446		<SponsorBasket<T>>::insert(contract_address, who.as_eth(), block_number);447448		Some(sponsor)449	}450}451452fn get_sponsoring_fee_limit<T: Config>(contract_address: Address) -> U256 {453	<SponsoringFeeLimit<T>>::get(contract_address)454		.get(&0xffffffff)455		.cloned()456		.unwrap_or(U256::MAX)457}458459generate_stubgen!(contract_helpers_impl, ContractHelpersCall<()>, true);460generate_stubgen!(contract_helpers_iface, ContractHelpersCall<()>, false);