diff --git a/pallets/payments/Cargo.toml b/pallets/payments/Cargo.toml index cf4a05c9..970b4c9b 100644 --- a/pallets/payments/Cargo.toml +++ b/pallets/payments/Cargo.toml @@ -32,7 +32,10 @@ pallet-scheduler= { workspace = true } [features] default = ["std"] -runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "pallet-assets/runtime-benchmarks" +] std = [ "parity-scale-codec/std", "scale-info/std", diff --git a/pallets/payments/src/benchmarking.rs b/pallets/payments/src/benchmarking.rs new file mode 100644 index 00000000..22dde6bb --- /dev/null +++ b/pallets/payments/src/benchmarking.rs @@ -0,0 +1,271 @@ +use super::*; +#[allow(unused)] +use crate::{types::*, Pallet as Payments}; +use frame_benchmarking::{account, v2::*}; +use frame_support::{ + traits::{ + fungibles::{Inspect, Mutate}, + Get, + }, + BoundedVec, +}; + +use frame_system::RawOrigin; +use sp_runtime::Percent; + +// Compare `generic_event` to the last emitted event. +fn assert_last_event(generic_event: ::RuntimeEvent) { + frame_system::Pallet::::assert_last_event(generic_event.into()); +} + +fn create_accounts() -> (T::AccountId, T::AccountId, AccountIdLookupOf, AccountIdLookupOf) { + let sender: T::AccountId = account("Alice", 0, 10); + let beneficiary: T::AccountId = account("Bob", 0, 11); + let sender_lookup = T::Lookup::unlookup(sender.clone()); + let beneficiary_lookup = T::Lookup::unlookup(beneficiary.clone()); + + (sender, beneficiary, sender_lookup, beneficiary_lookup) +} + +fn create_and_mint_asset( + sender: &T::AccountId, + beneficiary: &T::AccountId, + asset: &AssetIdOf, + amount: &BalanceOf, +) -> Result<(), BenchmarkError> { + T::BenchmarkHelper::create_asset(asset.clone(), sender.clone(), true, >::from(1u32)); + T::Assets::mint_into(asset.clone(), &sender, >::from(100000u32))?; + T::Assets::mint_into(asset.clone(), &beneficiary, >::from(100000u32))?; + + Ok(()) +} + +fn create_payment( + amount: &BalanceOf, + asset: &AssetIdOf, + remark: Option>, +) -> Result< + ( + T::PaymentId, + T::AccountId, + T::AccountId, + AccountIdLookupOf, + AccountIdLookupOf, + ), + BenchmarkError, +> { + let (sender, beneficiary, sender_lookup, beneficiary_lookup) = create_accounts::(); + create_and_mint_asset::(&sender, &beneficiary, &asset, &>::from(100000u32))?; + + let payment_id: T::PaymentId = Payments::::next_payment_id()?; + + let payment_detail = Payments::::create_payment( + &sender, + &beneficiary, + asset.clone(), + amount.clone(), + PaymentState::Created, + T::IncentivePercentage::get(), + remark.as_ref().map(|x| x.as_slice()), + )?; + + // reserve funds for payment + Payments::::reserve_payment_amount(&sender, &beneficiary, payment_detail)?; + + // TODO: check storage items + + Ok((payment_id, sender, beneficiary, sender_lookup, beneficiary_lookup)) +} + +#[benchmarks( + where + <::Assets as Inspect<::AccountId>>::AssetId: Zero, +)] +mod benchmarks { + use super::*; + + #[benchmark] + fn pay(q: Linear<1, { T::MaxRemarkLength::get() }>) -> Result<(), BenchmarkError> { + let (sender, beneficiary, _, beneficiary_lookup) = create_accounts::(); + let asset: AssetIdOf = >::zero(); + create_and_mint_asset::(&sender, &beneficiary, &asset, &>::from(100000u32))?; + let amount = >::from(50_u32); + + let remark: Option> = if q == 0 { + None + } else { + Some(BoundedVec::try_from(vec![1 as u8; q as usize]).unwrap()) + }; + + #[extrinsic_call] + _( + RawOrigin::Signed(sender.clone()), + beneficiary_lookup, + asset.clone(), + amount, + remark.clone(), + ); + + assert_last_event::( + Event::PaymentCreated { + sender, + beneficiary, + asset, + amount, + remark, + } + .into(), + ); + Ok(()) + } + + #[benchmark] + fn release() -> Result<(), BenchmarkError> { + let amount = >::from(50_u32); + let asset = >::zero(); + let (payment_id, sender, beneficiary, _, beneficiary_lookup) = create_payment::(&amount, &asset, None)?; + + #[extrinsic_call] + _(RawOrigin::Signed(sender.clone()), beneficiary_lookup, payment_id); + + assert_last_event::(Event::PaymentReleased { sender, beneficiary }.into()); + Ok(()) + } + + #[benchmark] + fn cancel() -> Result<(), BenchmarkError> { + let amount = >::from(50_u32); + let asset = >::zero(); + let (payment_id, sender, beneficiary, sender_lookup, _beneficiary_lookup) = + create_payment::(&amount, &asset, None)?; + + #[extrinsic_call] + _(RawOrigin::Signed(beneficiary.clone()), sender_lookup, payment_id); + + assert_last_event::(Event::PaymentCancelled { sender, beneficiary }.into()); + Ok(()) + } + + #[benchmark] + fn request_refund() -> Result<(), BenchmarkError> { + let amount = >::from(50_u32); + let asset = >::zero(); + let (payment_id, sender, beneficiary, _sender_lookup, beneficiary_lookup) = + create_payment::(&amount, &asset, None)?; + + #[extrinsic_call] + _(RawOrigin::Signed(sender.clone()), beneficiary_lookup, payment_id); + + let current_block = frame_system::Pallet::::block_number(); + let expiry = current_block + T::CancelBufferBlockLength::get(); + + assert_last_event::( + Event::PaymentCreatorRequestedRefund { + sender, + beneficiary, + expiry, + } + .into(), + ); + Ok(()) + } + + #[benchmark] + fn dispute_refund() -> Result<(), BenchmarkError> { + let amount = >::from(50_u32); + let asset = >::zero(); + let (payment_id, sender, beneficiary, sender_lookup, beneficiary_lookup) = + create_payment::(&amount, &asset, None)?; + + assert!(Payments::::request_refund( + RawOrigin::Signed(sender.clone()).into(), + beneficiary_lookup, + payment_id + ) + .is_ok()); + + #[extrinsic_call] + _(RawOrigin::Signed(beneficiary.clone()), sender_lookup, payment_id); + + assert_last_event::(Event::PaymentRefundDisputed { sender, beneficiary }.into()); + Ok(()) + } + + #[benchmark] + fn resolve_dispute() -> Result<(), BenchmarkError> { + let amount = >::from(50_u32); + let asset = >::zero(); + let (payment_id, sender, beneficiary, sender_lookup, beneficiary_lookup) = + create_payment::(&amount, &asset, None)?; + + assert!(Payments::::request_refund( + RawOrigin::Signed(sender.clone()).into(), + beneficiary_lookup.clone(), + payment_id + ) + .is_ok()); + + assert!(Payments::::dispute_refund( + RawOrigin::Signed(beneficiary.clone()).into(), + sender_lookup.clone(), + payment_id + ) + .is_ok()); + + let dispute_result = DisputeResult { + percent_beneficiary: Percent::from_percent(90), + in_favor_of: Role::Sender, + }; + + #[extrinsic_call] + _( + RawOrigin::Root, + sender_lookup, + beneficiary_lookup, + payment_id, + dispute_result, + ); + + assert_last_event::(Event::PaymentDisputeResolved { sender, beneficiary }.into()); + Ok(()) + } + + #[benchmark] + fn request_payment() -> Result<(), BenchmarkError> { + let (sender, beneficiary, sender_lookup, _beneficiary_lookup) = create_accounts::(); + let asset: AssetIdOf = >::zero(); + create_and_mint_asset::(&sender, &beneficiary, &asset, &>::from(100000u32))?; + let amount = >::from(50_u32); + + #[extrinsic_call] + _(RawOrigin::Signed(beneficiary.clone()), sender_lookup, asset, amount); + + assert_last_event::(Event::PaymentRequestCreated { sender, beneficiary }.into()); + Ok(()) + } + + #[benchmark] + fn accept_and_pay() -> Result<(), BenchmarkError> { + let (sender, beneficiary, sender_lookup, beneficiary_lookup) = create_accounts::(); + let asset: AssetIdOf = >::zero(); + create_and_mint_asset::(&sender, &beneficiary, &asset, &>::from(100000u32))?; + let amount = >::from(50_u32); + let payment_id: T::PaymentId = Payments::::next_payment_id()?; + + assert!(Payments::::request_payment( + RawOrigin::Signed(beneficiary.clone()).into(), + sender_lookup, + asset, + amount + ) + .is_ok()); + + #[extrinsic_call] + _(RawOrigin::Signed(sender.clone()), beneficiary_lookup, payment_id); + + assert_last_event::(Event::PaymentRequestCreated { sender, beneficiary }.into()); + Ok(()) + } + + impl_benchmark_test_suite!(Payments, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/pallets/payments/src/lib.rs b/pallets/payments/src/lib.rs index 028f020e..02bd2b1b 100644 --- a/pallets/payments/src/lib.rs +++ b/pallets/payments/src/lib.rs @@ -6,6 +6,9 @@ use frame_system::pallet_prelude::BlockNumberFor; /// pub use pallet::*; +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; + #[cfg(test)] mod mock; @@ -52,6 +55,12 @@ pub mod pallet { use frame_system::pallet_prelude::*; use sp_runtime::{traits::Get, Percent}; + + #[cfg(feature = "runtime-benchmarks")] + pub trait BenchmarkHelper { + fn create_asset(id: AssetId, admin: AccountId, is_sufficient: bool, min_balance: Balance); + } + #[pallet::config] pub trait Config: frame_system::Config { type RuntimeEvent: From> + IsType<::RuntimeEvent>; @@ -127,6 +136,9 @@ pub mod pallet { /// canceled payment #[pallet::constant] type CancelBufferBlockLength: Get>; + + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper: BenchmarkHelper, AssetIdOf, BalanceOf>; } #[pallet::pallet] @@ -208,6 +220,11 @@ pub mod pallet { sender: T::AccountId, beneficiary: T::AccountId, }, + /// Payment disputed resolved + PaymentDisputeResolved { + sender: T::AccountId, + beneficiary: T::AccountId, + }, } #[pallet::error] @@ -473,7 +490,7 @@ pub mod pallet { let dispute = Some((dispute_result, dispute_resolver)); Self::settle_payment(&sender, &beneficiary, &payment_id, dispute)?; - Self::deposit_event(Event::PaymentRefundDisputed { sender, beneficiary }); + Self::deposit_event(Event::PaymentDisputeResolved { sender, beneficiary }); Ok(().into()) } diff --git a/pallets/payments/src/mock.rs b/pallets/payments/src/mock.rs index f2ccbdd8..c5378d7f 100644 --- a/pallets/payments/src/mock.rs +++ b/pallets/payments/src/mock.rs @@ -17,6 +17,8 @@ use sp_runtime::{ type Block = frame_system::mocking::MockBlock; type AccountId = u64; +#[allow(unused)] +type AssetId = u32; pub const SENDER_ACCOUNT: AccountId = 10; pub const PAYMENT_BENEFICIARY: AccountId = 11; @@ -213,8 +215,23 @@ impl crate::types::FeeHandler for MockFeeHandler { } } +#[cfg(feature = "runtime-benchmarks")] +pub struct BenchmarkHelper; +#[cfg(feature = "runtime-benchmarks")] +impl super::BenchmarkHelper for BenchmarkHelper { + fn create_asset(id: AssetId, admin: AccountId, is_sufficient: bool, min_balance: Balance) { + >::create( + id, + admin, + is_sufficient, + min_balance, + ) + .unwrap(); + } +} + parameter_types! { - pub const MaxRemarkLength: u32 = 50; + pub const MaxRemarkLength: u8 = 50; pub const IncentivePercentage: Percent = Percent::from_percent(INCENTIVE_PERCENTAGE); pub const PaymentPalletId: PalletId = PalletId(*b"payments"); } @@ -237,6 +254,8 @@ impl pallet_payments::Config for Test { type Preimages = (); type CancelBufferBlockLength = ConstU64<10>; type PalletsOrigin = OriginCaller; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = BenchmarkHelper; } // Build genesis storage according to the mock runtime. diff --git a/runtime/kreivo/Cargo.toml b/runtime/kreivo/Cargo.toml index 0e99c0a0..e7100d17 100644 --- a/runtime/kreivo/Cargo.toml +++ b/runtime/kreivo/Cargo.toml @@ -170,6 +170,7 @@ runtime-benchmarks = [ "pallet-burner/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", "pallet-utility/runtime-benchmarks", + "pallet-payments/runtime-benchmarks", "pallet-proxy/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks",