1#![cfg_attr(not(feature = "std"), no_std)]23pub use pallet::*;45#[frame_support::pallet]6pub mod pallet {7 use frame_support::sp_runtime::traits::StaticLookup;8 use frame_support::{pallet_prelude::*, traits::IsSubType};9 use frame_system::pallet_prelude::*;10 use frame_system::Config as SysConfig;11 use pallet_contracts::chain_extension::UncheckedFrom;12 use sp_runtime::{13 traits::{DispatchInfoOf, Hash, PostDispatchInfoOf, SignedExtension},14 transaction_validity,15 };16 use sp_std::vec::Vec;17 use up_sponsorship::SponsorshipHandler;1819 #[pallet::error]20 pub enum Error<T> {21 22 NoPermission,23 }2425 #[pallet::config]26 pub trait Config: frame_system::Config + pallet_contracts::Config {27 type DefaultSponsoringRateLimit: Get<Self::BlockNumber>;28 }2930 #[pallet::pallet]31 #[pallet::generate_store(pub(super) trait Store)]32 pub struct Pallet<T>(_);3334 #[pallet::storage]35 pub(super) type Owner<T: Config> = StorageMap<36 Hasher = Twox128,37 Key = T::AccountId,38 Value = T::AccountId,39 QueryKind = ValueQuery,40 >;4142 #[pallet::storage]43 pub(super) type AllowlistEnabled<T: Config> =44 StorageMap<Hasher = Twox128, Key = T::AccountId, Value = bool, QueryKind = ValueQuery>;4546 #[pallet::storage]47 pub(super) type Allowlist<T: Config> = StorageDoubleMap<48 Hasher1 = Twox128,49 Key1 = T::AccountId,50 Hasher2 = Twox64Concat,51 Key2 = T::AccountId,52 Value = bool,53 QueryKind = ValueQuery,54 >;5556 #[pallet::storage]57 pub(super) type SelfSponsoring<T: Config> =58 StorageMap<Hasher = Twox128, Key = T::AccountId, Value = bool, QueryKind = ValueQuery>;5960 #[pallet::storage]61 pub(super) type SponsoringRateLimit<T: Config> = StorageMap<62 Hasher = Twox128,63 Key = T::AccountId,64 Value = T::BlockNumber,65 QueryKind = ValueQuery,66 OnEmpty = T::DefaultSponsoringRateLimit,67 >;6869 #[pallet::storage]70 pub(super) type SponsorBasket<T: Config> = StorageDoubleMap<71 Hasher1 = Twox128,72 Key1 = T::AccountId,73 Hasher2 = Twox128,74 Key2 = T::AccountId,75 Value = T::BlockNumber,76 QueryKind = ValueQuery,77 >;7879 impl<T: Config> Pallet<T> {80 pub fn allowed(contract: T::AccountId, user: T::AccountId, default: bool) -> bool {81 if !<AllowlistEnabled<T>>::get(&contract) {82 return default;83 }84 <Allowlist<T>>::get(&contract, &user) || <Owner<T>>::get(contract) == user85 }86 }8788 #[pallet::call]89 impl<T: Config> Pallet<T> {90 #[pallet::weight(0)]91 pub fn toggle_sponsoring(92 origin: OriginFor<T>,93 contract: T::AccountId,94 sponsoring: bool,95 ) -> DispatchResult {96 let sender = ensure_signed(origin)?;97 ensure!(98 <Owner<T>>::get(&contract) == sender,99 <Error<T>>::NoPermission100 );101102 if sponsoring {103 <SelfSponsoring<T>>::insert(contract, true);104 } else {105 <SelfSponsoring<T>>::remove(contract);106 }107 Ok(())108 }109110 #[pallet::weight(0)]111 pub fn toggle_allowlist(112 origin: OriginFor<T>,113 contract: T::AccountId,114 enabled: bool,115 ) -> DispatchResult {116 let sender = ensure_signed(origin)?;117 ensure!(118 <Owner<T>>::get(&contract) == sender,119 <Error<T>>::NoPermission120 );121122 if enabled {123 <AllowlistEnabled<T>>::insert(contract, true);124 } else {125 <AllowlistEnabled<T>>::remove(contract);126 }127 Ok(())128 }129130 #[pallet::weight(0)]131 pub fn toggle_allowed(132 origin: OriginFor<T>,133 contract: T::AccountId,134 user: T::AccountId,135 allowed: bool,136 ) -> DispatchResult {137 let sender = ensure_signed(origin)?;138 ensure!(139 <Owner<T>>::get(&contract) == sender,140 <Error<T>>::NoPermission141 );142143 if allowed {144 <Allowlist<T>>::insert(contract, user, true);145 } else {146 <Allowlist<T>>::remove(contract, user);147 }148 Ok(())149 }150151 #[pallet::weight(0)]152 pub fn set_sponsoring_rate_limit(153 origin: OriginFor<T>,154 contract: T::AccountId,155 rate_limit: T::BlockNumber,156 ) -> DispatchResult {157 let sender = ensure_signed(origin)?;158 ensure!(159 <Owner<T>>::get(&contract) == sender,160 <Error<T>>::NoPermission161 );162163 <SponsoringRateLimit<T>>::insert(contract, rate_limit);164 Ok(())165 }166 }167168 #[derive(Encode, Decode, Clone, PartialEq, Eq)]169 pub struct ContractHelpersExtension<T>(PhantomData<T>);170 impl<T> core::fmt::Debug for ContractHelpersExtension<T> {171 fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {172 fmt.debug_struct("ContractHelpersExtension").finish()173 }174 }175176 type CodeHash<T> = <T as frame_system::Config>::Hash;177 impl<T> SignedExtension for ContractHelpersExtension<T>178 where179 T: Config + Send + Sync,180 <T as SysConfig>::Call: sp_runtime::traits::Dispatchable,181 <T as SysConfig>::Call: IsSubType<pallet_contracts::Call<T>>,182 T::AccountId: UncheckedFrom<T::Hash>,183 T::AccountId: AsRef<[u8]>,184 {185 const IDENTIFIER: &'static str = "ContractHelpers";186 type AccountId = T::AccountId;187 type Call = <T as SysConfig>::Call;188 type AdditionalSigned = ();189 type Pre = Option<(Self::AccountId, CodeHash<T>, Vec<u8>)>;190191 fn additional_signed(&self) -> Result<(), transaction_validity::TransactionValidityError> {192 Ok(())193 }194195 fn validate(196 &self,197 who: &T::AccountId,198 call: &Self::Call,199 _info: &DispatchInfoOf<Self::Call>,200 _len: usize,201 ) -> transaction_validity::TransactionValidity {202 if let Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) =203 IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call)204 {205 let called_contract: T::AccountId =206 T::Lookup::lookup((*dest).clone()).unwrap_or_default();207 if !<Pallet<T>>::allowed(called_contract, who.clone(), true) {208 return Err(transaction_validity::InvalidTransaction::Call.into());209 }210 }211 Ok(transaction_validity::ValidTransaction::default())212 }213214 fn pre_dispatch(215 self,216 who: &Self::AccountId,217 call: &Self::Call,218 _info: &DispatchInfoOf<Self::Call>,219 _len: usize,220 ) -> Result<Self::Pre, TransactionValidityError> {221 match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {222 Some(pallet_contracts::Call::instantiate(_, _, code_hash, _, salt)) => {223 Ok(Some((who.clone(), *code_hash, salt.clone())))224 }225 Some(pallet_contracts::Call::instantiate_with_code(_, _, code, _, salt)) => {226 let code_hash = &T::Hashing::hash(code);227 Ok(Some((who.clone(), *code_hash, salt.clone())))228 }229 _ => Ok(None),230 }231 }232233 fn post_dispatch(234 pre: Self::Pre,235 _info: &DispatchInfoOf<Self::Call>,236 _post_info: &PostDispatchInfoOf<Self::Call>,237 _len: usize,238 _result: &DispatchResult,239 ) -> Result<(), TransactionValidityError> {240 if let Some((who, code_hash, salt)) = pre {241 let new_contract_address =242 <pallet_contracts::Pallet<T>>::contract_address(&who, &code_hash, &salt);243 <Owner<T>>::insert(&new_contract_address, &who);244 }245246 Ok(())247 }248 }249250 pub struct ContractSponsorshipHandler<T>(PhantomData<T>);251 impl<T, C> SponsorshipHandler<T::AccountId, C> for ContractSponsorshipHandler<T>252 where253 T: Config,254 C: IsSubType<pallet_contracts::Call<T>>,255 T::AccountId: UncheckedFrom<T::Hash>,256 T::AccountId: AsRef<[u8]>,257 {258 fn get_sponsor(who: &T::AccountId, call: &C) -> Option<T::AccountId> {259 if let Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) =260 IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call)261 {262 let called_contract: T::AccountId =263 T::Lookup::lookup((*dest).clone()).unwrap_or_default();264 if <SelfSponsoring<T>>::get(&called_contract)265 && <Pallet<T>>::allowed(called_contract.clone(), who.clone(), false)266 {267 let last_tx_block = SponsorBasket::<T>::get(&called_contract, &who);268 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;269 let rate_limit = SponsoringRateLimit::<T>::get(&called_contract);270 let limit_time = last_tx_block + rate_limit;271272 if block_number >= limit_time {273 SponsorBasket::<T>::insert(&called_contract, who, block_number);274 return Some(called_contract);275 }276 }277 }278 None279 }280 }281}