1234567891011121314151617use core::marker::PhantomData;18use evm_coder::{abi::AbiWriter, execution::*, generate_stubgen, solidity_interface, types::*, ToLog};19use ethereum as _;20use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};21use pallet_evm::{22 ExitRevert, OnCreate, OnMethodCall, PrecompileResult, PrecompileFailure,23 account::CrossAccountId, Pallet as PalletEvm24};25use sp_core::H160;26use up_data_structs::{27 CreateCollectionData, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,28 MAX_COLLECTION_NAME_LENGTH,29};30use crate::{31 AllowlistEnabled, Config, Owner, Pallet, SponsorBasket, SponsoringRateLimit, SponsoringModeT,32};33use frame_support::traits::Get;34use up_sponsorship::SponsorshipHandler;35use sp_std::vec::Vec;36use alloc::format;3738struct ContractHelpers<T: Config>(SubstrateRecorder<T>);39impl<T: Config> WithRecorder<T> for ContractHelpers<T> {40 fn recorder(&self) -> &SubstrateRecorder<T> {41 &self.042 }4344 fn into_recorder(self) -> SubstrateRecorder<T> {45 self.046 }47}4849#[derive(ToLog)]50pub enum ContractHelperEvent {51 CollectionCreated {52 #[indexed]53 owner: address,54 #[indexed]55 collection_id: address,56 },57}5859#[solidity_interface(name = "ContractHelpers")]60impl<T: Config> ContractHelpers<T> {61 fn contract_owner(&self, contract_address: address) -> Result<address> {62 Ok(<Owner<T>>::get(contract_address))63 }6465 fn sponsoring_enabled(&self, contract_address: address) -> Result<bool> {66 Ok(<Pallet<T>>::sponsoring_mode(contract_address) != SponsoringModeT::Disabled)67 }6869 70 fn toggle_sponsoring(71 &mut self,72 caller: caller,73 contract_address: address,74 enabled: bool,75 ) -> Result<void> {76 <Pallet<T>>::ensure_owner(contract_address, caller)?;77 <Pallet<T>>::toggle_sponsoring(contract_address, enabled);78 Ok(())79 }8081 fn set_sponsoring_mode(82 &mut self,83 caller: caller,84 contract_address: address,85 mode: uint8,86 ) -> Result<void> {87 <Pallet<T>>::ensure_owner(contract_address, caller)?;88 let mode = SponsoringModeT::from_eth(mode).ok_or("unknown mode")?;89 <Pallet<T>>::set_sponsoring_mode(contract_address, mode);90 Ok(())91 }9293 fn sponsoring_mode(&self, contract_address: address) -> Result<uint8> {94 Ok(<Pallet<T>>::sponsoring_mode(contract_address).to_eth())95 }9697 fn set_sponsoring_rate_limit(98 &mut self,99 caller: caller,100 contract_address: address,101 rate_limit: uint32,102 ) -> Result<void> {103 <Pallet<T>>::ensure_owner(contract_address, caller)?;104 <Pallet<T>>::set_sponsoring_rate_limit(contract_address, rate_limit.into());105 Ok(())106 }107108 fn get_sponsoring_rate_limit(&self, contract_address: address) -> Result<uint32> {109 Ok(<SponsoringRateLimit<T>>::get(contract_address)110 .try_into()111 .map_err(|_| "rate limit > u32::MAX")?)112 }113114 fn allowed(&self, contract_address: address, user: address) -> Result<bool> {115 self.0.consume_sload()?;116 Ok(<Pallet<T>>::allowed(contract_address, user))117 }118119 fn allowlist_enabled(&self, contract_address: address) -> Result<bool> {120 Ok(<AllowlistEnabled<T>>::get(contract_address))121 }122123 fn toggle_allowlist(124 &mut self,125 caller: caller,126 contract_address: address,127 enabled: bool,128 ) -> Result<void> {129 <Pallet<T>>::ensure_owner(contract_address, caller)?;130 <Pallet<T>>::toggle_allowlist(contract_address, enabled);131 Ok(())132 }133134 fn toggle_allowed(135 &mut self,136 caller: caller,137 contract_address: address,138 user: address,139 allowed: bool,140 ) -> Result<void> {141 <Pallet<T>>::ensure_owner(contract_address, caller)?;142 <Pallet<T>>::toggle_allowed(contract_address, user, allowed);143 Ok(())144 }145146 fn create_721_collection(147 &self,148 caller: caller,149 name: string,150 description: string,151 token_prefix: string,152 ) -> Result<address> {153 let caller = T::CrossAccountId::from_eth(caller);154 let name = name155 .encode_utf16()156 .collect::<Vec<u16>>()157 .try_into()158 .map_err(|_| error_feild_too_long("name", MAX_COLLECTION_NAME_LENGTH))?;159 let description = description160 .encode_utf16()161 .collect::<Vec<u16>>()162 .try_into()163 .map_err(|_| error_feild_too_long("description", MAX_COLLECTION_DESCRIPTION_LENGTH))?;164 let token_prefix = token_prefix165 .into_bytes()166 .try_into()167 .map_err(|_| error_feild_too_long("token_prefix", MAX_TOKEN_PREFIX_LENGTH))?;168169 let data = CreateCollectionData {170 name,171 description,172 token_prefix,173 ..Default::default()174 };175176 let collection_id =177 <pallet_nonfungible::Pallet<T>>::init_collection(caller.as_sub().clone(), data)178 .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;179180 let address = pallet_common::eth::collection_id_to_address(collection_id);181182 <PalletEvm<T>>::deposit_log(183 ContractHelperEvent::CollectionCreated {184 owner: *caller.as_eth(),185 collection_id: address,186 }187 .to_log(address),188 );189 Ok(address)190 }191}192193fn error_feild_too_long(feild: &str, bound: u32) -> Error {194 Error::Revert(format!("{} is too long. Max length is {}.", feild, bound))195}196197pub struct HelpersOnMethodCall<T: Config>(PhantomData<*const T>);198impl<T: Config> OnMethodCall<T> for HelpersOnMethodCall<T> {199 fn is_reserved(contract: &sp_core::H160) -> bool {200 contract == &T::ContractAddress::get()201 }202203 fn is_used(contract: &sp_core::H160) -> bool {204 contract == &T::ContractAddress::get()205 }206207 fn call(208 source: &sp_core::H160,209 target: &sp_core::H160,210 gas_left: u64,211 input: &[u8],212 value: sp_core::U256,213 ) -> Option<PrecompileResult> {214 215 if <AllowlistEnabled<T>>::get(target) && !<Pallet<T>>::allowed(*target, *source) {216 return Some(Err(PrecompileFailure::Revert {217 exit_status: ExitRevert::Reverted,218 cost: 0,219 output: {220 let mut writer = AbiWriter::new_call(evm_coder::fn_selector!(Error(string)));221 writer.string("Target contract is allowlisted");222 writer.finish()223 },224 }));225 }226227 if target != &T::ContractAddress::get() {228 return None;229 }230231 let helpers = ContractHelpers::<T>(SubstrateRecorder::<T>::new(gas_left));232 pallet_evm_coder_substrate::call(*source, helpers, value, input)233 }234235 fn get_code(contract: &sp_core::H160) -> Option<Vec<u8>> {236 (contract == &T::ContractAddress::get())237 .then(|| include_bytes!("./stubs/ContractHelpers.raw").to_vec())238 }239}240241pub struct HelpersOnCreate<T: Config>(PhantomData<*const T>);242impl<T: Config> OnCreate<T> for HelpersOnCreate<T> {243 fn on_create(owner: H160, contract: H160) {244 <Owner<T>>::insert(contract, owner);245 }246}247248pub struct HelpersContractSponsoring<T: Config>(PhantomData<*const T>);249impl<T: Config> SponsorshipHandler<T::CrossAccountId, (H160, Vec<u8>)>250 for HelpersContractSponsoring<T>251{252 fn get_sponsor(who: &T::CrossAccountId, call: &(H160, Vec<u8>)) -> Option<T::CrossAccountId> {253 let mode = <Pallet<T>>::sponsoring_mode(call.0);254 if mode == SponsoringModeT::Disabled {255 return None;256 }257258 if mode == SponsoringModeT::Allowlisted && !<Pallet<T>>::allowed(call.0, *who.as_eth()) {259 return None;260 }261 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;262263 if let Some(last_tx_block) = <SponsorBasket<T>>::get(&call.0, who.as_eth()) {264 let limit = <SponsoringRateLimit<T>>::get(&call.0);265266 let timeout = last_tx_block + limit;267 if block_number < timeout {268 return None;269 }270 }271272 <SponsorBasket<T>>::insert(&call.0, who.as_eth(), block_number);273274 let sponsor = T::CrossAccountId::from_eth(call.0);275 Some(sponsor)276 }277}278279generate_stubgen!(contract_helpers_impl, ContractHelpersCall<()>, true);280generate_stubgen!(contract_helpers_iface, ContractHelpersCall<()>, false);