diff --git a/Cargo.lock b/Cargo.lock index d9d5e2072..136ba251e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2107,6 +2107,7 @@ dependencies = [ "kate", "log", "pallet-balances", + "pallet-timestamp", "pallet-transaction-payment", "pallet-utility", "pallet-vector", diff --git a/pallets/dactr/Cargo.toml b/pallets/dactr/Cargo.toml index 4477b53bd..9759bde25 100644 --- a/pallets/dactr/Cargo.toml +++ b/pallets/dactr/Cargo.toml @@ -41,6 +41,7 @@ rayon = "1.5.2" [dev-dependencies] pallet-balances = { workspace = true, default-features = false, features = ["std"] } +pallet-timestamp = { workspace = true, default-features = false, features = ["std"] } test-case.workspace = true [features] diff --git a/pallets/dactr/src/extensions/check_batch_transactions.rs b/pallets/dactr/src/extensions/check_batch_transactions.rs index e1fc37aa9..85322335c 100644 --- a/pallets/dactr/src/extensions/check_batch_transactions.rs +++ b/pallets/dactr/src/extensions/check_batch_transactions.rs @@ -103,7 +103,7 @@ where ) -> TransactionValidity { use InvalidTransactionCustomId::*; - if iteration > MAX_ITERATIONS { + if iteration >= MAX_ITERATIONS { return Err(InvalidTransaction::Custom(MaxRecursionExceeded as u8).into()); } for call in calls { @@ -193,22 +193,24 @@ where } } -/* #[cfg(test)] +#[cfg(test)] mod tests { use avail_core::{ asdr::AppUncheckedExtrinsic, - InvalidTransactionCustomId::{ForbiddenAppId, InvalidAppId}, + data_proof::Message, + InvalidTransactionCustomId::{ + MaxRecursionExceeded, UnexpectedSendMessageCall, UnexpectedSubmitDataCall, + }, }; use frame_system::pallet::Call as SysCall; use pallet_utility::pallet::Call as UtilityCall; + use sp_core::H256; use sp_runtime::transaction_validity::InvalidTransaction; use test_case::test_case; use super::*; - use crate::{ - mock::{new_test_ext, RuntimeCall, Test}, - pallet::Call as DACall, - }; + use crate::extensions::check_batch_transactions_mock::{new_test_ext, RuntimeCall, Test}; + use crate::pallet::Call as DACall; fn remark_call() -> RuntimeCall { RuntimeCall::System(SysCall::remark { remark: vec![] }) @@ -220,19 +222,24 @@ mod tests { }) } - fn batch_submit_call() -> RuntimeCall { - let call = submit_data_call(); - RuntimeCall::Utility(UtilityCall::batch { - calls: vec![call.clone(), call.clone(), call], + fn send_message_call() -> RuntimeCall { + let message = Message::FungibleToken { + asset_id: H256::default(), + amount: 0, + }; + RuntimeCall::Vector(VectorCall::send_message { + message, + to: H256::default(), + domain: 0, }) } - fn batch_mixed_call() -> RuntimeCall { - let call = submit_data_call(); - let remark = remark_call(); - RuntimeCall::Utility(UtilityCall::batch { - calls: vec![remark, call.clone(), call], - }) + fn batch_call(calls: Vec) -> RuntimeCall { + RuntimeCall::Utility(UtilityCall::batch { calls }) + } + + fn batch_all_call(calls: Vec) -> RuntimeCall { + RuntimeCall::Utility(UtilityCall::batch_all { calls }) } fn to_invalid_tx(custom_id: InvalidTransactionCustomId) -> TransactionValidity { @@ -241,18 +248,40 @@ mod tests { )) } - #[test_case(100, submit_data_call() => to_invalid_tx(InvalidAppId); "100 AppId is invalid" )] - #[test_case(0, remark_call() => Ok(ValidTransaction::default()); "System::remark can be called if AppId == 0" )] - #[test_case(1, remark_call() => to_invalid_tx(ForbiddenAppId); "System::remark cannot be called if AppId != 0" )] - #[test_case(1, submit_data_call() => Ok(ValidTransaction::default()); "submit_data can be called with any valid AppId" )] - #[test_case(1, batch_submit_call() => Ok(ValidTransaction::default()); "utility batch filled with submit_data can be called with any valid AppId" )] - #[test_case(1, batch_mixed_call() => to_invalid_tx(ForbiddenAppId); "utility batch filled with submit_data and remark cannot be called if AppId != 0" )] - #[test_case(0, batch_mixed_call() => Ok(ValidTransaction::default()); "utility batch filled with submit_data and remark can be called if AppId == 0" )] - fn do_validate_test(id: u32, call: RuntimeCall) -> TransactionValidity { + fn validate(call: RuntimeCall) -> TransactionValidity { let extrinsic = AppUncheckedExtrinsic::::new_unsigned(call.clone()); let len = extrinsic.encoded_size(); - new_test_ext().execute_with(|| CheckAppId::::from(AppId(id)).do_validate(&call, len)) + new_test_ext() + .execute_with(|| CheckBatchTransactions::::new().do_validate(&call, len)) + } + + #[test] + fn test_batch_iterations() { + let mut call = batch_call(vec![remark_call()]); + for _ in 0..MAX_ITERATIONS { + call = batch_call(vec![call]) + } + + assert_eq!( + validate(call), + to_invalid_tx(MaxRecursionExceeded), + "Stacking too many Batch calls should be blocked" + ); + } + + #[test_case(submit_data_call() => Ok(ValidTransaction::default()); "Single Submit Data call should be allowed" )] + #[test_case(send_message_call() => Ok(ValidTransaction::default()); "Single Send Message call should be allowed" )] + #[test_case(send_message_call() => Ok(ValidTransaction::default()); "Single Non-Submit-Data and Non-Send-Message call should be allowed" )] + fn test_single_call(call: RuntimeCall) -> TransactionValidity { + validate(call) + } + + #[test_case(vec![remark_call(), submit_data_call()] => to_invalid_tx(UnexpectedSubmitDataCall); "Submit Data call inside a Batch call should be blocked" )] + #[test_case(vec![remark_call(), send_message_call()] => to_invalid_tx(UnexpectedSendMessageCall); "Send Message call inside a Batch call should be blocked" )] + #[test_case(vec![remark_call()] => Ok(ValidTransaction::default()); "Non-Submit-Data and Non-Send-Message call inside a Batch call should be allowed" )] + fn test_batch_call(calls: Vec) -> TransactionValidity { + validate(batch_call(calls.clone()))?; + validate(batch_all_call(calls)) } } - */ diff --git a/pallets/dactr/src/extensions/check_batch_transactions_mock.rs b/pallets/dactr/src/extensions/check_batch_transactions_mock.rs new file mode 100644 index 000000000..486d275f9 --- /dev/null +++ b/pallets/dactr/src/extensions/check_batch_transactions_mock.rs @@ -0,0 +1,104 @@ +#![cfg(test)] + +use frame_support::{derive_impl, weights::IdentityFee}; +use frame_system::{ + header_builder::da::HeaderExtensionBuilder, mocking::MockUncheckedExtrinsic, + test_utils::TestRandomness, +}; +use pallet_transaction_payment::CurrencyAdapter; +use sp_runtime::{AccountId32, BuildStorage}; + +use crate::{self as da_control, *}; + +/// An unchecked extrinsic type to be used in tests. +type Extrinsic = MockUncheckedExtrinsic; +/// An implementation of `sp_runtime::traits::Block` to be used in tests. +type Block = frame_system::mocking::MockDaBlock; +type Balance = u64; + +frame_support::construct_runtime!( + pub struct Test { + Timestamp: pallet_timestamp, + System: frame_system, + Utility: pallet_utility, + Balances: pallet_balances, + TransactionPayment: pallet_transaction_payment, + DataAvailability: da_control, + Vector: pallet_vector, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Test { + type AccountData = pallet_balances::AccountData; + type Block = Block; + type HeaderExtensionBuilder = HeaderExtensionBuilder; + type PalletInfo = PalletInfo; + type Randomness = TestRandomness; + type Extrinsic = Extrinsic; + type AccountId = AccountId32; + type Lookup = sp_runtime::traits::IdentityLookup; +} + +#[derive_impl(pallet_transaction_payment::config_preludes::TestDefaultConfig as pallet_transaction_payment::DefaultConfig)] +impl pallet_transaction_payment::Config for Test { + type LengthToFee = pallet_transaction_payment::TestLengthToFeeU64; + type OnChargeTransaction = CurrencyAdapter; + type WeightToFee = IdentityFee; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig as pallet_balances::DefaultConfig)] +impl pallet_balances::Config for Test { + type AccountStore = System; +} + +impl pallet_utility::Config for Test { + type PalletsOrigin = OriginCaller; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); +} + +#[derive_impl(da_control::config_preludes::TestDefaultConfig)] +impl da_control::Config for Test {} + +#[derive_impl(pallet_vector::config_preludes::TestDefaultConfig as pallet_vector::DefaultConfig)] +impl pallet_vector::Config for Test { + type TimeProvider = Timestamp; + type Currency = Balances; +} + +#[derive_impl(pallet_timestamp::config_preludes::TestDefaultConfig as pallet_timestamp::DefaultConfig)] +impl pallet_timestamp::Config for Test {} + +fn u8_to_account_id(value: u8) -> AccountId32 { + let mut account = [0u8; 32]; + account[0] = value; + + AccountId32::new(account) +} + +/// Create new externalities for `System` module tests. +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut storage = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + let alice = u8_to_account_id(1); + + pallet_balances::GenesisConfig:: { + balances: vec![(alice.clone(), 1_000_000u64)], + } + .assimilate_storage(&mut storage) + .unwrap(); + + da_control::GenesisConfig:: { + app_keys: vec![(b"Avail".to_vec(), (alice, 0))], + } + .assimilate_storage(&mut storage) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(storage); + ext.execute_with(|| System::set_block_number(1)); + ext +} diff --git a/pallets/dactr/src/extensions/mod.rs b/pallets/dactr/src/extensions/mod.rs index 01785af3d..24524f5ab 100644 --- a/pallets/dactr/src/extensions/mod.rs +++ b/pallets/dactr/src/extensions/mod.rs @@ -1,4 +1,5 @@ pub mod check_app_id; pub mod check_batch_transactions; +pub mod check_batch_transactions_mock; const MAX_ITERATIONS: usize = 2; diff --git a/pallets/dactr/src/lib.rs b/pallets/dactr/src/lib.rs index ffb714218..2d1fffea1 100644 --- a/pallets/dactr/src/lib.rs +++ b/pallets/dactr/src/lib.rs @@ -55,6 +55,7 @@ pub mod pallet { pub mod config_preludes { use super::*; use frame_support::derive_impl; + use frame_support::parameter_types; /// Provides a viable default config that can be used with /// [`derive_impl`](`frame_support::derive_impl`) to derive a testing pallet config @@ -64,15 +65,24 @@ pub mod pallet { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig, no_aggregated_types)] impl frame_system::DefaultConfig for TestDefaultConfig {} + parameter_types! { + pub const MinBlockRows: BlockLengthRows = BlockLengthRows(32); + pub const MaxBlockRows: BlockLengthRows = BlockLengthRows(1024); + pub const MinBlockCols: BlockLengthColumns = BlockLengthColumns(64); + pub const MaxBlockCols: BlockLengthColumns = BlockLengthColumns(256); + } + pub type MaxAppKeyLength = ConstU32<64>; + pub type MaxAppDataLength = ConstU32<524_288>; // 512 Kb + #[frame_support::register_default_impl(TestDefaultConfig)] impl DefaultConfig for TestDefaultConfig { type BlockLenProposalId = u32; - type MaxAppDataLength = (); - type MaxAppKeyLength = (); - type MaxBlockCols = (); - type MaxBlockRows = (); - type MinBlockCols = (); - type MinBlockRows = (); + type MaxAppDataLength = MaxAppDataLength; + type MaxAppKeyLength = MaxAppKeyLength; + type MaxBlockCols = MaxBlockCols; + type MaxBlockRows = MaxBlockRows; + type MinBlockCols = MinBlockCols; + type MinBlockRows = MinBlockRows; type WeightInfo = (); #[inject_runtime_type] type RuntimeEvent = (); diff --git a/pallets/system/src/lib.rs b/pallets/system/src/lib.rs index 2c45d9c92..7172f9ae0 100644 --- a/pallets/system/src/lib.rs +++ b/pallets/system/src/lib.rs @@ -272,7 +272,7 @@ pub mod pallet { /// Default implementations of [`DefaultConfig`], which can be used to implement [`Config`]. pub mod config_preludes { use super::{inject_runtime_type, AccountInfo, BlakeTwo256, DaHeader, DefaultConfig}; - use frame_support::derive_impl; + use frame_support::{derive_impl, traits::ConstU32}; /// Provides a viable default config that can be used with /// [`derive_impl`](`frame_support::derive_impl`) to derive a testing pallet config @@ -310,9 +310,12 @@ pub mod pallet { #[inject_runtime_type] type RuntimeTask = (); type BaseCallFilter = frame_support::traits::Everything; - type BlockHashCount = frame_support::traits::ConstU64<10>; + type BlockHashCount = frame_support::traits::ConstU32<10>; type OnSetCode = (); type Header = DaHeader; + type MaxDiffAppIdPerBlock = ConstU32<1_024>; + type MaxTxPerAppIdPerBlock = ConstU32<8_192>; + type TxDataExtractor = (); } /// Default configurations of this pallet in a solo-chain environment. @@ -409,6 +412,9 @@ pub mod pallet { type OnSetCode = (); type Header = DaHeader; + type MaxDiffAppIdPerBlock = ConstU32<1_024>; + type MaxTxPerAppIdPerBlock = ConstU32<8_192>; + type TxDataExtractor = (); } /// Default configurations of this pallet in a relay-chain environment. @@ -608,21 +614,18 @@ pub mod pallet { type MaxConsumers: ConsumerLimits; /// Filter used by `DataRootBuilder`. - #[pallet::no_default] type TxDataExtractor: TxDataFilter; /// Maximum different `AppId` allowed per block. /// This is used during the calculation of padded length of the block when /// a transaction is validated (see `CheckAppId` signed extension). #[pallet::constant] - #[pallet::no_default] type MaxDiffAppIdPerBlock: Get; /// Maximum number of Tx per AppId allowed per block. /// This is used during the calculation of padded length of the block when /// a transaction is validated (see `CheckAppId` signed extension). #[pallet::constant] - #[pallet::no_default] type MaxTxPerAppIdPerBlock: Get; } diff --git a/pallets/transaction-payment/src/lib.rs b/pallets/transaction-payment/src/lib.rs index d6d80924d..04b69e1ce 100644 --- a/pallets/transaction-payment/src/lib.rs +++ b/pallets/transaction-payment/src/lib.rs @@ -1048,3 +1048,13 @@ where Self::compute_actual_fee(len, &info, &post_info, Zero::zero()) } } + +pub struct TestLengthToFeeU64; + +impl WeightToFee for TestLengthToFeeU64 { + type Balance = u64; + + fn weight_to_fee(_weight: &Weight) -> Self::Balance { + 0 + } +} diff --git a/pallets/vector/src/lib.rs b/pallets/vector/src/lib.rs index 6a6190b81..9607b1bb6 100644 --- a/pallets/vector/src/lib.rs +++ b/pallets/vector/src/lib.rs @@ -241,19 +241,55 @@ pub mod pallet { #[pallet::getter(fn source_chain_id)] pub type SourceChainId = StorageValue<_, u64, ValueQuery>; - #[pallet::config] + /// Default implementations of [`DefaultConfig`], which can be used to implement [`Config`]. + pub mod config_preludes { + use super::*; + use frame_support::derive_impl; + use frame_support::parameter_types; + use frame_support::traits::ConstU64; + + parameter_types! { + pub const BridgePalletId: PalletId = PalletId(*b"avl/brdg"); + } + + /// Provides a viable default config that can be used with + /// [`derive_impl`](`frame_support::derive_impl`) to derive a testing pallet config + /// based on this one. + pub struct TestDefaultConfig; + + #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig, no_aggregated_types)] + impl frame_system::DefaultConfig for TestDefaultConfig {} + + #[frame_support::register_default_impl(TestDefaultConfig)] + impl DefaultConfig for TestDefaultConfig { + type WeightInfo = (); + type MessageMappingStorageIndex = ConstU64<1>; + type AvailDomain = ConstU32<1>; + #[inject_runtime_type] + type RuntimeEvent = (); + #[inject_runtime_type] + type RuntimeCall = (); + type PalletId = BridgePalletId; + } + } + + #[pallet::config(with_default)] pub trait Config: frame_system::Config { /// Because this pallet emits events, it depends on the runtime's definition of an event. + #[pallet::no_default_bounds] type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// Because this pallet has dispatchables, it depends on the runtime's definition of a call. + #[pallet::no_default_bounds] type RuntimeCall: Parameter + UnfilteredDispatchable + GetDispatchInfo; /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; /// Currency type for this pallet. + #[pallet::no_default] type Currency: LockableCurrency>; /// Dependency that can provide current time. + #[pallet::no_default] type TimeProvider: UnixTime; /// The index of the `messages` mapping in contract. /// This is mandatory when calling execute messages via storage proofs. diff --git a/runtime/src/data_root_tests.rs b/runtime/src/data_root_tests.rs index 79ae41c53..5c46ddf3b 100644 --- a/runtime/src/data_root_tests.rs +++ b/runtime/src/data_root_tests.rs @@ -3,7 +3,7 @@ use crate::{Extrinsic, Runtime, SignedExtra}; use avail_base::data_root::build_tx_data; use avail_core::data_proof::{BoundedData, Message, TxDataRoots}; -use da_control::{AppDataFor, Call as DaCall, CheckAppId}; +use da_control::{AppDataFor, Call as DaCall, CheckAppId, CheckBatchTransactions}; use frame_system::{ CheckEra, CheckGenesis, CheckNonZeroSender, CheckNonce, CheckSpecVersion, CheckTxVersion, CheckWeight, @@ -33,6 +33,7 @@ fn extra() -> SignedExtra { CheckWeight::::new(), ChargeTransactionPayment::::from(0), CheckAppId::::from(AppId(1)), + CheckBatchTransactions::::new(), ) } fn additional_signed() -> ::AdditionalSigned { @@ -41,7 +42,7 @@ fn additional_signed() -> ::AdditionalSigned { let genesis = H256::default(); let era = H256::repeat_byte(1); - ((), spec_ver, tx_ver, genesis, era, (), (), (), ()) + ((), spec_ver, tx_ver, genesis, era, (), (), (), (), ()) } fn signed_extrinsic(function: RuntimeCall) -> Vec {