diff --git a/gsdk/src/metadata/generated.rs b/gsdk/src/metadata/generated.rs index 51c73b4575d..1d1eead40d9 100644 --- a/gsdk/src/metadata/generated.rs +++ b/gsdk/src/metadata/generated.rs @@ -2856,6 +2856,10 @@ pub mod runtime_types { value: _0, keep_alive: ::core::primitive::bool, }, + #[codec(index = 2)] + UploadCode { + code: ::std::vec::Vec<::core::primitive::u8>, + }, } #[derive(Debug, crate::gp::Decode, crate::gp::DecodeAsType, crate::gp::Encode)] pub struct VoucherId(pub [::core::primitive::u8; 32usize]); @@ -2865,6 +2869,7 @@ pub mod runtime_types { pub programs: ::core::option::Option< ::std::vec::Vec, >, + pub code_uploading: ::core::primitive::bool, pub expiry: _1, } } @@ -2881,6 +2886,7 @@ pub mod runtime_types { programs: ::core::option::Option< ::std::vec::Vec, >, + code_uploading: ::core::primitive::bool, duration: ::core::primitive::u32, }, #[codec(index = 1)] @@ -2909,6 +2915,7 @@ pub mod runtime_types { ::std::vec::Vec, >, >, + code_uploading: ::core::option::Option<::core::primitive::bool>, prolong_duration: ::core::option::Option<::core::primitive::u32>, }, #[codec(index = 4)] @@ -2949,6 +2956,12 @@ pub mod runtime_types { #[codec(index = 8)] #[doc = "Voucher issue/prolongation duration out of [min; max] constants."] DurationOutOfBounds, + #[codec(index = 9)] + #[doc = "Voucher update function tries to cut voucher ability of code upload."] + CodeUploadingEnabled, + #[codec(index = 10)] + #[doc = "Voucher is disabled for code uploading, but requested."] + CodeUploadingDisabled, } #[derive(Debug, crate::gp::Decode, crate::gp::DecodeAsType, crate::gp::Encode)] #[doc = "Pallet Gear Voucher event."] diff --git a/pallets/gear-voucher/src/benchmarking.rs b/pallets/gear-voucher/src/benchmarking.rs index d07a7d8170b..b803b6ff906 100644 --- a/pallets/gear-voucher/src/benchmarking.rs +++ b/pallets/gear-voucher/src/benchmarking.rs @@ -51,10 +51,13 @@ benchmarks! { .map(|i| benchmarking::account::("program", 0, i).cast()) .collect(); + // Allow uploading codes. + let code_uploading = true; + // Voucher validity. let validity = <::MinDuration as Get>>::get(); - }: _(RawOrigin::Signed(origin.clone()), spender.clone(), balance, Some(set), validity) + }: _(RawOrigin::Signed(origin.clone()), spender.clone(), balance, Some(set), code_uploading, validity) verify { let (key_spender, voucher_id, voucher_info) = Vouchers::::iter().next().expect("Couldn't find voucher"); @@ -80,11 +83,14 @@ benchmarks! { // Voucher balance. let balance = 10_000_000_000_000_u128.unique_saturated_into(); + // Forbid uploading codes. + let code_uploading = false; + // Voucher validity. let validity = <::MinDuration as Get>>::get(); // Issue voucher. - assert!(Pallet::::issue(RawOrigin::Signed(origin.clone()).into(), spender.clone(), balance, None, validity).is_ok()); + assert!(Pallet::::issue(RawOrigin::Signed(origin.clone()).into(), spender.clone(), balance, None, code_uploading, validity).is_ok()); let (_, voucher_id, _) = Vouchers::::iter().next().expect("Couldn't find voucher"); frame_system::Pallet::::set_block_number(frame_system::Pallet::::block_number() + validity + One::one()); @@ -113,11 +119,14 @@ benchmarks! { .map(|i| benchmarking::account::("program", 0, i).cast()) .collect(); + // Forbid uploading codes. + let code_uploading = false; + // Voucher validity. let validity = <::MinDuration as Get>>::get(); // Issue voucher. - assert!(Pallet::::issue(RawOrigin::Signed(origin.clone()).into(), spender.clone(), balance, Some(set), validity).is_ok()); + assert!(Pallet::::issue(RawOrigin::Signed(origin.clone()).into(), spender.clone(), balance, Some(set), code_uploading, validity).is_ok()); let (_, voucher_id, _) = Vouchers::::iter().next().expect("Couldn't find voucher"); // New owner account. @@ -132,9 +141,12 @@ benchmarks! { .collect(); let append_programs = Some(Some(append_programs_set)); + // Allow uploading codes. + let code_uploading = Some(true); + // prolong duration. let prolong_duration = Some(validity); - }: _(RawOrigin::Signed(origin.clone()), spender.clone(), voucher_id, move_ownership, balance_top_up, append_programs, prolong_duration) + }: _(RawOrigin::Signed(origin.clone()), spender.clone(), voucher_id, move_ownership, balance_top_up, append_programs, code_uploading, prolong_duration) verify { let voucher_info = Vouchers::::get(spender, voucher_id).expect("Must be"); assert_eq!(voucher_info.programs.map(|v| v.len()), Some(<::MaxProgramsAmount as Get>::get() as usize)); diff --git a/pallets/gear-voucher/src/internal.rs b/pallets/gear-voucher/src/internal.rs index b5d91bc64b1..598a9be7f7d 100644 --- a/pallets/gear-voucher/src/internal.rs +++ b/pallets/gear-voucher/src/internal.rs @@ -29,17 +29,22 @@ where T::AccountId: Origin, { /// Returns account id that pays for gas purchase and transaction fee - /// for processing this ['pallet_gear_voucher::Call']s processing if: + /// for processing this ['pallet_gear_voucher::Call'], if: /// /// * Call is [`Self::call`]: /// * Voucher with the given voucher id exists; /// * Caller is eligible to use the voucher; /// * The voucher is not expired; - /// * Destination program of the given prepaid call can be determined; - /// * The voucher destinations limitations accept determined destination. + /// * For messaging calls: The destination program of the given prepaid + /// call can be determined; + /// * For messaging calls: The voucher destinations limitations accept + /// determined destination; + /// * For codes uploading: The voucher allows code uploading. /// /// * Call is [`Self::call_deprecated`]: - /// * Destination program of the given prepaid call can be determined. + /// * For messaging calls: The destination program of the given prepaid + /// call can be determined. + /// * For codes uploading: NEVER. /// /// Returns [`None`] for other cases. pub fn get_sponsor(&self, caller: AccountIdOf) -> Option> { @@ -92,20 +97,27 @@ impl Pallet { Error::::VoucherExpired ); - if let Some(ref programs) = voucher.programs { - let destination = Self::prepaid_call_destination(&origin, call) - .ok_or(Error::::UnknownDestination)?; - - ensure!( - programs.contains(&destination), - Error::::InappropriateDestination - ); + match call { + PrepaidCall::UploadCode { .. } => { + ensure!(voucher.code_uploading, Error::::CodeUploadingDisabled) + } + PrepaidCall::SendMessage { .. } | PrepaidCall::SendReply { .. } => { + if let Some(ref programs) = voucher.programs { + let destination = Self::prepaid_call_destination(&origin, call) + .ok_or(Error::::UnknownDestination)?; + + ensure!( + programs.contains(&destination), + Error::::InappropriateDestination + ); + } + } } Ok(()) } - /// Return destination program of the [`PrepaidCall`]. + /// Return destination program of the [`PrepaidCall`], if exists. pub fn prepaid_call_destination( who: &T::AccountId, call: &PrepaidCall>, @@ -115,6 +127,7 @@ impl Pallet { PrepaidCall::SendReply { reply_to_id, .. } => { T::Mailbox::peek(who, reply_to_id).map(|stored_message| stored_message.source()) } + PrepaidCall::UploadCode { .. } => None, } } } @@ -169,6 +182,8 @@ pub struct VoucherInfo { /// Set of programs this voucher could be used to interact with. /// In case of [`None`] means any gear program. pub programs: Option>, + /// Flag if this voucher's covers uploading codes as prepaid call. + pub code_uploading: bool, /// The block number at and after which voucher couldn't be used and /// can be revoked by owner. pub expiry: BlockNumber, @@ -199,4 +214,7 @@ pub enum PrepaidCall { value: Balance, keep_alive: bool, }, + UploadCode { + code: Vec, + }, } diff --git a/pallets/gear-voucher/src/lib.rs b/pallets/gear-voucher/src/lib.rs index ed7b3455344..17a8b6360a1 100644 --- a/pallets/gear-voucher/src/lib.rs +++ b/pallets/gear-voucher/src/lib.rs @@ -184,6 +184,10 @@ pub mod pallet { VoucherExpired, /// Voucher issue/prolongation duration out of [min; max] constants. DurationOutOfBounds, + /// Voucher update function tries to cut voucher ability of code upload. + CodeUploadingEnabled, + /// Voucher is disabled for code uploading, but requested. + CodeUploadingDisabled, } /// Storage containing amount of the total vouchers issued. @@ -224,6 +228,9 @@ pub mod pallet { /// * programs: pool of programs spender can interact with, /// if None - means any program, /// limited by Config param; + /// * code_uploading: + /// allow voucher to be used as payer for `upload_code` + /// transactions fee; /// * duration: amount of blocks voucher could be used by spender /// and couldn't be revoked by owner. /// Must be out in [MinDuration; MaxDuration] constants. @@ -236,6 +243,7 @@ pub mod pallet { spender: AccountIdOf, balance: BalanceOf, programs: Option>, + code_uploading: bool, duration: BlockNumberFor, ) -> DispatchResultWithPostInfo { // Ensuring origin. @@ -276,6 +284,7 @@ pub mod pallet { let voucher_info = VoucherInfo { owner: owner.clone(), programs, + code_uploading, expiry, }; @@ -401,6 +410,8 @@ pub mod pallet { /// `Some(programs_set)` passed or allows /// it to interact with any program by /// `None` passed; + /// * code_uploading: optionally allows voucher to be used to pay + /// fees for `upload_code` extrinsics; /// * prolong_duration: optionally increases expiry block number. /// If voucher is expired, prolongs since current bn. /// Validity prolongation (since current block number @@ -408,6 +419,7 @@ pub mod pallet { /// should be in [MinDuration; MaxDuration], in other /// words voucher couldn't have expiry greater than /// current block number + MaxDuration. + #[allow(clippy::too_many_arguments)] #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::update())] pub fn update( @@ -417,6 +429,7 @@ pub mod pallet { move_ownership: Option>, balance_top_up: Option>, append_programs: Option>>, + code_uploading: Option, prolong_duration: Option>, ) -> DispatchResultWithPostInfo { // Ensuring origin. @@ -433,7 +446,10 @@ pub mod pallet { let mut updated = false; // Flattening move ownership back to current owner. - let new_owner = move_ownership.filter(|addr| addr.ne(&voucher.owner)); + let new_owner = move_ownership.filter(|addr| *addr != voucher.owner); + + // Flattening code uploading. + let code_uploading = code_uploading.filter(|v| *v != voucher.code_uploading); // Flattening duration prolongation. let prolong_duration = prolong_duration.filter(|dur| !dur.is_zero()); @@ -482,6 +498,14 @@ pub mod pallet { _ => (), } + // Optionally enabling code uploading. + if let Some(code_uploading) = code_uploading { + ensure!(code_uploading, Error::::CodeUploadingEnabled); + + voucher.code_uploading = true; + updated = true; + } + // Optionally prolongs validity of the voucher. if let Some(duration) = prolong_duration { let current_bn = >::block_number(); @@ -534,6 +558,12 @@ pub mod pallet { // Ensuring origin. let origin = ensure_signed(origin)?; + // Validating the call for legacy implementation. + ensure!( + !matches!(call, PrepaidCall::UploadCode { .. }), + Error::::CodeUploadingDisabled + ); + // Looking for sponsor synthetic account. #[allow(deprecated)] let sponsor = Self::call_deprecated_sponsor(&origin, &call) diff --git a/pallets/gear-voucher/src/tests.rs b/pallets/gear-voucher/src/tests.rs index 5345ec34f30..7f27f7d76a3 100644 --- a/pallets/gear-voucher/src/tests.rs +++ b/pallets/gear-voucher/src/tests.rs @@ -17,11 +17,12 @@ // along with this program. If not, see . use super::*; -use crate::{mock::*, tests::utils::DEFAULT_VALIDITY}; +use crate::mock::*; use common::Origin; use frame_support::{assert_noop, assert_ok, assert_storage_noop}; use primitive_types::H256; use sp_runtime::traits::{One, Zero}; +use utils::{DEFAULT_BALANCE, DEFAULT_VALIDITY}; #[test] fn voucher_issue_works() { @@ -30,7 +31,9 @@ fn voucher_issue_works() { let initial_balance = Balances::free_balance(ALICE); - let voucher_id = utils::issue(ALICE, BOB, program_id).expect("Failed to issue voucher"); + let voucher_id = + utils::issue_w_balance_and_uploading(ALICE, BOB, DEFAULT_BALANCE, program_id, false) + .expect("Failed to issue voucher"); assert_eq!( initial_balance, @@ -38,7 +41,9 @@ fn voucher_issue_works() { + Balances::free_balance(ALICE) ); - let voucher_id_2 = utils::issue(ALICE, BOB, program_id).expect("Failed to issue voucher"); + let voucher_id_2 = + utils::issue_w_balance_and_uploading(ALICE, BOB, DEFAULT_BALANCE, program_id, true) + .expect("Failed to issue voucher"); assert_ne!(voucher_id, voucher_id_2); @@ -47,8 +52,12 @@ fn voucher_issue_works() { assert_eq!(voucher_info.programs, Some([program_id].into())); assert_eq!( voucher_info.expiry, - System::block_number().saturating_add(utils::DEFAULT_VALIDITY + 1) + System::block_number().saturating_add(DEFAULT_VALIDITY + 1) ); + assert!(!voucher_info.code_uploading); + + let voucher_info = Vouchers::::get(BOB, voucher_id_2).expect("Voucher isn't found"); + assert!(voucher_info.code_uploading); }); } @@ -63,13 +72,20 @@ fn voucher_issue_err_cases() { .collect(); assert_noop!( - Voucher::issue(RuntimeOrigin::signed(ALICE), BOB, 1_000, Some(set), 100), + Voucher::issue( + RuntimeOrigin::signed(ALICE), + BOB, + 1_000, + Some(set), + false, + 100 + ), Error::::MaxProgramsLimitExceeded, ); // Not enough balance. assert_noop!( - utils::issue_w_balance(ALICE, BOB, 1_000_000_000_000, program_id), + utils::issue_w_balance_and_uploading(ALICE, BOB, 1_000_000_000_000, program_id, false), Error::::BalanceTransfer ); @@ -81,6 +97,7 @@ fn voucher_issue_err_cases() { BOB, 1_000, Some([program_id].into()), + false, duration, ), Error::::DurationOutOfBounds, @@ -137,7 +154,7 @@ fn voucher_call_works() { } )); - // Ok if message exists in mailbox. + // Ok, if message exists in mailbox. assert_ok!(Voucher::call_deprecated( RuntimeOrigin::signed(ALICE), PrepaidCall::SendReply { @@ -155,7 +172,8 @@ fn voucher_call_works() { BOB, 1_000, None, - utils::DEFAULT_VALIDITY, + false, + DEFAULT_VALIDITY, )); let voucher_id_any = utils::get_last_voucher_id(); @@ -182,6 +200,26 @@ fn voucher_call_works() { keep_alive: false }, )); + + // Checking case of code uploading. + assert_ok!(Voucher::issue( + RuntimeOrigin::signed(ALICE), + BOB, + 1_000, + Some(Default::default()), + true, + DEFAULT_VALIDITY, + )); + let voucher_id_code = utils::get_last_voucher_id(); + + // For current mock PrepaidCallDispatcher set as (), so this call passes + // successfully, but in real runtime the result will be + // `Err(pallet_gear::Error::CodeConstructionFailed)`. + assert_ok!(Voucher::call( + RuntimeOrigin::signed(BOB), + voucher_id_code, + PrepaidCall::UploadCode { code: vec![] }, + )); }) } @@ -271,8 +309,18 @@ fn voucher_call_err_cases() { Error::::InappropriateDestination ); + // Voucher doesn't allow code uploading. + assert_noop!( + Voucher::call( + RuntimeOrigin::signed(BOB), + voucher_id, + PrepaidCall::UploadCode { code: vec![] }, + ), + Error::::CodeUploadingDisabled + ); + // Voucher is out of date. - System::set_block_number(System::block_number() + utils::DEFAULT_VALIDITY + 1); + System::set_block_number(System::block_number() + DEFAULT_VALIDITY + 1); assert_noop!( Voucher::call( @@ -303,6 +351,15 @@ fn voucher_call_err_cases() { ), Error::::UnknownDestination ); + + // Code uploading is always forbidden for `call_deprecated`. + assert_noop!( + Voucher::call_deprecated( + RuntimeOrigin::signed(BOB), + PrepaidCall::UploadCode { code: vec![] }, + ), + Error::::CodeUploadingDisabled + ); }) } @@ -314,7 +371,7 @@ fn voucher_revoke_works() { let voucher_id = utils::issue(ALICE, BOB, program_id).expect("Failed to issue voucher"); let voucher_id_acc = voucher_id.cast::>(); - System::set_block_number(System::block_number() + utils::DEFAULT_VALIDITY + 1); + System::set_block_number(System::block_number() + DEFAULT_VALIDITY + 1); let balance_after_issue = Balances::free_balance(ALICE); let voucher_balance = Balances::free_balance(voucher_id_acc); @@ -409,6 +466,8 @@ fn voucher_update_works() { Some(0), // extra programs Some(Some([program_id].into())), + // forbid code uploading (already forbidden) + Some(false), // prolong duration Some(0), ))); @@ -423,6 +482,8 @@ fn voucher_update_works() { Some(balance_top_up), // extra programs Some(Some([new_program_id].into())), + // allow code uploading + Some(true), // prolong duration Some(duration_prolong), )); @@ -448,9 +509,10 @@ fn voucher_update_works() { ); assert_eq!(voucher.owner, BOB); assert_eq!(voucher.programs, Some([program_id, new_program_id].into())); + assert!(voucher.code_uploading); assert_eq!( voucher.expiry, - System::block_number() + utils::DEFAULT_VALIDITY + 1 + duration_prolong + System::block_number() + DEFAULT_VALIDITY + 1 + duration_prolong ); let voucher_balance = Balances::free_balance(voucher_id_acc); @@ -465,6 +527,8 @@ fn voucher_update_works() { None, // extra programs Some(None), + // code uploading + None, // prolong duration None, )); @@ -485,7 +549,7 @@ fn voucher_update_works() { assert_eq!(voucher.programs, None); assert_eq!( voucher.expiry, - System::block_number() + utils::DEFAULT_VALIDITY + 1 + duration_prolong + System::block_number() + DEFAULT_VALIDITY + 1 + duration_prolong ); assert_storage_noop!(assert_ok!(Voucher::update( @@ -498,6 +562,8 @@ fn voucher_update_works() { None, // extra programs Some(Some([program_id].into())), + // code uploading + Some(true), // prolong duration None, ))); @@ -516,6 +582,8 @@ fn voucher_update_works() { None, // extra programs None, + // code uploading + None, // prolong duration Some(duration_prolong), )); @@ -542,6 +610,8 @@ fn voucher_update_works() { None, // extra programs None, + // code uploading + None, // prolong duration Some(valid_prolong + 1), ), @@ -558,6 +628,8 @@ fn voucher_update_works() { None, // extra programs None, + // code uploading + None, // prolong duration Some(valid_prolong), ),); @@ -569,7 +641,9 @@ fn voucher_update_err_cases() { new_test_ext().execute_with(|| { let program_id = H256::random().cast(); - let voucher_id = utils::issue(ALICE, BOB, program_id).expect("Failed to issue voucher"); + let voucher_id = + utils::issue_w_balance_and_uploading(ALICE, BOB, DEFAULT_BALANCE, program_id, true) + .expect("Failed to issue voucher"); // Inexistent voucher. assert_noop!( @@ -583,6 +657,8 @@ fn voucher_update_err_cases() { None, // extra programs None, + // code uploading + None, // prolong duration None, ), @@ -601,6 +677,8 @@ fn voucher_update_err_cases() { None, // extra programs None, + // code uploading + None, // prolong duration None, ), @@ -619,6 +697,8 @@ fn voucher_update_err_cases() { Some(100_000_000_000_000), // extra programs None, + // code uploading + None, // prolong duration None, ), @@ -641,12 +721,34 @@ fn voucher_update_err_cases() { None, // extra programs Some(Some(set)), + // code uploading + None, // prolong duration None, ), Error::::MaxProgramsLimitExceeded ); + // Try to restrict code uploading. + assert_noop!( + Voucher::update( + RuntimeOrigin::signed(ALICE), + BOB, + voucher_id, + // move ownership + None, + // balance top up + None, + // extra programs + None, + // code uploading + Some(false), + // prolong duration + None, + ), + Error::::CodeUploadingEnabled + ); + // Prolongation duration error. assert_noop!( Voucher::update( @@ -659,6 +761,8 @@ fn voucher_update_err_cases() { None, // extra programs None, + // code uploading + None, // prolong duration Some(MaxVoucherDuration::get().saturating_sub(DEFAULT_VALIDITY)), ), @@ -681,21 +785,23 @@ mod utils { to: AccountIdOf, program: ProgramId, ) -> Result { - issue_w_balance(from, to, DEFAULT_BALANCE, program) + issue_w_balance_and_uploading(from, to, DEFAULT_BALANCE, program, false) } #[track_caller] - pub(crate) fn issue_w_balance( + pub(crate) fn issue_w_balance_and_uploading( from: AccountIdOf, to: AccountIdOf, balance: BalanceOf, program: ProgramId, + code_uploading: bool, ) -> Result { Voucher::issue( RuntimeOrigin::signed(from), to, balance, Some([program].into()), + code_uploading, DEFAULT_VALIDITY, ) .map(|_| get_last_voucher_id()) diff --git a/pallets/gear/src/lib.rs b/pallets/gear/src/lib.rs index 4ac0be7f759..d2b2cb95d10 100644 --- a/pallets/gear/src/lib.rs +++ b/pallets/gear/src/lib.rs @@ -1256,18 +1256,7 @@ pub mod pallet { pub fn upload_code(origin: OriginFor, code: Vec) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - let code_id = - Self::set_code_with_metadata(Self::try_new_code(code)?, who.into_origin())?; - - // TODO: replace this temporary (`None`) value - // for expiration block number with properly - // calculated one (issues #646 and #969). - Self::deposit_event(Event::CodeChanged { - id: code_id, - change: CodeChangeKind::Active { expiration: None }, - }); - - Ok(().into()) + Self::upload_code_impl(who, code) } /// Creates program initialization request (message), that is scheduled to be run in the same block. @@ -1764,6 +1753,25 @@ pub mod pallet { Ok(().into()) } + + /// Underlying implementation of `GearPallet::upload_code`. + pub fn upload_code_impl( + origin: AccountIdOf, + code: Vec, + ) -> DispatchResultWithPostInfo { + let code_id = + Self::set_code_with_metadata(Self::try_new_code(code)?, origin.into_origin())?; + + // TODO: replace this temporary (`None`) value + // for expiration block number with properly + // calculated one (issues #646 and #969). + Self::deposit_event(Event::CodeChanged { + id: code_id, + change: CodeChangeKind::Active { expiration: None }, + }); + + Ok(().into()) + } } impl PrepaidCallsDispatcher for Pallet @@ -1781,6 +1789,9 @@ pub mod pallet { PrepaidCall::SendReply { payload, .. } => { ::WeightInfo::send_reply(payload.len() as u32) } + PrepaidCall::UploadCode { code } => { + ::WeightInfo::upload_code(code.len() as u32 / 1024) + } } } @@ -1820,6 +1831,7 @@ pub mod pallet { keep_alive, Some(sponsor_id), ), + PrepaidCall::UploadCode { code } => Self::upload_code_impl(account_id, code), } } } diff --git a/pallets/gear/src/tests.rs b/pallets/gear/src/tests.rs index fcb7ce182ec..82f7f3d9486 100644 --- a/pallets/gear/src/tests.rs +++ b/pallets/gear/src/tests.rs @@ -13316,6 +13316,7 @@ fn send_gasless_message_works() { USER_2, gas_price(DEFAULT_GAS_LIMIT), Some([program_id].into()), + false, 100, )); @@ -13417,6 +13418,7 @@ fn send_gasless_reply_works() { USER_1, gas_price(DEFAULT_GAS_LIMIT), Some([prog_id].into()), + false, 100, )); let voucher_id = utils::get_last_voucher_id(); diff --git a/pallets/payment/src/tests.rs b/pallets/payment/src/tests.rs index b0c25a11b70..a9bbb344356 100644 --- a/pallets/payment/src/tests.rs +++ b/pallets/payment/src/tests.rs @@ -528,6 +528,7 @@ fn fee_payer_replacement_works() { BOB, synthesized_initial_balance, Some([program_id].into()), + false, 100, )); let voucher_id = get_last_voucher_id(); @@ -615,6 +616,7 @@ fn reply_with_voucher_pays_fee_from_voucher_ok() { BOB, 200_000_000, Some([program_id].into()), + false, 100, )); let voucher_id = get_last_voucher_id(); @@ -699,6 +701,7 @@ fn voucher_call_send_payer_ok() { BOB, voucher_initial_balance, Some([program_id].into()), + false, 100, )); let voucher_id = get_last_voucher_id(); @@ -812,6 +815,7 @@ fn voucher_call_send_payer_wrong_program_err() { BOB, voucher_initial_balance, Some([voucher_program_id].into()), + false, 100, )); let voucher_id = get_last_voucher_id(); @@ -869,6 +873,7 @@ fn voucher_call_send_payer_expiry_err() { BOB, voucher_initial_balance, Some([program_id].into()), + false, 100, )); let voucher_id = get_last_voucher_id(); @@ -1023,6 +1028,7 @@ fn voucher_call_reply_payer_ok() { BOB, voucher_initial_balance, Some([program_id].into()), + false, 100, )); let voucher_id = get_last_voucher_id(); @@ -1214,6 +1220,175 @@ fn voucher_call_deprecated_reply_payer_err() { }); } +#[test] +fn voucher_call_upload_payer_ok() { + new_test_ext().execute_with(|| { + // Snapshot of the initial data. + let bob_initial_balance = Balances::free_balance(BOB); + let validator_initial_balance = Balances::free_balance(BLOCK_AUTHOR); + let voucher_initial_balance = 1_000_000_000; + + // Issuing a voucher to Bob from Alice. + assert_ok!(GearVoucher::issue( + RuntimeOrigin::signed(ALICE), + BOB, + voucher_initial_balance, + None, + true, + 100, + )); + let voucher_id = get_last_voucher_id(); + let voucher_account_id = voucher_id.cast::>(); + + assert_eq!( + Balances::free_balance(voucher_account_id), + voucher_initial_balance + ); + + // Creating a RuntimeCall that should be free for caller. + let call = voucher_call_upload(voucher_id, vec![]); + + // Creating simulation of weight params for call to calculate fee. + let len = 100usize; + let fee_length = LengthToFeeFor::::weight_to_fee(&Weight::from_parts(len as u64, 0)); + + let weight = Weight::from_parts(1_000, 0); + let fee_weight = WeightToFeeFor::::weight_to_fee(&weight); + + let call_fee = fee_length + fee_weight; + + // Pre-dispatch of the call. + let pre = CustomChargeTransactionPayment::::from(0) + .pre_dispatch(&BOB, &call, &info_from_weight(weight), len) + .unwrap(); + + // Bob hasn't paid fees. + assert_eq!(Balances::free_balance(BOB), bob_initial_balance); + + // But the voucher has. + assert_eq!( + Balances::free_balance(voucher_account_id), + voucher_initial_balance - call_fee, + ); + + // Validator hasn't received fee yet. + assert_eq!( + Balances::free_balance(BLOCK_AUTHOR), + validator_initial_balance + ); + + // Post-dispatch of the call. + assert_ok!(CustomChargeTransactionPayment::::post_dispatch( + Some(pre), + &info_from_weight(weight), + &default_post_info(), + len, + &Err(pallet_gear::Error::::ProgramConstructionFailed.into()) + )); + + // Validating balances and validator reward. + assert_eq!(Balances::free_balance(BOB), bob_initial_balance); + assert_eq!( + Balances::free_balance(voucher_account_id), + voucher_initial_balance - call_fee, + ); + assert_eq!( + Balances::free_balance(BLOCK_AUTHOR), + validator_initial_balance + call_fee, + ); + }); +} + +#[test] +fn voucher_call_upload_payer_forbidden_err() { + new_test_ext().execute_with(|| { + // Snapshot of the initial data. + let bob_initial_balance = Balances::free_balance(BOB); + let voucher_initial_balance = 1_000_000_000; + + // Issuing a voucher to Bob from Alice. + assert_ok!(GearVoucher::issue( + RuntimeOrigin::signed(ALICE), + BOB, + voucher_initial_balance, + None, + false, + 100, + )); + let voucher_id = get_last_voucher_id(); + let voucher_account_id = voucher_id.cast::>(); + + assert_eq!( + Balances::free_balance(voucher_account_id), + voucher_initial_balance + ); + + // Creating a RuntimeCall that should not be free for caller (voucher is invalid for call). + let call = voucher_call_upload(voucher_id, vec![]); + + // Creating simulation of weight params for call to calculate fee. + let len = 1usize; + let fee_length = LengthToFeeFor::::weight_to_fee(&Weight::from_parts(len as u64, 0)); + + let weight = Weight::from_parts(1, 0); + let fee_weight = WeightToFeeFor::::weight_to_fee(&weight); + + let call_fee = fee_length + fee_weight; + + // Pre-dispatch of the call. + assert_ok!( + CustomChargeTransactionPayment::::from(0).pre_dispatch( + &BOB, + &call, + &info_from_weight(weight), + len + ) + ); + + // Bob has paid fees. + assert_eq!(Balances::free_balance(BOB), bob_initial_balance - call_fee); + + // While voucher is kept the same. + assert_eq!( + Balances::free_balance(voucher_account_id), + voucher_initial_balance + ); + }); +} + +#[test] +fn voucher_call_deprecated_upload_payer_err() { + new_test_ext().execute_with(|| { + // Snapshot of the initial data. + let bob_initial_balance = Balances::free_balance(BOB); + + // Creating a RuntimeCall (deprecated) that should not be free for caller. + let call = voucher_call_deprecated_upload(vec![]); + + // Creating simulation of weight params for call to calculate fee. + let len = 1usize; + let fee_length = LengthToFeeFor::::weight_to_fee(&Weight::from_parts(len as u64, 0)); + + let weight = Weight::from_parts(1, 0); + let fee_weight = WeightToFeeFor::::weight_to_fee(&weight); + + let call_fee = fee_length + fee_weight; + + // Pre-dispatch of the call. + assert_ok!( + CustomChargeTransactionPayment::::from(0).pre_dispatch( + &BOB, + &call, + &info_from_weight(weight), + len + ) + ); + + // Bob has paid fees due forbid for the code uploading in call_deprecated. + assert_eq!(Balances::free_balance(BOB), bob_initial_balance - call_fee); + }); +} + mod utils { use super::*; use crate::BalanceOf; @@ -1251,6 +1426,19 @@ mod utils { }) } + pub fn voucher_call_upload(voucher_id: VoucherId, code: Vec) -> RuntimeCall { + RuntimeCall::GearVoucher(VoucherCall::call { + voucher_id, + call: PrepaidCall::UploadCode { code }, + }) + } + + pub fn voucher_call_deprecated_upload(code: Vec) -> RuntimeCall { + RuntimeCall::GearVoucher(VoucherCall::call_deprecated { + call: PrepaidCall::UploadCode { code }, + }) + } + pub fn prepaid_send(destination: ProgramId) -> PrepaidCall> { PrepaidCall::SendMessage { destination,