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 pallet_contracts::chain_extension::UncheckedFrom;11 use sp_runtime::{12 traits::{DispatchInfoOf, Hash, PostDispatchInfoOf, SignedExtension},13 transaction_validity,14 };15 use sp_std::vec::Vec;16 use up_sponsorship::SponsorshipHandler;1718 #[pallet::error]19 pub enum Error<T> {20 21 NoPermission,22 }2324 #[pallet::config]25 pub trait Config: frame_system::Config + pallet_contracts::Config {26 type DefaultSponsoringRateLimit: Get<Self::BlockNumber>;27 }2829 #[pallet::pallet]30 #[pallet::generate_store(pub(super) trait Store)]31 pub struct Pallet<T>(_);3233 #[pallet::storage]34 pub(super) type Owner<T: Config> = StorageMap<35 Hasher = Twox128,36 Key = T::AccountId,37 Value = T::AccountId,38 QueryKind = ValueQuery,39 >;4041 #[pallet::storage]42 pub(super) type AllowlistEnabled<T: Config> =43 StorageMap<Hasher = Twox128, Key = T::AccountId, Value = bool, QueryKind = ValueQuery>;4445 #[pallet::storage]46 pub(super) type Allowlist<T: Config> = StorageDoubleMap<47 Hasher1 = Twox128,48 Key1 = T::AccountId,49 Hasher2 = Twox64Concat,50 Key2 = T::AccountId,51 Value = bool,52 QueryKind = ValueQuery,53 >;5455 #[pallet::storage]56 pub(super) type SelfSponsoring<T: Config> =57 StorageMap<Hasher = Twox128, Key = T::AccountId, Value = bool, QueryKind = ValueQuery>;5859 #[pallet::storage]60 pub(super) type SponsoringRateLimit<T: Config> = StorageMap<61 Hasher = Twox128,62 Key = T::AccountId,63 Value = T::BlockNumber,64 QueryKind = ValueQuery,65 OnEmpty = T::DefaultSponsoringRateLimit,66 >;6768 #[pallet::storage]69 pub(super) type SponsorBasket<T: Config> = StorageDoubleMap<70 Hasher1 = Twox128,71 Key1 = T::AccountId,72 Hasher2 = Twox128,73 Key2 = T::AccountId,74 Value = T::BlockNumber,75 QueryKind = ValueQuery,76 >;7778 impl<T: Config> Pallet<T> {79 pub fn allowed(contract: T::AccountId, user: T::AccountId, default: bool) -> bool {80 if !<AllowlistEnabled<T>>::get(&contract) {81 return default;82 }83 <Allowlist<T>>::get(&contract, &user) || <Owner<T>>::get(contract) == user84 }85 }8687 #[pallet::call]88 impl<T: Config> Pallet<T> {89 #[pallet::weight(0)]90 pub fn toggle_sponsoring(91 origin: OriginFor<T>,92 contract: T::AccountId,93 sponsoring: bool,94 ) -> DispatchResult {95 let sender = ensure_signed(origin)?;96 ensure!(97 <Owner<T>>::get(&contract) == sender,98 <Error<T>>::NoPermission99 );100101 if sponsoring {102 <SelfSponsoring<T>>::insert(contract, true);103 } else {104 <SelfSponsoring<T>>::remove(contract);105 }106 Ok(())107 }108109 #[pallet::weight(0)]110 pub fn toggle_allowlist(111 origin: OriginFor<T>,112 contract: T::AccountId,113 enabled: bool,114 ) -> DispatchResult {115 let sender = ensure_signed(origin)?;116 ensure!(117 <Owner<T>>::get(&contract) == sender,118 <Error<T>>::NoPermission119 );120121 if enabled {122 <AllowlistEnabled<T>>::insert(contract, true);123 } else {124 <AllowlistEnabled<T>>::remove(contract);125 }126 Ok(())127 }128129 #[pallet::weight(0)]130 pub fn toggle_allowed(131 origin: OriginFor<T>,132 contract: T::AccountId,133 user: T::AccountId,134 allowed: bool,135 ) -> DispatchResult {136 let sender = ensure_signed(origin)?;137 ensure!(138 <Owner<T>>::get(&contract) == sender,139 <Error<T>>::NoPermission140 );141142 if allowed {143 <Allowlist<T>>::insert(contract, user, true);144 } else {145 <Allowlist<T>>::remove(contract, user);146 }147 Ok(())148 }149150 #[pallet::weight(0)]151 pub fn set_sponsoring_rate_limit(152 origin: OriginFor<T>,153 contract: T::AccountId,154 rate_limit: T::BlockNumber,155 ) -> DispatchResult {156 let sender = ensure_signed(origin)?;157 ensure!(158 <Owner<T>>::get(&contract) == sender,159 <Error<T>>::NoPermission160 );161162 <SponsoringRateLimit<T>>::insert(contract, rate_limit);163 Ok(())164 }165 }166167 #[derive(Encode, Decode, Clone, PartialEq, Eq)]168 pub struct ContractHelpersExtension<T>(PhantomData<T>);169 impl<T> core::fmt::Debug for ContractHelpersExtension<T> {170 fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {171 fmt.debug_struct("ContractHelpersExtension").finish()172 }173 }174175 type CodeHash<T> = <T as frame_system::Config>::Hash;176 impl<T> SignedExtension for ContractHelpersExtension<T>177 where178 T: Config + Send + Sync,179 T::Call: sp_runtime::traits::Dispatchable,180 T::Call: IsSubType<pallet_contracts::Call<T>>,181 T::AccountId: UncheckedFrom<T::Hash>,182 T::AccountId: AsRef<[u8]>,183 {184 const IDENTIFIER: &'static str = "ContractHelpers";185 type AccountId = T::AccountId;186 type Call = T::Call;187 type AdditionalSigned = ();188 type Pre = Option<(Self::AccountId, CodeHash<T>, Vec<u8>)>;189190 fn additional_signed(&self) -> Result<(), transaction_validity::TransactionValidityError> {191 Ok(())192 }193194 fn validate(195 &self,196 who: &T::AccountId,197 call: &Self::Call,198 _info: &DispatchInfoOf<Self::Call>,199 _len: usize,200 ) -> transaction_validity::TransactionValidity {201 if let Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) =202 IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call)203 {204 let called_contract: T::AccountId =205 T::Lookup::lookup((*dest).clone()).unwrap_or_default();206 if !<Pallet<T>>::allowed(called_contract, who.clone(), true) {207 return Err(transaction_validity::InvalidTransaction::Call.into());208 }209 }210 Ok(transaction_validity::ValidTransaction::default())211 }212213 fn pre_dispatch(214 self,215 who: &Self::AccountId,216 call: &Self::Call,217 _info: &DispatchInfoOf<Self::Call>,218 _len: usize,219 ) -> Result<Self::Pre, TransactionValidityError> {220 match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {221 Some(pallet_contracts::Call::instantiate(_, _, code_hash, _, salt)) => {222 Ok(Some((who.clone(), *code_hash, salt.clone())))223 }224 Some(pallet_contracts::Call::instantiate_with_code(_, _, code, _, salt)) => {225 let code_hash = &T::Hashing::hash(code);226 Ok(Some((who.clone(), *code_hash, salt.clone())))227 }228 _ => Ok(None),229 }230 }231232 fn post_dispatch(233 pre: Self::Pre,234 _info: &DispatchInfoOf<Self::Call>,235 _post_info: &PostDispatchInfoOf<Self::Call>,236 _len: usize,237 _result: &DispatchResult,238 ) -> Result<(), TransactionValidityError> {239 if let Some((who, code_hash, salt)) = pre {240 let new_contract_address =241 <pallet_contracts::Pallet<T>>::contract_address(&who, &code_hash, &salt);242 <Owner<T>>::insert(&new_contract_address, &who);243 }244245 Ok(())246 }247 }248249 pub struct ContractSponsorshipHandler<T>(PhantomData<T>);250 impl<T, C> SponsorshipHandler<T::AccountId, C> for ContractSponsorshipHandler<T>251 where252 T: Config,253 C: IsSubType<pallet_contracts::Call<T>>,254 T::AccountId: UncheckedFrom<T::Hash>,255 T::AccountId: AsRef<[u8]>,256 {257 fn get_sponsor(who: &T::AccountId, call: &C) -> Option<T::AccountId> {258 if let Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) =259 IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call)260 {261 let called_contract: T::AccountId =262 T::Lookup::lookup((*dest).clone()).unwrap_or_default();263 if <SelfSponsoring<T>>::get(&called_contract)264 && <Pallet<T>>::allowed(called_contract.clone(), who.clone(), false)265 {266 let last_tx_block = SponsorBasket::<T>::get(&called_contract, &who);267 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;268 let rate_limit = SponsoringRateLimit::<T>::get(&called_contract);269 let limit_time = last_tx_block + rate_limit;270271 if block_number >= limit_time {272 SponsorBasket::<T>::insert(&called_contract, who, block_number);273 return Some(called_contract);274 }275 }276 }277 None278 }279 }280}