diff --git a/Cargo.lock b/Cargo.lock index dad578ba0c1b..d35f223f59c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7424,6 +7424,7 @@ dependencies = [ "derive-syn-parse", "docify", "expander", + "frame-benchmarking 28.0.0", "frame-support 28.0.0", "frame-support-procedural-tools 10.0.0", "frame-system 28.0.0", diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs index b6f3ccd3901b..fd11671c8bfa 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -1008,6 +1008,7 @@ pub type SignedBlock = generic::SignedBlock; pub type BlockId = generic::BlockId; /// The extension to the basic transaction logic. pub type TxExtension = ( + frame_system::AuthorizeCall, frame_system::CheckNonZeroSender, frame_system::CheckSpecVersion, frame_system::CheckTxVersion, diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 21368e9c2b4b..1cfb7406e5ae 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -1053,6 +1053,7 @@ pub type SignedBlock = generic::SignedBlock; pub type BlockId = generic::BlockId; /// The extension to the basic transaction logic. pub type TxExtension = ( + frame_system::AuthorizeCall, frame_system::CheckNonZeroSender, frame_system::CheckSpecVersion, frame_system::CheckTxVersion, @@ -1075,6 +1076,7 @@ impl EthExtra for EthExtraImpl { fn get_eth_extension(nonce: u32, tip: Balance) -> Self::Extension { ( + frame_system::AuthorizeCall::::new(), frame_system::CheckNonZeroSender::::new(), frame_system::CheckSpecVersion::::new(), frame_system::CheckTxVersion::::new(), diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index d87ff9b43fef..9f9b6cf0fb40 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -125,7 +125,8 @@ pub type BlockId = generic::BlockId; /// The TransactionExtension to the basic transaction logic. pub type TxExtension = ( - frame_system::CheckNonZeroSender, + // `Debug` is implemented for tuple of at most 12 elements, so we group extensions. + (frame_system::AuthorizeCall, frame_system::CheckNonZeroSender), frame_system::CheckSpecVersion, frame_system::CheckTxVersion, frame_system::CheckGenesis, @@ -1559,7 +1560,10 @@ mod tests { sp_io::TestExternalities::default().execute_with(|| { frame_system::BlockHash::::insert(BlockNumber::zero(), Hash::default()); let payload: TxExtension = ( - frame_system::CheckNonZeroSender::new(), + ( + frame_system::AuthorizeCall::::new(), + frame_system::CheckNonZeroSender::new(), + ), frame_system::CheckSpecVersion::new(), frame_system::CheckTxVersion::new(), frame_system::CheckGenesis::new(), diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs index 8be2993c68f4..3fe5f6585320 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs @@ -171,7 +171,10 @@ fn construct_extrinsic( ) -> UncheckedExtrinsic { let account_id = AccountId32::from(sender.public()); let tx_ext: TxExtension = ( - frame_system::CheckNonZeroSender::::new(), + ( + frame_system::AuthorizeCall::::new(), + frame_system::CheckNonZeroSender::::new(), + ), frame_system::CheckSpecVersion::::new(), frame_system::CheckTxVersion::::new(), frame_system::CheckGenesis::::new(), diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs index 44e69c31a560..0475271cf365 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs @@ -50,7 +50,10 @@ fn construct_extrinsic( ) -> UncheckedExtrinsic { let account_id = AccountId32::from(sender.public()); let tx_ext: TxExtension = ( - frame_system::CheckNonZeroSender::::new(), + ( + frame_system::AuthorizeCall::::new(), + frame_system::CheckNonZeroSender::::new(), + ), frame_system::CheckSpecVersion::::new(), frame_system::CheckTxVersion::::new(), frame_system::CheckGenesis::::new(), diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index ae3dbfa06cba..f6df2f92488f 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -121,7 +121,8 @@ pub type BlockId = generic::BlockId; /// The TransactionExtension to the basic transaction logic. pub type TxExtension = ( - frame_system::CheckNonZeroSender, + // `Debug` is implemented for tuple of at most 12 elements, so we group extensions. + (frame_system::AuthorizeCall, frame_system::CheckNonZeroSender), frame_system::CheckSpecVersion, frame_system::CheckTxVersion, frame_system::CheckGenesis, @@ -1381,7 +1382,10 @@ mod tests { sp_io::TestExternalities::default().execute_with(|| { frame_system::BlockHash::::insert(BlockNumber::zero(), Hash::default()); let payload: TxExtension = ( - frame_system::CheckNonZeroSender::new(), + ( + frame_system::AuthorizeCall::new(), + frame_system::CheckNonZeroSender::new(), + ), frame_system::CheckSpecVersion::new(), frame_system::CheckTxVersion::new(), frame_system::CheckGenesis::new(), diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs index 1a1ce2a28ea3..9bb6c4fde7af 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs @@ -172,7 +172,10 @@ fn construct_extrinsic( ) -> UncheckedExtrinsic { let account_id = AccountId32::from(sender.public()); let extra: TxExtension = ( - frame_system::CheckNonZeroSender::::new(), + ( + frame_system::AuthorizeCall::::new(), + frame_system::CheckNonZeroSender::::new(), + ), frame_system::CheckSpecVersion::::new(), frame_system::CheckTxVersion::::new(), frame_system::CheckGenesis::::new(), diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs index d7e70ed769b1..0cd9200c8e50 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs @@ -82,7 +82,10 @@ fn construct_extrinsic( ) -> UncheckedExtrinsic { let account_id = AccountId32::from(sender.public()); let tx_ext: TxExtension = ( - frame_system::CheckNonZeroSender::::new(), + ( + frame_system::AuthorizeCall::::new(), + frame_system::CheckNonZeroSender::::new(), + ), frame_system::CheckSpecVersion::::new(), frame_system::CheckTxVersion::::new(), frame_system::CheckGenesis::::new(), diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs index f4c62f212e8c..d3cf5944d874 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs @@ -736,6 +736,7 @@ pub type SignedBlock = generic::SignedBlock; pub type BlockId = generic::BlockId; /// The extension to the basic transaction logic. pub type TxExtension = ( + frame_system::AuthorizeCall, frame_system::CheckNonZeroSender, frame_system::CheckSpecVersion, frame_system::CheckTxVersion, diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs index 2951662a979b..42299f8fcf44 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs @@ -89,6 +89,7 @@ pub type SignedBlock = generic::SignedBlock; pub type BlockId = generic::BlockId; /// The extension to the basic transaction logic. pub type TxExtension = ( + frame_system::AuthorizeCall, frame_system::CheckNonZeroSender, frame_system::CheckSpecVersion, frame_system::CheckTxVersion, diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs index ae3ad93a9e85..3e455873d767 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs @@ -101,6 +101,7 @@ pub type BlockId = generic::BlockId; /// The TransactionExtension to the basic transaction logic. pub type TxExtension = ( frame_system::CheckNonZeroSender, + frame_system::AuthorizeCall, frame_system::CheckSpecVersion, frame_system::CheckTxVersion, frame_system::CheckGenesis, diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs index 39ea39f25a8b..197bfa5d7ddb 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs @@ -100,6 +100,7 @@ pub type BlockId = generic::BlockId; /// The TransactionExtension to the basic transaction logic. pub type TxExtension = ( + frame_system::AuthorizeCall, frame_system::CheckNonZeroSender, frame_system::CheckSpecVersion, frame_system::CheckTxVersion, diff --git a/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs b/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs index fdf467ab64b8..7f6c2ee87f62 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs @@ -292,6 +292,7 @@ pub type SignedBlock = generic::SignedBlock; pub type BlockId = generic::BlockId; /// The extension to the basic transaction logic. pub type TxExtension = ( + frame_system::AuthorizeCall, pallet_sudo::CheckOnlySudoAccount, frame_system::CheckNonZeroSender, frame_system::CheckSpecVersion, diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs index dc5f2ac0997c..7bdd703f7f19 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs @@ -93,6 +93,7 @@ pub type BlockId = generic::BlockId; /// The TransactionExtension to the basic transaction logic. pub type TxExtension = ( + frame_system::AuthorizeCall, frame_system::CheckNonZeroSender, frame_system::CheckSpecVersion, frame_system::CheckTxVersion, diff --git a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs index 1b9a3b60a2c4..a21a61f1774c 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs @@ -93,6 +93,7 @@ pub type BlockId = generic::BlockId; /// The transactionExtension to the basic transaction logic. pub type TxExtension = ( + frame_system::AuthorizeCall, frame_system::CheckNonZeroSender, frame_system::CheckSpecVersion, frame_system::CheckTxVersion, diff --git a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs index b51670c792d6..93a4469af6b0 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs @@ -132,6 +132,7 @@ pub type AssetId = u32; /// The extension to the basic transaction logic. pub type TxExtension = ( + frame_system::AuthorizeCall, frame_system::CheckNonZeroSender, frame_system::CheckSpecVersion, frame_system::CheckTxVersion, diff --git a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs index 42556e0b493c..2f07123e49c2 100644 --- a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs @@ -658,6 +658,7 @@ pub type SignedBlock = generic::SignedBlock; pub type BlockId = generic::BlockId; /// The extension to the basic transaction logic. pub type TxExtension = ( + frame_system::AuthorizeCall, frame_system::CheckNonZeroSender, frame_system::CheckSpecVersion, frame_system::CheckTxVersion, diff --git a/cumulus/test/client/src/lib.rs b/cumulus/test/client/src/lib.rs index 863a8fa93f6f..337b71430ca8 100644 --- a/cumulus/test/client/src/lib.rs +++ b/cumulus/test/client/src/lib.rs @@ -136,6 +136,7 @@ pub fn generate_extrinsic_with_pair( BlockHashCount::get().checked_next_power_of_two().map(|c| c / 2).unwrap_or(2) as u64; let tip = 0; let tx_ext: TxExtension = ( + frame_system::AuthorizeCall::::new(), frame_system::CheckNonZeroSender::::new(), frame_system::CheckSpecVersion::::new(), frame_system::CheckGenesis::::new(), @@ -152,7 +153,7 @@ pub fn generate_extrinsic_with_pair( let raw_payload = SignedPayload::from_raw( function.clone(), tx_ext.clone(), - ((), VERSION.spec_version, genesis_block, current_block_hash, (), (), (), ()), + ((), (), VERSION.spec_version, genesis_block, current_block_hash, (), (), (), ()), ); let signature = raw_payload.using_encoded(|e| origin.sign(e)); diff --git a/cumulus/test/runtime/src/lib.rs b/cumulus/test/runtime/src/lib.rs index b1649c410581..9d1769abc94d 100644 --- a/cumulus/test/runtime/src/lib.rs +++ b/cumulus/test/runtime/src/lib.rs @@ -373,6 +373,7 @@ pub type SignedBlock = generic::SignedBlock; pub type BlockId = generic::BlockId; /// The extension to the basic transaction logic. pub type TxExtension = ( + frame_system::AuthorizeCall, frame_system::CheckNonZeroSender, frame_system::CheckSpecVersion, frame_system::CheckGenesis, diff --git a/cumulus/test/service/src/lib.rs b/cumulus/test/service/src/lib.rs index f01da9becef1..4b740e6f7a0f 100644 --- a/cumulus/test/service/src/lib.rs +++ b/cumulus/test/service/src/lib.rs @@ -958,6 +958,7 @@ pub fn construct_extrinsic( .unwrap_or(2) as u64; let tip = 0; let tx_ext: runtime::TxExtension = ( + frame_system::AuthorizeCall::::new(), frame_system::CheckNonZeroSender::::new(), frame_system::CheckSpecVersion::::new(), frame_system::CheckGenesis::::new(), @@ -974,7 +975,7 @@ pub fn construct_extrinsic( let raw_payload = runtime::SignedPayload::from_raw( function.clone(), tx_ext.clone(), - ((), runtime::VERSION.spec_version, genesis_block, current_block_hash, (), (), (), ()), + ((), (), runtime::VERSION.spec_version, genesis_block, current_block_hash, (), (), (), ()), ); let signature = raw_payload.using_encoded(|e| caller.sign(e)); runtime::UncheckedExtrinsic::new_signed( diff --git a/polkadot/node/service/src/benchmarking.rs b/polkadot/node/service/src/benchmarking.rs index 0cf16edc03cc..c34bf9799b0f 100644 --- a/polkadot/node/service/src/benchmarking.rs +++ b/polkadot/node/service/src/benchmarking.rs @@ -143,6 +143,7 @@ fn westend_sign_call( use westend_runtime as runtime; let tx_ext: runtime::TxExtension = ( + frame_system::AuthorizeCall::::new(), frame_system::CheckNonZeroSender::::new(), frame_system::CheckSpecVersion::::new(), frame_system::CheckTxVersion::::new(), @@ -162,6 +163,7 @@ fn westend_sign_call( call.clone(), tx_ext.clone(), ( + (), (), runtime::VERSION.spec_version, runtime::VERSION.transaction_version, @@ -198,6 +200,7 @@ fn rococo_sign_call( use sp_core::Pair; let tx_ext: runtime::TxExtension = ( + frame_system::AuthorizeCall::::new(), frame_system::CheckNonZeroSender::::new(), frame_system::CheckSpecVersion::::new(), frame_system::CheckTxVersion::::new(), @@ -217,6 +220,7 @@ fn rococo_sign_call( call.clone(), tx_ext.clone(), ( + (), (), runtime::VERSION.spec_version, runtime::VERSION.transaction_version, diff --git a/polkadot/node/test/service/src/lib.rs b/polkadot/node/test/service/src/lib.rs index 6e09bb9e4310..d3b54047ef0e 100644 --- a/polkadot/node/test/service/src/lib.rs +++ b/polkadot/node/test/service/src/lib.rs @@ -415,6 +415,7 @@ pub fn construct_extrinsic( BlockHashCount::get().checked_next_power_of_two().map(|c| c / 2).unwrap_or(2) as u64; let tip = 0; let tx_ext: TxExtension = ( + frame_system::AuthorizeCall::::new(), frame_system::CheckNonZeroSender::::new(), frame_system::CheckSpecVersion::::new(), frame_system::CheckTxVersion::::new(), @@ -429,6 +430,7 @@ pub fn construct_extrinsic( function.clone(), tx_ext.clone(), ( + (), (), VERSION.spec_version, VERSION.transaction_version, diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index c832ace91c07..78dd54476ff6 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -651,6 +651,7 @@ where .saturating_sub(1); let tip = 0; let tx_ext: TxExtension = ( + frame_system::AuthorizeCall::::new(), frame_system::CheckNonZeroSender::::new(), frame_system::CheckSpecVersion::::new(), frame_system::CheckTxVersion::::new(), @@ -698,6 +699,26 @@ where } } +impl frame_system::offchain::CreateAuthorizedTransaction for Runtime +where + RuntimeCall: From, +{ + fn create_extension() -> Self::Extension { + ( + frame_system::AuthorizeCall::::new(), + frame_system::CheckNonZeroSender::::new(), + frame_system::CheckSpecVersion::::new(), + frame_system::CheckTxVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckMortality::::from(generic::Era::Immortal), + frame_system::CheckNonce::::from(0), + frame_system::CheckWeight::::new(), + pallet_transaction_payment::ChargeTransactionPayment::::from(0), + frame_metadata_hash_extension::CheckMetadataHash::new(false), + ) + } +} + parameter_types! { pub Prefix: &'static [u8] = b"Pay ROCs to the Rococo account:"; } @@ -1596,6 +1617,7 @@ pub type SignedBlock = generic::SignedBlock; pub type BlockId = generic::BlockId; /// The extension to the basic transaction logic. pub type TxExtension = ( + frame_system::AuthorizeCall, frame_system::CheckNonZeroSender, frame_system::CheckSpecVersion, frame_system::CheckTxVersion, diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs index d4031f7ac57a..8a8170749cce 100644 --- a/polkadot/runtime/test-runtime/src/lib.rs +++ b/polkadot/runtime/test-runtime/src/lib.rs @@ -197,6 +197,25 @@ where } } +impl frame_system::offchain::CreateAuthorizedTransaction for Runtime +where + RuntimeCall: From, +{ + fn create_extension() -> Self::Extension { + ( + frame_system::AuthorizeCall::::new(), + frame_system::CheckNonZeroSender::::new(), + frame_system::CheckSpecVersion::::new(), + frame_system::CheckTxVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckMortality::::from(generic::Era::Immortal), + frame_system::CheckNonce::::from(0), + frame_system::CheckWeight::::new(), + pallet_transaction_payment::ChargeTransactionPayment::::from(0), + ) + } +} + parameter_types! { pub storage EpochDuration: u64 = EPOCH_DURATION_IN_SLOTS as u64; pub storage ExpectedBlockTime: Moment = MILLISECS_PER_BLOCK; @@ -432,6 +451,7 @@ where let current_block = System::block_number().saturated_into::().saturating_sub(1); let tip = 0; let tx_ext: TxExtension = ( + frame_system::AuthorizeCall::::new(), frame_system::CheckNonZeroSender::::new(), frame_system::CheckSpecVersion::::new(), frame_system::CheckTxVersion::::new(), @@ -826,6 +846,7 @@ pub type SignedBlock = generic::SignedBlock; pub type BlockId = generic::BlockId; /// The extension to the basic transaction logic. pub type TxExtension = ( + frame_system::AuthorizeCall, frame_system::CheckNonZeroSender, frame_system::CheckSpecVersion, frame_system::CheckTxVersion, diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index f25ed33012a2..245798ab6684 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -911,6 +911,7 @@ where .saturating_sub(1); let tip = 0; let tx_ext: TxExtension = ( + frame_system::AuthorizeCall::::new(), frame_system::CheckNonZeroSender::::new(), frame_system::CheckSpecVersion::::new(), frame_system::CheckTxVersion::::new(), @@ -947,6 +948,26 @@ where } } +impl frame_system::offchain::CreateAuthorizedTransaction for Runtime +where + RuntimeCall: From, +{ + fn create_extension() -> Self::Extension { + ( + frame_system::AuthorizeCall::::new(), + frame_system::CheckNonZeroSender::::new(), + frame_system::CheckSpecVersion::::new(), + frame_system::CheckTxVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckMortality::::from(generic::Era::Immortal), + frame_system::CheckNonce::::from(0), + frame_system::CheckWeight::::new(), + pallet_transaction_payment::ChargeTransactionPayment::::from(0), + frame_metadata_hash_extension::CheckMetadataHash::::new(false), + ) + } +} + parameter_types! { // Minimum 100 bytes/KSM deposited (1 CENT/byte) pub const BasicDeposit: Balance = 1000 * CENTS; // 258 bytes on-chain @@ -1804,6 +1825,7 @@ pub type SignedBlock = generic::SignedBlock; pub type BlockId = generic::BlockId; /// The extension to the basic transaction logic. pub type TxExtension = ( + frame_system::AuthorizeCall, frame_system::CheckNonZeroSender, frame_system::CheckSpecVersion, frame_system::CheckTxVersion, diff --git a/polkadot/xcm/xcm-builder/src/tests/pay/mock.rs b/polkadot/xcm/xcm-builder/src/tests/pay/mock.rs index 26ea226313f0..0cc3682a0432 100644 --- a/polkadot/xcm/xcm-builder/src/tests/pay/mock.rs +++ b/polkadot/xcm/xcm-builder/src/tests/pay/mock.rs @@ -30,6 +30,7 @@ use sp_runtime::{generic, traits::MaybeEquivalence, AccountId32, BuildStorage}; use xcm_executor::{traits::ConvertLocation, XcmExecutor}; pub type TxExtension = ( + frame_system::AuthorizeCall, frame_system::CheckNonZeroSender, frame_system::CheckSpecVersion, frame_system::CheckTxVersion, diff --git a/prdoc/pr_6324.prdoc b/prdoc/pr_6324.prdoc new file mode 100644 index 000000000000..9b3987bc05b3 --- /dev/null +++ b/prdoc/pr_6324.prdoc @@ -0,0 +1,75 @@ +title: Introduce `#[pallet::authorize(...)]` macro attribute and `AuthorizeCall` system transaction extension +doc: +- audience: Runtime Dev + description: | + Some calls such as `claim` or `apply_authorized_upgrade` wants to be valid depending on the + parameters of the call or/and the state of the chain. + For instance `claim` is valid if the signature given as a parameter of the call is valid and if the state of the chain contains some pending claim. + Or the `apply_authorized_upgrade` is valid if the code given as a paramter of the call match the authorized hashed code on chain. + Those operation don't require a signed origin and are currently achieved using `ValidateUnsigned`. + This PR introduce a new pallet attribute `#[pallet::authorize(...)]` and a new system transaction extension `AuthorizeCall` to ease such operations. + This change is part of the broader plan for extrinsics: https://github.com/paritytech/polkadot-sdk/issues/2415. + + The usage of `pallet::authorize` can be found in the rust documentation. It takes a function to define the validation logic. Another attribute `pallet::weight_of_authorize` is introduce, it takes a function to define the weight of the validation logic. + + The runtime must now use the new system transaction extension `AuthorizeCall` in their transaction extension pipeline. It is suggested to put it first. + ``` + pub type TxExtension = ( + frame_system::AuthorizeCall + frame_system::CheckNonZeroSender, + frame_system::CheckSpecVersion, + ... + ); + ``` + +crates: +- name: frame-support + bump: major +- name: sp-runtime + bump: minor +- name: frame-executive + bump: none +- name: frame-system + bump: major +- name: frame-benchmarking + bump: minor +- name: frame-support-procedural + bump: major +- name: staging-xcm-builder + bump: none +- name: polkadot-service + bump: major +- name: westend-runtime + bump: major +- name: frame-metadata-hash-extension + bump: none +- name: polkadot-sdk-frame + bump: major +- name: rococo-runtime + bump: major +- name: asset-hub-rococo-runtime + bump: major +- name: asset-hub-westend-runtime + bump: major +- name: bridge-hub-rococo-runtime + bump: major +- name: bridge-hub-westend-runtime + bump: major +- name: collectives-westend-runtime + bump: major +- name: coretime-rococo-runtime + bump: major +- name: coretime-westend-runtime + bump: major +- name: people-rococo-runtime + bump: major +- name: people-westend-runtime + bump: major +- name: penpal-runtime + bump: major +- name: contracts-rococo-runtime + bump: major +- name: glutton-westend-runtime + bump: major +- name: rococo-parachain-runtime + bump: major diff --git a/substrate/bin/node/cli/src/service.rs b/substrate/bin/node/cli/src/service.rs index 5cde85ae5790..82645d6d9fd6 100644 --- a/substrate/bin/node/cli/src/service.rs +++ b/substrate/bin/node/cli/src/service.rs @@ -122,6 +122,7 @@ pub fn create_extrinsic( let tip = 0; let tx_ext: kitchensink_runtime::TxExtension = ( + frame_system::AuthorizeCall::::new(), frame_system::CheckNonZeroSender::::new(), frame_system::CheckSpecVersion::::new(), frame_system::CheckTxVersion::::new(), @@ -144,6 +145,7 @@ pub fn create_extrinsic( function.clone(), tx_ext.clone(), ( + (), (), kitchensink_runtime::VERSION.spec_version, kitchensink_runtime::VERSION.transaction_version, @@ -1050,6 +1052,7 @@ mod tests { value: amount, }); + let authorize_call = frame_system::AuthorizeCall::new(); let check_non_zero_sender = frame_system::CheckNonZeroSender::new(); let check_spec_version = frame_system::CheckSpecVersion::new(); let check_tx_version = frame_system::CheckTxVersion::new(); @@ -1062,6 +1065,7 @@ mod tests { ); let metadata_hash = frame_metadata_hash_extension::CheckMetadataHash::new(false); let tx_ext: TxExtension = ( + authorize_call, check_non_zero_sender, check_spec_version, check_tx_version, @@ -1076,6 +1080,7 @@ mod tests { function, tx_ext, ( + (), (), spec_version, transaction_version, diff --git a/substrate/bin/node/cli/tests/submit_transaction.rs b/substrate/bin/node/cli/tests/submit_transaction.rs index 3672432ae342..3d486030fea8 100644 --- a/substrate/bin/node/cli/tests/submit_transaction.rs +++ b/substrate/bin/node/cli/tests/submit_transaction.rs @@ -132,7 +132,7 @@ fn should_submit_signed_twice_from_the_same_account() { let s = state.read(); fn nonce(tx: UncheckedExtrinsic) -> frame_system::CheckNonce { let extra = tx.0.preamble.to_signed().unwrap().2; - extra.5 + extra.6 } let nonce1 = nonce(UncheckedExtrinsic::decode(&mut &*s.transactions[0]).unwrap()); let nonce2 = nonce(UncheckedExtrinsic::decode(&mut &*s.transactions[1]).unwrap()); @@ -181,7 +181,7 @@ fn should_submit_signed_twice_from_all_accounts() { let s = state.read(); fn nonce(tx: UncheckedExtrinsic) -> frame_system::CheckNonce { let extra = tx.0.preamble.to_signed().unwrap().2; - extra.5 + extra.6 } let nonce1 = nonce(UncheckedExtrinsic::decode(&mut &*s.transactions[0]).unwrap()); let nonce2 = nonce(UncheckedExtrinsic::decode(&mut &*s.transactions[1]).unwrap()); diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index faffcd23fbcf..7a5ee90018b6 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1519,6 +1519,7 @@ where .saturating_sub(1); let era = Era::mortal(period, current_block); let tx_ext: TxExtension = ( + frame_system::AuthorizeCall::::new(), frame_system::CheckNonZeroSender::::new(), frame_system::CheckSpecVersion::::new(), frame_system::CheckTxVersion::::new(), @@ -1570,6 +1571,28 @@ where type RuntimeCall = RuntimeCall; } +impl frame_system::offchain::CreateAuthorizedTransaction for Runtime +where + RuntimeCall: From, +{ + fn create_extension() -> Self::Extension { + ( + frame_system::AuthorizeCall::::new(), + frame_system::CheckNonZeroSender::::new(), + frame_system::CheckSpecVersion::::new(), + frame_system::CheckTxVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckEra::::from(Era::Immortal), + frame_system::CheckNonce::::from(0), + frame_system::CheckWeight::::new(), + pallet_skip_feeless_payment::SkipCheckIfFeeless::from( + pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::::from(0, None), + ), + frame_metadata_hash_extension::CheckMetadataHash::new(false), + ) + } +} + impl pallet_im_online::Config for Runtime { type AuthorityId = ImOnlineId; type RuntimeEvent = RuntimeEvent; @@ -2661,6 +2684,7 @@ pub type BlockId = generic::BlockId; /// /// [`sign`]: <../../testing/src/keyring.rs.html> pub type TxExtension = ( + frame_system::AuthorizeCall, frame_system::CheckNonZeroSender, frame_system::CheckSpecVersion, frame_system::CheckTxVersion, @@ -2684,6 +2708,7 @@ impl EthExtra for EthExtraImpl { fn get_eth_extension(nonce: u32, tip: Balance) -> Self::Extension { ( + frame_system::AuthorizeCall::::new(), frame_system::CheckNonZeroSender::::new(), frame_system::CheckSpecVersion::::new(), frame_system::CheckTxVersion::::new(), diff --git a/substrate/bin/node/testing/src/keyring.rs b/substrate/bin/node/testing/src/keyring.rs index e5b0299f01a8..92d9c541ccf5 100644 --- a/substrate/bin/node/testing/src/keyring.rs +++ b/substrate/bin/node/testing/src/keyring.rs @@ -75,6 +75,7 @@ pub fn session_keys_from_seed(seed: &str) -> SessionKeys { /// Returns transaction extra. pub fn tx_ext(nonce: Nonce, extra_fee: Balance) -> TxExtension { ( + frame_system::AuthorizeCall::new(), frame_system::CheckNonZeroSender::new(), frame_system::CheckSpecVersion::new(), frame_system::CheckTxVersion::new(), diff --git a/substrate/frame/benchmarking/src/utils.rs b/substrate/frame/benchmarking/src/utils.rs index 3a10e43d83b8..590ed40de3af 100644 --- a/substrate/frame/benchmarking/src/utils.rs +++ b/substrate/frame/benchmarking/src/utils.rs @@ -23,7 +23,9 @@ use scale_info::TypeInfo; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; use sp_io::hashing::blake2_256; -use sp_runtime::{traits::TrailingZeroInput, DispatchError}; +use sp_runtime::{ + traits::TrailingZeroInput, transaction_validity::TransactionValidityError, DispatchError, +}; use sp_storage::TrackedStorageKey; /// An alphabet of possible parameters to use for benchmarking. @@ -195,6 +197,12 @@ impl From for BenchmarkError { } } +impl From for BenchmarkError { + fn from(e: TransactionValidityError) -> Self { + Self::Stop(e.into()) + } +} + /// Configuration used to setup and run runtime benchmarks. #[derive(Encode, Decode, Default, Clone, PartialEq, Debug, TypeInfo)] pub struct BenchmarkConfig { diff --git a/substrate/frame/examples/kitchensink/src/benchmarking.rs b/substrate/frame/examples/kitchensink/src/benchmarking.rs index 5f1d378e06fe..57594b5aa64b 100644 --- a/substrate/frame/examples/kitchensink/src/benchmarking.rs +++ b/substrate/frame/examples/kitchensink/src/benchmarking.rs @@ -25,6 +25,7 @@ use super::*; use crate::Pallet as Kitchensink; use frame_benchmarking::v2::*; +use frame_support::pallet_prelude::TransactionSource; use frame_system::RawOrigin; // To actually run this benchmark on pallet-example-kitchensink, we need to put this pallet into the @@ -54,11 +55,47 @@ mod benchmarks { assert_eq!(Foo::::get(), Some(value)) } + // This will measure the execution time of `set_foo_using_authorize`. + #[benchmark] + fn set_foo_using_authorize() { + // This is the benchmark setup phase. + + // `set_foo_using_authorize` is only authorized when value is 42 so we will use it. + let value = 42u32; + // We dispatch with authorized origin, it is the origin resulting from authorization. + let origin = RawOrigin::Authorized; + + #[extrinsic_call] + _(origin, value); // The execution phase is just running `set_foo_using_authorize` extrinsic call + + // This is the optional benchmark verification phase, asserting certain states. + assert_eq!(Foo::::get(), Some(42)) + } + + // This will measure the weight for the closure in `[pallet::authorize(...)]`. + #[benchmark] + fn authorize_set_foo_using_authorize() { + // This is the benchmark setup phase. + + let call = Call::::set_foo_using_authorize { new_foo: 42 }; + let source = TransactionSource::External; + + // We use a block with specific code to benchmark the closure. + #[block] + { + use frame_support::traits::Authorize; + call.authorize(source) + .expect("Call give some authorization") + .expect("Authorization is successful"); + } + } + // This line generates test cases for benchmarking, and could be run by: // `cargo test -p pallet-example-kitchensink --all-features`, you will see one line per case: - // `test benchmarking::bench_sort_vector ... ok` - // `test benchmarking::bench_accumulate_dummy ... ok` - // `test benchmarking::bench_set_dummy_benchmark ... ok` in the result. + // `test benchmarking::bench_set_foo_benchmark ... ok` + // `test benchmarking::bench_set_foo_using_authorize_benchmark ... ok` in the result. + // `test benchmarking::bench_authorize_set_foo_using_authorize_benchmark ... ok` in the + // result. // // The line generates three steps per benchmark, with repeat=1 and the three steps are // [low, mid, high] of the range. diff --git a/substrate/frame/examples/kitchensink/src/lib.rs b/substrate/frame/examples/kitchensink/src/lib.rs index 442318565426..90cc04acc716 100644 --- a/substrate/frame/examples/kitchensink/src/lib.rs +++ b/substrate/frame/examples/kitchensink/src/lib.rs @@ -220,6 +220,32 @@ pub mod pallet { Ok(()) } + + /// A call that is specially authorized. + /// Authorized call can be dispatched by anybody without requiring any signature or fee. + #[pallet::call_index(1)] + #[pallet::authorize(| + _source: TransactionSource, + new_foo: &u32, + | -> TransactionValidityWithRefund { + if *new_foo == 42 { + let refund = Weight::zero(); + let validity = ValidTransaction::default(); + Ok((validity, refund)) + } else { + Err(InvalidTransaction::Call.into()) + } + })] + #[pallet::weight(T::WeightInfo::set_foo_using_authorize())] + #[pallet::weight_of_authorize(T::WeightInfo::authorize_set_foo_using_authorize())] + pub fn set_foo_using_authorize(origin: OriginFor, new_foo: u32) -> DispatchResult { + // We only dispatch if it comes from the authorized origin. Meaning that the closure + // passed in `pallet::authorize` has successfully authorized the call. + ensure_authorized(origin)?; + Foo::::set(Some(new_foo)); + + Ok(()) + } } /// The event type. This exactly like a normal Rust enum. @@ -300,20 +326,6 @@ pub mod pallet { Staking, } - /// Allows the pallet to validate some unsigned transaction. See - /// [`sp_runtime::traits::ValidateUnsigned`] for more info. - #[pallet::validate_unsigned] - impl ValidateUnsigned for Pallet { - type Call = Call; - fn validate_unsigned(_: TransactionSource, _: &Self::Call) -> TransactionValidity { - unimplemented!() - } - - fn pre_dispatch(_: &Self::Call) -> Result<(), TransactionValidityError> { - unimplemented!() - } - } - /// Allows the pallet to provide some inherent. See [`frame_support::inherent::ProvideInherent`] /// for more info. #[pallet::inherent] diff --git a/substrate/frame/examples/kitchensink/src/weights.rs b/substrate/frame/examples/kitchensink/src/weights.rs index 43f48332ae94..f52a10e828f3 100644 --- a/substrate/frame/examples/kitchensink/src/weights.rs +++ b/substrate/frame/examples/kitchensink/src/weights.rs @@ -51,6 +51,8 @@ use core::marker::PhantomData; /// Weight functions needed for pallet_template. pub trait WeightInfo { fn set_foo_benchmark() -> Weight; + fn set_foo_using_authorize() -> Weight; + fn authorize_set_foo_using_authorize() -> Weight; } /// Weight functions for `pallet_example_kitchensink`. @@ -67,6 +69,12 @@ impl WeightInfo for SubstrateWeight { .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } + fn set_foo_using_authorize() -> Weight { + Weight::zero() + } + fn authorize_set_foo_using_authorize() -> Weight { + Weight::zero() + } } impl WeightInfo for () { @@ -81,4 +89,10 @@ impl WeightInfo for () { .saturating_add(Weight::from_parts(0, 0)) .saturating_add(RocksDbWeight::get().writes(1)) } + fn set_foo_using_authorize() -> Weight { + Weight::zero() + } + fn authorize_set_foo_using_authorize() -> Weight { + Weight::zero() + } } diff --git a/substrate/frame/executive/src/tests.rs b/substrate/frame/executive/src/tests.rs index 3841b010325b..aaafade65826 100644 --- a/substrate/frame/executive/src/tests.rs +++ b/substrate/frame/executive/src/tests.rs @@ -448,6 +448,7 @@ parameter_types! { } type TxExtension = ( + frame_system::AuthorizeCall, frame_system::CheckEra, frame_system::CheckNonce, frame_system::CheckWeight, @@ -556,6 +557,7 @@ impl MultiStepMigrator for MockedModeGetter { fn tx_ext(nonce: u64, fee: Balance) -> TxExtension { ( + frame_system::AuthorizeCall::::new(), frame_system::CheckEra::from(Era::Immortal), frame_system::CheckNonce::from(nonce), frame_system::CheckWeight::new(), diff --git a/substrate/frame/metadata-hash-extension/src/tests.rs b/substrate/frame/metadata-hash-extension/src/tests.rs index 11a3345ee15c..257d2d321008 100644 --- a/substrate/frame/metadata-hash-extension/src/tests.rs +++ b/substrate/frame/metadata-hash-extension/src/tests.rs @@ -134,6 +134,7 @@ mod docs { /// The `TransactionExtension` to the basic transaction logic. pub type TxExtension = ( + frame_system::AuthorizeCall, frame_system::CheckNonZeroSender, frame_system::CheckSpecVersion, frame_system::CheckTxVersion, diff --git a/substrate/frame/src/lib.rs b/substrate/frame/src/lib.rs index 03d815e349df..0f8140584cc1 100644 --- a/substrate/frame/src/lib.rs +++ b/substrate/frame/src/lib.rs @@ -477,6 +477,7 @@ pub mod runtime { /// /// crucially, this does NOT contain any tx-payment extension. pub type SystemTransactionExtensionsOf = ( + frame_system::AuthorizeCall, frame_system::CheckNonZeroSender, frame_system::CheckSpecVersion, frame_system::CheckTxVersion, diff --git a/substrate/frame/support/procedural/Cargo.toml b/substrate/frame/support/procedural/Cargo.toml index 51790062b2c2..0a372a53fa38 100644 --- a/substrate/frame/support/procedural/Cargo.toml +++ b/substrate/frame/support/procedural/Cargo.toml @@ -49,11 +49,13 @@ frame-system = { workspace = true } frame-support = { workspace = true } pretty_assertions = { workspace = true } static_assertions = { workspace = true } +frame-benchmarking = { workspace = true } [features] default = ["std"] std = [ "codec/std", + "frame-benchmarking/std", "frame-support/std", "frame-system/std", "scale-info/std", diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs index f055e8ce28e9..f8a774e38554 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs @@ -29,6 +29,7 @@ pub fn expand_outer_dispatch( ) -> TokenStream { let mut variant_defs = TokenStream::new(); let mut variant_patterns = Vec::new(); + let mut variant_usages = Vec::new(); let mut query_call_part_macros = Vec::new(); let mut pallet_names = Vec::new(); let mut pallet_attrs = Vec::new(); @@ -55,6 +56,7 @@ pub fn expand_outer_dispatch( #[codec(index = #index)] #name( #scrate::dispatch::CallableCallFor<#name, #runtime> ), }); + variant_usages.push(quote!( #scrate::dispatch::CallableCallFor<#name, #runtime> )); variant_patterns.push(quote!(RuntimeCall::#name(call))); pallet_names.push(name); pallet_attrs.push(attr); @@ -220,5 +222,37 @@ pub fn expand_outer_dispatch( } } )* + + impl #scrate::traits::Authorize for RuntimeCall { + fn authorize( + &self, + source: #scrate::pallet_prelude::TransactionSource, + ) -> ::core::option::Option< + ::core::result::Result< + ( + #scrate::pallet_prelude::ValidTransaction, + #scrate::pallet_prelude::Weight, + ), + #scrate::pallet_prelude::TransactionValidityError + > + > { + match self { + #( + #pallet_attrs + #variant_patterns => #scrate::traits::Authorize::authorize(call, source), + )* + } + } + + fn weight_of_authorize(&self) -> #scrate::pallet_prelude::Weight { + match self { + #( + #pallet_attrs + #variant_patterns => + #scrate::traits::Authorize::weight_of_authorize(call), + )* + } + } + } } } diff --git a/substrate/frame/support/procedural/src/lib.rs b/substrate/frame/support/procedural/src/lib.rs index c2f546d92048..ee2648c43c9b 100644 --- a/substrate/frame/support/procedural/src/lib.rs +++ b/substrate/frame/support/procedural/src/lib.rs @@ -1338,3 +1338,154 @@ pub fn dynamic_aggregated_params_internal(attrs: TokenStream, input: TokenStream .unwrap_or_else(|r| r.into_compile_error()) .into() } + +/// Allows to authorize some general transactions with specific call. +/// +/// This attribute allows to specify a special validation logic for a specific call. +/// A general transaction with this specific call can then be validated by the given function, +/// and if valid then dispatched with the origin `frame_system::Origin::Authorized`. +/// +/// To ensure the origin of the call is the authorization process, the call must check the origin +/// with `frame_system::ensure_authorized` function. +/// +/// To enable the authorization process on the extrinsic, the runtime must use +/// `frame_system::AuthorizeCall` transaction extension in the transaction extension pipeline. +/// +/// To enable the creation of authorized call from offchain worker. The runtime should implement +/// `frame_system::CreateAuthorizedTransaction`. This trait allows to specify which transaction +/// extension to use when creating a transaction for an authorized call. +/// +/// # Usage in the pallet +/// +/// ## Example/Overview: +/// +/// ``` +/// # #[allow(unused)] +/// #[frame_support::pallet] +/// pub mod pallet { +/// use frame_support::pallet_prelude::*; +/// use frame_system::pallet_prelude::*; +/// +/// #[pallet::pallet] +/// pub struct Pallet(_); +/// +/// #[pallet::config] +/// pub trait Config: frame_system::Config {} +/// +/// #[pallet::call] +/// impl Pallet { +/// #[pallet::weight(Weight::zero())] +/// #[pallet::authorize(|_source, foo| if *foo == 42 { +/// let refund = Weight::zero(); +/// let validity = ValidTransaction::default(); +/// Ok((validity, refund)) +/// } else { +/// Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) +/// })] +/// #[pallet::weight_of_authorize(Weight::zero())] +/// #[pallet::call_index(0)] +/// pub fn some_call(origin: OriginFor, arg: u32) -> DispatchResult { +/// ensure_authorized(origin)?; +/// +/// Ok(()) +/// } +/// +/// #[pallet::weight(Weight::zero())] +/// // We can also give the callback as a function +/// #[pallet::authorize(Self::authorize_some_other_call)] +/// #[pallet::weight_of_authorize(Weight::zero())] +/// #[pallet::call_index(1)] +/// pub fn some_other_call(origin: OriginFor, arg: u32) -> DispatchResult { +/// ensure_authorized(origin)?; +/// +/// Ok(()) +/// } +/// } +/// +/// impl Pallet { +/// fn authorize_some_other_call( +/// source: TransactionSource, +/// foo: &u32 +/// ) -> TransactionValidityWithRefund { +/// if *foo == 42 { +/// let refund = Weight::zero(); +/// let validity = ValidTransaction::default(); +/// Ok((validity, refund)) +/// } else { +/// Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) +/// } +/// } +/// } +/// +/// #[frame_benchmarking::v2::benchmarks] +/// mod benchmarks { +/// use super::*; +/// use frame_benchmarking::v2::BenchmarkError; +/// +/// #[benchmark] +/// fn authorize_some_call() -> Result<(), BenchmarkError> { +/// let call = Call::::some_call { arg: 42 }; +/// +/// #[block] +/// { +/// use frame_support::pallet_prelude::Authorize; +/// call.authorize(TransactionSource::External) +/// .ok_or("Call must give some authorization")??; +/// } +/// +/// Ok(()) +/// } +/// } +/// } +/// ``` +/// +/// ## Specification: +/// +/// Authorize process comes with 2 attributes macro on top of the authorized call: +/// +/// * `#[pallet::authorize($authorized_function)]` - defines the function that authorizes the call. +/// First argument is the transaction source `TransactionSource` then followed by the same as call +/// arguments but by reference `&`. Return type is `TransactionValidityWithRefund`. +/// * `#[pallet::weight_of_authorize($weight)]` - defines the value of the weight of the authorize +/// function. This attribute is similar to `#[pallet::weight]`: +/// * it can be ignore in `dev_mode` +/// * it can be automatically infered from weight info. For the call `foo` the function +/// `authorize_foo` in the weight info will be used. +/// * it can be a fixed value like `Weight::from_all(0)` (not recommended in production). +/// +/// The weight must be small enough so that nodes don't get DDOS by validating transactions. +/// +/// Then in the call it must be ensured that the origin is the authorization process. This can +/// be done using `frame_system::ensure_authorized` function. +/// +/// # The macro expansion +/// +/// From the given "authorize" function and weight, the macro will implement the trait +/// `Authorize` on the call. +/// +/// # How to benchmark +/// +/// The authorize function is used as the implementation of the trait +/// `Authorize` for the call. +/// To benchmark a call variant, use the function +/// `Authorize::authorize` on a call value. +/// See the example in the first section. +#[proc_macro_attribute] +pub fn authorize(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + +/// Allows to define the weight of the authorize function. +/// +/// See [`authorize`](macro@authorize) for more information on how authorization works. +/// +/// Defines the value of the weight of the authorize function. This attribute is similar to +/// `#[pallet::weight]`: +/// * it can be ignore in `dev_mode` +/// * it can be automatically infered from weight info. For the call `foo` the function +/// `authorize_foo` in the weight info will be used. +/// * it can be a fixed value like `Weight::from_all(0)` (not recommended in production). +#[proc_macro_attribute] +pub fn weight_of_authorize(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} diff --git a/substrate/frame/support/procedural/src/pallet/expand/call.rs b/substrate/frame/support/procedural/src/pallet/expand/call.rs index 87fb4b8967e6..13eff2a5f458 100644 --- a/substrate/frame/support/procedural/src/pallet/expand/call.rs +++ b/substrate/frame/support/procedural/src/pallet/expand/call.rs @@ -18,7 +18,10 @@ use crate::{ pallet::{ expand::warnings::{weight_constant_warning, weight_witness_warning}, - parse::{call::CallWeightDef, helper::CallReturnType}, + parse::{ + call::{CallVariantDef, CallWeightDef}, + helper::CallReturnType, + }, Def, }, COUNTER, @@ -28,6 +31,33 @@ use proc_macro_warning::Warning; use quote::{quote, ToTokens}; use syn::spanned::Spanned; +/// Expand the weight to final token stream and accumulate warnings. +fn expand_weight( + prefix: &str, + frame_support: &syn::Path, + dev_mode: bool, + weight_warnings: &mut Vec, + method: &CallVariantDef, + weight: &CallWeightDef, +) -> TokenStream2 { + match weight { + CallWeightDef::DevModeDefault => quote::quote!( + #frame_support::pallet_prelude::Weight::zero() + ), + CallWeightDef::Immediate(e) => { + weight_constant_warning(e, dev_mode, weight_warnings); + weight_witness_warning(method, dev_mode, weight_warnings); + + e.into_token_stream() + }, + CallWeightDef::Inherited(t) => { + // Expand `<::WeightInfo>::$prefix$call_name()`. + let n = &syn::Ident::new(&format!("{}{}", prefix, method.name), method.name.span()); + quote!({ < #t > :: #n () }) + }, + } +} + /// /// * Generate enum call and implement various trait on it. /// * Implement Callable and call_function on `Pallet` @@ -86,29 +116,15 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { let mut fn_weight = Vec::::new(); let mut weight_warnings = Vec::new(); for method in &methods { - match &method.weight { - CallWeightDef::DevModeDefault => fn_weight.push(syn::parse_quote!(0)), - CallWeightDef::Immediate(e) => { - weight_constant_warning(e, def.dev_mode, &mut weight_warnings); - weight_witness_warning(method, def.dev_mode, &mut weight_warnings); - - fn_weight.push(e.into_token_stream()); - }, - CallWeightDef::Inherited => { - let pallet_weight = def - .call - .as_ref() - .expect("we have methods; we have calls; qed") - .inherited_call_weight - .as_ref() - .expect("the parser prevents this"); - - // Expand `<::WeightInfo>::call_name()`. - let t = &pallet_weight.typename; - let n = &method.name; - fn_weight.push(quote!({ < #t > :: #n () })); - }, - } + let w = expand_weight( + "", + frame_support, + def.dev_mode, + &mut weight_warnings, + method, + &method.weight, + ); + fn_weight.push(w); } debug_assert_eq!(fn_weight.len(), methods.len()); @@ -272,6 +288,67 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { Err(e) => return e.into_compile_error(), }; + let authorize_fn = methods.iter().zip(args_name.iter()).zip(args_type.iter()).map( + |((method, arg_name), arg_type)| { + if let Some(authorize_def) = &method.authorize { + let authorize_fn = &authorize_def.expr; + let attr_fn_getter = syn::Ident::new( + &format!("__macro_inner_authorize_call_for_{}", method.name), + authorize_fn.span(), + ); + let source = syn::Ident::new("source", span); + + let typed_authorize_fn = quote::quote_spanned!(authorize_fn.span() => { + // Closure don't have a writable type. So we fix the authorize token stream to + // be any implementation of a specific function. + // This allows to have good type inference on the closure. + // + // Then we wrap this into an implementation for `Pallet` in order to get access + // to `Self` as `Pallet` instead of `Call`. + impl<#type_impl_gen> Pallet<#type_use_gen> #where_clause { + #[doc(hidden)] + fn #attr_fn_getter() -> impl Fn( + #frame_support::pallet_prelude::TransactionSource, + #( &#arg_type ),* + ) -> #frame_support::pallet_prelude::TransactionValidityWithRefund { + #authorize_fn + } + } + + Pallet::<#type_use_gen>::#attr_fn_getter() + }); + + // `source` is from outside this block, so we can't use the authorize_fn span. + quote::quote!( + let authorize_fn = #typed_authorize_fn; + let res = authorize_fn(#source, #( #arg_name, )*); + + Some(res) + ) + } else { + quote::quote!(None) + } + }, + ); + + let mut authorize_fn_weight = Vec::::new(); + for method in &methods { + let w = match &method.authorize { + Some(authorize_def) => expand_weight( + "authorize_", + frame_support, + def.dev_mode, + &mut weight_warnings, + method, + &authorize_def.weight, + ), + // No authorize logic, weight is negligible + None => quote::quote!(#frame_support::pallet_prelude::Weight::zero()), + }; + authorize_fn_weight.push(w); + } + assert_eq!(authorize_fn_weight.len(), methods.len()); + quote::quote_spanned!(span => #[doc(hidden)] mod warnings { @@ -451,6 +528,7 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { #frame_support::__private::sp_tracing::trace_span!(stringify!(#fn_name)) ); #maybe_allow_attrs + #[allow(deprecated)] <#pallet_ident<#type_use_gen>>::#fn_name(origin, #( #args_name, )* ) .map(Into::into).map_err(Into::into) }, @@ -480,5 +558,43 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { } } } + + impl<#type_impl_gen> #frame_support::traits::Authorize for #call_ident<#type_use_gen> + #where_clause + { + fn authorize(&self, source: #frame_support::pallet_prelude::TransactionSource) -> ::core::option::Option<::core::result::Result< + ( + #frame_support::pallet_prelude::ValidTransaction, + #frame_support::pallet_prelude::Weight, + ), + #frame_support::pallet_prelude::TransactionValidityError + >> + { + match *self { + #( + #cfg_attrs + Self::#fn_name { #( #args_name_pattern_ref, )* } => { + #authorize_fn + }, + )* + Self::__Ignore(_, _) => { + let _ = source; + unreachable!("__Ignore cannot be used") + }, + } + } + + fn weight_of_authorize(&self) -> #frame_support::pallet_prelude::Weight { + match *self { + #( + #cfg_attrs + Self::#fn_name { #( #args_name_pattern_ref, )* } => { + #authorize_fn_weight + }, + )* + Self::__Ignore(_, _) => unreachable!("__Ignore cannot be used"), + } + } + } ) } diff --git a/substrate/frame/support/procedural/src/pallet/expand/mod.rs b/substrate/frame/support/procedural/src/pallet/expand/mod.rs index 3f9b50f79c0c..f59ce1f2dc59 100644 --- a/substrate/frame/support/procedural/src/pallet/expand/mod.rs +++ b/substrate/frame/support/procedural/src/pallet/expand/mod.rs @@ -72,7 +72,7 @@ pub fn expand(mut def: Def) -> proc_macro2::TokenStream { let genesis_build = genesis_build::expand_genesis_build(&mut def); let genesis_config = genesis_config::expand_genesis_config(&mut def); let type_values = type_value::expand_type_values(&mut def); - let origins = origin::expand_origins(&mut def); + let origin = origin::expand_origin(&mut def); let validate_unsigned = validate_unsigned::expand_validate_unsigned(&mut def); let tt_default_parts = tt_default_parts::expand_tt_default_parts(&mut def); let doc_only = doc_only::expand_doc_only(&mut def); @@ -114,7 +114,7 @@ storage item. Otherwise, all storage items are listed among [*Type Definitions*] #genesis_build #genesis_config #type_values - #origins + #origin #validate_unsigned #tt_default_parts #doc_only diff --git a/substrate/frame/support/procedural/src/pallet/expand/origin.rs b/substrate/frame/support/procedural/src/pallet/expand/origin.rs index 55865b42491a..b7a560bc5b64 100644 --- a/substrate/frame/support/procedural/src/pallet/expand/origin.rs +++ b/substrate/frame/support/procedural/src/pallet/expand/origin.rs @@ -20,7 +20,8 @@ use proc_macro2::TokenStream; use quote::quote; use syn::{spanned::Spanned, Ident}; -pub fn expand_origins(def: &mut Def) -> TokenStream { +/// expand the `is_origin_part_defined` macro. +pub fn expand_origin(def: &mut Def) -> TokenStream { let count = COUNTER.with(|counter| counter.borrow_mut().inc()); let macro_ident = Ident::new(&format!("__is_origin_part_defined_{}", count), def.item.span()); diff --git a/substrate/frame/support/procedural/src/pallet/parse/call.rs b/substrate/frame/support/procedural/src/pallet/parse/call.rs index 68ced1bc0ed3..da340caa85e2 100644 --- a/substrate/frame/support/procedural/src/pallet/parse/call.rs +++ b/substrate/frame/support/procedural/src/pallet/parse/call.rs @@ -20,7 +20,7 @@ use frame_support_procedural_tools::get_doc_literals; use proc_macro2::Span; use quote::ToTokens; use std::collections::HashMap; -use syn::{spanned::Spanned, ExprClosure}; +use syn::spanned::Spanned; /// List of additional token to be used for parsing. mod keyword { @@ -33,6 +33,8 @@ mod keyword { syn::custom_keyword!(T); syn::custom_keyword!(pallet); syn::custom_keyword!(feeless_if); + syn::custom_keyword!(authorize); + syn::custom_keyword!(weight_of_authorize); } /// Definition of dispatchables typically `impl Pallet { ... }` @@ -49,16 +51,15 @@ pub struct CallDef { pub attr_span: proc_macro2::Span, /// Docs, specified on the impl Block. pub docs: Vec, - /// The optional `weight` attribute on the `pallet::call`. - pub inherited_call_weight: Option, /// attributes pub attrs: Vec, } -/// The weight of a call. +/// The weight of a call or the weight of authorize. #[derive(Clone)] pub enum CallWeightDef { - /// Explicitly set on the call itself with `#[pallet::weight(…)]`. This value is used. + /// Explicitly set on the call itself with `#[pallet::weight(…)]` or + /// `#[pallet::weight_of_authorize(…)]`. This value is used. Immediate(syn::Expr), /// The default value that should be set for dev-mode pallets. Usually zero. @@ -67,7 +68,22 @@ pub enum CallWeightDef { /// Inherits whatever value is configured on the pallet level. /// /// The concrete value is not known at this point. - Inherited, + Inherited(syn::Type), +} + +impl CallWeightDef { + fn try_from( + weight: Option, + inherited_call_weight: &Option, + dev_mode: bool, + ) -> Option { + match (weight, inherited_call_weight) { + (Some(weight), _) => Some(CallWeightDef::Immediate(weight)), + (None, Some(inherited)) => Some(CallWeightDef::Inherited(inherited.typename.clone())), + (None, _) if dev_mode => Some(CallWeightDef::DevModeDefault), + (None, _) => None, + } + } } /// Definition of dispatchable typically: `#[weight...] fn foo(origin .., param1: ...) -> ..` @@ -93,6 +109,19 @@ pub struct CallVariantDef { pub feeless_check: Option, /// The return type of the call: `DispatchInfo` or `DispatchResultWithPostInfo`. pub return_type: helper::CallReturnType, + /// The information related to `authorize` attribute. + /// `(authorize expression, weight of authorize)` + pub authorize: Option, +} + +/// Definition related to the `authorize` attribute and other related attributes. +#[derive(Clone)] +pub struct AuthorizeDef { + /// The expression of the authorize attribute. + pub expr: syn::Expr, + /// The weight of the authorize attribute as define by the attribute + /// `[pallet::weight_of_authorize]`. + pub weight: CallWeightDef, } /// Attributes for functions in call impl block. @@ -103,6 +132,10 @@ pub enum FunctionAttr { Weight(syn::Expr), /// Parse for `#[pallet::feeless_if(expr)]` FeelessIf(Span, syn::ExprClosure), + /// Parse for `#[pallet::authorize(expr)]` + Authorize(syn::Expr), + /// Parse for `#[pallet::weight_of_authorize(expr)]` + WeightOfAuthorize(syn::Expr), } impl syn::parse::Parse for FunctionAttr { @@ -142,6 +175,16 @@ impl syn::parse::Parse for FunctionAttr { err })?, )) + } else if lookahead.peek(keyword::authorize) { + content.parse::()?; + let closure_content; + syn::parenthesized!(closure_content in content); + Ok(FunctionAttr::Authorize(closure_content.parse::()?)) + } else if lookahead.peek(keyword::weight_of_authorize) { + content.parse::()?; + let closure_content; + syn::parenthesized!(closure_content in content); + Ok(FunctionAttr::WeightOfAuthorize(closure_content.parse::()?)) } else { Err(lookahead.error()) } @@ -266,57 +309,94 @@ impl CallDef { let return_type = helper::check_pallet_call_return_type(&method.sig)?; let cfg_attrs: Vec = helper::get_item_cfg_attrs(&method.attrs); - let mut call_idx_attrs = vec![]; - let mut weight_attrs = vec![]; - let mut feeless_attrs = vec![]; + let mut call_index = None; + let mut weight = None; + let mut feeless_check = None; + let mut authorize = None; + let mut weight_of_authorize = None; + for attr in helper::take_item_pallet_attrs(&mut method.attrs)?.into_iter() { match attr { - FunctionAttr::CallIndex(_) => { - call_idx_attrs.push(attr); + FunctionAttr::CallIndex(idx) => { + if call_index.is_some() { + let msg = + "Invalid pallet::call, too many call_index attributes given"; + return Err(syn::Error::new(method.sig.span(), msg)) + } + + call_index = Some(idx); }, - FunctionAttr::Weight(_) => { - weight_attrs.push(attr); + FunctionAttr::Weight(w) => { + if weight.is_some() { + let msg = "Invalid pallet::call, too many weight attributes given"; + return Err(syn::Error::new(method.sig.span(), msg)) + } + weight = Some(w); }, - FunctionAttr::FeelessIf(span, _) => { - feeless_attrs.push((span, attr)); + FunctionAttr::FeelessIf(span, closure) => { + if feeless_check.is_some() { + let msg = + "Invalid pallet::call, there can only be one feeless_if attribute"; + return Err(syn::Error::new(span, msg)) + } + + feeless_check = Some(closure); + }, + FunctionAttr::Authorize(expr) => { + if authorize.is_some() { + let msg = + "Invalid pallet::call, there can only be one authorize attribute"; + return Err(syn::Error::new(method.sig.span(), msg)) + } + + authorize = Some(expr); + }, + FunctionAttr::WeightOfAuthorize(expr) => { + if weight_of_authorize.is_some() { + let msg = "Invalid pallet::call, there can only be one weight_of_authorize attribute"; + return Err(syn::Error::new(method.sig.span(), msg)) + } + + weight_of_authorize = Some(expr); }, } } - if weight_attrs.is_empty() && dev_mode { - // inject a default O(1) weight when dev mode is enabled and no weight has - // been specified on the call - let empty_weight: syn::Expr = syn::parse_quote!(0); - weight_attrs.push(FunctionAttr::Weight(empty_weight)); + if weight_of_authorize.is_some() && authorize.is_none() { + let msg = "Invalid pallet::call, weight_of_authorize attribute must be used with authorize attribute"; + return Err(syn::Error::new(weight_of_authorize.unwrap().span(), msg)) } - let weight = match weight_attrs.len() { - 0 if inherited_call_weight.is_some() => CallWeightDef::Inherited, - 0 if dev_mode => CallWeightDef::DevModeDefault, - 0 => return Err(syn::Error::new( - method.sig.span(), - "A pallet::call requires either a concrete `#[pallet::weight($expr)]` or an - inherited weight from the `#[pallet:call(weight($type))]` attribute, but - none were given.", - )), - 1 => match weight_attrs.pop().unwrap() { - FunctionAttr::Weight(w) => CallWeightDef::Immediate(w), - _ => unreachable!("checked during creation of the let binding"), - }, - _ => { - let msg = "Invalid pallet::call, too many weight attributes given"; - return Err(syn::Error::new(method.sig.span(), msg)); - }, + let authorize = if let Some(expr) = authorize { + let weight_of_authorize = CallWeightDef::try_from( + weight_of_authorize, + &inherited_call_weight, + dev_mode, + ) + .ok_or_else(|| { + syn::Error::new( + method.sig.span(), + "A pallet::call using authorize requires either a concrete \ + `#[pallet::weight_of_authorize($expr)]` or an inherited weight from \ + the `#[pallet:call(weight($type))]` attribute, but \ + none were given.", + ) + })?; + Some(AuthorizeDef { expr, weight: weight_of_authorize }) + } else { + None }; - if call_idx_attrs.len() > 1 { - let msg = "Invalid pallet::call, too many call_index attributes given"; - return Err(syn::Error::new(method.sig.span(), msg)); - } - let call_index = call_idx_attrs.pop().map(|attr| match attr { - FunctionAttr::CallIndex(idx) => idx, - _ => unreachable!("checked during creation of the let binding"), - }); + let weight = CallWeightDef::try_from(weight, &inherited_call_weight, dev_mode) + .ok_or_else(|| { + syn::Error::new( + method.sig.span(), + "A pallet::call requires either a concrete `#[pallet::weight($expr)]` \ + or an inherited weight from the `#[pallet:call(weight($type))]` \ + attribute, but none were given.", + ) + })?; + let explicit_call_index = call_index.is_some(); let final_index = match call_index { @@ -367,16 +447,6 @@ impl CallDef { let docs = get_doc_literals(&method.attrs); - if feeless_attrs.len() > 1 { - let msg = "Invalid pallet::call, there can only be one feeless_if attribute"; - return Err(syn::Error::new(feeless_attrs[1].0, msg)); - } - let feeless_check: Option = - feeless_attrs.pop().map(|(_, attr)| match attr { - FunctionAttr::FeelessIf(_, closure) => closure, - _ => unreachable!("checked during creation of the let binding"), - }); - if let Some(ref feeless_check) = feeless_check { if feeless_check.inputs.len() != args.len() + 1 { let msg = "Invalid pallet::call, feeless_if closure must have same \ @@ -446,6 +516,7 @@ impl CallDef { cfg_attrs, feeless_check, return_type, + authorize, }); } else { let msg = "Invalid pallet::call, only method accepted"; @@ -460,7 +531,6 @@ impl CallDef { methods, where_clause: item_impl.generics.where_clause.clone(), docs: get_doc_literals(&item_impl.attrs), - inherited_call_weight, attrs: item_impl.attrs.clone(), }) } diff --git a/substrate/frame/support/procedural/src/pallet/parse/origin.rs b/substrate/frame/support/procedural/src/pallet/parse/origin.rs index 11311b3d5033..495dbea11392 100644 --- a/substrate/frame/support/procedural/src/pallet/parse/origin.rs +++ b/substrate/frame/support/procedural/src/pallet/parse/origin.rs @@ -33,7 +33,7 @@ pub struct OriginDef { impl OriginDef { pub fn try_from(item: &mut syn::Item) -> syn::Result { let item_span = item.span(); - let (vis, ident, generics) = match &item { + let (vis, ident, generics) = match item { syn::Item::Enum(item) => (&item.vis, &item.ident, &item.generics), syn::Item::Struct(item) => (&item.vis, &item.ident, &item.generics), syn::Item::Type(item) => (&item.vis, &item.ident, &item.generics), @@ -46,7 +46,7 @@ impl OriginDef { let is_generic = !generics.params.is_empty(); let mut instances = vec![]; - if let Some(u) = helper::check_type_def_optional_gen(generics, item.span())? { + if let Some(u) = helper::check_type_def_optional_gen(generics, item_span)? { instances.push(u); } else { // construct_runtime only allow generic event for instantiable pallet. diff --git a/substrate/frame/support/src/dispatch.rs b/substrate/frame/support/src/dispatch.rs index 483a3dce77f6..38977db95a39 100644 --- a/substrate/frame/support/src/dispatch.rs +++ b/substrate/frame/support/src/dispatch.rs @@ -82,6 +82,8 @@ pub enum RawOrigin { /// * included and agreed upon by the validators anyway, /// * or unsigned transaction validated by a pallet. None, + /// It is signed by nobody, call is authorized by the runtime. + Authorized, } impl From> for RawOrigin { diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs index c64987b17d35..187f6b45a4e9 100644 --- a/substrate/frame/support/src/lib.rs +++ b/substrate/frame/support/src/lib.rs @@ -904,7 +904,7 @@ pub mod pallet_prelude { StorageList, }, traits::{ - BuildGenesisConfig, ConstU32, ConstUint, EnsureOrigin, Get, GetDefault, + Authorize, BuildGenesisConfig, ConstU32, ConstUint, EnsureOrigin, Get, GetDefault, GetStorageVersion, Hooks, IsType, PalletInfoAccess, StorageInfoTrait, StorageVersion, Task, TypedGet, }, @@ -920,12 +920,13 @@ pub mod pallet_prelude { pub use sp_runtime::{ traits::{ CheckedAdd, CheckedConversion, CheckedDiv, CheckedMul, CheckedShl, CheckedShr, - CheckedSub, MaybeSerializeDeserialize, Member, One, ValidateUnsigned, Zero, + CheckedSub, MaybeSerializeDeserialize, Member, One, ValidateResult, ValidateUnsigned, + Zero, }, transaction_validity::{ InvalidTransaction, TransactionLongevity, TransactionPriority, TransactionSource, - TransactionTag, TransactionValidity, TransactionValidityError, UnknownTransaction, - ValidTransaction, + TransactionTag, TransactionValidity, TransactionValidityError, + TransactionValidityWithRefund, UnknownTransaction, ValidTransaction, }, DispatchError, RuntimeDebug, MAX_MODULE_ERROR_ENCODED_SIZE, }; @@ -2517,7 +2518,8 @@ pub mod pallet_macros { pub use frame_support_procedural::storage; pub use frame_support_procedural::{ - task_condition, task_index, task_list, task_weight, tasks_experimental, + authorize, task_condition, task_index, task_list, task_weight, tasks_experimental, + weight_of_authorize, }; /// Allows a pallet to declare a type as an origin. diff --git a/substrate/frame/support/src/traits.rs b/substrate/frame/support/src/traits.rs index 728426cc84c7..592d6b715c3d 100644 --- a/substrate/frame/support/src/traits.rs +++ b/substrate/frame/support/src/traits.rs @@ -104,7 +104,7 @@ mod dispatch; #[allow(deprecated)] pub use dispatch::EnsureOneOf; pub use dispatch::{ - AsEnsureOriginWithArg, CallerTrait, EitherOf, EitherOfDiverse, EnsureOrigin, + AsEnsureOriginWithArg, Authorize, CallerTrait, EitherOf, EitherOfDiverse, EnsureOrigin, EnsureOriginEqualOrHigherPrivilege, EnsureOriginWithArg, MapSuccess, NeverEnsureOrigin, OriginTrait, TryMapSuccess, TryWithMorphedArg, UnfilteredDispatchable, }; diff --git a/substrate/frame/support/src/traits/dispatch.rs b/substrate/frame/support/src/traits/dispatch.rs index dbdf0885dd24..9ac1635668d3 100644 --- a/substrate/frame/support/src/traits/dispatch.rs +++ b/substrate/frame/support/src/traits/dispatch.rs @@ -22,8 +22,10 @@ use codec::MaxEncodedLen; use core::{cmp::Ordering, marker::PhantomData}; use sp_runtime::{ traits::{BadOrigin, Get, Member, Morph, TryMorph}, + transaction_validity::{TransactionSource, TransactionValidityError, ValidTransaction}, Either, }; +use sp_weights::Weight; use super::misc; @@ -565,6 +567,26 @@ pub trait OriginTrait: Sized { } } +/// A trait to allow calls to authorize themselves from origin `None`. +/// +/// It is usually implemented by the [`crate::pallet`] macro and used by the +/// `frame_system::AuthorizeCall` transaction extension. +pub trait Authorize { + /// The authorize function. + /// + /// Returns + /// * `Some(Ok((valid_transaction, unspent weight)))` if the call is successfully authorized, + /// * `Some(Err(error))` if the call authorization is invalid, + /// * `None` if the call doesn't provide any authorization. + fn authorize( + &self, + source: TransactionSource, + ) -> Option>; + + /// The weight of the authorization function. + fn weight_of_authorize(&self) -> Weight; +} + #[cfg(test)] mod tests { use super::*; diff --git a/substrate/frame/support/test/tests/authorize.rs b/substrate/frame/support/test/tests/authorize.rs new file mode 100644 index 000000000000..345b1cc46ad9 --- /dev/null +++ b/substrate/frame/support/test/tests/authorize.rs @@ -0,0 +1,573 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use codec::Encode; +use frame_support::{ + derive_impl, + dispatch::GetDispatchInfo, + pallet_prelude::{TransactionSource, Weight}, +}; +use sp_runtime::{ + testing::UintAuthorityId, + traits::{Applyable, Checkable}, + transaction_validity::{ + InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction, + }, + BuildStorage, DispatchError, +}; + +// test for instance +#[frame_support::pallet] +pub mod pallet1 { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + pub const CALL_1_AUTH_WEIGHT: Weight = Weight::from_all(1); + pub const CALL_1_WEIGHT: Weight = Weight::from_all(2); + pub const CALL_2_AUTH_WEIGHT: Weight = Weight::from_all(3); + pub const CALL_2_WEIGHT: Weight = Weight::from_all(5); + pub const CALL_2_REFUND: Weight = Weight::from_all(4); + pub const CALL_3_AUTH_WEIGHT: Weight = Weight::from_all(6); + pub const CALL_3_WEIGHT: Weight = Weight::from_all(7); + pub const CALL_3_REFUND: Weight = Weight::from_all(6); + pub const CALL_4_AUTH_WEIGHT: Weight = Weight::from_all(10); + pub const CALL_4_WEIGHT: Weight = Weight::from_all(11); + + #[pallet::pallet] + pub struct Pallet(_); + + pub trait WeightInfo { + fn call1() -> Weight; + fn call2() -> Weight; + fn authorize_call2() -> Weight; + fn call3() -> Weight; + fn authorize_call3() -> Weight; + } + + impl WeightInfo for () { + fn call1() -> Weight { + CALL_1_WEIGHT + } + fn call2() -> Weight { + CALL_2_WEIGHT + } + fn authorize_call2() -> Weight { + CALL_2_AUTH_WEIGHT + } + fn call3() -> Weight { + CALL_3_WEIGHT + } + fn authorize_call3() -> Weight { + CALL_3_AUTH_WEIGHT + } + } + + #[pallet::config] + pub trait Config: frame_system::Config { + type SomeGeneric: Parameter; + type WeightInfo: WeightInfo; + } + + #[pallet::call(weight = T::WeightInfo)] + impl, I: 'static> Pallet { + #[pallet::authorize(|_source| Ok((ValidTransaction::default(), Weight::zero())))] + #[pallet::weight_of_authorize(CALL_1_AUTH_WEIGHT)] + #[pallet::call_index(0)] + pub fn call1(origin: OriginFor) -> DispatchResult { + ensure_authorized(origin)?; + + Ok(()) + } + + #[pallet::authorize(|_source, a, b, c, d, e, f, authorize_refund| + if *a { + let valid = ValidTransaction { + priority: *b, + requires: vec![c.encode()], + provides: vec![d.encode()], + longevity: *e, + propagate: *f, + }; + Ok((valid, *authorize_refund)) + } else { + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) + } + )] + #[pallet::call_index(1)] + pub fn call2( + origin: OriginFor, + a: bool, + b: u64, + c: u8, + d: u8, + e: u64, + f: bool, + authorize_refund: Weight, + ) -> DispatchResultWithPostInfo { + ensure_authorized(origin)?; + + let _ = (a, b, c, d, e, f, authorize_refund); + + Ok(Some(CALL_2_REFUND).into()) + } + + #[pallet::authorize(Self::authorize_call3)] + #[pallet::call_index(2)] + pub fn call3( + origin: OriginFor, + valid: bool, + _some_gen: T::SomeGeneric, + ) -> DispatchResultWithPostInfo { + ensure_authorized(origin)?; + + let _ = valid; + + Err(sp_runtime::DispatchErrorWithPostInfo { + post_info: Some(CALL_3_REFUND).into(), + error: DispatchError::Other("Call3 failed"), + }) + } + + #[cfg(feature = "frame-feature-testing")] + #[pallet::call_index(3)] + #[pallet::authorize(|_source| Ok((ValidTransaction::default(), Weight::zero())))] + #[pallet::weight_of_authorize(CALL_4_AUTH_WEIGHT)] + #[pallet::weight(CALL_4_WEIGHT)] + pub fn call4(origin: OriginFor) -> DispatchResult { + ensure_authorized(origin)?; + + Ok(()) + } + } + + impl, I: 'static> Pallet { + fn authorize_call3( + _source: TransactionSource, + valid: &bool, + _some_gen: &T::SomeGeneric, + ) -> TransactionValidityWithRefund { + if *valid { + Ok(Default::default()) + } else { + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) + } + } + } +} + +// test for dev mode. +#[frame_support::pallet(dev_mode)] +pub mod pallet2 { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + pub trait SomeTrait {} + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::call] + impl Pallet { + #[pallet::authorize(|_source| Ok(Default::default()))] + pub fn call1(origin: OriginFor) -> DispatchResult { + ensure_authorized(origin)?; + Ok(()) + } + } +} + +// test for no pallet info. +#[frame_support::pallet] +pub mod pallet3 { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + pub const CALL_1_AUTH_WEIGHT: Weight = Weight::from_all(1); + pub const CALL_1_WEIGHT: Weight = Weight::from_all(1); + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: crate::pallet1::Config + frame_system::Config {} + + #[pallet::call] + impl Pallet { + #[pallet::authorize(|_source| Ok(Default::default()))] + #[pallet::weight_of_authorize(CALL_1_AUTH_WEIGHT)] + #[pallet::weight(CALL_1_WEIGHT)] + #[pallet::call_index(0)] + pub fn call1(origin: OriginFor) -> DispatchResult { + ensure_authorized(origin)?; + Ok(()) + } + } +} + +// test for pallet with no authorized call +#[frame_support::pallet(dev_mode)] +pub mod pallet4 { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + pub fn call1(origin: OriginFor) -> DispatchResult { + ensure_authorized(origin)?; + Ok(()) + } + } +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Runtime { + type Block = Block; +} + +impl pallet2::SomeTrait for RuntimeOrigin {} + +impl pallet1::Config for Runtime { + type SomeGeneric = u32; + type WeightInfo = (); +} + +impl pallet1::Config for Runtime { + type SomeGeneric = u32; + type WeightInfo = (); +} + +#[cfg(feature = "frame-feature-testing")] +impl pallet1::Config for Runtime { + type SomeGeneric = u32; + type WeightInfo = (); +} + +impl pallet2::Config for Runtime {} + +impl pallet3::Config for Runtime {} + +impl pallet4::Config for Runtime {} + +pub type TransactionExtension = frame_system::AuthorizeCall; + +pub type Header = sp_runtime::generic::Header; +pub type Block = sp_runtime::generic::Block; +pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic< + u64, + RuntimeCall, + UintAuthorityId, + TransactionExtension, +>; + +frame_support::construct_runtime!( + pub enum Runtime { + System: frame_system, + Pallet1: pallet1, + Pallet1Instance2: pallet1::, + Pallet2: pallet2, + Pallet3: pallet3, + #[cfg(feature = "frame-feature-testing")] + Pallet1Instance3: pallet1::, + Pallet4: pallet4, + } +); + +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = RuntimeGenesisConfig { ..Default::default() }.build_storage().unwrap(); + t.into() +} + +#[test] +fn valid_call_weight_test() { + // Tests for valid successful calls and assert their weight + + struct Test { + call: RuntimeCall, + dispatch_success: bool, + call_weight: Weight, + ext_weight: Weight, + actual_weight: Weight, + } + + let call_2_auth_weight_refund = Weight::from_all(2); + + let tests = vec![ + Test { + call: RuntimeCall::Pallet1(pallet1::Call::call1 {}), + dispatch_success: true, + call_weight: pallet1::CALL_1_WEIGHT, + ext_weight: pallet1::CALL_1_AUTH_WEIGHT, + actual_weight: pallet1::CALL_1_WEIGHT + pallet1::CALL_1_AUTH_WEIGHT, + }, + Test { + call: RuntimeCall::Pallet1(pallet1::Call::call2 { + a: true, + b: 1, + c: 2, + d: 3, + e: 4, + f: true, + authorize_refund: Weight::zero(), + }), + dispatch_success: true, + call_weight: pallet1::CALL_2_WEIGHT, + ext_weight: pallet1::CALL_2_AUTH_WEIGHT, + actual_weight: pallet1::CALL_2_REFUND + pallet1::CALL_2_AUTH_WEIGHT, + }, + Test { + call: RuntimeCall::Pallet1(pallet1::Call::call3 { valid: true, some_gen: 1 }), + dispatch_success: false, + call_weight: pallet1::CALL_3_WEIGHT, + ext_weight: pallet1::CALL_3_AUTH_WEIGHT, + actual_weight: pallet1::CALL_3_REFUND + pallet1::CALL_3_AUTH_WEIGHT, + }, + Test { + call: RuntimeCall::Pallet1Instance2(pallet1::Call::call1 {}), + dispatch_success: true, + call_weight: pallet1::CALL_1_WEIGHT, + ext_weight: pallet1::CALL_1_AUTH_WEIGHT, + actual_weight: pallet1::CALL_1_WEIGHT + pallet1::CALL_1_AUTH_WEIGHT, + }, + Test { + call: RuntimeCall::Pallet1Instance2(pallet1::Call::call2 { + a: true, + b: 1, + c: 2, + d: 3, + e: 4, + f: true, + authorize_refund: call_2_auth_weight_refund, + }), + dispatch_success: true, + call_weight: pallet1::CALL_2_WEIGHT, + ext_weight: pallet1::CALL_2_AUTH_WEIGHT, + actual_weight: pallet1::CALL_2_REFUND + pallet1::CALL_2_AUTH_WEIGHT - + call_2_auth_weight_refund, + }, + Test { + call: RuntimeCall::Pallet1Instance2(pallet1::Call::call3 { valid: true, some_gen: 1 }), + dispatch_success: false, + call_weight: pallet1::CALL_3_WEIGHT, + ext_weight: pallet1::CALL_3_AUTH_WEIGHT, + actual_weight: pallet1::CALL_3_REFUND + pallet1::CALL_3_AUTH_WEIGHT, + }, + Test { + call: RuntimeCall::Pallet2(pallet2::Call::call1 {}), + dispatch_success: true, + call_weight: Weight::zero(), + ext_weight: Weight::zero(), + actual_weight: Weight::zero(), + }, + Test { + call: RuntimeCall::Pallet3(pallet3::Call::call1 {}), + dispatch_success: true, + call_weight: pallet3::CALL_1_WEIGHT, + ext_weight: pallet3::CALL_1_AUTH_WEIGHT, + actual_weight: pallet3::CALL_1_WEIGHT + pallet3::CALL_1_AUTH_WEIGHT, + }, + #[cfg(feature = "frame-feature-testing")] + Test { + call: RuntimeCall::Pallet1Instance3(pallet1::Call::call4 {}), + dispatch_success: true, + call_weight: pallet1::CALL_4_WEIGHT, + ext_weight: pallet1::CALL_4_AUTH_WEIGHT, + actual_weight: pallet1::CALL_4_WEIGHT + pallet1::CALL_4_AUTH_WEIGHT, + }, + ]; + + for (index, test) in tests.into_iter().enumerate() { + let Test { call, dispatch_success, call_weight, ext_weight, actual_weight } = test; + + println!("Running test {}", index); + + new_test_ext().execute_with(|| { + let tx_ext = frame_system::AuthorizeCall::::new(); + + let tx = UncheckedExtrinsic::new_transaction(call, tx_ext); + + let info = tx.get_dispatch_info(); + let len = tx.using_encoded(|e| e.len()); + + let checked = Checkable::check(tx, &frame_system::ChainContext::::default()) + .expect("Transaction is general so signature is good"); + + checked + .validate::(TransactionSource::External, &info, len) + .expect("call1 is always valid"); + + let dispatch_result = + checked.apply::(&info, len).expect("Transaction is valid"); + + assert_eq!(dispatch_result.is_ok(), dispatch_success); + + let post_info = dispatch_result.unwrap_or_else(|e| e.post_info); + + assert_eq!(info.call_weight, call_weight); + assert_eq!(info.extension_weight, ext_weight); + assert_eq!(post_info.actual_weight, Some(actual_weight)); + }); + } +} + +#[test] +fn call_validity() { + struct Test { + call: RuntimeCall, + validate_res: TransactionValidity, + } + + let tests = vec![ + Test { + call: RuntimeCall::Pallet1(pallet1::Call::call1 {}), + validate_res: Ok(Default::default()), + }, + Test { + call: RuntimeCall::Pallet1(pallet1::Call::call2 { + a: true, + b: 1, + c: 2, + d: 3, + e: 4, + f: true, + authorize_refund: Weight::zero(), + }), + validate_res: Ok(ValidTransaction { + priority: 1, + requires: vec![2u8.encode()], + provides: vec![3u8.encode()], + longevity: 4, + propagate: true, + }), + }, + Test { + call: RuntimeCall::Pallet1(pallet1::Call::call2 { + a: false, + b: 1, + c: 2, + d: 3, + e: 4, + f: true, + authorize_refund: Weight::zero(), + }), + validate_res: Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), + }, + Test { + call: RuntimeCall::Pallet1(pallet1::Call::call3 { valid: true, some_gen: 1 }), + validate_res: Ok(Default::default()), + }, + Test { + call: RuntimeCall::Pallet1(pallet1::Call::call3 { valid: false, some_gen: 1 }), + validate_res: Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), + }, + ]; + + for (index, test) in tests.into_iter().enumerate() { + let Test { call, validate_res } = test; + + println!("Running test {}", index); + + new_test_ext().execute_with(|| { + let tx_ext = frame_system::AuthorizeCall::::new(); + + let tx = UncheckedExtrinsic::new_transaction(call, tx_ext); + + let info = tx.get_dispatch_info(); + let len = tx.using_encoded(|e| e.len()); + + let checked = Checkable::check(tx, &frame_system::ChainContext::::default()) + .expect("Transaction is general so signature is good"); + + let res = checked.validate::(TransactionSource::External, &info, len); + assert_eq!(res, validate_res); + }); + } +} + +#[test] +fn signed_is_valid_but_dispatch_error() { + new_test_ext().execute_with(|| { + let call = RuntimeCall::Pallet1(pallet1::Call::call1 {}); + let tx_ext = frame_system::AuthorizeCall::::new(); + + let tx = UncheckedExtrinsic::new_signed(call, 1u64, 1.into(), tx_ext); + + let info = tx.get_dispatch_info(); + let len = tx.using_encoded(|e| e.len()); + + let checked = Checkable::check(tx, &frame_system::ChainContext::::default()) + .expect("Signature is good"); + + checked + .validate::(TransactionSource::External, &info, len) + .expect("origin is signed, transaction is valid"); + + let dispatch_err = checked + .apply::(&info, len) + .expect("origin is signed, transaction is valid") + .expect_err("origin is wrong for the dispatched call"); + + assert_eq!(dispatch_err.error, DispatchError::BadOrigin); + }); +} + +#[test] +fn call_without_authorization() { + use frame_support::traits::Authorize; + + new_test_ext().execute_with(|| { + let call = RuntimeCall::Pallet4(pallet4::Call::call1 {}); + + // tests for trait implementation + assert_eq!(call.weight_of_authorize(), Weight::zero()); + assert_eq!(call.authorize(TransactionSource::External), None); + assert_eq!(call.authorize(TransactionSource::InBlock), None); + assert_eq!(call.authorize(TransactionSource::Local), None); + + // tests for transaction extension implementation + let tx_ext = frame_system::AuthorizeCall::::new(); + + let tx = UncheckedExtrinsic::new_transaction(call, tx_ext); + + let info = tx.get_dispatch_info(); + let len = tx.using_encoded(|e| e.len()); + + let checked = Checkable::check(tx, &frame_system::ChainContext::::default()) + .expect("Transaction is general so signature is good"); + + let err = checked + .validate::(TransactionSource::External, &info, len) + .expect_err("Call is not authorized, transaction is invalid"); + + assert_eq!(err, TransactionValidityError::Invalid(InvalidTransaction::UnknownOrigin)); + + let err = checked + .apply::(&info, len) + .expect_err("Call is not authorized, transaction is invalid"); + + assert_eq!(err, TransactionValidityError::Invalid(InvalidTransaction::UnknownOrigin)); + }); +} diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr index 726b09cf54c9..c20e9129faa1 100644 --- a/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr +++ b/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr @@ -433,7 +433,7 @@ error[E0277]: the trait bound `Runtime: Config` is not satisfied note: required by a bound in `frame_system::Call` --> $WORKSPACE/substrate/frame/system/src/lib.rs | - | #[pallet::call] + | #[pallet::call(weight = ::SystemWeightInfo)] | ^^^^ required by this bound in `Call` = note: this error originates in the derive macro `self::sp_api_hidden_includes_construct_runtime::hidden_include::__private::codec::Encode` (in Nightly builds, run with -Z macro-backtrace for more info) @@ -486,7 +486,7 @@ error[E0599]: the method `get_dispatch_info` exists for reference `&Call::SystemWeightInfo)] | ---- doesn't satisfy `frame_system::Call: GetDispatchInfo` | = note: the following trait bounds were not satisfied: @@ -517,7 +517,7 @@ error[E0599]: the method `is_feeless` exists for reference `&Call`, but | ::: $WORKSPACE/substrate/frame/system/src/lib.rs | - | #[pallet::call] + | #[pallet::call(weight = ::SystemWeightInfo)] | ---- doesn't satisfy `frame_system::Call: CheckIfFeeless` | = note: the following trait bounds were not satisfied: @@ -548,7 +548,7 @@ error[E0599]: the method `get_call_name` exists for reference `&Call`, | ::: $WORKSPACE/substrate/frame/system/src/lib.rs | - | #[pallet::call] + | #[pallet::call(weight = ::SystemWeightInfo)] | ---- doesn't satisfy `frame_system::Call: GetCallName` | = note: the following trait bounds were not satisfied: diff --git a/substrate/frame/support/test/tests/pallet_ui/authorize_weight_but_no_closure.rs b/substrate/frame/support/test/tests/pallet_ui/authorize_weight_but_no_closure.rs new file mode 100644 index 000000000000..a472133d3376 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/authorize_weight_but_no_closure.rs @@ -0,0 +1,47 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config(with_default)] + pub trait Config: frame_system::Config { + type WeightInfo: WeightInfo; + } + + pub trait WeightInfo { + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::call(weight = T::WeightInfo)] + impl Pallet { + #[pallet::weight_of_authorize(Weight::zero())] + #[pallet::weight(Weight::zero())] + #[pallet::call_index(0)] + pub fn call1(origin: OriginFor, a: u32) -> DispatchResult { + let _ = origin; + let _ = a; + Ok(()) + } + } +} + +fn main() {} diff --git a/substrate/frame/support/test/tests/pallet_ui/authorize_weight_but_no_closure.stderr b/substrate/frame/support/test/tests/pallet_ui/authorize_weight_but_no_closure.stderr new file mode 100644 index 000000000000..bb61ef04922b --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/authorize_weight_but_no_closure.stderr @@ -0,0 +1,5 @@ +error: Invalid pallet::call, weight_of_authorize attribute must be used with authorize attribute + --> tests/pallet_ui/authorize_weight_but_no_closure.rs:36:33 + | +36 | #[pallet::weight_of_authorize(Weight::zero())] + | ^^^^^^ diff --git a/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_closure.rs b/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_closure.rs new file mode 100644 index 000000000000..1d82c42b1d58 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_closure.rs @@ -0,0 +1,48 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config(with_default)] + pub trait Config: frame_system::Config { + type WeightInfo: WeightInfo; + } + + pub trait WeightInfo { + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::call(weight = T::WeightInfo)] + impl Pallet { + #[pallet::authorize(|_| true)] + #[pallet::weight_of_authorize(Weight::zero())] + #[pallet::weight(Weight::zero())] + #[pallet::call_index(0)] + pub fn call1(origin: OriginFor, a: u32) -> DispatchResult { + let _ = origin; + let _ = a; + Ok(()) + } + } +} + +fn main() {} diff --git a/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_closure.stderr b/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_closure.stderr new file mode 100644 index 000000000000..8abc901520bf --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_closure.stderr @@ -0,0 +1,8 @@ +error[E0593]: closure is expected to take 2 arguments, but it takes 1 argument + --> tests/pallet_ui/authorize_wrong_closure.rs:36:23 + | +36 | #[pallet::authorize(|_| true)] + | ^-- + | | + | expected closure that takes 2 arguments + | takes 1 argument diff --git a/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_closure_2.rs b/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_closure_2.rs new file mode 100644 index 000000000000..c0710efd9813 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_closure_2.rs @@ -0,0 +1,48 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config(with_default)] + pub trait Config: frame_system::Config { + type WeightInfo: WeightInfo; + } + + pub trait WeightInfo { + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::call(weight = T::WeightInfo)] + impl Pallet { + #[pallet::authorize(|_, _: u8| -> bool { true })] + #[pallet::weight_of_authorize(Weight::zero())] + #[pallet::weight(Weight::zero())] + #[pallet::call_index(0)] + pub fn call1(origin: OriginFor, a: u32) -> DispatchResult { + let _ = origin; + let _ = a; + Ok(()) + } + } +} + +fn main() {} diff --git a/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_closure_2.stderr b/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_closure_2.stderr new file mode 100644 index 000000000000..3f38dec1cb4b --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_closure_2.stderr @@ -0,0 +1,11 @@ +error[E0631]: type mismatch in closure arguments + --> tests/pallet_ui/authorize_wrong_closure_2.rs:36:23 + | +36 | #[pallet::authorize(|_, _: u8| -> bool { true })] + | ^----------------- + | | + | expected due to this + | found signature defined here + | + = note: expected closure signature `for<'a> fn(TransactionSource, &'a u32) -> _` + found closure signature `fn(TransactionSource, u8) -> _` diff --git a/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_closure_3.rs b/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_closure_3.rs new file mode 100644 index 000000000000..03a1de2e9ca8 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_closure_3.rs @@ -0,0 +1,48 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config(with_default)] + pub trait Config: frame_system::Config { + type WeightInfo: WeightInfo; + } + + pub trait WeightInfo { + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::call(weight = T::WeightInfo)] + impl Pallet { + #[pallet::authorize()] + #[pallet::weight_of_authorize(Weight::zero())] + #[pallet::weight(Weight::zero())] + #[pallet::call_index(0)] + pub fn call1(origin: OriginFor, a: u32) -> DispatchResult { + let _ = origin; + let _ = a; + Ok(()) + } + } +} + +fn main() {} diff --git a/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_closure_3.stderr b/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_closure_3.stderr new file mode 100644 index 000000000000..895955b2c658 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_closure_3.stderr @@ -0,0 +1,5 @@ +error: unexpected end of input, expected an expression + --> tests/pallet_ui/authorize_wrong_closure_3.rs:36:22 + | +36 | #[pallet::authorize()] + | ^^ diff --git a/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_closure_4.rs b/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_closure_4.rs new file mode 100644 index 000000000000..f8d2826fb604 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_closure_4.rs @@ -0,0 +1,48 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config(with_default)] + pub trait Config: frame_system::Config { + type WeightInfo: WeightInfo; + } + + pub trait WeightInfo { + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::call(weight = T::WeightInfo)] + impl Pallet { + #[pallet::authorize(Ok(Default::default()))] + #[pallet::weight_of_authorize(Weight::zero())] + #[pallet::weight(Weight::zero())] + #[pallet::call_index(0)] + pub fn call1(origin: OriginFor, a: u32) -> DispatchResult { + let _ = origin; + let _ = a; + Ok(()) + } + } +} + +fn main() {} diff --git a/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_closure_4.stderr b/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_closure_4.stderr new file mode 100644 index 000000000000..4033c7857d96 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_closure_4.stderr @@ -0,0 +1,7 @@ +error[E0277]: expected a `Fn(TransactionSource, &u32)` closure, found `Result<_, _>` + --> tests/pallet_ui/authorize_wrong_closure_4.rs:36:23 + | +36 | #[pallet::authorize(Ok(Default::default()))] + | ^^ expected an `Fn(TransactionSource, &u32)` closure, found `Result<_, _>` + | + = help: the trait `for<'a> Fn(TransactionSource, &'a u32)` is not implemented for `Result<_, _>` diff --git a/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_closure_5.rs b/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_closure_5.rs new file mode 100644 index 000000000000..1aca309e0530 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_closure_5.rs @@ -0,0 +1,50 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config(with_default)] + pub trait Config: frame_system::Config { + type WeightInfo: WeightInfo; + } + + pub trait WeightInfo { + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::call(weight = T::WeightInfo)] + impl Pallet { + #[pallet::authorize(|_a: &u32| -> TransactionValidityWithRefund { + Ok(Default::default()) + })] + #[pallet::weight_of_authorize(Weight::zero())] + #[pallet::weight(Weight::zero())] + #[pallet::call_index(0)] + pub fn call1(origin: OriginFor, a: u32) -> DispatchResult { + let _ = origin; + let _ = a; + Ok(()) + } + } +} + +fn main() {} diff --git a/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_closure_5.stderr b/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_closure_5.stderr new file mode 100644 index 000000000000..4b72a44daa61 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_closure_5.stderr @@ -0,0 +1,8 @@ +error[E0593]: closure is expected to take 2 arguments, but it takes 1 argument + --> tests/pallet_ui/authorize_wrong_closure_5.rs:36:23 + | +36 | #[pallet::authorize(|_a: &u32| -> TransactionValidityWithRefund { + | ^------------------------------------------ + | | + | expected closure that takes 2 arguments + | takes 1 argument diff --git a/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_weight.rs b/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_weight.rs new file mode 100644 index 000000000000..1b86cd3770ce --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_weight.rs @@ -0,0 +1,48 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config(with_default)] + pub trait Config: frame_system::Config { + type WeightInfo: WeightInfo; + } + + pub trait WeightInfo { + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::call(weight = T::WeightInfo)] + impl Pallet { + #[pallet::authorize(|_, _| Ok(Default::default()))] + #[pallet::weight_of_authorize("foo")] + #[pallet::weight(Weight::zero())] + #[pallet::call_index(0)] + pub fn call1(origin: OriginFor, a: u32) -> DispatchResult { + let _ = origin; + let _ = a; + Ok(()) + } + } +} + +fn main() {} diff --git a/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_weight.stderr b/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_weight.stderr new file mode 100644 index 000000000000..83dac4430990 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_weight.stderr @@ -0,0 +1,22 @@ +error: use of deprecated constant `pallet::warnings::ConstantWeight_0::_w`: + It is deprecated to use hard-coded constant as call weight. + Please instead benchmark all calls or put the pallet into `dev` mode. + + For more info see: + + --> tests/pallet_ui/authorize_wrong_weight.rs:37:33 + | +37 | #[pallet::weight_of_authorize("foo")] + | ^^^^^ + | + = note: `-D deprecated` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(deprecated)]` + +error[E0308]: mismatched types + --> tests/pallet_ui/authorize_wrong_weight.rs:37:33 + | +18 | #[frame_support::pallet] + | ------------------------ expected `frame_support::weights::Weight` because of return type +... +37 | #[pallet::weight_of_authorize("foo")] + | ^^^^^ expected `Weight`, found `&str` diff --git a/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_weight_info.rs b/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_weight_info.rs new file mode 100644 index 000000000000..5ec08ec50530 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_weight_info.rs @@ -0,0 +1,47 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config(with_default)] + pub trait Config: frame_system::Config { + type WeightInfo: WeightInfo; + } + + pub trait WeightInfo { + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::call(weight = T::WeightInfo)] + impl Pallet { + #[pallet::authorize(|_, _| Ok(Default::default()))] + #[pallet::weight(Weight::zero())] + #[pallet::call_index(0)] + pub fn call1(origin: OriginFor, a: u32) -> DispatchResult { + let _ = origin; + let _ = a; + Ok(()) + } + } +} + +fn main() {} diff --git a/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_weight_info.stderr b/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_weight_info.stderr new file mode 100644 index 000000000000..6997dfb2a6b2 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_weight_info.stderr @@ -0,0 +1,5 @@ +error[E0599]: no function or associated item named `authorize_call1` found for associated type `::WeightInfo` in the current scope + --> tests/pallet_ui/authorize_wrong_weight_info.rs:39:10 + | +39 | pub fn call1(origin: OriginFor, a: u32) -> DispatchResult { + | ^^^^^ function or associated item not found in `::WeightInfo` diff --git a/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_weight_info_2.rs b/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_weight_info_2.rs new file mode 100644 index 000000000000..c628605c8312 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_weight_info_2.rs @@ -0,0 +1,47 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config(with_default)] + pub trait Config: frame_system::Config { + type WeightInfo: WeightInfo; + } + + pub trait WeightInfo { + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::call(weight = T::WeightIn)] + impl Pallet { + #[pallet::authorize(|_, _| Ok(Default::default()))] + #[pallet::weight(Weight::zero())] + #[pallet::call_index(0)] + pub fn call1(origin: OriginFor, a: u32) -> DispatchResult { + let _ = origin; + let _ = a; + Ok(()) + } + } +} + +fn main() {} diff --git a/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_weight_info_2.stderr b/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_weight_info_2.stderr new file mode 100644 index 000000000000..237b1fc05461 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/authorize_wrong_weight_info_2.stderr @@ -0,0 +1,5 @@ +error[E0220]: associated type `WeightIn` not found for `T` + --> tests/pallet_ui/authorize_wrong_weight_info_2.rs:34:29 + | +34 | #[pallet::call(weight = T::WeightIn)] + | ^^^^^^^^ help: there is an associated type with a similar name: `WeightInfo` diff --git a/substrate/frame/support/test/tests/pallet_ui/call_invalid_attr.stderr b/substrate/frame/support/test/tests/pallet_ui/call_invalid_attr.stderr index 1809fcb6ed99..b42697294ec9 100644 --- a/substrate/frame/support/test/tests/pallet_ui/call_invalid_attr.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/call_invalid_attr.stderr @@ -1,4 +1,4 @@ -error: expected one of: `weight`, `call_index`, `feeless_if` +error: expected one of: `weight`, `call_index`, `feeless_if`, `authorize`, `weight_of_authorize` --> tests/pallet_ui/call_invalid_attr.rs:31:13 | 31 | #[pallet::weird_attr] diff --git a/substrate/frame/support/test/tests/pallet_ui/call_missing_weight.stderr b/substrate/frame/support/test/tests/pallet_ui/call_missing_weight.stderr index 555dd426fe29..39b599ebe80e 100644 --- a/substrate/frame/support/test/tests/pallet_ui/call_missing_weight.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/call_missing_weight.stderr @@ -1,6 +1,4 @@ -error: A pallet::call requires either a concrete `#[pallet::weight($expr)]` or an - inherited weight from the `#[pallet:call(weight($type))]` attribute, but - none were given. +error: A pallet::call requires either a concrete `#[pallet::weight($expr)]` or an inherited weight from the `#[pallet:call(weight($type))]` attribute, but none were given. --> tests/pallet_ui/call_missing_weight.rs:34:7 | 34 | pub fn foo(origin: OriginFor) -> DispatchResultWithPostInfo {} diff --git a/substrate/frame/support/test/tests/pallet_ui/dev_mode_without_arg.stderr b/substrate/frame/support/test/tests/pallet_ui/dev_mode_without_arg.stderr index 5aea7d83a61c..828fd3aeeb91 100644 --- a/substrate/frame/support/test/tests/pallet_ui/dev_mode_without_arg.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/dev_mode_without_arg.stderr @@ -1,6 +1,4 @@ -error: A pallet::call requires either a concrete `#[pallet::weight($expr)]` or an - inherited weight from the `#[pallet:call(weight($type))]` attribute, but - none were given. +error: A pallet::call requires either a concrete `#[pallet::weight($expr)]` or an inherited weight from the `#[pallet:call(weight($type))]` attribute, but none were given. --> tests/pallet_ui/dev_mode_without_arg.rs:39:7 | 39 | pub fn my_call(_origin: OriginFor) -> DispatchResult { diff --git a/substrate/frame/support/test/tests/split_ui/pass/call/mod.rs b/substrate/frame/support/test/tests/split_ui/pass/call/mod.rs index 27b3ec31b835..62f1326ef0aa 100644 --- a/substrate/frame/support/test/tests/split_ui/pass/call/mod.rs +++ b/substrate/frame/support/test/tests/split_ui/pass/call/mod.rs @@ -32,7 +32,7 @@ mod call { ensure_signed(origin)?; Ok(()) } - + #[pallet::call_index(2)] pub fn noop2(origin: OriginFor, _x: u64, _y: u64) -> DispatchResult { ensure_signed(origin)?; @@ -59,5 +59,12 @@ mod call { ensure_signed(origin)?; Ok(()) } + + #[pallet::call_index(6)] + #[pallet::authorize(|_source, _x, _y| Ok(Default::default()))] + pub fn noop_authorize(origin: OriginFor, _x: u64, _y: u64) -> DispatchResult { + ensure_authorized(origin)?; + Ok(()) + } } } diff --git a/substrate/frame/system/src/extensions/authorize_call.rs b/substrate/frame/system/src/extensions/authorize_call.rs new file mode 100644 index 000000000000..3dcab3bb0498 --- /dev/null +++ b/substrate/frame/system/src/extensions/authorize_call.rs @@ -0,0 +1,329 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::Config; +use frame_support::{ + dispatch::DispatchInfo, + pallet_prelude::{Decode, DispatchResult, Encode, TransactionSource, TypeInfo, Weight}, + traits::Authorize, + CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, +}; +use sp_runtime::{ + traits::{ + AsTransactionAuthorizedOrigin, Dispatchable, PostDispatchInfoOf, TransactionExtension, + ValidateResult, + }, + transaction_validity::TransactionValidityError, +}; + +#[derive( + Encode, Decode, CloneNoBound, EqNoBound, PartialEqNoBound, TypeInfo, RuntimeDebugNoBound, +)] +#[scale_info(skip_type_params(T))] +pub struct AuthorizeCall(core::marker::PhantomData); + +impl AuthorizeCall { + pub fn new() -> Self { + Self(Default::default()) + } +} + +impl TransactionExtension for AuthorizeCall +where + T::RuntimeCall: Dispatchable, +{ + const IDENTIFIER: &'static str = "AuthorizeCall"; + type Implicit = (); + type Val = Weight; + type Pre = Weight; + + fn validate( + &self, + origin: T::RuntimeOrigin, + call: &T::RuntimeCall, + _info: &DispatchInfo, + _len: usize, + _self_implicit: Self::Implicit, + _inherited_implication: &impl Encode, + source: TransactionSource, + ) -> ValidateResult { + if !origin.is_transaction_authorized() { + if let Some(authorize) = call.authorize(source) { + return authorize.map(|(validity, unspent)| { + (validity, unspent, crate::Origin::::Authorized.into()) + }) + } + } + + Ok((Default::default(), Weight::zero(), origin)) + } + + fn prepare( + self, + val: Self::Val, + _origin: &T::RuntimeOrigin, + _call: &T::RuntimeCall, + _info: &DispatchInfo, + _len: usize, + ) -> Result { + Ok(val) + } + + fn post_dispatch_details( + pre: Self::Pre, + _info: &DispatchInfo, + _post_info: &PostDispatchInfoOf, + _len: usize, + _result: &DispatchResult, + ) -> Result { + Ok(pre) + } + + fn weight(&self, call: &T::RuntimeCall) -> Weight { + call.weight_of_authorize() + } +} + +#[cfg(test)] +mod tests { + use crate as frame_system; + use codec::Encode; + use frame_support::{ + derive_impl, dispatch::GetDispatchInfo, pallet_prelude::TransactionSource, + traits::OriginTrait, + }; + use sp_runtime::{ + testing::UintAuthorityId, + traits::{Applyable, Checkable, TransactionExtension as _}, + transaction_validity::{ + InvalidTransaction, TransactionSource::External, TransactionValidityError, + }, + BuildStorage, DispatchError, + }; + + #[frame_support::pallet] + pub mod pallet1 { + use crate as frame_system; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + pub const CALL_WEIGHT: Weight = Weight::from_all(4); + pub const AUTH_WEIGHT: Weight = Weight::from_all(5); + + pub fn valid_transaction() -> ValidTransaction { + ValidTransaction { + priority: 10, + provides: vec![1u8.encode()], + requires: vec![], + longevity: 1000, + propagate: true, + } + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::call] + impl Pallet { + #[pallet::weight(CALL_WEIGHT)] + #[pallet::call_index(0)] + #[pallet::authorize(|_source, valid| if *valid { + Ok((valid_transaction(), Weight::zero())) + } else { + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) + })] + #[pallet::weight_of_authorize(AUTH_WEIGHT)] + pub fn call1(origin: OriginFor, valid: bool) -> DispatchResult { + crate::ensure_authorized(origin)?; + let _ = valid; + Ok(()) + } + } + } + + #[frame_support::runtime] + mod runtime { + #[runtime::runtime] + #[runtime::derive( + RuntimeCall, + RuntimeEvent, + RuntimeError, + RuntimeOrigin, + RuntimeFreezeReason, + RuntimeHoldReason, + RuntimeSlashReason, + RuntimeLockId, + RuntimeTask + )] + pub struct Runtime; + + #[runtime::pallet_index(0)] + pub type System = frame_system::Pallet; + + #[runtime::pallet_index(1)] + pub type Pallet1 = pallet1::Pallet; + } + + pub type TransactionExtension = (frame_system::AuthorizeCall,); + + pub type Header = sp_runtime::generic::Header; + pub type Block = sp_runtime::generic::Block; + pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic< + u64, + RuntimeCall, + UintAuthorityId, + TransactionExtension, + >; + + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] + impl frame_system::Config for Runtime { + type Block = Block; + } + + impl pallet1::Config for Runtime {} + + pub fn new_test_ext() -> sp_io::TestExternalities { + let t = RuntimeGenesisConfig { ..Default::default() }.build_storage().unwrap(); + t.into() + } + + #[test] + fn valid_transaction() { + let call = RuntimeCall::Pallet1(pallet1::Call::call1 { valid: true }); + + new_test_ext().execute_with(|| { + let tx_ext = (frame_system::AuthorizeCall::::new(),); + + let tx = UncheckedExtrinsic::new_transaction(call, tx_ext); + + let info = tx.get_dispatch_info(); + let len = tx.using_encoded(|e| e.len()); + + let checked = Checkable::check(tx, &frame_system::ChainContext::::default()) + .expect("Transaction is general so signature is good"); + + let valid_tx = checked + .validate::(TransactionSource::External, &info, len) + .expect("call valid"); + + let dispatch_result = + checked.apply::(&info, len).expect("Transaction is valid"); + + assert!(dispatch_result.is_ok()); + + let post_info = dispatch_result.unwrap_or_else(|e| e.post_info); + + assert_eq!(valid_tx, pallet1::valid_transaction()); + assert_eq!(info.call_weight, pallet1::CALL_WEIGHT); + assert_eq!(info.extension_weight, pallet1::AUTH_WEIGHT); + assert_eq!(post_info.actual_weight, Some(pallet1::CALL_WEIGHT + pallet1::AUTH_WEIGHT)); + }); + } + + #[test] + fn invalid_transaction_fail_authorization() { + let call = RuntimeCall::Pallet1(pallet1::Call::call1 { valid: false }); + + new_test_ext().execute_with(|| { + let tx_ext = (frame_system::AuthorizeCall::::new(),); + + let tx = UncheckedExtrinsic::new_transaction(call, tx_ext); + + let info = tx.get_dispatch_info(); + let len = tx.using_encoded(|e| e.len()); + + let checked = Checkable::check(tx, &frame_system::ChainContext::::default()) + .expect("Transaction is general so signature is good"); + + let validate_err = checked + .validate::(TransactionSource::External, &info, len) + .expect_err("call is invalid"); + + let apply_err = + checked.apply::(&info, len).expect_err("Transaction is invalid"); + + assert_eq!(validate_err, TransactionValidityError::Invalid(InvalidTransaction::Call)); + assert_eq!(apply_err, TransactionValidityError::Invalid(InvalidTransaction::Call)); + assert_eq!(info.call_weight, pallet1::CALL_WEIGHT); + assert_eq!(info.extension_weight, pallet1::AUTH_WEIGHT); + }); + } + + #[test] + fn failing_transaction_invalid_origin() { + let call = RuntimeCall::Pallet1(pallet1::Call::call1 { valid: true }); + + new_test_ext().execute_with(|| { + let tx_ext = (frame_system::AuthorizeCall::::new(),); + + let tx = UncheckedExtrinsic::new_signed(call, 42, 42.into(), tx_ext); + + let info = tx.get_dispatch_info(); + let len = tx.using_encoded(|e| e.len()); + + let checked = Checkable::check(tx, &frame_system::ChainContext::::default()) + .expect("Signature is good"); + + checked + .validate::(TransactionSource::External, &info, len) + .expect("Transaction is valid, tx ext doesn't deny none"); + + let dispatch_res = checked + .apply::(&info, len) + .expect("Transaction is valid") + .expect_err("Transaction is failing, because origin is wrong"); + + assert_eq!(dispatch_res.error, DispatchError::BadOrigin); + assert_eq!(info.call_weight, pallet1::CALL_WEIGHT); + assert_eq!(info.extension_weight, pallet1::AUTH_WEIGHT); + }); + } + + #[test] + fn call_filter_preserved() { + new_test_ext().execute_with(|| { + let ext = frame_system::AuthorizeCall::::new(); + let filtered_call = RuntimeCall::System(frame_system::Call::remark { remark: vec![] }); + + let origin = { + let mut o: RuntimeOrigin = crate::Origin::::Signed(42).into(); + let filter_clone = filtered_call.clone(); + o.add_filter(move |call| filter_clone != *call); + o + }; + + assert!(!origin.filter_call(&filtered_call)); + + let (_, _, new_origin) = ext + .validate( + origin, + &RuntimeCall::Pallet1(pallet1::Call::call1 { valid: true }), + &crate::DispatchInfo::default(), + Default::default(), + (), + &(), + External, + ) + .expect("valid"); + + assert!(!new_origin.filter_call(&filtered_call)); + }); + } +} diff --git a/substrate/frame/system/src/extensions/mod.rs b/substrate/frame/system/src/extensions/mod.rs index d79104d22403..b05042aed7ed 100644 --- a/substrate/frame/system/src/extensions/mod.rs +++ b/substrate/frame/system/src/extensions/mod.rs @@ -15,6 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +pub mod authorize_call; pub mod check_genesis; pub mod check_mortality; pub mod check_non_zero_sender; diff --git a/substrate/frame/system/src/lib.rs b/substrate/frame/system/src/lib.rs index 862fb4cf9faf..279ac26b08b2 100644 --- a/substrate/frame/system/src/lib.rs +++ b/substrate/frame/system/src/lib.rs @@ -110,9 +110,9 @@ use sp_runtime::traits::TrailingZeroInput; use sp_runtime::{ generic, traits::{ - self, AtLeast32Bit, BadOrigin, BlockNumberProvider, Bounded, CheckEqual, Dispatchable, - Hash, Header, Lookup, LookupError, MaybeDisplay, MaybeSerializeDeserialize, Member, One, - Saturating, SimpleBitOps, StaticLookup, Zero, + self, AsTransactionAuthorizedOrigin, AtLeast32Bit, BadOrigin, BlockNumberProvider, Bounded, + CheckEqual, Dispatchable, Hash, Header, Lookup, LookupError, MaybeDisplay, + MaybeSerializeDeserialize, Member, One, Saturating, SimpleBitOps, StaticLookup, Zero, }, transaction_validity::{ InvalidTransaction, TransactionLongevity, TransactionSource, TransactionValidity, @@ -167,7 +167,7 @@ pub mod weights; pub mod migrations; pub use extensions::{ - check_genesis::CheckGenesis, check_mortality::CheckMortality, + authorize_call::AuthorizeCall, check_genesis::CheckGenesis, check_mortality::CheckMortality, check_non_zero_sender::CheckNonZeroSender, check_nonce::CheckNonce, check_spec_version::CheckSpecVersion, check_tx_version::CheckTxVersion, check_weight::CheckWeight, WeightInfo as ExtensionsWeightInfo, @@ -175,7 +175,7 @@ pub use extensions::{ // Backward compatible re-export. pub use extensions::check_mortality::CheckMortality as CheckEra; pub use frame_support::dispatch::RawOrigin; -use frame_support::traits::{PostInherents, PostTransactions, PreInherents}; +use frame_support::traits::{Authorize, PostInherents, PostTransactions, PreInherents}; use sp_core::storage::StateVersion; pub use weights::WeightInfo; @@ -500,7 +500,8 @@ pub mod pallet { type RuntimeOrigin: Into, Self::RuntimeOrigin>> + From> + Clone - + OriginTrait; + + OriginTrait + + AsTransactionAuthorizedOrigin; #[docify::export(system_runtime_call)] /// The aggregated `RuntimeCall` type. @@ -509,7 +510,8 @@ pub mod pallet { + Dispatchable + Debug + GetDispatchInfo - + From>; + + From> + + Authorize; /// The aggregated `RuntimeTask` type. #[pallet::no_default_bounds] @@ -669,7 +671,7 @@ pub mod pallet { } } - #[pallet::call] + #[pallet::call(weight = ::SystemWeightInfo)] impl Pallet { /// Make some on-chain remark. /// @@ -1418,6 +1420,18 @@ where } } +/// Ensure that the origin `o` represents an extrinsic with authorized call. Returns `Ok` or an +/// `Err` otherwise. +pub fn ensure_authorized(o: OuterOrigin) -> Result<(), BadOrigin> +where + OuterOrigin: Into, OuterOrigin>>, +{ + match o.into() { + Ok(RawOrigin::Authorized) => Ok(()), + _ => Err(BadOrigin), + } +} + /// Reference status; can be either referenced or unreferenced. #[derive(RuntimeDebug)] pub enum RefStatus { @@ -2295,7 +2309,9 @@ impl Lookup for ChainContext { /// Prelude to be used alongside pallet macro, for ease of use. pub mod pallet_prelude { - pub use crate::{ensure_none, ensure_root, ensure_signed, ensure_signed_or_root}; + pub use crate::{ + ensure_authorized, ensure_none, ensure_root, ensure_signed, ensure_signed_or_root, + }; /// Type alias for the `Origin` associated type of system config. pub type OriginFor = ::RuntimeOrigin; diff --git a/substrate/frame/system/src/offchain.rs b/substrate/frame/system/src/offchain.rs index bedfdded8183..3a911cb7eda8 100644 --- a/substrate/frame/system/src/offchain.rs +++ b/substrate/frame/system/src/offchain.rs @@ -517,6 +517,26 @@ pub trait SignMessage { TPayload: SignedPayload; } +/// Interface for creating a transaction with for a call that will be authorized. +/// +/// This interface allows to define the transaction extension that will be used alongside this call +/// to create the transaction. +/// +/// For more information about authorized call see [`frame_support::pallet_prelude::authorize`]. +pub trait CreateAuthorizedTransaction: CreateTransaction { + /// Create the transaction extension to be used alongside an authorized call. + /// + /// For more information about authorized call see [`frame_support::pallet_prelude::authorize`]. + fn create_extension() -> Self::Extension; + + /// Create a new transaction for an authorized call. + /// + /// For more information about authorized call see [`frame_support::pallet_prelude::authorize`]. + fn create_authorized_transaction(call: Self::RuntimeCall) -> Self::Extrinsic { + Self::create_transaction(call, Self::create_extension()) + } +} + /// Submit a signed transaction to the transaction pool. pub trait SendSignedTransaction< T: CreateSignedTransaction, diff --git a/substrate/primitives/runtime/src/transaction_validity.rs b/substrate/primitives/runtime/src/transaction_validity.rs index a48c8ee7ba84..4fe69d60ec6d 100644 --- a/substrate/primitives/runtime/src/transaction_validity.rs +++ b/substrate/primitives/runtime/src/transaction_validity.rs @@ -23,6 +23,7 @@ use crate::{ }; use alloc::{vec, vec::Vec}; use scale_info::TypeInfo; +use sp_weights::Weight; /// Priority for a transaction. Additive. Higher is better. pub type TransactionPriority = u64; @@ -217,6 +218,11 @@ impl std::fmt::Display for TransactionValidityError { /// Information on a transaction's validity and, if valid, on how it relates to other transactions. pub type TransactionValidity = Result; +/// Information on a transaction's validity and, if valid, on how it relates to other transactions +/// and some refund for the operation. +pub type TransactionValidityWithRefund = + Result<(ValidTransaction, Weight), TransactionValidityError>; + impl From for TransactionValidity { fn from(invalid_transaction: InvalidTransaction) -> Self { Err(TransactionValidityError::Invalid(invalid_transaction)) diff --git a/templates/minimal/runtime/src/lib.rs b/templates/minimal/runtime/src/lib.rs index 7b8449f2abe4..f5360ae5c43b 100644 --- a/templates/minimal/runtime/src/lib.rs +++ b/templates/minimal/runtime/src/lib.rs @@ -101,6 +101,8 @@ pub fn native_version() -> NativeVersion { /// The transaction extensions that are added to the runtime. type TxExtension = ( + // Authorize calls that validate themselves. + frame_system::AuthorizeCall, // Checks that the sender is not the zero address. frame_system::CheckNonZeroSender, // Checks that the runtime version is correct. diff --git a/templates/parachain/runtime/src/lib.rs b/templates/parachain/runtime/src/lib.rs index 43e76dba0591..3c81d60d85ac 100644 --- a/templates/parachain/runtime/src/lib.rs +++ b/templates/parachain/runtime/src/lib.rs @@ -75,6 +75,7 @@ pub type BlockId = generic::BlockId; /// The extension to the basic transaction logic. #[docify::export(template_signed_extra)] pub type TxExtension = ( + frame_system::AuthorizeCall, frame_system::CheckNonZeroSender, frame_system::CheckSpecVersion, frame_system::CheckTxVersion, diff --git a/templates/solochain/node/src/benchmarking.rs b/templates/solochain/node/src/benchmarking.rs index 0d60230cd19c..c588563d4824 100644 --- a/templates/solochain/node/src/benchmarking.rs +++ b/templates/solochain/node/src/benchmarking.rs @@ -110,6 +110,7 @@ pub fn create_benchmark_extrinsic( .map(|c| c / 2) .unwrap_or(2) as u64; let tx_ext: runtime::TxExtension = ( + frame_system::AuthorizeCall::::new(), frame_system::CheckNonZeroSender::::new(), frame_system::CheckSpecVersion::::new(), frame_system::CheckTxVersion::::new(), @@ -128,6 +129,7 @@ pub fn create_benchmark_extrinsic( call.clone(), tx_ext.clone(), ( + (), (), runtime::VERSION.spec_version, runtime::VERSION.transaction_version, diff --git a/templates/solochain/runtime/src/lib.rs b/templates/solochain/runtime/src/lib.rs index ae0ea16ae42e..03529e503071 100644 --- a/templates/solochain/runtime/src/lib.rs +++ b/templates/solochain/runtime/src/lib.rs @@ -148,6 +148,7 @@ pub type BlockId = generic::BlockId; /// The `TransactionExtension` to the basic transaction logic. pub type TxExtension = ( + frame_system::AuthorizeCall, frame_system::CheckNonZeroSender, frame_system::CheckSpecVersion, frame_system::CheckTxVersion,