12345678910111213141516171819extern 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}525354#[derive(ToLog)]55pub enum ContractHelpersEvents {56 57 ContractSponsorSet {58 59 #[indexed]60 contract_address: Address,61 62 sponsor: Address,63 },6465 66 ContractSponsorshipConfirmed {67 68 #[indexed]69 contract_address: Address,70 71 sponsor: Address,72 },7374 75 ContractSponsorRemoved {76 77 #[indexed]78 contract_address: Address,79 },80}818283pub 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}939495#[solidity_interface(name = ContractHelpers, events(ContractHelpersEvents), enum(derive(PreDispatch)))]96impl<T: Config> ContractHelpers<T>97where98 T::AccountId: AsRef<[u8; 32]>,99{100 101 102 103 104 105 106 107 fn contract_owner(&self, contract_address: Address) -> Result<Address> {108 Ok(<Owner<T>>::get(contract_address))109 }110111 112 113 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 134 135 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 155 156 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 168 169 170 171 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 183 184 185 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 193 194 195 196 fn has_sponsor(&self, contract_address: Address) -> Result<bool> {197 Ok(Pallet::<T>::get_sponsor(contract_address).is_some())198 }199200 201 202 203 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 231 232 233 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 242 243 244 245 246 247 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 262 263 264 265 266 267 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 283 284 285 286 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 293 294 295 296 297 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 303 304 305 306 307 308 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 325 326 327 328 329 330 fn allowlist_enabled(&self, contract_address: Address) -> Result<bool> {331 Ok(<AllowlistEnabled<T>>::get(contract_address))332 }333334 335 336 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}351352353pub 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 368 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}391392393pub 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}399400401pub 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);