12345678910111213141516171819extern 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}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(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 194 195 196 197 fn has_sponsor(&self, contract_address: Address) -> Result<bool> {198 Ok(Pallet::<T>::get_sponsor(contract_address).is_some())199 }200201 202 203 204 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 232 233 234 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 243 244 245 246 247 248 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 263 264 265 266 267 268 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 284 285 286 287 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 294 295 296 297 298 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 304 305 306 307 308 309 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 326 327 328 329 330 331 fn allowlist_enabled(&self, contract_address: Address) -> Result<bool> {332 Ok(<AllowlistEnabled<T>>::get(contract_address))333 }334335 336 337 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}352353354pub 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 369 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}395396397pub 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}403404405pub 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);