diff --git a/.github/commands-readme.md b/.github/commands-readme.md index 20644c048c60..793524e056f8 100644 --- a/.github/commands-readme.md +++ b/.github/commands-readme.md @@ -25,12 +25,36 @@ Each command will have the same two required values, but it could have more. GitHub's official documentation: [Manually running a workflow](https://docs.github.com/en/actions/using-workflows/manually-running-a-workflow) +#### Running from CLI + +You can use [`gh cli`](https://cli.github.com/) to run the commands too. Refers to the [`gh workflow run`](https://cli.github.com/manual/gh_workflow_run) section from the documentation for more information. + ### Number of the Pull Request The number of the pull request. Required so the action can fetch the correct branch and comment if it fails. ## Action configurations +### FMT + +For FMT you only need the PR number. + +You can use the following [`gh cli`](https://cli.github.com/) inside the repo: + +```bash +gh workflow run command-fmt.yml -f pr=1000 +``` + +### Update UI + +For Update UI you only need the PR number. + +You can use the following [`gh cli`](https://cli.github.com/) inside the repo: + +```bash +gh workflow run command-update-ui.yml -f pr=1000 +``` + ### Bench Runs `benchmark pallet` or `benchmark overhead` against your PR and commits back updated weights. @@ -136,6 +160,12 @@ Posible combinations based on the `benchmark` dropdown. - Requires `Runtime Dir` to be `testing` - Requires `Target Directory` to be `cumulus` +You can use the following [`gh cli`](https://cli.github.com/) inside the repo: + +```bash +gh workflow run command-bench.yml -f pr=1000 -f benchmark=polkadot-pallet -f subcommand=pallet -f runtime=rococo -f pallet=pallet_name -f target_dir=polkadot +``` + ### Bench-all This is a wrapper to run `bench` for all pallets. @@ -174,6 +204,12 @@ Posible combinations based on the `benchmark` dropdown. - `people-westend` - Requires `Target Directory` to be `cumulus` +You can use the following [`gh cli`](https://cli.github.com/) inside the repo: + +```bash +gh workflow run command-bench-all.yml -f pr=1000 -f benchmark=pallet -f pallet=pallet_name -f target_dir=polkadot -f runtime=rococo +``` + ### Bench-overhead Run benchmarks overhead and commit back results to PR. @@ -193,6 +229,35 @@ Posible combinations based on the `benchmark` dropdown. - `asset-hub-westend` - Requires `Target directory` to be `cumulus` +You can use the following [`gh cli`](https://cli.github.com/) inside the repo: + +```bash +gh workflow run command-bench-overheard.yml -f pr=1000 -f benchmark=substrate -f runtime=rococo -f target_dir=substrate +``` + +### Sync + +Run sync and commit back results to PR. + +Posible combinations based on the `benchmark` dropdown. + +- `chain` + - Requires one of the following: + - `rococo` + - `westend` +- `sync-type` + - Requires one of the following: + - `warp` + - `full` + - `fast` + - `fast-unsafe` + +You can use the following [`gh cli`](https://cli.github.com/) inside the repo: + +```bash +gh workflow run command-sync.yml -f pr=1000 -f chain=rococo -f sync-type=full +``` + ## How to modify an action If you want to modify an action and test it, you can do by simply pushing your changes and then selecting your branch in the `Use worflow from` option. diff --git a/Cargo.lock b/Cargo.lock index c6841a929cf3..d8e961f2b679 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -766,9 +766,9 @@ dependencies = [ [[package]] name = "assert_cmd" -version = "2.0.12" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88903cb14723e4d4003335bb7f8a14f27691649105346a0f0957466c096adfe6" +checksum = "ed72493ac66d5804837f480ab3766c72bdfab91a65e565fc54fa9e42db0073a8" dependencies = [ "anstyle", "bstr", @@ -6053,15 +6053,26 @@ dependencies = [ "Inflector", "cfg-expr", "derive-syn-parse", + "docify", "expander", + "frame-support", "frame-support-procedural-tools", + "frame-system", "itertools 0.11.0", "macro_magic", + "parity-scale-codec", + "pretty_assertions", "proc-macro-warning 1.0.0", "proc-macro2 1.0.82", "quote 1.0.36", "regex", + "scale-info", + "sp-core", "sp-crypto-hashing", + "sp-io", + "sp-metadata-ir", + "sp-runtime", + "static_assertions", "syn 2.0.61", ] @@ -7686,9 +7697,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libflate" @@ -20919,6 +20930,7 @@ dependencies = [ "parity-scale-codec", "platforms", "polkadot-sdk", + "pretty_assertions", "rand", "regex", "sc-service-test", diff --git a/Cargo.toml b/Cargo.toml index 71e554336212..3886014dc2b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -594,7 +594,7 @@ ark-ed-on-bls12-381-bandersnatch-ext = { version = "0.4.1", default-features = f ark-scale = { version = "0.0.12", default-features = false } array-bytes = { version = "6.2.2", default-features = false } arrayvec = { version = "0.7.4" } -assert_cmd = { version = "2.0.10" } +assert_cmd = { version = "2.0.14" } assert_matches = { version = "1.5.0" } asset-hub-rococo-emulated-chain = { path = "cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo" } asset-hub-rococo-runtime = { path = "cumulus/parachains/runtimes/assets/asset-hub-rococo", default-features = false } @@ -815,7 +815,7 @@ kvdb-rocksdb = { version = "0.19.0" } kvdb-shared-tests = { version = "0.11.0" } landlock = { version = "0.3.0" } lazy_static = { version = "1.4.0" } -libc = { version = "0.2.153" } +libc = { version = "0.2.155" } libfuzzer-sys = { version = "0.4" } libp2p = { version = "0.52.4" } libp2p-identity = { version = "0.2.3" } diff --git a/polkadot/node/core/pvf/Cargo.toml b/polkadot/node/core/pvf/Cargo.toml index 7444f7927f56..d603af04bf06 100644 --- a/polkadot/node/core/pvf/Cargo.toml +++ b/polkadot/node/core/pvf/Cargo.toml @@ -60,7 +60,7 @@ test-parachain-adder = { workspace = true } test-parachain-halt = { workspace = true } [target.'cfg(target_os = "linux")'.dev-dependencies] -libc = "0.2.153" +libc = "0.2.155" procfs = "0.16.0" rusty-fork = "0.3.0" sc-sysinfo = { workspace = true, default-features = true } diff --git a/prdoc/pr_4936.prdoc b/prdoc/pr_4936.prdoc new file mode 100644 index 000000000000..f9b7ee506a7a --- /dev/null +++ b/prdoc/pr_4936.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Balances Pallet: Emit events when TI is updated in currency impl" + +doc: + - audience: Runtime Dev + description: | + Previously, in the Currency impl, the implementation of pallet_balances was not emitting any instances of Issued and Rescinded events, even though the Fungible equivalent was. This PR adds the Issued and Rescinded events in appropriate places in impl_currency along with tests. + +crates: +- name: pallet-balances + bump: patch diff --git a/substrate/bin/node/bench/src/import.rs b/substrate/bin/node/bench/src/import.rs index e340869dea02..0b972650ef5a 100644 --- a/substrate/bin/node/bench/src/import.rs +++ b/substrate/bin/node/bench/src/import.rs @@ -121,22 +121,23 @@ impl core::Benchmark for ImportBenchmark { .inspect_state(|| { match self.block_type { BlockType::RandomTransfersKeepAlive => { - // should be 8 per signed extrinsic + 1 per unsigned + // should be 9 per signed extrinsic + 1 per unsigned // we have 2 unsigned (timestamp and glutton bloat) while the rest are // signed in the block. - // those 8 events per signed are: + // those 9 events per signed are: // - transaction paid for the transaction payment // - withdraw (Balances::Withdraw) for charging the transaction fee // - new account (System::NewAccount) as we always transfer fund to // non-existent account // - endowed (Balances::Endowed) for this new account + // - issued (Balances::Issued) as total issuance is increased // - successful transfer (Event::Transfer) for this transfer operation // - 2x deposit (Balances::Deposit and Treasury::Deposit) for depositing // the transaction fee into the treasury // - extrinsic success assert_eq!( kitchensink_runtime::System::events().len(), - (self.block.extrinsics.len() - 2) * 8 + 2, + (self.block.extrinsics.len() - 2) * 9 + 2, ); }, BlockType::Noop => { diff --git a/substrate/bin/node/cli/Cargo.toml b/substrate/bin/node/cli/Cargo.toml index ab665f0792a4..1856487ac9ca 100644 --- a/substrate/bin/node/cli/Cargo.toml +++ b/substrate/bin/node/cli/Cargo.toml @@ -71,6 +71,7 @@ wait-timeout = { workspace = true } wat = { workspace = true } serde_json = { workspace = true, default-features = true } scale-info = { features = ["derive", "serde"], workspace = true, default-features = true } +pretty_assertions.workspace = true # These testing-only dependencies are not exported by the Polkadot-SDK crate: node-testing = { workspace = true } diff --git a/substrate/bin/node/cli/tests/basic.rs b/substrate/bin/node/cli/tests/basic.rs index b1f737ce399b..0a2e3fd25047 100644 --- a/substrate/bin/node/cli/tests/basic.rs +++ b/substrate/bin/node/cli/tests/basic.rs @@ -35,6 +35,7 @@ use kitchensink_runtime::{ }; use node_primitives::{Balance, Hash}; use node_testing::keyring::*; +use pretty_assertions::assert_eq; use wat; pub mod common; @@ -380,6 +381,13 @@ fn full_native_block_import_works() { }), topics: vec![], }, + EventRecord { + phase: Phase::ApplyExtrinsic(1), + event: RuntimeEvent::Balances(pallet_balances::Event::Rescinded { + amount: fees * 2 / 10, + }), + topics: vec![], + }, EventRecord { phase: Phase::ApplyExtrinsic(1), event: RuntimeEvent::TransactionPayment( @@ -465,6 +473,13 @@ fn full_native_block_import_works() { }), topics: vec![], }, + EventRecord { + phase: Phase::ApplyExtrinsic(1), + event: RuntimeEvent::Balances(pallet_balances::Event::Rescinded { + amount: fees - fees * 8 / 10, + }), + topics: vec![], + }, EventRecord { phase: Phase::ApplyExtrinsic(1), event: RuntimeEvent::TransactionPayment( @@ -515,6 +530,13 @@ fn full_native_block_import_works() { }), topics: vec![], }, + EventRecord { + phase: Phase::ApplyExtrinsic(2), + event: RuntimeEvent::Balances(pallet_balances::Event::Rescinded { + amount: fees - fees * 8 / 10, + }), + topics: vec![], + }, EventRecord { phase: Phase::ApplyExtrinsic(2), event: RuntimeEvent::TransactionPayment( diff --git a/substrate/client/consensus/beefy/src/communication/peers.rs b/substrate/client/consensus/beefy/src/communication/peers.rs index 2d801aceaa8a..929cc0c650b3 100644 --- a/substrate/client/consensus/beefy/src/communication/peers.rs +++ b/substrate/client/consensus/beefy/src/communication/peers.rs @@ -75,6 +75,11 @@ impl KnownPeers { pub fn contains(&self, peer: &PeerId) -> bool { self.live.contains_key(peer) } + + /// Number of peers in the set. + pub fn len(&self) -> usize { + self.live.len() + } } #[cfg(test)] diff --git a/substrate/client/consensus/beefy/src/communication/request_response/outgoing_requests_engine.rs b/substrate/client/consensus/beefy/src/communication/request_response/outgoing_requests_engine.rs index e127e5a89590..95ecf35557a5 100644 --- a/substrate/client/consensus/beefy/src/communication/request_response/outgoing_requests_engine.rs +++ b/substrate/client/consensus/beefy/src/communication/request_response/outgoing_requests_engine.rs @@ -249,9 +249,16 @@ impl OnDemandJustificationsEngine OnDemandJustificationsEngine Some(RequestFailure::NotConnected), - RequestResponseError::Rejected | RequestResponseError::Timeout => - Some(RequestFailure::Refused), + let status = match error { + RequestResponseError::NotConnected => + Some((RequestFailure::NotConnected, "not-connected")), + RequestResponseError::Rejected => Some((RequestFailure::Refused, "rejected")), + RequestResponseError::Timeout => + Some((RequestFailure::Network(OutboundFailure::Timeout), "timeout")), RequestResponseError::Canceled => { log::debug!( target: LOG_TARGET, @@ -387,7 +389,7 @@ impl RequestResponseProtocol { "{}: tried to send too large request to {peer:?} ({request_id:?})", self.protocol, ); - Some(RequestFailure::Refused) + Some((RequestFailure::Refused, "payload-too-large")) }, RequestResponseError::UnsupportedProtocol => match fallback_request { Some((request, protocol)) => match self.request_tx.get(&protocol) { @@ -426,15 +428,15 @@ impl RequestResponseProtocol { peer, ); - Some(RequestFailure::Refused) + Some((RequestFailure::Refused, "invalid-fallback-protocol")) }, }, - None => Some(RequestFailure::Refused), + None => Some((RequestFailure::Refused, "unsupported-protocol")), }, }; - if let Some(error) = error { - self.metrics.register_outbound_request_failure(error.to_string().as_ref()); + if let Some((error, reason)) = status { + self.metrics.register_outbound_request_failure(reason); let _ = tx.send(Err(error)); } } diff --git a/substrate/frame/balances/src/impl_currency.rs b/substrate/frame/balances/src/impl_currency.rs index 454aead1773f..049f0cc626c2 100644 --- a/substrate/frame/balances/src/impl_currency.rs +++ b/substrate/frame/balances/src/impl_currency.rs @@ -34,11 +34,12 @@ use frame_support::{ }; use frame_system::pallet_prelude::BlockNumberFor; pub use imbalances::{NegativeImbalance, PositiveImbalance}; +use sp_runtime::traits::Bounded; // wrapping these imbalances in a private module is necessary to ensure absolute privacy // of the inner member. mod imbalances { - use super::{result, Config, Imbalance, RuntimeDebug, Saturating, TryDrop, Zero}; + use super::*; use core::mem; use frame_support::traits::SameOrOther; @@ -199,14 +200,20 @@ mod imbalances { impl, I: 'static> Drop for PositiveImbalance { /// Basic drop handler will just square up the total issuance. fn drop(&mut self) { - >::mutate(|v| *v = v.saturating_add(self.0)); + if !self.0.is_zero() { + >::mutate(|v| *v = v.saturating_add(self.0)); + Pallet::::deposit_event(Event::::Issued { amount: self.0 }); + } } } impl, I: 'static> Drop for NegativeImbalance { /// Basic drop handler will just square up the total issuance. fn drop(&mut self) { - >::mutate(|v| *v = v.saturating_sub(self.0)); + if !self.0.is_zero() { + >::mutate(|v| *v = v.saturating_sub(self.0)); + Pallet::::deposit_event(Event::::Rescinded { amount: self.0 }); + } } } } @@ -263,6 +270,8 @@ where Zero::zero() }); }); + + Pallet::::deposit_event(Event::::Rescinded { amount }); PositiveImbalance::new(amount) } @@ -279,6 +288,8 @@ where Self::Balance::max_value() }) }); + + Pallet::::deposit_event(Event::::Issued { amount }); NegativeImbalance::new(amount) } diff --git a/substrate/frame/balances/src/lib.rs b/substrate/frame/balances/src/lib.rs index 0aaf618b303f..ddca685aa012 100644 --- a/substrate/frame/balances/src/lib.rs +++ b/substrate/frame/balances/src/lib.rs @@ -175,8 +175,8 @@ pub use impl_currency::{NegativeImbalance, PositiveImbalance}; use scale_info::TypeInfo; use sp_runtime::{ traits::{ - AtLeast32BitUnsigned, Bounded, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, - Saturating, StaticLookup, Zero, + AtLeast32BitUnsigned, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Saturating, + StaticLookup, Zero, }, ArithmeticError, DispatchError, FixedPointOperand, Perbill, RuntimeDebug, TokenError, }; diff --git a/substrate/frame/balances/src/tests/currency_tests.rs b/substrate/frame/balances/src/tests/currency_tests.rs index 9ad4aca64406..fb69368a6216 100644 --- a/substrate/frame/balances/src/tests/currency_tests.rs +++ b/substrate/frame/balances/src/tests/currency_tests.rs @@ -399,10 +399,13 @@ fn reward_should_work() { ExtBuilder::default().monied(true).build_and_execute_with(|| { assert_eq!(Balances::total_balance(&1), 10); assert_ok!(Balances::deposit_into_existing(&1, 10).map(drop)); - System::assert_last_event(RuntimeEvent::Balances(crate::Event::Deposit { - who: 1, - amount: 10, - })); + assert_eq!( + events(), + [ + RuntimeEvent::Balances(crate::Event::Deposit { who: 1, amount: 10 }), + RuntimeEvent::Balances(crate::Event::Issued { amount: 10 }), + ] + ); assert_eq!(Balances::total_balance(&1), 20); assert_eq!(Balances::total_issuance(), 120); }); @@ -480,7 +483,7 @@ fn withdrawing_balance_should_work() { let _ = Balances::deposit_creating(&2, 111); let _ = Balances::withdraw(&2, 11, WithdrawReasons::TRANSFER, ExistenceRequirement::KeepAlive); - System::assert_last_event(RuntimeEvent::Balances(crate::Event::Withdraw { + System::assert_has_event(RuntimeEvent::Balances(crate::Event::Withdraw { who: 2, amount: 11, })); @@ -889,6 +892,7 @@ fn emit_events_with_existential_deposit() { [ RuntimeEvent::System(system::Event::NewAccount { account: 1 }), RuntimeEvent::Balances(crate::Event::Endowed { account: 1, free_balance: 100 }), + RuntimeEvent::Balances(crate::Event::Issued { amount: 100 }), RuntimeEvent::Balances(crate::Event::BalanceSet { who: 1, free: 100 }), ] ); @@ -902,6 +906,7 @@ fn emit_events_with_existential_deposit() { RuntimeEvent::System(system::Event::KilledAccount { account: 1 }), RuntimeEvent::Balances(crate::Event::DustLost { account: 1, amount: 99 }), RuntimeEvent::Balances(crate::Event::Slashed { who: 1, amount: 1 }), + RuntimeEvent::Balances(crate::Event::Rescinded { amount: 1 }), ] ); }); @@ -918,6 +923,7 @@ fn emit_events_with_no_existential_deposit_suicide() { RuntimeEvent::Balances(crate::Event::BalanceSet { who: 1, free: 100 }), RuntimeEvent::System(system::Event::NewAccount { account: 1 }), RuntimeEvent::Balances(crate::Event::Endowed { account: 1, free_balance: 100 }), + RuntimeEvent::Balances(crate::Event::Issued { amount: 100 }), ] ); @@ -929,6 +935,7 @@ fn emit_events_with_no_existential_deposit_suicide() { [ RuntimeEvent::System(system::Event::KilledAccount { account: 1 }), RuntimeEvent::Balances(crate::Event::Slashed { who: 1, amount: 100 }), + RuntimeEvent::Balances(crate::Event::Rescinded { amount: 100 }), ] ); }); @@ -954,7 +961,7 @@ fn slash_full_works() { assert_eq!(Balances::slash(&1, 1_000), (NegativeImbalance::new(1000), 0)); // Account is still alive assert!(!System::account_exists(&1)); - System::assert_last_event(RuntimeEvent::Balances(crate::Event::Slashed { + System::assert_has_event(RuntimeEvent::Balances(crate::Event::Slashed { who: 1, amount: 1000, })); @@ -969,7 +976,7 @@ fn slash_partial_works() { assert_eq!(Balances::slash(&1, 900), (NegativeImbalance::new(900), 0)); // Account is still alive assert!(System::account_exists(&1)); - System::assert_last_event(RuntimeEvent::Balances(crate::Event::Slashed { + System::assert_has_event(RuntimeEvent::Balances(crate::Event::Slashed { who: 1, amount: 900, })); @@ -983,7 +990,7 @@ fn slash_dusting_works() { // Slashed completed in full assert_eq!(Balances::slash(&1, 950), (NegativeImbalance::new(950), 0)); assert!(!System::account_exists(&1)); - System::assert_last_event(RuntimeEvent::Balances(crate::Event::Slashed { + System::assert_has_event(RuntimeEvent::Balances(crate::Event::Slashed { who: 1, amount: 950, })); @@ -998,7 +1005,7 @@ fn slash_does_not_take_from_reserve() { // Slashed completed in full assert_eq!(Balances::slash(&1, 900), (NegativeImbalance::new(800), 100)); assert_eq!(Balances::reserved_balance(&1), 100); - System::assert_last_event(RuntimeEvent::Balances(crate::Event::Slashed { + System::assert_has_event(RuntimeEvent::Balances(crate::Event::Slashed { who: 1, amount: 800, })); @@ -1399,6 +1406,7 @@ fn self_transfer_noop() { Event::Deposit { who: 1, amount: 100 }.into(), SysEvent::NewAccount { account: 1 }.into(), Event::Endowed { account: 1, free_balance: 100 }.into(), + Event::Issued { amount: 100 }.into(), ] ); assert_eq!(Balances::free_balance(1), 100); diff --git a/substrate/frame/balances/src/tests/reentrancy_tests.rs b/substrate/frame/balances/src/tests/reentrancy_tests.rs index 717f04978577..0b66f2c93e3a 100644 --- a/substrate/frame/balances/src/tests/reentrancy_tests.rs +++ b/substrate/frame/balances/src/tests/reentrancy_tests.rs @@ -52,7 +52,7 @@ fn transfer_dust_removal_tst1_should_work() { assert_eq!(Balances::free_balance(&1), 1050); // Verify the events - assert_eq!(System::events().len(), 12); + assert_eq!(System::events().len(), 14); System::assert_has_event(RuntimeEvent::Balances(crate::Event::Transfer { from: 2, @@ -93,7 +93,7 @@ fn transfer_dust_removal_tst2_should_work() { assert_eq!(Balances::free_balance(&1), 1500); // Verify the events - assert_eq!(System::events().len(), 10); + assert_eq!(System::events().len(), 12); System::assert_has_event(RuntimeEvent::Balances(crate::Event::Transfer { from: 2, @@ -139,7 +139,7 @@ fn repatriating_reserved_balance_dust_removal_should_work() { assert_eq!(Balances::free_balance(1), 1500); // Verify the events - assert_eq!(System::events().len(), 10); + assert_eq!(System::events().len(), 12); System::assert_has_event(RuntimeEvent::Balances(crate::Event::Transfer { from: 2, @@ -167,6 +167,7 @@ fn emit_events_with_no_existential_deposit_suicide_with_dust() { [ RuntimeEvent::System(system::Event::NewAccount { account: 1 }), RuntimeEvent::Balances(crate::Event::Endowed { account: 1, free_balance: 100 }), + RuntimeEvent::Balances(crate::Event::Issued { amount: 100 }), RuntimeEvent::Balances(crate::Event::BalanceSet { who: 1, free: 100 }), ] ); diff --git a/substrate/frame/support/procedural/Cargo.toml b/substrate/frame/support/procedural/Cargo.toml index fbb4da0177a4..aa0665a2250f 100644 --- a/substrate/frame/support/procedural/Cargo.toml +++ b/substrate/frame/support/procedural/Cargo.toml @@ -19,6 +19,7 @@ proc-macro = true [dependencies] derive-syn-parse = { workspace = true } +docify = { workspace = true } Inflector = { workspace = true } cfg-expr = { workspace = true } itertools = { workspace = true } @@ -32,14 +33,42 @@ expander = { workspace = true } sp-crypto-hashing = { workspace = true } [dev-dependencies] +codec = { features = [ + "derive", + "max-encoded-len", +], workspace = true } regex = { workspace = true } +sp-metadata-ir = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { features = [ + "serde", +], workspace = true } +frame-system = { workspace = true } +frame-support = { workspace = true } +pretty_assertions = { workspace = true } +static_assertions = { workspace = true } [features] default = ["std"] -std = ["sp-crypto-hashing/std"] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "sp-core/std", + "sp-crypto-hashing/std", + "sp-io/std", + "sp-metadata-ir/std", + "sp-runtime/std", +] no-metadata-docs = [] experimental = [] # Generate impl-trait for tuples with the given number of tuples. Will be needed as the number of # pallets in a runtime grows. Does increase the compile time! tuples-96 = [] tuples-128 = [] + +[[example]] +name = "proc_main" diff --git a/substrate/frame/support/src/tests/inject_runtime_type.rs b/substrate/frame/support/procedural/examples/proc_main/inject_runtime_type.rs similarity index 97% rename from substrate/frame/support/src/tests/inject_runtime_type.rs rename to substrate/frame/support/procedural/examples/proc_main/inject_runtime_type.rs index 429a743d3b7b..cd5660f2bda7 100644 --- a/substrate/frame/support/src/tests/inject_runtime_type.rs +++ b/substrate/frame/support/procedural/examples/proc_main/inject_runtime_type.rs @@ -15,8 +15,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +#[cfg(test)] use super::{Config, Runtime}; +#[cfg(test)] use crate::{derive_impl, pallet_prelude::inject_runtime_type}; +#[cfg(test)] use static_assertions::assert_type_eq_all; #[docify::export] diff --git a/substrate/frame/support/procedural/examples/proc_main/main.rs b/substrate/frame/support/procedural/examples/proc_main/main.rs new file mode 100644 index 000000000000..4bdfc76dd92f --- /dev/null +++ b/substrate/frame/support/procedural/examples/proc_main/main.rs @@ -0,0 +1,737 @@ +// 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 frame_support::*; +use frame_support_procedural::import_section; +#[cfg(test)] +use sp_io::{MultiRemovalResults, TestExternalities}; +#[cfg(test)] +use sp_metadata_ir::{ + PalletStorageMetadataIR, StorageEntryMetadataIR, StorageEntryModifierIR, StorageEntryTypeIR, + StorageHasherIR, +}; +#[cfg(test)] +use sp_runtime::BuildStorage; +use sp_runtime::{generic, traits::BlakeTwo256}; + +pub use self::frame_system::{pallet_prelude::*, Config, Pallet}; + +mod inject_runtime_type; +mod runtime; +mod tasks; + +#[import_section(tasks::tasks_example)] +#[pallet] +pub mod frame_system { + #[allow(unused)] + use super::{frame_system, frame_system::pallet_prelude::*}; + pub use crate::dispatch::RawOrigin; + use crate::{pallet_prelude::*, traits::tasks::Task as TaskTrait}; + + pub mod config_preludes { + use super::{inject_runtime_type, DefaultConfig}; + pub struct TestDefaultConfig; + + #[crate::register_default_impl(TestDefaultConfig)] + impl DefaultConfig for TestDefaultConfig { + type AccountId = u64; + type BaseCallFilter = frame_support::traits::Everything; + #[inject_runtime_type] + type RuntimeOrigin = (); + #[inject_runtime_type] + type RuntimeCall = (); + #[inject_runtime_type] + type PalletInfo = (); + #[inject_runtime_type] + type RuntimeTask = (); + type DbWeight = (); + } + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config(with_default)] + #[pallet::disable_frame_system_supertrait_check] + pub trait Config: 'static { + #[pallet::no_default] + type Block: Parameter + sp_runtime::traits::Block; + type AccountId; + #[pallet::no_default_bounds] + type BaseCallFilter: crate::traits::Contains; + #[pallet::no_default_bounds] + type RuntimeOrigin; + #[pallet::no_default_bounds] + type RuntimeCall; + #[pallet::no_default_bounds] + type RuntimeTask: crate::traits::tasks::Task; + #[pallet::no_default_bounds] + type PalletInfo: crate::traits::PalletInfo; + type DbWeight: Get; + } + + #[pallet::error] + pub enum Error { + /// Required by construct_runtime + CallFiltered, + /// Used in tasks example. + NotFound, + /// The specified [`Task`] is not valid. + InvalidTask, + /// The specified [`Task`] failed during execution. + FailedTask, + } + + #[pallet::origin] + pub type Origin = RawOrigin<::AccountId>; + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(task.weight())] + pub fn do_task(_origin: OriginFor, task: T::RuntimeTask) -> DispatchResultWithPostInfo { + if !task.is_valid() { + return Err(Error::::InvalidTask.into()) + } + + if let Err(_err) = task.run() { + return Err(Error::::FailedTask.into()) + } + + Ok(().into()) + } + } + + #[pallet::storage] + pub type Data = StorageMap<_, Twox64Concat, u32, u64, ValueQuery>; + + #[pallet::storage] + pub type OptionLinkedMap = StorageMap<_, Blake2_128Concat, u32, u32, OptionQuery>; + + #[pallet::storage] + pub type GenericData = + StorageMap<_, Identity, BlockNumberFor, BlockNumberFor, ValueQuery>; + + #[pallet::storage] + pub type GenericData2 = + StorageMap<_, Blake2_128Concat, BlockNumberFor, BlockNumberFor, OptionQuery>; + + #[pallet::storage] + pub type DataDM = + StorageDoubleMap<_, Twox64Concat, u32, Blake2_128Concat, u32, u64, ValueQuery>; + + #[pallet::storage] + pub type GenericDataDM = StorageDoubleMap< + _, + Blake2_128Concat, + BlockNumberFor, + Identity, + BlockNumberFor, + BlockNumberFor, + ValueQuery, + >; + + #[pallet::storage] + pub type GenericData2DM = StorageDoubleMap< + _, + Blake2_128Concat, + BlockNumberFor, + Twox64Concat, + BlockNumberFor, + BlockNumberFor, + OptionQuery, + >; + + #[pallet::storage] + #[pallet::unbounded] + pub type AppendableDM = StorageDoubleMap< + _, + Blake2_128Concat, + u32, + Blake2_128Concat, + BlockNumberFor, + Vec, + ValueQuery, + >; + + #[pallet::genesis_config] + pub struct GenesisConfig { + pub data: Vec<(u32, u64)>, + pub test_config: Vec<(u32, u32, u64)>, + #[serde(skip)] + pub _config: core::marker::PhantomData, + } + + impl Default for GenesisConfig { + fn default() -> Self { + Self { + _config: Default::default(), + data: vec![(15u32, 42u64)], + test_config: vec![(15u32, 16u32, 42u64)], + } + } + } + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + for (k, v) in &self.data { + >::insert(k, v); + } + for (k1, k2, v) in &self.test_config { + >::insert(k1, k2, v); + } + } + } + + /// Some running total. + #[pallet::storage] + pub type Total = StorageValue<_, (u32, u32), ValueQuery>; + + /// Numbers to be added into the total. + #[pallet::storage] + pub type Numbers = StorageMap<_, Twox64Concat, u32, u32, OptionQuery>; + + pub mod pallet_prelude { + pub type OriginFor = ::RuntimeOrigin; + + pub type HeaderFor = + <::Block as sp_runtime::traits::HeaderProvider>::HeaderT; + + pub type BlockNumberFor = as sp_runtime::traits::Header>::Number; + } +} + +type BlockNumber = u32; +type AccountId = u32; +type Header = generic::Header; +type UncheckedExtrinsic = generic::UncheckedExtrinsic; +type Block = generic::Block; + +#[crate::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 = self::frame_system; +} + +#[crate::derive_impl(self::frame_system::config_preludes::TestDefaultConfig as self::frame_system::DefaultConfig)] +impl Config for Runtime { + type Block = Block; + type AccountId = AccountId; +} + +#[cfg(test)] +fn new_test_ext() -> TestExternalities { + RuntimeGenesisConfig::default().build_storage().unwrap().into() +} + +#[cfg(test)] +trait Sorted { + fn sorted(self) -> Self; +} + +#[cfg(test)] +impl Sorted for Vec { + fn sorted(mut self) -> Self { + self.sort(); + self + } +} + +#[test] +fn map_issue_3318() { + new_test_ext().execute_with(|| { + type OptionLinkedMap = self::frame_system::OptionLinkedMap; + + OptionLinkedMap::insert(1, 1); + assert_eq!(OptionLinkedMap::get(1), Some(1)); + OptionLinkedMap::insert(1, 2); + assert_eq!(OptionLinkedMap::get(1), Some(2)); + }); +} + +#[test] +fn map_swap_works() { + new_test_ext().execute_with(|| { + type OptionLinkedMap = self::frame_system::OptionLinkedMap; + + OptionLinkedMap::insert(0, 0); + OptionLinkedMap::insert(1, 1); + OptionLinkedMap::insert(2, 2); + OptionLinkedMap::insert(3, 3); + + let collect = || OptionLinkedMap::iter().collect::>().sorted(); + assert_eq!(collect(), vec![(0, 0), (1, 1), (2, 2), (3, 3)]); + + // Two existing + OptionLinkedMap::swap(1, 2); + assert_eq!(collect(), vec![(0, 0), (1, 2), (2, 1), (3, 3)]); + + // Back to normal + OptionLinkedMap::swap(2, 1); + assert_eq!(collect(), vec![(0, 0), (1, 1), (2, 2), (3, 3)]); + + // Left existing + OptionLinkedMap::swap(2, 5); + assert_eq!(collect(), vec![(0, 0), (1, 1), (3, 3), (5, 2)]); + + // Right existing + OptionLinkedMap::swap(5, 2); + assert_eq!(collect(), vec![(0, 0), (1, 1), (2, 2), (3, 3)]); + }); +} + +#[test] +fn double_map_swap_works() { + new_test_ext().execute_with(|| { + type DataDM = self::frame_system::DataDM; + + DataDM::insert(0, 1, 1); + DataDM::insert(1, 0, 2); + DataDM::insert(1, 1, 3); + + let get_all = || { + vec![ + DataDM::get(0, 1), + DataDM::get(1, 0), + DataDM::get(1, 1), + DataDM::get(2, 0), + DataDM::get(2, 1), + ] + }; + assert_eq!(get_all(), vec![1, 2, 3, 0, 0]); + + // Two existing + DataDM::swap(0, 1, 1, 0); + assert_eq!(get_all(), vec![2, 1, 3, 0, 0]); + + // Left existing + DataDM::swap(1, 0, 2, 0); + assert_eq!(get_all(), vec![2, 0, 3, 1, 0]); + + // Right existing + DataDM::swap(2, 1, 1, 1); + assert_eq!(get_all(), vec![2, 0, 0, 1, 3]); + }); +} + +#[test] +fn map_basic_insert_remove_should_work() { + new_test_ext().execute_with(|| { + type Map = self::frame_system::Data; + + // initialized during genesis + assert_eq!(Map::get(&15u32), 42u64); + + // get / insert / take + let key = 17u32; + assert_eq!(Map::get(&key), 0u64); + Map::insert(key, 4u64); + assert_eq!(Map::get(&key), 4u64); + assert_eq!(Map::take(&key), 4u64); + assert_eq!(Map::get(&key), 0u64); + + // mutate + Map::mutate(&key, |val| { + *val = 15; + }); + assert_eq!(Map::get(&key), 15u64); + + // remove + Map::remove(&key); + assert_eq!(Map::get(&key), 0u64); + }); +} + +#[test] +fn map_iteration_should_work() { + new_test_ext().execute_with(|| { + type Map = self::frame_system::Data; + + assert_eq!(Map::iter().collect::>().sorted(), vec![(15, 42)]); + // insert / remove + let key = 17u32; + Map::insert(key, 4u64); + assert_eq!(Map::iter().collect::>().sorted(), vec![(15, 42), (key, 4)]); + assert_eq!(Map::take(&15), 42u64); + assert_eq!(Map::take(&key), 4u64); + assert_eq!(Map::iter().collect::>().sorted(), vec![]); + + // Add couple of more elements + Map::insert(key, 42u64); + assert_eq!(Map::iter().collect::>().sorted(), vec![(key, 42)]); + Map::insert(key + 1, 43u64); + assert_eq!(Map::iter().collect::>().sorted(), vec![(key, 42), (key + 1, 43)]); + + // mutate + let key = key + 2; + Map::mutate(&key, |val| { + *val = 15; + }); + assert_eq!( + Map::iter().collect::>().sorted(), + vec![(key - 2, 42), (key - 1, 43), (key, 15)] + ); + Map::mutate(&key, |val| { + *val = 17; + }); + assert_eq!( + Map::iter().collect::>().sorted(), + vec![(key - 2, 42), (key - 1, 43), (key, 17)] + ); + + // remove first + Map::remove(&key); + assert_eq!(Map::iter().collect::>().sorted(), vec![(key - 2, 42), (key - 1, 43)]); + + // remove last from the list + Map::remove(&(key - 2)); + assert_eq!(Map::iter().collect::>().sorted(), vec![(key - 1, 43)]); + + // remove the last element + Map::remove(&(key - 1)); + assert_eq!(Map::iter().collect::>().sorted(), vec![]); + }); +} + +#[test] +fn double_map_basic_insert_remove_remove_prefix_with_commit_should_work() { + let key1 = 17u32; + let key2 = 18u32; + type DoubleMap = self::frame_system::DataDM; + let mut e = new_test_ext(); + e.execute_with(|| { + // initialized during genesis + assert_eq!(DoubleMap::get(&15u32, &16u32), 42u64); + + // get / insert / take + assert_eq!(DoubleMap::get(&key1, &key2), 0u64); + DoubleMap::insert(&key1, &key2, &4u64); + assert_eq!(DoubleMap::get(&key1, &key2), 4u64); + assert_eq!(DoubleMap::take(&key1, &key2), 4u64); + assert_eq!(DoubleMap::get(&key1, &key2), 0u64); + + // mutate + DoubleMap::mutate(&key1, &key2, |val| *val = 15); + assert_eq!(DoubleMap::get(&key1, &key2), 15u64); + + // remove + DoubleMap::remove(&key1, &key2); + assert_eq!(DoubleMap::get(&key1, &key2), 0u64); + + // remove prefix + DoubleMap::insert(&key1, &key2, &4u64); + DoubleMap::insert(&key1, &(key2 + 1), &4u64); + DoubleMap::insert(&(key1 + 1), &key2, &4u64); + DoubleMap::insert(&(key1 + 1), &(key2 + 1), &4u64); + }); + e.commit_all().unwrap(); + e.execute_with(|| { + assert!(matches!( + DoubleMap::clear_prefix(&key1, u32::max_value(), None), + MultiRemovalResults { maybe_cursor: None, backend: 2, unique: 2, loops: 2 } + )); + assert_eq!(DoubleMap::get(&key1, &key2), 0u64); + assert_eq!(DoubleMap::get(&key1, &(key2 + 1)), 0u64); + assert_eq!(DoubleMap::get(&(key1 + 1), &key2), 4u64); + assert_eq!(DoubleMap::get(&(key1 + 1), &(key2 + 1)), 4u64); + }); +} + +#[test] +fn double_map_basic_insert_remove_remove_prefix_should_work() { + new_test_ext().execute_with(|| { + let key1 = 17u32; + let key2 = 18u32; + type DoubleMap = self::frame_system::DataDM; + + // initialized during genesis + assert_eq!(DoubleMap::get(&15u32, &16u32), 42u64); + + // get / insert / take + assert_eq!(DoubleMap::get(&key1, &key2), 0u64); + DoubleMap::insert(&key1, &key2, &4u64); + assert_eq!(DoubleMap::get(&key1, &key2), 4u64); + assert_eq!(DoubleMap::take(&key1, &key2), 4u64); + assert_eq!(DoubleMap::get(&key1, &key2), 0u64); + + // mutate + DoubleMap::mutate(&key1, &key2, |val| *val = 15); + assert_eq!(DoubleMap::get(&key1, &key2), 15u64); + + // remove + DoubleMap::remove(&key1, &key2); + assert_eq!(DoubleMap::get(&key1, &key2), 0u64); + + // remove prefix + DoubleMap::insert(&key1, &key2, &4u64); + DoubleMap::insert(&key1, &(key2 + 1), &4u64); + DoubleMap::insert(&(key1 + 1), &key2, &4u64); + DoubleMap::insert(&(key1 + 1), &(key2 + 1), &4u64); + // all in overlay + assert!(matches!( + DoubleMap::clear_prefix(&key1, u32::max_value(), None), + MultiRemovalResults { maybe_cursor: None, backend: 0, unique: 0, loops: 0 } + )); + // Note this is the incorrect answer (for now), since we are using v2 of + // `clear_prefix`. + // When we switch to v3, then this will become: + // MultiRemovalResults:: { maybe_cursor: None, backend: 0, unique: 2, loops: 2 }, + assert!(matches!( + DoubleMap::clear_prefix(&key1, u32::max_value(), None), + MultiRemovalResults { maybe_cursor: None, backend: 0, unique: 0, loops: 0 } + )); + assert_eq!(DoubleMap::get(&key1, &key2), 0u64); + assert_eq!(DoubleMap::get(&key1, &(key2 + 1)), 0u64); + assert_eq!(DoubleMap::get(&(key1 + 1), &key2), 4u64); + assert_eq!(DoubleMap::get(&(key1 + 1), &(key2 + 1)), 4u64); + }); +} + +#[test] +fn double_map_append_should_work() { + new_test_ext().execute_with(|| { + type DoubleMap = self::frame_system::AppendableDM; + + let key1 = 17u32; + let key2 = 18u32; + + DoubleMap::insert(&key1, &key2, &vec![1]); + DoubleMap::append(&key1, &key2, 2); + assert_eq!(DoubleMap::get(&key1, &key2), &[1, 2]); + }); +} + +#[test] +fn double_map_mutate_exists_should_work() { + new_test_ext().execute_with(|| { + type DoubleMap = self::frame_system::DataDM; + + let (key1, key2) = (11, 13); + + // mutated + DoubleMap::mutate_exists(key1, key2, |v| *v = Some(1)); + assert_eq!(DoubleMap::get(&key1, key2), 1); + + // removed if mutated to `None` + DoubleMap::mutate_exists(key1, key2, |v| *v = None); + assert!(!DoubleMap::contains_key(&key1, key2)); + }); +} + +#[test] +fn double_map_try_mutate_exists_should_work() { + new_test_ext().execute_with(|| { + type DoubleMap = self::frame_system::DataDM; + type TestResult = Result<(), &'static str>; + + let (key1, key2) = (11, 13); + + // mutated if `Ok` + assert_ok!(DoubleMap::try_mutate_exists(key1, key2, |v| -> TestResult { + *v = Some(1); + Ok(()) + })); + assert_eq!(DoubleMap::get(&key1, key2), 1); + + // no-op if `Err` + assert_noop!( + DoubleMap::try_mutate_exists(key1, key2, |v| -> TestResult { + *v = Some(2); + Err("nah") + }), + "nah" + ); + + // removed if mutated to`None` + assert_ok!(DoubleMap::try_mutate_exists(key1, key2, |v| -> TestResult { + *v = None; + Ok(()) + })); + assert!(!DoubleMap::contains_key(&key1, key2)); + }); +} + +#[cfg(test)] +fn expected_metadata() -> PalletStorageMetadataIR { + PalletStorageMetadataIR { + prefix: "System", + entries: vec![ + StorageEntryMetadataIR { + name: "Data", + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Map { + hashers: vec![StorageHasherIR::Twox64Concat], + key: scale_info::meta_type::(), + value: scale_info::meta_type::(), + }, + default: vec![0, 0, 0, 0, 0, 0, 0, 0], + docs: vec![], + }, + StorageEntryMetadataIR { + name: "OptionLinkedMap", + modifier: StorageEntryModifierIR::Optional, + ty: StorageEntryTypeIR::Map { + hashers: vec![StorageHasherIR::Blake2_128Concat], + key: scale_info::meta_type::(), + value: scale_info::meta_type::(), + }, + default: vec![0], + docs: vec![], + }, + StorageEntryMetadataIR { + name: "GenericData", + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Map { + hashers: vec![StorageHasherIR::Identity], + key: scale_info::meta_type::(), + value: scale_info::meta_type::(), + }, + default: vec![0, 0, 0, 0], + docs: vec![], + }, + StorageEntryMetadataIR { + name: "GenericData2", + modifier: StorageEntryModifierIR::Optional, + ty: StorageEntryTypeIR::Map { + hashers: vec![StorageHasherIR::Blake2_128Concat], + key: scale_info::meta_type::(), + value: scale_info::meta_type::(), + }, + default: vec![0], + docs: vec![], + }, + StorageEntryMetadataIR { + name: "DataDM", + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Map { + hashers: vec![StorageHasherIR::Twox64Concat, StorageHasherIR::Blake2_128Concat], + key: scale_info::meta_type::<(u32, u32)>(), + value: scale_info::meta_type::(), + }, + default: vec![0, 0, 0, 0, 0, 0, 0, 0], + docs: vec![], + }, + StorageEntryMetadataIR { + name: "GenericDataDM", + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Map { + hashers: vec![StorageHasherIR::Blake2_128Concat, StorageHasherIR::Identity], + key: scale_info::meta_type::<(u32, u32)>(), + value: scale_info::meta_type::(), + }, + default: vec![0, 0, 0, 0], + docs: vec![], + }, + StorageEntryMetadataIR { + name: "GenericData2DM", + modifier: StorageEntryModifierIR::Optional, + ty: StorageEntryTypeIR::Map { + hashers: vec![StorageHasherIR::Blake2_128Concat, StorageHasherIR::Twox64Concat], + key: scale_info::meta_type::<(u32, u32)>(), + value: scale_info::meta_type::(), + }, + default: vec![0], + docs: vec![], + }, + StorageEntryMetadataIR { + name: "AppendableDM", + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Map { + hashers: vec![ + StorageHasherIR::Blake2_128Concat, + StorageHasherIR::Blake2_128Concat, + ], + key: scale_info::meta_type::<(u32, u32)>(), + value: scale_info::meta_type::>(), + }, + default: vec![0], + docs: vec![], + }, + StorageEntryMetadataIR { + name: "Total", + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Plain(scale_info::meta_type::<(u32, u32)>()), + default: vec![0, 0, 0, 0, 0, 0, 0, 0], + docs: vec![" Some running total."], + }, + StorageEntryMetadataIR { + name: "Numbers", + modifier: StorageEntryModifierIR::Optional, + ty: StorageEntryTypeIR::Map { + hashers: vec![StorageHasherIR::Twox64Concat], + key: scale_info::meta_type::(), + value: scale_info::meta_type::(), + }, + default: vec![0], + docs: vec![" Numbers to be added into the total."], + }, + ], + } +} + +#[test] +fn store_metadata() { + let metadata = Pallet::::storage_metadata(); + pretty_assertions::assert_eq!(expected_metadata(), metadata); +} + +parameter_types! { + storage StorageParameter: u64 = 10; +} + +#[test] +fn check_storage_parameter_type_works() { + TestExternalities::default().execute_with(|| { + assert_eq!(sp_io::hashing::twox_128(b":StorageParameter:"), StorageParameter::key()); + + assert_eq!(10, StorageParameter::get()); + + StorageParameter::set(&300); + assert_eq!(300, StorageParameter::get()); + }) +} + +#[test] +fn derive_partial_eq_no_bound_core_mod() { + mod core {} + + #[derive( + crate::PartialEqNoBound, + crate::CloneNoBound, + crate::DebugNoBound, + crate::DefaultNoBound, + crate::EqNoBound, + )] + struct Test; +} + +fn main() {} diff --git a/substrate/frame/support/src/tests/runtime.rs b/substrate/frame/support/procedural/examples/proc_main/runtime.rs similarity index 100% rename from substrate/frame/support/src/tests/runtime.rs rename to substrate/frame/support/procedural/examples/proc_main/runtime.rs diff --git a/substrate/frame/support/src/tests/tasks.rs b/substrate/frame/support/procedural/examples/proc_main/tasks.rs similarity index 91% rename from substrate/frame/support/src/tests/tasks.rs rename to substrate/frame/support/procedural/examples/proc_main/tasks.rs index 2774c1300757..326c6e5bcbc5 100644 --- a/substrate/frame/support/src/tests/tasks.rs +++ b/substrate/frame/support/procedural/examples/proc_main/tasks.rs @@ -15,12 +15,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{ +#[cfg(test)] +use super::{ assert_ok, - tests::{ - frame_system::{Numbers, Total}, - new_test_ext, Runtime, RuntimeOrigin, RuntimeTask, System, - }, + frame_system::{Numbers, Total}, + Runtime, RuntimeOrigin, RuntimeTask, System, }; use frame_support_procedural::pallet_section; @@ -48,7 +47,7 @@ mod tasks_example { #[docify::export] #[test] fn tasks_work() { - new_test_ext().execute_with(|| { + super::new_test_ext().execute_with(|| { Numbers::::insert(0, 1); let task = RuntimeTask::System(super::frame_system::Task::::AddNumberIntoTotal { diff --git a/substrate/frame/support/procedural/src/lib.rs b/substrate/frame/support/procedural/src/lib.rs index 51e5657a2e8b..ef99faee86ae 100644 --- a/substrate/frame/support/procedural/src/lib.rs +++ b/substrate/frame/support/procedural/src/lib.rs @@ -779,11 +779,20 @@ pub fn register_default_impl(attrs: TokenStream, tokens: TokenStream) -> TokenSt } } -/// -/// --- -/// -/// Documentation for this macro can be found at -/// `frame_support::pallet_prelude::inject_runtime_type`. +/// The optional attribute `#[inject_runtime_type]` can be attached to `RuntimeCall`, +/// `RuntimeEvent`, `RuntimeOrigin` or `PalletInfo` in an impl statement that has +/// `#[register_default_impl]` attached to indicate that this item is generated by +/// `construct_runtime`. +/// +/// Attaching this attribute to such an item ensures that the combined impl generated via +/// [`#[derive_impl(..)]`](macro@derive_impl) will use the correct type auto-generated by +/// `construct_runtime!`. +#[doc = docify::embed!("examples/proc_main/inject_runtime_type.rs", derive_impl_works_with_runtime_type_injection)] +/// +/// However, if `no_aggregated_types` is specified while using +/// [`#[derive_impl(..)]`](macro@derive_impl), then these items are attached verbatim to the +/// combined impl. +#[doc = docify::embed!("examples/proc_main/inject_runtime_type.rs", derive_impl_works_with_no_aggregated_types)] #[proc_macro_attribute] pub fn inject_runtime_type(_: TokenStream, tokens: TokenStream) -> TokenStream { let item = tokens.clone(); @@ -1073,51 +1082,87 @@ pub fn composite_enum(_: TokenStream, _: TokenStream) -> TokenStream { pallet_macro_stub() } +/// Allows you to define some service work that can be recognized by a script or an +/// off-chain worker. /// -/// --- +/// Such a script can then create and submit all such work items at any given time. /// -/// **Rust-Analyzer users**: See the documentation of the Rust item in -/// `frame_support::pallet_macros::tasks_experimental`. +/// These work items are defined as instances of the `Task` trait (found at +/// `frame_support::traits::Task`). [`pallet:tasks_experimental`](macro@tasks_experimental) when +/// attached to an `impl` block inside a pallet, will generate an enum `Task` whose variants +/// are mapped to functions inside this `impl` block. +/// +/// Each such function must have the following set of attributes: +/// +/// * [`pallet::task_list`](macro@task_list) +/// * [`pallet::task_condition`](macro@task_condition) +/// * [`pallet::task_weight`](macro@task_weight) +/// * [`pallet::task_index`](macro@task_index) +/// +/// All of such Tasks are then aggregated into a `RuntimeTask` by +/// [`construct_runtime`](macro@construct_runtime). +/// +/// Finally, the `RuntimeTask` can then used by a script or off-chain worker to create and +/// submit such tasks via an extrinsic defined in `frame_system` called `do_task`. +/// +/// When submitted as unsigned transactions (for example via an off-chain workder), note +/// that the tasks will be executed in a random order. +/// +/// ## Example +#[doc = docify::embed!("examples/proc_main/tasks.rs", tasks_example)] +/// Now, this can be executed as follows: +#[doc = docify::embed!("examples/proc_main/tasks.rs", tasks_work)] #[proc_macro_attribute] pub fn tasks_experimental(_: TokenStream, _: TokenStream) -> TokenStream { pallet_macro_stub() } +/// Allows defining an iterator over available work items for a task. /// -/// --- +/// This attribute is attached to a function inside an `impl` block annotated with +/// [`pallet::tasks_experimental`](macro@tasks_experimental). /// -/// **Rust-Analyzer users**: See the documentation of the Rust item in -/// `frame_support::pallet_macros::task_list`. +/// It takes an iterator as input that yields a tuple with same types as the function +/// arguments. #[proc_macro_attribute] pub fn task_list(_: TokenStream, _: TokenStream) -> TokenStream { pallet_macro_stub() } +/// Allows defining conditions for a task to run. /// -/// --- +/// This attribute is attached to a function inside an `impl` block annotated with +/// [`pallet::tasks_experimental`](macro@tasks_experimental) to define the conditions for a +/// given work item to be valid. /// -/// **Rust-Analyzer users**: See the documentation of the Rust item in -/// `frame_support::pallet_macros::task_condition`. +/// It takes a closure as input, which is then used to define the condition. The closure +/// should have the same signature as the function it is attached to, except that it should +/// return a `bool` instead. #[proc_macro_attribute] pub fn task_condition(_: TokenStream, _: TokenStream) -> TokenStream { pallet_macro_stub() } +/// Allows defining the weight of a task. /// -/// --- +/// This attribute is attached to a function inside an `impl` block annotated with +/// [`pallet::tasks_experimental`](macro@tasks_experimental) define the weight of a given work +/// item. /// -/// **Rust-Analyzer users**: See the documentation of the Rust item in -/// `frame_support::pallet_macros::task_weight`. +/// It takes a closure as input, which should return a `Weight` value. #[proc_macro_attribute] pub fn task_weight(_: TokenStream, _: TokenStream) -> TokenStream { pallet_macro_stub() } +/// Allows defining an index for a task. /// -/// --- +/// This attribute is attached to a function inside an `impl` block annotated with +/// [`pallet::tasks_experimental`](macro@tasks_experimental) to define the index of a given +/// work item. /// -/// **Rust-Analyzer users**: See the documentation of the Rust item in -/// `frame_support::pallet_macros::task_index`. +/// It takes an integer literal as input, which is then used to define the index. This +/// index should be unique for each function in the `impl` block. #[proc_macro_attribute] pub fn task_index(_: TokenStream, _: TokenStream) -> TokenStream { pallet_macro_stub() @@ -1191,11 +1236,29 @@ pub fn import_section(attr: TokenStream, tokens: TokenStream) -> TokenStream { .into() } +/// Construct a runtime, with the given name and the given pallets. /// -/// --- +/// # Example: +#[doc = docify::embed!("examples/proc_main/runtime.rs", runtime_macro)] /// -/// **Rust-Analyzer users**: See the documentation of the Rust item in -/// `frame_support::runtime`. +/// # Supported Attributes: +/// +/// ## Legacy Ordering +/// +/// An optional attribute can be defined as #[frame_support::runtime(legacy_ordering)] to +/// ensure that the order of hooks is same as the order of pallets (and not based on the +/// pallet_index). This is to support legacy runtimes and should be avoided for new ones. +/// +/// # Note +/// +/// The population of the genesis storage depends on the order of pallets. So, if one of your +/// pallets depends on another pallet, the pallet that is depended upon needs to come before +/// the pallet depending on it. +/// +/// # Type definitions +/// +/// * The macro generates a type alias for each pallet to their `Pallet`. E.g. `type System = +/// frame_system::Pallet` #[proc_macro_attribute] pub fn runtime(attr: TokenStream, item: TokenStream) -> TokenStream { runtime::runtime(attr, item) diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs index 3ad8c5fabaa2..06696488e298 100644 --- a/substrate/frame/support/src/lib.rs +++ b/substrate/frame/support/src/lib.rs @@ -517,29 +517,6 @@ pub use frame_support_procedural::{ construct_runtime, match_and_insert, transactional, PalletError, RuntimeDebugNoBound, }; -/// Construct a runtime, with the given name and the given pallets. -/// -/// # Example: -#[doc = docify::embed!("src/tests/runtime.rs", runtime_macro)] -/// -/// # Supported Attributes: -/// -/// ## Legacy Ordering -/// -/// An optional attribute can be defined as #[frame_support::runtime(legacy_ordering)] to -/// ensure that the order of hooks is same as the order of pallets (and not based on the -/// pallet_index). This is to support legacy runtimes and should be avoided for new ones. -/// -/// # Note -/// -/// The population of the genesis storage depends on the order of pallets. So, if one of your -/// pallets depends on another pallet, the pallet that is depended upon needs to come before -/// the pallet depending on it. -/// -/// # Type definitions -/// -/// * The macro generates a type alias for each pallet to their `Pallet`. E.g. `type System = -/// frame_system::Pallet` pub use frame_support_procedural::runtime; #[doc(hidden)] @@ -933,26 +910,9 @@ pub mod pallet_prelude { PartialEqNoBound, RuntimeDebugNoBound, Twox128, Twox256, Twox64Concat, }; pub use codec::{Decode, Encode, MaxEncodedLen}; - pub use frame_support::pallet_macros::*; - pub use core::marker::PhantomData; - /// The optional attribute `#[inject_runtime_type]` can be attached to `RuntimeCall`, - /// `RuntimeEvent`, `RuntimeOrigin` or `PalletInfo` in an impl statement that has - /// `#[register_default_impl]` attached to indicate that this item is generated by - /// `construct_runtime`. - /// - /// Attaching this attribute to such an item ensures that the combined impl generated via - /// [`#[derive_impl(..)]`](`frame_support::derive_impl`) will use the correct - /// type auto-generated by - /// `construct_runtime!`. - #[doc = docify::embed!("src/tests/inject_runtime_type.rs", derive_impl_works_with_runtime_type_injection)] - /// - /// However, if `no_aggregated_types` is specified while using - /// `[`#[derive_impl(..)]`](`frame_support::derive_impl`)`, then these items are attached - /// verbatim to the combined impl. - #[doc = docify::embed!("src/tests/inject_runtime_type.rs", derive_impl_works_with_no_aggregated_types)] - pub use frame_support_procedural::inject_runtime_type; - pub use frame_support_procedural::register_default_impl; + pub use frame_support::pallet_macros::*; + pub use frame_support_procedural::{inject_runtime_type, register_default_impl}; pub use scale_info::TypeInfo; pub use sp_inherents::MakeFatalError; pub use sp_runtime::{ @@ -2446,76 +2406,9 @@ pub mod pallet_macros { /// ``` pub use frame_support_procedural::storage; - /// Allows defining conditions for a task to run. - /// - /// This attribute is attached to a function inside an `impl` block annotated with - /// [`pallet::tasks_experimental`](`tasks_experimental`) to define the conditions for a - /// given work item to be valid. - /// - /// It takes a closure as input, which is then used to define the condition. The closure - /// should have the same signature as the function it is attached to, except that it should - /// return a `bool` instead. - pub use frame_support_procedural::task_condition; - - /// Allows defining an index for a task. - /// - /// This attribute is attached to a function inside an `impl` block annotated with - /// [`pallet::tasks_experimental`](`tasks_experimental`) to define the index of a given - /// work item. - /// - /// It takes an integer literal as input, which is then used to define the index. This - /// index should be unique for each function in the `impl` block. - pub use frame_support_procedural::task_index; - - /// Allows defining an iterator over available work items for a task. - /// - /// This attribute is attached to a function inside an `impl` block annotated with - /// [`pallet::tasks_experimental`](`tasks_experimental`). - /// - /// It takes an iterator as input that yields a tuple with same types as the function - /// arguments. - pub use frame_support_procedural::task_list; - - /// Allows defining the weight of a task. - /// - /// This attribute is attached to a function inside an `impl` block annotated with - /// [`pallet::tasks_experimental`](`tasks_experimental`) define the weight of a given work - /// item. - /// - /// It takes a closure as input, which should return a `Weight` value. - pub use frame_support_procedural::task_weight; - - /// Allows you to define some service work that can be recognized by a script or an - /// off-chain worker. - /// - /// Such a script can then create and submit all such work items at any given time. - /// - /// These work items are defined as instances of the [`Task`](frame_support::traits::Task) - /// trait. [`pallet:tasks_experimental`](`tasks_experimental`) when attached to an `impl` - /// block inside a pallet, will generate an enum `Task` whose variants are mapped to - /// functions inside this `impl` block. - /// - /// Each such function must have the following set of attributes: - /// - /// * [`pallet::task_list`](`task_list`) - /// * [`pallet::task_condition`](`task_condition`) - /// * [`pallet::task_weight`](`task_weight`) - /// * [`pallet::task_index`](`task_index`) - /// - /// All of such Tasks are then aggregated into a `RuntimeTask` by - /// [`construct_runtime`](frame_support::construct_runtime). - /// - /// Finally, the `RuntimeTask` can then used by a script or off-chain worker to create and - /// submit such tasks via an extrinsic defined in `frame_system` called `do_task`. - /// - /// When submitted as unsigned transactions (for example via an off-chain workder), note - /// that the tasks will be executed in a random order. - /// - /// ## Example - #[doc = docify::embed!("src/tests/tasks.rs", tasks_example)] - /// Now, this can be executed as follows: - #[doc = docify::embed!("src/tests/tasks.rs", tasks_work)] - pub use frame_support_procedural::tasks_experimental; + pub use frame_support_procedural::{ + task_condition, task_index, task_list, task_weight, tasks_experimental, + }; /// Allows a pallet to declare a type as an origin. /// diff --git a/substrate/frame/support/src/tests/mod.rs b/substrate/frame/support/src/tests/mod.rs index 34652231e3bc..a9f8ebdd63f2 100644 --- a/substrate/frame/support/src/tests/mod.rs +++ b/substrate/frame/support/src/tests/mod.rs @@ -16,7 +16,6 @@ // limitations under the License. use super::*; -use frame_support_procedural::import_section; use sp_io::{MultiRemovalResults, TestExternalities}; use sp_metadata_ir::{ PalletStorageMetadataIR, StorageEntryMetadataIR, StorageEntryModifierIR, StorageEntryTypeIR, @@ -26,12 +25,8 @@ use sp_runtime::{generic, traits::BlakeTwo256, BuildStorage}; pub use self::frame_system::{pallet_prelude::*, Config, Pallet}; -mod inject_runtime_type; -mod runtime; mod storage_alias; -mod tasks; -#[import_section(tasks::tasks_example)] #[pallet] pub mod frame_system { #[allow(unused)]