diff --git a/common/src/lib.rs b/common/src/lib.rs index 6f40a64d449..5c67ea3f18d 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -350,19 +350,3 @@ where self.function.clone() } } - -pub trait PaymentVoucher { - type VoucherId; - type Error; - - fn voucher_id(who: AccountId, program: ProgramId) -> Self::VoucherId; -} - -impl PaymentVoucher for () { - type VoucherId = AccountId; - type Error = &'static str; - - fn voucher_id(_who: AccountId, _program: ProgramId) -> Self::VoucherId { - unimplemented!() - } -} diff --git a/common/src/storage/complex/mailbox.rs b/common/src/storage/complex/mailbox.rs index 0dd98ac590e..da73ebdba30 100644 --- a/common/src/storage/complex/mailbox.rs +++ b/common/src/storage/complex/mailbox.rs @@ -99,6 +99,11 @@ pub trait MailboxError { fn element_not_found() -> Self; } +impl MailboxError for () { + fn duplicate_key() -> Self {} + fn element_not_found() -> Self {} +} + /// `Mailbox` implementation based on `DoubleMapStorage`. /// /// Generic parameter `Error` requires `MailboxError` implementation. diff --git a/gsdk/src/metadata/generated.rs b/gsdk/src/metadata/generated.rs index b64cac4330e..007041e2882 100644 --- a/gsdk/src/metadata/generated.rs +++ b/gsdk/src/metadata/generated.rs @@ -2667,12 +2667,9 @@ pub mod runtime_types { #[doc = "Program with the specified id is not found."] ProgramNotFound, #[codec(index = 14)] - #[doc = "Voucher can't be redeemed"] - FailureRedeemingVoucher, - #[codec(index = 15)] #[doc = "Gear::run() already included in current block."] GearRunAlreadyInBlock, - #[codec(index = 16)] + #[codec(index = 15)] #[doc = "The program rent logic is disabled."] ProgramRentDisabled, } @@ -3339,9 +3336,9 @@ pub mod runtime_types { #[doc = "\n\t\t\tCustom [dispatch errors](https://docs.substrate.io/main-docs/build/events-errors/)\n\t\t\tof this pallet.\n\t\t\t"] pub enum Error { #[codec(index = 0)] - FailureToCreateVoucher, + InsufficientBalance, #[codec(index = 1)] - FailureToRedeemVoucher, + InvalidVoucher, } #[derive(Debug, crate::gp::Decode, crate::gp::DecodeAsType, crate::gp::Encode)] #[doc = "\n\t\t\tThe [event](https://docs.substrate.io/main-docs/build/events-errors/) emitted\n\t\t\tby this pallet.\n\t\t\t"] diff --git a/pallets/gear-voucher/src/benchmarking.rs b/pallets/gear-voucher/src/benchmarking.rs index 29f1caadfd9..06216d854ff 100644 --- a/pallets/gear-voucher/src/benchmarking.rs +++ b/pallets/gear-voucher/src/benchmarking.rs @@ -48,9 +48,9 @@ benchmarks! { let holder_lookup = T::Lookup::unlookup(holder.clone()); }: _(RawOrigin::Signed(issuer), holder_lookup, program_id, 10_000_000_000_000_u128.unique_saturated_into()) verify { - let voucher_account_id = GearVoucher::::voucher_account_id(&holder, &program_id); + let voucher_id = GearVoucher::::voucher_id(&holder, &program_id); assert_eq!( - CurrencyOf::::free_balance(&voucher_account_id), + CurrencyOf::::free_balance(&voucher_id), 10_000_000_000_000_u128.unique_saturated_into(), ); } diff --git a/pallets/gear-voucher/src/lib.rs b/pallets/gear-voucher/src/lib.rs index c3257765888..e7e7d0a2980 100644 --- a/pallets/gear-voucher/src/lib.rs +++ b/pallets/gear-voucher/src/lib.rs @@ -57,7 +57,6 @@ mod mock; #[cfg(test)] mod tests; -use common::PaymentVoucher; use frame_support::{ pallet_prelude::*, traits::{Currency, ExistenceRequirement, ReservableCurrency, StorageVersion}, @@ -82,7 +81,9 @@ const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); #[frame_support::pallet] pub mod pallet { use super::*; + use common::storage::Mailbox; use frame_system::pallet_prelude::*; + use gear_core::message::UserStoredMessage; #[pallet::config] pub trait Config: frame_system::Config { @@ -103,6 +104,8 @@ pub mod pallet { AccountId = Self::AccountId, Balance = BalanceOf, >; + + type Mailbox: Mailbox; } #[pallet::pallet] @@ -124,8 +127,8 @@ pub mod pallet { // Gas pallet error. #[pallet::error] pub enum Error { - FailureToCreateVoucher, - FailureToRedeemVoucher, + InsufficientBalance, + InvalidVoucher, } #[pallet::hooks] @@ -158,13 +161,13 @@ pub mod pallet { let to = T::Lookup::lookup(to)?; // Generate unique account id corresponding to the pair (user, program) - let voucher_id = Self::voucher_account_id(&to, &program); + let voucher_id = Self::voucher_id(&to, &program); // Transfer funds to the keyless account T::Currency::transfer(&who, &voucher_id, value, ExistenceRequirement::KeepAlive) .map_err(|e| { log::debug!("Failed to transfer funds to the voucher account: {:?}", e); - Error::::FailureToCreateVoucher + Error::::InsufficientBalance })?; Self::deposit_event(Event::VoucherIssued { @@ -185,27 +188,33 @@ pub mod pallet { ) -> DispatchResultWithPostInfo { let origin = ensure_signed(origin)?; - T::CallsDispatcher::dispatch(origin, call) + let sponsor = Self::sponsor_of(&origin, &call).ok_or(Error::::InvalidVoucher)?; + + T::CallsDispatcher::dispatch(origin, sponsor, call) } } -} -impl Pallet { - /// Derive a synthesized account ID from an account ID and a program ID. - pub fn voucher_account_id(who: &T::AccountId, program_id: &ProgramId) -> T::AccountId { - let entropy = (b"modlpy/voucher__", who, program_id).using_encoded(blake2_256); - Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref())) - .expect("infinite length input; no invalid inputs for type; qed") - } -} - -impl PaymentVoucher> for Pallet { - type VoucherId = T::AccountId; - type Error = DispatchError; + impl Pallet { + /// Derive a synthesized account ID from an account ID and a program ID. + pub fn voucher_id(who: &T::AccountId, program_id: &ProgramId) -> T::AccountId { + let entropy = (b"modlpy/voucher__", who, program_id).using_encoded(blake2_256); + Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref())) + .expect("infinite length input; no invalid inputs for type; qed") + } - #[inline] - fn voucher_id(who: T::AccountId, program: ProgramId) -> Self::VoucherId { - Self::voucher_account_id(&who, &program) + /// Return synthesized account ID based on call data. + pub fn sponsor_of( + who: &T::AccountId, + call: &PrepaidCall>, + ) -> Option { + match call { + PrepaidCall::SendMessage { destination, .. } => { + Some(Self::voucher_id(who, destination)) + } + PrepaidCall::SendReply { reply_to_id, .. } => T::Mailbox::peek(who, reply_to_id) + .map(|stored_message| Self::voucher_id(who, &stored_message.source())), + } + } } } @@ -235,6 +244,7 @@ pub trait PrepaidCallsDispatcher { fn dispatch( account_id: Self::AccountId, + sponsor_id: Self::AccountId, call: PrepaidCall, ) -> DispatchResultWithPostInfo; } diff --git a/pallets/gear-voucher/src/mock.rs b/pallets/gear-voucher/src/mock.rs index 418dedc28bc..8224c66199d 100644 --- a/pallets/gear-voucher/src/mock.rs +++ b/pallets/gear-voucher/src/mock.rs @@ -17,10 +17,12 @@ // along with this program. If not, see . use crate as pallet_gear_voucher; +use common::storage::{Interval, Mailbox}; use frame_support::{ construct_runtime, parameter_types, weights::constants::RocksDbWeight, PalletId, }; use frame_system as system; +use gear_core::{ids::MessageId, message::UserStoredMessage}; use primitive_types::H256; use sp_runtime::{ generic, @@ -71,18 +73,50 @@ impl crate::PrepaidCallsDispatcher for () { } fn dispatch( _account_id: Self::AccountId, + _sponsor_id: Self::AccountId, _call: pallet_gear_voucher::PrepaidCall, ) -> frame_support::pallet_prelude::DispatchResultWithPostInfo { unimplemented!() } } +pub struct MailboxMock; + +impl Mailbox for MailboxMock { + type BlockNumber = (); + type Error = (); + type Key1 = AccountId; + type Key2 = MessageId; + type Value = UserStoredMessage; + type OutputError = (); + + fn clear() { + unimplemented!() + } + fn contains(_key1: &Self::Key1, _key2: &Self::Key2) -> bool { + unimplemented!() + } + fn insert(_value: Self::Value, _bn: Self::BlockNumber) -> Result<(), Self::OutputError> { + unimplemented!() + } + fn peek(_key1: &Self::Key1, _key2: &Self::Key2) -> Option { + unimplemented!() + } + fn remove( + _key1: Self::Key1, + _key2: Self::Key2, + ) -> Result<(Self::Value, Interval), Self::OutputError> { + unimplemented!() + } +} + impl pallet_gear_voucher::Config for Test { type RuntimeEvent = RuntimeEvent; type Currency = Balances; type PalletId = VoucherPalletId; type WeightInfo = (); type CallsDispatcher = (); + type Mailbox = MailboxMock; } // Build genesis storage according to the mock runtime. diff --git a/pallets/gear-voucher/src/tests.rs b/pallets/gear-voucher/src/tests.rs index b3d47736fb2..5d16c90ab5e 100644 --- a/pallets/gear-voucher/src/tests.rs +++ b/pallets/gear-voucher/src/tests.rs @@ -26,7 +26,7 @@ use primitive_types::H256; fn voucher_issue_works() { new_test_ext().execute_with(|| { let program_id = H256::from(b"some//quasy//random//program//id").cast(); - let synthesized = Voucher::voucher_account_id(&BOB, &program_id); + let synthesized = Voucher::voucher_id(&BOB, &program_id); assert_ok!(Voucher::issue( RuntimeOrigin::signed(ALICE), @@ -47,7 +47,7 @@ fn voucher_issue_works() { // Insufficient funds assert_noop!( Voucher::issue(RuntimeOrigin::signed(ALICE), BOB, program_id, 100_000_000,), - Error::::FailureToCreateVoucher + Error::::InsufficientBalance ); }); } @@ -56,7 +56,7 @@ fn voucher_issue_works() { fn voucher_redemption_works() { new_test_ext().execute_with(|| { let program_id = H256::from(b"some//quasy//random//program//id").cast(); - let synthesized = Voucher::voucher_account_id(&BOB, &program_id); + let synthesized = Voucher::voucher_id(&BOB, &program_id); assert_ok!(Voucher::issue( RuntimeOrigin::signed(ALICE), @@ -69,13 +69,13 @@ fn voucher_redemption_works() { // Redemption ok assert_ok!(Balances::reserve( - &Voucher::voucher_id(BOB, program_id), + &Voucher::voucher_id(&BOB, &program_id), 2_000 )); // Redemption fails assert_noop!( - Balances::reserve(&Voucher::voucher_id(BOB, program_id), 100_000_000), + Balances::reserve(&Voucher::voucher_id(&BOB, &program_id), 100_000_000), pallet_balances::Error::::InsufficientBalance ); }); diff --git a/pallets/gear/src/lib.rs b/pallets/gear/src/lib.rs index 50ab4193ff1..687afb77268 100644 --- a/pallets/gear/src/lib.rs +++ b/pallets/gear/src/lib.rs @@ -54,7 +54,7 @@ use alloc::{format, string::String}; use common::{ self, event::*, gas_provider::GasNodeId, paused_program_storage::SessionId, scheduler::*, storage::*, BlockLimiter, CodeMetadata, CodeStorage, GasProvider, GasTree, Origin, - PausedProgramStorage, PaymentVoucher, Program, ProgramState, ProgramStorage, QueueRunner, + PausedProgramStorage, Program, ProgramState, ProgramStorage, QueueRunner, }; use core::marker::PhantomData; use core_processor::{ @@ -119,7 +119,6 @@ pub type RentFreePeriodOf = ::ProgramRentFreePeriod; pub type RentCostPerBlockOf = ::ProgramRentCostPerBlock; pub type ResumeMinimalPeriodOf = ::ProgramResumeMinimalRentPeriod; pub type ResumeSessionDurationOf = ::ProgramResumeSessionDuration; -pub(crate) type VoucherOf = ::Voucher; pub(crate) type GearBank = pallet_gear_bank::Pallet; /// The current storage version. @@ -240,14 +239,6 @@ pub mod pallet { /// Message Queue processing routing provider. type QueueRunner: QueueRunner>; - /// Type that allows to check caller's eligibility for using voucher for payment. - type Voucher: PaymentVoucher< - Self::AccountId, - ProgramId, - BalanceOf, - VoucherId = Self::AccountId, - >; - /// The free of charge period of rent. #[pallet::constant] type ProgramRentFreePeriod: Get>; @@ -450,8 +441,6 @@ pub mod pallet { ResumePeriodLessThanMinimal, /// Program with the specified id is not found. ProgramNotFound, - /// Voucher can't be redeemed - FailureRedeemingVoucher, /// Gear::run() already included in current block. GearRunAlreadyInBlock, /// The program rent logic is disabled. @@ -1480,8 +1469,8 @@ pub mod pallet { payload, gas_limit, value, - false, keep_alive, + None, ) } @@ -1517,8 +1506,8 @@ pub mod pallet { payload, gas_limit, value, - false, keep_alive, + None, ) } @@ -1818,8 +1807,8 @@ pub mod pallet { payload: Vec, gas_limit: u64, value: BalanceOf, - prepaid: bool, keep_alive: bool, + gas_sponsor: Option>, ) -> DispatchResultWithPostInfo { let payload = payload .try_into() @@ -1850,27 +1839,11 @@ pub mod pallet { // a voucher exists. The latter can only be used to pay for gas or transaction fee. GearBank::::deposit_value(&who, value, keep_alive)?; - let external_node = if prepaid { - // If voucher is used, we attempt to reserve funds on the respective account. - // If no such voucher exists, the call is invalidated. - let voucher_id = VoucherOf::::voucher_id(who.clone(), destination); - - GearBank::::deposit_gas(&voucher_id, gas_limit, keep_alive).map_err(|e| { - log::debug!( - "Failed to redeem voucher for user {who:?} and program {destination:?}: {e:?}" - ); - Error::::FailureRedeemingVoucher - })?; - - voucher_id - } else { - // If voucher is not used, we reserve gas limit on the user's account. - GearBank::::deposit_gas(&who, gas_limit, keep_alive)?; - - who.clone() - }; - - Self::create(external_node, message.id(), gas_limit, false); + // If voucher or any other prepaid mechanism is not used, + // gas limit is taken from user's account. + let gas_sponsor = gas_sponsor.unwrap_or_else(|| who.clone()); + GearBank::::deposit_gas(&gas_sponsor, gas_limit, keep_alive)?; + Self::create(gas_sponsor, message.id(), gas_limit, false); let message = message.into_stored_dispatch(origin.cast()); @@ -1918,8 +1891,8 @@ pub mod pallet { payload: Vec, gas_limit: u64, value: BalanceOf, - prepaid: bool, keep_alive: bool, + gas_sponsor: Option>, ) -> DispatchResultWithPostInfo { let payload = payload .try_into() @@ -1950,28 +1923,11 @@ pub mod pallet { GearBank::::deposit_value(&origin, value, keep_alive)?; - let external_node = if prepaid { - // If voucher is used, we attempt to reserve funds on the respective account. - // If no such voucher exists, the call is invalidated. - let voucher_id = VoucherOf::::voucher_id(origin.clone(), destination); - - GearBank::::deposit_gas(&voucher_id, gas_limit, keep_alive).map_err(|e| { - log::debug!( - "Failed to redeem voucher for user {origin:?} and program {destination:?}: {e:?}" - ); - Error::::FailureRedeemingVoucher - })?; - - voucher_id - } else { - // If voucher is not used, we reserve gas limit on the user's account. - GearBank::::deposit_gas(&origin, gas_limit, keep_alive)?; - - origin.clone() - }; - - // Following up with a gas node creation. - Self::create(external_node, reply_id, gas_limit, true); + // If voucher or any other prepaid mechanism is not used, + // gas limit is taken from user's account. + let gas_sponsor = gas_sponsor.unwrap_or_else(|| origin.clone()); + GearBank::::deposit_gas(&gas_sponsor, gas_limit, keep_alive)?; + Self::create(gas_sponsor, reply_id, gas_limit, true); // Creating reply message. let message = ReplyMessage::from_packet( @@ -2022,6 +1978,7 @@ pub mod pallet { fn dispatch( account_id: Self::AccountId, + sponsor_id: Self::AccountId, call: PrepaidCall, ) -> DispatchResultWithPostInfo { match call { @@ -2037,8 +1994,8 @@ pub mod pallet { payload, gas_limit, value, - true, keep_alive, + Some(sponsor_id), ), PrepaidCall::SendReply { reply_to_id, @@ -2052,8 +2009,8 @@ pub mod pallet { payload, gas_limit, value, - true, keep_alive, + Some(sponsor_id), ), } } diff --git a/pallets/gear/src/mock.rs b/pallets/gear/src/mock.rs index d63d5e3d09b..107358b2c0c 100644 --- a/pallets/gear/src/mock.rs +++ b/pallets/gear/src/mock.rs @@ -93,7 +93,7 @@ pallet_gear_program::impl_config!(Test); pallet_gear_messenger::impl_config!(Test, CurrentBlockNumber = Gear); pallet_gear_scheduler::impl_config!(Test); pallet_gear_bank::impl_config!(Test); -pallet_gear::impl_config!(Test, Schedule = DynamicSchedule, Voucher = GearVoucher); +pallet_gear::impl_config!(Test, Schedule = DynamicSchedule); pallet_gear_gas::impl_config!(Test); common::impl_pallet_balances!(Test); common::impl_pallet_authorship!(Test); @@ -174,6 +174,7 @@ impl pallet_gear_voucher::Config for Test { type PalletId = VoucherPalletId; type WeightInfo = (); type CallsDispatcher = Gear; + type Mailbox = MailboxOf; } // Build genesis storage according to the mock runtime. diff --git a/pallets/gear/src/pallet_tests.rs b/pallets/gear/src/pallet_tests.rs index 7cd98cb72c7..ccc6d78ece6 100644 --- a/pallets/gear/src/pallet_tests.rs +++ b/pallets/gear/src/pallet_tests.rs @@ -56,7 +56,6 @@ macro_rules! impl_config_inner { type BlockLimiter = GearGas; type Scheduler = GearScheduler; type QueueRunner = Gear; - type Voucher = GearConfigVoucher; type ProgramRentFreePeriod = RentFreePeriod; type ProgramResumeMinimalRentPeriod = ResumeMinimalPeriod; type ProgramRentCostPerBlock = RentCostPerBlock; @@ -72,12 +71,6 @@ macro_rules! impl_config_inner { $crate::impl_config_inner!($runtime, $($( $rest )*)?); }; - ($runtime:ty, Voucher = $voucher:ty $(, $( $rest:tt )*)?) => { - type GearConfigVoucher = $voucher; - - $crate::impl_config_inner!($runtime, $($( $rest )*)?); - }; - ($runtime:ty, DebugInfo = $debug_info:ty $(, $( $rest:tt )*)?) => { type GearConfigDebugInfo = $debug_info; diff --git a/pallets/gear/src/tests.rs b/pallets/gear/src/tests.rs index b9604dababc..9ceea5eac7f 100644 --- a/pallets/gear/src/tests.rs +++ b/pallets/gear/src/tests.rs @@ -14295,7 +14295,7 @@ fn send_gasless_message_works() { keep_alive: false, } ), - Error::::FailureRedeemingVoucher + pallet_gear_bank::Error::::InsufficientBalance ); // USER_1 as the program owner issues a voucher for USER_2 enough to send a message @@ -14319,7 +14319,7 @@ fn send_gasless_message_works() { // Balance check // Voucher has been issued, but not used yet, so funds should be still in the respective account - let voucher_id = GearVoucher::voucher_account_id(&USER_2, &program_id); + let voucher_id = GearVoucher::voucher_id(&USER_2, &program_id); assert_eq!( Balances::free_balance(voucher_id), gas_price(DEFAULT_GAS_LIMIT) @@ -14401,7 +14401,7 @@ fn send_gasless_reply_works() { prog_id, gas_price(DEFAULT_GAS_LIMIT), )); - let voucher_id = GearVoucher::voucher_account_id(&USER_1, &prog_id); + let voucher_id = GearVoucher::voucher_id(&USER_1, &prog_id); run_to_block(3, None); diff --git a/pallets/payment/src/mock.rs b/pallets/payment/src/mock.rs index b989c655a8d..7cf4809e880 100644 --- a/pallets/payment/src/mock.rs +++ b/pallets/payment/src/mock.rs @@ -164,11 +164,9 @@ impl DelegateFee for DelegateFeeAccountBuilder { RuntimeCall::GearVoucher(pallet_gear_voucher::Call::call { call: pallet_gear_voucher::PrepaidCall::SendMessage { .. }, }) => Some(FEE_PAYER), - RuntimeCall::GearVoucher(pallet_gear_voucher::Call::call { - call: pallet_gear_voucher::PrepaidCall::SendReply { reply_to_id, .. }, - }) => as common::storage::Mailbox>::peek(who, reply_to_id).map( - |stored_message| GearVoucher::voucher_account_id(who, &stored_message.source()), - ), + RuntimeCall::GearVoucher(pallet_gear_voucher::Call::call { call }) => { + GearVoucher::sponsor_of(who, call) + } _ => None, } } @@ -190,6 +188,7 @@ impl pallet_gear_voucher::Config for Test { type PalletId = VoucherPalletId; type WeightInfo = (); type CallsDispatcher = Gear; + type Mailbox = MailboxOf; } // Build genesis storage according to the mock runtime. diff --git a/pallets/payment/src/tests.rs b/pallets/payment/src/tests.rs index c468452e1e1..af5e36d8335 100644 --- a/pallets/payment/src/tests.rs +++ b/pallets/payment/src/tests.rs @@ -645,7 +645,7 @@ fn reply_with_voucher_pays_fee_from_voucher_ok() { program_id, 200_000_000, )); - let voucher_id = GearVoucher::voucher_account_id(&BOB, &program_id); + let voucher_id = GearVoucher::voucher_id(&BOB, &program_id); run_to_block(2); diff --git a/runtime/vara/src/lib.rs b/runtime/vara/src/lib.rs index 784f3a7b435..8b01682a855 100644 --- a/runtime/vara/src/lib.rs +++ b/runtime/vara/src/lib.rs @@ -24,7 +24,7 @@ #[cfg(all(feature = "std", not(feature = "fuzz")))] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); -use common::storage::{Mailbox, Messenger}; +use common::storage::Messenger; use frame_election_provider_support::{ onchain, ElectionDataProvider, NposSolution, SequentialPhragmen, VoteWeight, }; @@ -994,7 +994,6 @@ impl pallet_gear::Config for Runtime { type BlockLimiter = GearGas; type Scheduler = GearScheduler; type QueueRunner = Gear; - type Voucher = GearVoucher; type ProgramRentFreePeriod = ConstU32<{ MONTHS * RENT_FREE_PERIOD_MONTH_FACTOR }>; type ProgramResumeMinimalRentPeriod = ConstU32<{ WEEKS * RENT_RESUME_WEEK_FACTOR }>; type ProgramRentCostPerBlock = ConstU128; @@ -1058,14 +1057,9 @@ pub struct DelegateFeeAccountBuilder; impl DelegateFee for DelegateFeeAccountBuilder { fn delegate_fee(call: &RuntimeCall, who: &AccountId) -> Option { match call { - RuntimeCall::GearVoucher(pallet_gear_voucher::Call::call { - call: pallet_gear_voucher::PrepaidCall::SendMessage { destination, .. }, - }) => Some(GearVoucher::voucher_account_id(who, destination)), - RuntimeCall::GearVoucher(pallet_gear_voucher::Call::call { - call: pallet_gear_voucher::PrepaidCall::SendReply { reply_to_id, .. }, - }) => <::Mailbox as Mailbox>::peek(who, reply_to_id).map( - |stored_message| GearVoucher::voucher_account_id(who, &stored_message.source()), - ), + RuntimeCall::GearVoucher(pallet_gear_voucher::Call::call { call }) => { + GearVoucher::sponsor_of(who, call) + } _ => None, } } @@ -1087,6 +1081,7 @@ impl pallet_gear_voucher::Config for Runtime { type PalletId = VoucherPalletId; type WeightInfo = weights::pallet_gear_voucher::SubstrateWeight; type CallsDispatcher = Gear; + type Mailbox = ::Mailbox; } impl frame_system::offchain::SendTransactionTypes for Runtime