git.delta.rocks / unique-network / refs/commits / 01896baccffe

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 alloc::{format, string::ToString};21use core::marker::PhantomData;22use evm_coder::{23	abi::AbiWriter, execution::Result, generate_stubgen, solidity_interface, types::*, ToLog,24};25use pallet_evm::{26	ExitRevert, OnCreate, OnMethodCall, PrecompileResult, PrecompileFailure, PrecompileHandle,27	account::CrossAccountId,28};29use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder, dispatch_to_evm};30use pallet_evm_transaction_payment::CallContext;31use sp_core::{H160, U256};32use up_data_structs::SponsorshipState;33use crate::{34	AllowlistEnabled, Config, Owner, Pallet, SponsorBasket, SponsoringFeeLimit,35	SponsoringRateLimit, SponsoringModeT, Sponsoring,36};37use frame_support::traits::Get;38use up_sponsorship::SponsorshipHandler;39use sp_std::vec::Vec;4041/// Pallet events.42#[derive(ToLog)]43pub enum ContractHelpersEvents {44	/// Contract sponsor was set.45	ContractSponsorSet {46		/// Contract address of the affected collection.47		#[indexed]48		contract_address: address,49		/// New sponsor address.50		sponsor: address,51	},5253	/// New sponsor was confirm.54	ContractSponsorshipConfirmed {55		/// Contract address of the affected collection.56		#[indexed]57		contract_address: address,58		/// New sponsor address.59		sponsor: address,60	},6162	/// Collection sponsor was removed.63	ContractSponsorRemoved {64		/// Contract address of the affected collection.65		#[indexed]66		contract_address: address,67	},68}6970/// See [`ContractHelpersCall`]71pub struct ContractHelpers<T: Config>(SubstrateRecorder<T>);72impl<T: Config> WithRecorder<T> for ContractHelpers<T> {73	fn recorder(&self) -> &SubstrateRecorder<T> {74		&self.075	}7677	fn into_recorder(self) -> SubstrateRecorder<T> {78		self.079	}80}8182/// @title Magic contract, which allows users to reconfigure other contracts83#[solidity_interface(name = ContractHelpers, events(ContractHelpersEvents))]84impl<T: Config> ContractHelpers<T>85where86	T::AccountId: AsRef<[u8; 32]>,87{88	/// Get user, which deployed specified contract89	/// @dev May return zero address in case if contract is deployed90	///  using uniquenetwork evm-migration pallet, or using other terms not91	///  intended by pallet-evm92	/// @dev Returns zero address if contract does not exists93	/// @param contractAddress Contract to get owner of94	/// @return address Owner of contract95	fn contract_owner(&self, contract_address: address) -> Result<address> {96		Ok(<Owner<T>>::get(contract_address))97	}9899	/// Set sponsor.100	/// @param contractAddress Contract for which a sponsor is being established.101	/// @param sponsor User address who set as pending sponsor.102	fn set_sponsor(103		&mut self,104		caller: caller,105		contract_address: address,106		sponsor: address,107	) -> Result<void> {108		self.recorder().consume_sload()?;109		self.recorder().consume_sstore()?;110111		Pallet::<T>::set_sponsor(112			&T::CrossAccountId::from_eth(caller),113			contract_address,114			&T::CrossAccountId::from_eth(sponsor),115		)116		.map_err(dispatch_to_evm::<T>)?;117118		Ok(())119	}120121	/// Set contract as self sponsored.122	///123	/// @param contractAddress Contract for which a self sponsoring is being enabled.124	fn self_sponsored_enable(&mut self, caller: caller, contract_address: address) -> Result<void> {125		self.recorder().consume_sload()?;126		self.recorder().consume_sstore()?;127128		let caller = T::CrossAccountId::from_eth(caller);129130		Pallet::<T>::ensure_owner(contract_address, *caller.as_eth())131			.map_err(dispatch_to_evm::<T>)?;132133		Pallet::<T>::force_set_sponsor(134			contract_address,135			&T::CrossAccountId::from_eth(contract_address),136		)137		.map_err(dispatch_to_evm::<T>)?;138139		Ok(())140	}141142	/// Remove sponsor.143	///144	/// @param contractAddress Contract for which a sponsorship is being removed.145	fn remove_sponsor(&mut self, caller: caller, contract_address: address) -> Result<void> {146		self.recorder().consume_sload()?;147		self.recorder().consume_sstore()?;148149		Pallet::<T>::remove_sponsor(&T::CrossAccountId::from_eth(caller), contract_address)150			.map_err(dispatch_to_evm::<T>)?;151152		Ok(())153	}154155	/// Confirm sponsorship.156	///157	/// @dev Caller must be same that set via [`setSponsor`].158	///159	/// @param contractAddress Сontract for which need to confirm sponsorship.160	fn confirm_sponsorship(&mut self, caller: caller, contract_address: address) -> Result<void> {161		self.recorder().consume_sload()?;162		self.recorder().consume_sstore()?;163164		Pallet::<T>::confirm_sponsorship(&T::CrossAccountId::from_eth(caller), contract_address)165			.map_err(dispatch_to_evm::<T>)?;166167		Ok(())168	}169170	/// Get current sponsor.171	///172	/// @param contractAddress The contract for which a sponsor is requested.173	/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.174	fn sponsor(&self, contract_address: address) -> Result<(address, uint256)> {175		let sponsor =176			Pallet::<T>::get_sponsor(contract_address).ok_or("Contract has no sponsor")?;177		Ok(pallet_common::eth::convert_cross_account_to_tuple::<T>(178			&sponsor,179		))180	}181182	/// Check tat contract has confirmed sponsor.183	///184	/// @param contractAddress The contract for which the presence of a confirmed sponsor is checked.185	/// @return **true** if contract has confirmed sponsor.186	fn has_sponsor(&self, contract_address: address) -> Result<bool> {187		Ok(Pallet::<T>::get_sponsor(contract_address).is_some())188	}189190	/// Check tat contract has pending sponsor.191	///192	/// @param contractAddress The contract for which the presence of a pending sponsor is checked.193	/// @return **true** if contract has pending sponsor.194	fn has_pending_sponsor(&self, contract_address: address) -> Result<bool> {195		Ok(match Sponsoring::<T>::get(contract_address) {196			SponsorshipState::Disabled | SponsorshipState::Confirmed(_) => false,197			SponsorshipState::Unconfirmed(_) => true,198		})199	}200201	fn sponsoring_enabled(&self, contract_address: address) -> Result<bool> {202		Ok(<Pallet<T>>::sponsoring_mode(contract_address) != SponsoringModeT::Disabled)203	}204205	fn set_sponsoring_mode(206		&mut self,207		caller: caller,208		contract_address: address,209		// TODO: implement support for enums in evm-coder210		mode: uint8,211	) -> Result<void> {212		self.recorder().consume_sload()?;213		self.recorder().consume_sstore()?;214215		<Pallet<T>>::ensure_owner(contract_address, caller).map_err(dispatch_to_evm::<T>)?;216		let mode = SponsoringModeT::from_eth(mode).ok_or("unknown mode")?;217		<Pallet<T>>::set_sponsoring_mode(contract_address, mode);218219		Ok(())220	}221222	/// Get current contract sponsoring rate limit223	/// @param contractAddress Contract to get sponsoring rate limit of224	/// @return uint32 Amount of blocks between two sponsored transactions225	fn sponsoring_rate_limit(&self, contract_address: address) -> Result<uint32> {226		self.recorder().consume_sload()?;227228		Ok(<SponsoringRateLimit<T>>::get(contract_address)229			.try_into()230			.map_err(|_| "rate limit > u32::MAX")?)231	}232233	/// Set contract sponsoring rate limit234	/// @dev Sponsoring rate limit - is a minimum amount of blocks that should235	///  pass between two sponsored transactions236	/// @param contractAddress Contract to change sponsoring rate limit of237	/// @param rateLimit Target rate limit238	/// @dev Only contract owner can change this setting239	fn set_sponsoring_rate_limit(240		&mut self,241		caller: caller,242		contract_address: address,243		rate_limit: uint32,244	) -> Result<void> {245		self.recorder().consume_sload()?;246		self.recorder().consume_sstore()?;247248		<Pallet<T>>::ensure_owner(contract_address, caller).map_err(dispatch_to_evm::<T>)?;249		<Pallet<T>>::set_sponsoring_rate_limit(contract_address, rate_limit.into());250		Ok(())251	}252253	/// Set contract sponsoring fee limit254	/// @dev Sponsoring fee limit - is maximum fee that could be spent by255	///  single transaction256	/// @param contractAddress Contract to change sponsoring fee limit of257	/// @param feeLimit Fee limit258	/// @dev Only contract owner can change this setting259	fn set_sponsoring_fee_limit(260		&mut self,261		caller: caller,262		contract_address: address,263		fee_limit: uint256,264	) -> Result<void> {265		self.recorder().consume_sload()?;266		self.recorder().consume_sstore()?;267268		<Pallet<T>>::ensure_owner(contract_address, caller).map_err(dispatch_to_evm::<T>)?;269		<Pallet<T>>::set_sponsoring_fee_limit(contract_address, fee_limit.into())270			.map_err(dispatch_to_evm::<T>)?;271		Ok(())272	}273274	/// Get current contract sponsoring fee limit275	/// @param contractAddress Contract to get sponsoring fee limit of276	/// @return uint256 Maximum amount of fee that could be spent by single277	///  transaction278	fn sponsoring_fee_limit(&self, contract_address: address) -> Result<uint256> {279		self.recorder().consume_sload()?;280281		Ok(get_sponsoring_fee_limit::<T>(contract_address))282	}283284	/// Is specified user present in contract allow list285	/// @dev Contract owner always implicitly included286	/// @param contractAddress Contract to check allowlist of287	/// @param user User to check288	/// @return bool Is specified users exists in contract allowlist289	fn allowed(&self, contract_address: address, user: address) -> Result<bool> {290		self.0.consume_sload()?;291		Ok(<Pallet<T>>::allowed(contract_address, user))292	}293294	/// Toggle user presence in contract allowlist295	/// @param contractAddress Contract to change allowlist of296	/// @param user Which user presence should be toggled297	/// @param isAllowed `true` if user should be allowed to be sponsored298	///  or call this contract, `false` otherwise299	/// @dev Only contract owner can change this setting300	fn toggle_allowed(301		&mut self,302		caller: caller,303		contract_address: address,304		user: address,305		is_allowed: bool,306	) -> Result<void> {307		self.recorder().consume_sload()?;308		self.recorder().consume_sstore()?;309310		<Pallet<T>>::ensure_owner(contract_address, caller).map_err(dispatch_to_evm::<T>)?;311		<Pallet<T>>::toggle_allowed(contract_address, user, is_allowed);312313		Ok(())314	}315316	/// Is this contract has allowlist access enabled317	/// @dev Allowlist always can have users, and it is used for two purposes:318	///  in case of allowlist sponsoring mode, users will be sponsored if they exist in allowlist319	///  in case of allowlist access enabled, only users from allowlist may call this contract320	/// @param contractAddress Contract to get allowlist access of321	/// @return bool Is specified contract has allowlist access enabled322	fn allowlist_enabled(&self, contract_address: address) -> Result<bool> {323		Ok(<AllowlistEnabled<T>>::get(contract_address))324	}325326	/// Toggle contract allowlist access327	/// @param contractAddress Contract to change allowlist access of328	/// @param enabled Should allowlist access to be enabled?329	fn toggle_allowlist(330		&mut self,331		caller: caller,332		contract_address: address,333		enabled: bool,334	) -> Result<void> {335		self.recorder().consume_sload()?;336		self.recorder().consume_sstore()?;337338		<Pallet<T>>::ensure_owner(contract_address, caller).map_err(dispatch_to_evm::<T>)?;339		<Pallet<T>>::toggle_allowlist(contract_address, enabled);340		Ok(())341	}342}343344/// Implements [`OnMethodCall`], which delegates call to [`ContractHelpers`]345pub struct HelpersOnMethodCall<T: Config>(PhantomData<*const T>);346impl<T: Config> OnMethodCall<T> for HelpersOnMethodCall<T>347where348	T::AccountId: AsRef<[u8; 32]>,349{350	fn is_reserved(contract: &sp_core::H160) -> bool {351		contract == &T::ContractAddress::get()352	}353354	fn is_used(contract: &sp_core::H160) -> bool {355		contract == &T::ContractAddress::get()356	}357358	fn call(handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {359		// TODO: Extract to another OnMethodCall handler360		if <AllowlistEnabled<T>>::get(handle.code_address())361			&& !<Pallet<T>>::allowed(handle.code_address(), handle.context().caller)362		{363			return Some(Err(PrecompileFailure::Revert {364				exit_status: ExitRevert::Reverted,365				output: {366					let mut writer = AbiWriter::new_call(evm_coder::fn_selector!(Error(string)));367					writer.string("Target contract is allowlisted");368					writer.finish()369				},370			}));371		}372373		if handle.code_address() != T::ContractAddress::get() {374			return None;375		}376377		let helpers = ContractHelpers::<T>(SubstrateRecorder::<T>::new(handle.remaining_gas()));378		pallet_evm_coder_substrate::call(handle, helpers)379	}380381	fn get_code(contract: &sp_core::H160) -> Option<Vec<u8>> {382		(contract == &T::ContractAddress::get())383			.then(|| include_bytes!("./stubs/ContractHelpers.raw").to_vec())384	}385}386387/// Hooks into contract creation, storing owner of newly deployed contract388pub struct HelpersOnCreate<T: Config>(PhantomData<*const T>);389impl<T: Config> OnCreate<T> for HelpersOnCreate<T> {390	fn on_create(owner: H160, contract: H160) {391		<Owner<T>>::insert(contract, owner);392	}393}394395/// Bridge to pallet-sponsoring396pub struct HelpersContractSponsoring<T: Config>(PhantomData<*const T>);397impl<T: Config> SponsorshipHandler<T::CrossAccountId, CallContext>398	for HelpersContractSponsoring<T>399{400	fn get_sponsor(401		who: &T::CrossAccountId,402		call_context: &CallContext,403	) -> Option<T::CrossAccountId> {404		let contract_address = call_context.contract_address;405		let mode = <Pallet<T>>::sponsoring_mode(contract_address);406		if mode == SponsoringModeT::Disabled {407			return None;408		}409410		let sponsor = match <Pallet<T>>::get_sponsor(contract_address) {411			Some(sponsor) => sponsor,412			None => return None,413		};414415		if mode == SponsoringModeT::Allowlisted416			&& !<Pallet<T>>::allowed(contract_address, *who.as_eth())417		{418			return None;419		}420		let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;421422		if let Some(last_tx_block) = <SponsorBasket<T>>::get(contract_address, who.as_eth()) {423			let limit = <SponsoringRateLimit<T>>::get(contract_address);424425			let timeout = last_tx_block + limit;426			if block_number < timeout {427				return None;428			}429		}430431		let sponsored_fee_limit = get_sponsoring_fee_limit::<T>(contract_address);432433		if call_context.max_fee > sponsored_fee_limit {434			return None;435		}436437		<SponsorBasket<T>>::insert(contract_address, who.as_eth(), block_number);438439		Some(sponsor)440	}441}442443fn get_sponsoring_fee_limit<T: Config>(contract_address: address) -> uint256 {444	<SponsoringFeeLimit<T>>::get(contract_address)445		.get(&0xffffffff)446		.cloned()447		.unwrap_or(U256::MAX)448}449450generate_stubgen!(contract_helpers_impl, ContractHelpersCall<()>, true);451generate_stubgen!(contract_helpers_iface, ContractHelpersCall<()>, false);