From d38bb9533b70abb7eff4e8770177d7840899ca86 Mon Sep 17 00:00:00 2001 From: Alexandru Gheorghe <49718502+alexggh@users.noreply.github.com> Date: Tue, 14 Jan 2025 19:10:27 +0200 Subject: [PATCH 01/17] approval-voting: Fix sending of assignments after restart (#6973) There is a problem on restart where nodes will not trigger their needed assignment if they were offline while the time of the assignment passed. That happens because after restart we will hit this condition https://github.com/paritytech/polkadot-sdk/blob/4e805ca05067f6ed970f33f9be51483185b0cc0b/polkadot/node/core/approval-voting/src/lib.rs#L2495 and considered will be `tick_now` which is already higher than the tick of our assignment. The fix is to schedule a wakeup for untriggered assignments at restart and let the logic of processing an wakeup decide if it needs to trigger the assignment or not. One thing that we need to be careful here is to make sure we don't schedule the wake up immediately after restart because, the node would still be behind with all the assignments that should have received and might make it wrongfully decide it needs to trigger its assignment, so I added a `RESTART_WAKEUP_DELAY: Tick = 12` which should be more than enough for the node to catch up. --------- Signed-off-by: Alexandru Gheorghe Co-authored-by: ordian Co-authored-by: Andrei Eres --- polkadot/node/core/approval-voting/src/lib.rs | 25 +- .../node/core/approval-voting/src/tests.rs | 246 ++++++++++++++++++ prdoc/pr_6973.prdoc | 16 ++ 3 files changed, 286 insertions(+), 1 deletion(-) create mode 100644 prdoc/pr_6973.prdoc diff --git a/polkadot/node/core/approval-voting/src/lib.rs b/polkadot/node/core/approval-voting/src/lib.rs index 27361df37310..b4c2a6afee0e 100644 --- a/polkadot/node/core/approval-voting/src/lib.rs +++ b/polkadot/node/core/approval-voting/src/lib.rs @@ -132,6 +132,16 @@ pub(crate) const LOG_TARGET: &str = "parachain::approval-voting"; // The max number of ticks we delay sending the approval after we are ready to issue the approval const MAX_APPROVAL_COALESCE_WAIT_TICKS: Tick = 12; +// If the node restarted and the tranche has passed without the assignment +// being trigger, we won't trigger the assignment at restart because we don't have +// an wakeup schedule for it. +// The solution, is to always schedule a wake up after the restart and let the +// process_wakeup to decide if the assignment needs to be triggered. +// We need to have a delay after restart to give time to the node to catch up with +// messages and not trigger its assignment unnecessarily, because it hasn't seen +// the assignments from the other validators. +const RESTART_WAKEUP_DELAY: Tick = 12; + /// Configuration for the approval voting subsystem #[derive(Debug, Clone)] pub struct Config { @@ -1837,7 +1847,20 @@ async fn distribution_messages_for_activation { match approval_entry.local_statements() { - (None, None) | (None, Some(_)) => {}, // second is impossible case. + (None, None) => + if approval_entry + .our_assignment() + .map(|assignment| !assignment.triggered()) + .unwrap_or(false) + { + actions.push(Action::ScheduleWakeup { + block_hash, + block_number: block_entry.block_number(), + candidate_hash: *candidate_hash, + tick: state.clock.tick_now() + RESTART_WAKEUP_DELAY, + }) + }, + (None, Some(_)) => {}, // second is impossible case. (Some(assignment), None) => { let claimed_core_indices = get_core_indices_on_startup(&assignment.cert().kind, *core_index); diff --git a/polkadot/node/core/approval-voting/src/tests.rs b/polkadot/node/core/approval-voting/src/tests.rs index b72993fe1a94..9fe716833b88 100644 --- a/polkadot/node/core/approval-voting/src/tests.rs +++ b/polkadot/node/core/approval-voting/src/tests.rs @@ -5380,6 +5380,252 @@ fn subsystem_sends_assignment_approval_in_correct_order_on_approval_restart() { }); } +// Test that if the subsystem missed the triggering of some tranches because it was not running +// it launches the missed assignements on restart. +#[test] +fn subsystem_launches_missed_assignments_on_restart() { + let test_tranche = 20; + let assignment_criteria = Box::new(MockAssignmentCriteria( + move || { + let mut assignments = HashMap::new(); + let _ = assignments.insert( + CoreIndex(0), + approval_db::v2::OurAssignment { + cert: garbage_assignment_cert_v2(AssignmentCertKindV2::RelayVRFDelay { + core_index: CoreIndex(0), + }), + tranche: test_tranche, + validator_index: ValidatorIndex(0), + triggered: false, + } + .into(), + ); + + assignments + }, + |_| Ok(0), + )); + let config = HarnessConfigBuilder::default().assignment_criteria(assignment_criteria).build(); + let store = config.backend(); + let store_clone = config.backend(); + + test_harness(config, |test_harness| async move { + let TestHarness { mut virtual_overseer, clock, sync_oracle_handle } = test_harness; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => { + rx.send(Ok(0)).unwrap(); + } + ); + + let block_hash = Hash::repeat_byte(0x01); + let fork_block_hash = Hash::repeat_byte(0x02); + let candidate_commitments = CandidateCommitments::default(); + let mut candidate_receipt = dummy_candidate_receipt_v2(block_hash); + candidate_receipt.commitments_hash = candidate_commitments.hash(); + let candidate_hash = candidate_receipt.hash(); + let slot = Slot::from(1); + let (chain_builder, _session_info) = build_chain_with_two_blocks_with_one_candidate_each( + block_hash, + fork_block_hash, + slot, + sync_oracle_handle, + candidate_receipt, + ) + .await; + chain_builder.build(&mut virtual_overseer).await; + + assert!(!clock.inner.lock().current_wakeup_is(1)); + clock.inner.lock().wakeup_all(1); + + assert!(clock.inner.lock().current_wakeup_is(slot_to_tick(slot) + test_tranche as u64)); + clock.inner.lock().wakeup_all(slot_to_tick(slot)); + + futures_timer::Delay::new(Duration::from_millis(200)).await; + + clock.inner.lock().wakeup_all(slot_to_tick(slot + 2)); + + assert_eq!(clock.inner.lock().wakeups.len(), 0); + + futures_timer::Delay::new(Duration::from_millis(200)).await; + + let candidate_entry = store.load_candidate_entry(&candidate_hash).unwrap().unwrap(); + let our_assignment = + candidate_entry.approval_entry(&block_hash).unwrap().our_assignment().unwrap(); + assert!(!our_assignment.triggered()); + + // Assignment is not triggered because its tranches has not been reached. + virtual_overseer + }); + + // Restart a new approval voting subsystem with the same database and major syncing true until + // the last leaf. + let config = HarnessConfigBuilder::default().backend(store_clone).major_syncing(true).build(); + + test_harness(config, |test_harness| async move { + let TestHarness { mut virtual_overseer, clock, sync_oracle_handle } = test_harness; + let slot = Slot::from(1); + // 1. Set the clock to the to a tick past the tranche where the assignment should be + // triggered. + clock.inner.lock().set_tick(slot_to_tick(slot) + 2 * test_tranche as u64); + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => { + rx.send(Ok(0)).unwrap(); + } + ); + + let block_hash = Hash::repeat_byte(0x01); + let fork_block_hash = Hash::repeat_byte(0x02); + let candidate_commitments = CandidateCommitments::default(); + let mut candidate_receipt = dummy_candidate_receipt_v2(block_hash); + candidate_receipt.commitments_hash = candidate_commitments.hash(); + let (chain_builder, session_info) = build_chain_with_two_blocks_with_one_candidate_each( + block_hash, + fork_block_hash, + slot, + sync_oracle_handle, + candidate_receipt, + ) + .await; + + chain_builder.build(&mut virtual_overseer).await; + + futures_timer::Delay::new(Duration::from_millis(2000)).await; + + // On major syncing ending Approval voting should send all the necessary messages for a + // candidate to be approved. + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NewBlocks( + _, + )) => { + } + ); + + clock + .inner + .lock() + .wakeup_all(slot_to_tick(slot) + 2 * test_tranche as u64 + RESTART_WAKEUP_DELAY - 1); + + // Subsystem should not send any messages because the assignment is not triggered yet. + assert!(overseer_recv(&mut virtual_overseer).timeout(TIMEOUT / 2).await.is_none()); + + // Set the clock to the tick where the assignment should be triggered. + clock + .inner + .lock() + .wakeup_all(slot_to_tick(slot) + 2 * test_tranche as u64 + RESTART_WAKEUP_DELAY); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request( + _, + RuntimeApiRequest::SessionInfo(_, si_tx), + ) + ) => { + si_tx.send(Ok(Some(session_info.clone()))).unwrap(); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request( + _, + RuntimeApiRequest::SessionExecutorParams(_, si_tx), + ) + ) => { + // Make sure all SessionExecutorParams calls are not made for the leaf (but for its relay parent) + si_tx.send(Ok(Some(ExecutorParams::default()))).unwrap(); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), ) + ) => { + si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap(); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment( + _, + _, + )) => { + } + ); + + // Guarantees the approval work has been relaunched. + recover_available_data(&mut virtual_overseer).await; + fetch_validation_code(&mut virtual_overseer).await; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::CandidateValidation(CandidateValidationMessage::ValidateFromExhaustive { + exec_kind, + response_sender, + .. + }) if exec_kind == PvfExecKind::Approval => { + response_sender.send(Ok(ValidationResult::Valid(Default::default(), Default::default()))) + .unwrap(); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => { + let _ = sender.send(Ok(ApprovalVotingParams { + max_approval_coalesce_count: 1, + })); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(_)) + ); + + clock + .inner + .lock() + .wakeup_all(slot_to_tick(slot) + 2 * test_tranche as u64 + RESTART_WAKEUP_DELAY); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment( + _, + _, + )) => { + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => { + let _ = sender.send(Ok(ApprovalVotingParams { + max_approval_coalesce_count: 1, + })); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(_)) + ); + + // Assert that there are no more messages being sent by the subsystem + assert!(overseer_recv(&mut virtual_overseer).timeout(TIMEOUT / 2).await.is_none()); + + virtual_overseer + }); +} + // Test we correctly update the timer when we mark the beginning of gathering assignments. #[test] fn test_gathering_assignments_statements() { diff --git a/prdoc/pr_6973.prdoc b/prdoc/pr_6973.prdoc new file mode 100644 index 000000000000..416789b9171a --- /dev/null +++ b/prdoc/pr_6973.prdoc @@ -0,0 +1,16 @@ +# 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: approval-voting fix sending of assignments after restart + +doc: + - audience: Node Dev + description: | + There is a problem on restart where nodes will not trigger their needed assignment if + they were offline and the time of the assignment passed, so after restart always + schedule a wakeup so that nodes a have the opportunity of triggering their assignments + if they are still needed. + +crates: + - name: polkadot-node-core-approval-voting + bump: minor From ba36b2d2293d72d087072254e6371d9089f192b7 Mon Sep 17 00:00:00 2001 From: Sebastian Kunert Date: Tue, 14 Jan 2025 18:56:30 +0100 Subject: [PATCH 02/17] CI: Only format umbrella crate during umbrella check (#7139) The umbrella crate quick-check was always failing whenever there was something misformated in the whole codebase. This leads to an error that indicates that a new crate was added, even when it was not. After this PR we only apply `cargo fmt` to the newly generated umbrella crate `polkadot-sdk`. This results in this check being independent from the fmt job which should check the entire codebase. --- .github/workflows/checks-quick.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/checks-quick.yml b/.github/workflows/checks-quick.yml index 4c26b85a6303..1a8813833def 100644 --- a/.github/workflows/checks-quick.yml +++ b/.github/workflows/checks-quick.yml @@ -138,7 +138,7 @@ jobs: # Fixes "detected dubious ownership" error in the ci git config --global --add safe.directory '*' python3 scripts/generate-umbrella.py --sdk . --version 0.1.0 - cargo +nightly fmt --all + cargo +nightly fmt -p polkadot-sdk if [ -n "$(git status --porcelain)" ]; then cat < Date: Tue, 14 Jan 2025 20:57:05 +0100 Subject: [PATCH 03/17] xcm: convert properly assets in xcmpayment apis (#7134) Port #6459 changes to relays as well, which were probably forgotten in that PR. Thanks! --------- Co-authored-by: Francisco Aguirre Co-authored-by: command-bot <> --- polkadot/runtime/rococo/src/lib.rs | 3 ++- polkadot/runtime/westend/src/lib.rs | 3 ++- prdoc/pr_7134.prdoc | 11 +++++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 prdoc/pr_7134.prdoc diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index e5d703700fee..b3f2a0033278 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -1885,7 +1885,8 @@ sp_api::impl_runtime_apis! { } fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result { - match asset.try_as::() { + let latest_asset_id: Result = asset.clone().try_into(); + match latest_asset_id { Ok(asset_id) if asset_id.0 == xcm_config::TokenLocation::get() => { // for native token Ok(WeightToFee::weight_to_fee(&weight)) diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 9d77a5e5eea1..58d2bdcb7c7d 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -2445,7 +2445,8 @@ sp_api::impl_runtime_apis! { } fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result { - match asset.try_as::() { + let latest_asset_id: Result = asset.clone().try_into(); + match latest_asset_id { Ok(asset_id) if asset_id.0 == xcm_config::TokenLocation::get() => { // for native token Ok(WeightToFee::weight_to_fee(&weight)) diff --git a/prdoc/pr_7134.prdoc b/prdoc/pr_7134.prdoc new file mode 100644 index 000000000000..095d4757f438 --- /dev/null +++ b/prdoc/pr_7134.prdoc @@ -0,0 +1,11 @@ +title: 'xcm: convert properly assets in xcmpayment apis' +doc: +- audience: Runtime User + description: |- + Port #6459 changes to relays as well, which were probably forgotten in that PR. + Thanks! +crates: +- name: rococo-runtime + bump: patch +- name: westend-runtime + bump: patch From 5f391db8af50a79db83acfe37f73c7202177d71c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 14 Jan 2025 21:22:52 +0100 Subject: [PATCH 04/17] PRDOC: Document `validate: false` (#7117) --- docs/contributor/prdoc.md | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/docs/contributor/prdoc.md b/docs/contributor/prdoc.md index 1f6252425e69..b3f7a7e94f0c 100644 --- a/docs/contributor/prdoc.md +++ b/docs/contributor/prdoc.md @@ -81,9 +81,6 @@ picked if no other applies. The `None` option is equivalent to the `R0-silent` l level. Experimental and private APIs are exempt from bumping and can be broken at any time. Please read the [Crate Section](../RELEASE.md) of the RELEASE doc about them. -> **Note**: There is currently no CI in place to sanity check this information, but should be added -> soon. - ### Example For example when you modified two crates and record the changes: @@ -106,3 +103,21 @@ you do not need to bump a crate that had a SemVer breaking change only from re-e crate with a breaking change. `minor` an `patch` bumps do not need to be inherited, since `cargo` will automatically update them to the latest compatible version. + +### Overwrite CI check + +The `check-semver` CI check is doing sanity checks based on the provided `PRDoc` and the mentioned +crate version bumps. The tooling is not perfect and it may recommends incorrect bumps of the version. +The CI check can be forced to accept the provided version bump. This can be done like: + +```yaml +crates: + - name: frame-example + bump: major + validate: false + - name: frame-example-pallet + bump: minor +``` + +By putting `validate: false` for `frame-example`, the version bump is ignored by the tooling. For +`frame-example-pallet` the version bump is still validated by the CI check. From d5539aa63edc8068eff9c4cbb78214c3a5ab66b2 Mon Sep 17 00:00:00 2001 From: Sebastian Kunert Date: Tue, 14 Jan 2025 23:47:19 +0100 Subject: [PATCH 05/17] Parachains: Use relay chain slot for velocity measurement (#6825) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes #3967 ## Changes We now use relay chain slots to measure velocity on chain. Previously we were storing the current parachain slot. Then in `on_state_proof` of the `ConsensusHook` we were checking how many blocks were athored in the current parachain slot. This works well when the parachain slot time and relay chain slot time is the same. With elastic scaling, we can have parachain slot times lower than that of the relay chain. In these cases we want to measure velocity in relation to the relay chain. This PR adjusts that. ## Migration This PR includes a migration. Storage item `SlotInfo` of pallet `aura-ext` is renamed to `RelaySlotInfo` to better reflect its new content. A migration has been added that just kills the old storage item. `RelaySlotInfo` will be `None` initially but its value will be adjusted after one new relay chain slot arrives. --------- Co-authored-by: command-bot <> Co-authored-by: Bastian Köcher --- Cargo.lock | 5 + .../consensus/aura/src/collators/lookahead.rs | 1 + .../consensus/aura/src/collators/mod.rs | 30 +- .../slot_based/block_builder_task.rs | 11 +- cumulus/client/parachain-inherent/src/mock.rs | 11 +- cumulus/pallets/aura-ext/Cargo.toml | 8 +- .../pallets/aura-ext/src/consensus_hook.rs | 42 ++- cumulus/pallets/aura-ext/src/lib.rs | 26 +- cumulus/pallets/aura-ext/src/migration.rs | 74 ++++ cumulus/pallets/aura-ext/src/test.rs | 338 ++++++++++++++++++ .../parachain-system/src/consensus_hook.rs | 4 +- cumulus/pallets/parachain-system/src/lib.rs | 4 +- .../assets/asset-hub-rococo/src/lib.rs | 1 + .../assets/asset-hub-westend/src/lib.rs | 2 +- .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 1 + .../bridge-hubs/bridge-hub-westend/src/lib.rs | 1 + .../collectives-westend/src/lib.rs | 1 + .../contracts/contracts-rococo/src/lib.rs | 1 + .../coretime/coretime-rococo/src/lib.rs | 1 + .../coretime/coretime-westend/src/lib.rs | 1 + .../runtimes/people/people-rococo/src/lib.rs | 1 + .../runtimes/people/people-westend/src/lib.rs | 1 + cumulus/primitives/aura/src/lib.rs | 6 +- cumulus/xcm/xcm-emulator/src/lib.rs | 1 + prdoc/pr_6825.prdoc | 50 +++ 25 files changed, 560 insertions(+), 62 deletions(-) create mode 100644 cumulus/pallets/aura-ext/src/migration.rs create mode 100644 cumulus/pallets/aura-ext/src/test.rs create mode 100644 prdoc/pr_6825.prdoc diff --git a/Cargo.lock b/Cargo.lock index 3eab84d5ed16..7725db743c41 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4874,6 +4874,8 @@ name = "cumulus-pallet-aura-ext" version = "0.7.0" dependencies = [ "cumulus-pallet-parachain-system 0.7.0", + "cumulus-primitives-core 0.7.0", + "cumulus-test-relay-sproof-builder 0.7.0", "frame-support 28.0.0", "frame-system 28.0.0", "pallet-aura 27.0.0", @@ -4882,7 +4884,10 @@ dependencies = [ "scale-info", "sp-application-crypto 30.0.0", "sp-consensus-aura 0.32.0", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-runtime 31.0.1", + "sp-version 29.0.0", ] [[package]] diff --git a/cumulus/client/consensus/aura/src/collators/lookahead.rs b/cumulus/client/consensus/aura/src/collators/lookahead.rs index 2dbcf5eb58e9..7723de5a576a 100644 --- a/cumulus/client/consensus/aura/src/collators/lookahead.rs +++ b/cumulus/client/consensus/aura/src/collators/lookahead.rs @@ -336,6 +336,7 @@ where ); Some(super::can_build_upon::<_, _, P>( slot_now, + relay_slot, timestamp, block_hash, included_block, diff --git a/cumulus/client/consensus/aura/src/collators/mod.rs b/cumulus/client/consensus/aura/src/collators/mod.rs index 89070607fbab..031fa963ba6a 100644 --- a/cumulus/client/consensus/aura/src/collators/mod.rs +++ b/cumulus/client/consensus/aura/src/collators/mod.rs @@ -34,7 +34,7 @@ use polkadot_primitives::{ ValidationCodeHash, }; use sc_consensus_aura::{standalone as aura_internal, AuraApi}; -use sp_api::ProvideRuntimeApi; +use sp_api::{ApiExt, ProvideRuntimeApi}; use sp_core::Pair; use sp_keystore::KeystorePtr; use sp_timestamp::Timestamp; @@ -160,7 +160,8 @@ async fn cores_scheduled_for_para( // Checks if we own the slot at the given block and whether there // is space in the unincluded segment. async fn can_build_upon( - slot: Slot, + para_slot: Slot, + relay_slot: Slot, timestamp: Timestamp, parent_hash: Block::Hash, included_block: Block::Hash, @@ -169,25 +170,28 @@ async fn can_build_upon( ) -> Option> where Client: ProvideRuntimeApi, - Client::Api: AuraApi + AuraUnincludedSegmentApi, + Client::Api: AuraApi + AuraUnincludedSegmentApi + ApiExt, P: Pair, P::Public: Codec, P::Signature: Codec, { let runtime_api = client.runtime_api(); let authorities = runtime_api.authorities(parent_hash).ok()?; - let author_pub = aura_internal::claim_slot::

(slot, &authorities, keystore).await?; + let author_pub = aura_internal::claim_slot::

(para_slot, &authorities, keystore).await?; - // Here we lean on the property that building on an empty unincluded segment must always - // be legal. Skipping the runtime API query here allows us to seamlessly run this - // collator against chains which have not yet upgraded their runtime. - if parent_hash != included_block && - !runtime_api.can_build_upon(parent_hash, included_block, slot).ok()? - { - return None - } + let Ok(Some(api_version)) = + runtime_api.api_version::>(parent_hash) + else { + return (parent_hash == included_block) + .then(|| SlotClaim::unchecked::

(author_pub, para_slot, timestamp)); + }; + + let slot = if api_version > 1 { relay_slot } else { para_slot }; - Some(SlotClaim::unchecked::

(author_pub, slot, timestamp)) + runtime_api + .can_build_upon(parent_hash, included_block, slot) + .ok()? + .then(|| SlotClaim::unchecked::

(author_pub, para_slot, timestamp)) } /// Use [`cumulus_client_consensus_common::find_potential_parents`] to find parachain blocks that diff --git a/cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs b/cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs index 41751f1db530..48287555dea6 100644 --- a/cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs +++ b/cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs @@ -23,7 +23,7 @@ use cumulus_primitives_aura::AuraUnincludedSegmentApi; use cumulus_primitives_core::{GetCoreSelectorApi, PersistedValidationData}; use cumulus_relay_chain_interface::RelayChainInterface; -use polkadot_primitives::Id as ParaId; +use polkadot_primitives::{Block as RelayBlock, Id as ParaId}; use futures::prelude::*; use sc_client_api::{backend::AuxStore, BlockBackend, BlockOf, UsageProvider}; @@ -302,8 +302,17 @@ where // on-chain data. collator.collator_service().check_block_status(parent_hash, &parent_header); + let Ok(relay_slot) = + sc_consensus_babe::find_pre_digest::(relay_parent_header) + .map(|babe_pre_digest| babe_pre_digest.slot()) + else { + tracing::error!(target: crate::LOG_TARGET, "Relay chain does not contain babe slot. This should never happen."); + continue; + }; + let slot_claim = match crate::collators::can_build_upon::<_, _, P>( para_slot.slot, + relay_slot, para_slot.timestamp, parent_hash, included_block, diff --git a/cumulus/client/parachain-inherent/src/mock.rs b/cumulus/client/parachain-inherent/src/mock.rs index e08aca932564..8dbc6ace0f06 100644 --- a/cumulus/client/parachain-inherent/src/mock.rs +++ b/cumulus/client/parachain-inherent/src/mock.rs @@ -17,8 +17,9 @@ use crate::{ParachainInherentData, INHERENT_IDENTIFIER}; use codec::Decode; use cumulus_primitives_core::{ - relay_chain, relay_chain::UpgradeGoAhead, InboundDownwardMessage, InboundHrmpMessage, ParaId, - PersistedValidationData, + relay_chain, + relay_chain::{Slot, UpgradeGoAhead}, + InboundDownwardMessage, InboundHrmpMessage, ParaId, PersistedValidationData, }; use cumulus_primitives_parachain_inherent::MessageQueueChain; use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; @@ -28,9 +29,6 @@ use sp_inherents::{InherentData, InherentDataProvider}; use sp_runtime::traits::Block; use std::collections::BTreeMap; -/// Relay chain slot duration, in milliseconds. -pub const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32 = 6000; - /// Inherent data provider that supplies mocked validation data. /// /// This is useful when running a node that is not actually backed by any relay chain. @@ -175,8 +173,7 @@ impl> InherentDataProvider // Calculate the mocked relay block based on the current para block let relay_parent_number = self.relay_offset + self.relay_blocks_per_para_block * self.current_para_block; - sproof_builder.current_slot = - ((relay_parent_number / RELAY_CHAIN_SLOT_DURATION_MILLIS) as u64).into(); + sproof_builder.current_slot = Slot::from(relay_parent_number as u64); sproof_builder.upgrade_go_ahead = self.upgrade_go_ahead; // Process the downward messages and set up the correct head diff --git a/cumulus/pallets/aura-ext/Cargo.toml b/cumulus/pallets/aura-ext/Cargo.toml index fcda79f1d5c1..82638de71aa1 100644 --- a/cumulus/pallets/aura-ext/Cargo.toml +++ b/cumulus/pallets/aura-ext/Cargo.toml @@ -28,9 +28,15 @@ sp-runtime = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } [dev-dependencies] - # Cumulus cumulus-pallet-parachain-system = { workspace = true, default-features = true } +cumulus-primitives-core = { workspace = true, default-features = true } +cumulus-test-relay-sproof-builder = { workspace = true, default-features = true } + +# Substrate +sp-core = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } +sp-version = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/cumulus/pallets/aura-ext/src/consensus_hook.rs b/cumulus/pallets/aura-ext/src/consensus_hook.rs index c1a8568bdd83..56966aa0c8f8 100644 --- a/cumulus/pallets/aura-ext/src/consensus_hook.rs +++ b/cumulus/pallets/aura-ext/src/consensus_hook.rs @@ -18,7 +18,6 @@ //! block velocity. //! //! The velocity `V` refers to the rate of block processing by the relay chain. - use super::{pallet, Aura}; use core::{marker::PhantomData, num::NonZeroU32}; use cumulus_pallet_parachain_system::{ @@ -54,8 +53,23 @@ where let velocity = V.max(1); let relay_chain_slot = state_proof.read_slot().expect("failed to read relay chain slot"); - let (slot, authored) = - pallet::SlotInfo::::get().expect("slot info is inserted on block initialization"); + let (relay_chain_slot, authored_in_relay) = match pallet::RelaySlotInfo::::get() { + Some((slot, authored)) if slot == relay_chain_slot => (slot, authored), + Some((slot, _)) if slot < relay_chain_slot => (relay_chain_slot, 0), + Some((slot, _)) => { + panic!("Slot moved backwards: stored_slot={slot:?}, relay_chain_slot={relay_chain_slot:?}") + }, + None => (relay_chain_slot, 0), + }; + + // We need to allow one additional block to be built to fill the unincluded segment. + if authored_in_relay > velocity { + panic!("authored blocks limit is reached for the slot: relay_chain_slot={relay_chain_slot:?}, authored={authored_in_relay:?}, velocity={velocity:?}"); + } + + pallet::RelaySlotInfo::::put((relay_chain_slot, authored_in_relay + 1)); + + let para_slot = pallet_aura::CurrentSlot::::get(); // Convert relay chain timestamp. let relay_chain_timestamp = @@ -67,19 +81,16 @@ where // Check that we are not too far in the future. Since we expect `V` parachain blocks // during the relay chain slot, we can allow for `V` parachain slots into the future. - if *slot > *para_slot_from_relay + u64::from(velocity) { + if *para_slot > *para_slot_from_relay + u64::from(velocity) { panic!( - "Parachain slot is too far in the future: parachain_slot: {:?}, derived_from_relay_slot: {:?} velocity: {:?}", - slot, + "Parachain slot is too far in the future: parachain_slot={:?}, derived_from_relay_slot={:?} velocity={:?}, relay_chain_slot={:?}", + para_slot, para_slot_from_relay, - velocity + velocity, + relay_chain_slot ); } - // We need to allow authoring multiple blocks in the same slot. - if slot != para_slot_from_relay && authored > velocity { - panic!("authored blocks limit is reached for the slot") - } let weight = T::DbWeight::get().reads(1); ( @@ -110,7 +121,7 @@ impl< /// is more recent than the included block itself. pub fn can_build_upon(included_hash: T::Hash, new_slot: Slot) -> bool { let velocity = V.max(1); - let (last_slot, authored_so_far) = match pallet::SlotInfo::::get() { + let (last_slot, authored_so_far) = match pallet::RelaySlotInfo::::get() { None => return true, Some(x) => x, }; @@ -123,11 +134,8 @@ impl< return false } - // TODO: This logic needs to be adjusted. - // It checks that we have not authored more than `V + 1` blocks in the slot. - // As a slot however, we take the parachain slot here. Velocity should - // be measured in relation to the relay chain slot. - // https://github.com/paritytech/polkadot-sdk/issues/3967 + // Check that we have not authored more than `V + 1` parachain blocks in the current relay + // chain slot. if last_slot == new_slot { authored_so_far < velocity + 1 } else { diff --git a/cumulus/pallets/aura-ext/src/lib.rs b/cumulus/pallets/aura-ext/src/lib.rs index dc854eb82018..19c2634ca708 100644 --- a/cumulus/pallets/aura-ext/src/lib.rs +++ b/cumulus/pallets/aura-ext/src/lib.rs @@ -40,6 +40,9 @@ use sp_consensus_aura::{digests::CompatibleDigestItem, Slot}; use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; pub mod consensus_hook; +pub mod migration; +mod test; + pub use consensus_hook::FixedVelocityConsensusHook; type Aura = pallet_aura::Pallet; @@ -57,6 +60,7 @@ pub mod pallet { pub trait Config: pallet_aura::Config + frame_system::Config {} #[pallet::pallet] + #[pallet::storage_version(migration::STORAGE_VERSION)] pub struct Pallet(_); #[pallet::hooks] @@ -70,20 +74,7 @@ pub mod pallet { // Fetch the authorities once to get them into the storage proof of the PoV. Authorities::::get(); - let new_slot = pallet_aura::CurrentSlot::::get(); - - let (new_slot, authored) = match SlotInfo::::get() { - Some((slot, authored)) if slot == new_slot => (slot, authored + 1), - Some((slot, _)) if slot < new_slot => (new_slot, 1), - Some(..) => { - panic!("slot moved backwards") - }, - None => (new_slot, 1), - }; - - SlotInfo::::put((new_slot, authored)); - - T::DbWeight::get().reads_writes(4, 2) + T::DbWeight::get().reads_writes(1, 0) } } @@ -99,11 +90,12 @@ pub mod pallet { ValueQuery, >; - /// Current slot paired with a number of authored blocks. + /// Current relay chain slot paired with a number of authored blocks. /// - /// Updated on each block initialization. + /// This is updated in [`FixedVelocityConsensusHook::on_state_proof`] with the current relay + /// chain slot as provided by the relay chain state proof. #[pallet::storage] - pub(crate) type SlotInfo = StorageValue<_, (Slot, u32), OptionQuery>; + pub(crate) type RelaySlotInfo = StorageValue<_, (Slot, u32), OptionQuery>; #[pallet::genesis_config] #[derive(frame_support::DefaultNoBound)] diff --git a/cumulus/pallets/aura-ext/src/migration.rs b/cumulus/pallets/aura-ext/src/migration.rs new file mode 100644 index 000000000000..b580c19fc733 --- /dev/null +++ b/cumulus/pallets/aura-ext/src/migration.rs @@ -0,0 +1,74 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . +extern crate alloc; + +use crate::{Config, Pallet}; +#[cfg(feature = "try-runtime")] +use alloc::vec::Vec; +use frame_support::{migrations::VersionedMigration, pallet_prelude::StorageVersion}; + +/// The in-code storage version. +pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + +mod v0 { + use super::*; + use frame_support::{pallet_prelude::OptionQuery, storage_alias}; + use sp_consensus_aura::Slot; + + /// Current slot paired with a number of authored blocks. + /// + /// Updated on each block initialization. + #[storage_alias] + pub(super) type SlotInfo = StorageValue, (Slot, u32), OptionQuery>; +} +mod v1 { + use super::*; + use frame_support::{pallet_prelude::*, traits::UncheckedOnRuntimeUpgrade}; + + pub struct UncheckedMigrationToV1(PhantomData); + + impl UncheckedOnRuntimeUpgrade for UncheckedMigrationToV1 { + fn on_runtime_upgrade() -> Weight { + let mut weight: Weight = Weight::zero(); + weight += migrate::(); + weight + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + Ok(Vec::new()) + } + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { + ensure!(!v0::SlotInfo::::exists(), "SlotInfo should not exist"); + Ok(()) + } + } + + pub fn migrate() -> Weight { + v0::SlotInfo::::kill(); + T::DbWeight::get().writes(1) + } +} + +/// Migrate `V0` to `V1`. +pub type MigrateV0ToV1 = VersionedMigration< + 0, + 1, + v1::UncheckedMigrationToV1, + Pallet, + ::DbWeight, +>; diff --git a/cumulus/pallets/aura-ext/src/test.rs b/cumulus/pallets/aura-ext/src/test.rs new file mode 100644 index 000000000000..b0099381e682 --- /dev/null +++ b/cumulus/pallets/aura-ext/src/test.rs @@ -0,0 +1,338 @@ +// Copyright Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +#![cfg(test)] +extern crate alloc; + +use super::*; + +use core::num::NonZeroU32; +use cumulus_pallet_parachain_system::{ + consensus_hook::ExpectParentIncluded, AnyRelayNumber, DefaultCoreSelector, ParachainSetCode, +}; +use cumulus_primitives_core::ParaId; +use frame_support::{ + derive_impl, + pallet_prelude::ConstU32, + parameter_types, + traits::{ConstBool, ConstU64, EnqueueWithOrigin}, +}; +use sp_io::TestExternalities; +use sp_version::RuntimeVersion; + +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test { + System: frame_system, + ParachainSystem: cumulus_pallet_parachain_system, + Aura: pallet_aura, + AuraExt: crate, + } +); + +parameter_types! { + pub Version: RuntimeVersion = RuntimeVersion { + spec_name: "test".into(), + impl_name: "system-test".into(), + authoring_version: 1, + spec_version: 1, + impl_version: 1, + apis: sp_version::create_apis_vec!([]), + transaction_version: 1, + system_version: 1, + }; +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type Block = Block; + type Version = Version; + type OnSetCode = ParachainSetCode; + type RuntimeEvent = (); +} + +impl crate::Config for Test {} + +impl pallet_aura::Config for Test { + type AuthorityId = sp_consensus_aura::sr25519::AuthorityId; + type MaxAuthorities = ConstU32<100_000>; + type DisabledValidators = (); + type AllowMultipleBlocksPerSlot = ConstBool; + type SlotDuration = ConstU64<6000>; +} + +impl pallet_timestamp::Config for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = (); + type WeightInfo = (); +} + +impl cumulus_pallet_parachain_system::Config for Test { + type WeightInfo = (); + type RuntimeEvent = (); + type OnSystemEvent = (); + type SelfParaId = (); + type OutboundXcmpMessageSource = (); + // Ignore all DMP messages by enqueueing them into `()`: + type DmpQueue = EnqueueWithOrigin<(), sp_core::ConstU8<0>>; + type ReservedDmpWeight = (); + type XcmpMessageHandler = (); + type ReservedXcmpWeight = (); + type CheckAssociatedRelayNumber = AnyRelayNumber; + type ConsensusHook = ExpectParentIncluded; + type SelectCore = DefaultCoreSelector; +} + +#[cfg(test)] +mod test { + use crate::test::*; + use cumulus_pallet_parachain_system::{ + Ancestor, ConsensusHook, RelayChainStateProof, UsedBandwidth, + }; + use sp_core::H256; + + fn set_ancestors() { + let mut ancestors = Vec::new(); + for i in 0..3 { + let mut ancestor = Ancestor::new_unchecked(UsedBandwidth::default(), None); + ancestor.replace_para_head_hash(H256::repeat_byte(i + 1)); + ancestors.push(ancestor); + } + cumulus_pallet_parachain_system::UnincludedSegment::::put(ancestors); + } + + pub fn new_test_ext(para_slot: u64) -> sp_io::TestExternalities { + let mut ext = TestExternalities::new_empty(); + ext.execute_with(|| { + set_ancestors(); + // Set initial parachain slot + pallet_aura::CurrentSlot::::put(Slot::from(para_slot)); + }); + ext + } + + fn set_relay_slot(slot: u64, authored: u32) { + RelaySlotInfo::::put((Slot::from(slot), authored)) + } + + fn relay_chain_state_proof(relay_slot: u64) -> RelayChainStateProof { + let mut builder = cumulus_test_relay_sproof_builder::RelayStateSproofBuilder::default(); + builder.current_slot = relay_slot.into(); + + let (hash, state_proof) = builder.into_state_root_and_proof(); + + RelayChainStateProof::new(ParaId::from(200), hash, state_proof) + .expect("Should be able to construct state proof.") + } + + fn assert_slot_info(expected_slot: u64, expected_authored: u32) { + let (slot, authored) = pallet::RelaySlotInfo::::get().unwrap(); + assert_eq!(slot, Slot::from(expected_slot), "Slot stored in RelaySlotInfo is incorrect."); + assert_eq!( + authored, expected_authored, + "Number of authored blocks stored in RelaySlotInfo is incorrect." + ); + } + + #[test] + fn test_velocity() { + type Hook = FixedVelocityConsensusHook; + + new_test_ext(1).execute_with(|| { + let state_proof = relay_chain_state_proof(10); + let (_, capacity) = Hook::on_state_proof(&state_proof); + assert_eq!(capacity, NonZeroU32::new(1).unwrap().into()); + assert_slot_info(10, 1); + + let (_, capacity) = Hook::on_state_proof(&state_proof); + assert_eq!(capacity, NonZeroU32::new(1).unwrap().into()); + assert_slot_info(10, 2); + }); + } + + #[test] + #[should_panic(expected = "authored blocks limit is reached for the slot")] + fn test_exceeding_velocity_limit() { + const VELOCITY: u32 = 2; + type Hook = FixedVelocityConsensusHook; + + new_test_ext(1).execute_with(|| { + let state_proof = relay_chain_state_proof(10); + for authored in 0..=VELOCITY + 1 { + Hook::on_state_proof(&state_proof); + assert_slot_info(10, authored + 1); + } + }); + } + + #[test] + fn test_para_slot_calculated_from_slot_duration() { + const VELOCITY: u32 = 2; + type Hook = FixedVelocityConsensusHook; + + new_test_ext(6).execute_with(|| { + let state_proof = relay_chain_state_proof(10); + Hook::on_state_proof(&state_proof); + + let para_slot = Slot::from(7); + pallet_aura::CurrentSlot::::put(para_slot); + Hook::on_state_proof(&state_proof); + }); + } + + #[test] + fn test_velocity_at_least_one() { + // Even though this is 0, one block should always be allowed. + const VELOCITY: u32 = 0; + type Hook = FixedVelocityConsensusHook; + + new_test_ext(6).execute_with(|| { + let state_proof = relay_chain_state_proof(10); + Hook::on_state_proof(&state_proof); + }); + } + + #[test] + #[should_panic( + expected = "Parachain slot is too far in the future: parachain_slot=Slot(8), derived_from_relay_slot=Slot(5) velocity=2" + )] + fn test_para_slot_calculated_from_slot_duration_2() { + const VELOCITY: u32 = 2; + type Hook = FixedVelocityConsensusHook; + + new_test_ext(8).execute_with(|| { + let state_proof = relay_chain_state_proof(10); + let (_, _) = Hook::on_state_proof(&state_proof); + }); + } + + #[test] + fn test_velocity_resets_on_new_relay_slot() { + const VELOCITY: u32 = 2; + type Hook = FixedVelocityConsensusHook; + + new_test_ext(1).execute_with(|| { + let state_proof = relay_chain_state_proof(10); + for authored in 0..=VELOCITY { + Hook::on_state_proof(&state_proof); + assert_slot_info(10, authored + 1); + } + + let state_proof = relay_chain_state_proof(11); + for authored in 0..=VELOCITY { + Hook::on_state_proof(&state_proof); + assert_slot_info(11, authored + 1); + } + }); + } + + #[test] + #[should_panic( + expected = "Slot moved backwards: stored_slot=Slot(10), relay_chain_slot=Slot(9)" + )] + fn test_backward_relay_slot_not_tolerated() { + type Hook = FixedVelocityConsensusHook; + + new_test_ext(1).execute_with(|| { + let state_proof = relay_chain_state_proof(10); + Hook::on_state_proof(&state_proof); + assert_slot_info(10, 1); + + let state_proof = relay_chain_state_proof(9); + Hook::on_state_proof(&state_proof); + }); + } + + #[test] + #[should_panic( + expected = "Parachain slot is too far in the future: parachain_slot=Slot(13), derived_from_relay_slot=Slot(10) velocity=2" + )] + fn test_future_parachain_slot_errors() { + type Hook = FixedVelocityConsensusHook; + + new_test_ext(13).execute_with(|| { + let state_proof = relay_chain_state_proof(10); + Hook::on_state_proof(&state_proof); + }); + } + + #[test] + fn test_can_build_upon_true_when_empty() { + const VELOCITY: u32 = 2; + type Hook = FixedVelocityConsensusHook; + + new_test_ext(1).execute_with(|| { + let hash = H256::repeat_byte(0x1); + assert!(Hook::can_build_upon(hash, Slot::from(1))); + }); + } + + #[test] + fn test_can_build_upon_respects_velocity() { + const VELOCITY: u32 = 2; + type Hook = FixedVelocityConsensusHook; + + new_test_ext(1).execute_with(|| { + let hash = H256::repeat_byte(0x1); + let relay_slot = Slot::from(10); + + set_relay_slot(10, VELOCITY - 1); + assert!(Hook::can_build_upon(hash, relay_slot)); + + set_relay_slot(10, VELOCITY); + assert!(Hook::can_build_upon(hash, relay_slot)); + + set_relay_slot(10, VELOCITY + 1); + // Velocity too high + assert!(!Hook::can_build_upon(hash, relay_slot)); + }); + } + + #[test] + fn test_can_build_upon_slot_can_not_decrease() { + const VELOCITY: u32 = 2; + type Hook = FixedVelocityConsensusHook; + + new_test_ext(1).execute_with(|| { + let hash = H256::repeat_byte(0x1); + + set_relay_slot(10, VELOCITY); + // Slot moves backwards + assert!(!Hook::can_build_upon(hash, Slot::from(9))); + }); + } + + #[test] + fn test_can_build_upon_unincluded_segment_size() { + const VELOCITY: u32 = 2; + type Hook = FixedVelocityConsensusHook; + + new_test_ext(1).execute_with(|| { + let relay_slot = Slot::from(10); + + set_relay_slot(10, VELOCITY); + // Size after included is two, we can not build + let hash = H256::repeat_byte(0x1); + assert!(!Hook::can_build_upon(hash, relay_slot)); + + // Size after included is one, we can build + let hash = H256::repeat_byte(0x2); + assert!(Hook::can_build_upon(hash, relay_slot)); + }); + } +} diff --git a/cumulus/pallets/parachain-system/src/consensus_hook.rs b/cumulus/pallets/parachain-system/src/consensus_hook.rs index 3062396a4e78..6d65bdc77186 100644 --- a/cumulus/pallets/parachain-system/src/consensus_hook.rs +++ b/cumulus/pallets/parachain-system/src/consensus_hook.rs @@ -22,7 +22,7 @@ use core::num::NonZeroU32; use frame_support::weights::Weight; /// The possible capacity of the unincluded segment. -#[derive(Clone)] +#[derive(Clone, Debug, PartialEq)] pub struct UnincludedSegmentCapacity(UnincludedSegmentCapacityInner); impl UnincludedSegmentCapacity { @@ -41,7 +41,7 @@ impl UnincludedSegmentCapacity { } } -#[derive(Clone)] +#[derive(Clone, Debug, PartialEq)] pub(crate) enum UnincludedSegmentCapacityInner { ExpectParentIncluded, Value(NonZeroU32), diff --git a/cumulus/pallets/parachain-system/src/lib.rs b/cumulus/pallets/parachain-system/src/lib.rs index 0fa759357f65..6857b08e66b7 100644 --- a/cumulus/pallets/parachain-system/src/lib.rs +++ b/cumulus/pallets/parachain-system/src/lib.rs @@ -80,8 +80,7 @@ pub mod relay_state_snapshot; pub mod validate_block; use unincluded_segment::{ - Ancestor, HrmpChannelUpdate, HrmpWatermarkUpdate, OutboundBandwidthLimits, SegmentTracker, - UsedBandwidth, + HrmpChannelUpdate, HrmpWatermarkUpdate, OutboundBandwidthLimits, SegmentTracker, }; pub use consensus_hook::{ConsensusHook, ExpectParentIncluded}; @@ -109,6 +108,7 @@ pub use consensus_hook::{ConsensusHook, ExpectParentIncluded}; /// ``` pub use cumulus_pallet_parachain_system_proc_macro::register_validate_block; pub use relay_state_snapshot::{MessagingStateSnapshot, RelayChainStateProof}; +pub use unincluded_segment::{Ancestor, UsedBandwidth}; pub use pallet::*; 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 1db152e39fd9..db9a8201ebbe 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -1050,6 +1050,7 @@ pub type Migrations = ( >, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, + cumulus_pallet_aura_ext::migration::MigrateV0ToV1, ); parameter_types! { 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 71cfdc58cceb..cfc150ce5d6f 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -341,7 +341,6 @@ pub type LocalAndForeignAssets = fungibles::UnionOf< xcm::v5::Location, AccountId, >; - /// Union fungibles implementation for [`LocalAndForeignAssets`] and `Balances`. pub type NativeAndAssets = fungible::UnionOf< Balances, @@ -1129,6 +1128,7 @@ pub type Migrations = ( >, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, + cumulus_pallet_aura_ext::migration::MigrateV0ToV1, ); /// Asset Hub Westend has some undecodable storage, delete it. 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 35af034310d9..67bc06a9321e 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 @@ -184,6 +184,7 @@ pub type Migrations = ( pallet_bridge_relayers::migration::v1::MigrationToV1, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, + cumulus_pallet_aura_ext::migration::MigrateV0ToV1, ); parameter_types! { 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 2c2e01b4d21d..3824a4e9a7cb 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 @@ -171,6 +171,7 @@ pub type Migrations = ( bridge_to_ethereum_config::migrations::MigrationForXcmV5, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, + cumulus_pallet_aura_ext::migration::MigrateV0ToV1, ); parameter_types! { diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs index e9adc4d1eae7..5eafc2960cc8 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs @@ -770,6 +770,7 @@ type Migrations = ( pallet_core_fellowship::migration::MigrateV0ToV1, // unreleased pallet_core_fellowship::migration::MigrateV0ToV1, + cumulus_pallet_aura_ext::migration::MigrateV0ToV1, ); /// Executive: handles dispatch to the various modules. diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs index 3348a635df01..eaaaf0a9a9a7 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs @@ -118,6 +118,7 @@ pub type Migrations = ( cumulus_pallet_xcmp_queue::migration::v5::MigrateV4ToV5, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, + cumulus_pallet_aura_ext::migration::MigrateV0ToV1, ); type EventRecord = frame_system::EventRecord< diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs index e9171c79afae..622a40e1d8dc 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs @@ -129,6 +129,7 @@ pub type Migrations = ( pallet_broker::migration::MigrateV3ToV4, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, + cumulus_pallet_aura_ext::migration::MigrateV0ToV1, ); /// Executive: handles dispatch to the various modules. diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs index 975856b3b6ff..7312c9c1639d 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs @@ -129,6 +129,7 @@ pub type Migrations = ( pallet_broker::migration::MigrateV3ToV4, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, + cumulus_pallet_aura_ext::migration::MigrateV0ToV1, ); /// Executive: handles dispatch to the various modules. diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs index ffdd86c500e5..cb0282b17a6c 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs @@ -116,6 +116,7 @@ pub type Migrations = ( cumulus_pallet_xcmp_queue::migration::v5::MigrateV4ToV5, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, + cumulus_pallet_aura_ext::migration::MigrateV0ToV1, ); /// Executive: handles dispatch to the various modules. diff --git a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs index ee6b0db55b91..050256dd4f6a 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs @@ -115,6 +115,7 @@ pub type Migrations = ( pallet_collator_selection::migration::v2::MigrationToV2, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, + cumulus_pallet_aura_ext::migration::MigrateV0ToV1, ); /// Executive: handles dispatch to the various modules. diff --git a/cumulus/primitives/aura/src/lib.rs b/cumulus/primitives/aura/src/lib.rs index aeeee5f8bafa..4e7d7dc3e79d 100644 --- a/cumulus/primitives/aura/src/lib.rs +++ b/cumulus/primitives/aura/src/lib.rs @@ -34,10 +34,14 @@ sp_api::decl_runtime_apis! { /// When the unincluded segment is short, Aura chains will allow authors to create multiple /// blocks per slot in order to build a backlog. When it is saturated, this API will limit /// the amount of blocks that can be created. + /// + /// Changes: + /// - Version 2: Update to `can_build_upon` to take a relay chain `Slot` instead of a parachain `Slot`. + #[api_version(2)] pub trait AuraUnincludedSegmentApi { /// Whether it is legal to extend the chain, assuming the given block is the most /// recently included one as-of the relay parent that will be built against, and - /// the given slot. + /// the given relay chain slot. /// /// This should be consistent with the logic the runtime uses when validating blocks to /// avoid issues. diff --git a/cumulus/xcm/xcm-emulator/src/lib.rs b/cumulus/xcm/xcm-emulator/src/lib.rs index ff14b747973c..d9b1e7fd9d04 100644 --- a/cumulus/xcm/xcm-emulator/src/lib.rs +++ b/cumulus/xcm/xcm-emulator/src/lib.rs @@ -1118,6 +1118,7 @@ macro_rules! decl_test_networks { ) -> $crate::ParachainInherentData { let mut sproof = $crate::RelayStateSproofBuilder::default(); sproof.para_id = para_id.into(); + sproof.current_slot = $crate::polkadot_primitives::Slot::from(relay_parent_number as u64); // egress channel let e_index = sproof.hrmp_egress_channel_index.get_or_insert_with(Vec::new); diff --git a/prdoc/pr_6825.prdoc b/prdoc/pr_6825.prdoc new file mode 100644 index 000000000000..d57b2b573a10 --- /dev/null +++ b/prdoc/pr_6825.prdoc @@ -0,0 +1,50 @@ +# 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: Use relay chain slot for velocity measurement on parachains + +doc: + - audience: Runtime Dev + description: | + The AuraExt pallets `ConsensusHook` is performing checks based on a parachains velocity. It was previously + checking how many blocks where produced in a given parachain slot. This only works well when the parachain + and relay chain slot length is the same. After this PR, we are checking against the relay chain slot. + + **🚨 Action Required:** A migration of name `cumulus_pallet_aura_ext::migration::MigrateV0ToV1` is included + that cleans up a renamed storage item. Parachain must add it to their runtimes. More information is available in + the [reference docs](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/frame_runtime_upgrades_and_migrations/index.html#single-block-migrations). + +crates: + - name: cumulus-pallet-parachain-system + bump: minor + - name: cumulus-pallet-aura-ext + bump: major + - name: cumulus-primitives-aura + bump: major + - name: cumulus-client-parachain-inherent + bump: minor + - name: cumulus-client-consensus-aura + bump: minor + - name: xcm-emulator + bump: minor + - name: asset-hub-rococo-runtime + bump: minor + - name: asset-hub-westend-runtime + bump: minor + - name: bridge-hub-rococo-runtime + bump: minor + - name: bridge-hub-westend-runtime + bump: minor + - name: collectives-westend-runtime + bump: minor + - name: coretime-rococo-runtime + bump: minor + - name: coretime-westend-runtime + bump: minor + - name: people-rococo-runtime + bump: minor + - name: people-westend-runtime + bump: minor + - name: contracts-rococo-runtime + bump: minor + From 0d660a420fbc11a90cde5aa4e43ce2027b502162 Mon Sep 17 00:00:00 2001 From: Alexandru Gheorghe <49718502+alexggh@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:13:23 +0200 Subject: [PATCH 06/17] approval-voting: Make importing of duplicate assignment idempotent (#6971) Normally, approval-voting wouldn't receive duplicate assignments because approval-distribution makes sure of it, however in the situation where we restart we might receive the same assignment again and since approval-voting already persisted it we will end up inserting it twice in `ApprovalEntry.tranches.assignments` because that's an array. Fix this by making sure duplicate assignments are a noop if the validator already had an assignment imported at the same tranche. --------- Signed-off-by: Alexandru Gheorghe Co-authored-by: ordian --- .../approval-voting/src/approval_checking.rs | 78 ++++++++++++------- polkadot/node/core/approval-voting/src/lib.rs | 11 ++- .../approval-voting/src/persisted_entries.rs | 14 +++- prdoc/pr_6971.prdoc | 16 ++++ 4 files changed, 86 insertions(+), 33 deletions(-) create mode 100644 prdoc/pr_6971.prdoc diff --git a/polkadot/node/core/approval-voting/src/approval_checking.rs b/polkadot/node/core/approval-voting/src/approval_checking.rs index 3b7262a46826..c7f38619ea1f 100644 --- a/polkadot/node/core/approval-voting/src/approval_checking.rs +++ b/polkadot/node/core/approval-voting/src/approval_checking.rs @@ -712,13 +712,13 @@ mod tests { } .into(); - approval_entry.import_assignment(0, ValidatorIndex(0), block_tick); - approval_entry.import_assignment(0, ValidatorIndex(1), block_tick); + approval_entry.import_assignment(0, ValidatorIndex(0), block_tick, false); + approval_entry.import_assignment(0, ValidatorIndex(1), block_tick, false); - approval_entry.import_assignment(1, ValidatorIndex(2), block_tick + 1); - approval_entry.import_assignment(1, ValidatorIndex(3), block_tick + 1); + approval_entry.import_assignment(1, ValidatorIndex(2), block_tick + 1, false); + approval_entry.import_assignment(1, ValidatorIndex(3), block_tick + 1, false); - approval_entry.import_assignment(2, ValidatorIndex(4), block_tick + 2); + approval_entry.import_assignment(2, ValidatorIndex(4), block_tick + 2, false); let approvals = bitvec![u8, BitOrderLsb0; 1; 5]; @@ -757,8 +757,10 @@ mod tests { } .into(); - approval_entry.import_assignment(0, ValidatorIndex(0), block_tick); - approval_entry.import_assignment(1, ValidatorIndex(2), block_tick); + approval_entry.import_assignment(0, ValidatorIndex(0), block_tick, false); + approval_entry.import_assignment(0, ValidatorIndex(0), block_tick, true); + approval_entry.import_assignment(1, ValidatorIndex(2), block_tick, false); + approval_entry.import_assignment(1, ValidatorIndex(2), block_tick, true); let approvals = bitvec![u8, BitOrderLsb0; 0; 10]; @@ -798,10 +800,10 @@ mod tests { } .into(); - approval_entry.import_assignment(0, ValidatorIndex(0), block_tick); - approval_entry.import_assignment(0, ValidatorIndex(1), block_tick); + approval_entry.import_assignment(0, ValidatorIndex(0), block_tick, false); + approval_entry.import_assignment(0, ValidatorIndex(1), block_tick, false); - approval_entry.import_assignment(1, ValidatorIndex(2), block_tick); + approval_entry.import_assignment(1, ValidatorIndex(2), block_tick, false); let mut approvals = bitvec![u8, BitOrderLsb0; 0; 10]; approvals.set(0, true); @@ -844,11 +846,11 @@ mod tests { } .into(); - approval_entry.import_assignment(0, ValidatorIndex(0), block_tick); - approval_entry.import_assignment(0, ValidatorIndex(1), block_tick); + approval_entry.import_assignment(0, ValidatorIndex(0), block_tick, false); + approval_entry.import_assignment(0, ValidatorIndex(1), block_tick, false); - approval_entry.import_assignment(1, ValidatorIndex(2), block_tick); - approval_entry.import_assignment(1, ValidatorIndex(3), block_tick); + approval_entry.import_assignment(1, ValidatorIndex(2), block_tick, false); + approval_entry.import_assignment(1, ValidatorIndex(3), block_tick, false); let mut approvals = bitvec![u8, BitOrderLsb0; 0; n_validators]; approvals.set(0, true); @@ -913,14 +915,24 @@ mod tests { } .into(); - approval_entry.import_assignment(0, ValidatorIndex(0), block_tick); - approval_entry.import_assignment(0, ValidatorIndex(1), block_tick); + approval_entry.import_assignment(0, ValidatorIndex(0), block_tick, false); + approval_entry.import_assignment(0, ValidatorIndex(1), block_tick, false); - approval_entry.import_assignment(1, ValidatorIndex(2), block_tick + 1); - approval_entry.import_assignment(1, ValidatorIndex(3), block_tick + 1); + approval_entry.import_assignment(1, ValidatorIndex(2), block_tick + 1, false); + approval_entry.import_assignment(1, ValidatorIndex(3), block_tick + 1, false); - approval_entry.import_assignment(2, ValidatorIndex(4), block_tick + no_show_duration + 2); - approval_entry.import_assignment(2, ValidatorIndex(5), block_tick + no_show_duration + 2); + approval_entry.import_assignment( + 2, + ValidatorIndex(4), + block_tick + no_show_duration + 2, + false, + ); + approval_entry.import_assignment( + 2, + ValidatorIndex(5), + block_tick + no_show_duration + 2, + false, + ); let mut approvals = bitvec![u8, BitOrderLsb0; 0; n_validators]; approvals.set(0, true); @@ -1007,14 +1019,24 @@ mod tests { } .into(); - approval_entry.import_assignment(0, ValidatorIndex(0), block_tick); - approval_entry.import_assignment(0, ValidatorIndex(1), block_tick); + approval_entry.import_assignment(0, ValidatorIndex(0), block_tick, false); + approval_entry.import_assignment(0, ValidatorIndex(1), block_tick, false); - approval_entry.import_assignment(1, ValidatorIndex(2), block_tick + 1); - approval_entry.import_assignment(1, ValidatorIndex(3), block_tick + 1); + approval_entry.import_assignment(1, ValidatorIndex(2), block_tick + 1, false); + approval_entry.import_assignment(1, ValidatorIndex(3), block_tick + 1, false); - approval_entry.import_assignment(2, ValidatorIndex(4), block_tick + no_show_duration + 2); - approval_entry.import_assignment(2, ValidatorIndex(5), block_tick + no_show_duration + 2); + approval_entry.import_assignment( + 2, + ValidatorIndex(4), + block_tick + no_show_duration + 2, + false, + ); + approval_entry.import_assignment( + 2, + ValidatorIndex(5), + block_tick + no_show_duration + 2, + false, + ); let mut approvals = bitvec![u8, BitOrderLsb0; 0; n_validators]; approvals.set(0, true); @@ -1066,7 +1088,7 @@ mod tests { }, ); - approval_entry.import_assignment(3, ValidatorIndex(6), block_tick); + approval_entry.import_assignment(3, ValidatorIndex(6), block_tick, false); approvals.set(6, true); let tranche_now = no_show_duration as DelayTranche + 3; @@ -1176,7 +1198,7 @@ mod tests { // Populate the requested tranches. The assignments aren't inspected in // this test. for &t in &test_tranche { - approval_entry.import_assignment(t, ValidatorIndex(0), 0) + approval_entry.import_assignment(t, ValidatorIndex(0), 0, false); } let filled_tranches = filled_tranche_iterator(approval_entry.tranches()); diff --git a/polkadot/node/core/approval-voting/src/lib.rs b/polkadot/node/core/approval-voting/src/lib.rs index b4c2a6afee0e..2deca5a1aba8 100644 --- a/polkadot/node/core/approval-voting/src/lib.rs +++ b/polkadot/node/core/approval-voting/src/lib.rs @@ -2808,8 +2808,15 @@ where Vec::new(), )), }; - is_duplicate &= approval_entry.is_assigned(assignment.validator); - approval_entry.import_assignment(tranche, assignment.validator, tick_now); + + let is_duplicate_for_candidate = approval_entry.is_assigned(assignment.validator); + is_duplicate &= is_duplicate_for_candidate; + approval_entry.import_assignment( + tranche, + assignment.validator, + tick_now, + is_duplicate_for_candidate, + ); // We've imported a new assignment, so we need to schedule a wake-up for when that might // no-show. diff --git a/polkadot/node/core/approval-voting/src/persisted_entries.rs b/polkadot/node/core/approval-voting/src/persisted_entries.rs index a5d42d9fd6e6..14c678913dc3 100644 --- a/polkadot/node/core/approval-voting/src/persisted_entries.rs +++ b/polkadot/node/core/approval-voting/src/persisted_entries.rs @@ -172,7 +172,7 @@ impl ApprovalEntry { }); our.map(|a| { - self.import_assignment(a.tranche(), a.validator_index(), tick_now); + self.import_assignment(a.tranche(), a.validator_index(), tick_now, false); (a.cert().clone(), a.validator_index(), a.tranche()) }) @@ -197,6 +197,7 @@ impl ApprovalEntry { tranche: DelayTranche, validator_index: ValidatorIndex, tick_now: Tick, + is_duplicate: bool, ) { // linear search probably faster than binary. not many tranches typically. let idx = match self.tranches.iter().position(|t| t.tranche >= tranche) { @@ -213,8 +214,15 @@ impl ApprovalEntry { self.tranches.len() - 1 }, }; - - self.tranches[idx].assignments.push((validator_index, tick_now)); + // At restart we might have duplicate assignments because approval-distribution is not + // persistent across restarts, so avoid adding duplicates. + // We already know if we have seen an assignment from this validator and since this + // function is on the hot path we can avoid iterating through tranches by using + // !is_duplicate to determine if it is already present in the vector and does not need + // adding. + if !is_duplicate { + self.tranches[idx].assignments.push((validator_index, tick_now)); + } self.assigned_validators.set(validator_index.0 as _, true); } diff --git a/prdoc/pr_6971.prdoc b/prdoc/pr_6971.prdoc new file mode 100644 index 000000000000..4790d773fee4 --- /dev/null +++ b/prdoc/pr_6971.prdoc @@ -0,0 +1,16 @@ +# 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: Make importing of duplicate assignment idempotent + +doc: + - audience: Node Dev + description: | + Normally, approval-voting wouldn't receive duplicate assignments because approval-distribution makes + sure of it, however in the situation where we restart we might receive the same assignment again and + since approval-voting already persisted it we will end up inserting it twice in ApprovalEntry.tranches.assignments + because that's an array. Fix this by inserting only assignments that are not duplicate. + +crates: + - name: polkadot-node-core-approval-voting + bump: minor From f798111afc15f464a772cd7ed37910cc6208b713 Mon Sep 17 00:00:00 2001 From: Sebastian Kunert Date: Wed, 15 Jan 2025 11:08:49 +0100 Subject: [PATCH 07/17] Fix reversed error message in DispatchInfo (#7170) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix error message in `DispatchInfo` where post-dispatch and pre-dispatch weight was reversed. --------- Co-authored-by: command-bot <> Co-authored-by: Bastian Köcher --- prdoc/pr_7170.prdoc | 8 ++++++++ substrate/frame/support/src/dispatch.rs | 6 ++---- 2 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 prdoc/pr_7170.prdoc diff --git a/prdoc/pr_7170.prdoc b/prdoc/pr_7170.prdoc new file mode 100644 index 000000000000..fae908f7407d --- /dev/null +++ b/prdoc/pr_7170.prdoc @@ -0,0 +1,8 @@ +title: Fix reversed error message in DispatchInfo +doc: +- audience: Runtime Dev + description: "Fix error message in `DispatchInfo` where post-dispatch and pre-dispatch\ + \ weight was reversed.\r\n" +crates: +- name: frame-support + bump: patch diff --git a/substrate/frame/support/src/dispatch.rs b/substrate/frame/support/src/dispatch.rs index 990996830030..14bc2667def1 100644 --- a/substrate/frame/support/src/dispatch.rs +++ b/substrate/frame/support/src/dispatch.rs @@ -315,10 +315,8 @@ impl PostDispatchInfo { "Post dispatch weight is greater than pre dispatch weight. \ Pre dispatch weight may underestimating the actual weight. \ Greater post dispatch weight components are ignored. - Pre dispatch weight: {:?}, - Post dispatch weight: {:?}", - actual_weight, - info_total_weight, + Pre dispatch weight: {info_total_weight:?}, + Post dispatch weight: {actual_weight:?}", ); } actual_weight.min(info.total_weight()) From b72e76fba819e8029df27d127c57e3d6f532f1b8 Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Wed, 15 Jan 2025 18:57:37 +0800 Subject: [PATCH 08/17] Add "run to block" tools (#7109) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce `frame_system::Pallet::run_to_block`, `frame_system::Pallet::run_to_block_with`, and `frame_system::RunToBlockHooks` to establish a generic `run_to_block` mechanism for mock tests, minimizing redundant implementations across various pallets. Closes #299. --- Polkadot address: 156HGo9setPcU2qhFMVWLkcmtCEGySLwNqa3DaEiYSWtte4Y --------- Signed-off-by: Xavier Lau Co-authored-by: Bastian Köcher Co-authored-by: command-bot <> Co-authored-by: Guillaume Thiolliere Co-authored-by: Guillaume Thiolliere --- cumulus/pallets/dmp-queue/src/tests.rs | 20 +- .../runtime/common/src/assigned_slots/mod.rs | 89 ++--- polkadot/runtime/common/src/auctions/mock.rs | 15 +- polkadot/runtime/common/src/auctions/tests.rs | 134 +++---- polkadot/runtime/common/src/crowdloan/mod.rs | 57 +-- .../runtime/common/src/integration_tests.rs | 16 +- .../common/src/paras_registrar/mock.rs | 40 +- polkadot/runtime/common/src/slots/mod.rs | 66 ++-- prdoc/pr_7109.prdoc | 11 + .../multi-block-migrations/src/mock.rs | 21 +- substrate/frame/fast-unstake/src/mock.rs | 29 +- substrate/frame/identity/src/tests.rs | 22 +- substrate/frame/lottery/src/mock.rs | 18 +- substrate/frame/lottery/src/tests.rs | 25 +- substrate/frame/migrations/src/mock.rs | 31 +- substrate/frame/nis/src/mock.rs | 14 +- substrate/frame/nis/src/tests.rs | 90 ++--- substrate/frame/nomination-pools/src/mock.rs | 13 +- substrate/frame/recovery/src/mock.rs | 16 +- substrate/frame/recovery/src/tests.rs | 22 +- substrate/frame/root-offences/src/mock.rs | 18 +- substrate/frame/scheduler/src/mock.rs | 10 +- substrate/frame/scheduler/src/tests.rs | 360 +++++++++--------- substrate/frame/society/src/mock.rs | 23 +- substrate/frame/society/src/tests.rs | 12 +- substrate/frame/src/lib.rs | 2 +- substrate/frame/staking/src/mock.rs | 47 +-- .../frame/state-trie-migration/src/lib.rs | 15 +- substrate/frame/system/src/lib.rs | 111 ++++++ .../frame/transaction-storage/src/mock.rs | 25 +- 30 files changed, 650 insertions(+), 722 deletions(-) create mode 100644 prdoc/pr_7109.prdoc diff --git a/cumulus/pallets/dmp-queue/src/tests.rs b/cumulus/pallets/dmp-queue/src/tests.rs index 70d542ea2ed2..368a1c0b4364 100644 --- a/cumulus/pallets/dmp-queue/src/tests.rs +++ b/cumulus/pallets/dmp-queue/src/tests.rs @@ -21,11 +21,7 @@ use super::{migration::*, mock::*}; use crate::*; -use frame_support::{ - pallet_prelude::*, - traits::{OnFinalize, OnIdle, OnInitialize}, - StorageNoopGuard, -}; +use frame_support::{pallet_prelude::*, traits::OnIdle, StorageNoopGuard}; #[test] fn migration_works() { @@ -183,14 +179,12 @@ fn migration_too_long_ignored() { } fn run_to_block(n: u64) { - assert!(n > System::block_number(), "Cannot go back in time"); - - while System::block_number() < n { - AllPalletsWithSystem::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - AllPalletsWithSystem::on_initialize(System::block_number()); - AllPalletsWithSystem::on_idle(System::block_number(), Weight::MAX); - } + System::run_to_block_with::( + n, + frame_system::RunToBlockHooks::default().after_initialize(|bn| { + AllPalletsWithSystem::on_idle(bn, Weight::MAX); + }), + ); } fn assert_only_event(e: Event) { diff --git a/polkadot/runtime/common/src/assigned_slots/mod.rs b/polkadot/runtime/common/src/assigned_slots/mod.rs index 65942c127b1c..dea29f53cad4 100644 --- a/polkadot/runtime/common/src/assigned_slots/mod.rs +++ b/polkadot/runtime/common/src/assigned_slots/mod.rs @@ -788,39 +788,14 @@ mod tests { t.into() } - fn run_to_block(n: BlockNumber) { - while System::block_number() < n { - let mut block = System::block_number(); - // on_finalize hooks - AssignedSlots::on_finalize(block); - Slots::on_finalize(block); - Parachains::on_finalize(block); - ParasShared::on_finalize(block); - Configuration::on_finalize(block); - Balances::on_finalize(block); - System::on_finalize(block); - // Set next block - System::set_block_number(block + 1); - block = System::block_number(); - // on_initialize hooks - System::on_initialize(block); - Balances::on_initialize(block); - Configuration::on_initialize(block); - ParasShared::on_initialize(block); - Parachains::on_initialize(block); - Slots::on_initialize(block); - AssignedSlots::on_initialize(block); - } - } - #[test] fn basic_setup_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_eq!(AssignedSlots::current_lease_period_index(), 0); assert_eq!(Slots::deposit_held(1.into(), &1), 0); - run_to_block(3); + System::run_to_block::(3); assert_eq!(AssignedSlots::current_lease_period_index(), 1); }); } @@ -828,7 +803,7 @@ mod tests { #[test] fn assign_perm_slot_fails_for_unknown_para() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_noop!( AssignedSlots::assign_perm_parachain_slot( @@ -843,7 +818,7 @@ mod tests { #[test] fn assign_perm_slot_fails_for_invalid_origin() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_noop!( AssignedSlots::assign_perm_parachain_slot( @@ -858,7 +833,7 @@ mod tests { #[test] fn assign_perm_slot_fails_when_not_parathread() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(TestRegistrar::::register( 1, @@ -881,7 +856,7 @@ mod tests { #[test] fn assign_perm_slot_fails_when_existing_lease() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(TestRegistrar::::register( 1, @@ -920,7 +895,7 @@ mod tests { #[test] fn assign_perm_slot_fails_when_max_perm_slots_exceeded() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(TestRegistrar::::register( 1, @@ -967,7 +942,7 @@ mod tests { fn assign_perm_slot_succeeds_for_parathread() { new_test_ext().execute_with(|| { let mut block = 1; - run_to_block(block); + System::run_to_block::(block); assert_ok!(TestRegistrar::::register( 1, ParaId::from(1_u32), @@ -1000,7 +975,7 @@ mod tests { assert_eq!(Slots::already_leased(ParaId::from(1_u32), 0, 2), true); block += 1; - run_to_block(block); + System::run_to_block::(block); } // Para lease ended, downgraded back to parathread (on-demand parachain) @@ -1012,7 +987,7 @@ mod tests { #[test] fn assign_temp_slot_fails_for_unknown_para() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_noop!( AssignedSlots::assign_temp_parachain_slot( @@ -1028,7 +1003,7 @@ mod tests { #[test] fn assign_temp_slot_fails_for_invalid_origin() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_noop!( AssignedSlots::assign_temp_parachain_slot( @@ -1044,7 +1019,7 @@ mod tests { #[test] fn assign_temp_slot_fails_when_not_parathread() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(TestRegistrar::::register( 1, @@ -1068,7 +1043,7 @@ mod tests { #[test] fn assign_temp_slot_fails_when_existing_lease() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(TestRegistrar::::register( 1, @@ -1109,7 +1084,7 @@ mod tests { #[test] fn assign_temp_slot_fails_when_max_temp_slots_exceeded() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); // Register 6 paras & a temp slot for each for n in 0..=5 { @@ -1151,7 +1126,7 @@ mod tests { fn assign_temp_slot_succeeds_for_single_parathread() { new_test_ext().execute_with(|| { let mut block = 1; - run_to_block(block); + System::run_to_block::(block); assert_ok!(TestRegistrar::::register( 1, ParaId::from(1_u32), @@ -1195,7 +1170,7 @@ mod tests { assert_eq!(Slots::already_leased(ParaId::from(1_u32), 0, 1), true); block += 1; - run_to_block(block); + System::run_to_block::(block); } // Block 6 @@ -1210,7 +1185,7 @@ mod tests { // Block 12 // Para should get a turn after TemporarySlotLeasePeriodLength * LeasePeriod blocks - run_to_block(12); + System::run_to_block::(12); println!("block #{}", block); println!("lease period #{}", AssignedSlots::current_lease_period_index()); println!("lease {:?}", slots::Leases::::get(ParaId::from(1_u32))); @@ -1225,7 +1200,7 @@ mod tests { fn assign_temp_slot_succeeds_for_multiple_parathreads() { new_test_ext().execute_with(|| { // Block 1, Period 0 - run_to_block(1); + System::run_to_block::(1); // Register 6 paras & a temp slot for each // (3 slots in current lease period, 3 in the next one) @@ -1251,7 +1226,7 @@ mod tests { // Block 1-5, Period 0-1 for n in 1..=5 { if n > 1 { - run_to_block(n); + System::run_to_block::(n); } assert_eq!(TestRegistrar::::is_parachain(ParaId::from(0)), true); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(1_u32)), false); @@ -1264,7 +1239,7 @@ mod tests { // Block 6-11, Period 2-3 for n in 6..=11 { - run_to_block(n); + System::run_to_block::(n); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(0)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(1_u32)), true); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(2_u32)), false); @@ -1276,7 +1251,7 @@ mod tests { // Block 12-17, Period 4-5 for n in 12..=17 { - run_to_block(n); + System::run_to_block::(n); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(0)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(1_u32)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(2_u32)), false); @@ -1288,7 +1263,7 @@ mod tests { // Block 18-23, Period 6-7 for n in 18..=23 { - run_to_block(n); + System::run_to_block::(n); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(0)), true); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(1_u32)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(2_u32)), true); @@ -1300,7 +1275,7 @@ mod tests { // Block 24-29, Period 8-9 for n in 24..=29 { - run_to_block(n); + System::run_to_block::(n); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(0)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(1_u32)), true); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(2_u32)), false); @@ -1312,7 +1287,7 @@ mod tests { // Block 30-35, Period 10-11 for n in 30..=35 { - run_to_block(n); + System::run_to_block::(n); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(0)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(1_u32)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(2_u32)), false); @@ -1327,7 +1302,7 @@ mod tests { #[test] fn unassign_slot_fails_for_unknown_para() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_noop!( AssignedSlots::unassign_parachain_slot(RuntimeOrigin::root(), ParaId::from(1_u32),), @@ -1339,7 +1314,7 @@ mod tests { #[test] fn unassign_slot_fails_for_invalid_origin() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_noop!( AssignedSlots::assign_perm_parachain_slot( @@ -1354,7 +1329,7 @@ mod tests { #[test] fn unassign_perm_slot_succeeds() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(TestRegistrar::::register( 1, @@ -1386,7 +1361,7 @@ mod tests { #[test] fn unassign_temp_slot_succeeds() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(TestRegistrar::::register( 1, @@ -1419,7 +1394,7 @@ mod tests { #[test] fn set_max_permanent_slots_fails_for_no_root_origin() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_noop!( AssignedSlots::set_max_permanent_slots(RuntimeOrigin::signed(1), 5), @@ -1430,7 +1405,7 @@ mod tests { #[test] fn set_max_permanent_slots_succeeds() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_eq!(MaxPermanentSlots::::get(), 2); assert_ok!(AssignedSlots::set_max_permanent_slots(RuntimeOrigin::root(), 10),); @@ -1441,7 +1416,7 @@ mod tests { #[test] fn set_max_temporary_slots_fails_for_no_root_origin() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_noop!( AssignedSlots::set_max_temporary_slots(RuntimeOrigin::signed(1), 5), @@ -1452,7 +1427,7 @@ mod tests { #[test] fn set_max_temporary_slots_succeeds() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_eq!(MaxTemporarySlots::::get(), 6); assert_ok!(AssignedSlots::set_max_temporary_slots(RuntimeOrigin::root(), 12),); diff --git a/polkadot/runtime/common/src/auctions/mock.rs b/polkadot/runtime/common/src/auctions/mock.rs index 9fe19e579cfa..e0365d363ca2 100644 --- a/polkadot/runtime/common/src/auctions/mock.rs +++ b/polkadot/runtime/common/src/auctions/mock.rs @@ -20,8 +20,7 @@ use super::*; use crate::{auctions, mock::TestRegistrar}; use frame_support::{ - assert_ok, derive_impl, ord_parameter_types, parameter_types, - traits::{EitherOfDiverse, OnFinalize, OnInitialize}, + assert_ok, derive_impl, ord_parameter_types, parameter_types, traits::EitherOfDiverse, }; use frame_system::{EnsureRoot, EnsureSignedBy}; use pallet_balances; @@ -244,15 +243,3 @@ pub fn new_test_ext() -> sp_io::TestExternalities { }); ext } - -pub fn run_to_block(n: BlockNumber) { - while System::block_number() < n { - Auctions::on_finalize(System::block_number()); - Balances::on_finalize(System::block_number()); - System::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - Balances::on_initialize(System::block_number()); - Auctions::on_initialize(System::block_number()); - } -} diff --git a/polkadot/runtime/common/src/auctions/tests.rs b/polkadot/runtime/common/src/auctions/tests.rs index 07574eeb295d..26e2ac47df84 100644 --- a/polkadot/runtime/common/src/auctions/tests.rs +++ b/polkadot/runtime/common/src/auctions/tests.rs @@ -36,7 +36,7 @@ fn basic_setup_works() { AuctionStatus::::NotStarted ); - run_to_block(10); + System::run_to_block::(10); assert_eq!(AuctionCounter::::get(), 0); assert_eq!(TestLeaser::deposit_held(0u32.into(), &1), 0); @@ -50,7 +50,7 @@ fn basic_setup_works() { #[test] fn can_start_auction() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_noop!(Auctions::new_auction(RuntimeOrigin::signed(1), 5, 1), BadOrigin); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); @@ -66,7 +66,7 @@ fn can_start_auction() { #[test] fn bidding_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 5)); @@ -82,7 +82,7 @@ fn bidding_works() { #[test] fn under_bidding_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 5)); @@ -96,7 +96,7 @@ fn under_bidding_works() { #[test] fn over_bidding_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 5)); assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), 0.into(), 1, 1, 4, 6)); @@ -115,7 +115,7 @@ fn over_bidding_works() { #[test] fn auction_proceeds_correctly() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); @@ -125,49 +125,49 @@ fn auction_proceeds_correctly() { AuctionStatus::::StartingPeriod ); - run_to_block(2); + System::run_to_block::(2); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::StartingPeriod ); - run_to_block(3); + System::run_to_block::(3); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::StartingPeriod ); - run_to_block(4); + System::run_to_block::(4); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::StartingPeriod ); - run_to_block(5); + System::run_to_block::(5); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::StartingPeriod ); - run_to_block(6); + System::run_to_block::(6); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(0, 0) ); - run_to_block(7); + System::run_to_block::(7); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(1, 0) ); - run_to_block(8); + System::run_to_block::(8); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(2, 0) ); - run_to_block(9); + System::run_to_block::(9); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::NotStarted @@ -178,12 +178,12 @@ fn auction_proceeds_correctly() { #[test] fn can_win_auction() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 1)); assert_eq!(Balances::reserved_balance(1), 1); assert_eq!(Balances::free_balance(1), 9); - run_to_block(9); + System::run_to_block::(9); assert_eq!( leases(), @@ -201,7 +201,7 @@ fn can_win_auction() { #[test] fn can_win_auction_with_late_randomness() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 1)); assert_eq!(Balances::reserved_balance(1), 1); @@ -210,7 +210,7 @@ fn can_win_auction_with_late_randomness() { Auctions::auction_status(System::block_number()), AuctionStatus::::StartingPeriod ); - run_to_block(8); + System::run_to_block::(8); // Auction has not yet ended. assert_eq!(leases(), vec![]); assert_eq!( @@ -222,7 +222,7 @@ fn can_win_auction_with_late_randomness() { set_last_random(H256::zero(), 8); // Auction definitely ended now, but we don't know exactly when in the last 3 blocks yet // since no randomness available yet. - run_to_block(9); + System::run_to_block::(9); // Auction has now ended... But auction winner still not yet decided, so no leases yet. assert_eq!( Auctions::auction_status(System::block_number()), @@ -233,7 +233,7 @@ fn can_win_auction_with_late_randomness() { // Random seed now updated to a value known at block 9, when the auction ended. This // means that the winner can now be chosen. set_last_random(H256::zero(), 9); - run_to_block(10); + System::run_to_block::(10); // Auction ended and winner selected assert_eq!( Auctions::auction_status(System::block_number()), @@ -255,10 +255,10 @@ fn can_win_auction_with_late_randomness() { #[test] fn can_win_incomplete_auction() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 4, 4, 5)); - run_to_block(9); + System::run_to_block::(9); assert_eq!(leases(), vec![((0.into(), 4), LeaseData { leaser: 1, amount: 5 }),]); assert_eq!(TestLeaser::deposit_held(0.into(), &1), 5); @@ -268,13 +268,13 @@ fn can_win_incomplete_auction() { #[test] fn should_choose_best_combination() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 1, 1)); assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), 0.into(), 1, 2, 3, 4)); assert_ok!(Auctions::bid(RuntimeOrigin::signed(3), 0.into(), 1, 4, 4, 2)); assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 1.into(), 1, 1, 4, 2)); - run_to_block(9); + System::run_to_block::(9); assert_eq!( leases(), @@ -295,7 +295,7 @@ fn should_choose_best_combination() { #[test] fn gap_bid_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); // User 1 will make a bid for period 1 and 4 for the same Para 0 @@ -314,7 +314,7 @@ fn gap_bid_works() { assert_eq!(Balances::reserved_balance(3), 3); // End the auction. - run_to_block(9); + System::run_to_block::(9); assert_eq!( leases(), @@ -334,11 +334,11 @@ fn gap_bid_works() { #[test] fn deposit_credit_should_work() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 1, 5)); assert_eq!(Balances::reserved_balance(1), 5); - run_to_block(10); + System::run_to_block::(10); assert_eq!(leases(), vec![((0.into(), 1), LeaseData { leaser: 1, amount: 5 }),]); assert_eq!(TestLeaser::deposit_held(0.into(), &1), 5); @@ -347,7 +347,7 @@ fn deposit_credit_should_work() { assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 2, 2, 6)); // Only 1 reserved since we have a deposit credit of 5. assert_eq!(Balances::reserved_balance(1), 1); - run_to_block(20); + System::run_to_block::(20); assert_eq!( leases(), @@ -363,11 +363,11 @@ fn deposit_credit_should_work() { #[test] fn deposit_credit_on_alt_para_should_not_count() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 1, 5)); assert_eq!(Balances::reserved_balance(1), 5); - run_to_block(10); + System::run_to_block::(10); assert_eq!(leases(), vec![((0.into(), 1), LeaseData { leaser: 1, amount: 5 }),]); assert_eq!(TestLeaser::deposit_held(0.into(), &1), 5); @@ -376,7 +376,7 @@ fn deposit_credit_on_alt_para_should_not_count() { assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 1.into(), 2, 2, 2, 6)); // 6 reserved since we are bidding on a new para; only works because we don't assert_eq!(Balances::reserved_balance(1), 6); - run_to_block(20); + System::run_to_block::(20); assert_eq!( leases(), @@ -393,12 +393,12 @@ fn deposit_credit_on_alt_para_should_not_count() { #[test] fn multiple_bids_work_pre_ending() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); for i in 1..6u64 { - run_to_block(i as _); + System::run_to_block::(i as _); assert_ok!(Auctions::bid(RuntimeOrigin::signed(i), 0.into(), 1, 1, 4, i)); for j in 1..6 { assert_eq!(Balances::reserved_balance(j), if j == i { j } else { 0 }); @@ -406,7 +406,7 @@ fn multiple_bids_work_pre_ending() { } } - run_to_block(9); + System::run_to_block::(9); assert_eq!( leases(), vec![ @@ -422,12 +422,12 @@ fn multiple_bids_work_pre_ending() { #[test] fn multiple_bids_work_post_ending() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 0, 1)); for i in 1..6u64 { - run_to_block(((i - 1) / 2 + 1) as _); + System::run_to_block::(((i - 1) / 2 + 1) as _); assert_ok!(Auctions::bid(RuntimeOrigin::signed(i), 0.into(), 1, 1, 4, i)); for j in 1..6 { assert_eq!(Balances::reserved_balance(j), if j <= i { j } else { 0 }); @@ -438,7 +438,7 @@ fn multiple_bids_work_post_ending() { assert_eq!(ReservedAmounts::::get((i, ParaId::from(0))).unwrap(), i); } - run_to_block(5); + System::run_to_block::(5); assert_eq!( leases(), (1..=4) @@ -501,7 +501,7 @@ fn calculate_winners_works() { #[test] fn lower_bids_are_correctly_refunded() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 1, 1)); let para_1 = ParaId::from(1_u32); let para_2 = ParaId::from(2_u32); @@ -527,7 +527,7 @@ fn initialize_winners_in_ending_period_works() { new_test_ext().execute_with(|| { let ed: u64 = ::ExistentialDeposit::get(); assert_eq!(ed, 1); - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 9, 1)); let para_1 = ParaId::from(1_u32); let para_2 = ParaId::from(2_u32); @@ -546,20 +546,20 @@ fn initialize_winners_in_ending_period_works() { winning[SlotRange::TwoThree as u8 as usize] = Some((2, para_2, 19)); assert_eq!(Winning::::get(0), Some(winning)); - run_to_block(9); + System::run_to_block::(9); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::StartingPeriod ); - run_to_block(10); + System::run_to_block::(10); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(0, 0) ); assert_eq!(Winning::::get(0), Some(winning)); - run_to_block(11); + System::run_to_block::(11); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(1, 0) @@ -567,7 +567,7 @@ fn initialize_winners_in_ending_period_works() { assert_eq!(Winning::::get(1), Some(winning)); assert_ok!(Auctions::bid(RuntimeOrigin::signed(3), para_3, 1, 3, 4, 29)); - run_to_block(12); + System::run_to_block::(12); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(2, 0) @@ -580,7 +580,7 @@ fn initialize_winners_in_ending_period_works() { #[test] fn handle_bid_requires_registered_para() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); assert_noop!( Auctions::bid(RuntimeOrigin::signed(1), 1337.into(), 1, 1, 4, 1), @@ -599,12 +599,12 @@ fn handle_bid_requires_registered_para() { #[test] fn handle_bid_checks_existing_lease_periods() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 2, 3, 1)); assert_eq!(Balances::reserved_balance(1), 1); assert_eq!(Balances::free_balance(1), 9); - run_to_block(9); + System::run_to_block::(9); assert_eq!( leases(), @@ -644,7 +644,7 @@ fn less_winning_samples_work() { EndingPeriod::set(30); SampleLength::set(10); - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 9, 11)); let para_1 = ParaId::from(1_u32); let para_2 = ParaId::from(2_u32); @@ -663,13 +663,13 @@ fn less_winning_samples_work() { winning[SlotRange::TwoThree as u8 as usize] = Some((2, para_2, 19)); assert_eq!(Winning::::get(0), Some(winning)); - run_to_block(9); + System::run_to_block::(9); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::StartingPeriod ); - run_to_block(10); + System::run_to_block::(10); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(0, 0) @@ -681,19 +681,19 @@ fn less_winning_samples_work() { winning[SlotRange::ThreeThree as u8 as usize] = Some((3, para_3, 29)); assert_eq!(Winning::::get(0), Some(winning)); - run_to_block(20); + System::run_to_block::(20); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(1, 0) ); assert_eq!(Winning::::get(1), Some(winning)); - run_to_block(25); + System::run_to_block::(25); // Overbid mid sample assert_ok!(Auctions::bid(RuntimeOrigin::signed(3), para_3, 1, 13, 14, 29)); winning[SlotRange::TwoThree as u8 as usize] = Some((3, para_3, 29)); assert_eq!(Winning::::get(1), Some(winning)); - run_to_block(30); + System::run_to_block::(30); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(2, 0) @@ -701,7 +701,7 @@ fn less_winning_samples_work() { assert_eq!(Winning::::get(2), Some(winning)); set_last_random(H256::from([254; 32]), 40); - run_to_block(40); + System::run_to_block::(40); // Auction ended and winner selected assert_eq!( Auctions::auction_status(System::block_number()), @@ -729,71 +729,71 @@ fn auction_status_works() { AuctionStatus::::NotStarted ); - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 9, 11)); - run_to_block(9); + System::run_to_block::(9); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::StartingPeriod ); - run_to_block(10); + System::run_to_block::(10); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(0, 0) ); - run_to_block(11); + System::run_to_block::(11); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(0, 1) ); - run_to_block(19); + System::run_to_block::(19); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(0, 9) ); - run_to_block(20); + System::run_to_block::(20); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(1, 0) ); - run_to_block(25); + System::run_to_block::(25); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(1, 5) ); - run_to_block(30); + System::run_to_block::(30); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(2, 0) ); - run_to_block(39); + System::run_to_block::(39); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(2, 9) ); - run_to_block(40); + System::run_to_block::(40); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::VrfDelay(0) ); - run_to_block(44); + System::run_to_block::(44); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::VrfDelay(4) ); set_last_random(dummy_hash(), 45); - run_to_block(45); + System::run_to_block::(45); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::NotStarted @@ -804,7 +804,7 @@ fn auction_status_works() { #[test] fn can_cancel_auction() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 1)); assert_eq!(Balances::reserved_balance(1), 1); diff --git a/polkadot/runtime/common/src/crowdloan/mod.rs b/polkadot/runtime/common/src/crowdloan/mod.rs index 8cf288197e3d..f8b3169407e8 100644 --- a/polkadot/runtime/common/src/crowdloan/mod.rs +++ b/polkadot/runtime/common/src/crowdloan/mod.rs @@ -858,10 +858,7 @@ mod crypto { mod tests { use super::*; - use frame_support::{ - assert_noop, assert_ok, derive_impl, parameter_types, - traits::{OnFinalize, OnInitialize}, - }; + use frame_support::{assert_noop, assert_ok, derive_impl, parameter_types}; use polkadot_primitives::Id as ParaId; use sp_core::H256; use std::{cell::RefCell, collections::BTreeMap, sync::Arc}; @@ -1111,18 +1108,6 @@ mod tests { unreachable!() } - fn run_to_block(n: u64) { - while System::block_number() < n { - Crowdloan::on_finalize(System::block_number()); - Balances::on_finalize(System::block_number()); - System::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - Balances::on_initialize(System::block_number()); - Crowdloan::on_initialize(System::block_number()); - } - } - fn last_event() -> RuntimeEvent { System::events().pop().expect("RuntimeEvent expected").event } @@ -1426,7 +1411,7 @@ mod tests { ); // Move past end date - run_to_block(10); + System::run_to_block::(10); // Cannot contribute to ended fund assert_noop!( @@ -1451,7 +1436,7 @@ mod tests { // crowdloan that has starting period 1. let para_3 = new_para(); assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para_3, 1000, 1, 4, 40, None)); - run_to_block(40); + System::run_to_block::(40); let now = System::block_number(); assert_eq!(TestAuctioneer::lease_period_index(now).unwrap().0, 2); assert_noop!( @@ -1483,12 +1468,12 @@ mod tests { None )); - run_to_block(8); + System::run_to_block::(8); // Can def contribute when auction is running. assert!(TestAuctioneer::auction_status(System::block_number()).is_ending().is_some()); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 250, None)); - run_to_block(10); + System::run_to_block::(10); // Can't contribute when auction is in the VRF delay period. assert!(TestAuctioneer::auction_status(System::block_number()).is_vrf()); assert_noop!( @@ -1496,7 +1481,7 @@ mod tests { Error::::VrfDelayInProgress ); - run_to_block(15); + System::run_to_block::(15); // Its fine to contribute when no auction is running. assert!(!TestAuctioneer::auction_status(System::block_number()).is_in_progress()); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 250, None)); @@ -1526,15 +1511,15 @@ mod tests { let bidder = Crowdloan::fund_account_id(index); // Fund crowdloan - run_to_block(1); + System::run_to_block::(1); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 100, None)); - run_to_block(3); + System::run_to_block::(3); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(3), para, 150, None)); - run_to_block(5); + System::run_to_block::(5); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(4), para, 200, None)); - run_to_block(8); + System::run_to_block::(8); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 250, None)); - run_to_block(10); + System::run_to_block::(10); assert_eq!( bids(), @@ -1561,7 +1546,7 @@ mod tests { assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 100, None)); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(3), para, 50, None)); - run_to_block(10); + System::run_to_block::(10); let account_id = Crowdloan::fund_account_id(index); // para has no reserved funds, indicating it did not win the auction. assert_eq!(Balances::reserved_balance(&account_id), 0); @@ -1591,7 +1576,7 @@ mod tests { assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para, 1000, 1, 1, 9, None)); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 100, None)); - run_to_block(10); + System::run_to_block::(10); let account_id = Crowdloan::fund_account_id(index); // user sends the crowdloan funds trying to make an accounting error @@ -1636,7 +1621,7 @@ mod tests { ); // Move to the end of the crowdloan - run_to_block(10); + System::run_to_block::(10); assert_ok!(Crowdloan::refund(RuntimeOrigin::signed(1337), para)); // Funds are returned @@ -1671,7 +1656,7 @@ mod tests { assert_eq!(Balances::free_balance(account_id), 21000); // Move to the end of the crowdloan - run_to_block(10); + System::run_to_block::(10); assert_ok!(Crowdloan::refund(RuntimeOrigin::signed(1337), para)); assert_eq!( last_event(), @@ -1705,7 +1690,7 @@ mod tests { assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 100, None)); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(3), para, 50, None)); - run_to_block(10); + System::run_to_block::(10); // All funds are refunded assert_ok!(Crowdloan::refund(RuntimeOrigin::signed(2), para)); @@ -1730,7 +1715,7 @@ mod tests { assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 100, None)); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(3), para, 50, None)); - run_to_block(10); + System::run_to_block::(10); // We test the historic case where crowdloan accounts only have one provider: { @@ -1770,7 +1755,7 @@ mod tests { Error::::NotReadyToDissolve ); - run_to_block(10); + System::run_to_block::(10); set_winner(para, 1, true); // Can't dissolve when it won. assert_noop!( @@ -1815,13 +1800,13 @@ mod tests { // simulate the reserving of para's funds. this actually happens in the Slots pallet. assert_ok!(Balances::reserve(&account_id, 149)); - run_to_block(19); + System::run_to_block::(19); assert_noop!( Crowdloan::withdraw(RuntimeOrigin::signed(2), 2, para), Error::::BidOrLeaseActive ); - run_to_block(20); + System::run_to_block::(20); // simulate the unreserving of para's funds, now that the lease expired. this actually // happens in the Slots pallet. Balances::unreserve(&account_id, 150); @@ -1949,7 +1934,7 @@ mod tests { Error::::NoContributions ); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para_1, 100, None)); - run_to_block(6); + System::run_to_block::(6); assert_ok!(Crowdloan::poke(RuntimeOrigin::signed(1), para_1)); assert_eq!(crowdloan::NewRaise::::get(), vec![para_1]); assert_noop!( diff --git a/polkadot/runtime/common/src/integration_tests.rs b/polkadot/runtime/common/src/integration_tests.rs index 8a76a138305e..bb4ad8b75065 100644 --- a/polkadot/runtime/common/src/integration_tests.rs +++ b/polkadot/runtime/common/src/integration_tests.rs @@ -28,7 +28,7 @@ use alloc::sync::Arc; use codec::Encode; use frame_support::{ assert_noop, assert_ok, derive_impl, parameter_types, - traits::{ConstU32, Currency, OnFinalize, OnInitialize}, + traits::{ConstU32, Currency}, weights::Weight, PalletId, }; @@ -377,14 +377,12 @@ fn add_blocks(n: u32) { } fn run_to_block(n: u32) { - assert!(System::block_number() < n); - while System::block_number() < n { - let block_number = System::block_number(); - AllPalletsWithSystem::on_finalize(block_number); - System::set_block_number(block_number + 1); - maybe_new_session(block_number + 1); - AllPalletsWithSystem::on_initialize(block_number + 1); - } + System::run_to_block_with::( + n, + frame_system::RunToBlockHooks::default().before_initialize(|bn| { + maybe_new_session(bn); + }), + ); } fn run_to_session(n: u32) { diff --git a/polkadot/runtime/common/src/paras_registrar/mock.rs b/polkadot/runtime/common/src/paras_registrar/mock.rs index 1627fd70365d..07b8fbca5189 100644 --- a/polkadot/runtime/common/src/paras_registrar/mock.rs +++ b/polkadot/runtime/common/src/paras_registrar/mock.rs @@ -20,10 +20,7 @@ use super::*; use crate::paras_registrar; use alloc::collections::btree_map::BTreeMap; -use frame_support::{ - derive_impl, parameter_types, - traits::{OnFinalize, OnInitialize}, -}; +use frame_support::{derive_impl, parameter_types}; use frame_system::limits; use polkadot_primitives::{Balance, BlockNumber, MAX_CODE_SIZE}; use polkadot_runtime_parachains::{configuration, origin, shared}; @@ -205,26 +202,21 @@ pub const VALIDATORS: &[Sr25519Keyring] = &[ pub fn run_to_block(n: BlockNumber) { // NOTE that this function only simulates modules of interest. Depending on new pallet may // require adding it here. - assert!(System::block_number() < n); - while System::block_number() < n { - let b = System::block_number(); - - if System::block_number() > 1 { - System::on_finalize(System::block_number()); - } - // Session change every 3 blocks. - if (b + 1) % BLOCKS_PER_SESSION == 0 { - let session_index = shared::CurrentSessionIndex::::get() + 1; - let validators_pub_keys = VALIDATORS.iter().map(|v| v.public().into()).collect(); - - shared::Pallet::::set_session_index(session_index); - shared::Pallet::::set_active_validators_ascending(validators_pub_keys); - - Parachains::test_on_new_session(); - } - System::set_block_number(b + 1); - System::on_initialize(System::block_number()); - } + System::run_to_block_with::( + n, + frame_system::RunToBlockHooks::default().before_finalize(|bn| { + // Session change every 3 blocks. + if (bn + 1) % BLOCKS_PER_SESSION == 0 { + let session_index = shared::CurrentSessionIndex::::get() + 1; + let validators_pub_keys = VALIDATORS.iter().map(|v| v.public().into()).collect(); + + shared::Pallet::::set_session_index(session_index); + shared::Pallet::::set_active_validators_ascending(validators_pub_keys); + + Parachains::test_on_new_session(); + } + }), + ); } pub fn run_to_session(n: BlockNumber) { diff --git a/polkadot/runtime/common/src/slots/mod.rs b/polkadot/runtime/common/src/slots/mod.rs index 333f14c6608a..59a1f1870b2d 100644 --- a/polkadot/runtime/common/src/slots/mod.rs +++ b/polkadot/runtime/common/src/slots/mod.rs @@ -584,28 +584,16 @@ mod tests { t.into() } - fn run_to_block(n: BlockNumber) { - while System::block_number() < n { - Slots::on_finalize(System::block_number()); - Balances::on_finalize(System::block_number()); - System::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - Balances::on_initialize(System::block_number()); - Slots::on_initialize(System::block_number()); - } - } - #[test] fn basic_setup_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_eq!(Slots::lease_period_length(), (10, 0)); let now = System::block_number(); assert_eq!(Slots::lease_period_index(now).unwrap().0, 0); assert_eq!(Slots::deposit_held(1.into(), &1), 0); - run_to_block(10); + System::run_to_block::(10); let now = System::block_number(); assert_eq!(Slots::lease_period_index(now).unwrap().0, 1); }); @@ -614,7 +602,7 @@ mod tests { #[test] fn lease_lifecycle_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(TestRegistrar::::register( 1, @@ -627,11 +615,11 @@ mod tests { assert_eq!(Slots::deposit_held(1.into(), &1), 1); assert_eq!(Balances::reserved_balance(1), 1); - run_to_block(19); + System::run_to_block::(19); assert_eq!(Slots::deposit_held(1.into(), &1), 1); assert_eq!(Balances::reserved_balance(1), 1); - run_to_block(20); + System::run_to_block::(20); assert_eq!(Slots::deposit_held(1.into(), &1), 0); assert_eq!(Balances::reserved_balance(1), 0); @@ -645,7 +633,7 @@ mod tests { #[test] fn lease_interrupted_lifecycle_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(TestRegistrar::::register( 1, @@ -657,19 +645,19 @@ mod tests { assert_ok!(Slots::lease_out(1.into(), &1, 6, 1, 1)); assert_ok!(Slots::lease_out(1.into(), &1, 4, 3, 1)); - run_to_block(19); + System::run_to_block::(19); assert_eq!(Slots::deposit_held(1.into(), &1), 6); assert_eq!(Balances::reserved_balance(1), 6); - run_to_block(20); + System::run_to_block::(20); assert_eq!(Slots::deposit_held(1.into(), &1), 4); assert_eq!(Balances::reserved_balance(1), 4); - run_to_block(39); + System::run_to_block::(39); assert_eq!(Slots::deposit_held(1.into(), &1), 4); assert_eq!(Balances::reserved_balance(1), 4); - run_to_block(40); + System::run_to_block::(40); assert_eq!(Slots::deposit_held(1.into(), &1), 0); assert_eq!(Balances::reserved_balance(1), 0); @@ -688,7 +676,7 @@ mod tests { #[test] fn lease_relayed_lifecycle_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(TestRegistrar::::register( 1, @@ -704,25 +692,25 @@ mod tests { assert_eq!(Slots::deposit_held(1.into(), &2), 4); assert_eq!(Balances::reserved_balance(2), 4); - run_to_block(19); + System::run_to_block::(19); assert_eq!(Slots::deposit_held(1.into(), &1), 6); assert_eq!(Balances::reserved_balance(1), 6); assert_eq!(Slots::deposit_held(1.into(), &2), 4); assert_eq!(Balances::reserved_balance(2), 4); - run_to_block(20); + System::run_to_block::(20); assert_eq!(Slots::deposit_held(1.into(), &1), 0); assert_eq!(Balances::reserved_balance(1), 0); assert_eq!(Slots::deposit_held(1.into(), &2), 4); assert_eq!(Balances::reserved_balance(2), 4); - run_to_block(29); + System::run_to_block::(29); assert_eq!(Slots::deposit_held(1.into(), &1), 0); assert_eq!(Balances::reserved_balance(1), 0); assert_eq!(Slots::deposit_held(1.into(), &2), 4); assert_eq!(Balances::reserved_balance(2), 4); - run_to_block(30); + System::run_to_block::(30); assert_eq!(Slots::deposit_held(1.into(), &1), 0); assert_eq!(Balances::reserved_balance(1), 0); assert_eq!(Slots::deposit_held(1.into(), &2), 0); @@ -738,7 +726,7 @@ mod tests { #[test] fn lease_deposit_increase_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(TestRegistrar::::register( 1, @@ -755,11 +743,11 @@ mod tests { assert_eq!(Slots::deposit_held(1.into(), &1), 6); assert_eq!(Balances::reserved_balance(1), 6); - run_to_block(29); + System::run_to_block::(29); assert_eq!(Slots::deposit_held(1.into(), &1), 6); assert_eq!(Balances::reserved_balance(1), 6); - run_to_block(30); + System::run_to_block::(30); assert_eq!(Slots::deposit_held(1.into(), &1), 0); assert_eq!(Balances::reserved_balance(1), 0); @@ -773,7 +761,7 @@ mod tests { #[test] fn lease_deposit_decrease_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(TestRegistrar::::register( 1, @@ -790,19 +778,19 @@ mod tests { assert_eq!(Slots::deposit_held(1.into(), &1), 6); assert_eq!(Balances::reserved_balance(1), 6); - run_to_block(19); + System::run_to_block::(19); assert_eq!(Slots::deposit_held(1.into(), &1), 6); assert_eq!(Balances::reserved_balance(1), 6); - run_to_block(20); + System::run_to_block::(20); assert_eq!(Slots::deposit_held(1.into(), &1), 4); assert_eq!(Balances::reserved_balance(1), 4); - run_to_block(29); + System::run_to_block::(29); assert_eq!(Slots::deposit_held(1.into(), &1), 4); assert_eq!(Balances::reserved_balance(1), 4); - run_to_block(30); + System::run_to_block::(30); assert_eq!(Slots::deposit_held(1.into(), &1), 0); assert_eq!(Balances::reserved_balance(1), 0); @@ -816,7 +804,7 @@ mod tests { #[test] fn clear_all_leases_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(TestRegistrar::::register( 1, @@ -852,7 +840,7 @@ mod tests { #[test] fn lease_out_current_lease_period() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(TestRegistrar::::register( 1, @@ -867,7 +855,7 @@ mod tests { dummy_validation_code() )); - run_to_block(20); + System::run_to_block::(20); let now = System::block_number(); assert_eq!(Slots::lease_period_index(now).unwrap().0, 2); // Can't lease from the past @@ -884,7 +872,7 @@ mod tests { #[test] fn trigger_onboard_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(TestRegistrar::::register( 1, ParaId::from(1_u32), diff --git a/prdoc/pr_7109.prdoc b/prdoc/pr_7109.prdoc new file mode 100644 index 000000000000..e54ef3295135 --- /dev/null +++ b/prdoc/pr_7109.prdoc @@ -0,0 +1,11 @@ +title: Add "run to block" tools +doc: +- audience: Runtime Dev + description: |- + Introduce `frame_system::Pallet::run_to_block`, `frame_system::Pallet::run_to_block_with`, and `frame_system::RunToBlockHooks` to establish a generic `run_to_block` mechanism for mock tests, minimizing redundant implementations across various pallets. + + Closes #299. + +crates: +- name: frame-system + bump: minor diff --git a/substrate/frame/examples/multi-block-migrations/src/mock.rs b/substrate/frame/examples/multi-block-migrations/src/mock.rs index b2a946e1c505..64940db080c4 100644 --- a/substrate/frame/examples/multi-block-migrations/src/mock.rs +++ b/substrate/frame/examples/multi-block-migrations/src/mock.rs @@ -25,10 +25,7 @@ //! using the [`Migrations`] type. use frame_support::{ - construct_runtime, derive_impl, - migrations::MultiStepMigrator, - pallet_prelude::Weight, - traits::{OnFinalize, OnInitialize}, + construct_runtime, derive_impl, migrations::MultiStepMigrator, pallet_prelude::Weight, }; type Block = frame_system::mocking::MockBlock; @@ -81,13 +78,11 @@ pub fn new_test_ext() -> sp_io::TestExternalities { #[allow(dead_code)] pub fn run_to_block(n: u64) { - assert!(System::block_number() < n); - while System::block_number() < n { - let b = System::block_number(); - AllPalletsWithSystem::on_finalize(b); - // Done by Executive: - ::MultiBlockMigrator::step(); - System::set_block_number(b + 1); - AllPalletsWithSystem::on_initialize(b + 1); - } + System::run_to_block_with::( + n, + frame_system::RunToBlockHooks::default().after_initialize(|_| { + // Done by Executive: + ::MultiBlockMigrator::step(); + }), + ); } diff --git a/substrate/frame/fast-unstake/src/mock.rs b/substrate/frame/fast-unstake/src/mock.rs index 757052e230a1..f044fc610187 100644 --- a/substrate/frame/fast-unstake/src/mock.rs +++ b/substrate/frame/fast-unstake/src/mock.rs @@ -266,22 +266,19 @@ impl ExtBuilder { } pub(crate) fn run_to_block(n: u64, on_idle: bool) { - let current_block = System::block_number(); - assert!(n > current_block); - while System::block_number() < n { - Balances::on_finalize(System::block_number()); - Staking::on_finalize(System::block_number()); - FastUnstake::on_finalize(System::block_number()); - - System::set_block_number(System::block_number() + 1); - - Balances::on_initialize(System::block_number()); - Staking::on_initialize(System::block_number()); - FastUnstake::on_initialize(System::block_number()); - if on_idle { - FastUnstake::on_idle(System::block_number(), BlockWeights::get().max_block); - } - } + System::run_to_block_with::( + n, + frame_system::RunToBlockHooks::default() + .before_finalize(|_| { + // Satisfy the timestamp pallet. + Timestamp::set_timestamp(0); + }) + .after_initialize(|bn| { + if on_idle { + FastUnstake::on_idle(bn, BlockWeights::get().max_block); + } + }), + ); } pub(crate) fn next_block(on_idle: bool) { diff --git a/substrate/frame/identity/src/tests.rs b/substrate/frame/identity/src/tests.rs index 7bf5b2a72760..01bc312723aa 100644 --- a/substrate/frame/identity/src/tests.rs +++ b/substrate/frame/identity/src/tests.rs @@ -26,7 +26,7 @@ use crate::{ use codec::{Decode, Encode}; use frame_support::{ assert_err, assert_noop, assert_ok, derive_impl, parameter_types, - traits::{ConstU32, ConstU64, Get, OnFinalize, OnInitialize}, + traits::{ConstU32, ConstU64, Get}, BoundedVec, }; use frame_system::EnsureRoot; @@ -114,18 +114,6 @@ pub fn new_test_ext() -> sp_io::TestExternalities { ext } -fn run_to_block(n: u64) { - while System::block_number() < n { - Identity::on_finalize(System::block_number()); - Balances::on_finalize(System::block_number()); - System::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - Balances::on_initialize(System::block_number()); - Identity::on_initialize(System::block_number()); - } -} - fn account(id: u8) -> AccountIdOf { [id; 32].into() } @@ -1714,7 +1702,7 @@ fn unaccepted_usernames_through_grant_should_expire() { Some((who.clone(), expiration, Provider::Allocation)) ); - run_to_block(now + expiration - 1); + System::run_to_block::(now + expiration - 1); // Cannot be removed assert_noop!( @@ -1722,7 +1710,7 @@ fn unaccepted_usernames_through_grant_should_expire() { Error::::NotExpired ); - run_to_block(now + expiration); + System::run_to_block::(now + expiration); // Anyone can remove assert_ok!(Identity::remove_expired_approval( @@ -1782,7 +1770,7 @@ fn unaccepted_usernames_through_deposit_should_expire() { Some((who.clone(), expiration, Provider::AuthorityDeposit(username_deposit))) ); - run_to_block(now + expiration - 1); + System::run_to_block::(now + expiration - 1); // Cannot be removed assert_noop!( @@ -1790,7 +1778,7 @@ fn unaccepted_usernames_through_deposit_should_expire() { Error::::NotExpired ); - run_to_block(now + expiration); + System::run_to_block::(now + expiration); // Anyone can remove assert_eq!( diff --git a/substrate/frame/lottery/src/mock.rs b/substrate/frame/lottery/src/mock.rs index d2c442e2ac6e..b771ed0849f6 100644 --- a/substrate/frame/lottery/src/mock.rs +++ b/substrate/frame/lottery/src/mock.rs @@ -20,10 +20,7 @@ use super::*; use crate as pallet_lottery; -use frame_support::{ - derive_impl, parameter_types, - traits::{ConstU32, OnFinalize, OnInitialize}, -}; +use frame_support::{derive_impl, parameter_types, traits::ConstU32}; use frame_support_test::TestRandomness; use frame_system::EnsureRoot; use sp_runtime::{BuildStorage, Perbill}; @@ -83,16 +80,3 @@ pub fn new_test_ext() -> sp_io::TestExternalities { .unwrap(); t.into() } - -/// Run until a particular block. -pub fn run_to_block(n: u64) { - while System::block_number() < n { - if System::block_number() > 1 { - Lottery::on_finalize(System::block_number()); - System::on_finalize(System::block_number()); - } - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - Lottery::on_initialize(System::block_number()); - } -} diff --git a/substrate/frame/lottery/src/tests.rs b/substrate/frame/lottery/src/tests.rs index ae3a6c858f24..119be5df4925 100644 --- a/substrate/frame/lottery/src/tests.rs +++ b/substrate/frame/lottery/src/tests.rs @@ -17,12 +17,11 @@ //! Tests for the module. -use super::*; -use frame_support::{assert_noop, assert_ok, assert_storage_noop}; -use mock::{ - new_test_ext, run_to_block, Balances, BalancesCall, Lottery, RuntimeCall, RuntimeOrigin, - SystemCall, Test, +use crate::{ + mock::{Lottery, *}, + *, }; +use frame_support::{assert_noop, assert_ok, assert_storage_noop}; use sp_runtime::{traits::BadOrigin, TokenError}; #[test] @@ -74,13 +73,13 @@ fn basic_end_to_end_works() { assert_eq!(TicketsCount::::get(), 4); // Go to end - run_to_block(20); + System::run_to_block::(20); assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(5), call.clone())); // Ticket isn't bought assert_eq!(TicketsCount::::get(), 4); // Go to payout - run_to_block(25); + System::run_to_block::(25); // User 1 wins assert_eq!(Balances::free_balance(&1), 70 + 40); // Lottery is reset and restarted @@ -115,11 +114,11 @@ fn stop_repeat_works() { // Lottery still exists. assert!(crate::Lottery::::get().is_some()); // End and pick a winner. - run_to_block(length + delay); + System::run_to_block::(length + delay); // Lottery stays dead and does not repeat. assert!(crate::Lottery::::get().is_none()); - run_to_block(length + delay + 1); + System::run_to_block::(length + delay + 1); assert!(crate::Lottery::::get().is_none()); }); } @@ -281,7 +280,7 @@ fn buy_ticket_works() { assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 1, 20, 5, false)); // Go to start, buy ticket for transfer - run_to_block(5); + System::run_to_block::(5); assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(1), call)); assert_eq!(TicketsCount::::get(), 1); @@ -300,12 +299,12 @@ fn buy_ticket_works() { assert_eq!(TicketsCount::::get(), 2); // Go to end, can't buy tickets anymore - run_to_block(20); + System::run_to_block::(20); assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(2), call.clone())); assert_eq!(TicketsCount::::get(), 2); // Go to payout, can't buy tickets when there is no lottery open - run_to_block(25); + System::run_to_block::(25); assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(2), call.clone())); assert_eq!(TicketsCount::::get(), 0); assert_eq!(LotteryIndex::::get(), 1); @@ -409,7 +408,7 @@ fn no_participants_works() { assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 10, length, delay, false)); // End the lottery, no one wins. - run_to_block(length + delay); + System::run_to_block::(length + delay); }); } diff --git a/substrate/frame/migrations/src/mock.rs b/substrate/frame/migrations/src/mock.rs index 48ff175f8137..ea86899cad83 100644 --- a/substrate/frame/migrations/src/mock.rs +++ b/substrate/frame/migrations/src/mock.rs @@ -21,12 +21,7 @@ use crate::{mock_helpers::*, Event, Historic}; -use frame_support::{ - derive_impl, - migrations::*, - traits::{OnFinalize, OnInitialize}, - weights::Weight, -}; +use frame_support::{derive_impl, migrations::*, weights::Weight}; use frame_system::EventRecord; use sp_core::H256; @@ -113,18 +108,18 @@ pub fn test_closure(f: impl FnOnce() -> R) -> R { ext.execute_with(f) } -pub fn run_to_block(n: u32) { - while System::block_number() < n as u64 { - log::debug!("Block {}", System::block_number()); - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - Migrations::on_initialize(System::block_number()); - // Executive calls this: - ::step(); - - Migrations::on_finalize(System::block_number()); - System::on_finalize(System::block_number()); - } +pub fn run_to_block(n: u64) { + System::run_to_block_with::( + n, + frame_system::RunToBlockHooks::default() + .before_initialize(|bn| { + log::debug!("Block {bn}"); + }) + .after_initialize(|_| { + // Executive calls this: + ::step(); + }), + ); } /// Returns the historic migrations, sorted by their identifier. diff --git a/substrate/frame/nis/src/mock.rs b/substrate/frame/nis/src/mock.rs index 2b008f8ec2a4..08e69ef0de05 100644 --- a/substrate/frame/nis/src/mock.rs +++ b/substrate/frame/nis/src/mock.rs @@ -21,7 +21,7 @@ use crate::{self as pallet_nis, Perquintill, WithMaximumOf}; use frame_support::{ derive_impl, ord_parameter_types, parameter_types, - traits::{fungible::Inspect, ConstU32, ConstU64, OnFinalize, OnInitialize, StorageMapShim}, + traits::{fungible::Inspect, ConstU32, ConstU64, StorageMapShim}, weights::Weight, PalletId, }; @@ -145,15 +145,3 @@ pub fn new_test_ext() -> sp_io::TestExternalities { pub fn new_test_ext_empty() -> sp_io::TestExternalities { frame_system::GenesisConfig::::default().build_storage().unwrap().into() } - -pub fn run_to_block(n: u64) { - while System::block_number() < n { - Nis::on_finalize(System::block_number()); - Balances::on_finalize(System::block_number()); - System::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - Balances::on_initialize(System::block_number()); - Nis::on_initialize(System::block_number()); - } -} diff --git a/substrate/frame/nis/src/tests.rs b/substrate/frame/nis/src/tests.rs index a17aaf421827..10c39a0d48ed 100644 --- a/substrate/frame/nis/src/tests.rs +++ b/substrate/frame/nis/src/tests.rs @@ -55,7 +55,7 @@ fn enlarge(amount: Balance, max_bids: u32) { #[test] fn basic_setup_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); for q in 0..3 { assert!(Queues::::get(q).is_empty()); @@ -76,7 +76,7 @@ fn basic_setup_works() { #[test] fn place_bid_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_noop!(Nis::place_bid(signed(1), 1, 2), Error::::AmountTooSmall); assert_noop!(Nis::place_bid(signed(1), 101, 2), FundsUnavailable); assert_noop!(Nis::place_bid(signed(1), 10, 4), Error::::DurationTooBig); @@ -90,7 +90,7 @@ fn place_bid_works() { #[test] fn place_bid_queuing_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Nis::place_bid(signed(1), 20, 2)); assert_ok!(Nis::place_bid(signed(1), 10, 2)); assert_ok!(Nis::place_bid(signed(1), 5, 2)); @@ -116,7 +116,7 @@ fn place_bid_queuing_works() { #[test] fn place_bid_fails_when_queue_full() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Nis::place_bid(signed(1), 10, 2)); assert_ok!(Nis::place_bid(signed(2), 10, 2)); assert_ok!(Nis::place_bid(signed(3), 10, 2)); @@ -128,7 +128,7 @@ fn place_bid_fails_when_queue_full() { #[test] fn multiple_place_bids_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Nis::place_bid(signed(1), 10, 1)); assert_ok!(Nis::place_bid(signed(1), 10, 2)); assert_ok!(Nis::place_bid(signed(1), 10, 2)); @@ -154,7 +154,7 @@ fn multiple_place_bids_works() { #[test] fn retract_single_item_queue_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Nis::place_bid(signed(1), 10, 1)); assert_ok!(Nis::place_bid(signed(1), 10, 2)); assert_ok!(Nis::retract_bid(signed(1), 10, 1)); @@ -169,7 +169,7 @@ fn retract_single_item_queue_works() { #[test] fn retract_with_other_and_duplicate_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Nis::place_bid(signed(1), 10, 1)); assert_ok!(Nis::place_bid(signed(1), 10, 2)); assert_ok!(Nis::place_bid(signed(1), 10, 2)); @@ -190,7 +190,7 @@ fn retract_with_other_and_duplicate_works() { #[test] fn retract_non_existent_item_fails() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_noop!(Nis::retract_bid(signed(1), 10, 1), Error::::UnknownBid); assert_ok!(Nis::place_bid(signed(1), 10, 1)); assert_noop!(Nis::retract_bid(signed(1), 20, 1), Error::::UnknownBid); @@ -202,7 +202,7 @@ fn retract_non_existent_item_fails() { #[test] fn basic_enlarge_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Nis::place_bid(signed(1), 40, 1)); assert_ok!(Nis::place_bid(signed(2), 40, 2)); enlarge(40, 2); @@ -240,7 +240,7 @@ fn basic_enlarge_works() { #[test] fn enlarge_respects_bids_limit() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Nis::place_bid(signed(1), 40, 1)); assert_ok!(Nis::place_bid(signed(2), 40, 2)); assert_ok!(Nis::place_bid(signed(3), 40, 2)); @@ -285,7 +285,7 @@ fn enlarge_respects_bids_limit() { #[test] fn enlarge_respects_amount_limit_and_will_split() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Nis::place_bid(signed(1), 80, 1)); enlarge(40, 2); @@ -317,7 +317,7 @@ fn enlarge_respects_amount_limit_and_will_split() { #[test] fn basic_thaw_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Nis::place_bid(signed(1), 40, 1)); assert_eq!(Nis::issuance().effective, 400); assert_eq!(Balances::free_balance(1), 60); @@ -330,9 +330,9 @@ fn basic_thaw_works() { assert_eq!(Balances::reserved_balance(1), 40); assert_eq!(holdings(), 40); - run_to_block(3); + System::run_to_block::(3); assert_noop!(Nis::thaw_private(signed(1), 0, None), Error::::NotExpired); - run_to_block(4); + System::run_to_block::(4); assert_noop!(Nis::thaw_private(signed(1), 1, None), Error::::UnknownReceipt); assert_noop!(Nis::thaw_private(signed(2), 0, None), Error::::NotOwner); @@ -359,12 +359,12 @@ fn basic_thaw_works() { #[test] fn partial_thaw_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Nis::place_bid(signed(1), 80, 1)); enlarge(80, 1); assert_eq!(holdings(), 80); - run_to_block(4); + System::run_to_block::(4); let prop = Perquintill::from_rational(4_100_000, 21_000_000u64); assert_noop!(Nis::thaw_private(signed(1), 0, Some(prop)), Error::::MakesDust); let prop = Perquintill::from_rational(1_050_000, 21_000_000u64); @@ -402,10 +402,10 @@ fn partial_thaw_works() { #[test] fn thaw_respects_transfers() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Nis::place_bid(signed(1), 40, 1)); enlarge(40, 1); - run_to_block(4); + System::run_to_block::(4); assert_eq!(Nis::owner(&0), Some(1)); assert_eq!(Balances::reserved_balance(&1), 40); @@ -428,10 +428,10 @@ fn thaw_respects_transfers() { #[test] fn communify_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Nis::place_bid(signed(1), 40, 1)); enlarge(40, 1); - run_to_block(4); + System::run_to_block::(4); assert_eq!(Nis::owner(&0), Some(1)); assert_eq!(Balances::reserved_balance(&1), 40); @@ -479,10 +479,10 @@ fn communify_works() { #[test] fn privatize_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Nis::place_bid(signed(1), 40, 1)); enlarge(40, 1); - run_to_block(4); + System::run_to_block::(4); assert_noop!(Nis::privatize(signed(2), 0), Error::::AlreadyPrivate); assert_ok!(Nis::communify(signed(1), 0)); @@ -503,11 +503,11 @@ fn privatize_works() { #[test] fn privatize_and_thaw_with_another_receipt_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Nis::place_bid(signed(1), 40, 1)); assert_ok!(Nis::place_bid(signed(2), 40, 1)); enlarge(80, 2); - run_to_block(4); + System::run_to_block::(4); assert_ok!(Nis::communify(signed(1), 0)); assert_ok!(Nis::communify(signed(2), 1)); @@ -535,7 +535,7 @@ fn privatize_and_thaw_with_another_receipt_works() { #[test] fn communal_thaw_when_issuance_higher_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Balances::transfer_allow_death(signed(2), 1, 1)); assert_ok!(Nis::place_bid(signed(1), 100, 1)); enlarge(100, 1); @@ -552,7 +552,7 @@ fn communal_thaw_when_issuance_higher_works() { assert_ok!(Balances::mint_into(&3, 50)); assert_ok!(Balances::mint_into(&4, 50)); - run_to_block(4); + System::run_to_block::(4); // Unfunded initially... assert_noop!(Nis::thaw_communal(signed(1), 0), Error::::Unfunded); @@ -581,7 +581,7 @@ fn communal_thaw_when_issuance_higher_works() { #[test] fn private_thaw_when_issuance_higher_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Balances::transfer_allow_death(signed(2), 1, 1)); assert_ok!(Nis::place_bid(signed(1), 100, 1)); enlarge(100, 1); @@ -591,7 +591,7 @@ fn private_thaw_when_issuance_higher_works() { assert_ok!(Balances::mint_into(&3, 50)); assert_ok!(Balances::mint_into(&4, 50)); - run_to_block(4); + System::run_to_block::(4); // Unfunded initially... assert_noop!(Nis::thaw_private(signed(1), 0, None), Error::::Unfunded); @@ -609,7 +609,7 @@ fn private_thaw_when_issuance_higher_works() { #[test] fn thaw_with_ignored_issuance_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); // Give account zero some balance. assert_ok!(Balances::mint_into(&0, 200)); @@ -622,7 +622,7 @@ fn thaw_with_ignored_issuance_works() { assert_ok!(Balances::transfer_allow_death(signed(0), 3, 50)); assert_ok!(Balances::transfer_allow_death(signed(0), 4, 50)); - run_to_block(4); + System::run_to_block::(4); // Unfunded initially... assert_noop!(Nis::thaw_private(signed(1), 0, None), Error::::Unfunded); // ...so we fund... @@ -640,7 +640,7 @@ fn thaw_with_ignored_issuance_works() { #[test] fn thaw_when_issuance_lower_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Balances::transfer_allow_death(signed(2), 1, 1)); assert_ok!(Nis::place_bid(signed(1), 100, 1)); enlarge(100, 1); @@ -650,7 +650,7 @@ fn thaw_when_issuance_lower_works() { assert_ok!(Balances::burn_from(&3, 25, Expendable, Exact, Force)); assert_ok!(Balances::burn_from(&4, 25, Expendable, Exact, Force)); - run_to_block(4); + System::run_to_block::(4); assert_ok!(Nis::thaw_private(signed(1), 0, None)); assert_ok!(Balances::transfer_allow_death(signed(1), 2, 1)); @@ -662,7 +662,7 @@ fn thaw_when_issuance_lower_works() { #[test] fn multiple_thaws_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Balances::transfer_allow_death(signed(3), 1, 1)); assert_ok!(Nis::place_bid(signed(1), 40, 1)); assert_ok!(Nis::place_bid(signed(1), 60, 1)); @@ -675,11 +675,11 @@ fn multiple_thaws_works() { assert_ok!(Balances::mint_into(&4, 100)); assert_ok!(Nis::fund_deficit(signed(1))); - run_to_block(4); + System::run_to_block::(4); assert_ok!(Nis::thaw_private(signed(1), 0, None)); assert_ok!(Nis::thaw_private(signed(1), 1, None)); assert_noop!(Nis::thaw_private(signed(2), 2, None), Error::::Throttled); - run_to_block(5); + System::run_to_block::(5); assert_ok!(Nis::thaw_private(signed(2), 2, None)); assert_ok!(Balances::transfer_allow_death(signed(1), 3, 1)); @@ -693,7 +693,7 @@ fn multiple_thaws_works() { #[test] fn multiple_thaws_works_in_alternative_thaw_order() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Balances::transfer_allow_death(signed(3), 1, 1)); assert_ok!(Nis::place_bid(signed(1), 40, 1)); assert_ok!(Nis::place_bid(signed(1), 60, 1)); @@ -706,12 +706,12 @@ fn multiple_thaws_works_in_alternative_thaw_order() { assert_ok!(Balances::mint_into(&4, 100)); assert_ok!(Nis::fund_deficit(signed(1))); - run_to_block(4); + System::run_to_block::(4); assert_ok!(Nis::thaw_private(signed(2), 2, None)); assert_noop!(Nis::thaw_private(signed(1), 1, None), Error::::Throttled); assert_ok!(Nis::thaw_private(signed(1), 0, None)); - run_to_block(5); + System::run_to_block::(5); assert_ok!(Nis::thaw_private(signed(1), 1, None)); assert_ok!(Balances::transfer_allow_death(signed(1), 3, 1)); @@ -725,7 +725,7 @@ fn multiple_thaws_works_in_alternative_thaw_order() { #[test] fn enlargement_to_target_works() { new_test_ext().execute_with(|| { - run_to_block(2); + System::run_to_block::(2); let w = <() as WeightInfo>::process_queues() + <() as WeightInfo>::process_queue() + (<() as WeightInfo>::process_bid() * 2); @@ -737,7 +737,7 @@ fn enlargement_to_target_works() { assert_ok!(Nis::place_bid(signed(3), 40, 3)); Target::set(Perquintill::from_percent(40)); - run_to_block(3); + System::run_to_block::(3); assert_eq!(Queues::::get(1), vec![Bid { amount: 40, who: 1 },]); assert_eq!( Queues::::get(2), @@ -749,7 +749,7 @@ fn enlargement_to_target_works() { ); assert_eq!(QueueTotals::::get(), vec![(1, 40), (2, 80), (2, 80)]); - run_to_block(4); + System::run_to_block::(4); // Two new items should have been issued to 2 & 3 for 40 each & duration of 3. assert_eq!( Receipts::::get(0).unwrap(), @@ -778,7 +778,7 @@ fn enlargement_to_target_works() { } ); - run_to_block(5); + System::run_to_block::(5); // No change assert_eq!( Summary::::get(), @@ -791,7 +791,7 @@ fn enlargement_to_target_works() { } ); - run_to_block(6); + System::run_to_block::(6); // Two new items should have been issued to 1 & 2 for 40 each & duration of 2. assert_eq!( Receipts::::get(2).unwrap(), @@ -820,7 +820,7 @@ fn enlargement_to_target_works() { } ); - run_to_block(8); + System::run_to_block::(8); // No change now. assert_eq!( Summary::::get(), @@ -835,7 +835,7 @@ fn enlargement_to_target_works() { // Set target a bit higher to use up the remaining bid. Target::set(Perquintill::from_percent(60)); - run_to_block(10); + System::run_to_block::(10); // One new item should have been issued to 1 for 40 each & duration of 2. assert_eq!( diff --git a/substrate/frame/nomination-pools/src/mock.rs b/substrate/frame/nomination-pools/src/mock.rs index cc942039760c..f544e79ec481 100644 --- a/substrate/frame/nomination-pools/src/mock.rs +++ b/substrate/frame/nomination-pools/src/mock.rs @@ -435,18 +435,7 @@ parameter_types! { /// Helper to run a specified amount of blocks. pub fn run_blocks(n: u64) { let current_block = System::block_number(); - run_to_block(n + current_block); -} - -/// Helper to run to a specific block. -pub fn run_to_block(n: u64) { - let current_block = System::block_number(); - assert!(n > current_block); - while System::block_number() < n { - Pools::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - Pools::on_initialize(System::block_number()); - } + System::run_to_block::(n + current_block); } /// All events of this pallet. diff --git a/substrate/frame/recovery/src/mock.rs b/substrate/frame/recovery/src/mock.rs index 3930db82d6c7..86f13b0da4f7 100644 --- a/substrate/frame/recovery/src/mock.rs +++ b/substrate/frame/recovery/src/mock.rs @@ -20,10 +20,7 @@ use super::*; use crate as recovery; -use frame_support::{ - derive_impl, parameter_types, - traits::{OnFinalize, OnInitialize}, -}; +use frame_support::{derive_impl, parameter_types}; use sp_runtime::BuildStorage; type Block = frame_system::mocking::MockBlock; @@ -86,14 +83,3 @@ pub fn new_test_ext() -> sp_io::TestExternalities { .unwrap(); t.into() } - -/// Run until a particular block. -pub fn run_to_block(n: u64) { - while System::block_number() < n { - if System::block_number() > 1 { - System::on_finalize(System::block_number()); - } - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - } -} diff --git a/substrate/frame/recovery/src/tests.rs b/substrate/frame/recovery/src/tests.rs index 93df07015852..97085df2ae78 100644 --- a/substrate/frame/recovery/src/tests.rs +++ b/substrate/frame/recovery/src/tests.rs @@ -17,12 +17,8 @@ //! Tests for the module. -use super::*; +use crate::{mock::*, *}; use frame_support::{assert_noop, assert_ok, traits::Currency}; -use mock::{ - new_test_ext, run_to_block, Balances, BalancesCall, MaxFriends, Recovery, RecoveryCall, - RuntimeCall, RuntimeOrigin, Test, -}; use sp_runtime::{bounded_vec, traits::BadOrigin}; #[test] @@ -70,7 +66,7 @@ fn recovery_life_cycle_works() { delay_period )); // Some time has passed, and the user lost their keys! - run_to_block(10); + System::run_to_block::(10); // Using account 1, the user begins the recovery process to recover the lost account assert_ok!(Recovery::initiate_recovery(RuntimeOrigin::signed(1), 5)); // Off chain, the user contacts their friends and asks them to vouch for the recovery @@ -84,7 +80,7 @@ fn recovery_life_cycle_works() { Error::::DelayPeriod ); // We need to wait at least the delay_period number of blocks before we can recover - run_to_block(20); + System::run_to_block::(20); assert_ok!(Recovery::claim_recovery(RuntimeOrigin::signed(1), 5)); // Account 1 can use account 5 to close the active recovery process, claiming the deposited // funds used to initiate the recovery process into account 5. @@ -128,7 +124,7 @@ fn malicious_recovery_fails() { delay_period )); // Some time has passed, and account 1 wants to try and attack this account! - run_to_block(10); + System::run_to_block::(10); // Using account 1, the malicious user begins the recovery process on account 5 assert_ok!(Recovery::initiate_recovery(RuntimeOrigin::signed(1), 5)); // Off chain, the user **tricks** their friends and asks them to vouch for the recovery @@ -144,7 +140,7 @@ fn malicious_recovery_fails() { Error::::DelayPeriod ); // Account 1 needs to wait... - run_to_block(19); + System::run_to_block::(19); // One more block to wait! assert_noop!( Recovery::claim_recovery(RuntimeOrigin::signed(1), 5), @@ -158,7 +154,7 @@ fn malicious_recovery_fails() { // Thanks for the free money! assert_eq!(Balances::total_balance(&5), 110); // The recovery process has been closed, so account 1 can't make the claim - run_to_block(20); + System::run_to_block::(20); assert_noop!( Recovery::claim_recovery(RuntimeOrigin::signed(1), 5), Error::::NotStarted @@ -397,7 +393,7 @@ fn claim_recovery_handles_basic_errors() { Recovery::claim_recovery(RuntimeOrigin::signed(1), 5), Error::::DelayPeriod ); - run_to_block(11); + System::run_to_block::(11); // Cannot claim an account which has not passed the threshold number of votes assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(2), 5, 1)); assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(3), 5, 1)); @@ -427,7 +423,7 @@ fn claim_recovery_works() { assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(3), 5, 1)); assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(4), 5, 1)); - run_to_block(11); + System::run_to_block::(11); // Account can be recovered. assert_ok!(Recovery::claim_recovery(RuntimeOrigin::signed(1), 5)); @@ -439,7 +435,7 @@ fn claim_recovery_works() { assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(3), 5, 4)); assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(4), 5, 4)); - run_to_block(21); + System::run_to_block::(21); // Account is re-recovered. assert_ok!(Recovery::claim_recovery(RuntimeOrigin::signed(4), 5)); diff --git a/substrate/frame/root-offences/src/mock.rs b/substrate/frame/root-offences/src/mock.rs index a27fb36f64a6..7a96b8eade4e 100644 --- a/substrate/frame/root-offences/src/mock.rs +++ b/substrate/frame/root-offences/src/mock.rs @@ -25,7 +25,7 @@ use frame_election_provider_support::{ }; use frame_support::{ derive_impl, parameter_types, - traits::{ConstU32, ConstU64, Hooks, OneSessionHandler}, + traits::{ConstU32, ConstU64, OneSessionHandler}, }; use pallet_staking::StakerStatus; use sp_runtime::{curve::PiecewiseLinear, testing::UintAuthorityId, traits::Zero, BuildStorage}; @@ -283,16 +283,12 @@ pub(crate) fn start_session(session_index: SessionIndex) { /// a block import/propose process where we first initialize the block, then execute some stuff (not /// in the function), and then finalize the block. pub(crate) fn run_to_block(n: BlockNumber) { - Staking::on_finalize(System::block_number()); - for b in (System::block_number() + 1)..=n { - System::set_block_number(b); - Session::on_initialize(b); - >::on_initialize(b); - Timestamp::set_timestamp(System::block_number() * BLOCK_TIME + INIT_TIMESTAMP); - if b != n { - Staking::on_finalize(System::block_number()); - } - } + System::run_to_block_with::( + n, + frame_system::RunToBlockHooks::default().after_initialize(|bn| { + Timestamp::set_timestamp(bn * BLOCK_TIME + INIT_TIMESTAMP); + }), + ); } pub(crate) fn active_era() -> EraIndex { diff --git a/substrate/frame/scheduler/src/mock.rs b/substrate/frame/scheduler/src/mock.rs index 8d36ca1c42e3..43a964bcf149 100644 --- a/substrate/frame/scheduler/src/mock.rs +++ b/substrate/frame/scheduler/src/mock.rs @@ -22,7 +22,7 @@ use super::*; use crate as scheduler; use frame_support::{ derive_impl, ord_parameter_types, parameter_types, - traits::{ConstU32, Contains, EitherOfDiverse, EqualPrivilegeOnly, OnFinalize, OnInitialize}, + traits::{ConstU32, Contains, EitherOfDiverse, EqualPrivilegeOnly}, }; use frame_system::{EnsureRoot, EnsureSignedBy}; use sp_runtime::{BuildStorage, Perbill}; @@ -236,14 +236,6 @@ pub fn new_test_ext() -> sp_io::TestExternalities { t.into() } -pub fn run_to_block(n: u64) { - while System::block_number() < n { - Scheduler::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - Scheduler::on_initialize(System::block_number()); - } -} - pub fn root() -> OriginCaller { system::RawOrigin::Root.into() } diff --git a/substrate/frame/scheduler/src/tests.rs b/substrate/frame/scheduler/src/tests.rs index 3023a370a4b6..755223934108 100644 --- a/substrate/frame/scheduler/src/tests.rs +++ b/substrate/frame/scheduler/src/tests.rs @@ -20,7 +20,7 @@ use super::*; use crate::mock::{ logger::{self, Threshold}, - new_test_ext, root, run_to_block, LoggerCall, RuntimeCall, Scheduler, Test, *, + new_test_ext, root, LoggerCall, RuntimeCall, Scheduler, Test, *, }; use frame_support::{ assert_err, assert_noop, assert_ok, @@ -52,14 +52,14 @@ fn basic_scheduling_works() { )); // `log` runtime call should not have executed yet - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); - run_to_block(4); + System::run_to_block::(4); // `log` runtime call should have executed at block 4 assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -87,17 +87,17 @@ fn scheduling_with_preimages_works() { assert!(Preimage::is_requested(&hash)); // `log` runtime call should not have executed yet - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); - run_to_block(4); + System::run_to_block::(4); // preimage should not have been removed when executed by the scheduler assert!(!Preimage::len(&hash).is_some()); assert!(!Preimage::is_requested(&hash)); // `log` runtime call should have executed at block 4 assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -105,7 +105,7 @@ fn scheduling_with_preimages_works() { #[test] fn schedule_after_works() { new_test_ext().execute_with(|| { - run_to_block(2); + System::run_to_block::(2); let call = RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) }); assert!(!::BaseCallFilter::contains(&call)); @@ -117,11 +117,11 @@ fn schedule_after_works() { root(), Preimage::bound(call).unwrap() )); - run_to_block(5); + System::run_to_block::(5); assert!(logger::log().is_empty()); - run_to_block(6); + System::run_to_block::(6); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -129,7 +129,7 @@ fn schedule_after_works() { #[test] fn schedule_after_zero_works() { new_test_ext().execute_with(|| { - run_to_block(2); + System::run_to_block::(2); let call = RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) }); assert!(!::BaseCallFilter::contains(&call)); @@ -141,9 +141,9 @@ fn schedule_after_zero_works() { Preimage::bound(call).unwrap() )); // Will trigger on the next block. - run_to_block(3); + System::run_to_block::(3); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -163,19 +163,19 @@ fn periodic_scheduling_works() { })) .unwrap() )); - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); - run_to_block(4); + System::run_to_block::(4); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(6); + System::run_to_block::(6); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(7); + System::run_to_block::(7); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); - run_to_block(9); + System::run_to_block::(9); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); - run_to_block(10); + System::run_to_block::(10); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); }); } @@ -201,37 +201,37 @@ fn retry_scheduling_works() { // retry 10 times every 3 blocks assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 3)); assert_eq!(Retries::::iter().count(), 1); - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); assert!(Agenda::::get(4)[0].is_some()); // task should be retried in block 7 - run_to_block(4); + System::run_to_block::(4); assert!(Agenda::::get(4).is_empty()); assert!(Agenda::::get(7)[0].is_some()); assert!(logger::log().is_empty()); - run_to_block(6); + System::run_to_block::(6); assert!(Agenda::::get(7)[0].is_some()); assert!(logger::log().is_empty()); // task still fails, should be retried in block 10 - run_to_block(7); + System::run_to_block::(7); assert!(Agenda::::get(7).is_empty()); assert!(Agenda::::get(10)[0].is_some()); assert!(logger::log().is_empty()); - run_to_block(8); + System::run_to_block::(8); assert!(Agenda::::get(10)[0].is_some()); assert!(logger::log().is_empty()); - run_to_block(9); + System::run_to_block::(9); assert!(logger::log().is_empty()); assert_eq!(Retries::::iter().count(), 1); // finally it should succeed - run_to_block(10); + System::run_to_block::(10); assert_eq!(logger::log(), vec![(root(), 42u32)]); assert_eq!(Retries::::iter().count(), 0); - run_to_block(11); + System::run_to_block::(11); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(12); + System::run_to_block::(12); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -262,37 +262,37 @@ fn named_retry_scheduling_works() { // retry 10 times every 3 blocks assert_ok!(Scheduler::set_retry_named(root().into(), [1u8; 32], 10, 3)); assert_eq!(Retries::::iter().count(), 1); - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); assert!(Agenda::::get(4)[0].is_some()); // task should be retried in block 7 - run_to_block(4); + System::run_to_block::(4); assert!(Agenda::::get(4).is_empty()); assert!(Agenda::::get(7)[0].is_some()); assert!(logger::log().is_empty()); - run_to_block(6); + System::run_to_block::(6); assert!(Agenda::::get(7)[0].is_some()); assert!(logger::log().is_empty()); // task still fails, should be retried in block 10 - run_to_block(7); + System::run_to_block::(7); assert!(Agenda::::get(7).is_empty()); assert!(Agenda::::get(10)[0].is_some()); assert!(logger::log().is_empty()); - run_to_block(8); + System::run_to_block::(8); assert!(Agenda::::get(10)[0].is_some()); assert!(logger::log().is_empty()); - run_to_block(9); + System::run_to_block::(9); assert!(logger::log().is_empty()); assert_eq!(Retries::::iter().count(), 1); // finally it should succeed - run_to_block(10); + System::run_to_block::(10); assert_eq!(logger::log(), vec![(root(), 42u32)]); assert_eq!(Retries::::iter().count(), 0); - run_to_block(11); + System::run_to_block::(11); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(12); + System::run_to_block::(12); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -333,11 +333,11 @@ fn retry_scheduling_multiple_tasks_works() { // task 42 will be retried 10 times every 3 blocks assert_ok!(Scheduler::set_retry(root().into(), (4, 1), 10, 3)); assert_eq!(Retries::::iter().count(), 2); - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); assert_eq!(Agenda::::get(4).len(), 2); // both tasks fail - run_to_block(4); + System::run_to_block::(4); assert!(Agenda::::get(4).is_empty()); // 20 is rescheduled for next block assert_eq!(Agenda::::get(5).len(), 1); @@ -345,41 +345,41 @@ fn retry_scheduling_multiple_tasks_works() { assert_eq!(Agenda::::get(7).len(), 1); assert!(logger::log().is_empty()); // 20 still fails - run_to_block(5); + System::run_to_block::(5); // 20 rescheduled for next block assert_eq!(Agenda::::get(6).len(), 1); assert_eq!(Agenda::::get(7).len(), 1); assert_eq!(Retries::::iter().count(), 2); assert!(logger::log().is_empty()); // 20 still fails - run_to_block(6); + System::run_to_block::(6); // rescheduled for next block together with 42 assert_eq!(Agenda::::get(7).len(), 2); assert_eq!(Retries::::iter().count(), 2); assert!(logger::log().is_empty()); // both tasks will fail, for 20 it was the last retry so it's dropped - run_to_block(7); + System::run_to_block::(7); assert!(Agenda::::get(7).is_empty()); assert!(Agenda::::get(8).is_empty()); // 42 is rescheduled for block 10 assert_eq!(Agenda::::get(10).len(), 1); assert_eq!(Retries::::iter().count(), 1); assert!(logger::log().is_empty()); - run_to_block(8); + System::run_to_block::(8); assert_eq!(Agenda::::get(10).len(), 1); assert!(logger::log().is_empty()); - run_to_block(9); + System::run_to_block::(9); assert!(logger::log().is_empty()); assert_eq!(Retries::::iter().count(), 1); // 42 runs successfully - run_to_block(10); + System::run_to_block::(10); assert_eq!(logger::log(), vec![(root(), 42u32)]); assert_eq!(Retries::::iter().count(), 0); - run_to_block(11); + System::run_to_block::(11); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(12); + System::run_to_block::(12); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -422,11 +422,11 @@ fn retry_scheduling_multiple_named_tasks_works() { // task 42 will be retried 10 times every 3 block assert_ok!(Scheduler::set_retry_named(root().into(), [42u8; 32], 10, 3)); assert_eq!(Retries::::iter().count(), 2); - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); assert_eq!(Agenda::::get(4).len(), 2); // both tasks fail - run_to_block(4); + System::run_to_block::(4); assert!(Agenda::::get(4).is_empty()); // 42 is rescheduled for block 7 assert_eq!(Agenda::::get(7).len(), 1); @@ -434,41 +434,41 @@ fn retry_scheduling_multiple_named_tasks_works() { assert_eq!(Agenda::::get(5).len(), 1); assert!(logger::log().is_empty()); // 20 still fails - run_to_block(5); + System::run_to_block::(5); // 20 rescheduled for next block assert_eq!(Agenda::::get(6).len(), 1); assert_eq!(Agenda::::get(7).len(), 1); assert_eq!(Retries::::iter().count(), 2); assert!(logger::log().is_empty()); // 20 still fails - run_to_block(6); + System::run_to_block::(6); // 20 rescheduled for next block together with 42 assert_eq!(Agenda::::get(7).len(), 2); assert_eq!(Retries::::iter().count(), 2); assert!(logger::log().is_empty()); // both tasks will fail, for 20 it was the last retry so it's dropped - run_to_block(7); + System::run_to_block::(7); assert!(Agenda::::get(7).is_empty()); assert!(Agenda::::get(8).is_empty()); // 42 is rescheduled for block 10 assert_eq!(Agenda::::get(10).len(), 1); assert_eq!(Retries::::iter().count(), 1); assert!(logger::log().is_empty()); - run_to_block(8); + System::run_to_block::(8); assert_eq!(Agenda::::get(10).len(), 1); assert!(logger::log().is_empty()); - run_to_block(9); + System::run_to_block::(9); assert!(logger::log().is_empty()); assert_eq!(Retries::::iter().count(), 1); // 42 runs successfully - run_to_block(10); + System::run_to_block::(10); assert_eq!(logger::log(), vec![(root(), 42u32)]); assert_eq!(Retries::::iter().count(), 0); - run_to_block(11); + System::run_to_block::(11); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(12); + System::run_to_block::(12); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -495,33 +495,33 @@ fn retry_scheduling_with_period_works() { // 42 will be retried 10 times every 2 blocks assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 2)); assert_eq!(Retries::::iter().count(), 1); - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); assert!(Agenda::::get(4)[0].is_some()); // 42 runs successfully once, it will run again at block 7 - run_to_block(4); + System::run_to_block::(4); assert!(Agenda::::get(4).is_empty()); assert!(Agenda::::get(7)[0].is_some()); assert_eq!(Retries::::iter().count(), 1); assert_eq!(logger::log(), vec![(root(), 42u32)]); // nothing changed - run_to_block(6); + System::run_to_block::(6); assert!(Agenda::::get(7)[0].is_some()); assert_eq!(logger::log(), vec![(root(), 42u32)]); // 42 runs successfully again, it will run again at block 10 - run_to_block(7); + System::run_to_block::(7); assert!(Agenda::::get(7).is_empty()); assert!(Agenda::::get(10)[0].is_some()); assert_eq!(Retries::::iter().count(), 1); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); - run_to_block(9); + System::run_to_block::(9); assert!(Agenda::::get(10)[0].is_some()); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); // 42 has 10 retries left out of a total of 10 assert_eq!(Retries::::get((10, 0)).unwrap().remaining, 10); // 42 will fail because we're outside the set threshold (block number in `4..8`), so it // should be retried in 2 blocks (at block 12) - run_to_block(10); + System::run_to_block::(10); // should be queued for the normal period of 3 blocks assert!(Agenda::::get(13)[0].is_some()); // should also be queued to be retried in 2 blocks @@ -532,7 +532,7 @@ fn retry_scheduling_with_period_works() { assert_eq!(Retries::::iter().count(), 2); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); // 42 will fail again - run_to_block(12); + System::run_to_block::(12); // should still be queued for the normal period assert!(Agenda::::get(13)[0].is_some()); // should be queued to be retried in 2 blocks @@ -543,7 +543,7 @@ fn retry_scheduling_with_period_works() { assert_eq!(Retries::::iter().count(), 2); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); // 42 will fail for the regular periodic run - run_to_block(13); + System::run_to_block::(13); // should still be queued for the normal period assert!(Agenda::::get(16)[0].is_some()); // should still be queued to be retried next block @@ -560,7 +560,7 @@ fn retry_scheduling_with_period_works() { // change the threshold to allow the task to succeed Threshold::::put((14, 100)); // first retry should now succeed - run_to_block(14); + System::run_to_block::(14); assert!(Agenda::::get(15)[0].as_ref().unwrap().maybe_periodic.is_none()); assert_eq!(Agenda::::get(16).iter().filter(|entry| entry.is_some()).count(), 1); assert!(Agenda::::get(16)[0].is_some()); @@ -569,7 +569,7 @@ fn retry_scheduling_with_period_works() { assert_eq!(Retries::::iter().count(), 2); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); // second retry should also succeed - run_to_block(15); + System::run_to_block::(15); assert_eq!(Agenda::::get(16).iter().filter(|entry| entry.is_some()).count(), 1); assert!(Agenda::::get(16)[0].is_some()); assert!(Agenda::::get(17).is_empty()); @@ -580,7 +580,7 @@ fn retry_scheduling_with_period_works() { vec![(root(), 42u32), (root(), 42u32), (root(), 42u32), (root(), 42u32)] ); // normal periodic run on block 16 will succeed - run_to_block(16); + System::run_to_block::(16); // next periodic run at block 19 assert!(Agenda::::get(19)[0].is_some()); assert!(Agenda::::get(18).is_empty()); @@ -598,7 +598,7 @@ fn retry_scheduling_with_period_works() { ] ); // final periodic run on block 19 will succeed - run_to_block(19); + System::run_to_block::(19); // next periodic run at block 19 assert_eq!(Agenda::::iter().count(), 0); assert_eq!(Retries::::iter().count(), 0); @@ -639,33 +639,33 @@ fn named_retry_scheduling_with_period_works() { // 42 will be retried 10 times every 2 blocks assert_ok!(Scheduler::set_retry_named(root().into(), [42u8; 32], 10, 2)); assert_eq!(Retries::::iter().count(), 1); - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); assert!(Agenda::::get(4)[0].is_some()); // 42 runs successfully once, it will run again at block 7 - run_to_block(4); + System::run_to_block::(4); assert!(Agenda::::get(4).is_empty()); assert!(Agenda::::get(7)[0].is_some()); assert_eq!(Retries::::iter().count(), 1); assert_eq!(logger::log(), vec![(root(), 42u32)]); // nothing changed - run_to_block(6); + System::run_to_block::(6); assert!(Agenda::::get(7)[0].is_some()); assert_eq!(logger::log(), vec![(root(), 42u32)]); // 42 runs successfully again, it will run again at block 10 - run_to_block(7); + System::run_to_block::(7); assert!(Agenda::::get(7).is_empty()); assert!(Agenda::::get(10)[0].is_some()); assert_eq!(Retries::::iter().count(), 1); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); - run_to_block(9); + System::run_to_block::(9); assert!(Agenda::::get(10)[0].is_some()); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); // 42 has 10 retries left out of a total of 10 assert_eq!(Retries::::get((10, 0)).unwrap().remaining, 10); // 42 will fail because we're outside the set threshold (block number in `4..8`), so it // should be retried in 2 blocks (at block 12) - run_to_block(10); + System::run_to_block::(10); // should be queued for the normal period of 3 blocks assert!(Agenda::::get(13)[0].is_some()); // should also be queued to be retried in 2 blocks @@ -677,7 +677,7 @@ fn named_retry_scheduling_with_period_works() { assert_eq!(Lookup::::get([42u8; 32]).unwrap(), (13, 0)); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); // 42 will fail again - run_to_block(12); + System::run_to_block::(12); // should still be queued for the normal period assert!(Agenda::::get(13)[0].is_some()); // should be queued to be retried in 2 blocks @@ -688,7 +688,7 @@ fn named_retry_scheduling_with_period_works() { assert_eq!(Retries::::iter().count(), 2); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); // 42 will fail for the regular periodic run - run_to_block(13); + System::run_to_block::(13); // should still be queued for the normal period assert!(Agenda::::get(16)[0].is_some()); // should still be queued to be retried next block @@ -706,7 +706,7 @@ fn named_retry_scheduling_with_period_works() { // change the threshold to allow the task to succeed Threshold::::put((14, 100)); // first retry should now succeed - run_to_block(14); + System::run_to_block::(14); assert!(Agenda::::get(15)[0].as_ref().unwrap().maybe_periodic.is_none()); assert_eq!(Agenda::::get(16).iter().filter(|entry| entry.is_some()).count(), 1); assert!(Agenda::::get(16)[0].is_some()); @@ -715,7 +715,7 @@ fn named_retry_scheduling_with_period_works() { assert_eq!(Retries::::iter().count(), 2); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); // second retry should also succeed - run_to_block(15); + System::run_to_block::(15); assert_eq!(Agenda::::get(16).iter().filter(|entry| entry.is_some()).count(), 1); assert!(Agenda::::get(16)[0].is_some()); assert!(Agenda::::get(17).is_empty()); @@ -727,7 +727,7 @@ fn named_retry_scheduling_with_period_works() { vec![(root(), 42u32), (root(), 42u32), (root(), 42u32), (root(), 42u32)] ); // normal periodic run on block 16 will succeed - run_to_block(16); + System::run_to_block::(16); // next periodic run at block 19 assert!(Agenda::::get(19)[0].is_some()); assert!(Agenda::::get(18).is_empty()); @@ -746,7 +746,7 @@ fn named_retry_scheduling_with_period_works() { ] ); // final periodic run on block 19 will succeed - run_to_block(19); + System::run_to_block::(19); // next periodic run at block 19 assert_eq!(Agenda::::iter().count(), 0); assert_eq!(Retries::::iter().count(), 0); @@ -786,12 +786,12 @@ fn retry_scheduling_expires() { // task 42 will be retried 3 times every block assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 3, 1)); assert_eq!(Retries::::iter().count(), 1); - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); // task 42 is scheduled for next block assert!(Agenda::::get(4)[0].is_some()); // task fails because we're past block 3 - run_to_block(4); + System::run_to_block::(4); // task is scheduled for next block assert!(Agenda::::get(4).is_empty()); assert!(Agenda::::get(5)[0].is_some()); @@ -799,7 +799,7 @@ fn retry_scheduling_expires() { assert_eq!(Retries::::get((5, 0)).unwrap().remaining, 2); assert!(logger::log().is_empty()); // task fails again - run_to_block(5); + System::run_to_block::(5); // task is scheduled for next block assert!(Agenda::::get(5).is_empty()); assert!(Agenda::::get(6)[0].is_some()); @@ -807,7 +807,7 @@ fn retry_scheduling_expires() { assert_eq!(Retries::::get((6, 0)).unwrap().remaining, 1); assert!(logger::log().is_empty()); // task fails again - run_to_block(6); + System::run_to_block::(6); // task is scheduled for next block assert!(Agenda::::get(6).is_empty()); assert!(Agenda::::get(7)[0].is_some()); @@ -815,7 +815,7 @@ fn retry_scheduling_expires() { assert_eq!(Retries::::get((7, 0)).unwrap().remaining, 0); assert!(logger::log().is_empty()); // task fails again - run_to_block(7); + System::run_to_block::(7); // task ran out of retries so it gets dropped assert_eq!(Agenda::::iter().count(), 0); assert_eq!(Retries::::iter().count(), 0); @@ -949,17 +949,17 @@ fn retry_periodic_full_cycle() { // 42 will be retried 2 times every block assert_ok!(Scheduler::set_retry_named(root().into(), [42u8; 32], 2, 1)); assert_eq!(Retries::::iter().count(), 1); - run_to_block(9); + System::run_to_block::(9); assert!(logger::log().is_empty()); assert!(Agenda::::get(10)[0].is_some()); // 42 runs successfully once, it will run again at block 110 - run_to_block(10); + System::run_to_block::(10); assert!(Agenda::::get(10).is_empty()); assert!(Agenda::::get(110)[0].is_some()); assert_eq!(Retries::::iter().count(), 1); assert_eq!(logger::log(), vec![(root(), 42u32)]); // nothing changed - run_to_block(109); + System::run_to_block::(109); assert!(Agenda::::get(110)[0].is_some()); // original task still has 2 remaining retries assert_eq!(Retries::::get((110, 0)).unwrap().remaining, 2); @@ -968,7 +968,7 @@ fn retry_periodic_full_cycle() { Threshold::::put((1, 2)); // 42 will fail because we're outside the set threshold (block number in `1..2`), so it // should be retried next block (at block 111) - run_to_block(110); + System::run_to_block::(110); // should be queued for the normal period of 100 blocks assert!(Agenda::::get(210)[0].is_some()); // should also be queued to be retried next block @@ -980,7 +980,7 @@ fn retry_periodic_full_cycle() { assert_eq!(Retries::::iter().count(), 2); assert_eq!(logger::log(), vec![(root(), 42u32)]); // 42 retry will fail again - run_to_block(111); + System::run_to_block::(111); // should still be queued for the normal period assert!(Agenda::::get(210)[0].is_some()); // should be queued to be retried next block @@ -991,20 +991,20 @@ fn retry_periodic_full_cycle() { assert_eq!(Retries::::iter().count(), 2); assert_eq!(logger::log(), vec![(root(), 42u32)]); // 42 retry will fail again - run_to_block(112); + System::run_to_block::(112); // should still be queued for the normal period assert!(Agenda::::get(210)[0].is_some()); // 42 retry clone ran out of retries, must have been evicted assert_eq!(Agenda::::iter().count(), 1); // advance - run_to_block(209); + System::run_to_block::(209); // should still be queued for the normal period assert!(Agenda::::get(210)[0].is_some()); // 42 retry clone ran out of retries, must have been evicted assert_eq!(Agenda::::iter().count(), 1); // 42 should fail again and should spawn another retry clone - run_to_block(210); + System::run_to_block::(210); // should be queued for the normal period of 100 blocks assert!(Agenda::::get(310)[0].is_some()); // should also be queued to be retried next block @@ -1018,7 +1018,7 @@ fn retry_periodic_full_cycle() { // make 42 run successfully again Threshold::::put((1, 1000)); // 42 retry clone should now succeed - run_to_block(211); + System::run_to_block::(211); // should be queued for the normal period of 100 blocks assert!(Agenda::::get(310)[0].is_some()); // retry was successful, retry task should have been discarded @@ -1029,7 +1029,7 @@ fn retry_periodic_full_cycle() { assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); // fast forward to the last periodic run of 42 - run_to_block(310); + System::run_to_block::(310); // 42 was successful, the period ended as this was the 4th scheduled periodic run so 42 must // have been discarded assert_eq!(Agenda::::iter().count(), 0); @@ -1057,7 +1057,7 @@ fn reschedule_works() { (4, 0) ); - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); assert_eq!(Scheduler::do_reschedule((4, 0), DispatchTime::At(6)).unwrap(), (6, 0)); @@ -1067,13 +1067,13 @@ fn reschedule_works() { Error::::RescheduleNoChange ); - run_to_block(4); + System::run_to_block::(4); assert!(logger::log().is_empty()); - run_to_block(6); + System::run_to_block::(6); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -1097,7 +1097,7 @@ fn reschedule_named_works() { (4, 0) ); - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); assert_eq!(Scheduler::do_reschedule_named([1u8; 32], DispatchTime::At(6)).unwrap(), (6, 0)); @@ -1107,13 +1107,13 @@ fn reschedule_named_works() { Error::::RescheduleNoChange ); - run_to_block(4); + System::run_to_block::(4); assert!(logger::log().is_empty()); - run_to_block(6); + System::run_to_block::(6); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -1137,16 +1137,16 @@ fn reschedule_named_periodic_works() { (4, 0) ); - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); assert_eq!(Scheduler::do_reschedule_named([1u8; 32], DispatchTime::At(5)).unwrap(), (5, 0)); assert_eq!(Scheduler::do_reschedule_named([1u8; 32], DispatchTime::At(6)).unwrap(), (6, 0)); - run_to_block(5); + System::run_to_block::(5); assert!(logger::log().is_empty()); - run_to_block(6); + System::run_to_block::(6); assert_eq!(logger::log(), vec![(root(), 42u32)]); assert_eq!( @@ -1154,16 +1154,16 @@ fn reschedule_named_periodic_works() { (10, 0) ); - run_to_block(9); + System::run_to_block::(9); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(10); + System::run_to_block::(10); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); - run_to_block(13); + System::run_to_block::(13); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); }); } @@ -1197,11 +1197,11 @@ fn cancel_named_scheduling_works_with_normal_cancel() { .unwrap(), ) .unwrap(); - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); assert_ok!(Scheduler::do_cancel_named(None, [1u8; 32])); assert_ok!(Scheduler::do_cancel(None, i)); - run_to_block(100); + System::run_to_block::(100); assert!(logger::log().is_empty()); }); } @@ -1251,13 +1251,13 @@ fn cancel_named_periodic_scheduling_works() { .unwrap(), ) .unwrap(); - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); - run_to_block(4); + System::run_to_block::(4); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(6); + System::run_to_block::(6); assert_ok!(Scheduler::do_cancel_named(None, [1u8; 32])); - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]); }); } @@ -1283,9 +1283,9 @@ fn scheduler_respects_weight_limits() { Preimage::bound(call).unwrap(), )); // 69 and 42 do not fit together - run_to_block(4); + System::run_to_block::(4); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(5); + System::run_to_block::(5); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]); }); } @@ -1316,26 +1316,26 @@ fn retry_respects_weight_limits() { // set a retry config for 20 for 10 retries every block assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 1)); // 20 should fail and be retried later - run_to_block(4); + System::run_to_block::(4); assert!(Agenda::::get(5)[0].is_some()); assert!(Agenda::::get(8)[0].is_some()); assert_eq!(Retries::::iter().count(), 1); assert!(logger::log().is_empty()); // 20 still fails but is scheduled next block together with 42 - run_to_block(7); + System::run_to_block::(7); assert_eq!(Agenda::::get(8).len(), 2); assert_eq!(Retries::::iter().count(), 1); assert!(logger::log().is_empty()); // 20 and 42 do not fit together // 42 is executed as it was first in the queue // 20 is still on the 8th block's agenda - run_to_block(8); + System::run_to_block::(8); assert!(Agenda::::get(8)[0].is_none()); assert!(Agenda::::get(8)[1].is_some()); assert_eq!(Retries::::iter().count(), 1); assert_eq!(logger::log(), vec![(root(), 42u32)]); // 20 is executed and the schedule is cleared - run_to_block(9); + System::run_to_block::(9); assert_eq!(Agenda::::iter().count(), 0); assert_eq!(Retries::::iter().count(), 0); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 20u32)]); @@ -1386,7 +1386,7 @@ fn try_schedule_retry_respects_weight_limits() { // set a retry config for 20 for 10 retries every block assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 1)); // 20 should fail and, because of insufficient weight, it should not be scheduled again - run_to_block(4); + System::run_to_block::(4); // nothing else should be scheduled assert_eq!(Agenda::::iter().count(), 0); assert_eq!(Retries::::iter().count(), 0); @@ -1415,7 +1415,7 @@ fn scheduler_does_not_delete_permanently_overweight_call() { Preimage::bound(call).unwrap(), )); // Never executes. - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log(), vec![]); // Assert the `PermanentlyOverweight` event. @@ -1445,7 +1445,7 @@ fn scheduler_handles_periodic_failure() { bound.clone(), )); // Executes 5 times till block 20. - run_to_block(20); + System::run_to_block::(20); assert_eq!(logger::log().len(), 5); // Block 28 will already be full. @@ -1460,7 +1460,7 @@ fn scheduler_handles_periodic_failure() { } // Going to block 24 will emit a `PeriodicFailed` event. - run_to_block(24); + System::run_to_block::(24); assert_eq!(logger::log().len(), 6); assert_eq!( @@ -1498,7 +1498,7 @@ fn scheduler_handles_periodic_unavailable_preimage() { assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(1), call.encode())); // Executes 1 times till block 4. - run_to_block(4); + System::run_to_block::(4); assert_eq!(logger::log().len(), 1); // As the public api doesn't support to remove a noted preimage, we need to first unnote it @@ -1508,7 +1508,7 @@ fn scheduler_handles_periodic_unavailable_preimage() { Preimage::request(&hash); // Does not ever execute again. - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log().len(), 1); // The preimage is not requested anymore. @@ -1536,7 +1536,7 @@ fn scheduler_respects_priority_ordering() { root(), Preimage::bound(call).unwrap(), )); - run_to_block(4); + System::run_to_block::(4); assert_eq!(logger::log(), vec![(root(), 69u32), (root(), 42u32)]); }); } @@ -1571,10 +1571,10 @@ fn scheduler_respects_priority_ordering_with_soft_deadlines() { )); // 2600 does not fit with 69 or 42, but has higher priority, so will go through - run_to_block(4); + System::run_to_block::(4); assert_eq!(logger::log(), vec![(root(), 2600u32)]); // 69 and 42 fit together - run_to_block(5); + System::run_to_block::(5); assert_eq!(logger::log(), vec![(root(), 2600u32), (root(), 69u32), (root(), 42u32)]); }); } @@ -1701,14 +1701,14 @@ fn root_calls_works() { Scheduler::schedule_named(RuntimeOrigin::root(), [1u8; 32], 4, None, 127, call,) ); assert_ok!(Scheduler::schedule(RuntimeOrigin::root(), 4, None, 127, call2)); - run_to_block(3); + System::run_to_block::(3); // Scheduled calls are in the agenda. assert_eq!(Agenda::::get(4).len(), 2); assert!(logger::log().is_empty()); assert_ok!(Scheduler::cancel_named(RuntimeOrigin::root(), [1u8; 32])); assert_ok!(Scheduler::cancel(RuntimeOrigin::root(), 4, 1)); // Scheduled calls are made NONE, so should not effect state - run_to_block(100); + System::run_to_block::(100); assert!(logger::log().is_empty()); }); } @@ -1716,7 +1716,7 @@ fn root_calls_works() { #[test] fn fails_to_schedule_task_in_the_past() { new_test_ext().execute_with(|| { - run_to_block(3); + System::run_to_block::(3); let call1 = Box::new(RuntimeCall::Logger(LoggerCall::log { i: 69, @@ -1768,14 +1768,14 @@ fn should_use_origin() { call, )); assert_ok!(Scheduler::schedule(system::RawOrigin::Signed(1).into(), 4, None, 127, call2,)); - run_to_block(3); + System::run_to_block::(3); // Scheduled calls are in the agenda. assert_eq!(Agenda::::get(4).len(), 2); assert!(logger::log().is_empty()); assert_ok!(Scheduler::cancel_named(system::RawOrigin::Signed(1).into(), [1u8; 32])); assert_ok!(Scheduler::cancel(system::RawOrigin::Signed(1).into(), 4, 1)); // Scheduled calls are made NONE, so should not effect state - run_to_block(100); + System::run_to_block::(100); assert!(logger::log().is_empty()); }); } @@ -1829,7 +1829,7 @@ fn should_check_origin_for_cancel() { call, )); assert_ok!(Scheduler::schedule(system::RawOrigin::Signed(1).into(), 4, None, 127, call2,)); - run_to_block(3); + System::run_to_block::(3); // Scheduled calls are in the agenda. assert_eq!(Agenda::::get(4).len(), 2); assert!(logger::log().is_empty()); @@ -1840,7 +1840,7 @@ fn should_check_origin_for_cancel() { assert_noop!(Scheduler::cancel(system::RawOrigin::Signed(2).into(), 4, 1), BadOrigin); assert_noop!(Scheduler::cancel_named(system::RawOrigin::Root.into(), [1u8; 32]), BadOrigin); assert_noop!(Scheduler::cancel(system::RawOrigin::Root.into(), 4, 1), BadOrigin); - run_to_block(5); + System::run_to_block::(5); assert_eq!( logger::log(), vec![ @@ -1888,17 +1888,17 @@ fn cancel_removes_retry_entry() { // task 42 will be retried 10 times every 3 blocks assert_ok!(Scheduler::set_retry_named(root().into(), [1u8; 32], 10, 1)); assert_eq!(Retries::::iter().count(), 2); - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); assert_eq!(Agenda::::get(4).len(), 2); // both tasks fail - run_to_block(4); + System::run_to_block::(4); assert!(Agenda::::get(4).is_empty()); // 42 and 20 are rescheduled for next block assert_eq!(Agenda::::get(5).len(), 2); assert!(logger::log().is_empty()); // 42 and 20 still fail - run_to_block(5); + System::run_to_block::(5); // 42 and 20 rescheduled for next block assert_eq!(Agenda::::get(6).len(), 2); assert_eq!(Retries::::iter().count(), 2); @@ -1909,7 +1909,7 @@ fn cancel_removes_retry_entry() { assert!(Scheduler::cancel(root().into(), 6, 0).is_ok()); // 20 is removed, 42 still fails - run_to_block(6); + System::run_to_block::(6); // 42 rescheduled for next block assert_eq!(Agenda::::get(7).len(), 1); // 20's retry entry is removed @@ -1920,7 +1920,7 @@ fn cancel_removes_retry_entry() { assert!(Scheduler::cancel(root().into(), 7, 0).is_ok()); // both tasks are canceled, everything is removed now - run_to_block(7); + System::run_to_block::(7); assert!(Agenda::::get(8).is_empty()); assert_eq!(Retries::::iter().count(), 0); }); @@ -1963,7 +1963,7 @@ fn cancel_retries_works() { // task 42 will be retried 10 times every 3 blocks assert_ok!(Scheduler::set_retry_named(root().into(), [1u8; 32], 10, 1)); assert_eq!(Retries::::iter().count(), 2); - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); assert_eq!(Agenda::::get(4).len(), 2); // cancel the retry config for 20 @@ -1972,7 +1972,7 @@ fn cancel_retries_works() { // cancel the retry config for 42 assert_ok!(Scheduler::cancel_retry_named(root().into(), [1u8; 32])); assert_eq!(Retries::::iter().count(), 0); - run_to_block(4); + System::run_to_block::(4); // both tasks failed and there are no more retries, so they are evicted assert_eq!(Agenda::::get(4).len(), 0); assert_eq!(Retries::::iter().count(), 0); @@ -2287,7 +2287,7 @@ fn postponed_named_task_cannot_be_rescheduled() { assert!(Lookup::::contains_key(name)); // Run to a very large block. - run_to_block(10); + System::run_to_block::(10); // It was not executed. assert!(logger::log().is_empty()); @@ -2321,7 +2321,7 @@ fn postponed_named_task_cannot_be_rescheduled() { // Finally add the preimage. assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(0), call.encode())); - run_to_block(1000); + System::run_to_block::(1000); // It did not execute. assert!(logger::log().is_empty()); assert!(!Preimage::is_requested(&hash)); @@ -2357,14 +2357,14 @@ fn scheduler_v3_anon_basic_works() { ) .unwrap(); - run_to_block(3); + System::run_to_block::(3); // Did not execute till block 3. assert!(logger::log().is_empty()); // Executes in block 4. - run_to_block(4); + System::run_to_block::(4); assert_eq!(logger::log(), vec![(root(), 42u32)]); // ... but not again. - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -2389,7 +2389,7 @@ fn scheduler_v3_anon_cancel_works() { // Cancel the call. assert_ok!(>::cancel(address)); // It did not get executed. - run_to_block(100); + System::run_to_block::(100); assert!(logger::log().is_empty()); // Cannot cancel again. assert_err!(>::cancel(address), DispatchError::Unavailable); @@ -2413,7 +2413,7 @@ fn scheduler_v3_anon_reschedule_works() { ) .unwrap(); - run_to_block(3); + System::run_to_block::(3); // Did not execute till block 3. assert!(logger::log().is_empty()); @@ -2430,9 +2430,9 @@ fn scheduler_v3_anon_reschedule_works() { // Re-schedule to block 5. assert_ok!(>::reschedule(address, DispatchTime::At(5))); // Scheduled for block 5. - run_to_block(4); + System::run_to_block::(4); assert!(logger::log().is_empty()); - run_to_block(5); + System::run_to_block::(5); // Does execute in block 5. assert_eq!(logger::log(), vec![(root(), 42)]); // Cannot re-schedule executed task. @@ -2461,14 +2461,14 @@ fn scheduler_v3_anon_next_schedule_time_works() { ) .unwrap(); - run_to_block(3); + System::run_to_block::(3); // Did not execute till block 3. assert!(logger::log().is_empty()); // Scheduled for block 4. assert_eq!(>::next_dispatch_time(address), Ok(4)); // Block 4 executes it. - run_to_block(4); + System::run_to_block::(4); assert_eq!(logger::log(), vec![(root(), 42)]); // It has no dispatch time anymore. @@ -2498,7 +2498,7 @@ fn scheduler_v3_anon_reschedule_and_next_schedule_time_work() { ) .unwrap(); - run_to_block(3); + System::run_to_block::(3); // Did not execute till block 3. assert!(logger::log().is_empty()); @@ -2512,10 +2512,10 @@ fn scheduler_v3_anon_reschedule_and_next_schedule_time_work() { assert_eq!(>::next_dispatch_time(address), Ok(5)); // Block 4 does nothing. - run_to_block(4); + System::run_to_block::(4); assert!(logger::log().is_empty()); // Block 5 executes it. - run_to_block(5); + System::run_to_block::(5); assert_eq!(logger::log(), vec![(root(), 42)]); }); } @@ -2548,7 +2548,7 @@ fn scheduler_v3_anon_schedule_agenda_overflows() { DispatchError::Exhausted ); - run_to_block(4); + System::run_to_block::(4); // All scheduled calls are executed. assert_eq!(logger::log().len() as u32, max); }); @@ -2597,7 +2597,7 @@ fn scheduler_v3_anon_cancel_and_schedule_fills_holes() { assert_eq!(i, index); } - run_to_block(4); + System::run_to_block::(4); // Maximum number of calls are executed. assert_eq!(logger::log().len() as u32, max); }); @@ -2643,7 +2643,7 @@ fn scheduler_v3_anon_reschedule_fills_holes() { assert_eq!(new, want); } - run_to_block(4); + System::run_to_block::(4); // Maximum number of calls are executed. assert_eq!(logger::log().len() as u32, max); }); @@ -2670,14 +2670,14 @@ fn scheduler_v3_named_basic_works() { ) .unwrap(); - run_to_block(3); + System::run_to_block::(3); // Did not execute till block 3. assert!(logger::log().is_empty()); // Executes in block 4. - run_to_block(4); + System::run_to_block::(4); assert_eq!(logger::log(), vec![(root(), 42u32)]); // ... but not again. - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -2705,7 +2705,7 @@ fn scheduler_v3_named_cancel_named_works() { // Cancel the call by name. assert_ok!(>::cancel_named(name)); // It did not get executed. - run_to_block(100); + System::run_to_block::(100); assert!(logger::log().is_empty()); // Cannot cancel again. assert_noop!(>::cancel_named(name), DispatchError::Unavailable); @@ -2735,7 +2735,7 @@ fn scheduler_v3_named_cancel_without_name_works() { // Cancel the call by address. assert_ok!(>::cancel(address)); // It did not get executed. - run_to_block(100); + System::run_to_block::(100); assert!(logger::log().is_empty()); // Cannot cancel again. assert_err!(>::cancel(address), DispatchError::Unavailable); @@ -2762,7 +2762,7 @@ fn scheduler_v3_named_reschedule_named_works() { ) .unwrap(); - run_to_block(3); + System::run_to_block::(3); // Did not execute till block 3. assert!(logger::log().is_empty()); @@ -2784,9 +2784,9 @@ fn scheduler_v3_named_reschedule_named_works() { // Re-schedule to block 5. assert_ok!(>::reschedule_named(name, DispatchTime::At(5))); // Scheduled for block 5. - run_to_block(4); + System::run_to_block::(4); assert!(logger::log().is_empty()); - run_to_block(5); + System::run_to_block::(5); // Does execute in block 5. assert_eq!(logger::log(), vec![(root(), 42)]); // Cannot re-schedule executed task. @@ -2822,7 +2822,7 @@ fn scheduler_v3_named_next_schedule_time_works() { ) .unwrap(); - run_to_block(3); + System::run_to_block::(3); // Did not execute till block 3. assert!(logger::log().is_empty()); @@ -2831,7 +2831,7 @@ fn scheduler_v3_named_next_schedule_time_works() { // Also works by address. assert_eq!(>::next_dispatch_time(address), Ok(4)); // Block 4 executes it. - run_to_block(4); + System::run_to_block::(4); assert_eq!(logger::log(), vec![(root(), 42)]); // It has no dispatch time anymore. @@ -3025,7 +3025,7 @@ fn unavailable_call_is_detected() { assert!(Preimage::is_requested(&hash)); // Executes in block 4. - run_to_block(4); + System::run_to_block::(4); assert_eq!( System::events().last().unwrap().event, diff --git a/substrate/frame/society/src/mock.rs b/substrate/frame/society/src/mock.rs index 3c27c08a1061..8cb5dc823753 100644 --- a/substrate/frame/society/src/mock.rs +++ b/substrate/frame/society/src/mock.rs @@ -138,18 +138,6 @@ impl EnvBuilder { } } -/// Run until a particular block. -pub fn run_to_block(n: u64) { - while System::block_number() < n { - if System::block_number() > 1 { - System::on_finalize(System::block_number()); - } - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - Society::on_initialize(System::block_number()); - } -} - /// Creates a bid struct using input parameters. pub fn bid( who: AccountId, @@ -173,12 +161,12 @@ pub fn candidacy( pub fn next_challenge() { let challenge_period: u64 = ::ChallengePeriod::get(); let now = System::block_number(); - run_to_block(now + challenge_period - now % challenge_period); + System::run_to_block::(now + challenge_period - now % challenge_period); } pub fn next_voting() { if let Period::Voting { more, .. } = Society::period() { - run_to_block(System::block_number() + more); + System::run_to_block::(System::block_number() + more); } } @@ -235,8 +223,11 @@ pub fn conclude_intake(allow_resignation: bool, judge_intake: Option) { pub fn next_intake() { let claim_period: u64 = ::ClaimPeriod::get(); match Society::period() { - Period::Voting { more, .. } => run_to_block(System::block_number() + more + claim_period), - Period::Claim { more, .. } => run_to_block(System::block_number() + more), + Period::Voting { more, .. } => System::run_to_block::( + System::block_number() + more + claim_period, + ), + Period::Claim { more, .. } => + System::run_to_block::(System::block_number() + more), } } diff --git a/substrate/frame/society/src/tests.rs b/substrate/frame/society/src/tests.rs index 2a13f99855b5..22832f18b6fe 100644 --- a/substrate/frame/society/src/tests.rs +++ b/substrate/frame/society/src/tests.rs @@ -272,7 +272,7 @@ fn bidding_works() { // 40, now a member, can vote for 50 assert_ok!(Society::vote(Origin::signed(40), 50, true)); conclude_intake(true, None); - run_to_block(12); + System::run_to_block::(12); // 50 is now a member assert_eq!(members(), vec![10, 30, 40, 50]); // Pot is increased by 1000, and 500 is paid out. Total payout so far is 1200. @@ -282,7 +282,7 @@ fn bidding_works() { assert_eq!(candidacies(), vec![]); assert_ok!(Society::defender_vote(Origin::signed(10), true)); // Keep defender around // Next period - run_to_block(16); + System::run_to_block::(16); // Same members assert_eq!(members(), vec![10, 30, 40, 50]); // Pot is increased by 1000 again @@ -294,7 +294,7 @@ fn bidding_works() { // Candidate 60 is voted in. assert_ok!(Society::vote(Origin::signed(50), 60, true)); conclude_intake(true, None); - run_to_block(20); + System::run_to_block::(20); // 60 joins as a member assert_eq!(members(), vec![10, 30, 40, 50, 60]); // Pay them @@ -368,7 +368,7 @@ fn rejecting_skeptic_on_approved_is_punished() { } conclude_intake(true, None); assert_eq!(Members::::get(10).unwrap().strikes, 0); - run_to_block(12); + System::run_to_block::(12); assert_eq!(members(), vec![10, 20, 30, 40]); assert_eq!(Members::::get(skeptic).unwrap().strikes, 1); }); @@ -418,7 +418,7 @@ fn slash_payout_works() { Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![(8, 500)].try_into().unwrap() } ); - run_to_block(8); + System::run_to_block::(8); // payout should be here, but 500 less assert_ok!(Society::payout(RuntimeOrigin::signed(20))); assert_eq!(Balances::free_balance(20), 550); @@ -1315,7 +1315,7 @@ fn drop_candidate_works() { assert_ok!(Society::vote(Origin::signed(10), 40, false)); assert_ok!(Society::vote(Origin::signed(20), 40, false)); assert_ok!(Society::vote(Origin::signed(30), 40, false)); - run_to_block(12); + System::run_to_block::(12); assert_ok!(Society::drop_candidate(Origin::signed(50), 40)); // 40 candidacy has gone. assert_eq!(candidates(), vec![]); diff --git a/substrate/frame/src/lib.rs b/substrate/frame/src/lib.rs index f79a52bc6c5b..e3e58fc01b5f 100644 --- a/substrate/frame/src/lib.rs +++ b/substrate/frame/src/lib.rs @@ -325,7 +325,7 @@ pub mod testing_prelude { assert_storage_noop, hypothetically, storage_alias, }; - pub use frame_system::{self, mocking::*}; + pub use frame_system::{self, mocking::*, RunToBlockHooks}; #[deprecated(note = "Use `frame::testing_prelude::TestState` instead.")] pub use sp_io::TestExternalities; diff --git a/substrate/frame/staking/src/mock.rs b/substrate/frame/staking/src/mock.rs index df8cb38e8b37..769b84826b41 100644 --- a/substrate/frame/staking/src/mock.rs +++ b/substrate/frame/staking/src/mock.rs @@ -25,7 +25,7 @@ use frame_election_provider_support::{ use frame_support::{ assert_ok, derive_impl, ord_parameter_types, parameter_types, traits::{ - ConstU64, Currency, EitherOfDiverse, FindAuthor, Get, Hooks, Imbalance, LockableCurrency, + ConstU64, Currency, EitherOfDiverse, FindAuthor, Get, Imbalance, LockableCurrency, OnUnbalanced, OneSessionHandler, WithdrawReasons, }, weights::constants::RocksDbWeight, @@ -155,7 +155,7 @@ impl pallet_session::historical::Config for Test { } impl pallet_authorship::Config for Test { type FindAuthor = Author11; - type EventHandler = Pallet; + type EventHandler = (); } impl pallet_timestamp::Config for Test { @@ -544,13 +544,10 @@ impl ExtBuilder { let mut ext = sp_io::TestExternalities::from(storage); if self.initialize_first_session { - // We consider all test to start after timestamp is initialized This must be ensured by - // having `timestamp::on_initialize` called before `staking::on_initialize`. Also, if - // session length is 1, then it is already triggered. ext.execute_with(|| { - System::set_block_number(1); - Session::on_initialize(1); - >::on_initialize(1); + run_to_block(1); + + // Force reset the timestamp to the initial timestamp for easy testing. Timestamp::set_timestamp(INIT_TIMESTAMP); }); } @@ -618,33 +615,31 @@ pub(crate) fn bond_virtual_nominator( /// a block import/propose process where we first initialize the block, then execute some stuff (not /// in the function), and then finalize the block. pub(crate) fn run_to_block(n: BlockNumber) { - Staking::on_finalize(System::block_number()); - for b in (System::block_number() + 1)..=n { - System::set_block_number(b); - Session::on_initialize(b); - >::on_initialize(b); - Timestamp::set_timestamp(System::block_number() * BLOCK_TIME + INIT_TIMESTAMP); - if b != n { - Staking::on_finalize(System::block_number()); - } - } + System::run_to_block_with::( + n, + frame_system::RunToBlockHooks::default().after_initialize(|bn| { + Timestamp::set_timestamp(bn * BLOCK_TIME + INIT_TIMESTAMP); + }), + ); } /// Progresses from the current block number (whatever that may be) to the `P * session_index + 1`. -pub(crate) fn start_session(session_index: SessionIndex) { +pub(crate) fn start_session(end_session_idx: SessionIndex) { + let period = Period::get(); let end: u64 = if Offset::get().is_zero() { - (session_index as u64) * Period::get() + (end_session_idx as u64) * period } else { - Offset::get() + (session_index.saturating_sub(1) as u64) * Period::get() + Offset::get() + (end_session_idx.saturating_sub(1) as u64) * period }; + run_to_block(end); + + let curr_session_idx = Session::current_index(); + // session must have progressed properly. assert_eq!( - Session::current_index(), - session_index, - "current session index = {}, expected = {}", - Session::current_index(), - session_index, + curr_session_idx, end_session_idx, + "current session index = {curr_session_idx}, expected = {end_session_idx}", ); } diff --git a/substrate/frame/state-trie-migration/src/lib.rs b/substrate/frame/state-trie-migration/src/lib.rs index 61323b70b33d..1dc1a3928f2b 100644 --- a/substrate/frame/state-trie-migration/src/lib.rs +++ b/substrate/frame/state-trie-migration/src/lib.rs @@ -1309,16 +1309,17 @@ mod mock { pub(crate) fn run_to_block(n: u32) -> (H256, Weight) { let mut root = Default::default(); let mut weight_sum = Weight::zero(); + log::trace!(target: LOG_TARGET, "running from {:?} to {:?}", System::block_number(), n); - while System::block_number() < n { - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - weight_sum += StateTrieMigration::on_initialize(System::block_number()); + System::run_to_block_with::( + n, + frame_system::RunToBlockHooks::default().after_initialize(|bn| { + weight_sum += StateTrieMigration::on_initialize(bn); + root = *System::finalize().state_root(); + }), + ); - root = *System::finalize().state_root(); - System::on_finalize(System::block_number()); - } (root, weight_sum) } } diff --git a/substrate/frame/system/src/lib.rs b/substrate/frame/system/src/lib.rs index 894e1898ed15..f2bb5e290c94 100644 --- a/substrate/frame/system/src/lib.rs +++ b/substrate/frame/system/src/lib.rs @@ -1974,6 +1974,51 @@ impl Pallet { .collect::<_>() } + /// Simulate the execution of a block sequence up to a specified height, injecting the + /// provided hooks at each block. + /// + /// `on_finalize` is always called before `on_initialize` with the current block number. + /// `on_initalize` is always called with the next block number. + /// + /// These hooks allows custom logic to be executed at each block at specific location. + /// For example, you might use one of them to set a timestamp for each block. + #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] + pub fn run_to_block_with( + n: BlockNumberFor, + mut hooks: RunToBlockHooks, + ) where + AllPalletsWithSystem: frame_support::traits::OnInitialize> + + frame_support::traits::OnFinalize>, + { + let mut bn = Self::block_number(); + + while bn < n { + // Skip block 0. + if !bn.is_zero() { + (hooks.before_finalize)(bn); + AllPalletsWithSystem::on_finalize(bn); + (hooks.after_finalize)(bn); + } + + bn += One::one(); + + Self::set_block_number(bn); + (hooks.before_initialize)(bn); + AllPalletsWithSystem::on_initialize(bn); + (hooks.after_initialize)(bn); + } + } + + /// Simulate the execution of a block sequence up to a specified height. + #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] + pub fn run_to_block(n: BlockNumberFor) + where + AllPalletsWithSystem: frame_support::traits::OnInitialize> + + frame_support::traits::OnFinalize>, + { + Self::run_to_block_with::(n, Default::default()); + } + /// Set the block number to something in particular. Can be used as an alternative to /// `initialize` for tests that don't need to bother with the other environment entries. #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] @@ -2347,6 +2392,72 @@ impl Lookup for ChainContext { } } +/// Hooks for the [`Pallet::run_to_block_with`] function. +#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] +pub struct RunToBlockHooks<'a, T> +where + T: 'a + Config, +{ + before_initialize: Box)>, + after_initialize: Box)>, + before_finalize: Box)>, + after_finalize: Box)>, +} + +#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] +impl<'a, T> RunToBlockHooks<'a, T> +where + T: 'a + Config, +{ + /// Set the hook function logic before the initialization of the block. + pub fn before_initialize(mut self, f: F) -> Self + where + F: 'a + FnMut(BlockNumberFor), + { + self.before_initialize = Box::new(f); + self + } + /// Set the hook function logic after the initialization of the block. + pub fn after_initialize(mut self, f: F) -> Self + where + F: 'a + FnMut(BlockNumberFor), + { + self.after_initialize = Box::new(f); + self + } + /// Set the hook function logic before the finalization of the block. + pub fn before_finalize(mut self, f: F) -> Self + where + F: 'a + FnMut(BlockNumberFor), + { + self.before_finalize = Box::new(f); + self + } + /// Set the hook function logic after the finalization of the block. + pub fn after_finalize(mut self, f: F) -> Self + where + F: 'a + FnMut(BlockNumberFor), + { + self.after_finalize = Box::new(f); + self + } +} + +#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] +impl<'a, T> Default for RunToBlockHooks<'a, T> +where + T: Config, +{ + fn default() -> Self { + Self { + before_initialize: Box::new(|_| {}), + after_initialize: Box::new(|_| {}), + before_finalize: Box::new(|_| {}), + after_finalize: Box::new(|_| {}), + } + } +} + /// 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}; diff --git a/substrate/frame/transaction-storage/src/mock.rs b/substrate/frame/transaction-storage/src/mock.rs index 73174b73dbac..84a77043d577 100644 --- a/substrate/frame/transaction-storage/src/mock.rs +++ b/substrate/frame/transaction-storage/src/mock.rs @@ -21,10 +21,7 @@ use crate::{ self as pallet_transaction_storage, TransactionStorageProof, DEFAULT_MAX_BLOCK_TRANSACTIONS, DEFAULT_MAX_TRANSACTION_SIZE, }; -use frame_support::{ - derive_impl, - traits::{ConstU32, OnFinalize, OnInitialize}, -}; +use frame_support::{derive_impl, traits::ConstU32}; use sp_runtime::{traits::IdentityLookup, BuildStorage}; pub type Block = frame_system::mocking::MockBlock; @@ -80,15 +77,13 @@ pub fn new_test_ext() -> sp_io::TestExternalities { t.into() } -pub fn run_to_block(n: u64, f: impl Fn() -> Option) { - while System::block_number() < n { - if let Some(proof) = f() { - TransactionStorage::check_proof(RuntimeOrigin::none(), proof).unwrap(); - } - TransactionStorage::on_finalize(System::block_number()); - System::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - TransactionStorage::on_initialize(System::block_number()); - } +pub fn run_to_block(n: u64, f: impl Fn() -> Option + 'static) { + System::run_to_block_with::( + n, + frame_system::RunToBlockHooks::default().before_finalize(|_| { + if let Some(proof) = f() { + TransactionStorage::check_proof(RuntimeOrigin::none(), proof).unwrap(); + } + }), + ); } From ef064a357c97c2635f05295aac1698a91fa2f4fd Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Wed, 15 Jan 2025 13:04:37 +0200 Subject: [PATCH 09/17] req-resp/litep2p: Reject inbound requests from banned peers (#7158) This PR rejects inbound requests from banned peers (reputation is below the banned threshold). This mirrors the request-response implementation from the libp2p side. I won't expect this to get triggered too often, but we'll monitor this metric. While at it, have registered a new inbound failure metric to have visibility into this. Discovered during the investigation of: https://github.com/paritytech/polkadot-sdk/issues/7076#issuecomment-2589613046 cc @paritytech/networking --------- Signed-off-by: Alexandru Vasile --- prdoc/pr_7158.prdoc | 12 +++++++++ .../src/litep2p/shim/request_response/mod.rs | 25 ++++++++++++++----- 2 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 prdoc/pr_7158.prdoc diff --git a/prdoc/pr_7158.prdoc b/prdoc/pr_7158.prdoc new file mode 100644 index 000000000000..e113a7fdcd1c --- /dev/null +++ b/prdoc/pr_7158.prdoc @@ -0,0 +1,12 @@ +title: Reject litep2p inbound requests from banned peers + +doc: + - audience: Node Dev + description: | + This PR rejects inbound requests from banned peers (reputation is below the banned threshold). + This mirrors the request-response implementation from the libp2p side. + While at it, have registered a new inbound failure metric to have visibility into this. + +crates: +- name: sc-network + bump: patch diff --git a/substrate/client/network/src/litep2p/shim/request_response/mod.rs b/substrate/client/network/src/litep2p/shim/request_response/mod.rs index 146f2e4add97..690d5a31e6ad 100644 --- a/substrate/client/network/src/litep2p/shim/request_response/mod.rs +++ b/substrate/client/network/src/litep2p/shim/request_response/mod.rs @@ -273,6 +273,13 @@ impl RequestResponseProtocol { request_id: RequestId, request: Vec, ) { + log::trace!( + target: LOG_TARGET, + "{}: request received from {peer:?} ({fallback:?} {request_id:?}), request size {:?}", + self.protocol, + request.len(), + ); + let Some(inbound_queue) = &self.inbound_queue else { log::trace!( target: LOG_TARGET, @@ -284,12 +291,18 @@ impl RequestResponseProtocol { return; }; - log::trace!( - target: LOG_TARGET, - "{}: request received from {peer:?} ({fallback:?} {request_id:?}), request size {:?}", - self.protocol, - request.len(), - ); + if self.peerstore_handle.is_banned(&peer.into()) { + log::trace!( + target: LOG_TARGET, + "{}: rejecting inbound request from banned {peer:?} ({request_id:?})", + self.protocol, + ); + + self.handle.reject_request(request_id); + self.metrics.register_inbound_request_failure("banned-peer"); + return; + } + let (tx, rx) = oneshot::channel(); match inbound_queue.try_send(IncomingRequest { From 88f898e74423ab32806f44c77c925b0081efa2cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20M=C3=BCller?= Date: Wed, 15 Jan 2025 14:14:00 +0100 Subject: [PATCH 10/17] [pallet-revive] Fix `caller_is_root` return value (#7086) Closes https://github.com/paritytech/polkadot-sdk/issues/6767. The return type of the host function `caller_is_root` was denoted as `u32` in `pallet_revive_uapi`. This PR fixes the return type to `bool`. As a drive-by, the PR re-exports `pallet_revive::exec::Origin` to extend what can be tested externally. --------- Co-authored-by: Cyrill Leutwiler --- prdoc/pr_7086.prdoc | 11 +++++++++++ substrate/frame/revive/src/exec.rs | 2 +- substrate/frame/revive/src/gas.rs | 2 +- substrate/frame/revive/src/lib.rs | 4 ++-- substrate/frame/revive/uapi/src/host.rs | 2 +- substrate/frame/revive/uapi/src/host/riscv64.rs | 5 +++-- 6 files changed, 19 insertions(+), 7 deletions(-) create mode 100644 prdoc/pr_7086.prdoc diff --git a/prdoc/pr_7086.prdoc b/prdoc/pr_7086.prdoc new file mode 100644 index 000000000000..55fed9bca3e6 --- /dev/null +++ b/prdoc/pr_7086.prdoc @@ -0,0 +1,11 @@ +title: '[pallet-revive] Fix `caller_is_root` return value' +doc: +- audience: Runtime Dev + description: The return type of the host function `caller_is_root` was denoted as `u32` + in `pallet_revive_uapi`. This PR fixes the return type to `bool`. As a drive-by, the + PR re-exports `pallet_revive::exec::Origin` to extend what can be tested externally. +crates: +- name: pallet-revive + bump: minor +- name: pallet-revive-uapi + bump: major diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index a6a259149768..478e96dc994d 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -325,7 +325,7 @@ pub trait Ext: sealing::Sealed { /// Returns `Err(InvalidImmutableAccess)` if called from a constructor. fn get_immutable_data(&mut self) -> Result; - /// Set the the immutable data of the current contract. + /// Set the immutable data of the current contract. /// /// Returns `Err(InvalidImmutableAccess)` if not called from a constructor. /// diff --git a/substrate/frame/revive/src/gas.rs b/substrate/frame/revive/src/gas.rs index 9aad84e69201..5c30a0a51009 100644 --- a/substrate/frame/revive/src/gas.rs +++ b/substrate/frame/revive/src/gas.rs @@ -89,7 +89,7 @@ pub struct RefTimeLeft(u64); /// Resource that needs to be synced to the executor. /// -/// Wrapped to make sure that the resource will be synced back the the executor. +/// Wrapped to make sure that the resource will be synced back to the executor. #[must_use] pub struct Syncable(polkavm::Gas); diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 04bce264a188..bdb4b92edd9e 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -45,7 +45,7 @@ use crate::{ runtime::{gas_from_fee, GAS_PRICE}, GasEncoder, GenericTransaction, }, - exec::{AccountIdOf, ExecError, Executable, Ext, Key, Origin, Stack as ExecStack}, + exec::{AccountIdOf, ExecError, Executable, Ext, Key, Stack as ExecStack}, gas::GasMeter, storage::{meter::Meter as StorageMeter, ContractInfo, DeletionQueueManager}, wasm::{CodeInfo, RuntimeCosts, WasmBlob}, @@ -84,7 +84,7 @@ use sp_runtime::{ pub use crate::{ address::{create1, create2, AccountId32Mapper, AddressMapper}, debug::Tracing, - exec::MomentOf, + exec::{MomentOf, Origin}, pallet::*, }; pub use primitives::*; diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index eced4843b552..d90c0f45205d 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -488,7 +488,7 @@ pub trait HostFn: private::Sealed { /// A return value of `true` indicates that this contract is being called by a root origin, /// and `false` indicates that the caller is a signed origin. #[unstable_hostfn] - fn caller_is_root() -> u32; + fn caller_is_root() -> bool; /// Clear the value at the given key in the contract storage. /// diff --git a/substrate/frame/revive/uapi/src/host/riscv64.rs b/substrate/frame/revive/uapi/src/host/riscv64.rs index 6fdda86892d5..c83be942a970 100644 --- a/substrate/frame/revive/uapi/src/host/riscv64.rs +++ b/substrate/frame/revive/uapi/src/host/riscv64.rs @@ -501,8 +501,9 @@ impl HostFn for HostFnImpl { } #[unstable_hostfn] - fn caller_is_root() -> u32 { - unsafe { sys::caller_is_root() }.into_u32() + fn caller_is_root() -> bool { + let ret_val = unsafe { sys::caller_is_root() }; + ret_val.into_bool() } #[unstable_hostfn] From cb0d8544dc8828c7b5e7f6a5fc20ce8c6ef9bbb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20R=2E=20Bald=C3=A9?= Date: Wed, 15 Jan 2025 13:14:54 +0000 Subject: [PATCH 11/17] Remove 0 as a special case in gas/storage meters (#6890) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #6846 . --------- Signed-off-by: xermicus Co-authored-by: command-bot <> Co-authored-by: Alexander Theißen Co-authored-by: xermicus --- .../people-westend/src/tests/governance.rs | 2 +- prdoc/pr_6890.prdoc | 19 ++++ .../frame/revive/fixtures/contracts/call.rs | 8 +- .../contracts/call_diverging_out_len.rs | 12 +-- .../fixtures/contracts/call_return_code.rs | 8 +- .../contracts/call_runtime_and_call.rs | 8 +- .../contracts/call_with_flags_and_value.rs | 8 +- .../fixtures/contracts/call_with_limit.rs | 4 +- .../fixtures/contracts/caller_contract.rs | 48 +++++----- .../contracts/chain_extension_temp_storage.rs | 8 +- .../fixtures/contracts/create1_with_value.rs | 12 ++- .../contracts/create_storage_and_call.rs | 8 +- .../create_storage_and_instantiate.rs | 6 +- .../create_transient_storage_and_call.rs | 8 +- .../fixtures/contracts/delegate_call.rs | 10 +- .../contracts/delegate_call_deposit_limit.rs | 10 +- .../contracts/delegate_call_simple.rs | 10 +- .../contracts/destroy_and_transfer.rs | 18 ++-- .../frame/revive/fixtures/contracts/drain.rs | 2 +- .../contracts/instantiate_return_code.rs | 7 +- .../contracts/locking_delegate_dependency.rs | 10 +- .../frame/revive/fixtures/contracts/origin.rs | 6 +- .../fixtures/contracts/read_only_call.rs | 8 +- .../revive/fixtures/contracts/recurse.rs | 8 +- .../fixtures/contracts/return_data_api.rs | 24 +++-- .../fixtures/contracts/self_destruct.rs | 8 +- .../contracts/transfer_return_code.rs | 2 +- substrate/frame/revive/fixtures/src/lib.rs | 2 +- .../rpc/examples/js/pvm/FlipperCaller.polkavm | Bin 4532 -> 4584 bytes .../rpc/examples/js/pvm/PiggyBank.polkavm | Bin 5062 -> 5088 bytes .../frame/revive/src/benchmarking/mod.rs | 14 +-- substrate/frame/revive/src/exec.rs | 60 +++++------- substrate/frame/revive/src/gas.rs | 74 +++++++++++---- substrate/frame/revive/src/primitives.rs | 2 +- substrate/frame/revive/src/storage/meter.rs | 89 ++++++++++-------- substrate/frame/revive/src/tests.rs | 72 +++++++------- substrate/frame/revive/src/wasm/runtime.rs | 6 +- substrate/frame/revive/uapi/src/host.rs | 6 +- .../frame/revive/uapi/src/host/riscv64.rs | 12 +-- 39 files changed, 355 insertions(+), 264 deletions(-) create mode 100644 prdoc/pr_6890.prdoc diff --git a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/governance.rs b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/governance.rs index ea438f80552e..3b1779e40b60 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/governance.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/governance.rs @@ -396,7 +396,7 @@ fn relay_commands_add_remove_username_authority() { ); }); - // Now, remove the username authority with another priviledged XCM call. + // Now, remove the username authority with another privileged XCM call. Westend::execute_with(|| { type Runtime = ::Runtime; type RuntimeCall = ::RuntimeCall; diff --git a/prdoc/pr_6890.prdoc b/prdoc/pr_6890.prdoc new file mode 100644 index 000000000000..b22a339035d8 --- /dev/null +++ b/prdoc/pr_6890.prdoc @@ -0,0 +1,19 @@ +# 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: Alter semantic meaning of 0 in metering limits of EVM contract calls + +doc: + - audience: [ Runtime Dev, Runtime User ] + description: | + A limit of 0, for gas meters and storage meters, no longer has the meaning of unlimited metering. + +crates: + - name: pallet-revive + bump: patch + - name: pallet-revive-fixtures + bump: patch + - name: pallet-revive-uapi + bump: patch + - name: pallet-revive-eth-rpc + bump: patch diff --git a/substrate/frame/revive/fixtures/contracts/call.rs b/substrate/frame/revive/fixtures/contracts/call.rs index ee51548879d9..7c4c0882c6b8 100644 --- a/substrate/frame/revive/fixtures/contracts/call.rs +++ b/substrate/frame/revive/fixtures/contracts/call.rs @@ -38,10 +38,10 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::empty(), callee_addr, - 0u64, // How much ref_time to devote for the execution. 0 = all. - 0u64, // How much proof_size to devote for the execution. 0 = all. - None, // No deposit limit. - &[0u8; 32], // Value transferred to the contract. + u64::MAX, // How much ref_time to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. + &[0u8; 32], // Value transferred to the contract. callee_input, None, ) diff --git a/substrate/frame/revive/fixtures/contracts/call_diverging_out_len.rs b/substrate/frame/revive/fixtures/contracts/call_diverging_out_len.rs index 129adde2cec9..9a8fe5f5f6cc 100644 --- a/substrate/frame/revive/fixtures/contracts/call_diverging_out_len.rs +++ b/substrate/frame/revive/fixtures/contracts/call_diverging_out_len.rs @@ -42,9 +42,9 @@ fn assert_call(callee_address: &[u8; 20], expected_output: [u8; api::call( uapi::CallFlags::ALLOW_REENTRY, callee_address, - 0u64, - 0u64, - None, + u64::MAX, + u64::MAX, + &[u8::MAX; 32], &[0u8; 32], &[], Some(output_buf_capped), @@ -67,9 +67,9 @@ fn assert_instantiate(expected_output: [u8; BUF_SIZE]) { api::instantiate( &code_hash, - 0u64, - 0u64, - None, + u64::MAX, + u64::MAX, + &[u8::MAX; 32], &[0; 32], &[0; 32], None, diff --git a/substrate/frame/revive/fixtures/contracts/call_return_code.rs b/substrate/frame/revive/fixtures/contracts/call_return_code.rs index 2d13b9f70956..19b3ae3fdb26 100644 --- a/substrate/frame/revive/fixtures/contracts/call_return_code.rs +++ b/substrate/frame/revive/fixtures/contracts/call_return_code.rs @@ -42,10 +42,10 @@ pub extern "C" fn call() { let err_code = match api::call( uapi::CallFlags::empty(), callee_addr, - 0u64, // How much ref_time to devote for the execution. 0 = all. - 0u64, // How much proof_size to devote for the execution. 0 = all. - None, // No deposit limit. - value, // Value transferred to the contract. + u64::MAX, // How much ref_time to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. + value, // Value transferred to the contract. input, None, ) { diff --git a/substrate/frame/revive/fixtures/contracts/call_runtime_and_call.rs b/substrate/frame/revive/fixtures/contracts/call_runtime_and_call.rs index 8c8aee962849..78b275459f0e 100644 --- a/substrate/frame/revive/fixtures/contracts/call_runtime_and_call.rs +++ b/substrate/frame/revive/fixtures/contracts/call_runtime_and_call.rs @@ -42,10 +42,10 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::empty(), callee_addr, - 0u64, // How much ref_time to devote for the execution. 0 = all. - 0u64, // How much proof_size to devote for the execution. 0 = all. - None, // No deposit limit. - &[0u8; 32], // Value transferred to the contract. + u64::MAX, // How much ref_time to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. + &[0u8; 32], // Value transferred to the contract. callee_input, None, ) diff --git a/substrate/frame/revive/fixtures/contracts/call_with_flags_and_value.rs b/substrate/frame/revive/fixtures/contracts/call_with_flags_and_value.rs index 330393e706e9..155a4b41bd95 100644 --- a/substrate/frame/revive/fixtures/contracts/call_with_flags_and_value.rs +++ b/substrate/frame/revive/fixtures/contracts/call_with_flags_and_value.rs @@ -40,10 +40,10 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::from_bits(flags).unwrap(), callee_addr, - 0u64, // How much ref_time to devote for the execution. 0 = all. - 0u64, // How much proof_size to devote for the execution. 0 = all. - None, // No deposit limit. - &u256_bytes(value), // Value transferred to the contract. + u64::MAX, // How much ref_time to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. + &u256_bytes(value), // Value transferred to the contract. forwarded_input, None, ) diff --git a/substrate/frame/revive/fixtures/contracts/call_with_limit.rs b/substrate/frame/revive/fixtures/contracts/call_with_limit.rs index 6ab892a6b7ae..af5c301a353c 100644 --- a/substrate/frame/revive/fixtures/contracts/call_with_limit.rs +++ b/substrate/frame/revive/fixtures/contracts/call_with_limit.rs @@ -43,8 +43,8 @@ pub extern "C" fn call() { callee_addr, ref_time, proof_size, - None, // No deposit limit. - &[0u8; 32], // value transferred to the contract. + &[u8::MAX; 32], // No deposit limit. + &[0u8; 32], // value transferred to the contract. forwarded_input, None, ) diff --git a/substrate/frame/revive/fixtures/contracts/caller_contract.rs b/substrate/frame/revive/fixtures/contracts/caller_contract.rs index edad43fae251..d042dc2c22a2 100644 --- a/substrate/frame/revive/fixtures/contracts/caller_contract.rs +++ b/substrate/frame/revive/fixtures/contracts/caller_contract.rs @@ -42,9 +42,9 @@ pub extern "C" fn call() { // Fail to deploy the contract since it returns a non-zero exit status. let res = api::instantiate( code_hash, - 0u64, // How much ref_time weight to devote for the execution. 0 = all. - 0u64, // How much proof_size weight to devote for the execution. 0 = all. - None, // No deposit limit. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. &value, &reverted_input, None, @@ -56,9 +56,9 @@ pub extern "C" fn call() { // Fail to deploy the contract due to insufficient ref_time weight. let res = api::instantiate( code_hash, - 1u64, // too little ref_time weight - 0u64, // How much proof_size weight to devote for the execution. 0 = all. - None, // No deposit limit. + 1u64, // too little ref_time weight + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. &value, &input, None, @@ -70,9 +70,9 @@ pub extern "C" fn call() { // Fail to deploy the contract due to insufficient proof_size weight. let res = api::instantiate( code_hash, - 0u64, // How much ref_time weight to devote for the execution. 0 = all. - 1u64, // Too little proof_size weight - None, // No deposit limit. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + 1u64, // Too little proof_size weight + &[u8::MAX; 32], // No deposit limit. &value, &input, None, @@ -86,9 +86,9 @@ pub extern "C" fn call() { api::instantiate( code_hash, - 0u64, // How much ref_time weight to devote for the execution. 0 = all. - 0u64, // How much proof_size weight to devote for the execution. 0 = all. - None, // No deposit limit. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. &value, &input, Some(&mut callee), @@ -101,9 +101,9 @@ pub extern "C" fn call() { let res = api::call( uapi::CallFlags::empty(), &callee, - 0u64, // How much ref_time weight to devote for the execution. 0 = all. - 0u64, // How much proof_size weight to devote for the execution. 0 = all. - None, // No deposit limit. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. &value, &reverted_input, None, @@ -114,9 +114,9 @@ pub extern "C" fn call() { let res = api::call( uapi::CallFlags::empty(), &callee, - 1u64, // Too little ref_time weight. - 0u64, // How much proof_size weight to devote for the execution. 0 = all. - None, // No deposit limit. + 1u64, // Too little ref_time weight. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. &value, &input, None, @@ -127,9 +127,9 @@ pub extern "C" fn call() { let res = api::call( uapi::CallFlags::empty(), &callee, - 0u64, // How much ref_time weight to devote for the execution. 0 = all. - 1u64, // too little proof_size weight - None, // No deposit limit. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + 1u64, // too little proof_size weight + &[u8::MAX; 32], // No deposit limit. &value, &input, None, @@ -141,9 +141,9 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::empty(), &callee, - 0u64, // How much ref_time weight to devote for the execution. 0 = all. - 0u64, // How much proof_size weight to devote for the execution. 0 = all. - None, // No deposit limit. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. &value, &input, Some(&mut &mut output[..]), diff --git a/substrate/frame/revive/fixtures/contracts/chain_extension_temp_storage.rs b/substrate/frame/revive/fixtures/contracts/chain_extension_temp_storage.rs index 22d6c5b548d8..9b76b9d39ee9 100644 --- a/substrate/frame/revive/fixtures/contracts/chain_extension_temp_storage.rs +++ b/substrate/frame/revive/fixtures/contracts/chain_extension_temp_storage.rs @@ -54,10 +54,10 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::ALLOW_REENTRY, &addr, - 0u64, // How much ref_time to devote for the execution. 0 = all. - 0u64, // How much proof_size to devote for the execution. 0 = all. - None, // No deposit limit. - &[0u8; 32], // Value transferred to the contract. + u64::MAX, // How much ref_time to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. + &[0u8; 32], // Value transferred to the contract. input, None, ) diff --git a/substrate/frame/revive/fixtures/contracts/create1_with_value.rs b/substrate/frame/revive/fixtures/contracts/create1_with_value.rs index c6adab828860..3554f8f620a2 100644 --- a/substrate/frame/revive/fixtures/contracts/create1_with_value.rs +++ b/substrate/frame/revive/fixtures/contracts/create1_with_value.rs @@ -34,6 +34,16 @@ pub extern "C" fn call() { api::value_transferred(&mut value); // Deploy the contract with no salt (equivalent to create1). - let ret = api::instantiate(code_hash, 0u64, 0u64, None, &value, &[], None, None, None); + let ret = api::instantiate( + code_hash, + u64::MAX, + u64::MAX, + &[u8::MAX; 32], + &value, + &[], + None, + None, + None + ); assert!(ret.is_ok()); } diff --git a/substrate/frame/revive/fixtures/contracts/create_storage_and_call.rs b/substrate/frame/revive/fixtures/contracts/create_storage_and_call.rs index a12c36af856a..5bb11e27903e 100644 --- a/substrate/frame/revive/fixtures/contracts/create_storage_and_call.rs +++ b/substrate/frame/revive/fixtures/contracts/create_storage_and_call.rs @@ -43,10 +43,10 @@ pub extern "C" fn call() { let ret = api::call( uapi::CallFlags::empty(), callee, - 0u64, // How much ref_time weight to devote for the execution. 0 = all. - 0u64, // How much proof_size weight to devote for the execution. 0 = all. - Some(deposit_limit), - &[0u8; 32], // Value transferred to the contract. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all resources. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all resources. + deposit_limit, + &[0u8; 32], // Value transferred to the contract. input, None, ); diff --git a/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs b/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs index ecc0fc79e6fd..f627bc8ba6c4 100644 --- a/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs +++ b/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs @@ -41,9 +41,9 @@ pub extern "C" fn call() { let ret = api::instantiate( code_hash, - 0u64, // How much ref_time weight to devote for the execution. 0 = all. - 0u64, // How much proof_size weight to devote for the execution. 0 = all. - Some(deposit_limit), + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + deposit_limit, &value, input, Some(&mut address), diff --git a/substrate/frame/revive/fixtures/contracts/create_transient_storage_and_call.rs b/substrate/frame/revive/fixtures/contracts/create_transient_storage_and_call.rs index cf12fed27563..660db84028db 100644 --- a/substrate/frame/revive/fixtures/contracts/create_transient_storage_and_call.rs +++ b/substrate/frame/revive/fixtures/contracts/create_transient_storage_and_call.rs @@ -49,10 +49,10 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::empty(), callee, - 0u64, // How much ref_time weight to devote for the execution. 0 = all. - 0u64, // How much proof_size weight to devote for the execution. 0 = all. - None, - &[0u8; 32], // Value transferred to the contract. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = all. + &[u8::MAX; 32], // No deposit limit. + &[0u8; 32], // Value transferred to the contract. input, None, ) diff --git a/substrate/frame/revive/fixtures/contracts/delegate_call.rs b/substrate/frame/revive/fixtures/contracts/delegate_call.rs index 3cf74acf1321..0dedd5f704cb 100644 --- a/substrate/frame/revive/fixtures/contracts/delegate_call.rs +++ b/substrate/frame/revive/fixtures/contracts/delegate_call.rs @@ -46,7 +46,15 @@ pub extern "C" fn call() { assert!(value[0] == 2u8); let input = [0u8; 0]; - api::delegate_call(uapi::CallFlags::empty(), address, ref_time, proof_size, None, &input, None).unwrap(); + api::delegate_call( + uapi::CallFlags::empty(), + address, + ref_time, + proof_size, + &[u8::MAX; 32], + &input, + None + ).unwrap(); api::get_storage(StorageFlags::empty(), &key, value).unwrap(); assert!(value[0] == 1u8); diff --git a/substrate/frame/revive/fixtures/contracts/delegate_call_deposit_limit.rs b/substrate/frame/revive/fixtures/contracts/delegate_call_deposit_limit.rs index 0f157f5a18ac..0c503aa93c56 100644 --- a/substrate/frame/revive/fixtures/contracts/delegate_call_deposit_limit.rs +++ b/substrate/frame/revive/fixtures/contracts/delegate_call_deposit_limit.rs @@ -34,7 +34,15 @@ pub extern "C" fn call() { ); let input = [0u8; 0]; - let ret = api::delegate_call(uapi::CallFlags::empty(), address, 0, 0, Some(&u256_bytes(deposit_limit)), &input, None); + let ret = api::delegate_call( + uapi::CallFlags::empty(), + address, + u64::MAX, + u64::MAX, + &u256_bytes(deposit_limit), + &input, + None + ); if let Err(code) = ret { api::return_value(uapi::ReturnFlags::REVERT, &(code as u32).to_le_bytes()); diff --git a/substrate/frame/revive/fixtures/contracts/delegate_call_simple.rs b/substrate/frame/revive/fixtures/contracts/delegate_call_simple.rs index a8501dad4692..b7bdb792c76c 100644 --- a/substrate/frame/revive/fixtures/contracts/delegate_call_simple.rs +++ b/substrate/frame/revive/fixtures/contracts/delegate_call_simple.rs @@ -32,5 +32,13 @@ pub extern "C" fn call() { // Delegate call into passed address. let input = [0u8; 0]; - api::delegate_call(uapi::CallFlags::empty(), address, 0, 0, None, &input, None).unwrap(); + api::delegate_call( + uapi::CallFlags::empty(), + address, + u64::MAX, + u64::MAX, + &[u8::MAX; 32], + &input, + None + ).unwrap(); } diff --git a/substrate/frame/revive/fixtures/contracts/destroy_and_transfer.rs b/substrate/frame/revive/fixtures/contracts/destroy_and_transfer.rs index 8342f4acf952..c2c7da528ba7 100644 --- a/substrate/frame/revive/fixtures/contracts/destroy_and_transfer.rs +++ b/substrate/frame/revive/fixtures/contracts/destroy_and_transfer.rs @@ -35,9 +35,9 @@ pub extern "C" fn deploy() { api::instantiate( code_hash, - 0u64, // How much ref_time weight to devote for the execution. 0 = all. - 0u64, // How much proof_size weight to devote for the execution. 0 = all. - None, // No deposit limit. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. &VALUE, &input, Some(&mut address), @@ -62,9 +62,9 @@ pub extern "C" fn call() { let res = api::call( uapi::CallFlags::empty(), &callee_addr, - 0u64, // How much ref_time weight to devote for the execution. 0 = all. - 0u64, // How much proof_size weight to devote for the execution. 0 = all. - None, // No deposit limit. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. &VALUE, &[0u8; 1], None, @@ -75,9 +75,9 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::empty(), &callee_addr, - 0u64, // How much ref_time weight to devote for the execution. 0 = all. - 0u64, // How much proof_size weight to devote for the execution. 0 = all. - None, // No deposit limit. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. &VALUE, &[0u8; 0], None, diff --git a/substrate/frame/revive/fixtures/contracts/drain.rs b/substrate/frame/revive/fixtures/contracts/drain.rs index 6e3e708a6b3d..53fb213143c4 100644 --- a/substrate/frame/revive/fixtures/contracts/drain.rs +++ b/substrate/frame/revive/fixtures/contracts/drain.rs @@ -41,7 +41,7 @@ pub extern "C" fn call() { &[0u8; 20], 0, 0, - None, + &[u8::MAX; 32], &u256_bytes(balance), &[], None, diff --git a/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs b/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs index 9764859c619b..f7cbd75be5aa 100644 --- a/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs +++ b/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs @@ -33,10 +33,9 @@ pub extern "C" fn call() { let err_code = match api::instantiate( code_hash, - 0u64, // How much ref_time weight to devote for the execution. 0 = all. - 0u64, /* How much proof_size weight to devote for the execution. 0 = - * all. */ - None, // No deposit limit. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. &u256_bytes(10_000u64), // Value to transfer. input, None, diff --git a/substrate/frame/revive/fixtures/contracts/locking_delegate_dependency.rs b/substrate/frame/revive/fixtures/contracts/locking_delegate_dependency.rs index 3d7702c6537a..6be5d5c72f9a 100644 --- a/substrate/frame/revive/fixtures/contracts/locking_delegate_dependency.rs +++ b/substrate/frame/revive/fixtures/contracts/locking_delegate_dependency.rs @@ -52,7 +52,15 @@ fn load_input(delegate_call: bool) { } if delegate_call { - api::delegate_call(uapi::CallFlags::empty(), address, 0, 0, None, &[], None).unwrap(); + api::delegate_call( + uapi::CallFlags::empty(), + address, + u64::MAX, + u64::MAX, + &[u8::MAX; 32], + &[], + None + ).unwrap(); } } diff --git a/substrate/frame/revive/fixtures/contracts/origin.rs b/substrate/frame/revive/fixtures/contracts/origin.rs index 8e9afd8e8052..151ca3da77cd 100644 --- a/substrate/frame/revive/fixtures/contracts/origin.rs +++ b/substrate/frame/revive/fixtures/contracts/origin.rs @@ -49,9 +49,9 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::ALLOW_REENTRY, &addr, - 0u64, - 0u64, - None, + u64::MAX, + u64::MAX, + &[u8::MAX; 32], &[0; 32], &[], Some(&mut &mut buf[..]), diff --git a/substrate/frame/revive/fixtures/contracts/read_only_call.rs b/substrate/frame/revive/fixtures/contracts/read_only_call.rs index ea74d56867f5..0a87ecbb9b14 100644 --- a/substrate/frame/revive/fixtures/contracts/read_only_call.rs +++ b/substrate/frame/revive/fixtures/contracts/read_only_call.rs @@ -39,10 +39,10 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::READ_ONLY, callee_addr, - 0u64, // How much ref_time to devote for the execution. 0 = all. - 0u64, // How much proof_size to devote for the execution. 0 = all. - None, // No deposit limit. - &[0u8; 32], // Value transferred to the contract. + u64::MAX, // How much ref_time to devote for the execution. u64::MAX = all. + u64::MAX, // How much proof_size to devote for the execution. u64::MAX = all. + &[u8::MAX; 32], // No deposit limit. + &[0u8; 32], // Value transferred to the contract. callee_input, None, ) diff --git a/substrate/frame/revive/fixtures/contracts/recurse.rs b/substrate/frame/revive/fixtures/contracts/recurse.rs index 2e70d67d8c73..ead565c01459 100644 --- a/substrate/frame/revive/fixtures/contracts/recurse.rs +++ b/substrate/frame/revive/fixtures/contracts/recurse.rs @@ -43,10 +43,10 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::ALLOW_REENTRY, &addr, - 0u64, // How much ref_time to devote for the execution. 0 = all. - 0u64, // How much deposit_limit to devote for the execution. 0 = all. - None, // No deposit limit. - &[0u8; 32], // Value transferred to the contract. + u64::MAX, // How much ref_time to devote for the execution. u64::MAX = use all resources. + u64::MAX, // How much proof_size to devote for the execution. u64::MAX = use all resources. + &[u8::MAX; 32], // No deposit limit. + &[0u8; 32], // Value transferred to the contract. &(calls_left - 1).to_le_bytes(), None, ) diff --git a/substrate/frame/revive/fixtures/contracts/return_data_api.rs b/substrate/frame/revive/fixtures/contracts/return_data_api.rs index 1d483373cffd..1407e5323ea1 100644 --- a/substrate/frame/revive/fixtures/contracts/return_data_api.rs +++ b/substrate/frame/revive/fixtures/contracts/return_data_api.rs @@ -80,8 +80,16 @@ fn assert_return_data_size_of(expected: u64) { /// Assert the return data to be reset after a balance transfer. fn assert_balance_transfer_does_reset() { - api::call(uapi::CallFlags::empty(), &[0u8; 20], 0, 0, None, &u256_bytes(128), &[], None) - .unwrap(); + api::call( + uapi::CallFlags::empty(), + &[0u8; 20], + u64::MAX, + u64::MAX, + &[u8::MAX; 32], + &u256_bytes(128), + &[], + None + ).unwrap(); assert_return_data_size_of(0); } @@ -111,9 +119,9 @@ pub extern "C" fn call() { let mut instantiate = |exit_flag| { api::instantiate( code_hash, - 0u64, - 0u64, - None, + u64::MAX, + u64::MAX, + &[u8::MAX; 32], &[0; 32], &construct_input(exit_flag), Some(&mut address_buf), @@ -125,9 +133,9 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::empty(), address_buf, - 0u64, - 0u64, - None, + u64::MAX, + u64::MAX, + &[u8::MAX; 32], &[0; 32], &construct_input(exit_flag), None, diff --git a/substrate/frame/revive/fixtures/contracts/self_destruct.rs b/substrate/frame/revive/fixtures/contracts/self_destruct.rs index 2f37706634bd..053e545deb19 100644 --- a/substrate/frame/revive/fixtures/contracts/self_destruct.rs +++ b/substrate/frame/revive/fixtures/contracts/self_destruct.rs @@ -42,10 +42,10 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::ALLOW_REENTRY, &addr, - 0u64, // How much ref_time to devote for the execution. 0 = all. - 0u64, // How much proof_size to devote for the execution. 0 = all. - None, // No deposit limit. - &[0u8; 32], // Value to transfer. + u64::MAX, // How much ref_time to devote for the execution. u64 = all. + u64::MAX, // How much proof_size to devote for the execution. u64 = all. + &[u8::MAX; 32], // No deposit limit. + &[0u8; 32], // Value to transfer. &[0u8; 0], None, ) diff --git a/substrate/frame/revive/fixtures/contracts/transfer_return_code.rs b/substrate/frame/revive/fixtures/contracts/transfer_return_code.rs index 09d45d0a8411..053f97feda4a 100644 --- a/substrate/frame/revive/fixtures/contracts/transfer_return_code.rs +++ b/substrate/frame/revive/fixtures/contracts/transfer_return_code.rs @@ -33,7 +33,7 @@ pub extern "C" fn call() { &[0u8; 20], 0, 0, - None, + &[u8::MAX; 32], &u256_bytes(100u64), &[], None, diff --git a/substrate/frame/revive/fixtures/src/lib.rs b/substrate/frame/revive/fixtures/src/lib.rs index 38171edf1152..7685253d1ea2 100644 --- a/substrate/frame/revive/fixtures/src/lib.rs +++ b/substrate/frame/revive/fixtures/src/lib.rs @@ -22,7 +22,7 @@ extern crate alloc; // generated file that tells us where to find the fixtures include!(concat!(env!("OUT_DIR"), "/fixture_location.rs")); -/// Load a given wasm module and returns a wasm binary contents along with it's hash. +/// Load a given wasm module and returns a wasm binary contents along with its hash. #[cfg(feature = "std")] pub fn compile_module(fixture_name: &str) -> anyhow::Result<(Vec, sp_core::H256)> { let out_dir: std::path::PathBuf = FIXTURE_DIR.into(); diff --git a/substrate/frame/revive/rpc/examples/js/pvm/FlipperCaller.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/FlipperCaller.polkavm index 585fbb392a314c15a35e7e529106738bde3be02a..38a1098fe3a767aa0af74764bf7247e59f6110b7 100644 GIT binary patch delta 1279 zcmY+CT}&KR6vy}8S>|h5m|e@38e(^sVY0h1Ep^(6SQDhH>}!P9i#ocF8;1We z1&cCkvXmJzsF9js>R6z3I3zPIG8J`_;T2Ml#D|l+*Vkt?i@5 ztCMe2THB{bUsz&LrtQSs>Z>1^N4Waj-Ek8*CVpF;+&yM4{tY!X(1&!Z#7oxDXw^P}Z0q$vizVG>x-;7qDqe!i}q7 zm%$dm=CRCrExkz<#HCvrQwyZwO;;*`%b}E1!eK?Kr8X2va2M8HJk&-?3RATspl2E) zrAUqLag5p+(LI!@H&G+{JT~?Hlw}B}(LzO2Qj+qTMhXHY2((jB6^ch0<`(8zbZ~pP zNPcCW@V!P`hT2uT^QlWuG#O&wM~{&@uATnvZ;h$hAfMB{ z4&4TN1n}5}cNf-OJlKWDEWjgxhaxn($1&`}16vaYcm(kH zKX?r6*kyf;y%cCMIlY&3 z_}VIF{?eE$7bkoPw9Q%D(r+1D(U|cytG|bQM#by;*YY{&BPgpvYa@i7TXXgLQgiRI zWRdP7S^timbAM{gPi2>t3(L$suuq?b$d_c=-*M<@8E`gXkCg9&P3?l5XWb~k!f5^v zi|@c&ZhAk<5ua2gUSs$L5x0Misix!wacG&pGmQ^}-MvDcC|Z8jM7LF=By0F&T{p zf=b5ecg~3MMEon;o)X`g61CImOk7cd#pGT`P@Gv&fuvwmam1~sAOstNMcWpWd>~Q~ zqOG=_D%f_tdq)f;lCz2}Dw%YrGbJjb<_Ja5wArD}54w_fgc|Pd?%#Adj+1H?l|)<# zHbl0v-JFcGEjn|Lc(mvgQeyE`QA$Jp*)x*jr0?culug+wF~T3@BL(q+_*(^`byyU~ MKxn~9Ll-^&0#ACHtN;K2 delta 1201 zcmY+CT}&KR6vy}8ncX|HA26&fC8pA0xJ-5l*6L^^*2Z-+Ei-FLW=5dI57KJu43x+Y zMt8T3p(sh0Ua-Hyiz1{>|>!WCTkm+%(FY#BP8PQ0eS^uTv0efs9##K&|iLXj{5)@MBxuYW`*?g7YQ||-kirLfusPhu3EG$;H-f3XJ*%#53}-|O9b3(fXB@+|HP=ZP z!V^zXjk!t(mbOJY)Ap>ReS8|_9Fd`3C}WG{WT?si^S>tg^TM~GdLT^R+VoF+xNM?N z35Yxx?Z1`FtPf5zgk%RTr%57`*|qSGiIz(q880g6F0=!eY3DccQ>3}$of18K9qp~s zJ!sksRP(CWP1_0jOKqVg;09U(f~=Ex?{um}hclcNEiO^YWwYLw8HSL0fNpk2Z2w%_Rs7()9MQZ8KX z%X2j$Pu3IfuzM?|c>vjDj>1@#|6mWFV!8HZksuw(wTEH~f zc=#He!ci*EZbX1)XCS|%wk2mYoVpP?WxS;NrBpZ6PTl%`X^}H|;Yl#mC Wh2pkok^_;#NP;SAo~e&x*7rX_jeaBm diff --git a/substrate/frame/revive/rpc/examples/js/pvm/PiggyBank.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/PiggyBank.polkavm index 3f96fdfc21d8d8fc5bf68a34f0c9f7b055afa2a4..d0082db90e5e398832e4a32a9ec86dce83d16dd5 100644 GIT binary patch delta 1052 zcmY*WZA@EL7{2Gw_S}z_-8R4wr?xkz7m8*QJKg?h8rF;+;@s^O6m*LyGwlzeYnDOL zOo#y~Rt-HbnCv4NHkbfe6h#-~4>NTxL}MhzD1Ic?s0qd;$P$NhVR$Yg!{j{Y&3VrA zydURWtS!}gtcUhXQmNosZhy+v`FK~?$T831Cy#VJ-Rt3B8+hcoLsGZ&taL^y!@F=Z zK8XAAW%ru9lN8BOIiqmpBjpzLpn9*?p{;5Ud&9m*eUrY{^5ycEw^isn^#l4b{rIef z9>d|_YDQncnaX*bshY$2$`XFYUpibBC9$X+wDiE>YLcwLS7p1=EO=ct>SCGQl)H4| zV{pSV+2w6_XH#uGJr8boS#Ee+sfBs619iYVE*f~rlWdyt{XDjZ-Ep;GM(;|k)sx8M)5*ViU!OCTW-gP43bib1y=MnyO+ z--kNkd-;Q#iYx2GwQhF>{+(n8U$Mz=L;|l?4WBBvqkg!e%%V}4R)gp$e5T$R+IuBQ z-o4(FwxR?@6{;%E%%aAOz@)%9+^kVF4l%9Lv-oF{JaQdglkb2b&4;F7Oe>)|==K`u zbvWTYjbymT*N)zR5#Q&#OP7dP)|z2*>vpW&}~=i z>aYFx;df2LX!-r<5d2hr7R|sb+fJbhsMk-SEL_xIz{^YUfWO-F%~F!Ezu--^2FK1p z&VQ@^<5HAZdgFH@AWM&b3FrJ5`Wj__C?2PV@kGsVE;m8PCTOxi!&4?>#$Zlnte&x= zF-9XSM_D+`iZsqgZB7|?uw^ydEjhn`i=Oiq*ec)^278{y8-yzo=lkL`%=n9a6K06h zu~9nAb!In5{+Cj?*DFyL;?t&5pi|8o30ru8@i?cUO$#4L+~{asHop(7 z^xrD%frv=7StiLE2PmSEa6!mK!p#f8RkDD=ZZIqmv Iuuw?)2l-oAiU0rr delta 1068 zcmY*XZ)h839KPS%OYTpa^e)h5|8Qx1b8REIvCCKmDKXsWy-&LYj{V!UA`ZEN1Ilf0!pWHNO@r< z1<#>yY$dB)K-uPLlx>+n<>omwq*nV{bRnjT!Ia`ZzLFNI^oNFBaEdnao22g>tnSjE zAAa>3-Nx_S9CzjIo?LEkfPc(Iv>rsX6obhSqXj++G5RTw{ja*y-P7J*ugvpSck=a3 zkABJ1?R?Zz;@5_8fp)mQgY)#3YZNxpvitMGOGp=VF{QM)|49pnk*O8I)XsodwquaB zD?qeqsF~UYs1a=fYGu2Z(X)`ZQ(1eCNf+4TWhmQ4wtfqP_8E|~c8@HACHzs3wS?c? zMBBiMDhwh6hsuHKP_EjSC4mjJ%(uHZ)PDKjdQaa<2M3Bsmuk8vs0*?O?*1HHqc3{8V2+;iRN-ej z+!%scI^B2*w$h|`KYUF;@h9!a&{H+nb=1s zw%%%w+`j9)W1dd=_P{IjrtchlL&uv=!Xxwv2ZtH%yT&RL%EO`i^f)}JJ)r3@9G;YV*_2hn!zDA} z;AWDH7chx*5P!JUsf<|+(PHXL)Eac~ij*JW!W!!WR_j^b7) Pdfaq^l8Pstc%1tmWiL>e diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index e67c39ec0899..1796348ff321 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -1648,8 +1648,8 @@ mod benchmarks { memory.as_mut_slice(), CallFlags::CLONE_INPUT.bits(), // flags 0, // callee_ptr - 0, // ref_time_limit - 0, // proof_size_limit + u64::MAX, // ref_time_limit + u64::MAX, // proof_size_limit callee_len, // deposit_ptr callee_len + deposit_len, // value_ptr 0, // input_data_ptr @@ -1688,8 +1688,8 @@ mod benchmarks { memory.as_mut_slice(), 0, // flags 0, // address_ptr - 0, // ref_time_limit - 0, // proof_size_limit + u64::MAX, // ref_time_limit + u64::MAX, // proof_size_limit address_len, // deposit_ptr 0, // input_data_ptr 0, // input_data_len @@ -1715,7 +1715,7 @@ mod benchmarks { let value_bytes = Into::::into(value).encode(); let value_len = value_bytes.len() as u32; - let deposit: BalanceOf = 0u32.into(); + let deposit: BalanceOf = BalanceOf::::max_value(); let deposit_bytes = Into::::into(deposit).encode(); let deposit_len = deposit_bytes.len() as u32; @@ -1750,8 +1750,8 @@ mod benchmarks { result = runtime.bench_instantiate( memory.as_mut_slice(), 0, // code_hash_ptr - 0, // ref_time_limit - 0, // proof_size_limit + u64::MAX, // ref_time_limit + u64::MAX, // proof_size_limit offset(hash_len), // deposit_ptr offset(deposit_len), // value_ptr offset(value_len), // input_data_ptr diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 478e96dc994d..c069216d6cc7 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -53,7 +53,7 @@ use sp_core::{ }; use sp_io::{crypto::secp256k1_ecdsa_recover_compressed, hashing::blake2_256}; use sp_runtime::{ - traits::{BadOrigin, Convert, Dispatchable, Saturating, Zero}, + traits::{BadOrigin, Bounded, Convert, Dispatchable, Saturating, Zero}, DispatchError, SaturatedConversion, }; @@ -885,9 +885,9 @@ where args, value, gas_meter, - Weight::zero(), + Weight::max_value(), storage_meter, - BalanceOf::::zero(), + BalanceOf::::max_value(), false, true, )? @@ -1117,25 +1117,15 @@ where return Ok(output); } - // Storage limit is normally enforced as late as possible (when the last frame returns) - // so that the ordering of storage accesses does not matter. - // (However, if a special limit was set for a sub-call, it should be enforced right - // after the sub-call returned. See below for this case of enforcement). - if self.frames.is_empty() { - let frame = &mut self.first_frame; - frame.contract_info.load(&frame.account_id); - let contract = frame.contract_info.as_contract(); - frame.nested_storage.enforce_limit(contract)?; - } - let frame = self.top_frame_mut(); - // If a special limit was set for the sub-call, we enforce it here. - // The sub-call will be rolled back in case the limit is exhausted. + // The storage deposit is only charged at the end of every call stack. + // To make sure that no sub call uses more than it is allowed to, + // the limit is manually enforced here. let contract = frame.contract_info.as_contract(); frame .nested_storage - .enforce_subcall_limit(contract) + .enforce_limit(contract) .map_err(|e| ExecError { error: e, origin: ErrorOrigin::Callee })?; let account_id = T::AddressMapper::to_address(&frame.account_id); @@ -1463,7 +1453,7 @@ where FrameArgs::Call { dest: dest.clone(), cached_info, delegated_call: None }, value, gas_limit, - deposit_limit.try_into().map_err(|_| Error::::BalanceConversionFailed)?, + deposit_limit.saturated_into::>(), // Enable read-only access if requested; cannot disable it if already set. read_only || self.is_read_only(), )? { @@ -1519,7 +1509,7 @@ where }, value, gas_limit, - deposit_limit.try_into().map_err(|_| Error::::BalanceConversionFailed)?, + deposit_limit.saturated_into::>(), self.is_read_only(), )?; self.run(executable.expect(FRAME_ALWAYS_EXISTS_ON_INSTANTIATE), input_data) @@ -1549,7 +1539,7 @@ where }, value.try_into().map_err(|_| Error::::BalanceConversionFailed)?, gas_limit, - deposit_limit.try_into().map_err(|_| Error::::BalanceConversionFailed)?, + deposit_limit.saturated_into::>(), self.is_read_only(), )?; let address = T::AddressMapper::to_address(&self.top_frame().account_id); @@ -3098,8 +3088,8 @@ mod tests { let (address, output) = ctx .ext .instantiate( - Weight::zero(), - U256::zero(), + Weight::MAX, + U256::MAX, dummy_ch, ::Currency::minimum_balance().into(), vec![], @@ -3802,8 +3792,8 @@ mod tests { let succ_fail_code = MockLoader::insert(Constructor, move |ctx, _| { ctx.ext .instantiate( - Weight::zero(), - U256::zero(), + Weight::MAX, + U256::MAX, fail_code, ctx.ext.minimum_balance() * 100, vec![], @@ -3819,8 +3809,8 @@ mod tests { let addr = ctx .ext .instantiate( - Weight::zero(), - U256::zero(), + Weight::MAX, + U256::MAX, success_code, ctx.ext.minimum_balance() * 100, vec![], @@ -4597,7 +4587,7 @@ mod tests { // Successful instantiation should set the output let address = ctx .ext - .instantiate(Weight::zero(), U256::zero(), ok_ch, value, vec![], None) + .instantiate(Weight::MAX, U256::MAX, ok_ch, value, vec![], None) .unwrap(); assert_eq!( ctx.ext.last_frame_output(), @@ -4606,15 +4596,7 @@ mod tests { // Balance transfers should reset the output ctx.ext - .call( - Weight::zero(), - U256::zero(), - &address, - U256::from(1), - vec![], - true, - false, - ) + .call(Weight::MAX, U256::MAX, &address, U256::from(1), vec![], true, false) .unwrap(); assert_eq!(ctx.ext.last_frame_output(), &Default::default()); @@ -4827,7 +4809,7 @@ mod tests { // Constructors can not access the immutable data ctx.ext - .instantiate(Weight::zero(), U256::zero(), dummy_ch, value, vec![], None) + .instantiate(Weight::MAX, U256::MAX, dummy_ch, value, vec![], None) .unwrap(); exec_success() @@ -4944,7 +4926,7 @@ mod tests { move |ctx, _| { let value = ::Currency::minimum_balance().into(); ctx.ext - .instantiate(Weight::zero(), U256::zero(), dummy_ch, value, vec![], None) + .instantiate(Weight::MAX, U256::MAX, dummy_ch, value, vec![], None) .unwrap(); exec_success() @@ -4989,7 +4971,7 @@ mod tests { move |ctx, _| { let value = ::Currency::minimum_balance().into(); ctx.ext - .instantiate(Weight::zero(), U256::zero(), dummy_ch, value, vec![], None) + .instantiate(Weight::MAX, U256::MAX, dummy_ch, value, vec![], None) .unwrap(); exec_success() diff --git a/substrate/frame/revive/src/gas.rs b/substrate/frame/revive/src/gas.rs index 5c30a0a51009..e8338db12192 100644 --- a/substrate/frame/revive/src/gas.rs +++ b/substrate/frame/revive/src/gas.rs @@ -22,7 +22,7 @@ use frame_support::{ weights::Weight, DefaultNoBound, }; -use sp_runtime::{traits::Zero, DispatchError}; +use sp_runtime::DispatchError; #[cfg(test)] use std::{any::Any, fmt::Debug}; @@ -168,25 +168,19 @@ impl GasMeter { } } - /// Create a new gas meter by removing gas from the current meter. + /// Create a new gas meter by removing *all* the gas from the current meter. /// - /// # Note - /// - /// Passing `0` as amount is interpreted as "all remaining gas". + /// This should only be used by the primordial frame in a sequence of calls - every subsequent + /// frame should use [`nested`](Self::nested). + pub fn nested_take_all(&mut self) -> Self { + let gas_left = self.gas_left; + self.gas_left -= gas_left; + GasMeter::new(gas_left) + } + + /// Create a new gas meter for a nested call by removing gas from the current meter. pub fn nested(&mut self, amount: Weight) -> Self { - let amount = Weight::from_parts( - if amount.ref_time().is_zero() { - self.gas_left().ref_time() - } else { - amount.ref_time() - }, - if amount.proof_size().is_zero() { - self.gas_left().proof_size() - } else { - amount.proof_size() - }, - ) - .min(self.gas_left); + let amount = amount.min(self.gas_left); self.gas_left -= amount; GasMeter::new(amount) } @@ -392,6 +386,50 @@ mod tests { assert!(gas_meter.charge(SimpleToken(1)).is_err()); } + /// Previously, passing a `Weight` of 0 to `nested` would consume all of the meter's current + /// gas. + /// + /// Now, a `Weight` of 0 means no gas for the nested call. + #[test] + fn nested_zero_gas_requested() { + let test_weight = 50000.into(); + let mut gas_meter = GasMeter::::new(test_weight); + let gas_for_nested_call = gas_meter.nested(0.into()); + + assert_eq!(gas_meter.gas_left(), 50000.into()); + assert_eq!(gas_for_nested_call.gas_left(), 0.into()) + } + + #[test] + fn nested_some_gas_requested() { + let test_weight = 50000.into(); + let mut gas_meter = GasMeter::::new(test_weight); + let gas_for_nested_call = gas_meter.nested(10000.into()); + + assert_eq!(gas_meter.gas_left(), 40000.into()); + assert_eq!(gas_for_nested_call.gas_left(), 10000.into()) + } + + #[test] + fn nested_all_gas_requested() { + let test_weight = Weight::from_parts(50000, 50000); + let mut gas_meter = GasMeter::::new(test_weight); + let gas_for_nested_call = gas_meter.nested(test_weight); + + assert_eq!(gas_meter.gas_left(), Weight::from_parts(0, 0)); + assert_eq!(gas_for_nested_call.gas_left(), 50_000.into()) + } + + #[test] + fn nested_excess_gas_requested() { + let test_weight = Weight::from_parts(50000, 50000); + let mut gas_meter = GasMeter::::new(test_weight); + let gas_for_nested_call = gas_meter.nested(test_weight + 10000.into()); + + assert_eq!(gas_meter.gas_left(), Weight::from_parts(0, 0)); + assert_eq!(gas_for_nested_call.gas_left(), 50_000.into()) + } + // Make sure that the gas meter does not charge in case of overcharge #[test] fn overcharge_does_not_charge() { diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs index a7127f812b4b..452d2c8a3067 100644 --- a/substrate/frame/revive/src/primitives.rs +++ b/substrate/frame/revive/src/primitives.rs @@ -72,7 +72,7 @@ pub struct ContractResult { /// /// # Note /// - /// This can only different from [`Self::gas_consumed`] when weight pre charging + /// This can only be different from [`Self::gas_consumed`] when weight pre charging /// is used. Currently, only `seal_call_runtime` makes use of pre charging. /// Additionally, any `seal_call` or `seal_instantiate` makes use of pre-charging /// when a non-zero `gas_limit` argument is supplied. diff --git a/substrate/frame/revive/src/storage/meter.rs b/substrate/frame/revive/src/storage/meter.rs index 6eddf048be98..4febcb0c4066 100644 --- a/substrate/frame/revive/src/storage/meter.rs +++ b/substrate/frame/revive/src/storage/meter.rs @@ -101,12 +101,8 @@ pub struct Root; /// State parameter that constitutes a meter that is in its nested state. /// Its value indicates whether the nested meter has its own limit. -#[derive(DefaultNoBound, RuntimeDebugNoBound)] -pub enum Nested { - #[default] - DerivedLimit, - OwnLimit, -} +#[derive(Default, Debug)] +pub struct Nested; impl State for Root {} impl State for Nested {} @@ -125,10 +121,8 @@ pub struct RawMeter { /// We only have one charge per contract hence the size of this vector is /// limited by the maximum call depth. charges: Vec>, - /// We store the nested state to determine if it has a special limit for sub-call. - nested: S, /// Type parameter only used in impls. - _phantom: PhantomData, + _phantom: PhantomData<(E, S)>, } /// This type is used to describe a storage change when charging from the meter. @@ -281,21 +275,14 @@ where S: State + Default + Debug, { /// Create a new child that has its `limit`. - /// Passing `0` as the limit is interpreted as to take whatever is remaining from its parent. /// /// This is called whenever a new subcall is initiated in order to track the storage /// usage for this sub call separately. This is necessary because we want to exchange balance /// with the current contract we are interacting with. pub fn nested(&self, limit: BalanceOf) -> RawMeter { debug_assert!(matches!(self.contract_state(), ContractState::Alive)); - // If a special limit is specified higher than it is available, - // we want to enforce the lesser limit to the nested meter, to fail in the sub-call. - let limit = self.available().min(limit); - if limit.is_zero() { - RawMeter { limit: self.available(), ..Default::default() } - } else { - RawMeter { limit, nested: Nested::OwnLimit, ..Default::default() } - } + + RawMeter { limit: self.available().min(limit), ..Default::default() } } /// Absorb a child that was spawned to handle a sub call. @@ -477,13 +464,6 @@ impl> RawMeter { /// [`Self::charge`] does not enforce the storage limit since we want to do this check as late /// as possible to allow later refunds to offset earlier charges. - /// - /// # Note - /// - /// We normally need to call this **once** for every call stack and not for every cross contract - /// call. However, if a dedicated limit is specified for a sub-call, this needs to be called - /// once the sub-call has returned. For this, the [`Self::enforce_subcall_limit`] wrapper is - /// used. pub fn enforce_limit( &mut self, info: Option<&mut ContractInfo>, @@ -502,18 +482,6 @@ impl> RawMeter { } Ok(()) } - - /// This is a wrapper around [`Self::enforce_limit`] to use on the exit from a sub-call to - /// enforce its special limit if needed. - pub fn enforce_subcall_limit( - &mut self, - info: Option<&mut ContractInfo>, - ) -> Result<(), DispatchError> { - match self.nested { - Nested::OwnLimit => self.enforce_limit(info), - Nested::DerivedLimit => Ok(()), - } - } } impl Ext for ReservingExt { @@ -724,6 +692,49 @@ mod tests { ) } + /// Previously, passing a limit of 0 meant unlimited storage for a nested call. + /// + /// Now, a limit of 0 means the subcall will not be able to use any storage. + #[test] + fn nested_zero_limit_requested() { + clear_ext(); + + let meter = TestMeter::new(&Origin::from_account_id(ALICE), 1_000, 0).unwrap(); + assert_eq!(meter.available(), 1_000); + let nested0 = meter.nested(BalanceOf::::zero()); + assert_eq!(nested0.available(), 0); + } + + #[test] + fn nested_some_limit_requested() { + clear_ext(); + + let meter = TestMeter::new(&Origin::from_account_id(ALICE), 1_000, 0).unwrap(); + assert_eq!(meter.available(), 1_000); + let nested0 = meter.nested(500); + assert_eq!(nested0.available(), 500); + } + + #[test] + fn nested_all_limit_requested() { + clear_ext(); + + let meter = TestMeter::new(&Origin::from_account_id(ALICE), 1_000, 0).unwrap(); + assert_eq!(meter.available(), 1_000); + let nested0 = meter.nested(1_000); + assert_eq!(nested0.available(), 1_000); + } + + #[test] + fn nested_over_limit_requested() { + clear_ext(); + + let meter = TestMeter::new(&Origin::from_account_id(ALICE), 1_000, 0).unwrap(); + assert_eq!(meter.available(), 1_000); + let nested0 = meter.nested(2_000); + assert_eq!(nested0.available(), 1_000); + } + #[test] fn empty_charge_works() { clear_ext(); @@ -879,7 +890,7 @@ mod tests { let mut meter = TestMeter::new(&test_case.origin, 1_000, 0).unwrap(); assert_eq!(meter.available(), 1_000); - let mut nested0 = meter.nested(BalanceOf::::zero()); + let mut nested0 = meter.nested(BalanceOf::::max_value()); nested0.charge(&Diff { bytes_added: 5, bytes_removed: 1, @@ -895,7 +906,7 @@ mod tests { items_deposit: 20, immutable_data_len: 0, }); - let mut nested1 = nested0.nested(BalanceOf::::zero()); + let mut nested1 = nested0.nested(BalanceOf::::max_value()); nested1.charge(&Diff { items_removed: 5, ..Default::default() }); nested1.charge(&Diff { bytes_added: 20, ..Default::default() }); nested1.terminate(&nested1_info, CHARLIE); diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 664578bf7672..cf02d17a4d03 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -1149,7 +1149,7 @@ fn delegate_call() { assert_ok!(builder::call(caller_addr) .value(1337) - .data((callee_addr, 0u64, 0u64).encode()) + .data((callee_addr, u64::MAX, u64::MAX).encode()) .build()); }); } @@ -2261,12 +2261,12 @@ fn gas_estimation_for_subcalls() { // Run the test for all of those weight limits for the subcall let weights = [ - Weight::zero(), + Weight::MAX, GAS_LIMIT, GAS_LIMIT * 2, GAS_LIMIT / 5, - Weight::from_parts(0, GAS_LIMIT.proof_size()), - Weight::from_parts(GAS_LIMIT.ref_time(), 0), + Weight::from_parts(u64::MAX, GAS_LIMIT.proof_size()), + Weight::from_parts(GAS_LIMIT.ref_time(), u64::MAX), ]; // This call is passed to the sub call in order to create a large `required_weight` @@ -3453,13 +3453,13 @@ fn deposit_limit_in_nested_calls() { // We do not remove any storage but add a storage item of 12 bytes in the caller // contract. This would cost 12 + 2 = 14 Balance. - // The nested call doesn't get a special limit, which is set by passing 0 to it. + // The nested call doesn't get a special limit, which is set by passing `u64::MAX` to it. // This should fail as the specified parent's limit is less than the cost: 13 < // 14. assert_err_ignore_postinfo!( builder::call(addr_caller) .storage_deposit_limit(13) - .data((100u32, &addr_callee, U256::from(0u64)).encode()) + .data((100u32, &addr_callee, U256::MAX).encode()) .build(), >::StorageDepositLimitExhausted, ); @@ -3467,13 +3467,13 @@ fn deposit_limit_in_nested_calls() { // Now we specify the parent's limit high enough to cover the caller's storage // additions. However, we use a single byte more in the callee, hence the storage // deposit should be 15 Balance. - // The nested call doesn't get a special limit, which is set by passing 0 to it. + // The nested call doesn't get a special limit, which is set by passing `u64::MAX` to it. // This should fail as the specified parent's limit is less than the cost: 14 // < 15. assert_err_ignore_postinfo!( builder::call(addr_caller) .storage_deposit_limit(14) - .data((101u32, &addr_callee, U256::from(0u64)).encode()) + .data((101u32, &addr_callee, &U256::MAX).encode()) .build(), >::StorageDepositLimitExhausted, ); @@ -3495,7 +3495,7 @@ fn deposit_limit_in_nested_calls() { assert_err_ignore_postinfo!( builder::call(addr_caller) .storage_deposit_limit(0) - .data((87u32, &addr_callee, U256::from(0u64)).encode()) + .data((87u32, &addr_callee, &U256::MAX.to_little_endian()).encode()) .build(), >::StorageDepositLimitExhausted, ); @@ -3551,28 +3551,24 @@ fn deposit_limit_in_nested_instantiate() { // // Provided the limit is set to be 1 Balance less, // this call should fail on the return from the caller contract. - assert_err_ignore_postinfo!( - builder::call(addr_caller) - .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit(callee_info_len + 2 + ED + 1) - .data((0u32, &code_hash_callee, U256::from(0u64)).encode()) - .build(), - >::StorageDepositLimitExhausted, - ); + let ret = builder::bare_call(addr_caller) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(DepositLimit::Balance(callee_info_len + 2 + ED + 1)) + .data((0u32, &code_hash_callee, &U256::MAX.to_little_endian()).encode()) + .build_and_unwrap_result(); + assert_return_code!(ret, RuntimeReturnCode::OutOfResources); // The charges made on instantiation should be rolled back. assert_eq!(::Currency::free_balance(&BOB), 1_000_000); // Now we give enough limit for the instantiation itself, but require for 1 more storage // byte in the constructor. Hence +1 Balance to the limit is needed. This should fail on // the return from constructor. - assert_err_ignore_postinfo!( - builder::call(addr_caller) - .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit(callee_info_len + 2 + ED + 2) - .data((1u32, &code_hash_callee, U256::from(0u64)).encode()) - .build(), - >::StorageDepositLimitExhausted, - ); + let ret = builder::bare_call(addr_caller) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(DepositLimit::Balance(callee_info_len + 2 + ED + 2)) + .data((1u32, &code_hash_callee, U256::from(0u64)).encode()) + .build_and_unwrap_result(); + assert_return_code!(ret, RuntimeReturnCode::OutOfResources); // The charges made on the instantiation should be rolled back. assert_eq!(::Currency::free_balance(&BOB), 1_000_000); @@ -4856,20 +4852,18 @@ fn skip_transfer_works() { ); // fails when calling from a contract when gas is specified. - assert_err!( - Pallet::::bare_eth_transact( - GenericTransaction { - from: Some(BOB_ADDR), - to: Some(caller_addr), - input: Some((0u32, &addr).encode().into()), - gas: Some(1u32.into()), - ..Default::default() - }, - Weight::MAX, - |_| 0u32 - ), - EthTransactError::Message(format!("insufficient funds for gas * price + value: address {BOB_ADDR:?} have 0 (supplied gas 1)")) - ); + assert!(Pallet::::bare_eth_transact( + GenericTransaction { + from: Some(BOB_ADDR), + to: Some(caller_addr), + input: Some((0u32, &addr).encode().into()), + gas: Some(1u32.into()), + ..Default::default() + }, + Weight::MAX, + |_| 0u32 + ) + .is_err(),); // works when no gas is specified. assert_ok!(Pallet::::bare_eth_transact( diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index 52f79f2eb55a..8529c7d9e73b 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -1004,8 +1004,7 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { self.charge_gas(call_type.cost())?; let callee = memory.read_h160(callee_ptr)?; - let deposit_limit = - if deposit_ptr == SENTINEL { U256::zero() } else { memory.read_u256(deposit_ptr)? }; + let deposit_limit = memory.read_u256(deposit_ptr)?; let input_data = if flags.contains(CallFlags::CLONE_INPUT) { let input = self.input_data.as_ref().ok_or(Error::::InputForwarded)?; @@ -1091,8 +1090,7 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { salt_ptr: u32, ) -> Result { self.charge_gas(RuntimeCosts::Instantiate { input_data_len })?; - let deposit_limit: U256 = - if deposit_ptr == SENTINEL { U256::zero() } else { memory.read_u256(deposit_ptr)? }; + let deposit_limit: U256 = memory.read_u256(deposit_ptr)?; let value = memory.read_u256(value_ptr)?; let code_hash = memory.read_h256(code_hash_ptr)?; let input_data = memory.read(input_data_ptr, input_data_len)?; diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index d90c0f45205d..ba0a63b15c37 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -113,7 +113,7 @@ pub trait HostFn: private::Sealed { callee: &[u8; 20], ref_time_limit: u64, proof_size_limit: u64, - deposit: Option<&[u8; 32]>, + deposit: &[u8; 32], value: &[u8; 32], input_data: &[u8], output: Option<&mut &mut [u8]>, @@ -202,7 +202,7 @@ pub trait HostFn: private::Sealed { address: &[u8; 20], ref_time_limit: u64, proof_size_limit: u64, - deposit_limit: Option<&[u8; 32]>, + deposit_limit: &[u8; 32], input_data: &[u8], output: Option<&mut &mut [u8]>, ) -> Result; @@ -318,7 +318,7 @@ pub trait HostFn: private::Sealed { code_hash: &[u8; 32], ref_time_limit: u64, proof_size_limit: u64, - deposit: Option<&[u8; 32]>, + deposit: &[u8; 32], value: &[u8; 32], input: &[u8], address: Option<&mut [u8; 20]>, diff --git a/substrate/frame/revive/uapi/src/host/riscv64.rs b/substrate/frame/revive/uapi/src/host/riscv64.rs index c83be942a970..8c40bc9f48ea 100644 --- a/substrate/frame/revive/uapi/src/host/riscv64.rs +++ b/substrate/frame/revive/uapi/src/host/riscv64.rs @@ -168,7 +168,7 @@ impl HostFn for HostFnImpl { code_hash: &[u8; 32], ref_time_limit: u64, proof_size_limit: u64, - deposit_limit: Option<&[u8; 32]>, + deposit_limit: &[u8; 32], value: &[u8; 32], input: &[u8], mut address: Option<&mut [u8; 20]>, @@ -180,7 +180,7 @@ impl HostFn for HostFnImpl { None => crate::SENTINEL as _, }; let (output_ptr, mut output_len) = ptr_len_or_sentinel(&mut output); - let deposit_limit_ptr = ptr_or_sentinel(&deposit_limit); + let deposit_limit_ptr = deposit_limit.as_ptr(); let salt_ptr = ptr_or_sentinel(&salt); #[repr(C)] #[allow(dead_code)] @@ -225,13 +225,13 @@ impl HostFn for HostFnImpl { callee: &[u8; 20], ref_time_limit: u64, proof_size_limit: u64, - deposit_limit: Option<&[u8; 32]>, + deposit_limit: &[u8; 32], value: &[u8; 32], input: &[u8], mut output: Option<&mut &mut [u8]>, ) -> Result { let (output_ptr, mut output_len) = ptr_len_or_sentinel(&mut output); - let deposit_limit_ptr = ptr_or_sentinel(&deposit_limit); + let deposit_limit_ptr = deposit_limit.as_ptr(); #[repr(C)] #[allow(dead_code)] struct Args { @@ -273,12 +273,12 @@ impl HostFn for HostFnImpl { address: &[u8; 20], ref_time_limit: u64, proof_size_limit: u64, - deposit_limit: Option<&[u8; 32]>, + deposit_limit: &[u8; 32], input: &[u8], mut output: Option<&mut &mut [u8]>, ) -> Result { let (output_ptr, mut output_len) = ptr_len_or_sentinel(&mut output); - let deposit_limit_ptr = ptr_or_sentinel(&deposit_limit); + let deposit_limit_ptr = deposit_limit.as_ptr(); #[repr(C)] #[allow(dead_code)] struct Args { From d822e07d51dda41982291dc6582a8c4a34821e94 Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Wed, 15 Jan 2025 14:48:38 +0100 Subject: [PATCH 12/17] [pallet-revive] Bump asset-hub westend spec version (#7176) Bump asset-hub westend spec version --------- Co-authored-by: command-bot <> --- .../assets/asset-hub-westend/src/lib.rs | 2 +- prdoc/pr_7176.prdoc | 9 +++++++++ .../frame/revive/rpc/revive_chain.metadata | Bin 661594 -> 661585 bytes 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 prdoc/pr_7176.prdoc 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 cfc150ce5d6f..7844b0d885ec 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -125,7 +125,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("westmint"), impl_name: alloc::borrow::Cow::Borrowed("westmint"), authoring_version: 1, - spec_version: 1_017_003, + spec_version: 1_017_004, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 16, diff --git a/prdoc/pr_7176.prdoc b/prdoc/pr_7176.prdoc new file mode 100644 index 000000000000..b78f42014afe --- /dev/null +++ b/prdoc/pr_7176.prdoc @@ -0,0 +1,9 @@ +title: '[pallet-revive] Bump asset-hub westend spec version' +doc: +- audience: Runtime Dev + description: Bump asset-hub westend spec version +crates: +- name: asset-hub-westend-runtime + bump: minor +- name: pallet-revive-eth-rpc + bump: minor diff --git a/substrate/frame/revive/rpc/revive_chain.metadata b/substrate/frame/revive/rpc/revive_chain.metadata index 402e8c2d22b21471929e9c61acd2cc968af614cf..a03c95b4944f663225642b1678ef66aaccec3fb5 100644 GIT binary patch delta 92 zcmcb$LF3{EjSX``7+ojN4N+-68q$6=gmL@P5T;BYM&9ZA3z{(Be4WXq_(SEW&&bnAZ7t#Rv=~rVs;?r*sgM!)65kB;58&I delta 100 zcmcb(LF3j2jSX``7(*w|4N+mvNGxtX5Ym1igmL?U5T-02M#<^g3zWLjq^ vbi_5UxU?u$p(G=*1W2SRB(_UmW&&bnAZ7t#Rv=~rVs;?r*e-pU)65kB*&QY! From 77c78e1561bbe5ee0ecf414312bae82396ae6d11 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Wed, 15 Jan 2025 18:50:42 +0200 Subject: [PATCH 13/17] litep2p: Provide partial results to speedup GetRecord queries (#7099) This PR provides the partial results of the `GetRecord` kademlia query. This significantly improves the authority discovery records, from ~37 minutes to ~2/3 minutes. In contrast, libp2p discovers authority records in around ~10 minutes. The authority discovery was slow because litep2p provided the records only after the Kademlia query was completed. A normal Kademlia query completes in around 40 seconds to a few minutes. In this PR, partial records are provided as soon as they are discovered from the network. ### Testing Done Started a node in Kusama with `--validator` and litep2p backend. The node discovered 996/1000 authority records in ~ 1 minute 45 seconds. ![Screenshot 2025-01-09 at 12 26 08](https://github.com/user-attachments/assets/b618bf7c-2bba-43a0-a021-4047e854c075) ### Before & After In this image, on the left side is libp2p, in the middle litep2p without this PR, on the right litep2p with this PR ![Screenshot 2025-01-07 at 17 57 56](https://github.com/user-attachments/assets/a8d467f7-8dc7-461c-bcff-163b94d01ae8) Closes: https://github.com/paritytech/polkadot-sdk/issues/7077 cc @paritytech/networking --------- Signed-off-by: Alexandru Vasile --- Cargo.lock | 4 +- Cargo.toml | 2 +- prdoc/pr_7099.prdoc | 16 ++++ .../client/network/src/litep2p/discovery.rs | 33 +++++-- substrate/client/network/src/litep2p/mod.rs | 87 ++++++++----------- 5 files changed, 79 insertions(+), 63 deletions(-) create mode 100644 prdoc/pr_7099.prdoc diff --git a/Cargo.lock b/Cargo.lock index 7725db743c41..0d71a770d38b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10446,9 +10446,9 @@ dependencies = [ [[package]] name = "litep2p" -version = "0.8.4" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b0fef34af8847e816003bf7fdeac5ea50b9a7a88441ac927a6166b5e812ab79" +checksum = "6ca6ee50a125dc4fc4e9a3ae3640010796d1d07bc517a0ac715fdf0b24a0b6ac" dependencies = [ "async-trait", "bs58", diff --git a/Cargo.toml b/Cargo.toml index c30a9949e85e..eb99b80e16fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -850,7 +850,7 @@ linked-hash-map = { version = "0.5.4" } linked_hash_set = { version = "0.1.4" } linregress = { version = "0.5.1" } lite-json = { version = "0.2.0", default-features = false } -litep2p = { version = "0.8.4", features = ["websocket"] } +litep2p = { version = "0.9.0", features = ["websocket"] } log = { version = "0.4.22", default-features = false } macro_magic = { version = "0.5.1" } maplit = { version = "1.0.2" } diff --git a/prdoc/pr_7099.prdoc b/prdoc/pr_7099.prdoc new file mode 100644 index 000000000000..58d809f3c090 --- /dev/null +++ b/prdoc/pr_7099.prdoc @@ -0,0 +1,16 @@ +title: Provide partial results to speedup GetRecord queries + +doc: + - audience: Node Dev + description: | + This PR provides the partial results of the GetRecord kademlia query. + + This significantly improves the authority discovery records, from ~37 minutes to ~2/3 minutes. + In contrast, libp2p discovers authority records in around ~10 minutes. + + The authority discovery was slow because litep2p provided the records only after the Kademlia query was completed. A normal Kademlia query completes in around 40 seconds to a few minutes. + In this PR, partial records are provided as soon as they are discovered from the network. + +crates: + - name: sc-network + bump: patch diff --git a/substrate/client/network/src/litep2p/discovery.rs b/substrate/client/network/src/litep2p/discovery.rs index b55df374f60e..eb571804f30e 100644 --- a/substrate/client/network/src/litep2p/discovery.rs +++ b/substrate/client/network/src/litep2p/discovery.rs @@ -33,8 +33,8 @@ use litep2p::{ identify::{Config as IdentifyConfig, IdentifyEvent}, kademlia::{ Config as KademliaConfig, ConfigBuilder as KademliaConfigBuilder, ContentProvider, - IncomingRecordValidationMode, KademliaEvent, KademliaHandle, QueryId, Quorum, - Record, RecordKey, RecordsType, + IncomingRecordValidationMode, KademliaEvent, KademliaHandle, PeerRecord, QueryId, + Quorum, Record, RecordKey, }, ping::{Config as PingConfig, PingEvent}, }, @@ -129,13 +129,19 @@ pub enum DiscoveryEvent { address: Multiaddr, }, - /// Record was found from the DHT. + /// `GetRecord` query succeeded. GetRecordSuccess { /// Query ID. query_id: QueryId, + }, - /// Records. - records: RecordsType, + /// Record was found from the DHT. + GetRecordPartialResult { + /// Query ID. + query_id: QueryId, + + /// Record. + record: PeerRecord, }, /// Record was successfully stored on the DHT. @@ -573,13 +579,24 @@ impl Stream for Discovery { peers: peers.into_iter().collect(), })) }, - Poll::Ready(Some(KademliaEvent::GetRecordSuccess { query_id, records })) => { + Poll::Ready(Some(KademliaEvent::GetRecordSuccess { query_id })) => { log::trace!( target: LOG_TARGET, - "`GET_RECORD` succeeded for {query_id:?}: {records:?}", + "`GET_RECORD` succeeded for {query_id:?}", ); - return Poll::Ready(Some(DiscoveryEvent::GetRecordSuccess { query_id, records })); + return Poll::Ready(Some(DiscoveryEvent::GetRecordSuccess { query_id })); + }, + Poll::Ready(Some(KademliaEvent::GetRecordPartialResult { query_id, record })) => { + log::trace!( + target: LOG_TARGET, + "`GET_RECORD` intermediary succeeded for {query_id:?}: {record:?}", + ); + + return Poll::Ready(Some(DiscoveryEvent::GetRecordPartialResult { + query_id, + record, + })); }, Poll::Ready(Some(KademliaEvent::PutRecordSuccess { query_id, key: _ })) => return Poll::Ready(Some(DiscoveryEvent::PutRecordSuccess { query_id })), diff --git a/substrate/client/network/src/litep2p/mod.rs b/substrate/client/network/src/litep2p/mod.rs index 52b2970525df..fc4cce476283 100644 --- a/substrate/client/network/src/litep2p/mod.rs +++ b/substrate/client/network/src/litep2p/mod.rs @@ -58,7 +58,7 @@ use litep2p::{ protocol::{ libp2p::{ bitswap::Config as BitswapConfig, - kademlia::{QueryId, Record, RecordsType}, + kademlia::{QueryId, Record}, }, request_response::ConfigBuilder as RequestResponseConfigBuilder, }, @@ -836,23 +836,45 @@ impl NetworkBackend for Litep2pNetworkBac self.peerstore_handle.add_known_peer(peer.into()); } } - Some(DiscoveryEvent::GetRecordSuccess { query_id, records }) => { + Some(DiscoveryEvent::GetRecordPartialResult { query_id, record }) => { + if !self.pending_queries.contains_key(&query_id) { + log::error!( + target: LOG_TARGET, + "Missing/invalid pending query for `GET_VALUE` partial result: {query_id:?}" + ); + + continue + } + + let peer_id: sc_network_types::PeerId = record.peer.into(); + let record = PeerRecord { + record: P2PRecord { + key: record.record.key.to_vec().into(), + value: record.record.value, + publisher: record.record.publisher.map(|peer_id| { + let peer_id: sc_network_types::PeerId = peer_id.into(); + peer_id.into() + }), + expires: record.record.expires, + }, + peer: Some(peer_id.into()), + }; + + self.event_streams.send( + Event::Dht( + DhtEvent::ValueFound( + record.into() + ) + ) + ); + } + Some(DiscoveryEvent::GetRecordSuccess { query_id }) => { match self.pending_queries.remove(&query_id) { Some(KadQuery::GetValue(key, started)) => { log::trace!( target: LOG_TARGET, - "`GET_VALUE` for {:?} ({query_id:?}) succeeded", - key, + "`GET_VALUE` for {key:?} ({query_id:?}) succeeded", ); - for record in litep2p_to_libp2p_peer_record(records) { - self.event_streams.send( - Event::Dht( - DhtEvent::ValueFound( - record.into() - ) - ) - ); - } if let Some(ref metrics) = self.metrics { metrics @@ -1165,42 +1187,3 @@ impl NetworkBackend for Litep2pNetworkBac } } } - -// Glue code to convert from a litep2p records type to a libp2p2 PeerRecord. -fn litep2p_to_libp2p_peer_record(records: RecordsType) -> Vec { - match records { - litep2p::protocol::libp2p::kademlia::RecordsType::LocalStore(record) => { - vec![PeerRecord { - record: P2PRecord { - key: record.key.to_vec().into(), - value: record.value, - publisher: record.publisher.map(|peer_id| { - let peer_id: sc_network_types::PeerId = peer_id.into(); - peer_id.into() - }), - expires: record.expires, - }, - peer: None, - }] - }, - litep2p::protocol::libp2p::kademlia::RecordsType::Network(records) => records - .into_iter() - .map(|record| { - let peer_id: sc_network_types::PeerId = record.peer.into(); - - PeerRecord { - record: P2PRecord { - key: record.record.key.to_vec().into(), - value: record.record.value, - publisher: record.record.publisher.map(|peer_id| { - let peer_id: sc_network_types::PeerId = peer_id.into(); - peer_id.into() - }), - expires: record.record.expires, - }, - peer: Some(peer_id.into()), - } - }) - .collect::>(), - } -} From ece32e38a1a37aa354d51b16c07a42c66f23976e Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Wed, 15 Jan 2025 18:37:59 +0100 Subject: [PATCH 14/17] [pallet-revive] Remove debug buffer (#7163) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the `debug_buffer` feature --------- Co-authored-by: command-bot <> Co-authored-by: Cyrill Leutwiler Co-authored-by: Alexander Theißen --- .../assets/asset-hub-westend/src/lib.rs | 15 +- prdoc/pr_7163.prdoc | 13 + substrate/bin/node/runtime/src/lib.rs | 10 +- substrate/frame/revive/README.md | 23 -- .../contracts/debug_message_invalid_utf8.rs | 33 --- .../debug_message_logging_disabled.rs | 33 --- .../fixtures/contracts/debug_message_works.rs | 33 --- substrate/frame/revive/proc-macro/src/lib.rs | 7 +- .../revive/src/benchmarking/call_builder.rs | 15 +- .../frame/revive/src/benchmarking/mod.rs | 28 --- substrate/frame/revive/src/exec.rs | 228 +----------------- substrate/frame/revive/src/lib.rs | 67 +---- substrate/frame/revive/src/limits.rs | 5 - substrate/frame/revive/src/primitives.rs | 53 +--- .../frame/revive/src/test_utils/builder.rs | 17 +- substrate/frame/revive/src/tests.rs | 146 +---------- .../frame/revive/src/tests/test_debug.rs | 4 - substrate/frame/revive/src/wasm/mod.rs | 2 +- substrate/frame/revive/src/wasm/runtime.rs | 34 +-- substrate/frame/revive/src/weights.rs | 21 -- substrate/frame/revive/uapi/src/host.rs | 20 -- .../frame/revive/uapi/src/host/riscv64.rs | 7 - substrate/frame/revive/uapi/src/lib.rs | 7 +- 23 files changed, 54 insertions(+), 767 deletions(-) create mode 100644 prdoc/pr_7163.prdoc delete mode 100644 substrate/frame/revive/fixtures/contracts/debug_message_invalid_utf8.rs delete mode 100644 substrate/frame/revive/fixtures/contracts/debug_message_logging_disabled.rs delete mode 100644 substrate/frame/revive/fixtures/contracts/debug_message_works.rs 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 7844b0d885ec..5966dd01f18f 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -952,11 +952,6 @@ parameter_types! { pub CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(30); } -type EventRecord = frame_system::EventRecord< - ::RuntimeEvent, - ::Hash, ->; - impl pallet_revive::Config for Runtime { type Time = Timestamp; type Currency = Balances; @@ -2073,7 +2068,7 @@ impl_runtime_apis! { } } - impl pallet_revive::ReviveApi for Runtime + impl pallet_revive::ReviveApi for Runtime { fn balance(address: H160) -> U256 { Revive::evm_balance(&address) @@ -2108,7 +2103,7 @@ impl_runtime_apis! { gas_limit: Option, storage_deposit_limit: Option, input_data: Vec, - ) -> pallet_revive::ContractResult { + ) -> pallet_revive::ContractResult { let blockweights= ::BlockWeights::get(); Revive::bare_call( RuntimeOrigin::signed(origin), @@ -2117,8 +2112,6 @@ impl_runtime_apis! { gas_limit.unwrap_or(blockweights.max_block), pallet_revive::DepositLimit::Balance(storage_deposit_limit.unwrap_or(u128::MAX)), input_data, - pallet_revive::DebugInfo::UnsafeDebug, - pallet_revive::CollectEvents::UnsafeCollect, ) } @@ -2130,7 +2123,7 @@ impl_runtime_apis! { code: pallet_revive::Code, data: Vec, salt: Option<[u8; 32]>, - ) -> pallet_revive::ContractResult + ) -> pallet_revive::ContractResult { let blockweights= ::BlockWeights::get(); Revive::bare_instantiate( @@ -2141,8 +2134,6 @@ impl_runtime_apis! { code, data, salt, - pallet_revive::DebugInfo::UnsafeDebug, - pallet_revive::CollectEvents::UnsafeCollect, ) } diff --git a/prdoc/pr_7163.prdoc b/prdoc/pr_7163.prdoc new file mode 100644 index 000000000000..669c480b835b --- /dev/null +++ b/prdoc/pr_7163.prdoc @@ -0,0 +1,13 @@ +title: '[pallet-revive] Remove debug buffer' +doc: +- audience: Runtime Dev + description: Remove the `debug_buffer` feature +crates: +- name: asset-hub-westend-runtime + bump: minor +- name: pallet-revive + bump: major +- name: pallet-revive-proc-macro + bump: minor +- name: pallet-revive-uapi + bump: minor diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index e11a009c1c3f..97728f12f5f9 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -3212,7 +3212,7 @@ impl_runtime_apis! { } } - impl pallet_revive::ReviveApi for Runtime + impl pallet_revive::ReviveApi for Runtime { fn balance(address: H160) -> U256 { Revive::evm_balance(&address) @@ -3247,7 +3247,7 @@ impl_runtime_apis! { gas_limit: Option, storage_deposit_limit: Option, input_data: Vec, - ) -> pallet_revive::ContractResult { + ) -> pallet_revive::ContractResult { Revive::bare_call( RuntimeOrigin::signed(origin), dest, @@ -3255,8 +3255,6 @@ impl_runtime_apis! { gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block), pallet_revive::DepositLimit::Balance(storage_deposit_limit.unwrap_or(u128::MAX)), input_data, - pallet_revive::DebugInfo::UnsafeDebug, - pallet_revive::CollectEvents::UnsafeCollect, ) } @@ -3268,7 +3266,7 @@ impl_runtime_apis! { code: pallet_revive::Code, data: Vec, salt: Option<[u8; 32]>, - ) -> pallet_revive::ContractResult + ) -> pallet_revive::ContractResult { Revive::bare_instantiate( RuntimeOrigin::signed(origin), @@ -3278,8 +3276,6 @@ impl_runtime_apis! { code, data, salt, - pallet_revive::DebugInfo::UnsafeDebug, - pallet_revive::CollectEvents::UnsafeCollect, ) } diff --git a/substrate/frame/revive/README.md b/substrate/frame/revive/README.md index 575920dfaac7..7538f77d10bc 100644 --- a/substrate/frame/revive/README.md +++ b/substrate/frame/revive/README.md @@ -49,29 +49,6 @@ This module executes PolkaVM smart contracts. These can potentially be written i RISC-V. For now, the only officially supported languages are Solidity (via [`revive`](https://github.com/xermicus/revive)) and Rust (check the `fixtures` directory for Rust examples). -## Debugging - -Contracts can emit messages to the client when called as RPC through the -[`debug_message`](https://paritytech.github.io/substrate/master/pallet_revive/trait.SyscallDocs.html#tymethod.debug_message) -API. - -Those messages are gathered into an internal buffer and sent to the RPC client. It is up to the individual client if -and how those messages are presented to the user. - -This buffer is also printed as a debug message. In order to see these messages on the node console the log level for the -`runtime::revive` target needs to be raised to at least the `debug` level. However, those messages are easy to -overlook because of the noise generated by block production. A good starting point for observing them on the console is -using this command line in the root directory of the Substrate repository: - -```bash -cargo run --release -- --dev -lerror,runtime::revive=debug -``` - -This raises the log level of `runtime::revive` to `debug` and all other targets to `error` in order to prevent them -from spamming the console. - -`--dev`: Use a dev chain spec `--tmp`: Use temporary storage for chain data (the chain state is deleted on exit) - ## Host function tracing For contract authors, it can be a helpful debugging tool to see which host functions are called, with which arguments, diff --git a/substrate/frame/revive/fixtures/contracts/debug_message_invalid_utf8.rs b/substrate/frame/revive/fixtures/contracts/debug_message_invalid_utf8.rs deleted file mode 100644 index 6c850a9ec663..000000000000 --- a/substrate/frame/revive/fixtures/contracts/debug_message_invalid_utf8.rs +++ /dev/null @@ -1,33 +0,0 @@ -// 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. - -//! Emit a debug message with an invalid utf-8 code. -#![no_std] -#![no_main] - -extern crate common; -use uapi::{HostFn, HostFnImpl as api}; - -#[no_mangle] -#[polkavm_derive::polkavm_export] -pub extern "C" fn deploy() {} - -#[no_mangle] -#[polkavm_derive::polkavm_export] -pub extern "C" fn call() { - api::debug_message(b"\xFC").unwrap(); -} diff --git a/substrate/frame/revive/fixtures/contracts/debug_message_logging_disabled.rs b/substrate/frame/revive/fixtures/contracts/debug_message_logging_disabled.rs deleted file mode 100644 index 0ce2b6b5628d..000000000000 --- a/substrate/frame/revive/fixtures/contracts/debug_message_logging_disabled.rs +++ /dev/null @@ -1,33 +0,0 @@ -// 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. - -//! Emit a "Hello World!" debug message but assume that logging is disabled. -#![no_std] -#![no_main] - -extern crate common; -use uapi::{HostFn, HostFnImpl as api, ReturnErrorCode}; - -#[no_mangle] -#[polkavm_derive::polkavm_export] -pub extern "C" fn deploy() {} - -#[no_mangle] -#[polkavm_derive::polkavm_export] -pub extern "C" fn call() { - assert_eq!(api::debug_message(b"Hello World!"), Err(ReturnErrorCode::LoggingDisabled)); -} diff --git a/substrate/frame/revive/fixtures/contracts/debug_message_works.rs b/substrate/frame/revive/fixtures/contracts/debug_message_works.rs deleted file mode 100644 index 3a2509509d8f..000000000000 --- a/substrate/frame/revive/fixtures/contracts/debug_message_works.rs +++ /dev/null @@ -1,33 +0,0 @@ -// 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. - -//! Emit a "Hello World!" debug message. -#![no_std] -#![no_main] - -extern crate common; -use uapi::{HostFn, HostFnImpl as api}; - -#[no_mangle] -#[polkavm_derive::polkavm_export] -pub extern "C" fn deploy() {} - -#[no_mangle] -#[polkavm_derive::polkavm_export] -pub extern "C" fn call() { - api::debug_message(b"Hello World!").unwrap(); -} diff --git a/substrate/frame/revive/proc-macro/src/lib.rs b/substrate/frame/revive/proc-macro/src/lib.rs index b09bdef14632..6e38063d20a6 100644 --- a/substrate/frame/revive/proc-macro/src/lib.rs +++ b/substrate/frame/revive/proc-macro/src/lib.rs @@ -510,12 +510,7 @@ fn expand_functions(def: &EnvDef) -> TokenStream2 { quote! { // wrap body in closure to make sure the tracing is always executed let result = (|| #body)(); - if ::log::log_enabled!(target: "runtime::revive::strace", ::log::Level::Trace) { - use core::fmt::Write; - let mut msg = alloc::string::String::default(); - let _ = core::write!(&mut msg, #trace_fmt_str, #( #trace_fmt_args, )* result); - self.ext().append_debug_buffer(&msg); - } + ::log::trace!(target: "runtime::revive::strace", #trace_fmt_str, #( #trace_fmt_args, )* result); result } }; diff --git a/substrate/frame/revive/src/benchmarking/call_builder.rs b/substrate/frame/revive/src/benchmarking/call_builder.rs index 1177d47aadc3..077e18ff5f0b 100644 --- a/substrate/frame/revive/src/benchmarking/call_builder.rs +++ b/substrate/frame/revive/src/benchmarking/call_builder.rs @@ -22,7 +22,7 @@ use crate::{ storage::meter::Meter, transient_storage::MeterEntry, wasm::{PreparedCall, Runtime}, - BalanceOf, Config, DebugBuffer, Error, GasMeter, MomentOf, Origin, WasmBlob, Weight, + BalanceOf, Config, Error, GasMeter, MomentOf, Origin, WasmBlob, Weight, }; use alloc::{vec, vec::Vec}; use frame_benchmarking::benchmarking; @@ -38,7 +38,6 @@ pub struct CallSetup { gas_meter: GasMeter, storage_meter: Meter, value: BalanceOf, - debug_message: Option, data: Vec, transient_storage_size: u32, } @@ -91,7 +90,6 @@ where gas_meter: GasMeter::new(Weight::MAX), storage_meter, value: 0u32.into(), - debug_message: None, data: vec![], transient_storage_size: 0, } @@ -122,16 +120,6 @@ where self.transient_storage_size = size; } - /// Set the debug message. - pub fn enable_debug_message(&mut self) { - self.debug_message = Some(Default::default()); - } - - /// Get the debug message. - pub fn debug_message(&self) -> Option { - self.debug_message.clone() - } - /// Get the call's input data. pub fn data(&self) -> Vec { self.data.clone() @@ -150,7 +138,6 @@ where &mut self.gas_meter, &mut self.storage_meter, self.value, - self.debug_message.as_mut(), ); if self.transient_storage_size > 0 { Self::with_transient_storage(&mut ext.0, self.transient_storage_size).unwrap(); diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index 1796348ff321..e23554f21ba8 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -107,8 +107,6 @@ where Code::Upload(module.code), data, salt, - DebugInfo::Skip, - CollectEvents::Skip, ); let address = outcome.result?.addr; @@ -1047,32 +1045,6 @@ mod benchmarks { ); } - // Benchmark debug_message call - // Whereas this function is used in RPC mode only, it still should be secured - // against an excessive use. - // - // i: size of input in bytes up to maximum allowed contract memory or maximum allowed debug - // buffer size, whichever is less. - #[benchmark] - fn seal_debug_message( - i: Linear<0, { (limits::code::BLOB_BYTES).min(limits::DEBUG_BUFFER_BYTES) }>, - ) { - let mut setup = CallSetup::::default(); - setup.enable_debug_message(); - let (mut ext, _) = setup.ext(); - let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, vec![]); - // Fill memory with printable ASCII bytes. - let mut memory = (0..i).zip((32..127).cycle()).map(|i| i.1).collect::>(); - - let result; - #[block] - { - result = runtime.bench_debug_message(memory.as_mut_slice(), 0, i); - } - assert_ok!(result); - assert_eq!(setup.debug_message().unwrap().len() as u32, i); - } - #[benchmark(skip_meta, pov_mode = Measured)] fn get_storage_empty() -> Result<(), BenchmarkError> { let max_key_len = limits::STORAGE_KEY_BYTES; diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index c069216d6cc7..e20c5dd7786e 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -24,8 +24,8 @@ use crate::{ runtime_decl_for_revive_api::{Decode, Encode, RuntimeDebugNoBound, TypeInfo}, storage::{self, meter::Diff, WriteOutcome}, transient_storage::TransientStorage, - BalanceOf, CodeInfo, CodeInfoOf, Config, ContractInfo, ContractInfoOf, DebugBuffer, Error, - Event, ImmutableData, ImmutableDataOf, Pallet as Contracts, LOG_TARGET, + BalanceOf, CodeInfo, CodeInfoOf, Config, ContractInfo, ContractInfoOf, Error, Event, + ImmutableData, ImmutableDataOf, Pallet as Contracts, }; use alloc::vec::Vec; use core::{fmt::Debug, marker::PhantomData, mem}; @@ -378,19 +378,6 @@ pub trait Ext: sealing::Sealed { /// Charges `diff` from the meter. fn charge_storage(&mut self, diff: &Diff); - /// Append a string to the debug buffer. - /// - /// It is added as-is without any additional new line. - /// - /// This is a no-op if debug message recording is disabled which is always the case - /// when the code is executing on-chain. - /// - /// Returns `true` if debug message recording is enabled. Otherwise `false` is returned. - fn append_debug_buffer(&mut self, msg: &str) -> bool; - - /// Returns `true` if debug message recording is enabled. Otherwise `false` is returned. - fn debug_buffer_enabled(&self) -> bool; - /// Call some dispatchable and return the result. fn call_runtime(&self, call: ::RuntimeCall) -> DispatchResultWithPostInfo; @@ -555,11 +542,6 @@ pub struct Stack<'a, T: Config, E> { frames: BoundedVec, ConstU32<{ limits::CALL_STACK_DEPTH }>>, /// Statically guarantee that each call stack has at least one frame. first_frame: Frame, - /// A text buffer used to output human readable information. - /// - /// All the bytes added to this field should be valid UTF-8. The buffer has no defined - /// structure and is intended to be shown to users as-is for debugging purposes. - debug_message: Option<&'a mut DebugBuffer>, /// Transient storage used to store data, which is kept for the duration of a transaction. transient_storage: TransientStorage, /// Whether or not actual transfer of funds should be performed. @@ -765,11 +747,6 @@ where { /// Create and run a new call stack by calling into `dest`. /// - /// # Note - /// - /// `debug_message` should only ever be set to `Some` when executing as an RPC because - /// it adds allocations and could be abused to drive the runtime into an OOM panic. - /// /// # Return Value /// /// Result<(ExecReturnValue, CodeSize), (ExecError, CodeSize)> @@ -781,7 +758,6 @@ where value: U256, input_data: Vec, skip_transfer: bool, - debug_message: Option<&'a mut DebugBuffer>, ) -> ExecResult { let dest = T::AddressMapper::to_account_id(&dest); if let Some((mut stack, executable)) = Self::new( @@ -791,7 +767,6 @@ where storage_meter, value, skip_transfer, - debug_message, )? { stack.run(executable, input_data).map(|_| stack.first_frame.last_frame_output) } else { @@ -801,11 +776,6 @@ where /// Create and run a new call stack by instantiating a new contract. /// - /// # Note - /// - /// `debug_message` should only ever be set to `Some` when executing as an RPC because - /// it adds allocations and could be abused to drive the runtime into an OOM panic. - /// /// # Return Value /// /// Result<(NewContractAccountId, ExecReturnValue), ExecError)> @@ -818,7 +788,6 @@ where input_data: Vec, salt: Option<&[u8; 32]>, skip_transfer: bool, - debug_message: Option<&'a mut DebugBuffer>, ) -> Result<(H160, ExecReturnValue), ExecError> { let (mut stack, executable) = Self::new( FrameArgs::Instantiate { @@ -832,7 +801,6 @@ where storage_meter, value, skip_transfer, - debug_message, )? .expect(FRAME_ALWAYS_EXISTS_ON_INSTANTIATE); let address = T::AddressMapper::to_address(&stack.top_frame().account_id); @@ -848,7 +816,6 @@ where gas_meter: &'a mut GasMeter, storage_meter: &'a mut storage::meter::Meter, value: BalanceOf, - debug_message: Option<&'a mut DebugBuffer>, ) -> (Self, E) { Self::new( FrameArgs::Call { @@ -861,7 +828,6 @@ where storage_meter, value.into(), false, - debug_message, ) .unwrap() .unwrap() @@ -878,7 +844,6 @@ where storage_meter: &'a mut storage::meter::Meter, value: U256, skip_transfer: bool, - debug_message: Option<&'a mut DebugBuffer>, ) -> Result, ExecError> { origin.ensure_mapped()?; let Some((first_frame, executable)) = Self::new_frame( @@ -903,7 +868,6 @@ where block_number: >::block_number(), first_frame, frames: Default::default(), - debug_message, transient_storage: TransientStorage::new(limits::TRANSIENT_STORAGE_BYTES), skip_transfer, _phantom: Default::default(), @@ -1250,13 +1214,6 @@ where } } } else { - if let Some((msg, false)) = self.debug_message.as_ref().map(|m| (m, m.is_empty())) { - log::debug!( - target: LOG_TARGET, - "Execution finished with debug buffer: {}", - core::str::from_utf8(msg).unwrap_or(""), - ); - } self.gas_meter.absorb_nested(mem::take(&mut self.first_frame.nested_gas)); if !persist { return; @@ -1759,28 +1716,6 @@ where self.top_frame_mut().nested_storage.charge(diff) } - fn debug_buffer_enabled(&self) -> bool { - self.debug_message.is_some() - } - - fn append_debug_buffer(&mut self, msg: &str) -> bool { - if let Some(buffer) = &mut self.debug_message { - buffer - .try_extend(&mut msg.bytes()) - .map_err(|_| { - log::debug!( - target: LOG_TARGET, - "Debug buffer (of {} bytes) exhausted!", - limits::DEBUG_BUFFER_BYTES, - ) - }) - .ok(); - true - } else { - false - } - } - fn call_runtime(&self, call: ::RuntimeCall) -> DispatchResultWithPostInfo { let mut origin: T::RuntimeOrigin = RawOrigin::Signed(self.account_id().clone()).into(); origin.add_filter(T::CallFilter::contains); @@ -2103,7 +2038,6 @@ mod tests { value.into(), vec![], false, - None, ), Ok(_) ); @@ -2196,7 +2130,6 @@ mod tests { value.into(), vec![], false, - None, ) .unwrap(); @@ -2237,7 +2170,6 @@ mod tests { value.into(), vec![], false, - None, )); assert_eq!(get_balance(&ALICE), 100 - value); @@ -2274,7 +2206,6 @@ mod tests { U256::zero(), vec![], false, - None, ), ExecError { error: Error::::CodeNotFound.into(), @@ -2292,7 +2223,6 @@ mod tests { U256::zero(), vec![], false, - None, )); }); } @@ -2321,7 +2251,6 @@ mod tests { 55u64.into(), vec![], false, - None, ) .unwrap(); @@ -2371,7 +2300,6 @@ mod tests { U256::zero(), vec![], false, - None, ); let output = result.unwrap(); @@ -2401,7 +2329,6 @@ mod tests { U256::zero(), vec![], false, - None, ); let output = result.unwrap(); @@ -2431,7 +2358,6 @@ mod tests { U256::zero(), vec![1, 2, 3, 4], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -2468,7 +2394,6 @@ mod tests { vec![1, 2, 3, 4], Some(&[0; 32]), false, - None, ); assert_matches!(result, Ok(_)); }); @@ -2523,7 +2448,6 @@ mod tests { value.into(), vec![], false, - None, ); assert_matches!(result, Ok(_)); @@ -2588,7 +2512,6 @@ mod tests { U256::zero(), vec![], false, - None, ); assert_matches!(result, Ok(_)); @@ -2654,7 +2577,6 @@ mod tests { U256::zero(), vec![], false, - None, ); assert_matches!(result, Ok(_)); @@ -2687,7 +2609,6 @@ mod tests { U256::zero(), vec![], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -2725,7 +2646,6 @@ mod tests { U256::zero(), vec![0], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -2752,7 +2672,6 @@ mod tests { U256::zero(), vec![0], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -2797,7 +2716,6 @@ mod tests { U256::zero(), vec![0], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -2824,7 +2742,6 @@ mod tests { U256::zero(), vec![0], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -2851,7 +2768,6 @@ mod tests { 1u64.into(), vec![0], false, - None, ); assert_matches!(result, Err(_)); }); @@ -2896,7 +2812,6 @@ mod tests { U256::zero(), vec![0], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -2942,7 +2857,6 @@ mod tests { U256::zero(), vec![], false, - None, ); assert_matches!(result, Ok(_)); @@ -2969,7 +2883,6 @@ mod tests { vec![], Some(&[0; 32]), false, - None, ), Err(_) ); @@ -3005,7 +2918,6 @@ mod tests { vec![], Some(&[0 ;32]), false, - None, ), Ok((address, ref output)) if output.data == vec![80, 65, 83, 83] => address ); @@ -3060,7 +2972,6 @@ mod tests { vec![], Some(&[0; 32]), false, - None, ), Ok((address, ref output)) if output.data == vec![70, 65, 73, 76] => address ); @@ -3125,7 +3036,6 @@ mod tests { (min_balance * 10).into(), vec![], false, - None, ), Ok(_) ); @@ -3206,7 +3116,6 @@ mod tests { U256::zero(), vec![], false, - None, ), Ok(_) ); @@ -3250,7 +3159,6 @@ mod tests { vec![], Some(&[0; 32]), false, - None, ), Err(Error::::TerminatedInConstructor.into()) ); @@ -3315,7 +3223,6 @@ mod tests { U256::zero(), vec![0], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -3378,7 +3285,6 @@ mod tests { vec![], Some(&[0; 32]), false, - None, ); assert_matches!(result, Ok(_)); }); @@ -3425,113 +3331,11 @@ mod tests { U256::zero(), vec![], false, - None, ) .unwrap(); }); } - #[test] - fn printing_works() { - let code_hash = MockLoader::insert(Call, |ctx, _| { - ctx.ext.append_debug_buffer("This is a test"); - ctx.ext.append_debug_buffer("More text"); - exec_success() - }); - - let mut debug_buffer = DebugBuffer::try_from(Vec::new()).unwrap(); - - ExtBuilder::default().build().execute_with(|| { - let min_balance = ::Currency::minimum_balance(); - - let mut gas_meter = GasMeter::::new(GAS_LIMIT); - set_balance(&ALICE, min_balance * 10); - place_contract(&BOB, code_hash); - let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(&origin, 0, 0).unwrap(); - MockStack::run_call( - origin, - BOB_ADDR, - &mut gas_meter, - &mut storage_meter, - U256::zero(), - vec![], - false, - Some(&mut debug_buffer), - ) - .unwrap(); - }); - - assert_eq!(&String::from_utf8(debug_buffer.to_vec()).unwrap(), "This is a testMore text"); - } - - #[test] - fn printing_works_on_fail() { - let code_hash = MockLoader::insert(Call, |ctx, _| { - ctx.ext.append_debug_buffer("This is a test"); - ctx.ext.append_debug_buffer("More text"); - exec_trapped() - }); - - let mut debug_buffer = DebugBuffer::try_from(Vec::new()).unwrap(); - - ExtBuilder::default().build().execute_with(|| { - let min_balance = ::Currency::minimum_balance(); - - let mut gas_meter = GasMeter::::new(GAS_LIMIT); - set_balance(&ALICE, min_balance * 10); - place_contract(&BOB, code_hash); - let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(&origin, 0, 0).unwrap(); - let result = MockStack::run_call( - origin, - BOB_ADDR, - &mut gas_meter, - &mut storage_meter, - U256::zero(), - vec![], - false, - Some(&mut debug_buffer), - ); - assert!(result.is_err()); - }); - - assert_eq!(&String::from_utf8(debug_buffer.to_vec()).unwrap(), "This is a testMore text"); - } - - #[test] - fn debug_buffer_is_limited() { - let code_hash = MockLoader::insert(Call, move |ctx, _| { - ctx.ext.append_debug_buffer("overflowing bytes"); - exec_success() - }); - - // Pre-fill the buffer almost up to its limit, leaving not enough space to the message - let debug_buf_before = DebugBuffer::try_from(vec![0u8; DebugBuffer::bound() - 5]).unwrap(); - let mut debug_buf_after = debug_buf_before.clone(); - - ExtBuilder::default().build().execute_with(|| { - let min_balance = ::Currency::minimum_balance(); - let mut gas_meter = GasMeter::::new(GAS_LIMIT); - set_balance(&ALICE, min_balance * 10); - place_contract(&BOB, code_hash); - let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(&origin, 0, 0).unwrap(); - MockStack::run_call( - origin, - BOB_ADDR, - &mut gas_meter, - &mut storage_meter, - U256::zero(), - vec![], - false, - Some(&mut debug_buf_after), - ) - .unwrap(); - assert_eq!(debug_buf_before, debug_buf_after); - }); - } - #[test] fn call_reentry_direct_recursion() { // call the contract passed as input with disabled reentry @@ -3559,7 +3363,6 @@ mod tests { U256::zero(), CHARLIE_ADDR.as_bytes().to_vec(), false, - None, )); // Calling into oneself fails @@ -3572,7 +3375,6 @@ mod tests { U256::zero(), BOB_ADDR.as_bytes().to_vec(), false, - None, ) .map_err(|e| e.error), >::ReentranceDenied, @@ -3623,7 +3425,6 @@ mod tests { U256::zero(), vec![0], false, - None, ) .map_err(|e| e.error), >::ReentranceDenied, @@ -3658,7 +3459,6 @@ mod tests { U256::zero(), vec![], false, - None, ) .unwrap(); @@ -3743,7 +3543,6 @@ mod tests { U256::zero(), vec![], false, - None, ) .unwrap(); @@ -3870,7 +3669,6 @@ mod tests { vec![], Some(&[0; 32]), false, - None, ) .ok(); assert_eq!(System::account_nonce(&ALICE), 0); @@ -3884,7 +3682,6 @@ mod tests { vec![], Some(&[0; 32]), false, - None, )); assert_eq!(System::account_nonce(&ALICE), 1); @@ -3897,7 +3694,6 @@ mod tests { vec![], Some(&[0; 32]), false, - None, )); assert_eq!(System::account_nonce(&ALICE), 2); @@ -3910,7 +3706,6 @@ mod tests { vec![], Some(&[0; 32]), false, - None, )); assert_eq!(System::account_nonce(&ALICE), 3); }); @@ -3979,7 +3774,6 @@ mod tests { U256::zero(), vec![], false, - None, )); }); } @@ -4091,7 +3885,6 @@ mod tests { U256::zero(), vec![], false, - None, )); }); } @@ -4131,7 +3924,6 @@ mod tests { U256::zero(), vec![], false, - None, )); }); } @@ -4171,7 +3963,6 @@ mod tests { U256::zero(), vec![], false, - None, )); }); } @@ -4225,7 +4016,6 @@ mod tests { U256::zero(), vec![], false, - None, )); }); } @@ -4282,7 +4072,6 @@ mod tests { U256::zero(), vec![], false, - None, )); }); } @@ -4358,7 +4147,6 @@ mod tests { U256::zero(), vec![], false, - None, )); }); } @@ -4429,7 +4217,6 @@ mod tests { U256::zero(), vec![0], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -4468,7 +4255,6 @@ mod tests { U256::zero(), vec![], false, - None, )); }); } @@ -4531,7 +4317,6 @@ mod tests { U256::zero(), vec![0], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -4565,7 +4350,6 @@ mod tests { U256::zero(), vec![], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -4641,7 +4425,6 @@ mod tests { U256::zero(), vec![], false, - None, ) .unwrap() }); @@ -4710,7 +4493,6 @@ mod tests { U256::zero(), vec![0], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -4782,7 +4564,6 @@ mod tests { U256::zero(), vec![], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -4834,7 +4615,6 @@ mod tests { U256::zero(), vec![], false, - None, ) .unwrap() }); @@ -4904,7 +4684,6 @@ mod tests { U256::zero(), vec![], false, - None, ) .unwrap() }); @@ -4951,7 +4730,6 @@ mod tests { U256::zero(), vec![], false, - None, ) .unwrap() }); @@ -4996,7 +4774,6 @@ mod tests { U256::zero(), vec![], false, - None, ) .unwrap() }); @@ -5052,7 +4829,6 @@ mod tests { U256::zero(), vec![0], false, - None, ), Ok(_) ); diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index bdb4b92edd9e..403598ae136e 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -71,7 +71,7 @@ use frame_support::{ use frame_system::{ ensure_signed, pallet_prelude::{BlockNumberFor, OriginFor}, - EventRecord, Pallet as System, + Pallet as System, }; use pallet_transaction_payment::OnChargeTransaction; use scale_info::TypeInfo; @@ -98,9 +98,6 @@ type BalanceOf = <::Currency as Inspect<::AccountId>>::Balance; type OnChargeTransactionBalanceOf = <::OnChargeTransaction as OnChargeTransaction>::Balance; type CodeVec = BoundedVec>; -type EventRecordOf = - EventRecord<::RuntimeEvent, ::Hash>; -type DebugBuffer = BoundedVec>; type ImmutableData = BoundedVec>; /// Used as a sentinel value when reading and writing contract memory. @@ -258,9 +255,9 @@ pub mod pallet { #[pallet::no_default_bounds] type InstantiateOrigin: EnsureOrigin; - /// For most production chains, it's recommended to use the `()` implementation of this - /// trait. This implementation offers additional logging when the log target - /// "runtime::revive" is set to trace. + /// Debugging utilities for contracts. + /// For production chains, it's recommended to use the `()` implementation of this + /// trait. #[pallet::no_default_bounds] type Debug: Debugger; @@ -810,9 +807,8 @@ pub mod pallet { gas_limit, DepositLimit::Balance(storage_deposit_limit), data, - DebugInfo::Skip, - CollectEvents::Skip, ); + if let Ok(return_value) = &output.result { if return_value.did_revert() { output.result = Err(>::ContractReverted.into()); @@ -848,8 +844,6 @@ pub mod pallet { Code::Existing(code_hash), data, salt, - DebugInfo::Skip, - CollectEvents::Skip, ); if let Ok(retval) = &output.result { if retval.result.did_revert() { @@ -914,8 +908,6 @@ pub mod pallet { Code::Upload(code), data, salt, - DebugInfo::Skip, - CollectEvents::Skip, ); if let Ok(retval) = &output.result { if retval.result.did_revert() { @@ -1085,16 +1077,10 @@ where gas_limit: Weight, storage_deposit_limit: DepositLimit>, data: Vec, - debug: DebugInfo, - collect_events: CollectEvents, - ) -> ContractResult, EventRecordOf> { + ) -> ContractResult> { let mut gas_meter = GasMeter::new(gas_limit); let mut storage_deposit = Default::default(); - let mut debug_message = if matches!(debug, DebugInfo::UnsafeDebug) { - Some(DebugBuffer::default()) - } else { - None - }; + let try_call = || { let origin = Origin::from_runtime_origin(origin)?; let mut storage_meter = match storage_deposit_limit { @@ -1109,7 +1095,6 @@ where Self::convert_native_to_evm(value), data, storage_deposit_limit.is_unchecked(), - debug_message.as_mut(), )?; storage_deposit = storage_meter .try_into_deposit(&origin, storage_deposit_limit.is_unchecked()) @@ -1119,18 +1104,11 @@ where Ok(result) }; let result = Self::run_guarded(try_call); - let events = if matches!(collect_events, CollectEvents::UnsafeCollect) { - Some(System::::read_events_no_consensus().map(|e| *e).collect()) - } else { - None - }; ContractResult { result: result.map_err(|r| r.error), gas_consumed: gas_meter.gas_consumed(), gas_required: gas_meter.gas_required(), storage_deposit, - debug_message: debug_message.unwrap_or_default().to_vec(), - events, } } @@ -1138,8 +1116,7 @@ where /// /// Identical to [`Self::instantiate`] or [`Self::instantiate_with_code`] but tailored towards /// being called by other code within the runtime as opposed to from an extrinsic. It returns - /// more information and allows the enablement of features that are not suitable for an - /// extrinsic (debugging, event collection). + /// more information to the caller useful to estimate the cost of the operation. pub fn bare_instantiate( origin: OriginFor, value: BalanceOf, @@ -1148,14 +1125,9 @@ where code: Code, data: Vec, salt: Option<[u8; 32]>, - debug: DebugInfo, - collect_events: CollectEvents, - ) -> ContractResult, EventRecordOf> { + ) -> ContractResult> { let mut gas_meter = GasMeter::new(gas_limit); let mut storage_deposit = Default::default(); - let mut debug_message = - if debug == DebugInfo::UnsafeDebug { Some(DebugBuffer::default()) } else { None }; - let unchecked_deposit_limit = storage_deposit_limit.is_unchecked(); let mut storage_deposit_limit = match storage_deposit_limit { DepositLimit::Balance(limit) => limit, @@ -1195,7 +1167,6 @@ where data, salt.as_ref(), unchecked_deposit_limit, - debug_message.as_mut(), ); storage_deposit = storage_meter .try_into_deposit(&instantiate_origin, unchecked_deposit_limit)? @@ -1203,11 +1174,6 @@ where result }; let output = Self::run_guarded(try_instantiate); - let events = if matches!(collect_events, CollectEvents::UnsafeCollect) { - Some(System::::read_events_no_consensus().map(|e| *e).collect()) - } else { - None - }; ContractResult { result: output .map(|(addr, result)| InstantiateReturnValue { result, addr }) @@ -1215,8 +1181,6 @@ where gas_consumed: gas_meter.gas_consumed(), gas_required: gas_meter.gas_required(), storage_deposit, - debug_message: debug_message.unwrap_or_default().to_vec(), - events, } } @@ -1273,8 +1237,6 @@ where }; let input = tx.input.clone().unwrap_or_default().0; - let debug = DebugInfo::Skip; - let collect_events = CollectEvents::Skip; let extract_error = |err| { if err == Error::::TransferFailed.into() || @@ -1305,8 +1267,6 @@ where gas_limit, storage_deposit_limit, input.clone(), - debug, - collect_events, ); let data = match result.result { @@ -1363,8 +1323,6 @@ where Code::Upload(code.to_vec()), data.to_vec(), None, - debug, - collect_events, ); let returned_data = match result.result { @@ -1535,12 +1493,11 @@ environmental!(executing_contract: bool); sp_api::decl_runtime_apis! { /// The API used to dry-run contract interactions. #[api_version(1)] - pub trait ReviveApi where + pub trait ReviveApi where AccountId: Codec, Balance: Codec, Nonce: Codec, BlockNumber: Codec, - EventRecord: Codec, { /// Returns the free balance of the given `[H160]` address, using EVM decimals. fn balance(address: H160) -> U256; @@ -1558,7 +1515,7 @@ sp_api::decl_runtime_apis! { gas_limit: Option, storage_deposit_limit: Option, input_data: Vec, - ) -> ContractResult; + ) -> ContractResult; /// Instantiate a new contract. /// @@ -1571,7 +1528,7 @@ sp_api::decl_runtime_apis! { code: Code, data: Vec, salt: Option<[u8; 32]>, - ) -> ContractResult; + ) -> ContractResult; /// Perform an Ethereum call. diff --git a/substrate/frame/revive/src/limits.rs b/substrate/frame/revive/src/limits.rs index 3b55106c67d8..f101abf0ea7e 100644 --- a/substrate/frame/revive/src/limits.rs +++ b/substrate/frame/revive/src/limits.rs @@ -57,11 +57,6 @@ pub const TRANSIENT_STORAGE_BYTES: u32 = 4 * 1024; /// The maximum allowable length in bytes for (transient) storage keys. pub const STORAGE_KEY_BYTES: u32 = 128; -/// The maximum size of the debug buffer contracts can write messages to. -/// -/// The buffer will always be disabled for on-chain execution. -pub const DEBUG_BUFFER_BYTES: u32 = 2 * 1024 * 1024; - /// The page size in which PolkaVM should allocate memory chunks. pub const PAGE_SIZE: u32 = 4 * 1024; diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs index 452d2c8a3067..9c149c7cc389 100644 --- a/substrate/frame/revive/src/primitives.rs +++ b/substrate/frame/revive/src/primitives.rs @@ -63,7 +63,7 @@ impl From for DepositLimit { /// `ContractsApi` version. Therefore when SCALE decoding a `ContractResult` its trailing data /// should be ignored to avoid any potential compatibility issues. #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct ContractResult { +pub struct ContractResult { /// How much weight was consumed during execution. pub gas_consumed: Weight, /// How much weight is required as gas limit in order to execute this call. @@ -84,26 +84,8 @@ pub struct ContractResult { /// is `Err`. This is because on error all storage changes are rolled back including the /// payment of the deposit. pub storage_deposit: StorageDeposit, - /// An optional debug message. This message is only filled when explicitly requested - /// by the code that calls into the contract. Otherwise it is empty. - /// - /// The contained bytes are valid UTF-8. This is not declared as `String` because - /// this type is not allowed within the runtime. - /// - /// Clients should not make any assumptions about the format of the buffer. - /// They should just display it as-is. It is **not** only a collection of log lines - /// provided by a contract but a formatted buffer with different sections. - /// - /// # Note - /// - /// The debug message is never generated during on-chain execution. It is reserved for - /// RPC calls. - pub debug_message: Vec, /// The execution result of the wasm code. pub result: Result, - /// The events that were emitted during execution. It is an option as event collection is - /// optional. - pub events: Option>, } /// The result of the execution of a `eth_transact` call. @@ -284,36 +266,3 @@ where } } } - -/// Determines whether events should be collected during execution. -#[derive( - Copy, Clone, PartialEq, Eq, RuntimeDebug, Decode, Encode, MaxEncodedLen, scale_info::TypeInfo, -)] -pub enum CollectEvents { - /// Collect events. - /// - /// # Note - /// - /// Events should only be collected when called off-chain, as this would otherwise - /// collect all the Events emitted in the block so far and put them into the PoV. - /// - /// **Never** use this mode for on-chain execution. - UnsafeCollect, - /// Skip event collection. - Skip, -} - -/// Determines whether debug messages will be collected. -#[derive( - Copy, Clone, PartialEq, Eq, RuntimeDebug, Decode, Encode, MaxEncodedLen, scale_info::TypeInfo, -)] -pub enum DebugInfo { - /// Collect debug messages. - /// # Note - /// - /// This should only ever be set to `UnsafeDebug` when executing as an RPC because - /// it adds allocations and could be abused to drive the runtime into an OOM panic. - UnsafeDebug, - /// Skip collection of debug messages. - Skip, -} diff --git a/substrate/frame/revive/src/test_utils/builder.rs b/substrate/frame/revive/src/test_utils/builder.rs index 8ba5e7384070..7fbb5b676439 100644 --- a/substrate/frame/revive/src/test_utils/builder.rs +++ b/substrate/frame/revive/src/test_utils/builder.rs @@ -17,9 +17,8 @@ use super::{deposit_limit, GAS_LIMIT}; use crate::{ - address::AddressMapper, AccountIdOf, BalanceOf, Code, CollectEvents, Config, ContractResult, - DebugInfo, DepositLimit, EventRecordOf, ExecReturnValue, InstantiateReturnValue, OriginFor, - Pallet, Weight, + address::AddressMapper, AccountIdOf, BalanceOf, Code, Config, ContractResult, DepositLimit, + ExecReturnValue, InstantiateReturnValue, OriginFor, Pallet, Weight, }; use frame_support::pallet_prelude::DispatchResultWithPostInfo; use paste::paste; @@ -138,9 +137,7 @@ builder!( code: Code, data: Vec, salt: Option<[u8; 32]>, - debug: DebugInfo, - collect_events: CollectEvents, - ) -> ContractResult, EventRecordOf>; + ) -> ContractResult>; /// Build the instantiate call and unwrap the result. pub fn build_and_unwrap_result(self) -> InstantiateReturnValue { @@ -164,8 +161,6 @@ builder!( code, data: vec![], salt: Some([0; 32]), - debug: DebugInfo::UnsafeDebug, - collect_events: CollectEvents::Skip, } } ); @@ -201,9 +196,7 @@ builder!( gas_limit: Weight, storage_deposit_limit: DepositLimit>, data: Vec, - debug: DebugInfo, - collect_events: CollectEvents, - ) -> ContractResult, EventRecordOf>; + ) -> ContractResult>; /// Build the call and unwrap the result. pub fn build_and_unwrap_result(self) -> ExecReturnValue { @@ -219,8 +212,6 @@ builder!( gas_limit: GAS_LIMIT, storage_deposit_limit: DepositLimit::Balance(deposit_limit::()), data: vec![], - debug: DebugInfo::UnsafeDebug, - collect_events: CollectEvents::Skip, } } ); diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index cf02d17a4d03..e2b30cf07c8d 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -38,9 +38,9 @@ use crate::{ tests::test_utils::{get_contract, get_contract_checked}, wasm::Memory, weights::WeightInfo, - AccountId32Mapper, BalanceOf, Code, CodeInfoOf, CollectEvents, Config, ContractInfo, - ContractInfoOf, DebugInfo, DeletionQueueCounter, DepositLimit, Error, EthTransactError, - HoldReason, Origin, Pallet, PristineCode, H160, + AccountId32Mapper, BalanceOf, Code, CodeInfoOf, Config, ContractInfo, ContractInfoOf, + DeletionQueueCounter, DepositLimit, Error, EthTransactError, HoldReason, Origin, Pallet, + PristineCode, H160, }; use crate::test_utils::builder::Contract; @@ -2184,58 +2184,6 @@ fn refcounter() { }); } -#[test] -fn debug_message_works() { - let (wasm, _code_hash) = compile_module("debug_message_works").unwrap(); - - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(30_000) - .build_and_unwrap_contract(); - let result = builder::bare_call(addr).debug(DebugInfo::UnsafeDebug).build(); - - assert_matches!(result.result, Ok(_)); - assert_eq!(std::str::from_utf8(&result.debug_message).unwrap(), "Hello World!"); - }); -} - -#[test] -fn debug_message_logging_disabled() { - let (wasm, _code_hash) = compile_module("debug_message_logging_disabled").unwrap(); - - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(30_000) - .build_and_unwrap_contract(); - // the dispatchables always run without debugging - assert_ok!(Contracts::call( - RuntimeOrigin::signed(ALICE), - addr, - 0, - GAS_LIMIT, - deposit_limit::(), - vec![] - )); - }); -} - -#[test] -fn debug_message_invalid_utf8() { - let (wasm, _code_hash) = compile_module("debug_message_invalid_utf8").unwrap(); - - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(30_000) - .build_and_unwrap_contract(); - let result = builder::bare_call(addr).debug(DebugInfo::UnsafeDebug).build(); - assert_ok!(result.result); - assert!(result.debug_message.is_empty()); - }); -} - #[test] fn gas_estimation_for_subcalls() { let (caller_code, _caller_hash) = compile_module("call_with_limit").unwrap(); @@ -2451,79 +2399,6 @@ fn ecdsa_recover() { }) } -#[test] -fn bare_instantiate_returns_events() { - let (wasm, _code_hash) = compile_module("transfer_return_code").unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let min_balance = Contracts::min_balance(); - let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); - - let result = builder::bare_instantiate(Code::Upload(wasm)) - .value(min_balance * 100) - .collect_events(CollectEvents::UnsafeCollect) - .build(); - - let events = result.events.unwrap(); - assert!(!events.is_empty()); - assert_eq!(events, System::events()); - }); -} - -#[test] -fn bare_instantiate_does_not_return_events() { - let (wasm, _code_hash) = compile_module("transfer_return_code").unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let min_balance = Contracts::min_balance(); - let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); - - let result = builder::bare_instantiate(Code::Upload(wasm)).value(min_balance * 100).build(); - - let events = result.events; - assert!(!System::events().is_empty()); - assert!(events.is_none()); - }); -} - -#[test] -fn bare_call_returns_events() { - let (wasm, _code_hash) = compile_module("transfer_return_code").unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let min_balance = Contracts::min_balance(); - let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); - - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(min_balance * 100) - .build_and_unwrap_contract(); - - let result = builder::bare_call(addr).collect_events(CollectEvents::UnsafeCollect).build(); - - let events = result.events.unwrap(); - assert_return_code!(&result.result.unwrap(), RuntimeReturnCode::Success); - assert!(!events.is_empty()); - assert_eq!(events, System::events()); - }); -} - -#[test] -fn bare_call_does_not_return_events() { - let (wasm, _code_hash) = compile_module("transfer_return_code").unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let min_balance = Contracts::min_balance(); - let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); - - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(min_balance * 100) - .build_and_unwrap_contract(); - - let result = builder::bare_call(addr).build(); - - let events = result.events; - assert_return_code!(&result.result.unwrap(), RuntimeReturnCode::Success); - assert!(!System::events().is_empty()); - assert!(events.is_none()); - }); -} - #[test] fn sr25519_verify() { let (wasm, _code_hash) = compile_module("sr25519_verify").unwrap(); @@ -3327,14 +3202,11 @@ fn set_code_hash() { // First call sets new code_hash and returns 1 let result = builder::bare_call(contract_addr) .data(new_code_hash.as_ref().to_vec()) - .debug(DebugInfo::UnsafeDebug) .build_and_unwrap_result(); assert_return_code!(result, 1); // Second calls new contract code that returns 2 - let result = builder::bare_call(contract_addr) - .debug(DebugInfo::UnsafeDebug) - .build_and_unwrap_result(); + let result = builder::bare_call(contract_addr).build_and_unwrap_result(); assert_return_code!(result, 2); // Checking for the last event only @@ -4810,7 +4682,7 @@ fn skip_transfer_works() { ..Default::default() }, Weight::MAX, - |_| 0u32 + |_| 0u32, ), EthTransactError::Message(format!( "insufficient funds for gas * price + value: address {BOB_ADDR:?} have 0 (supplied gas 1)" @@ -4825,7 +4697,7 @@ fn skip_transfer_works() { ..Default::default() }, Weight::MAX, - |_| 0u32 + |_| 0u32, )); let Contract { addr, .. } = @@ -4844,7 +4716,7 @@ fn skip_transfer_works() { ..Default::default() }, Weight::MAX, - |_| 0u32 + |_| 0u32, ), EthTransactError::Message(format!( "insufficient funds for gas * price + value: address {BOB_ADDR:?} have 0 (supplied gas 1)" @@ -4869,7 +4741,7 @@ fn skip_transfer_works() { assert_ok!(Pallet::::bare_eth_transact( GenericTransaction { from: Some(BOB_ADDR), to: Some(addr), ..Default::default() }, Weight::MAX, - |_| 0u32 + |_| 0u32, )); // works when calling from a contract when no gas is specified. @@ -4881,7 +4753,7 @@ fn skip_transfer_works() { ..Default::default() }, Weight::MAX, - |_| 0u32 + |_| 0u32, )); }); } diff --git a/substrate/frame/revive/src/tests/test_debug.rs b/substrate/frame/revive/src/tests/test_debug.rs index c9e19e52ace1..b1fdb2d47441 100644 --- a/substrate/frame/revive/src/tests/test_debug.rs +++ b/substrate/frame/revive/src/tests/test_debug.rs @@ -119,8 +119,6 @@ fn debugging_works() { Code::Upload(wasm), vec![], Some([0u8; 32]), - DebugInfo::Skip, - CollectEvents::Skip, ) .result .unwrap() @@ -204,8 +202,6 @@ fn call_interception_works() { vec![], // some salt to ensure that the address of this contract is unique among all tests Some([0x41; 32]), - DebugInfo::Skip, - CollectEvents::Skip, ) .result .unwrap() diff --git a/substrate/frame/revive/src/wasm/mod.rs b/substrate/frame/revive/src/wasm/mod.rs index 3bd4bde5679f..b45d7026ba91 100644 --- a/substrate/frame/revive/src/wasm/mod.rs +++ b/substrate/frame/revive/src/wasm/mod.rs @@ -288,7 +288,7 @@ impl WasmBlob { } let engine = polkavm::Engine::new(&config).expect( "on-chain (no_std) use of interpreter is hard coded. - interpreter is available on all plattforms; qed", + interpreter is available on all platforms; qed", ); let mut module_config = polkavm::ModuleConfig::new(); diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index 8529c7d9e73b..1ff6a80840a9 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -339,8 +339,6 @@ pub enum RuntimeCosts { Terminate(u32), /// Weight of calling `seal_deposit_event` with the given number of topics and event size. DepositEvent { num_topic: u32, len: u32 }, - /// Weight of calling `seal_debug_message` per byte of passed message. - DebugMessage(u32), /// Weight of calling `seal_set_storage` for the given storage item sizes. SetStorage { old_bytes: u32, new_bytes: u32 }, /// Weight of calling `seal_clear_storage` per cleared byte. @@ -489,7 +487,6 @@ impl Token for RuntimeCosts { WeightToFee => T::WeightInfo::seal_weight_to_fee(), Terminate(locked_dependencies) => T::WeightInfo::seal_terminate(locked_dependencies), DepositEvent { num_topic, len } => T::WeightInfo::seal_deposit_event(num_topic, len), - DebugMessage(len) => T::WeightInfo::seal_debug_message(len), SetStorage { new_bytes, old_bytes } => { cost_storage!(write, seal_set_storage, new_bytes, old_bytes) }, @@ -669,10 +666,7 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { match result { Ok(_) => Ok(ReturnErrorCode::Success), Err(e) => { - if self.ext.debug_buffer_enabled() { - self.ext.append_debug_buffer("call failed with: "); - self.ext.append_debug_buffer(e.into()); - }; + log::debug!(target: LOG_TARGET, "call failed with: {e:?}"); Ok(ErrorReturnCode::get()) }, } @@ -1832,27 +1826,6 @@ pub mod env { self.contains_storage(memory, flags, key_ptr, key_len) } - /// Emit a custom debug message. - /// See [`pallet_revive_uapi::HostFn::debug_message`]. - fn debug_message( - &mut self, - memory: &mut M, - str_ptr: u32, - str_len: u32, - ) -> Result { - let str_len = str_len.min(limits::DEBUG_BUFFER_BYTES); - self.charge_gas(RuntimeCosts::DebugMessage(str_len))?; - if self.ext.append_debug_buffer("") { - let data = memory.read(str_ptr, str_len)?; - if let Some(msg) = core::str::from_utf8(&data).ok() { - self.ext.append_debug_buffer(msg); - } - Ok(ReturnErrorCode::Success) - } else { - Ok(ReturnErrorCode::LoggingDisabled) - } - } - /// Recovers the ECDSA public key from the given message hash and signature. /// See [`pallet_revive_uapi::HostFn::ecdsa_recover`]. fn ecdsa_recover( @@ -2162,10 +2135,7 @@ pub mod env { Ok(ReturnErrorCode::Success) }, Err(e) => { - if self.ext.append_debug_buffer("") { - self.ext.append_debug_buffer("seal0::xcm_send failed with: "); - self.ext.append_debug_buffer(e.into()); - }; + log::debug!(target: LOG_TARGET, "seal0::xcm_send failed with: {e:?}"); Ok(ReturnErrorCode::XcmSendFailed) }, } diff --git a/substrate/frame/revive/src/weights.rs b/substrate/frame/revive/src/weights.rs index e35ba5ca0766..06495d5d21aa 100644 --- a/substrate/frame/revive/src/weights.rs +++ b/substrate/frame/revive/src/weights.rs @@ -96,7 +96,6 @@ pub trait WeightInfo { fn seal_return(n: u32, ) -> Weight; fn seal_terminate(n: u32, ) -> Weight; fn seal_deposit_event(t: u32, n: u32, ) -> Weight; - fn seal_debug_message(i: u32, ) -> Weight; fn get_storage_empty() -> Weight; fn get_storage_full() -> Weight; fn set_storage_empty() -> Weight; @@ -643,16 +642,6 @@ impl WeightInfo for SubstrateWeight { // Standard Error: 34 .saturating_add(Weight::from_parts(774, 0).saturating_mul(n.into())) } - /// The range of component `i` is `[0, 262144]`. - fn seal_debug_message(i: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 340_000 picoseconds. - Weight::from_parts(306_527, 0) - // Standard Error: 1 - .saturating_add(Weight::from_parts(728, 0).saturating_mul(i.into())) - } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) fn get_storage_empty() -> Weight { @@ -1539,16 +1528,6 @@ impl WeightInfo for () { // Standard Error: 34 .saturating_add(Weight::from_parts(774, 0).saturating_mul(n.into())) } - /// The range of component `i` is `[0, 262144]`. - fn seal_debug_message(i: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 340_000 picoseconds. - Weight::from_parts(306_527, 0) - // Standard Error: 1 - .saturating_add(Weight::from_parts(728, 0).saturating_mul(i.into())) - } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) fn get_storage_empty() -> Weight { diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index ba0a63b15c37..b82393826ddf 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -515,26 +515,6 @@ pub trait HostFn: private::Sealed { #[unstable_hostfn] fn contains_storage(flags: StorageFlags, key: &[u8]) -> Option; - /// Emit a custom debug message. - /// - /// No newlines are added to the supplied message. - /// Specifying invalid UTF-8 just drops the message with no trap. - /// - /// This is a no-op if debug message recording is disabled which is always the case - /// when the code is executing on-chain. The message is interpreted as UTF-8 and - /// appended to the debug buffer which is then supplied to the calling RPC client. - /// - /// # Note - /// - /// Even though no action is taken when debug message recording is disabled there is still - /// a non trivial overhead (and weight cost) associated with calling this function. Contract - /// languages should remove calls to this function (either at runtime or compile time) when - /// not being executed as an RPC. For example, they could allow users to disable logging - /// through compile time flags (cargo features) for on-chain deployment. Additionally, the - /// return value of this function can be cached in order to prevent further calls at runtime. - #[unstable_hostfn] - fn debug_message(str: &[u8]) -> Result; - /// Recovers the ECDSA public key from the given message hash and signature. /// /// Writes the public key into the given output buffer. diff --git a/substrate/frame/revive/uapi/src/host/riscv64.rs b/substrate/frame/revive/uapi/src/host/riscv64.rs index 8c40bc9f48ea..0023b8aa721d 100644 --- a/substrate/frame/revive/uapi/src/host/riscv64.rs +++ b/substrate/frame/revive/uapi/src/host/riscv64.rs @@ -109,7 +109,6 @@ mod sys { out_ptr: *mut u8, out_len_ptr: *mut u32, ) -> ReturnCode; - pub fn debug_message(str_ptr: *const u8, str_len: u32) -> ReturnCode; pub fn call_runtime(call_ptr: *const u8, call_len: u32) -> ReturnCode; pub fn ecdsa_recover( signature_ptr: *const u8, @@ -519,12 +518,6 @@ impl HostFn for HostFnImpl { ret_code.into() } - #[unstable_hostfn] - fn debug_message(str: &[u8]) -> Result { - let ret_code = unsafe { sys::debug_message(str.as_ptr(), str.len() as u32) }; - ret_code.into() - } - #[unstable_hostfn] fn ecdsa_recover( signature: &[u8; 65], diff --git a/substrate/frame/revive/uapi/src/lib.rs b/substrate/frame/revive/uapi/src/lib.rs index ef1798b4bf61..867f35633987 100644 --- a/substrate/frame/revive/uapi/src/lib.rs +++ b/substrate/frame/revive/uapi/src/lib.rs @@ -86,9 +86,8 @@ define_error_codes! { /// Transfer failed for other not further specified reason. Most probably /// reserved or locked balance of the sender that was preventing the transfer. TransferFailed = 4, - /// The call to `debug_message` had no effect because debug message - /// recording was disabled. - LoggingDisabled = 5, + /// The subcall ran out of weight or storage deposit. + OutOfResources = 5, /// The call dispatched by `call_runtime` was executed but returned an error. CallRuntimeFailed = 6, /// ECDSA public key recovery failed. Most probably wrong recovery id or signature. @@ -99,8 +98,6 @@ define_error_codes! { XcmExecutionFailed = 9, /// The `xcm_send` call failed. XcmSendFailed = 10, - /// The subcall ran out of weight or storage deposit. - OutOfResources = 11, } /// The raw return code returned by the host side. From 5be65872188a4ac1bf76333af3958b65f2a9629e Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Wed, 15 Jan 2025 20:23:54 +0100 Subject: [PATCH 15/17] [pallet-revive] Remove revive events (#7164) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove all pallet::events except for the `ContractEmitted` event that is emitted by contracts --------- Co-authored-by: command-bot <> Co-authored-by: Alexander Theißen --- prdoc/pr_7164.prdoc | 8 + substrate/frame/revive/src/exec.rs | 103 +------ substrate/frame/revive/src/lib.rs | 72 ----- substrate/frame/revive/src/storage/meter.rs | 16 +- substrate/frame/revive/src/tests.rs | 323 +------------------- substrate/frame/revive/src/wasm/mod.rs | 18 +- 6 files changed, 40 insertions(+), 500 deletions(-) create mode 100644 prdoc/pr_7164.prdoc diff --git a/prdoc/pr_7164.prdoc b/prdoc/pr_7164.prdoc new file mode 100644 index 000000000000..cb0410a9de79 --- /dev/null +++ b/prdoc/pr_7164.prdoc @@ -0,0 +1,8 @@ +title: '[pallet-revive] Remove revive events' +doc: +- audience: Runtime Dev + description: Remove all pallet::events except for the `ContractEmitted` event that + is emitted by contracts +crates: +- name: pallet-revive + bump: major diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index e20c5dd7786e..1c6ca435aefb 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -1092,34 +1092,11 @@ where .enforce_limit(contract) .map_err(|e| ExecError { error: e, origin: ErrorOrigin::Callee })?; - let account_id = T::AddressMapper::to_address(&frame.account_id); - match (entry_point, delegated_code_hash) { - (ExportedFunction::Constructor, _) => { - // It is not allowed to terminate a contract inside its constructor. - if matches!(frame.contract_info, CachedContract::Terminated) { - return Err(Error::::TerminatedInConstructor.into()); - } - - let caller = T::AddressMapper::to_address(self.caller().account_id()?); - // Deposit an instantiation event. - Contracts::::deposit_event(Event::Instantiated { - deployer: caller, - contract: account_id, - }); - }, - (ExportedFunction::Call, Some(code_hash)) => { - Contracts::::deposit_event(Event::DelegateCalled { - contract: account_id, - code_hash, - }); - }, - (ExportedFunction::Call, None) => { - let caller = self.caller(); - Contracts::::deposit_event(Event::Called { - caller: caller.clone(), - contract: account_id, - }); - }, + // It is not allowed to terminate a contract inside its constructor. + if entry_point == ExportedFunction::Constructor && + matches!(frame.contract_info, CachedContract::Terminated) + { + return Err(Error::::TerminatedInConstructor.into()); } Ok(output) @@ -1526,10 +1503,6 @@ where .charge_deposit(frame.account_id.clone(), StorageDeposit::Refund(*deposit)); } - Contracts::::deposit_event(Event::Terminated { - contract: account_address, - beneficiary: *beneficiary, - }); Ok(()) } @@ -1782,11 +1755,6 @@ where Self::increment_refcount(hash)?; Self::decrement_refcount(prev_hash); - Contracts::::deposit_event(Event::ContractCodeUpdated { - contract: T::AddressMapper::to_address(&frame.account_id), - new_code_hash: hash, - old_code_hash: prev_hash, - }); Ok(()) } @@ -2933,13 +2901,6 @@ mod tests { ContractInfo::::load_code_hash(&instantiated_contract_id).unwrap(), dummy_ch ); - assert_eq!( - &events(), - &[Event::Instantiated { - deployer: ALICE_ADDR, - contract: instantiated_contract_address - }] - ); }); } @@ -3055,19 +3016,6 @@ mod tests { ContractInfo::::load_code_hash(&instantiated_contract_id).unwrap(), dummy_ch ); - assert_eq!( - &events(), - &[ - Event::Instantiated { - deployer: BOB_ADDR, - contract: instantiated_contract_address - }, - Event::Called { - caller: Origin::from_account_id(ALICE), - contract: BOB_ADDR - }, - ] - ); }); } @@ -3119,13 +3067,6 @@ mod tests { ), Ok(_) ); - - // The contract wasn't instantiated so we don't expect to see an instantiation - // event here. - assert_eq!( - &events(), - &[Event::Called { caller: Origin::from_account_id(ALICE), contract: BOB_ADDR },] - ); }); } @@ -3465,24 +3406,14 @@ mod tests { let remark_hash = ::Hashing::hash(b"Hello World"); assert_eq!( System::events(), - vec![ - EventRecord { - phase: Phase::Initialization, - event: MetaEvent::System(frame_system::Event::Remarked { - sender: BOB_FALLBACK, - hash: remark_hash - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: MetaEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: BOB_ADDR, - }), - topics: vec![], - }, - ] + vec![EventRecord { + phase: Phase::Initialization, + event: MetaEvent::System(frame_system::Event::Remarked { + sender: BOB_FALLBACK, + hash: remark_hash + }), + topics: vec![], + },] ); }); } @@ -3571,14 +3502,6 @@ mod tests { },), topics: vec![], }, - EventRecord { - phase: Phase::Initialization, - event: MetaEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: BOB_ADDR, - }), - topics: vec![], - }, ] ); }); diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 403598ae136e..a9f2842c35f6 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -379,25 +379,6 @@ pub mod pallet { #[pallet::event] pub enum Event { - /// Contract deployed by address at the specified address. - Instantiated { deployer: H160, contract: H160 }, - - /// Contract has been removed. - /// - /// # Note - /// - /// The only way for a contract to be removed and emitting this event is by calling - /// `seal_terminate`. - Terminated { - /// The contract that was terminated. - contract: H160, - /// The account that received the contracts remaining balance - beneficiary: H160, - }, - - /// Code with the specified hash has been stored. - CodeStored { code_hash: H256, deposit_held: BalanceOf, uploader: H160 }, - /// A custom event emitted by the contract. ContractEmitted { /// The contract that emitted the event. @@ -409,54 +390,6 @@ pub mod pallet { /// Number of topics is capped by [`limits::NUM_EVENT_TOPICS`]. topics: Vec, }, - - /// A code with the specified hash was removed. - CodeRemoved { code_hash: H256, deposit_released: BalanceOf, remover: H160 }, - - /// A contract's code was updated. - ContractCodeUpdated { - /// The contract that has been updated. - contract: H160, - /// New code hash that was set for the contract. - new_code_hash: H256, - /// Previous code hash of the contract. - old_code_hash: H256, - }, - - /// A contract was called either by a plain account or another contract. - /// - /// # Note - /// - /// Please keep in mind that like all events this is only emitted for successful - /// calls. This is because on failure all storage changes including events are - /// rolled back. - Called { - /// The caller of the `contract`. - caller: Origin, - /// The contract that was called. - contract: H160, - }, - - /// A contract delegate called a code hash. - /// - /// # Note - /// - /// Please keep in mind that like all events this is only emitted for successful - /// calls. This is because on failure all storage changes including events are - /// rolled back. - DelegateCalled { - /// The contract that performed the delegate call and hence in whose context - /// the `code_hash` is executed. - contract: H160, - /// The code hash that was delegate called. - code_hash: H256, - }, - - /// Some funds have been transferred and held as storage deposit. - StorageDepositTransferredAndHeld { from: H160, to: H160, amount: BalanceOf }, - - /// Some storage deposit funds have been transferred and released. - StorageDepositTransferredAndReleased { from: H160, to: H160, amount: BalanceOf }, } #[pallet::error] @@ -985,11 +918,6 @@ pub mod pallet { }; >>::increment_refcount(code_hash)?; >>::decrement_refcount(contract.code_hash); - Self::deposit_event(Event::ContractCodeUpdated { - contract: dest, - new_code_hash: code_hash, - old_code_hash: contract.code_hash, - }); contract.code_hash = code_hash; Ok(()) }) diff --git a/substrate/frame/revive/src/storage/meter.rs b/substrate/frame/revive/src/storage/meter.rs index 4febcb0c4066..cd390c86f63a 100644 --- a/substrate/frame/revive/src/storage/meter.rs +++ b/substrate/frame/revive/src/storage/meter.rs @@ -18,8 +18,8 @@ //! This module contains functions to meter the storage deposit. use crate::{ - address::AddressMapper, storage::ContractInfo, AccountIdOf, BalanceOf, CodeInfo, Config, Error, - Event, HoldReason, Inspect, Origin, Pallet, StorageDeposit as Deposit, System, LOG_TARGET, + storage::ContractInfo, AccountIdOf, BalanceOf, CodeInfo, Config, Error, HoldReason, Inspect, + Origin, Pallet, StorageDeposit as Deposit, System, LOG_TARGET, }; use alloc::vec::Vec; use core::{fmt::Debug, marker::PhantomData}; @@ -516,12 +516,6 @@ impl Ext for ReservingExt { Preservation::Preserve, Fortitude::Polite, )?; - - Pallet::::deposit_event(Event::StorageDepositTransferredAndHeld { - from: T::AddressMapper::to_address(origin), - to: T::AddressMapper::to_address(contract), - amount: *amount, - }); }, Deposit::Refund(amount) => { let transferred = T::Currency::transfer_on_hold( @@ -534,12 +528,6 @@ impl Ext for ReservingExt { Fortitude::Polite, )?; - Pallet::::deposit_event(Event::StorageDepositTransferredAndReleased { - from: T::AddressMapper::to_address(contract), - to: T::AddressMapper::to_address(origin), - amount: transferred, - }); - if transferred < *amount { // This should never happen, if it does it means that there is a bug in the // runtime logic. In the rare case this happens we try to refund as much as we diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index e2b30cf07c8d..35940f544d00 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -713,25 +713,6 @@ fn instantiate_and_call_and_deposit_event() { }), topics: vec![], }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Instantiated { - deployer: ALICE_ADDR, - contract: addr - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndHeld { - from: ALICE_ADDR, - to: addr, - amount: test_utils::contract_info_storage_deposit(&addr), - } - ), - topics: vec![], - }, ] ); }); @@ -1078,14 +1059,6 @@ fn deploy_and_call_other_contract() { }), topics: vec![], }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Instantiated { - deployer: caller_addr, - contract: callee_addr, - }), - topics: vec![], - }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { @@ -1095,33 +1068,6 @@ fn deploy_and_call_other_contract() { }), topics: vec![], }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(caller_account.clone()), - contract: callee_addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: caller_addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndHeld { - from: ALICE_ADDR, - to: callee_addr, - amount: test_utils::contract_info_storage_deposit(&callee_addr), - } - ), - topics: vec![], - }, ] ); }); @@ -1373,8 +1319,6 @@ fn self_destruct_works() { // Check that the BOB contract has been instantiated. let _ = get_contract(&contract.addr); - let info_deposit = test_utils::contract_info_storage_deposit(&contract.addr); - // Drop all previous events initialize_block(2); @@ -1404,33 +1348,6 @@ fn self_destruct_works() { pretty_assertions::assert_eq!( System::events(), vec![ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Terminated { - contract: contract.addr, - beneficiary: DJANGO_ADDR, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: contract.addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndReleased { - from: contract.addr, - to: ALICE_ADDR, - amount: info_deposit, - } - ), - topics: vec![], - }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::System(frame_system::Event::KilledAccount { @@ -2512,23 +2429,9 @@ fn upload_code_works() { initialize_block(2); assert!(!PristineCode::::contains_key(&code_hash)); - assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, 1_000,)); // Ensure the contract was stored and get expected deposit amount to be reserved. - let deposit_expected = expected_deposit(ensure_stored(code_hash)); - - assert_eq!( - System::events(), - vec![EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::CodeStored { - code_hash, - deposit_held: deposit_expected, - uploader: ALICE_ADDR - }), - topics: vec![], - },] - ); + expected_deposit(ensure_stored(code_hash)); }); } @@ -2586,32 +2489,8 @@ fn remove_code_works() { assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, 1_000,)); // Ensure the contract was stored and get expected deposit amount to be reserved. - let deposit_expected = expected_deposit(ensure_stored(code_hash)); - + expected_deposit(ensure_stored(code_hash)); assert_ok!(Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash)); - assert_eq!( - System::events(), - vec![ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::CodeStored { - code_hash, - deposit_held: deposit_expected, - uploader: ALICE_ADDR - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::CodeRemoved { - code_hash, - deposit_released: deposit_expected, - remover: ALICE_ADDR - }), - topics: vec![], - }, - ] - ); }); } @@ -2627,25 +2506,12 @@ fn remove_code_wrong_origin() { assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, 1_000,)); // Ensure the contract was stored and get expected deposit amount to be reserved. - let deposit_expected = expected_deposit(ensure_stored(code_hash)); + expected_deposit(ensure_stored(code_hash)); assert_noop!( Contracts::remove_code(RuntimeOrigin::signed(BOB), code_hash), sp_runtime::traits::BadOrigin, ); - - assert_eq!( - System::events(), - vec![EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::CodeStored { - code_hash, - deposit_held: deposit_expected, - uploader: ALICE_ADDR - }), - topics: vec![], - },] - ); }); } @@ -2704,7 +2570,7 @@ fn instantiate_with_zero_balance_works() { builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_contract(); // Ensure the contract was stored and get expected deposit amount to be reserved. - let deposit_expected = expected_deposit(ensure_stored(code_hash)); + expected_deposit(ensure_stored(code_hash)); // Make sure the account exists even though no free balance was send assert_eq!(::Currency::free_balance(&account_id), min_balance); @@ -2716,15 +2582,6 @@ fn instantiate_with_zero_balance_works() { assert_eq!( System::events(), vec![ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::CodeStored { - code_hash, - deposit_held: deposit_expected, - uploader: ALICE_ADDR - }), - topics: vec![], - }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::System(frame_system::Event::NewAccount { @@ -2749,25 +2606,6 @@ fn instantiate_with_zero_balance_works() { }), topics: vec![], }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Instantiated { - deployer: ALICE_ADDR, - contract: addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndHeld { - from: ALICE_ADDR, - to: addr, - amount: test_utils::contract_info_storage_deposit(&addr), - } - ), - topics: vec![], - }, ] ); }); @@ -2790,7 +2628,7 @@ fn instantiate_with_below_existential_deposit_works() { .build_and_unwrap_contract(); // Ensure the contract was stored and get expected deposit amount to be reserved. - let deposit_expected = expected_deposit(ensure_stored(code_hash)); + expected_deposit(ensure_stored(code_hash)); // Make sure the account exists even though not enough free balance was send assert_eq!(::Currency::free_balance(&account_id), min_balance + value); assert_eq!( @@ -2801,15 +2639,6 @@ fn instantiate_with_below_existential_deposit_works() { assert_eq!( System::events(), vec![ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::CodeStored { - code_hash, - deposit_held: deposit_expected, - uploader: ALICE_ADDR - }), - topics: vec![], - }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::System(frame_system::Event::NewAccount { @@ -2843,25 +2672,6 @@ fn instantiate_with_below_existential_deposit_works() { }), topics: vec![], }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Instantiated { - deployer: ALICE_ADDR, - contract: addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndHeld { - from: ALICE_ADDR, - to: addr, - amount: test_utils::contract_info_storage_deposit(&addr), - } - ), - topics: vec![], - }, ] ); }); @@ -2903,74 +2713,15 @@ fn storage_deposit_works() { assert_eq!( System::events(), - vec![ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { - from: ALICE, - to: account_id.clone(), - amount: 42, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndHeld { - from: ALICE_ADDR, - to: addr, - amount: charged0, - } - ), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndHeld { - from: ALICE_ADDR, - to: addr, - amount: charged1, - } - ), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndReleased { - from: addr, - to: ALICE_ADDR, - amount: refunded0, - } - ), - topics: vec![], - }, - ] + vec![EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: account_id.clone(), + amount: 42, + }), + topics: vec![], + },] ); }); } @@ -3063,18 +2814,6 @@ fn set_code_extrinsic() { assert_eq!(get_contract(&addr).code_hash, new_code_hash); assert_refcount!(&code_hash, 0); assert_refcount!(&new_code_hash, 1); - assert_eq!( - System::events(), - vec![EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(pallet_revive::Event::ContractCodeUpdated { - contract: addr, - new_code_hash, - old_code_hash: code_hash, - }), - topics: vec![], - },] - ); }); } @@ -3180,7 +2919,7 @@ fn contract_reverted() { #[test] fn set_code_hash() { - let (wasm, code_hash) = compile_module("set_code_hash").unwrap(); + let (wasm, _) = compile_module("set_code_hash").unwrap(); let (new_wasm, new_code_hash) = compile_module("new_set_code_hash_contract").unwrap(); ExtBuilder::default().existential_deposit(100).build().execute_with(|| { @@ -3208,38 +2947,6 @@ fn set_code_hash() { // Second calls new contract code that returns 2 let result = builder::bare_call(contract_addr).build_and_unwrap_result(); assert_return_code!(result, 2); - - // Checking for the last event only - assert_eq!( - &System::events(), - &[ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::ContractCodeUpdated { - contract: contract_addr, - new_code_hash, - old_code_hash: code_hash, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: contract_addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: contract_addr, - }), - topics: vec![], - }, - ], - ); }); } diff --git a/substrate/frame/revive/src/wasm/mod.rs b/substrate/frame/revive/src/wasm/mod.rs index b45d7026ba91..527cf1630954 100644 --- a/substrate/frame/revive/src/wasm/mod.rs +++ b/substrate/frame/revive/src/wasm/mod.rs @@ -29,14 +29,13 @@ pub use crate::wasm::runtime::{ReturnData, TrapReason}; pub use crate::wasm::runtime::{Memory, Runtime, RuntimeCosts}; use crate::{ - address::AddressMapper, exec::{ExecResult, Executable, ExportedFunction, Ext}, gas::{GasMeter, Token}, limits, storage::meter::Diff, weights::WeightInfo, - AccountIdOf, BadOrigin, BalanceOf, CodeInfoOf, CodeVec, Config, Error, Event, ExecError, - HoldReason, Pallet, PristineCode, Weight, LOG_TARGET, + AccountIdOf, BadOrigin, BalanceOf, CodeInfoOf, CodeVec, Config, Error, ExecError, HoldReason, + PristineCode, Weight, LOG_TARGET, }; use alloc::vec::Vec; use codec::{Decode, Encode, MaxEncodedLen}; @@ -157,16 +156,9 @@ where code_info.deposit, BestEffort, ); - let deposit_released = code_info.deposit; - let remover = T::AddressMapper::to_address(&code_info.owner); *existing = None; >::remove(&code_hash); - >::deposit_event(Event::CodeRemoved { - code_hash, - deposit_released, - remover, - }); Ok(()) } else { Err(>::CodeNotFound.into()) @@ -202,12 +194,6 @@ where self.code_info.refcount = 0; >::insert(code_hash, &self.code); *stored_code_info = Some(self.code_info.clone()); - let uploader = T::AddressMapper::to_address(&self.code_info.owner); - >::deposit_event(Event::CodeStored { - code_hash, - deposit_held: deposit, - uploader, - }); Ok(deposit) }, } From 412aca6c48a01f11318228f4d8a79fec544a22bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20M=C3=BCller?= Date: Wed, 15 Jan 2025 22:58:51 +0100 Subject: [PATCH 16/17] [pallet-revive] Add host function `to_account_id` (#7091) Closes https://github.com/paritytech/polkadot-sdk/issues/6891. cc @athei @xermicus @pgherveou --- prdoc/pr_7091.prdoc | 12 ++++++ .../fixtures/contracts/to_account_id.rs | 40 ++++++++++++++++++ .../frame/revive/src/benchmarking/mod.rs | 32 +++++++++++++++ substrate/frame/revive/src/exec.rs | 41 +++++++++++++++++++ substrate/frame/revive/src/tests.rs | 37 +++++++++++++++++ substrate/frame/revive/src/wasm/runtime.rs | 24 +++++++++++ substrate/frame/revive/src/weights.rs | 21 ++++++++++ substrate/frame/revive/uapi/src/host.rs | 12 ++++++ .../frame/revive/uapi/src/host/riscv64.rs | 6 +++ 9 files changed, 225 insertions(+) create mode 100644 prdoc/pr_7091.prdoc create mode 100644 substrate/frame/revive/fixtures/contracts/to_account_id.rs diff --git a/prdoc/pr_7091.prdoc b/prdoc/pr_7091.prdoc new file mode 100644 index 000000000000..badea4e82fdb --- /dev/null +++ b/prdoc/pr_7091.prdoc @@ -0,0 +1,12 @@ +title: '[pallet-revive] Add new host function `to_account_id`' +doc: +- audience: Runtime Dev + description: A new host function `to_account_id` is added. It allows retrieving + the account id for a `H160` address. +crates: +- name: pallet-revive-fixtures + bump: minor +- name: pallet-revive + bump: minor +- name: pallet-revive-uapi + bump: minor diff --git a/substrate/frame/revive/fixtures/contracts/to_account_id.rs b/substrate/frame/revive/fixtures/contracts/to_account_id.rs new file mode 100644 index 000000000000..c2a8fce3ec99 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/to_account_id.rs @@ -0,0 +1,40 @@ +// 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. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + address: &[u8; 20], + expected_account_id: &[u8; 32], + ); + + let mut account_id = [0u8; 32]; + api::to_account_id(address, &mut account_id); + + assert!(&account_id == expected_account_id); +} diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index e23554f21ba8..18d7bb0afc31 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -556,6 +556,38 @@ mod benchmarks { assert_eq!(result.unwrap(), 1); } + #[benchmark(pov_mode = Measured)] + fn seal_to_account_id() { + // use a mapped address for the benchmark, to ensure that we bench the worst + // case (and not the fallback case). + let address = { + let caller = account("seal_to_account_id", 0, 0); + T::Currency::set_balance(&caller, caller_funding::()); + T::AddressMapper::map(&caller).unwrap(); + T::AddressMapper::to_address(&caller) + }; + + let len = ::max_encoded_len(); + build_runtime!(runtime, memory: [vec![0u8; len], address.0, ]); + + let result; + #[block] + { + result = runtime.bench_to_account_id(memory.as_mut_slice(), len as u32, 0); + } + + assert_ok!(result); + assert_ne!( + memory.as_slice()[20..32], + [0xEE; 12], + "fallback suffix found where none should be" + ); + assert_eq!( + T::AccountId::decode(&mut memory.as_slice()), + Ok(runtime.ext().to_account_id(&address)) + ); + } + #[benchmark(pov_mode = Measured)] fn seal_code_hash() { let contract = Contract::::with_index(1, WasmModule::dummy(), vec![]).unwrap(); diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 1c6ca435aefb..f696f75a4a13 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -293,6 +293,9 @@ pub trait Ext: sealing::Sealed { /// Check if a contract lives at the specified `address`. fn is_contract(&self, address: &H160) -> bool; + /// Returns the account id for the given `address`. + fn to_account_id(&self, address: &H160) -> AccountIdOf; + /// Returns the code hash of the contract for the given `address`. /// If not a contract but account exists then `keccak_256([])` is returned, otherwise `zero`. fn code_hash(&self, address: &H160) -> H256; @@ -1572,6 +1575,10 @@ where ContractInfoOf::::contains_key(&address) } + fn to_account_id(&self, address: &H160) -> T::AccountId { + T::AddressMapper::to_account_id(address) + } + fn code_hash(&self, address: &H160) -> H256 { >::get(&address) .map(|contract| contract.code_hash) @@ -2582,6 +2589,40 @@ mod tests { }); } + #[test] + fn to_account_id_returns_proper_values() { + let bob_code_hash = MockLoader::insert(Call, |ctx, _| { + let alice_account_id = ::AddressMapper::to_account_id(&ALICE_ADDR); + assert_eq!(ctx.ext.to_account_id(&ALICE_ADDR), alice_account_id); + + const UNMAPPED_ADDR: H160 = H160([99u8; 20]); + let mut unmapped_fallback_account_id = [0xEE; 32]; + unmapped_fallback_account_id[..20].copy_from_slice(UNMAPPED_ADDR.as_bytes()); + assert_eq!( + ctx.ext.to_account_id(&UNMAPPED_ADDR), + AccountId32::new(unmapped_fallback_account_id) + ); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, bob_code_hash); + let origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&origin, 0, 0).unwrap(); + let result = MockStack::run_call( + origin, + BOB_ADDR, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + U256::zero(), + vec![0], + false, + ); + assert_matches!(result, Ok(_)); + }); + } + #[test] fn code_hash_returns_proper_values() { let bob_code_hash = MockLoader::insert(Call, |ctx, _| { diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 35940f544d00..8398bc2cb66f 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -4239,6 +4239,43 @@ fn origin_api_works() { }); } +#[test] +fn to_account_id_works() { + let (code_hash_code, _) = compile_module("to_account_id").unwrap(); + + ExtBuilder::default().existential_deposit(1).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let _ = ::Currency::set_balance(&EVE, 1_000_000); + + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code_hash_code)).build_and_unwrap_contract(); + + // mapped account + >::map_account(RuntimeOrigin::signed(EVE)).unwrap(); + let expected_mapped_account_id = &::AddressMapper::to_account_id(&EVE_ADDR); + assert_ne!( + expected_mapped_account_id.encode()[20..32], + [0xEE; 12], + "fallback suffix found where none should be" + ); + assert_ok!(builder::call(addr) + .data((EVE_ADDR, expected_mapped_account_id).encode()) + .build()); + + // fallback for unmapped accounts + let expected_fallback_account_id = + &::AddressMapper::to_account_id(&BOB_ADDR); + assert_eq!( + expected_fallback_account_id.encode()[20..32], + [0xEE; 12], + "no fallback suffix found where one should be" + ); + assert_ok!(builder::call(addr) + .data((BOB_ADDR, expected_fallback_account_id).encode()) + .build()); + }); +} + #[test] fn code_hash_works() { let (code_hash_code, self_code_hash) = compile_module("code_hash").unwrap(); diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index 1ff6a80840a9..4fbcfe1b47f5 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -293,6 +293,8 @@ pub enum RuntimeCosts { CallDataSize, /// Weight of calling `seal_return_data_size`. ReturnDataSize, + /// Weight of calling `seal_to_account_id`. + ToAccountId, /// Weight of calling `seal_origin`. Origin, /// Weight of calling `seal_is_contract`. @@ -466,6 +468,7 @@ impl Token for RuntimeCosts { Caller => T::WeightInfo::seal_caller(), Origin => T::WeightInfo::seal_origin(), IsContract => T::WeightInfo::seal_is_contract(), + ToAccountId => T::WeightInfo::seal_to_account_id(), CodeHash => T::WeightInfo::seal_code_hash(), CodeSize => T::WeightInfo::seal_code_size(), OwnCodeHash => T::WeightInfo::seal_own_code_hash(), @@ -2140,4 +2143,25 @@ pub mod env { }, } } + + /// Retrieves the account id for a specified contract address. + /// + /// See [`pallet_revive_uapi::HostFn::to_account_id`]. + fn to_account_id( + &mut self, + memory: &mut M, + addr_ptr: u32, + out_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::ToAccountId)?; + let address = memory.read_h160(addr_ptr)?; + let account_id = self.ext.to_account_id(&address); + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + &account_id.encode(), + false, + already_charged, + )?) + } } diff --git a/substrate/frame/revive/src/weights.rs b/substrate/frame/revive/src/weights.rs index 06495d5d21aa..52153d74ca75 100644 --- a/substrate/frame/revive/src/weights.rs +++ b/substrate/frame/revive/src/weights.rs @@ -67,6 +67,7 @@ pub trait WeightInfo { fn seal_caller() -> Weight; fn seal_origin() -> Weight; fn seal_is_contract() -> Weight; + fn seal_to_account_id() -> Weight; fn seal_code_hash() -> Weight; fn seal_own_code_hash() -> Weight; fn seal_code_size() -> Weight; @@ -377,6 +378,16 @@ impl WeightInfo for SubstrateWeight { Weight::from_parts(10_336_000, 3771) .saturating_add(T::DbWeight::get().reads(1_u64)) } + /// Storage: `Revive::AddressSuffix` (r:1 w:0) + /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) + fn seal_to_account_id() -> Weight { + // Proof Size summary in bytes: + // Measured: `212` + // Estimated: `3677` + // Minimum execution time: 4_000_000 picoseconds. + Weight::from_parts(4_000_000, 3677) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) fn seal_code_hash() -> Weight { @@ -1263,6 +1274,16 @@ impl WeightInfo for () { Weight::from_parts(10_336_000, 3771) .saturating_add(RocksDbWeight::get().reads(1_u64)) } + /// Storage: `Revive::AddressSuffix` (r:1 w:0) + /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) + fn seal_to_account_id() -> Weight { + // Proof Size summary in bytes: + // Measured: `212` + // Estimated: `3677` + // Minimum execution time: 4_000_000 picoseconds. + Weight::from_parts(4_000_000, 3677) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) fn seal_code_hash() -> Weight { diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index b82393826ddf..3e5cf0eb0c24 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -144,6 +144,18 @@ pub trait HostFn: private::Sealed { /// - `output`: A reference to the output data buffer to write the origin's address. fn origin(output: &mut [u8; 20]); + /// Retrieve the account id for a specified address. + /// + /// # Parameters + /// + /// - `addr`: A `H160` address. + /// - `output`: A reference to the output data buffer to write the account id. + /// + /// # Note + /// + /// If no mapping exists for `addr`, the fallback account id will be returned. + fn to_account_id(addr: &[u8; 20], output: &mut [u8]); + /// Retrieve the code hash for a specified contract address. /// /// # Parameters diff --git a/substrate/frame/revive/uapi/src/host/riscv64.rs b/substrate/frame/revive/uapi/src/host/riscv64.rs index 0023b8aa721d..3726564e26eb 100644 --- a/substrate/frame/revive/uapi/src/host/riscv64.rs +++ b/substrate/frame/revive/uapi/src/host/riscv64.rs @@ -69,6 +69,7 @@ mod sys { pub fn caller(out_ptr: *mut u8); pub fn origin(out_ptr: *mut u8); pub fn is_contract(account_ptr: *const u8) -> ReturnCode; + pub fn to_account_id(address_ptr: *const u8, out_ptr: *mut u8); pub fn code_hash(address_ptr: *const u8, out_ptr: *mut u8); pub fn code_size(address_ptr: *const u8) -> u64; pub fn own_code_hash(out_ptr: *mut u8); @@ -456,6 +457,11 @@ impl HostFn for HostFnImpl { unsafe { sys::ref_time_left() } } + #[unstable_hostfn] + fn to_account_id(address: &[u8; 20], output: &mut [u8]) { + unsafe { sys::to_account_id(address.as_ptr(), output.as_mut_ptr()) } + } + #[unstable_hostfn] fn block_hash(block_number_ptr: &[u8; 32], output: &mut [u8; 32]) { unsafe { sys::block_hash(block_number_ptr.as_ptr(), output.as_mut_ptr()) }; From be2404cccd9923c41e2f16bfe655f19574f1ae0e Mon Sep 17 00:00:00 2001 From: liamaharon Date: Thu, 16 Jan 2025 10:26:59 +0400 Subject: [PATCH 17/17] Implement `pallet-asset-rewards` (#3926) Closes #3149 ## Description This PR introduces `pallet-asset-rewards`, which allows accounts to be rewarded for freezing `fungible` tokens. The motivation for creating this pallet is to allow incentivising LPs. See the pallet docs for more info about the pallet. ## Runtime changes The pallet has been added to - `asset-hub-rococo` - `asset-hub-westend` The `NativeAndAssets` `fungibles` Union did not contain `PoolAssets`, so it has been renamed `NativeAndNonPoolAssets` A new `fungibles` Union `NativeAndAllAssets` was created to encompass all assets and the native token. ## TODO - [x] Emulation tests - [x] Fill in Freeze logic (blocked https://github.com/paritytech/polkadot-sdk/issues/3342) and re-run benchmarks --------- Co-authored-by: command-bot <> Co-authored-by: Oliver Tale-Yazdi Co-authored-by: muharem Co-authored-by: Guillaume Thiolliere --- Cargo.lock | 27 + Cargo.toml | 2 + .../assets/asset-hub-rococo/Cargo.toml | 1 + .../emulated/common/src/lib.rs | 2 + .../tests/assets/asset-hub-rococo/Cargo.toml | 1 + .../tests/assets/asset-hub-rococo/src/lib.rs | 3 +- .../assets/asset-hub-rococo/src/tests/mod.rs | 1 + .../asset-hub-rococo/src/tests/reward_pool.rs | 114 ++ .../tests/assets/asset-hub-westend/Cargo.toml | 1 + .../tests/assets/asset-hub-westend/src/lib.rs | 8 +- .../assets/asset-hub-westend/src/tests/mod.rs | 1 + .../src/tests/reward_pool.rs | 113 ++ .../assets/asset-hub-rococo/Cargo.toml | 5 + .../assets/asset-hub-rococo/src/lib.rs | 142 +- .../asset-hub-rococo/src/weights/mod.rs | 1 + .../src/weights/pallet_asset_rewards.rs | 217 +++ .../assets/asset-hub-rococo/src/xcm_config.rs | 10 +- .../assets/asset-hub-westend/Cargo.toml | 5 + .../assets/asset-hub-westend/src/lib.rs | 144 +- .../asset-hub-westend/src/weights/mod.rs | 1 + .../src/weights/pallet_asset_rewards.rs | 217 +++ .../asset-hub-westend/src/xcm_config.rs | 7 +- .../runtimes/assets/common/src/lib.rs | 7 +- polkadot/runtime/rococo/src/xcm_config.rs | 20 +- polkadot/runtime/westend/src/xcm_config.rs | 5 +- prdoc/pr_3926.prdoc | 30 + substrate/bin/node/runtime/src/lib.rs | 77 +- substrate/frame/asset-rewards/Cargo.toml | 71 + .../frame/asset-rewards/src/benchmarking.rs | 355 ++++ substrate/frame/asset-rewards/src/lib.rs | 905 ++++++++++ substrate/frame/asset-rewards/src/mock.rs | 221 +++ substrate/frame/asset-rewards/src/tests.rs | 1457 +++++++++++++++++ substrate/frame/asset-rewards/src/weights.rs | 368 +++++ substrate/frame/support/src/traits.rs | 5 +- substrate/frame/support/src/traits/storage.rs | 12 + umbrella/Cargo.toml | 10 +- umbrella/src/lib.rs | 4 + 37 files changed, 4517 insertions(+), 53 deletions(-) create mode 100644 cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reward_pool.rs create mode 100644 cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reward_pool.rs create mode 100644 cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_asset_rewards.rs create mode 100644 cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_asset_rewards.rs create mode 100644 prdoc/pr_3926.prdoc create mode 100644 substrate/frame/asset-rewards/Cargo.toml create mode 100644 substrate/frame/asset-rewards/src/benchmarking.rs create mode 100644 substrate/frame/asset-rewards/src/lib.rs create mode 100644 substrate/frame/asset-rewards/src/mock.rs create mode 100644 substrate/frame/asset-rewards/src/tests.rs create mode 100644 substrate/frame/asset-rewards/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index 0d71a770d38b..6eba7e651096 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -910,6 +910,7 @@ dependencies = [ "cumulus-primitives-core 0.7.0", "emulated-integration-tests-common", "frame-support 28.0.0", + "pallet-asset-rewards", "parachains-common 7.0.0", "rococo-emulated-chain", "sp-core 28.0.0", @@ -928,6 +929,7 @@ dependencies = [ "emulated-integration-tests-common", "frame-support 28.0.0", "pallet-asset-conversion 10.0.0", + "pallet-asset-rewards", "pallet-assets 29.1.0", "pallet-balances 28.0.0", "pallet-message-queue 31.0.0", @@ -978,6 +980,7 @@ dependencies = [ "pallet-asset-conversion 10.0.0", "pallet-asset-conversion-ops 0.1.0", "pallet-asset-conversion-tx-payment 10.0.0", + "pallet-asset-rewards", "pallet-assets 29.1.0", "pallet-assets-freezer 0.1.0", "pallet-aura 27.0.0", @@ -1063,6 +1066,7 @@ dependencies = [ "frame-support 28.0.0", "frame-system 28.0.0", "pallet-asset-conversion 10.0.0", + "pallet-asset-rewards", "pallet-asset-tx-payment 28.0.0", "pallet-assets 29.1.0", "pallet-balances 28.0.0", @@ -1114,6 +1118,7 @@ dependencies = [ "pallet-asset-conversion 10.0.0", "pallet-asset-conversion-ops 0.1.0", "pallet-asset-conversion-tx-payment 10.0.0", + "pallet-asset-rewards", "pallet-assets 29.1.0", "pallet-assets-freezer 0.1.0", "pallet-aura 27.0.0", @@ -12036,6 +12041,27 @@ dependencies = [ "sp-runtime 39.0.2", ] +[[package]] +name = "pallet-asset-rewards" +version = "0.1.0" +dependencies = [ + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", + "pallet-assets 29.1.0", + "pallet-assets-freezer 0.1.0", + "pallet-balances 28.0.0", + "parity-scale-codec", + "primitive-types 0.13.1", + "scale-info", + "sp-api 26.0.0", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-std 14.0.0", +] + [[package]] name = "pallet-asset-tx-payment" version = "28.0.0" @@ -18715,6 +18741,7 @@ dependencies = [ "pallet-asset-conversion-ops 0.1.0", "pallet-asset-conversion-tx-payment 10.0.0", "pallet-asset-rate 7.0.0", + "pallet-asset-rewards", "pallet-asset-tx-payment 28.0.0", "pallet-assets 29.1.0", "pallet-assets-freezer 0.1.0", diff --git a/Cargo.toml b/Cargo.toml index eb99b80e16fa..509775fe99e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -315,6 +315,7 @@ members = [ "substrate/frame/asset-conversion", "substrate/frame/asset-conversion/ops", "substrate/frame/asset-rate", + "substrate/frame/asset-rewards", "substrate/frame/assets", "substrate/frame/assets-freezer", "substrate/frame/atomic-swap", @@ -893,6 +894,7 @@ pallet-asset-conversion = { path = "substrate/frame/asset-conversion", default-f pallet-asset-conversion-ops = { path = "substrate/frame/asset-conversion/ops", default-features = false } pallet-asset-conversion-tx-payment = { path = "substrate/frame/transaction-payment/asset-conversion-tx-payment", default-features = false } pallet-asset-rate = { path = "substrate/frame/asset-rate", default-features = false } +pallet-asset-rewards = { path = "substrate/frame/asset-rewards", default-features = false } pallet-asset-tx-payment = { path = "substrate/frame/transaction-payment/asset-tx-payment", default-features = false } pallet-assets = { path = "substrate/frame/assets", default-features = false } pallet-assets-freezer = { path = "substrate/frame/assets-freezer", default-features = false } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml index a164a8197f72..c6a8baeff3b3 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml @@ -14,6 +14,7 @@ workspace = true # Substrate frame-support = { workspace = true } +pallet-asset-rewards = { workspace = true } sp-core = { workspace = true } sp-keyring = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/common/src/lib.rs b/cumulus/parachains/integration-tests/emulated/common/src/lib.rs index e2757f8b9a35..f5466a63f1f5 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/lib.rs @@ -58,6 +58,8 @@ pub const USDT_ID: u32 = 1984; pub const PENPAL_A_ID: u32 = 2000; pub const PENPAL_B_ID: u32 = 2001; +pub const ASSET_HUB_ROCOCO_ID: u32 = 1000; +pub const ASSET_HUB_WESTEND_ID: u32 = 1000; pub const ASSETS_PALLET_ID: u8 = 50; parameter_types! { diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml index 9e8b8f2a52d7..b53edb39c73b 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml @@ -17,6 +17,7 @@ codec = { workspace = true } # Substrate frame-support = { workspace = true } pallet-asset-conversion = { workspace = true } +pallet-asset-rewards = { workspace = true } pallet-assets = { workspace = true } pallet-balances = { workspace = true } pallet-message-queue = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs index f3a1b3f5bfa2..513ca278a319 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs @@ -76,10 +76,11 @@ mod imports { genesis::ED as ROCOCO_ED, rococo_runtime::{ governance as rococo_governance, + governance::pallet_custom_origins::Origin::Treasurer, xcm_config::{ UniversalLocation as RococoUniversalLocation, XcmConfig as RococoXcmConfig, }, - OriginCaller as RococoOriginCaller, + Dmp, OriginCaller as RococoOriginCaller, }, RococoRelayPallet as RococoPallet, }, diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs index 88fa379c4072..75714acb07cd 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs @@ -16,6 +16,7 @@ mod claim_assets; mod hybrid_transfers; mod reserve_transfer; +mod reward_pool; mod send; mod set_xcm_versions; mod swap; diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reward_pool.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reward_pool.rs new file mode 100644 index 000000000000..2f3ee536a7b9 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reward_pool.rs @@ -0,0 +1,114 @@ +// 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::imports::*; +use codec::Encode; +use frame_support::{assert_ok, sp_runtime::traits::Dispatchable, traits::schedule::DispatchTime}; +use xcm_executor::traits::ConvertLocation; + +#[test] +fn treasury_creates_asset_reward_pool() { + AssetHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type Balances = ::Balances; + + let treasurer = + Location::new(1, [Plurality { id: BodyId::Treasury, part: BodyPart::Voice }]); + let treasurer_account = + ahr_xcm_config::LocationToAccountId::convert_location(&treasurer).unwrap(); + + assert_ok!(Balances::force_set_balance( + ::RuntimeOrigin::root(), + treasurer_account.clone().into(), + ASSET_HUB_ROCOCO_ED * 100_000, + )); + + let events = AssetHubRococo::events(); + match events.iter().last() { + Some(RuntimeEvent::Balances(pallet_balances::Event::BalanceSet { who, .. })) => + assert_eq!(*who, treasurer_account), + _ => panic!("Expected Balances::BalanceSet event"), + } + }); + + Rococo::execute_with(|| { + type AssetHubRococoRuntimeCall = ::RuntimeCall; + type AssetHubRococoRuntime = ::Runtime; + type RococoRuntimeCall = ::RuntimeCall; + type RococoRuntime = ::Runtime; + type RococoRuntimeEvent = ::RuntimeEvent; + type RococoRuntimeOrigin = ::RuntimeOrigin; + + Dmp::make_parachain_reachable(AssetHubRococo::para_id()); + + let staked_asset_id = bx!(RelayLocation::get()); + let reward_asset_id = bx!(RelayLocation::get()); + + let reward_rate_per_block = 1_000_000_000; + let lifetime = 1_000_000_000; + let admin = None; + + let create_pool_call = + RococoRuntimeCall::XcmPallet(pallet_xcm::Call::::send { + dest: bx!(VersionedLocation::V4( + xcm::v4::Junction::Parachain(AssetHubRococo::para_id().into()).into() + )), + message: bx!(VersionedXcm::V5(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind: OriginKind::SovereignAccount, + fallback_max_weight: None, + call: AssetHubRococoRuntimeCall::AssetRewards( + pallet_asset_rewards::Call::::create_pool { + staked_asset_id, + reward_asset_id, + reward_rate_per_block, + expiry: DispatchTime::After(lifetime), + admin + } + ) + .encode() + .into(), + } + ]))), + }); + + let treasury_origin: RococoRuntimeOrigin = Treasurer.into(); + assert_ok!(create_pool_call.dispatch(treasury_origin)); + + assert_expected_events!( + Rococo, + vec![ + RococoRuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + AssetHubRococo::execute_with(|| { + type Runtime = ::Runtime; + type RuntimeEvent = ::RuntimeEvent; + + assert_eq!(1, pallet_asset_rewards::Pools::::iter().count()); + + let events = AssetHubRococo::events(); + match events.iter().last() { + Some(RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { + success: true, + .. + })) => (), + _ => panic!("Expected MessageQueue::Processed event"), + } + }); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml index 5cd00c239e60..ef68a53c3b18 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml @@ -19,6 +19,7 @@ frame-metadata-hash-extension = { workspace = true, default-features = true } frame-support = { workspace = true } frame-system = { workspace = true } pallet-asset-conversion = { workspace = true } +pallet-asset-rewards = { workspace = true } pallet-asset-tx-payment = { workspace = true } pallet-assets = { workspace = true } pallet-balances = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs index 36630e2d2221..68dc87250f76 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs @@ -79,8 +79,12 @@ mod imports { }, westend_emulated_chain::{ genesis::ED as WESTEND_ED, - westend_runtime::xcm_config::{ - UniversalLocation as WestendUniversalLocation, XcmConfig as WestendXcmConfig, + westend_runtime::{ + governance::pallet_custom_origins::Origin::Treasurer, + xcm_config::{ + UniversalLocation as WestendUniversalLocation, XcmConfig as WestendXcmConfig, + }, + Dmp, }, WestendRelayPallet as WestendPallet, }, diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs index 0dfe7a85f4c2..576c44fc542f 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs @@ -17,6 +17,7 @@ mod claim_assets; mod fellowship_treasury; mod hybrid_transfers; mod reserve_transfer; +mod reward_pool; mod send; mod set_asset_claimer; mod set_xcm_versions; diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reward_pool.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reward_pool.rs new file mode 100644 index 000000000000..4df51abcaceb --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reward_pool.rs @@ -0,0 +1,113 @@ +// 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::imports::*; +use codec::Encode; +use frame_support::{assert_ok, sp_runtime::traits::Dispatchable, traits::schedule::DispatchTime}; +use xcm_executor::traits::ConvertLocation; + +#[test] +fn treasury_creates_asset_reward_pool() { + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type Balances = ::Balances; + + let treasurer = + Location::new(1, [Plurality { id: BodyId::Treasury, part: BodyPart::Voice }]); + let treasurer_account = + ahw_xcm_config::LocationToAccountId::convert_location(&treasurer).unwrap(); + + assert_ok!(Balances::force_set_balance( + ::RuntimeOrigin::root(), + treasurer_account.clone().into(), + ASSET_HUB_WESTEND_ED * 100_000, + )); + + let events = AssetHubWestend::events(); + match events.iter().last() { + Some(RuntimeEvent::Balances(pallet_balances::Event::BalanceSet { who, .. })) => + assert_eq!(*who, treasurer_account), + _ => panic!("Expected Balances::BalanceSet event"), + } + }); + Westend::execute_with(|| { + type AssetHubWestendRuntimeCall = ::RuntimeCall; + type AssetHubWestendRuntime = ::Runtime; + type WestendRuntimeCall = ::RuntimeCall; + type WestendRuntime = ::Runtime; + type WestendRuntimeEvent = ::RuntimeEvent; + type WestendRuntimeOrigin = ::RuntimeOrigin; + + Dmp::make_parachain_reachable(AssetHubWestend::para_id()); + + let staked_asset_id = bx!(RelayLocation::get()); + let reward_asset_id = bx!(RelayLocation::get()); + + let reward_rate_per_block = 1_000_000_000; + let lifetime = 1_000_000_000; + let admin = None; + + let create_pool_call = + WestendRuntimeCall::XcmPallet(pallet_xcm::Call::::send { + dest: bx!(VersionedLocation::V4( + xcm::v4::Junction::Parachain(AssetHubWestend::para_id().into()).into() + )), + message: bx!(VersionedXcm::V5(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind: OriginKind::SovereignAccount, + fallback_max_weight: None, + call: AssetHubWestendRuntimeCall::AssetRewards( + pallet_asset_rewards::Call::::create_pool { + staked_asset_id, + reward_asset_id, + reward_rate_per_block, + expiry: DispatchTime::After(lifetime), + admin + } + ) + .encode() + .into(), + } + ]))), + }); + + let treasury_origin: WestendRuntimeOrigin = Treasurer.into(); + assert_ok!(create_pool_call.dispatch(treasury_origin)); + + assert_expected_events!( + Westend, + vec![ + WestendRuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + AssetHubWestend::execute_with(|| { + type Runtime = ::Runtime; + type RuntimeEvent = ::RuntimeEvent; + + assert_eq!(1, pallet_asset_rewards::Pools::::iter().count()); + + let events = AssetHubWestend::events(); + match events.iter().last() { + Some(RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { + success: true, + .. + })) => (), + _ => panic!("Expected MessageQueue::Processed event"), + } + }); +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml index abe59a8439a8..d612dd03c247 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml @@ -30,6 +30,7 @@ frame-try-runtime = { optional = true, workspace = true } pallet-asset-conversion = { workspace = true } pallet-asset-conversion-ops = { workspace = true } pallet-asset-conversion-tx-payment = { workspace = true } +pallet-asset-rewards = { workspace = true } pallet-assets = { workspace = true } pallet-assets-freezer = { workspace = true } pallet-aura = { workspace = true } @@ -61,6 +62,7 @@ sp-storage = { workspace = true } sp-transaction-pool = { workspace = true } sp-version = { workspace = true } sp-weights = { workspace = true } + # num-traits feature needed for dex integer sq root: primitive-types = { features = ["codec", "num-traits", "scale-info"], workspace = true } @@ -123,6 +125,7 @@ runtime-benchmarks = [ "pallet-asset-conversion-ops/runtime-benchmarks", "pallet-asset-conversion-tx-payment/runtime-benchmarks", "pallet-asset-conversion/runtime-benchmarks", + "pallet-asset-rewards/runtime-benchmarks", "pallet-assets-freezer/runtime-benchmarks", "pallet-assets/runtime-benchmarks", "pallet-balances/runtime-benchmarks", @@ -162,6 +165,7 @@ try-runtime = [ "pallet-asset-conversion-ops/try-runtime", "pallet-asset-conversion-tx-payment/try-runtime", "pallet-asset-conversion/try-runtime", + "pallet-asset-rewards/try-runtime", "pallet-assets-freezer/try-runtime", "pallet-assets/try-runtime", "pallet-aura/try-runtime", @@ -212,6 +216,7 @@ std = [ "pallet-asset-conversion-ops/std", "pallet-asset-conversion-tx-payment/std", "pallet-asset-conversion/std", + "pallet-asset-rewards/std", "pallet-assets-freezer/std", "pallet-assets/std", "pallet-aura/std", 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 db9a8201ebbe..43b7bf0ba118 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -35,7 +35,7 @@ use assets_common::{ foreign_creators::ForeignCreators, local_and_foreign_assets::{LocalFromLeft, TargetFromLeft}, matching::{FromNetwork, FromSiblingParachain}, - AssetIdForTrustBackedAssetsConvert, + AssetIdForPoolAssets, AssetIdForPoolAssetsConvert, AssetIdForTrustBackedAssetsConvert, }; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use cumulus_primitives_core::{AggregateMessageOrigin, ClaimQueueOffset, CoreSelector}; @@ -61,9 +61,9 @@ use frame_support::{ genesis_builder_helper::{build_state, get_preset}, ord_parameter_types, parameter_types, traits::{ - fungible, fungibles, tokens::imbalance::ResolveAssetTo, AsEnsureOriginWithArg, ConstBool, - ConstU128, ConstU32, ConstU64, ConstU8, EitherOfDiverse, Equals, InstanceFilter, - TransformOrigin, + fungible, fungible::HoldConsideration, fungibles, tokens::imbalance::ResolveAssetTo, + AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, + ConstantStoragePrice, EitherOfDiverse, Equals, InstanceFilter, TransformOrigin, }, weights::{ConstantMultiplier, Weight, WeightToFee as _}, BoundedVec, PalletId, @@ -84,8 +84,8 @@ use sp_runtime::{Perbill, RuntimeDebug}; use testnet_parachains_constants::rococo::{consensus::*, currency::*, fee::WeightToFee, time::*}; use xcm_config::{ ForeignAssetsConvertedConcreteId, GovernanceLocation, LocationToAccountId, - PoolAssetsConvertedConcreteId, TokenLocation, TrustBackedAssetsConvertedConcreteId, - TrustBackedAssetsPalletLocation, + PoolAssetsConvertedConcreteId, PoolAssetsPalletLocation, TokenLocation, + TrustBackedAssetsConvertedConcreteId, TrustBackedAssetsPalletLocation, }; #[cfg(test)] @@ -111,6 +111,9 @@ use xcm_runtime_apis::{ fees::Error as XcmPaymentApiError, }; +#[cfg(feature = "runtime-benchmarks")] +use frame_support::traits::PalletInfoAccess; + use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; impl_opaque_keys! { @@ -217,8 +220,8 @@ impl pallet_balances::Config for Runtime { type ReserveIdentifier = [u8; 8]; type RuntimeHoldReason = RuntimeHoldReason; type RuntimeFreezeReason = RuntimeFreezeReason; - type FreezeIdentifier = (); - type MaxFreezes = ConstU32<0>; + type FreezeIdentifier = RuntimeFreezeReason; + type MaxFreezes = ConstU32<50>; type DoneSlashHandler = (); } @@ -302,7 +305,7 @@ impl pallet_assets::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Balance = Balance; type RemoveItemsLimit = ConstU32<1000>; - type AssetId = u32; + type AssetId = AssetIdForPoolAssets; type AssetIdParameter = u32; type Currency = Balances; type CreateOrigin = @@ -343,8 +346,21 @@ pub type LocalAndForeignAssets = fungibles::UnionOf< AccountId, >; -/// Union fungibles implementation for [`LocalAndForeignAssets`] and `Balances`. -pub type NativeAndAssets = fungible::UnionOf< +/// Union fungibles implementation for `AssetsFreezer` and `ForeignAssetsFreezer`. +pub type LocalAndForeignAssetsFreezer = fungibles::UnionOf< + AssetsFreezer, + ForeignAssetsFreezer, + LocalFromLeft< + AssetIdForTrustBackedAssetsConvert, + AssetIdForTrustBackedAssets, + xcm::v5::Location, + >, + xcm::v5::Location, + AccountId, +>; + +/// Union fungibles implementation for [`LocalAndForeignAssets`] and [`Balances`]. +pub type NativeAndNonPoolAssets = fungible::UnionOf< Balances, LocalAndForeignAssets, TargetFromLeft, @@ -352,6 +368,45 @@ pub type NativeAndAssets = fungible::UnionOf< AccountId, >; +/// Union fungibles implementation for [`LocalAndForeignAssetsFreezer`] and [`Balances`]. +pub type NativeAndNonPoolAssetsFreezer = fungible::UnionOf< + Balances, + LocalAndForeignAssetsFreezer, + TargetFromLeft, + xcm::v5::Location, + AccountId, +>; + +/// Union fungibles implementation for [`PoolAssets`] and [`NativeAndNonPoolAssets`]. +/// +/// NOTE: Should be kept updated to include ALL balances and assets in the runtime. +pub type NativeAndAllAssets = fungibles::UnionOf< + PoolAssets, + NativeAndNonPoolAssets, + LocalFromLeft< + AssetIdForPoolAssetsConvert, + AssetIdForPoolAssets, + xcm::v5::Location, + >, + xcm::v5::Location, + AccountId, +>; + +/// Union fungibles implementation for [`PoolAssetsFreezer`] and [`NativeAndNonPoolAssetsFreezer`]. +/// +/// NOTE: Should be kept updated to include ALL balances and assets in the runtime. +pub type NativeAndAllAssetsFreezer = fungibles::UnionOf< + PoolAssetsFreezer, + NativeAndNonPoolAssetsFreezer, + LocalFromLeft< + AssetIdForPoolAssetsConvert, + AssetIdForPoolAssets, + xcm::v5::Location, + >, + xcm::v5::Location, + AccountId, +>; + pub type PoolIdToAccountId = pallet_asset_conversion::AccountIdConverter< AssetConversionPalletId, (xcm::v5::Location, xcm::v5::Location), @@ -362,7 +417,7 @@ impl pallet_asset_conversion::Config for Runtime { type Balance = Balance; type HigherPrecisionBalance = sp_core::U256; type AssetKind = xcm::v5::Location; - type Assets = NativeAndAssets; + type Assets = NativeAndNonPoolAssets; type PoolId = (Self::AssetKind, Self::AssetKind); type PoolLocator = pallet_asset_conversion::WithFirstAsset< TokenLocation, @@ -823,9 +878,9 @@ impl pallet_asset_conversion_tx_payment::Config for Runtime { type AssetId = xcm::v5::Location; type OnChargeAssetTransaction = SwapAssetAdapter< TokenLocation, - NativeAndAssets, + NativeAndNonPoolAssets, AssetConversion, - ResolveAssetTo, + ResolveAssetTo, >; type WeightInfo = weights::pallet_asset_conversion_tx_payment::WeightInfo; #[cfg(feature = "runtime-benchmarks")] @@ -953,6 +1008,55 @@ impl pallet_xcm_bridge_hub_router::Config for Runtim type FeeAsset = xcm_config::bridging::XcmBridgeHubRouterFeeAssetId; } +#[cfg(feature = "runtime-benchmarks")] +pub struct PalletAssetRewardsBenchmarkHelper; + +#[cfg(feature = "runtime-benchmarks")] +impl pallet_asset_rewards::benchmarking::BenchmarkHelper + for PalletAssetRewardsBenchmarkHelper +{ + fn staked_asset() -> Location { + Location::new( + 0, + [PalletInstance(::index() as u8), GeneralIndex(100)], + ) + } + fn reward_asset() -> Location { + Location::new( + 0, + [PalletInstance(::index() as u8), GeneralIndex(101)], + ) + } +} + +parameter_types! { + pub const AssetRewardsPalletId: PalletId = PalletId(*b"py/astrd"); + pub const RewardsPoolCreationHoldReason: RuntimeHoldReason = + RuntimeHoldReason::AssetRewards(pallet_asset_rewards::HoldReason::PoolCreation); + // 1 item, 135 bytes into the storage on pool creation. + pub const StakePoolCreationDeposit: Balance = deposit(1, 135); +} + +impl pallet_asset_rewards::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type PalletId = AssetRewardsPalletId; + type Balance = Balance; + type Assets = NativeAndAllAssets; + type AssetsFreezer = NativeAndAllAssetsFreezer; + type AssetId = xcm::v5::Location; + type CreatePoolOrigin = EnsureSigned; + type RuntimeFreezeReason = RuntimeFreezeReason; + type Consideration = HoldConsideration< + AccountId, + Balances, + RewardsPoolCreationHoldReason, + ConstantStoragePrice, + >; + type WeightInfo = weights::pallet_asset_rewards::WeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = PalletAssetRewardsBenchmarkHelper; +} + // Create the runtime by composing the FRAME pallets that were previously configured. construct_runtime!( pub enum Runtime @@ -998,10 +1102,13 @@ construct_runtime!( NftFractionalization: pallet_nft_fractionalization = 54, PoolAssets: pallet_assets:: = 55, AssetConversion: pallet_asset_conversion = 56, + AssetsFreezer: pallet_assets_freezer:: = 57, ForeignAssetsFreezer: pallet_assets_freezer:: = 58, PoolAssetsFreezer: pallet_assets_freezer:: = 59, + AssetRewards: pallet_asset_rewards = 60, + // TODO: the pallet instance should be removed once all pools have migrated // to the new account IDs. AssetConversionMigration: pallet_asset_conversion_ops = 200, @@ -1193,6 +1300,7 @@ mod benches { [pallet_assets, Foreign] [pallet_assets, Pool] [pallet_asset_conversion, AssetConversion] + [pallet_asset_rewards, AssetRewards] [pallet_asset_conversion_tx_payment, AssetTxPayment] [pallet_balances, Balances] [pallet_message_queue, MessageQueue] @@ -1503,6 +1611,12 @@ impl_runtime_apis! { } } + impl pallet_asset_rewards::AssetRewards for Runtime { + fn pool_creation_cost() -> Balance { + StakePoolCreationDeposit::get() + } + } + impl cumulus_primitives_core::GetCoreSelectorApi for Runtime { fn core_selector() -> (CoreSelector, ClaimQueueOffset) { ParachainSystem::core_selector() diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/mod.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/mod.rs index ae78a56d8b3c..6893766ac72d 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/mod.rs @@ -24,6 +24,7 @@ pub mod frame_system_extensions; pub mod pallet_asset_conversion; pub mod pallet_asset_conversion_ops; pub mod pallet_asset_conversion_tx_payment; +pub mod pallet_asset_rewards; pub mod pallet_assets_foreign; pub mod pallet_assets_local; pub mod pallet_assets_pool; diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_asset_rewards.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_asset_rewards.rs new file mode 100644 index 000000000000..218c93c51035 --- /dev/null +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_asset_rewards.rs @@ -0,0 +1,217 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Autogenerated weights for `pallet_asset_rewards` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-rococo-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_asset_rewards +// --chain=asset-hub-rococo-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_asset_rewards`. +pub struct WeightInfo(PhantomData); +impl pallet_asset_rewards::WeightInfo for WeightInfo { + /// Storage: `Assets::Asset` (r:2 w:0) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::NextPoolId` (r:1 w:1) + /// Proof: `AssetRewards::NextPoolId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolCost` (r:0 w:1) + /// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::Pools` (r:0 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + fn create_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `358` + // Estimated: `6360` + // Minimum execution time: 65_882_000 picoseconds. + Weight::from_parts(67_073_000, 0) + .saturating_add(Weight::from_parts(0, 6360)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(5)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:1 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(87), added: 2562, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn stake() -> Weight { + // Proof Size summary in bytes: + // Measured: `872` + // Estimated: `4809` + // Minimum execution time: 56_950_000 picoseconds. + Weight::from_parts(58_088_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:1 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(87), added: 2562, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn unstake() -> Weight { + // Proof Size summary in bytes: + // Measured: `872` + // Estimated: `4809` + // Minimum execution time: 59_509_000 picoseconds. + Weight::from_parts(61_064_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:0) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:0) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn harvest_rewards() -> Weight { + // Proof Size summary in bytes: + // Measured: `1072` + // Estimated: `6208` + // Minimum execution time: 80_685_000 picoseconds. + Weight::from_parts(83_505_000, 0) + .saturating_add(Weight::from_parts(0, 6208)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + fn set_pool_reward_rate_per_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `318` + // Estimated: `4809` + // Minimum execution time: 17_032_000 picoseconds. + Weight::from_parts(17_628_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + fn set_pool_admin() -> Weight { + // Proof Size summary in bytes: + // Measured: `318` + // Estimated: `4809` + // Minimum execution time: 15_290_000 picoseconds. + Weight::from_parts(16_212_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + fn set_pool_expiry_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `318` + // Estimated: `4809` + // Minimum execution time: 17_721_000 picoseconds. + Weight::from_parts(18_603_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:0) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:0) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn deposit_reward_tokens() -> Weight { + // Proof Size summary in bytes: + // Measured: `747` + // Estimated: `6208` + // Minimum execution time: 67_754_000 picoseconds. + Weight::from_parts(69_428_000, 0) + .saturating_add(Weight::from_parts(0, 6208)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:0) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolCost` (r:1 w:1) + /// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:0 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(87), added: 2562, mode: `MaxEncodedLen`) + fn cleanup_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `1105` + // Estimated: `6208` + // Minimum execution time: 127_524_000 picoseconds. + Weight::from_parts(130_238_000, 0) + .saturating_add(Weight::from_parts(0, 6208)) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().writes(10)) + } +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs index 08b2f520c4b9..0c6ff5e4bfdd 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs @@ -76,6 +76,10 @@ parameter_types! { pub TrustBackedAssetsPalletLocation: Location = PalletInstance(TrustBackedAssetsPalletIndex::get()).into(); pub TrustBackedAssetsPalletIndex: u8 = ::index() as u8; + pub TrustBackedAssetsPalletLocationV3: xcm::v3::Location = + xcm::v3::Junction::PalletInstance(::index() as u8).into(); + pub PoolAssetsPalletLocationV3: xcm::v3::Location = + xcm::v3::Junction::PalletInstance(::index() as u8).into(); pub ForeignAssetsPalletLocation: Location = PalletInstance(::index() as u8).into(); pub PoolAssetsPalletLocation: Location = @@ -336,7 +340,7 @@ pub type TrustedTeleporters = ( /// asset and the asset required for fee payment. pub type PoolAssetsExchanger = SingleAssetExchangeAdapter< crate::AssetConversion, - crate::NativeAndAssets, + crate::NativeAndNonPoolAssets, ( TrustBackedAssetsAsLocation, ForeignAssetsConvertedConcreteId, @@ -387,7 +391,7 @@ impl xcm_executor::Config for XcmConfig { TokenLocation, crate::AssetConversion, WeightToFee, - crate::NativeAndAssets, + crate::NativeAndNonPoolAssets, ( TrustBackedAssetsAsLocation< TrustBackedAssetsPalletLocation, @@ -396,7 +400,7 @@ impl xcm_executor::Config for XcmConfig { >, ForeignAssetsConvertedConcreteId, ), - ResolveAssetTo, + ResolveAssetTo, AccountId, >, // This trader allows to pay with `is_sufficient=true` "Trust Backed" assets from dedicated diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml index cb10ae9a4800..65ef63a7fb35 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml @@ -30,6 +30,7 @@ frame-try-runtime = { optional = true, workspace = true } pallet-asset-conversion = { workspace = true } pallet-asset-conversion-ops = { workspace = true } pallet-asset-conversion-tx-payment = { workspace = true } +pallet-asset-rewards = { workspace = true } pallet-assets = { workspace = true } pallet-assets-freezer = { workspace = true } pallet-aura = { workspace = true } @@ -62,6 +63,7 @@ sp-std = { workspace = true } sp-storage = { workspace = true } sp-transaction-pool = { workspace = true } sp-version = { workspace = true } + # num-traits feature needed for dex integer sq root: primitive-types = { features = ["codec", "num-traits", "scale-info"], workspace = true } @@ -125,6 +127,7 @@ runtime-benchmarks = [ "pallet-asset-conversion-ops/runtime-benchmarks", "pallet-asset-conversion-tx-payment/runtime-benchmarks", "pallet-asset-conversion/runtime-benchmarks", + "pallet-asset-rewards/runtime-benchmarks", "pallet-assets-freezer/runtime-benchmarks", "pallet-assets/runtime-benchmarks", "pallet-balances/runtime-benchmarks", @@ -166,6 +169,7 @@ try-runtime = [ "pallet-asset-conversion-ops/try-runtime", "pallet-asset-conversion-tx-payment/try-runtime", "pallet-asset-conversion/try-runtime", + "pallet-asset-rewards/try-runtime", "pallet-assets-freezer/try-runtime", "pallet-assets/try-runtime", "pallet-aura/try-runtime", @@ -218,6 +222,7 @@ std = [ "pallet-asset-conversion-ops/std", "pallet-asset-conversion-tx-payment/std", "pallet-asset-conversion/std", + "pallet-asset-rewards/std", "pallet-assets-freezer/std", "pallet-assets/std", "pallet-aura/std", 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 5966dd01f18f..3ef5e87f24c4 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -33,7 +33,7 @@ extern crate alloc; use alloc::{vec, vec::Vec}; use assets_common::{ local_and_foreign_assets::{LocalFromLeft, TargetFromLeft}, - AssetIdForTrustBackedAssetsConvert, + AssetIdForPoolAssets, AssetIdForPoolAssetsConvert, AssetIdForTrustBackedAssetsConvert, }; use codec::{Decode, Encode, MaxEncodedLen}; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; @@ -44,10 +44,12 @@ use frame_support::{ genesis_builder_helper::{build_state, get_preset}, ord_parameter_types, parameter_types, traits::{ - fungible, fungibles, + fungible, + fungible::HoldConsideration, + fungibles, tokens::{imbalance::ResolveAssetTo, nonfungibles_v2::Inspect}, - AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, Equals, - InstanceFilter, Nothing, TransformOrigin, + AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, + ConstantStoragePrice, Equals, InstanceFilter, Nothing, TransformOrigin, }, weights::{ConstantMultiplier, Weight, WeightToFee as _}, BoundedVec, PalletId, @@ -81,8 +83,8 @@ use testnet_parachains_constants::westend::{ }; use xcm_config::{ ForeignAssetsConvertedConcreteId, LocationToAccountId, PoolAssetsConvertedConcreteId, - TrustBackedAssetsConvertedConcreteId, TrustBackedAssetsPalletLocation, WestendLocation, - XcmOriginToTransactDispatchOrigin, + PoolAssetsPalletLocation, TrustBackedAssetsConvertedConcreteId, + TrustBackedAssetsPalletLocation, WestendLocation, XcmOriginToTransactDispatchOrigin, }; #[cfg(any(feature = "std", test))] @@ -93,11 +95,15 @@ use assets_common::{ matching::{FromNetwork, FromSiblingParachain}, }; use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; +use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; use xcm::{ latest::prelude::AssetId, prelude::{VersionedAsset, VersionedAssetId, VersionedAssets, VersionedLocation, VersionedXcm}, }; +#[cfg(feature = "runtime-benchmarks")] +use frame_support::traits::PalletInfoAccess; + #[cfg(feature = "runtime-benchmarks")] use xcm::latest::prelude::{ Asset, Assets as XcmAssets, Fungible, Here, InteriorLocation, Junction, Junction::*, Location, @@ -109,8 +115,6 @@ use xcm_runtime_apis::{ fees::Error as XcmPaymentApiError, }; -use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; - impl_opaque_keys! { pub struct SessionKeys { pub aura: Aura, @@ -218,8 +222,8 @@ impl pallet_balances::Config for Runtime { type ReserveIdentifier = [u8; 8]; type RuntimeHoldReason = RuntimeHoldReason; type RuntimeFreezeReason = RuntimeFreezeReason; - type FreezeIdentifier = (); - type MaxFreezes = ConstU32<0>; + type FreezeIdentifier = RuntimeFreezeReason; + type MaxFreezes = ConstU32<50>; type DoneSlashHandler = (); } @@ -341,8 +345,22 @@ pub type LocalAndForeignAssets = fungibles::UnionOf< xcm::v5::Location, AccountId, >; + +/// Union fungibles implementation for `AssetsFreezer` and `ForeignAssetsFreezer`. +pub type LocalAndForeignAssetsFreezer = fungibles::UnionOf< + AssetsFreezer, + ForeignAssetsFreezer, + LocalFromLeft< + AssetIdForTrustBackedAssetsConvert, + AssetIdForTrustBackedAssets, + xcm::v5::Location, + >, + xcm::v5::Location, + AccountId, +>; + /// Union fungibles implementation for [`LocalAndForeignAssets`] and `Balances`. -pub type NativeAndAssets = fungible::UnionOf< +pub type NativeAndNonPoolAssets = fungible::UnionOf< Balances, LocalAndForeignAssets, TargetFromLeft, @@ -350,6 +368,45 @@ pub type NativeAndAssets = fungible::UnionOf< AccountId, >; +/// Union fungibles implementation for [`LocalAndForeignAssetsFreezer`] and [`Balances`]. +pub type NativeAndNonPoolAssetsFreezer = fungible::UnionOf< + Balances, + LocalAndForeignAssetsFreezer, + TargetFromLeft, + xcm::v5::Location, + AccountId, +>; + +/// Union fungibles implementation for [`PoolAssets`] and [`NativeAndNonPoolAssets`]. +/// +/// NOTE: Should be kept updated to include ALL balances and assets in the runtime. +pub type NativeAndAllAssets = fungibles::UnionOf< + PoolAssets, + NativeAndNonPoolAssets, + LocalFromLeft< + AssetIdForPoolAssetsConvert, + AssetIdForPoolAssets, + xcm::v5::Location, + >, + xcm::v5::Location, + AccountId, +>; + +/// Union fungibles implementation for [`PoolAssetsFreezer`] and [`NativeAndNonPoolAssetsFreezer`]. +/// +/// NOTE: Should be kept updated to include ALL balances and assets in the runtime. +pub type NativeAndAllAssetsFreezer = fungibles::UnionOf< + PoolAssetsFreezer, + NativeAndNonPoolAssetsFreezer, + LocalFromLeft< + AssetIdForPoolAssetsConvert, + AssetIdForPoolAssets, + xcm::v5::Location, + >, + xcm::v5::Location, + AccountId, +>; + pub type PoolIdToAccountId = pallet_asset_conversion::AccountIdConverter< AssetConversionPalletId, (xcm::v5::Location, xcm::v5::Location), @@ -360,7 +417,7 @@ impl pallet_asset_conversion::Config for Runtime { type Balance = Balance; type HigherPrecisionBalance = sp_core::U256; type AssetKind = xcm::v5::Location; - type Assets = NativeAndAssets; + type Assets = NativeAndNonPoolAssets; type PoolId = (Self::AssetKind, Self::AssetKind); type PoolLocator = pallet_asset_conversion::WithFirstAsset< WestendLocation, @@ -388,6 +445,55 @@ impl pallet_asset_conversion::Config for Runtime { >; } +#[cfg(feature = "runtime-benchmarks")] +pub struct PalletAssetRewardsBenchmarkHelper; + +#[cfg(feature = "runtime-benchmarks")] +impl pallet_asset_rewards::benchmarking::BenchmarkHelper + for PalletAssetRewardsBenchmarkHelper +{ + fn staked_asset() -> Location { + Location::new( + 0, + [PalletInstance(::index() as u8), GeneralIndex(100)], + ) + } + fn reward_asset() -> Location { + Location::new( + 0, + [PalletInstance(::index() as u8), GeneralIndex(101)], + ) + } +} + +parameter_types! { + pub const AssetRewardsPalletId: PalletId = PalletId(*b"py/astrd"); + pub const RewardsPoolCreationHoldReason: RuntimeHoldReason = + RuntimeHoldReason::AssetRewards(pallet_asset_rewards::HoldReason::PoolCreation); + // 1 item, 135 bytes into the storage on pool creation. + pub const StakePoolCreationDeposit: Balance = deposit(1, 135); +} + +impl pallet_asset_rewards::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type PalletId = AssetRewardsPalletId; + type Balance = Balance; + type Assets = NativeAndAllAssets; + type AssetsFreezer = NativeAndAllAssetsFreezer; + type AssetId = xcm::v5::Location; + type CreatePoolOrigin = EnsureSigned; + type RuntimeFreezeReason = RuntimeFreezeReason; + type Consideration = HoldConsideration< + AccountId, + Balances, + RewardsPoolCreationHoldReason, + ConstantStoragePrice, + >; + type WeightInfo = weights::pallet_asset_rewards::WeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = PalletAssetRewardsBenchmarkHelper; +} + impl pallet_asset_conversion_ops::Config for Runtime { type RuntimeEvent = RuntimeEvent; type PriorAccountIdConverter = pallet_asset_conversion::AccountIdConverterNoSeed< @@ -816,9 +922,9 @@ impl pallet_asset_conversion_tx_payment::Config for Runtime { type AssetId = xcm::v5::Location; type OnChargeAssetTransaction = SwapAssetAdapter< WestendLocation, - NativeAndAssets, + NativeAndNonPoolAssets, AssetConversion, - ResolveAssetTo, + ResolveAssetTo, >; type WeightInfo = weights::pallet_asset_conversion_tx_payment::WeightInfo; #[cfg(feature = "runtime-benchmarks")] @@ -1035,11 +1141,14 @@ construct_runtime!( NftFractionalization: pallet_nft_fractionalization = 54, PoolAssets: pallet_assets:: = 55, AssetConversion: pallet_asset_conversion = 56, + AssetsFreezer: pallet_assets_freezer:: = 57, ForeignAssetsFreezer: pallet_assets_freezer:: = 58, PoolAssetsFreezer: pallet_assets_freezer:: = 59, Revive: pallet_revive = 60, + AssetRewards: pallet_asset_rewards = 61, + StateTrieMigration: pallet_state_trie_migration = 70, // TODO: the pallet instance should be removed once all pools have migrated @@ -1317,6 +1426,7 @@ mod benches { [pallet_assets, Foreign] [pallet_assets, Pool] [pallet_asset_conversion, AssetConversion] + [pallet_asset_rewards, AssetRewards] [pallet_asset_conversion_tx_payment, AssetTxPayment] [pallet_balances, Balances] [pallet_message_queue, MessageQueue] @@ -1674,6 +1784,12 @@ impl_runtime_apis! { } } + impl pallet_asset_rewards::AssetRewards for Runtime { + fn pool_creation_cost() -> Balance { + StakePoolCreationDeposit::get() + } + } + impl cumulus_primitives_core::GetCoreSelectorApi for Runtime { fn core_selector() -> (CoreSelector, ClaimQueueOffset) { ParachainSystem::core_selector() diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/mod.rs index 442b58635f48..d653838ad80e 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/mod.rs @@ -23,6 +23,7 @@ pub mod frame_system_extensions; pub mod pallet_asset_conversion; pub mod pallet_asset_conversion_ops; pub mod pallet_asset_conversion_tx_payment; +pub mod pallet_asset_rewards; pub mod pallet_assets_foreign; pub mod pallet_assets_local; pub mod pallet_assets_pool; diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_asset_rewards.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_asset_rewards.rs new file mode 100644 index 000000000000..3bbc289fec7b --- /dev/null +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_asset_rewards.rs @@ -0,0 +1,217 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Autogenerated weights for `pallet_asset_rewards` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-westend-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_asset_rewards +// --chain=asset-hub-westend-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_asset_rewards`. +pub struct WeightInfo(PhantomData); +impl pallet_asset_rewards::WeightInfo for WeightInfo { + /// Storage: `Assets::Asset` (r:2 w:0) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::NextPoolId` (r:1 w:1) + /// Proof: `AssetRewards::NextPoolId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(157), added: 2632, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolCost` (r:0 w:1) + /// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::Pools` (r:0 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + fn create_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `392` + // Estimated: `6360` + // Minimum execution time: 60_734_000 picoseconds. + Weight::from_parts(61_828_000, 0) + .saturating_add(Weight::from_parts(0, 6360)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(5)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:1 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(87), added: 2562, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn stake() -> Weight { + // Proof Size summary in bytes: + // Measured: `906` + // Estimated: `4809` + // Minimum execution time: 56_014_000 picoseconds. + Weight::from_parts(58_487_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:1 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(87), added: 2562, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn unstake() -> Weight { + // Proof Size summary in bytes: + // Measured: `906` + // Estimated: `4809` + // Minimum execution time: 59_071_000 picoseconds. + Weight::from_parts(60_631_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:0) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:0) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn harvest_rewards() -> Weight { + // Proof Size summary in bytes: + // Measured: `1106` + // Estimated: `6208` + // Minimum execution time: 80_585_000 picoseconds. + Weight::from_parts(82_186_000, 0) + .saturating_add(Weight::from_parts(0, 6208)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + fn set_pool_reward_rate_per_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `318` + // Estimated: `4809` + // Minimum execution time: 17_083_000 picoseconds. + Weight::from_parts(17_816_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + fn set_pool_admin() -> Weight { + // Proof Size summary in bytes: + // Measured: `318` + // Estimated: `4809` + // Minimum execution time: 15_269_000 picoseconds. + Weight::from_parts(15_881_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + fn set_pool_expiry_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `318` + // Estimated: `4809` + // Minimum execution time: 17_482_000 picoseconds. + Weight::from_parts(18_124_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:0) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:0) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn deposit_reward_tokens() -> Weight { + // Proof Size summary in bytes: + // Measured: `781` + // Estimated: `6208` + // Minimum execution time: 66_644_000 picoseconds. + Weight::from_parts(67_950_000, 0) + .saturating_add(Weight::from_parts(0, 6208)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:0) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolCost` (r:1 w:1) + /// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(157), added: 2632, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:0 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(87), added: 2562, mode: `MaxEncodedLen`) + fn cleanup_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `1139` + // Estimated: `6208` + // Minimum execution time: 124_136_000 picoseconds. + Weight::from_parts(128_642_000, 0) + .saturating_add(Weight::from_parts(0, 6208)) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().writes(10)) + } +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs index b4e938f1f8b5..1ea2ce5136ab 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs @@ -65,6 +65,7 @@ use xcm_executor::XcmExecutor; parameter_types! { pub const RootLocation: Location = Location::here(); pub const WestendLocation: Location = Location::parent(); + pub const GovernanceLocation: Location = Location::parent(); pub const RelayNetwork: Option = Some(NetworkId::ByGenesis(WESTEND_GENESIS_HASH)); pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); pub UniversalLocation: InteriorLocation = @@ -359,7 +360,7 @@ pub type TrustedTeleporters = ( /// asset and the asset required for fee payment. pub type PoolAssetsExchanger = SingleAssetExchangeAdapter< crate::AssetConversion, - crate::NativeAndAssets, + crate::NativeAndNonPoolAssets, ( TrustBackedAssetsAsLocation, ForeignAssetsConvertedConcreteId, @@ -409,7 +410,7 @@ impl xcm_executor::Config for XcmConfig { WestendLocation, crate::AssetConversion, WeightToFee, - crate::NativeAndAssets, + crate::NativeAndNonPoolAssets, ( TrustBackedAssetsAsLocation< TrustBackedAssetsPalletLocation, @@ -418,7 +419,7 @@ impl xcm_executor::Config for XcmConfig { >, ForeignAssetsConvertedConcreteId, ), - ResolveAssetTo, + ResolveAssetTo, AccountId, >, // This trader allows to pay with `is_sufficient=true` "Trust Backed" assets from dedicated diff --git a/cumulus/parachains/runtimes/assets/common/src/lib.rs b/cumulus/parachains/runtimes/assets/common/src/lib.rs index 25c2df6b68d1..50b1b63146bc 100644 --- a/cumulus/parachains/runtimes/assets/common/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/common/src/lib.rs @@ -123,10 +123,11 @@ pub type ForeignAssetsConvertedConcreteId< BalanceConverter, >; -type AssetIdForPoolAssets = u32; +pub type AssetIdForPoolAssets = u32; + /// `Location` vs `AssetIdForPoolAssets` converter for `PoolAssets`. -pub type AssetIdForPoolAssetsConvert = - AsPrefixedGeneralIndex; +pub type AssetIdForPoolAssetsConvert = + AsPrefixedGeneralIndex; /// [`MatchedConvertedConcreteId`] converter dedicated for `PoolAssets` pub type PoolAssetsConvertedConcreteId = MatchedConvertedConcreteId< diff --git a/polkadot/runtime/rococo/src/xcm_config.rs b/polkadot/runtime/rococo/src/xcm_config.rs index bb77ec0000e5..10c3f6c0cbfc 100644 --- a/polkadot/runtime/rococo/src/xcm_config.rs +++ b/polkadot/runtime/rococo/src/xcm_config.rs @@ -18,7 +18,8 @@ use super::{ parachains_origin, AccountId, AllPalletsWithSystem, Balances, Dmp, Fellows, ParaId, Runtime, - RuntimeCall, RuntimeEvent, RuntimeOrigin, TransactionByteFee, Treasury, WeightToFee, XcmPallet, + RuntimeCall, RuntimeEvent, RuntimeOrigin, TransactionByteFee, Treasurer, Treasury, WeightToFee, + XcmPallet, }; use crate::governance::StakingAdmin; @@ -228,11 +229,14 @@ impl xcm_executor::Config for XcmConfig { } parameter_types! { + /// Collective pluralistic body. pub const CollectiveBodyId: BodyId = BodyId::Unit; - // StakingAdmin pluralistic body. + /// StakingAdmin pluralistic body. pub const StakingAdminBodyId: BodyId = BodyId::Defense; - // Fellows pluralistic body. + /// Fellows pluralistic body. pub const FellowsBodyId: BodyId = BodyId::Technical; + /// Treasury pluralistic body. + pub const TreasuryBodyId: BodyId = BodyId::Treasury; } /// Type to convert an `Origin` type value into a `Location` value which represents an interior @@ -249,6 +253,9 @@ pub type StakingAdminToPlurality = /// Type to convert the Fellows origin to a Plurality `Location` value. pub type FellowsToPlurality = OriginToPluralityVoice; +/// Type to convert the Treasury origin to a Plurality `Location` value. +pub type TreasurerToPlurality = OriginToPluralityVoice; + /// Type to convert a pallet `Origin` type value into a `Location` value which represents an /// interior location of this chain for a destination chain. pub type LocalPalletOriginToLocation = ( @@ -256,13 +263,18 @@ pub type LocalPalletOriginToLocation = ( StakingAdminToPlurality, // Fellows origin to be used in XCM as a corresponding Plurality `Location` value. FellowsToPlurality, + // Treasurer origin to be used in XCM as a corresponding Plurality `Location` value. + TreasurerToPlurality, ); impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; // Note that this configuration of `SendXcmOrigin` is different from the one present in // production. - type SendXcmOrigin = xcm_builder::EnsureXcmOrigin; + type SendXcmOrigin = xcm_builder::EnsureXcmOrigin< + RuntimeOrigin, + (LocalPalletOriginToLocation, LocalOriginToLocation), + >; type XcmRouter = XcmRouter; // Anyone can execute XCM messages locally. type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin; diff --git a/polkadot/runtime/westend/src/xcm_config.rs b/polkadot/runtime/westend/src/xcm_config.rs index 3f6a7304c8a9..4235edf82b24 100644 --- a/polkadot/runtime/westend/src/xcm_config.rs +++ b/polkadot/runtime/westend/src/xcm_config.rs @@ -280,7 +280,10 @@ impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; // Note that this configuration of `SendXcmOrigin` is different from the one present in // production. - type SendXcmOrigin = xcm_builder::EnsureXcmOrigin; + type SendXcmOrigin = xcm_builder::EnsureXcmOrigin< + RuntimeOrigin, + (LocalPalletOriginToLocation, LocalOriginToLocation), + >; type XcmRouter = XcmRouter; // Anyone can execute XCM messages locally. type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin; diff --git a/prdoc/pr_3926.prdoc b/prdoc/pr_3926.prdoc new file mode 100644 index 000000000000..7f352f7a45fb --- /dev/null +++ b/prdoc/pr_3926.prdoc @@ -0,0 +1,30 @@ +title: Introduce pallet-asset-rewards + +doc: + - audience: Runtime Dev + description: | + Introduce pallet-asset-rewards, which allows accounts to be rewarded for freezing fungible + tokens. The motivation for creating this pallet is to allow incentivising LPs. + See the pallet docs for more info about the pallet. + +crates: + - name: pallet-asset-rewards + bump: major + - name: polkadot-sdk + bump: minor + - name: kitchensink-runtime + bump: major + - name: asset-hub-rococo-runtime + bump: major + - name: asset-hub-westend-runtime + bump: major + - name: assets-common + bump: minor + - name: rococo-runtime + bump: minor + - name: westend-runtime + bump: patch + - name: frame-support + bump: minor + - name: emulated-integration-tests-common + bump: minor diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 97728f12f5f9..de377a55bc88 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -56,10 +56,10 @@ use frame_support::{ imbalance::ResolveAssetTo, nonfungibles_v2::Inspect, pay::PayAssetFromAccount, GetSalary, PayFromAccount, }, - AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, ConstU64, Contains, - Currency, EitherOfDiverse, EnsureOriginWithArg, EqualPrivilegeOnly, Imbalance, InsideBoth, - InstanceFilter, KeyOwnerProofSystem, LinearStoragePrice, LockIdentifier, Nothing, - OnUnbalanced, VariantCountOf, WithdrawReasons, + AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, ConstU64, + ConstantStoragePrice, Contains, Currency, EitherOfDiverse, EnsureOriginWithArg, + EqualPrivilegeOnly, Imbalance, InsideBoth, InstanceFilter, KeyOwnerProofSystem, + LinearStoragePrice, LockIdentifier, Nothing, OnUnbalanced, VariantCountOf, WithdrawReasons, }, weights::{ constants::{ @@ -511,7 +511,8 @@ impl pallet_glutton::Config for Runtime { } parameter_types! { - pub const PreimageHoldReason: RuntimeHoldReason = RuntimeHoldReason::Preimage(pallet_preimage::HoldReason::Preimage); + pub const PreimageHoldReason: RuntimeHoldReason = + RuntimeHoldReason::Preimage(pallet_preimage::HoldReason::Preimage); } impl pallet_preimage::Config for Runtime { @@ -618,6 +619,12 @@ impl pallet_transaction_payment::Config for Runtime { type WeightInfo = pallet_transaction_payment::weights::SubstrateWeight; } +pub type AssetsFreezerInstance = pallet_assets_freezer::Instance1; +impl pallet_assets_freezer::Config for Runtime { + type RuntimeFreezeReason = RuntimeFreezeReason; + type RuntimeEvent = RuntimeEvent; +} + impl pallet_asset_conversion_tx_payment::Config for Runtime { type RuntimeEvent = RuntimeEvent; type AssetId = NativeOrWithId; @@ -1858,6 +1865,53 @@ impl pallet_asset_conversion::Config for Runtime { type BenchmarkHelper = (); } +pub type NativeAndAssetsFreezer = + UnionOf, AccountId>; + +/// Benchmark Helper +#[cfg(feature = "runtime-benchmarks")] +pub struct AssetRewardsBenchmarkHelper; + +#[cfg(feature = "runtime-benchmarks")] +impl pallet_asset_rewards::benchmarking::BenchmarkHelper> + for AssetRewardsBenchmarkHelper +{ + fn staked_asset() -> NativeOrWithId { + NativeOrWithId::::WithId(100) + } + fn reward_asset() -> NativeOrWithId { + NativeOrWithId::::WithId(101) + } +} + +parameter_types! { + pub const StakingRewardsPalletId: PalletId = PalletId(*b"py/stkrd"); + pub const CreationHoldReason: RuntimeHoldReason = + RuntimeHoldReason::AssetRewards(pallet_asset_rewards::HoldReason::PoolCreation); + // 1 item, 135 bytes into the storage on pool creation. + pub const StakePoolCreationDeposit: Balance = deposit(1, 135); +} + +impl pallet_asset_rewards::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeFreezeReason = RuntimeFreezeReason; + type AssetId = NativeOrWithId; + type Balance = Balance; + type Assets = NativeAndAssets; + type PalletId = StakingRewardsPalletId; + type CreatePoolOrigin = EnsureSigned; + type WeightInfo = (); + type AssetsFreezer = NativeAndAssetsFreezer; + type Consideration = HoldConsideration< + AccountId, + Balances, + CreationHoldReason, + ConstantStoragePrice, + >; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = AssetRewardsBenchmarkHelper; +} + impl pallet_asset_conversion_ops::Config for Runtime { type RuntimeEvent = RuntimeEvent; type PriorAccountIdConverter = pallet_asset_conversion::AccountIdConverterNoSeed<( @@ -2636,6 +2690,12 @@ mod runtime { #[runtime::pallet_index(81)] pub type VerifySignature = pallet_verify_signature::Pallet; + + #[runtime::pallet_index(83)] + pub type AssetRewards = pallet_asset_rewards::Pallet; + + #[runtime::pallet_index(84)] + pub type AssetsFreezer = pallet_assets_freezer::Pallet; } impl TryFrom for pallet_revive::Call { @@ -2846,6 +2906,7 @@ mod benches { [pallet_example_tasks, TasksExample] [pallet_democracy, Democracy] [pallet_asset_conversion, AssetConversion] + [pallet_asset_rewards, AssetRewards] [pallet_asset_conversion_tx_payment, AssetConversionTxPayment] [pallet_transaction_payment, TransactionPayment] [pallet_election_provider_multi_phase, ElectionProviderMultiPhase] @@ -3553,6 +3614,12 @@ impl_runtime_apis! { } } + impl pallet_asset_rewards::AssetRewards for Runtime { + fn pool_creation_cost() -> Balance { + StakePoolCreationDeposit::get() + } + } + #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime for Runtime { fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { diff --git a/substrate/frame/asset-rewards/Cargo.toml b/substrate/frame/asset-rewards/Cargo.toml new file mode 100644 index 000000000000..a03fa17cf0dc --- /dev/null +++ b/substrate/frame/asset-rewards/Cargo.toml @@ -0,0 +1,71 @@ +[package] +name = "pallet-asset-rewards" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true +description = "FRAME asset rewards pallet" + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true } +frame-benchmarking = { workspace = true, optional = true } +frame-support = { workspace = true, features = ["experimental"] } +frame-system = { workspace = true } +scale-info = { workspace = true, features = ["derive"] } +sp-api = { workspace = true } +sp-arithmetic = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } + +[dev-dependencies] +pallet-assets = { workspace = true } +pallet-assets-freezer = { workspace = true } +pallet-balances = { workspace = true } +primitive-types = { workspace = true, features = ["codec", "num-traits", "scale-info"] } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "pallet-assets-freezer/std", + "pallet-assets/std", + "pallet-balances/std", + "primitive-types/std", + "scale-info/std", + "sp-api/std", + "sp-arithmetic/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-assets-freezer/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-assets-freezer/try-runtime", + "pallet-assets/try-runtime", + "pallet-balances/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/substrate/frame/asset-rewards/src/benchmarking.rs b/substrate/frame/asset-rewards/src/benchmarking.rs new file mode 100644 index 000000000000..5605804dd20e --- /dev/null +++ b/substrate/frame/asset-rewards/src/benchmarking.rs @@ -0,0 +1,355 @@ +// 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. + +//! Asset Rewards pallet benchmarking. + +use super::*; +use crate::Pallet as AssetRewards; +use frame_benchmarking::{v2::*, whitelisted_caller, BenchmarkError}; +use frame_support::{ + assert_ok, + traits::{ + fungibles::{Create, Inspect, Mutate}, + Consideration, EnsureOrigin, Footprint, + }, +}; +use frame_system::{pallet_prelude::BlockNumberFor, Pallet as System, RawOrigin}; +use sp_runtime::{traits::One, Saturating}; +use sp_std::prelude::*; + +/// Benchmark Helper +pub trait BenchmarkHelper { + /// Returns the staked asset id. + /// + /// If the asset does not exist, it will be created by the benchmark. + fn staked_asset() -> AssetId; + /// Returns the reward asset id. + /// + /// If the asset does not exist, it will be created by the benchmark. + fn reward_asset() -> AssetId; +} + +fn pool_expire() -> DispatchTime> { + DispatchTime::At(BlockNumberFor::::from(100u32)) +} + +fn create_reward_pool() -> Result +where + T::Assets: Create + Mutate, +{ + let caller_origin = + T::CreatePoolOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + let caller = T::CreatePoolOrigin::ensure_origin(caller_origin.clone()).unwrap(); + + let footprint = Footprint::from_mel::<(PoolId, PoolInfoFor)>(); + T::Consideration::ensure_successful(&caller, footprint); + + let staked_asset = T::BenchmarkHelper::staked_asset(); + let reward_asset = T::BenchmarkHelper::reward_asset(); + + let min_staked_balance = + T::Assets::minimum_balance(staked_asset.clone()).max(T::Balance::one()); + if !T::Assets::asset_exists(staked_asset.clone()) { + assert_ok!(T::Assets::create( + staked_asset.clone(), + caller.clone(), + true, + min_staked_balance + )); + } + let min_reward_balance = + T::Assets::minimum_balance(reward_asset.clone()).max(T::Balance::one()); + if !T::Assets::asset_exists(reward_asset.clone()) { + assert_ok!(T::Assets::create( + reward_asset.clone(), + caller.clone(), + true, + min_reward_balance + )); + } + + assert_ok!(AssetRewards::::create_pool( + caller_origin.clone(), + Box::new(staked_asset), + Box::new(reward_asset), + // reward rate per block + min_reward_balance, + pool_expire::(), + Some(caller), + )); + + Ok(caller_origin) +} + +fn mint_into(caller: &T::AccountId, asset: &T::AssetId) -> T::Balance +where + T::Assets: Mutate, +{ + let min_balance = T::Assets::minimum_balance(asset.clone()); + assert_ok!(T::Assets::mint_into( + asset.clone(), + &caller, + min_balance.saturating_mul(10u32.into()) + )); + min_balance +} + +fn assert_last_event(generic_event: ::RuntimeEvent) { + System::::assert_last_event(generic_event.into()); +} + +#[benchmarks(where T::Assets: Create + Mutate)] +mod benchmarks { + use super::*; + + #[benchmark] + fn create_pool() -> Result<(), BenchmarkError> { + let caller_origin = + T::CreatePoolOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + let caller = T::CreatePoolOrigin::ensure_origin(caller_origin.clone()).unwrap(); + + let footprint = Footprint::from_mel::<(PoolId, PoolInfoFor)>(); + T::Consideration::ensure_successful(&caller, footprint); + + let staked_asset = T::BenchmarkHelper::staked_asset(); + let reward_asset = T::BenchmarkHelper::reward_asset(); + + let min_balance = T::Assets::minimum_balance(staked_asset.clone()).max(T::Balance::one()); + if !T::Assets::asset_exists(staked_asset.clone()) { + assert_ok!(T::Assets::create(staked_asset.clone(), caller.clone(), true, min_balance)); + } + let min_balance = T::Assets::minimum_balance(reward_asset.clone()).max(T::Balance::one()); + if !T::Assets::asset_exists(reward_asset.clone()) { + assert_ok!(T::Assets::create(reward_asset.clone(), caller.clone(), true, min_balance)); + } + + #[extrinsic_call] + _( + caller_origin as T::RuntimeOrigin, + Box::new(staked_asset.clone()), + Box::new(reward_asset.clone()), + min_balance, + pool_expire::(), + Some(caller.clone()), + ); + + assert_last_event::( + Event::PoolCreated { + creator: caller.clone(), + admin: caller, + staked_asset_id: staked_asset, + reward_asset_id: reward_asset, + reward_rate_per_block: min_balance, + expiry_block: pool_expire::().evaluate(System::::block_number()), + pool_id: 0, + } + .into(), + ); + + Ok(()) + } + + #[benchmark] + fn stake() -> Result<(), BenchmarkError> { + create_reward_pool::()?; + + let staker: T::AccountId = whitelisted_caller(); + let min_balance = mint_into::(&staker, &T::BenchmarkHelper::staked_asset()); + + // stake first to get worth case benchmark. + assert_ok!(AssetRewards::::stake( + RawOrigin::Signed(staker.clone()).into(), + 0, + min_balance + )); + + #[extrinsic_call] + _(RawOrigin::Signed(staker.clone()), 0, min_balance); + + assert_last_event::(Event::Staked { staker, pool_id: 0, amount: min_balance }.into()); + + Ok(()) + } + + #[benchmark] + fn unstake() -> Result<(), BenchmarkError> { + create_reward_pool::()?; + + let staker: T::AccountId = whitelisted_caller(); + let min_balance = mint_into::(&staker, &T::BenchmarkHelper::staked_asset()); + + assert_ok!(AssetRewards::::stake( + RawOrigin::Signed(staker.clone()).into(), + 0, + min_balance, + )); + + #[extrinsic_call] + _(RawOrigin::Signed(staker.clone()), 0, min_balance, None); + + assert_last_event::( + Event::Unstaked { caller: staker.clone(), staker, pool_id: 0, amount: min_balance } + .into(), + ); + + Ok(()) + } + + #[benchmark] + fn harvest_rewards() -> Result<(), BenchmarkError> { + create_reward_pool::()?; + + let pool_acc = AssetRewards::::pool_account_id(&0u32); + let min_reward_balance = mint_into::(&pool_acc, &T::BenchmarkHelper::reward_asset()); + + let staker = whitelisted_caller(); + let _ = mint_into::(&staker, &T::BenchmarkHelper::staked_asset()); + assert_ok!(AssetRewards::::stake( + RawOrigin::Signed(staker.clone()).into(), + 0, + T::Balance::one(), + )); + + System::::set_block_number(System::::block_number() + BlockNumberFor::::one()); + + #[extrinsic_call] + _(RawOrigin::Signed(staker.clone()), 0, None); + + assert_last_event::( + Event::RewardsHarvested { + caller: staker.clone(), + staker, + pool_id: 0, + amount: min_reward_balance, + } + .into(), + ); + + Ok(()) + } + + #[benchmark] + fn set_pool_reward_rate_per_block() -> Result<(), BenchmarkError> { + let caller_origin = create_reward_pool::()?; + + // stake first to get worth case benchmark. + { + let staker: T::AccountId = whitelisted_caller(); + let min_balance = mint_into::(&staker, &T::BenchmarkHelper::staked_asset()); + + assert_ok!(AssetRewards::::stake(RawOrigin::Signed(staker).into(), 0, min_balance)); + } + + let new_reward_rate_per_block = + T::Assets::minimum_balance(T::BenchmarkHelper::reward_asset()).max(T::Balance::one()) + + T::Balance::one(); + + #[extrinsic_call] + _(caller_origin as T::RuntimeOrigin, 0, new_reward_rate_per_block); + + assert_last_event::( + Event::PoolRewardRateModified { pool_id: 0, new_reward_rate_per_block }.into(), + ); + Ok(()) + } + + #[benchmark] + fn set_pool_admin() -> Result<(), BenchmarkError> { + let caller_origin = create_reward_pool::()?; + let new_admin: T::AccountId = whitelisted_caller(); + + #[extrinsic_call] + _(caller_origin as T::RuntimeOrigin, 0, new_admin.clone()); + + assert_last_event::(Event::PoolAdminModified { pool_id: 0, new_admin }.into()); + + Ok(()) + } + + #[benchmark] + fn set_pool_expiry_block() -> Result<(), BenchmarkError> { + let create_origin = create_reward_pool::()?; + + // stake first to get worth case benchmark. + { + let staker: T::AccountId = whitelisted_caller(); + let min_balance = mint_into::(&staker, &T::BenchmarkHelper::staked_asset()); + + assert_ok!(AssetRewards::::stake(RawOrigin::Signed(staker).into(), 0, min_balance)); + } + + let new_expiry_block = + pool_expire::().evaluate(System::::block_number()) + BlockNumberFor::::one(); + + #[extrinsic_call] + _(create_origin as T::RuntimeOrigin, 0, DispatchTime::At(new_expiry_block)); + + assert_last_event::( + Event::PoolExpiryBlockModified { pool_id: 0, new_expiry_block }.into(), + ); + + Ok(()) + } + + #[benchmark] + fn deposit_reward_tokens() -> Result<(), BenchmarkError> { + create_reward_pool::()?; + let caller = whitelisted_caller(); + + let reward_asset = T::BenchmarkHelper::reward_asset(); + let pool_acc = AssetRewards::::pool_account_id(&0u32); + let min_balance = mint_into::(&caller, &reward_asset); + + let balance_before = T::Assets::balance(reward_asset.clone(), &pool_acc); + + #[extrinsic_call] + _(RawOrigin::Signed(caller), 0, min_balance); + + let balance_after = T::Assets::balance(reward_asset, &pool_acc); + + assert_eq!(balance_after, balance_before + min_balance); + + Ok(()) + } + + #[benchmark] + fn cleanup_pool() -> Result<(), BenchmarkError> { + let create_origin = create_reward_pool::()?; + let caller = T::CreatePoolOrigin::ensure_origin(create_origin.clone()).unwrap(); + + // deposit rewards tokens to get worth case benchmark. + { + let caller = whitelisted_caller(); + let reward_asset = T::BenchmarkHelper::reward_asset(); + let min_balance = mint_into::(&caller, &reward_asset); + assert_ok!(AssetRewards::::deposit_reward_tokens( + RawOrigin::Signed(caller).into(), + 0, + min_balance + )); + } + + #[extrinsic_call] + _(RawOrigin::Signed(caller), 0); + + assert_last_event::(Event::PoolCleanedUp { pool_id: 0 }.into()); + + Ok(()) + } + + impl_benchmark_test_suite!(AssetRewards, crate::mock::new_test_ext(), crate::mock::MockRuntime); +} diff --git a/substrate/frame/asset-rewards/src/lib.rs b/substrate/frame/asset-rewards/src/lib.rs new file mode 100644 index 000000000000..4ce73e9febf9 --- /dev/null +++ b/substrate/frame/asset-rewards/src/lib.rs @@ -0,0 +1,905 @@ +// 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 Staking Rewards Pallet +//! +//! Allows accounts to be rewarded for holding `fungible` asset/s, for example LP tokens. +//! +//! ## Overview +//! +//! Initiate an incentive program for a fungible asset by creating a new pool. +//! +//! During pool creation, a 'staking asset', 'reward asset', 'reward rate per block', 'expiry +//! block', and 'admin' are specified. +//! +//! Once created, holders of the 'staking asset' can 'stake' them in a corresponding pool, which +//! creates a Freeze on the asset. +//! +//! Once staked, rewards denominated in 'reward asset' begin accumulating to the staker, +//! proportional to their share of the total staked tokens in the pool. +//! +//! Reward assets pending distribution are held in an account unique to each pool. +//! +//! Care should be taken by the pool operator to keep pool accounts adequately funded with the +//! reward asset. +//! +//! The pool admin may increase reward rate per block, increase expiry block, and change admin. +//! +//! ## Disambiguation +//! +//! While this pallet shares some terminology with the `staking-pool` and similar native staking +//! related pallets, it is distinct and is entirely unrelated to native staking. +//! +//! ## Permissioning +//! +//! Currently, pool creation and management restricted to a configured Origin. +//! +//! Future iterations of this pallet may allow permissionless creation and management of pools. +//! +//! Note: The permissioned origin must return an AccountId. This can be achieved for any Origin by +//! wrapping it with `EnsureSuccess`. +//! +//! ## Implementation Notes +//! +//! Internal logic functions such as `update_pool_and_staker_rewards` were deliberately written +//! without side-effects. +//! +//! Storage interaction such as reads and writes are instead all performed in the top level +//! pallet Call method, which while slightly more verbose, makes it easier to understand the +//! code and reason about how storage reads and writes occur in the pallet. +//! +//! ## Rewards Algorithm +//! +//! The rewards algorithm is based on the Synthetix [StakingRewards.sol](https://github.com/Synthetixio/synthetix/blob/develop/contracts/StakingRewards.sol) +//! smart contract. +//! +//! Rewards are calculated JIT (just-in-time), and all operations are O(1) making the approach +//! scalable to many pools and stakers. +//! +//! ### Resources +//! +//! - [This video series](https://www.youtube.com/watch?v=6ZO5aYg1GI8), which walks through the math +//! of the algorithm. +//! - [This dev.to article](https://dev.to/heymarkkop/understanding-sushiswaps-masterchef-staking-rewards-1m6f), +//! which explains the algorithm of the SushiSwap MasterChef staking. While not identical to the +//! Synthetix approach, they are quite similar. +#![deny(missing_docs)] +#![cfg_attr(not(feature = "std"), no_std)] + +pub use pallet::*; + +use codec::{Codec, Decode, Encode, MaxEncodedLen}; +use frame_support::{ + traits::{ + fungibles::{Inspect, Mutate}, + schedule::DispatchTime, + tokens::Balance, + }, + PalletId, +}; +use frame_system::pallet_prelude::BlockNumberFor; +use scale_info::TypeInfo; +use sp_core::Get; +use sp_runtime::{ + traits::{MaybeDisplay, Zero}, + DispatchError, +}; +use sp_std::boxed::Box; + +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmarking; +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; +mod weights; + +pub use weights::WeightInfo; + +/// Unique id type for each pool. +pub type PoolId = u32; + +/// Multiplier to maintain precision when calculating rewards. +pub(crate) const PRECISION_SCALING_FACTOR: u16 = 4096; + +/// Convenience alias for `PoolInfo`. +pub type PoolInfoFor = PoolInfo< + ::AccountId, + ::AssetId, + ::Balance, + BlockNumberFor, +>; + +/// The state of a staker in a pool. +#[derive(Debug, Default, Clone, Decode, Encode, MaxEncodedLen, TypeInfo)] +pub struct PoolStakerInfo { + /// Amount of tokens staked. + amount: Balance, + /// Accumulated, unpaid rewards. + rewards: Balance, + /// Reward per token value at the time of the staker's last interaction with the contract. + reward_per_token_paid: Balance, +} + +/// The state and configuration of an incentive pool. +#[derive(Debug, Clone, Decode, Encode, Default, PartialEq, Eq, MaxEncodedLen, TypeInfo)] +pub struct PoolInfo { + /// The asset staked in this pool. + staked_asset_id: AssetId, + /// The asset distributed as rewards by this pool. + reward_asset_id: AssetId, + /// The amount of tokens rewarded per block. + reward_rate_per_block: Balance, + /// The block the pool will cease distributing rewards. + expiry_block: BlockNumber, + /// The account authorized to manage this pool. + admin: AccountId, + /// The total amount of tokens staked in this pool. + total_tokens_staked: Balance, + /// Total rewards accumulated per token, up to the `last_update_block`. + reward_per_token_stored: Balance, + /// Last block number the pool was updated. + last_update_block: BlockNumber, + /// The account that holds the pool's rewards. + account: AccountId, +} + +sp_api::decl_runtime_apis! { + /// The runtime API for the asset rewards pallet. + pub trait AssetRewards { + /// Get the cost of creating a pool. + /// + /// This is especially useful when the cost is dynamic. + fn pool_creation_cost() -> Cost; + } +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::{ + pallet_prelude::*, + traits::{ + fungibles::MutateFreeze, + tokens::{AssetId, Fortitude, Preservation}, + Consideration, Footprint, + }, + }; + use frame_system::pallet_prelude::*; + use sp_runtime::{ + traits::{ + AccountIdConversion, BadOrigin, EnsureAdd, EnsureAddAssign, EnsureDiv, EnsureMul, + EnsureSub, EnsureSubAssign, + }, + DispatchResult, + }; + + #[pallet::pallet] + pub struct Pallet(_); + + /// A reason for the pallet placing a hold on funds. + #[pallet::composite_enum] + pub enum FreezeReason { + /// Funds are staked in the pallet. + #[codec(index = 0)] + Staked, + } + + /// A reason for the pallet placing a hold on funds. + #[pallet::composite_enum] + pub enum HoldReason { + /// Cost associated with storing pool information on-chain. + #[codec(index = 0)] + PoolCreation, + } + + #[pallet::config] + pub trait Config: frame_system::Config { + /// Overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The pallet's unique identifier, used to derive the pool's account ID. + /// + /// The account ID is derived once during pool creation and stored in the storage. + #[pallet::constant] + type PalletId: Get; + + /// Identifier for each type of asset. + type AssetId: AssetId + Member + Parameter; + + /// The type in which the assets are measured. + type Balance: Balance + TypeInfo; + + /// The origin with permission to create pools. + /// + /// The Origin must return an AccountId. + type CreatePoolOrigin: EnsureOrigin; + + /// Registry of assets that can be configured to either stake for rewards, or be offered as + /// rewards for staking. + type Assets: Inspect + + Mutate; + + /// Freezer for the Assets. + type AssetsFreezer: MutateFreeze< + Self::AccountId, + Id = Self::RuntimeFreezeReason, + AssetId = Self::AssetId, + Balance = Self::Balance, + >; + + /// The overarching freeze reason. + type RuntimeFreezeReason: From; + + /// Means for associating a cost with the on-chain storage of pool information, which + /// is incurred by the pool creator. + /// + /// The passed `Footprint` specifically accounts for the storage footprint of the pool's + /// information itself, excluding any potential storage footprint related to the stakers. + type Consideration: Consideration; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + + /// Helper for benchmarking. + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper: benchmarking::BenchmarkHelper; + } + + /// State of pool stakers. + #[pallet::storage] + pub type PoolStakers = StorageDoubleMap< + _, + Blake2_128Concat, + PoolId, + Blake2_128Concat, + T::AccountId, + PoolStakerInfo, + >; + + /// State and configuration of each staking pool. + #[pallet::storage] + pub type Pools = StorageMap<_, Blake2_128Concat, PoolId, PoolInfoFor>; + + /// The cost associated with storing pool information on-chain which was incurred by the pool + /// creator. + /// + /// This cost may be [`None`], as determined by [`Config::Consideration`]. + #[pallet::storage] + pub type PoolCost = + StorageMap<_, Blake2_128Concat, PoolId, (T::AccountId, T::Consideration)>; + + /// Stores the [`PoolId`] to use for the next pool. + /// + /// Incremented when a new pool is created. + #[pallet::storage] + pub type NextPoolId = StorageValue<_, PoolId, ValueQuery>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// An account staked some tokens in a pool. + Staked { + /// The account that staked assets. + staker: T::AccountId, + /// The pool. + pool_id: PoolId, + /// The staked asset amount. + amount: T::Balance, + }, + /// An account unstaked some tokens from a pool. + Unstaked { + /// The account that signed transaction. + caller: T::AccountId, + /// The account that unstaked assets. + staker: T::AccountId, + /// The pool. + pool_id: PoolId, + /// The unstaked asset amount. + amount: T::Balance, + }, + /// An account harvested some rewards. + RewardsHarvested { + /// The account that signed transaction. + caller: T::AccountId, + /// The staker whos rewards were harvested. + staker: T::AccountId, + /// The pool. + pool_id: PoolId, + /// The amount of harvested tokens. + amount: T::Balance, + }, + /// A new reward pool was created. + PoolCreated { + /// The account that created the pool. + creator: T::AccountId, + /// The unique ID for the new pool. + pool_id: PoolId, + /// The staking asset. + staked_asset_id: T::AssetId, + /// The reward asset. + reward_asset_id: T::AssetId, + /// The initial reward rate per block. + reward_rate_per_block: T::Balance, + /// The block the pool will cease to accumulate rewards. + expiry_block: BlockNumberFor, + /// The account allowed to modify the pool. + admin: T::AccountId, + }, + /// A pool reward rate was modified by the admin. + PoolRewardRateModified { + /// The modified pool. + pool_id: PoolId, + /// The new reward rate per block. + new_reward_rate_per_block: T::Balance, + }, + /// A pool admin was modified. + PoolAdminModified { + /// The modified pool. + pool_id: PoolId, + /// The new admin. + new_admin: T::AccountId, + }, + /// A pool expiry block was modified by the admin. + PoolExpiryBlockModified { + /// The modified pool. + pool_id: PoolId, + /// The new expiry block. + new_expiry_block: BlockNumberFor, + }, + /// A pool information was cleared after it's completion. + PoolCleanedUp { + /// The cleared pool. + pool_id: PoolId, + }, + } + + #[pallet::error] + pub enum Error { + /// The staker does not have enough tokens to perform the operation. + NotEnoughTokens, + /// An operation was attempted on a non-existent pool. + NonExistentPool, + /// An operation was attempted for a non-existent staker. + NonExistentStaker, + /// An operation was attempted with a non-existent asset. + NonExistentAsset, + /// There was an error converting a block number. + BlockNumberConversionError, + /// The expiry block must be in the future. + ExpiryBlockMustBeInTheFuture, + /// Insufficient funds to create the freeze. + InsufficientFunds, + /// The expiry block can be only extended. + ExpiryCut, + /// The reward rate per block can be only increased. + RewardRateCut, + /// The pool still has staked tokens or rewards. + NonEmptyPool, + } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn integrity_test() { + // The AccountId is at least 16 bytes to contain the unique PalletId. + let pool_id: PoolId = 1; + assert!( + >::try_into_sub_account( + &T::PalletId::get(), pool_id, + ) + .is_some() + ); + } + } + + /// Pallet's callable functions. + #[pallet::call(weight(::WeightInfo))] + impl Pallet { + /// Create a new reward pool. + /// + /// Parameters: + /// - `origin`: must be `Config::CreatePoolOrigin`; + /// - `staked_asset_id`: the asset to be staked in the pool; + /// - `reward_asset_id`: the asset to be distributed as rewards; + /// - `reward_rate_per_block`: the amount of reward tokens distributed per block; + /// - `expiry`: the block number at which the pool will cease to accumulate rewards. The + /// [`DispatchTime::After`] variant evaluated at the execution time. + /// - `admin`: the account allowed to extend the pool expiration, increase the rewards rate + /// and receive the unutilized reward tokens back after the pool completion. If `None`, + /// the caller is set as an admin. + #[pallet::call_index(0)] + pub fn create_pool( + origin: OriginFor, + staked_asset_id: Box, + reward_asset_id: Box, + reward_rate_per_block: T::Balance, + expiry: DispatchTime>, + admin: Option, + ) -> DispatchResult { + // Check the origin. + let creator = T::CreatePoolOrigin::ensure_origin(origin)?; + + // Ensure the assets exist. + ensure!( + T::Assets::asset_exists(*staked_asset_id.clone()), + Error::::NonExistentAsset + ); + ensure!( + T::Assets::asset_exists(*reward_asset_id.clone()), + Error::::NonExistentAsset + ); + + // Check the expiry block. + let expiry_block = expiry.evaluate(frame_system::Pallet::::block_number()); + ensure!( + expiry_block > frame_system::Pallet::::block_number(), + Error::::ExpiryBlockMustBeInTheFuture + ); + + let pool_id = NextPoolId::::get(); + + let footprint = Self::pool_creation_footprint(); + let cost = T::Consideration::new(&creator, footprint)?; + PoolCost::::insert(pool_id, (creator.clone(), cost)); + + let admin = admin.unwrap_or(creator.clone()); + + // Create the pool. + let pool = PoolInfoFor:: { + staked_asset_id: *staked_asset_id.clone(), + reward_asset_id: *reward_asset_id.clone(), + reward_rate_per_block, + total_tokens_staked: 0u32.into(), + reward_per_token_stored: 0u32.into(), + last_update_block: 0u32.into(), + expiry_block, + admin: admin.clone(), + account: Self::pool_account_id(&pool_id), + }; + + // Insert it into storage. + Pools::::insert(pool_id, pool); + + NextPoolId::::put(pool_id.ensure_add(1)?); + + // Emit created event. + Self::deposit_event(Event::PoolCreated { + creator, + pool_id, + staked_asset_id: *staked_asset_id, + reward_asset_id: *reward_asset_id, + reward_rate_per_block, + expiry_block, + admin, + }); + + Ok(()) + } + + /// Stake additional tokens in a pool. + /// + /// A freeze is placed on the staked tokens. + #[pallet::call_index(1)] + pub fn stake(origin: OriginFor, pool_id: PoolId, amount: T::Balance) -> DispatchResult { + let staker = ensure_signed(origin)?; + + // Always start by updating staker and pool rewards. + let pool_info = Pools::::get(pool_id).ok_or(Error::::NonExistentPool)?; + let staker_info = PoolStakers::::get(pool_id, &staker).unwrap_or_default(); + let (mut pool_info, mut staker_info) = + Self::update_pool_and_staker_rewards(&pool_info, &staker_info)?; + + T::AssetsFreezer::increase_frozen( + pool_info.staked_asset_id.clone(), + &FreezeReason::Staked.into(), + &staker, + amount, + )?; + + // Update Pools. + pool_info.total_tokens_staked.ensure_add_assign(amount)?; + + Pools::::insert(pool_id, pool_info); + + // Update PoolStakers. + staker_info.amount.ensure_add_assign(amount)?; + PoolStakers::::insert(pool_id, &staker, staker_info); + + // Emit event. + Self::deposit_event(Event::Staked { staker, pool_id, amount }); + + Ok(()) + } + + /// Unstake tokens from a pool. + /// + /// Removes the freeze on the staked tokens. + /// + /// Parameters: + /// - origin: must be the `staker` if the pool is still active. Otherwise, any account. + /// - pool_id: the pool to unstake from. + /// - amount: the amount of tokens to unstake. + /// - staker: the account to unstake from. If `None`, the caller is used. + #[pallet::call_index(2)] + pub fn unstake( + origin: OriginFor, + pool_id: PoolId, + amount: T::Balance, + staker: Option, + ) -> DispatchResult { + let caller = ensure_signed(origin)?; + let staker = staker.unwrap_or(caller.clone()); + + // Always start by updating the pool rewards. + let pool_info = Pools::::get(pool_id).ok_or(Error::::NonExistentPool)?; + let now = frame_system::Pallet::::block_number(); + ensure!(now > pool_info.expiry_block || caller == staker, BadOrigin); + + let staker_info = PoolStakers::::get(pool_id, &staker).unwrap_or_default(); + let (mut pool_info, mut staker_info) = + Self::update_pool_and_staker_rewards(&pool_info, &staker_info)?; + + // Check the staker has enough staked tokens. + ensure!(staker_info.amount >= amount, Error::::NotEnoughTokens); + + // Unfreeze staker assets. + T::AssetsFreezer::decrease_frozen( + pool_info.staked_asset_id.clone(), + &FreezeReason::Staked.into(), + &staker, + amount, + )?; + + // Update Pools. + pool_info.total_tokens_staked.ensure_sub_assign(amount)?; + Pools::::insert(pool_id, pool_info); + + // Update PoolStakers. + staker_info.amount.ensure_sub_assign(amount)?; + + if staker_info.amount.is_zero() && staker_info.rewards.is_zero() { + PoolStakers::::remove(&pool_id, &staker); + } else { + PoolStakers::::insert(&pool_id, &staker, staker_info); + } + + // Emit event. + Self::deposit_event(Event::Unstaked { caller, staker, pool_id, amount }); + + Ok(()) + } + + /// Harvest unclaimed pool rewards. + /// + /// Parameters: + /// - origin: must be the `staker` if the pool is still active. Otherwise, any account. + /// - pool_id: the pool to harvest from. + /// - staker: the account for which to harvest rewards. If `None`, the caller is used. + #[pallet::call_index(3)] + pub fn harvest_rewards( + origin: OriginFor, + pool_id: PoolId, + staker: Option, + ) -> DispatchResult { + let caller = ensure_signed(origin)?; + let staker = staker.unwrap_or(caller.clone()); + + // Always start by updating the pool and staker rewards. + let pool_info = Pools::::get(pool_id).ok_or(Error::::NonExistentPool)?; + let now = frame_system::Pallet::::block_number(); + ensure!(now > pool_info.expiry_block || caller == staker, BadOrigin); + + let staker_info = + PoolStakers::::get(pool_id, &staker).ok_or(Error::::NonExistentStaker)?; + let (pool_info, mut staker_info) = + Self::update_pool_and_staker_rewards(&pool_info, &staker_info)?; + + // Transfer unclaimed rewards from the pool to the staker. + T::Assets::transfer( + pool_info.reward_asset_id, + &pool_info.account, + &staker, + staker_info.rewards, + // Could kill the account, but only if the pool was already almost empty. + Preservation::Expendable, + )?; + + // Emit event. + Self::deposit_event(Event::RewardsHarvested { + caller, + staker: staker.clone(), + pool_id, + amount: staker_info.rewards, + }); + + // Reset staker rewards. + staker_info.rewards = 0u32.into(); + + if staker_info.amount.is_zero() { + PoolStakers::::remove(&pool_id, &staker); + } else { + PoolStakers::::insert(&pool_id, &staker, staker_info); + } + + Ok(()) + } + + /// Modify a pool reward rate. + /// + /// Currently the reward rate can only be increased. + /// + /// Only the pool admin may perform this operation. + #[pallet::call_index(4)] + pub fn set_pool_reward_rate_per_block( + origin: OriginFor, + pool_id: PoolId, + new_reward_rate_per_block: T::Balance, + ) -> DispatchResult { + let caller = T::CreatePoolOrigin::ensure_origin(origin.clone()) + .or_else(|_| ensure_signed(origin))?; + + let pool_info = Pools::::get(pool_id).ok_or(Error::::NonExistentPool)?; + ensure!(pool_info.admin == caller, BadOrigin); + ensure!( + new_reward_rate_per_block > pool_info.reward_rate_per_block, + Error::::RewardRateCut + ); + + // Always start by updating the pool rewards. + let rewards_per_token = Self::reward_per_token(&pool_info)?; + let mut pool_info = Self::update_pool_rewards(&pool_info, rewards_per_token)?; + + pool_info.reward_rate_per_block = new_reward_rate_per_block; + Pools::::insert(pool_id, pool_info); + + Self::deposit_event(Event::PoolRewardRateModified { + pool_id, + new_reward_rate_per_block, + }); + + Ok(()) + } + + /// Modify a pool admin. + /// + /// Only the pool admin may perform this operation. + #[pallet::call_index(5)] + pub fn set_pool_admin( + origin: OriginFor, + pool_id: PoolId, + new_admin: T::AccountId, + ) -> DispatchResult { + let caller = T::CreatePoolOrigin::ensure_origin(origin.clone()) + .or_else(|_| ensure_signed(origin))?; + + let mut pool_info = Pools::::get(pool_id).ok_or(Error::::NonExistentPool)?; + ensure!(pool_info.admin == caller, BadOrigin); + + pool_info.admin = new_admin.clone(); + Pools::::insert(pool_id, pool_info); + + Self::deposit_event(Event::PoolAdminModified { pool_id, new_admin }); + + Ok(()) + } + + /// Set when the pool should expire. + /// + /// Currently the expiry block can only be extended. + /// + /// Only the pool admin may perform this operation. + #[pallet::call_index(6)] + pub fn set_pool_expiry_block( + origin: OriginFor, + pool_id: PoolId, + new_expiry: DispatchTime>, + ) -> DispatchResult { + let caller = T::CreatePoolOrigin::ensure_origin(origin.clone()) + .or_else(|_| ensure_signed(origin))?; + + let new_expiry = new_expiry.evaluate(frame_system::Pallet::::block_number()); + ensure!( + new_expiry > frame_system::Pallet::::block_number(), + Error::::ExpiryBlockMustBeInTheFuture + ); + + let pool_info = Pools::::get(pool_id).ok_or(Error::::NonExistentPool)?; + ensure!(pool_info.admin == caller, BadOrigin); + ensure!(new_expiry > pool_info.expiry_block, Error::::ExpiryCut); + + // Always start by updating the pool rewards. + let reward_per_token = Self::reward_per_token(&pool_info)?; + let mut pool_info = Self::update_pool_rewards(&pool_info, reward_per_token)?; + + pool_info.expiry_block = new_expiry; + Pools::::insert(pool_id, pool_info); + + Self::deposit_event(Event::PoolExpiryBlockModified { + pool_id, + new_expiry_block: new_expiry, + }); + + Ok(()) + } + + /// Convenience method to deposit reward tokens into a pool. + /// + /// This method is not strictly necessary (tokens could be transferred directly to the + /// pool pot address), but is provided for convenience so manual derivation of the + /// account id is not required. + #[pallet::call_index(7)] + pub fn deposit_reward_tokens( + origin: OriginFor, + pool_id: PoolId, + amount: T::Balance, + ) -> DispatchResult { + let caller = ensure_signed(origin)?; + let pool_info = Pools::::get(pool_id).ok_or(Error::::NonExistentPool)?; + T::Assets::transfer( + pool_info.reward_asset_id, + &caller, + &pool_info.account, + amount, + Preservation::Preserve, + )?; + Ok(()) + } + + /// Cleanup a pool. + /// + /// Origin must be the pool admin. + /// + /// Cleanup storage, release any associated storage cost and return the remaining reward + /// tokens to the admin. + #[pallet::call_index(8)] + pub fn cleanup_pool(origin: OriginFor, pool_id: PoolId) -> DispatchResult { + let who = ensure_signed(origin)?; + + let pool_info = Pools::::get(pool_id).ok_or(Error::::NonExistentPool)?; + ensure!(pool_info.admin == who, BadOrigin); + + let stakers = PoolStakers::::iter_key_prefix(pool_id).next(); + ensure!(stakers.is_none(), Error::::NonEmptyPool); + + let pool_balance = T::Assets::reducible_balance( + pool_info.reward_asset_id.clone(), + &pool_info.account, + Preservation::Expendable, + Fortitude::Polite, + ); + T::Assets::transfer( + pool_info.reward_asset_id, + &pool_info.account, + &pool_info.admin, + pool_balance, + Preservation::Expendable, + )?; + + if let Some((who, cost)) = PoolCost::::take(pool_id) { + T::Consideration::drop(cost, &who)?; + } + + Pools::::remove(pool_id); + + Self::deposit_event(Event::PoolCleanedUp { pool_id }); + + Ok(()) + } + } + + impl Pallet { + /// The pool creation footprint. + /// + /// The footprint specifically accounts for the storage footprint of the pool's information + /// itself, excluding any potential storage footprint related to the stakers. + pub fn pool_creation_footprint() -> Footprint { + Footprint::from_mel::<(PoolId, PoolInfoFor)>() + } + + /// Derive a pool account ID from the pool's ID. + pub fn pool_account_id(id: &PoolId) -> T::AccountId { + T::PalletId::get().into_sub_account_truncating(id) + } + + /// Computes update pool and staker reward state. + /// + /// Should be called prior to any operation involving a staker. + /// + /// Returns the updated pool and staker info. + /// + /// NOTE: this function has no side-effects. Side-effects such as storage modifications are + /// the responsibility of the caller. + pub fn update_pool_and_staker_rewards( + pool_info: &PoolInfoFor, + staker_info: &PoolStakerInfo, + ) -> Result<(PoolInfoFor, PoolStakerInfo), DispatchError> { + let reward_per_token = Self::reward_per_token(&pool_info)?; + let pool_info = Self::update_pool_rewards(pool_info, reward_per_token)?; + + let mut new_staker_info = staker_info.clone(); + new_staker_info.rewards = Self::derive_rewards(&staker_info, &reward_per_token)?; + new_staker_info.reward_per_token_paid = pool_info.reward_per_token_stored; + return Ok((pool_info, new_staker_info)); + } + + /// Computes update pool reward state. + /// + /// Should be called every time the pool is adjusted, and a staker is not involved. + /// + /// Returns the updated pool and staker info. + /// + /// NOTE: this function has no side-effects. Side-effects such as storage modifications are + /// the responsibility of the caller. + pub fn update_pool_rewards( + pool_info: &PoolInfoFor, + reward_per_token: T::Balance, + ) -> Result, DispatchError> { + let mut new_pool_info = pool_info.clone(); + new_pool_info.last_update_block = frame_system::Pallet::::block_number(); + new_pool_info.reward_per_token_stored = reward_per_token; + + Ok(new_pool_info) + } + + /// Derives the current reward per token for this pool. + fn reward_per_token(pool_info: &PoolInfoFor) -> Result { + if pool_info.total_tokens_staked.is_zero() { + return Ok(pool_info.reward_per_token_stored) + } + + let rewardable_blocks_elapsed: u32 = + match Self::last_block_reward_applicable(pool_info.expiry_block) + .ensure_sub(pool_info.last_update_block)? + .try_into() + { + Ok(b) => b, + Err(_) => return Err(Error::::BlockNumberConversionError.into()), + }; + + Ok(pool_info.reward_per_token_stored.ensure_add( + pool_info + .reward_rate_per_block + .ensure_mul(rewardable_blocks_elapsed.into())? + .ensure_mul(PRECISION_SCALING_FACTOR.into())? + .ensure_div(pool_info.total_tokens_staked)?, + )?) + } + + /// Derives the amount of rewards earned by a staker. + /// + /// This is a helper function for `update_pool_rewards` and should not be called directly. + fn derive_rewards( + staker_info: &PoolStakerInfo, + reward_per_token: &T::Balance, + ) -> Result { + Ok(staker_info + .amount + .ensure_mul(reward_per_token.ensure_sub(staker_info.reward_per_token_paid)?)? + .ensure_div(PRECISION_SCALING_FACTOR.into())? + .ensure_add(staker_info.rewards)?) + } + + fn last_block_reward_applicable(pool_expiry_block: BlockNumberFor) -> BlockNumberFor { + let now = frame_system::Pallet::::block_number(); + if now < pool_expiry_block { + now + } else { + pool_expiry_block + } + } + } +} diff --git a/substrate/frame/asset-rewards/src/mock.rs b/substrate/frame/asset-rewards/src/mock.rs new file mode 100644 index 000000000000..87c8a8a0dea0 --- /dev/null +++ b/substrate/frame/asset-rewards/src/mock.rs @@ -0,0 +1,221 @@ +// 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. + +//! Test environment for Asset Rewards pallet. + +use super::*; +use crate as pallet_asset_rewards; +use core::default::Default; +use frame_support::{ + construct_runtime, derive_impl, + instances::Instance1, + parameter_types, + traits::{ + tokens::fungible::{HoldConsideration, NativeFromLeft, NativeOrWithId, UnionOf}, + AsEnsureOriginWithArg, ConstU128, ConstU32, EnsureOrigin, LinearStoragePrice, + }, + PalletId, +}; +use frame_system::EnsureSigned; +use sp_runtime::{traits::IdentityLookup, BuildStorage}; + +#[cfg(feature = "runtime-benchmarks")] +use self::benchmarking::BenchmarkHelper; + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum MockRuntime + { + System: frame_system, + Balances: pallet_balances, + Assets: pallet_assets::, + AssetsFreezer: pallet_assets_freezer::, + StakingRewards: pallet_asset_rewards, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for MockRuntime { + type AccountId = u128; + type Lookup = IdentityLookup; + type Block = Block; + type AccountData = pallet_balances::AccountData; +} + +impl pallet_balances::Config for MockRuntime { + type Balance = u128; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU128<100>; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = (); + type MaxReserves = ConstU32<50>; + type ReserveIdentifier = [u8; 8]; + type FreezeIdentifier = RuntimeFreezeReason; + type MaxFreezes = ConstU32<50>; + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; + type DoneSlashHandler = (); +} + +impl pallet_assets::Config for MockRuntime { + type RuntimeEvent = RuntimeEvent; + type Balance = u128; + type RemoveItemsLimit = ConstU32<1000>; + type AssetId = u32; + type AssetIdParameter = u32; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = frame_system::EnsureRoot; + type AssetDeposit = ConstU128<1>; + type AssetAccountDeposit = ConstU128<10>; + type MetadataDepositBase = ConstU128<1>; + type MetadataDepositPerByte = ConstU128<1>; + type ApprovalDeposit = ConstU128<1>; + type StringLimit = ConstU32<50>; + type Freezer = AssetsFreezer; + type Extra = (); + type WeightInfo = (); + type CallbackHandle = (); + pallet_assets::runtime_benchmarks_enabled! { + type BenchmarkHelper = (); + } +} + +parameter_types! { + pub const StakingRewardsPalletId: PalletId = PalletId(*b"py/stkrd"); + pub const Native: NativeOrWithId = NativeOrWithId::Native; + pub const PermissionedAccountId: u128 = 0; +} + +/// Give Root Origin permission to create pools. +pub struct MockPermissionedOrigin; +impl EnsureOrigin for MockPermissionedOrigin { + type Success = ::AccountId; + + fn try_origin(origin: RuntimeOrigin) -> Result { + match origin.clone().into() { + Ok(frame_system::RawOrigin::Root) => Ok(PermissionedAccountId::get()), + _ => Err(origin), + } + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + Ok(RuntimeOrigin::root()) + } +} + +/// Allow Freezes for the `Assets` pallet +impl pallet_assets_freezer::Config for MockRuntime { + type RuntimeFreezeReason = RuntimeFreezeReason; + type RuntimeEvent = RuntimeEvent; +} + +pub type NativeAndAssets = UnionOf, u128>; + +pub type NativeAndAssetsFreezer = + UnionOf, u128>; + +#[cfg(feature = "runtime-benchmarks")] +pub struct AssetRewardsBenchmarkHelper; +#[cfg(feature = "runtime-benchmarks")] +impl BenchmarkHelper> for AssetRewardsBenchmarkHelper { + fn staked_asset() -> NativeOrWithId { + NativeOrWithId::::WithId(101) + } + fn reward_asset() -> NativeOrWithId { + NativeOrWithId::::WithId(102) + } +} + +parameter_types! { + pub const CreationHoldReason: RuntimeHoldReason = + RuntimeHoldReason::StakingRewards(pallet_asset_rewards::HoldReason::PoolCreation); +} + +impl Config for MockRuntime { + type RuntimeEvent = RuntimeEvent; + type AssetId = NativeOrWithId; + type Balance = ::Balance; + type Assets = NativeAndAssets; + type AssetsFreezer = NativeAndAssetsFreezer; + type PalletId = StakingRewardsPalletId; + type CreatePoolOrigin = MockPermissionedOrigin; + type WeightInfo = (); + type RuntimeFreezeReason = RuntimeFreezeReason; + type Consideration = HoldConsideration< + u128, + Balances, + CreationHoldReason, + LinearStoragePrice, ConstU128<0>, u128>, + >; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = AssetRewardsBenchmarkHelper; +} + +pub(crate) fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + + pallet_assets::GenesisConfig:: { + // Genesis assets: id, owner, is_sufficient, min_balance + // pub assets: Vec<(T::AssetId, T::AccountId, bool, T::Balance)>, + assets: vec![(1, 1, true, 1), (10, 1, true, 1), (20, 1, true, 1)], + // Genesis metadata: id, name, symbol, decimals + // pub metadata: Vec<(T::AssetId, Vec, Vec, u8)>, + metadata: vec![ + (1, b"test".to_vec(), b"TST".to_vec(), 18), + (10, b"test10".to_vec(), b"T10".to_vec(), 18), + (20, b"test20".to_vec(), b"T20".to_vec(), 18), + ], + // Genesis accounts: id, account_id, balance + // pub accounts: Vec<(T::AssetId, T::AccountId, T::Balance)>, + accounts: vec![ + (1, 1, 10000), + (1, 2, 20000), + (1, 3, 30000), + (1, 4, 40000), + (1, 10, 40000), + (1, 20, 40000), + ], + next_asset_id: None, + } + .assimilate_storage(&mut t) + .unwrap(); + + let pool_zero_account_id = 31086825966906540362769395565; + pallet_balances::GenesisConfig:: { + balances: vec![ + (0, 10000), + (1, 10000), + (2, 20000), + (3, 30000), + (4, 40000), + (10, 40000), + (20, 40000), + (pool_zero_account_id, 100_000), // Top up the default pool account id + ], + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} diff --git a/substrate/frame/asset-rewards/src/tests.rs b/substrate/frame/asset-rewards/src/tests.rs new file mode 100644 index 000000000000..399d6a54c939 --- /dev/null +++ b/substrate/frame/asset-rewards/src/tests.rs @@ -0,0 +1,1457 @@ +// 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::{mock::*, *}; +use frame_support::{ + assert_err, assert_noop, assert_ok, hypothetically, + traits::{ + fungible, + fungible::NativeOrWithId, + fungibles, + tokens::{Fortitude, Preservation}, + }, +}; +use sp_runtime::{traits::BadOrigin, ArithmeticError, TokenError}; + +const DEFAULT_STAKED_ASSET_ID: NativeOrWithId = NativeOrWithId::::WithId(1); +const DEFAULT_REWARD_ASSET_ID: NativeOrWithId = NativeOrWithId::::Native; +const DEFAULT_REWARD_RATE_PER_BLOCK: u128 = 100; +const DEFAULT_EXPIRE_AFTER: u64 = 200; +const DEFAULT_ADMIN: u128 = 1; + +/// Creates a basic pool with values: +/// - Staking asset: 1 +/// - Reward asset: Native +/// - Reward rate per block: 100 +/// - Lifetime: 100 +/// - Admin: 1 +/// +/// Useful to reduce boilerplate in tests when it's not important to customise or reuse pool +/// params. +pub fn create_default_pool() { + assert_ok!(StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(DEFAULT_STAKED_ASSET_ID.clone()), + Box::new(DEFAULT_REWARD_ASSET_ID.clone()), + DEFAULT_REWARD_RATE_PER_BLOCK, + DispatchTime::After(DEFAULT_EXPIRE_AFTER), + Some(DEFAULT_ADMIN) + )); +} + +/// The same as [`create_default_pool`], but with the admin parameter set to the creator. +pub fn create_default_pool_permissioned_admin() { + assert_ok!(StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(DEFAULT_STAKED_ASSET_ID.clone()), + Box::new(DEFAULT_REWARD_ASSET_ID.clone()), + DEFAULT_REWARD_RATE_PER_BLOCK, + DispatchTime::After(DEFAULT_EXPIRE_AFTER), + Some(PermissionedAccountId::get()), + )); +} + +fn assert_hypothetically_earned( + staker: u128, + expected_earned: u128, + pool_id: u32, + reward_asset_id: NativeOrWithId, +) { + hypothetically!({ + // Get the pre-harvest balance. + let balance_before: ::Balance = + <::Assets>::balance(reward_asset_id.clone(), &staker); + + // Harvest the rewards. + assert_ok!(StakingRewards::harvest_rewards(RuntimeOrigin::signed(staker), pool_id, None),); + + // Sanity check: staker rewards are reset to 0 if some `amount` is still staked, otherwise + // the storage item removed. + if let Some(staker_pool) = PoolStakers::::get(pool_id, staker) { + assert!(staker_pool.rewards == 0); + assert!(staker_pool.amount > 0); + } + + // Check that the staker has earned the expected amount. + let balance_after = + <::Assets>::balance(reward_asset_id.clone(), &staker); + assert_eq!(balance_after - balance_before, expected_earned); + }); +} + +fn events() -> Vec> { + let result = System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| { + if let mock::RuntimeEvent::StakingRewards(inner) = e { + Some(inner) + } else { + None + } + }) + .collect(); + + System::reset_events(); + + result +} + +fn pools() -> Vec<(u32, PoolInfo, u128, u64>)> { + Pools::::iter().collect() +} + +mod create_pool { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + assert_eq!(NextPoolId::::get(), 0); + + System::set_block_number(10); + let expected_expiry_block = DEFAULT_EXPIRE_AFTER + 10; + + // Create a pool with default values, and no admin override so [`PermissionedAccountId`] + // is admin. + assert_ok!(StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(DEFAULT_STAKED_ASSET_ID), + Box::new(DEFAULT_REWARD_ASSET_ID), + DEFAULT_REWARD_RATE_PER_BLOCK, + DispatchTime::After(DEFAULT_EXPIRE_AFTER), + Some(PermissionedAccountId::get()) + )); + + // Event is emitted. + assert_eq!( + events(), + [Event::::PoolCreated { + creator: PermissionedAccountId::get(), + pool_id: 0, + staked_asset_id: DEFAULT_STAKED_ASSET_ID, + reward_asset_id: DEFAULT_REWARD_ASSET_ID, + reward_rate_per_block: DEFAULT_REWARD_RATE_PER_BLOCK, + expiry_block: expected_expiry_block, + admin: PermissionedAccountId::get(), + }] + ); + + // State is updated correctly. + assert_eq!(NextPoolId::::get(), 1); + assert_eq!( + pools(), + vec![( + 0, + PoolInfo { + staked_asset_id: DEFAULT_STAKED_ASSET_ID, + reward_asset_id: DEFAULT_REWARD_ASSET_ID, + reward_rate_per_block: DEFAULT_REWARD_RATE_PER_BLOCK, + expiry_block: expected_expiry_block, + admin: PermissionedAccountId::get(), + total_tokens_staked: 0, + reward_per_token_stored: 0, + last_update_block: 0, + account: StakingRewards::pool_account_id(&0), + } + )] + ); + + // Create another pool with explicit admin and other overrides. + let admin = 2; + let staked_asset_id = NativeOrWithId::::WithId(10); + let reward_asset_id = NativeOrWithId::::WithId(20); + let reward_rate_per_block = 250; + let expiry_block = 500; + let expected_expiry_block = expiry_block + 10; + assert_ok!(StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(staked_asset_id.clone()), + Box::new(reward_asset_id.clone()), + reward_rate_per_block, + DispatchTime::After(expiry_block), + Some(admin) + )); + + // Event is emitted. + assert_eq!( + events(), + [Event::::PoolCreated { + creator: PermissionedAccountId::get(), + pool_id: 1, + staked_asset_id: staked_asset_id.clone(), + reward_asset_id: reward_asset_id.clone(), + reward_rate_per_block, + admin, + expiry_block: expected_expiry_block, + }] + ); + + // State is updated correctly. + assert_eq!(NextPoolId::::get(), 2); + assert_eq!( + pools(), + vec![ + ( + 0, + PoolInfo { + staked_asset_id: DEFAULT_STAKED_ASSET_ID, + reward_asset_id: DEFAULT_REWARD_ASSET_ID, + reward_rate_per_block: DEFAULT_REWARD_RATE_PER_BLOCK, + admin: PermissionedAccountId::get(), + expiry_block: DEFAULT_EXPIRE_AFTER + 10, + total_tokens_staked: 0, + reward_per_token_stored: 0, + last_update_block: 0, + account: StakingRewards::pool_account_id(&0), + } + ), + ( + 1, + PoolInfo { + staked_asset_id, + reward_asset_id, + reward_rate_per_block, + admin, + total_tokens_staked: 0, + expiry_block: expected_expiry_block, + reward_per_token_stored: 0, + last_update_block: 0, + account: StakingRewards::pool_account_id(&1), + } + ) + ] + ); + }); + } + + #[test] + fn success_same_assets() { + new_test_ext().execute_with(|| { + assert_eq!(NextPoolId::::get(), 0); + + System::set_block_number(10); + let expected_expiry_block = DEFAULT_EXPIRE_AFTER + 10; + + // Create a pool with the same staking and reward asset. + let asset = NativeOrWithId::::Native; + assert_ok!(StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(asset.clone()), + Box::new(asset.clone()), + DEFAULT_REWARD_RATE_PER_BLOCK, + DispatchTime::After(DEFAULT_EXPIRE_AFTER), + Some(PermissionedAccountId::get()) + )); + + // Event is emitted. + assert_eq!( + events(), + [Event::::PoolCreated { + creator: PermissionedAccountId::get(), + pool_id: 0, + staked_asset_id: asset.clone(), + reward_asset_id: asset.clone(), + reward_rate_per_block: DEFAULT_REWARD_RATE_PER_BLOCK, + expiry_block: expected_expiry_block, + admin: PermissionedAccountId::get(), + }] + ); + + // State is updated correctly. + assert_eq!(NextPoolId::::get(), 1); + assert_eq!( + pools(), + vec![( + 0, + PoolInfo { + staked_asset_id: asset.clone(), + reward_asset_id: asset, + reward_rate_per_block: DEFAULT_REWARD_RATE_PER_BLOCK, + expiry_block: expected_expiry_block, + admin: PermissionedAccountId::get(), + total_tokens_staked: 0, + reward_per_token_stored: 0, + last_update_block: 0, + account: StakingRewards::pool_account_id(&0), + } + )] + ); + }) + } + + #[test] + fn fails_for_non_existent_asset() { + new_test_ext().execute_with(|| { + let valid_asset = NativeOrWithId::::WithId(1); + let invalid_asset = NativeOrWithId::::WithId(200); + + assert_err!( + StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(valid_asset.clone()), + Box::new(invalid_asset.clone()), + 10, + DispatchTime::After(10u64), + None + ), + Error::::NonExistentAsset + ); + + assert_err!( + StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(invalid_asset.clone()), + Box::new(valid_asset.clone()), + 10, + DispatchTime::After(10u64), + None + ), + Error::::NonExistentAsset + ); + + assert_err!( + StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(invalid_asset.clone()), + Box::new(invalid_asset.clone()), + 10, + DispatchTime::After(10u64), + None + ), + Error::::NonExistentAsset + ); + }) + } + + #[test] + fn fails_for_not_permissioned() { + new_test_ext().execute_with(|| { + let user = 100; + let staked_asset_id = NativeOrWithId::::Native; + let reward_asset_id = NativeOrWithId::::WithId(1); + let reward_rate_per_block = 100; + let expiry_block = 100u64; + assert_err!( + StakingRewards::create_pool( + RuntimeOrigin::signed(user), + Box::new(staked_asset_id.clone()), + Box::new(reward_asset_id.clone()), + reward_rate_per_block, + DispatchTime::After(expiry_block), + None + ), + BadOrigin + ); + }); + } + + #[test] + fn create_pool_with_caller_admin() { + new_test_ext().execute_with(|| { + assert_eq!(NextPoolId::::get(), 0); + + System::set_block_number(10); + let expected_expiry_block = DEFAULT_EXPIRE_AFTER + 10; + + assert_ok!(StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(DEFAULT_STAKED_ASSET_ID), + Box::new(DEFAULT_REWARD_ASSET_ID), + DEFAULT_REWARD_RATE_PER_BLOCK, + DispatchTime::After(DEFAULT_EXPIRE_AFTER), + None, + )); + + assert_eq!( + events(), + [Event::::PoolCreated { + creator: PermissionedAccountId::get(), + pool_id: 0, + staked_asset_id: DEFAULT_STAKED_ASSET_ID, + reward_asset_id: DEFAULT_REWARD_ASSET_ID, + reward_rate_per_block: DEFAULT_REWARD_RATE_PER_BLOCK, + expiry_block: expected_expiry_block, + admin: PermissionedAccountId::get(), + }] + ); + + assert_eq!(Pools::::get(0).unwrap().admin, PermissionedAccountId::get()); + }); + } +} + +mod stake { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + let user = 1; + create_default_pool(); + let pool_id = 0; + let initial_balance = >::reducible_balance( + 1, + &user, + Preservation::Expendable, + Fortitude::Force, + ); + + // User stakes tokens + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(user), pool_id, 1000)); + + // Check that the user's staked amount is updated + assert_eq!(PoolStakers::::get(pool_id, user).unwrap().amount, 1000); + + // Event is emitted. + assert_eq!( + *events().last().unwrap(), + Event::::Staked { staker: user, amount: 1000, pool_id: 0 } + ); + + // Check that the pool's total tokens staked is updated + assert_eq!(Pools::::get(pool_id).unwrap().total_tokens_staked, 1000); + + // Check user's frozen balance is updated + assert_eq!( + >::reducible_balance( + 1, + &user, + Preservation::Expendable, + Fortitude::Force, + ), + // - extra 1 for ed + initial_balance - 1000 - 1 + ); + + // User stakes more tokens + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(user), pool_id, 500)); + + // Event is emitted. + assert_eq!( + *events().last().unwrap(), + Event::::Staked { staker: user, amount: 500, pool_id: 0 } + ); + + // Check that the user's staked amount is updated + assert_eq!(PoolStakers::::get(pool_id, user).unwrap().amount, 1000 + 500); + + // Check that the pool's total tokens staked is updated + assert_eq!(Pools::::get(pool_id).unwrap().total_tokens_staked, 1000 + 500); + + assert_eq!( + >::reducible_balance( + 1, + &user, + Preservation::Expendable, + Fortitude::Force, + ), + // - extra 1 for ed + initial_balance - 1500 - 1 + ); + + // Event is emitted. + assert_eq!(events(), []); + }); + } + + #[test] + fn fails_for_non_existent_pool() { + new_test_ext().execute_with(|| { + let user = 1; + assert_err!( + StakingRewards::stake(RuntimeOrigin::signed(user), 999, 1000), + Error::::NonExistentPool + ); + }); + } + + #[test] + fn fails_for_insufficient_balance() { + new_test_ext().execute_with(|| { + let user = 1; + create_default_pool(); + let pool_id = 0; + let initial_balance = >::reducible_balance( + 1, + &user, + Preservation::Expendable, + Fortitude::Force, + ); + assert_err!( + StakingRewards::stake(RuntimeOrigin::signed(user), pool_id, initial_balance + 1), + TokenError::FundsUnavailable, + ); + }) + } +} + +mod unstake { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + let user = 1; + create_default_pool(); + let pool_id = 0; + + // User stakes tokens + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(user), pool_id, 1000)); + + // User unstakes tokens + assert_ok!(StakingRewards::unstake(RuntimeOrigin::signed(user), pool_id, 500, None)); + + // Event is emitted. + assert_eq!( + *events().last().unwrap(), + Event::::Unstaked { + caller: user, + staker: user, + amount: 500, + pool_id: 0 + } + ); + + // Check that the user's staked amount is updated + assert_eq!(PoolStakers::::get(pool_id, user).unwrap().amount, 500); + + // Check that the pool's total tokens staked is updated + assert_eq!(Pools::::get(pool_id).unwrap().total_tokens_staked, 500); + + // User unstakes remaining tokens + assert_ok!(StakingRewards::unstake(RuntimeOrigin::signed(user), pool_id, 500, None)); + + // Check that the storage items is removed since stake amount and rewards are zero. + assert!(PoolStakers::::get(pool_id, user).is_none()); + + // Check that the pool's total tokens staked is zero + assert_eq!(Pools::::get(pool_id).unwrap().total_tokens_staked, 0); + }); + } + + #[test] + fn unstake_for_other() { + new_test_ext().execute_with(|| { + let staker = 1; + let caller = 2; + let pool_id = 0; + let init_block = System::block_number(); + + create_default_pool(); + + // User stakes tokens + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker), pool_id, 1000)); + + // Fails to unstake for other since pool is still active + assert_noop!( + StakingRewards::unstake(RuntimeOrigin::signed(caller), pool_id, 500, Some(staker)), + BadOrigin, + ); + + System::set_block_number(init_block + DEFAULT_EXPIRE_AFTER + 1); + + assert_ok!(StakingRewards::unstake( + RuntimeOrigin::signed(caller), + pool_id, + 500, + Some(staker) + )); + + // Event is emitted. + assert_eq!( + *events().last().unwrap(), + Event::::Unstaked { caller, staker, amount: 500, pool_id: 0 } + ); + }); + } + + #[test] + fn fails_for_non_existent_pool() { + new_test_ext().execute_with(|| { + let user = 1; + let non_existent_pool_id = 999; + + // User tries to unstake tokens from a non-existent pool + assert_err!( + StakingRewards::unstake( + RuntimeOrigin::signed(user), + non_existent_pool_id, + 500, + None + ), + Error::::NonExistentPool + ); + }); + } + + #[test] + fn fails_for_insufficient_staked_amount() { + new_test_ext().execute_with(|| { + let user = 1; + create_default_pool(); + let pool_id = 0; + + // User stakes tokens + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(user), pool_id, 1000)); + + // User tries to unstake more tokens than they have staked + assert_err!( + StakingRewards::unstake(RuntimeOrigin::signed(user), pool_id, 1500, None), + Error::::NotEnoughTokens + ); + }); + } +} + +mod harvest_rewards { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + let staker = 1; + let pool_id = 0; + let reward_asset_id = NativeOrWithId::::Native; + create_default_pool(); + + // Stake + System::set_block_number(10); + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker), pool_id, 1000)); + + // Harvest + System::set_block_number(20); + let balance_before: ::Balance = + <::Assets>::balance(reward_asset_id.clone(), &staker); + assert_ok!(StakingRewards::harvest_rewards( + RuntimeOrigin::signed(staker), + pool_id, + None + )); + let balance_after = + <::Assets>::balance(reward_asset_id.clone(), &staker); + + // Assert + assert_eq!( + balance_after - balance_before, + 10 * Pools::::get(pool_id).unwrap().reward_rate_per_block + ); + assert_eq!( + *events().last().unwrap(), + Event::::RewardsHarvested { + caller: staker, + staker, + pool_id, + amount: 10 * Pools::::get(pool_id).unwrap().reward_rate_per_block + } + ); + }); + } + + #[test] + fn harvest_for_other() { + new_test_ext().execute_with(|| { + let caller = 2; + let staker = 1; + let pool_id = 0; + let init_block = System::block_number(); + + create_default_pool(); + + // Stake + System::set_block_number(10); + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker), pool_id, 1000)); + + System::set_block_number(20); + + // Fails to harvest for staker since pool is still active + assert_noop!( + StakingRewards::harvest_rewards( + RuntimeOrigin::signed(caller), + pool_id, + Some(staker) + ), + BadOrigin + ); + + System::set_block_number(init_block + DEFAULT_EXPIRE_AFTER + 1); + + // Harvest for staker + assert_ok!(StakingRewards::harvest_rewards( + RuntimeOrigin::signed(caller), + pool_id, + Some(staker), + )); + + assert!(matches!( + events().last().unwrap(), + Event::::RewardsHarvested { + caller, + staker, + pool_id, + .. + } if caller == caller && staker == staker && pool_id == pool_id + )); + }); + } + + #[test] + fn fails_for_non_existent_staker() { + new_test_ext().execute_with(|| { + let non_existent_staker = 999; + + create_default_pool(); + assert_err!( + StakingRewards::harvest_rewards( + RuntimeOrigin::signed(non_existent_staker), + 0, + None + ), + Error::::NonExistentStaker + ); + }); + } + + #[test] + fn fails_for_non_existent_pool() { + new_test_ext().execute_with(|| { + let staker = 1; + let non_existent_pool_id = 999; + + assert_err!( + StakingRewards::harvest_rewards( + RuntimeOrigin::signed(staker), + non_existent_pool_id, + None, + ), + Error::::NonExistentPool + ); + }); + } +} + +mod set_pool_admin { + use super::*; + + #[test] + fn success_signed_admin() { + new_test_ext().execute_with(|| { + let admin = 1; + let new_admin = 2; + let pool_id = 0; + create_default_pool(); + + // Modify the pool admin + assert_ok!(StakingRewards::set_pool_admin( + RuntimeOrigin::signed(admin), + pool_id, + new_admin, + )); + + // Check state + assert_eq!( + *events().last().unwrap(), + Event::::PoolAdminModified { pool_id, new_admin } + ); + assert_eq!(Pools::::get(pool_id).unwrap().admin, new_admin); + }); + } + + #[test] + fn success_permissioned_admin() { + new_test_ext().execute_with(|| { + let new_admin = 2; + let pool_id = 0; + create_default_pool_permissioned_admin(); + + // Modify the pool admin + assert_ok!(StakingRewards::set_pool_admin(RuntimeOrigin::root(), pool_id, new_admin)); + + // Check state + assert_eq!( + *events().last().unwrap(), + Event::::PoolAdminModified { pool_id, new_admin } + ); + assert_eq!(Pools::::get(pool_id).unwrap().admin, new_admin); + }); + } + + #[test] + fn fails_for_non_existent_pool() { + new_test_ext().execute_with(|| { + let admin = 1; + let new_admin = 2; + let non_existent_pool_id = 999; + + assert_err!( + StakingRewards::set_pool_admin( + RuntimeOrigin::signed(admin), + non_existent_pool_id, + new_admin + ), + Error::::NonExistentPool + ); + }); + } + + #[test] + fn fails_for_non_admin() { + new_test_ext().execute_with(|| { + let new_admin = 2; + let non_admin = 3; + let pool_id = 0; + create_default_pool(); + + assert_err!( + StakingRewards::set_pool_admin( + RuntimeOrigin::signed(non_admin), + pool_id, + new_admin + ), + BadOrigin + ); + }); + } +} + +mod set_pool_expiry_block { + use super::*; + + #[test] + fn success_permissioned_admin() { + new_test_ext().execute_with(|| { + let pool_id = 0; + let new_expiry_block = System::block_number() + DEFAULT_EXPIRE_AFTER + 1u64; + create_default_pool_permissioned_admin(); + + assert_ok!(StakingRewards::set_pool_expiry_block( + RuntimeOrigin::root(), + pool_id, + DispatchTime::At(new_expiry_block), + )); + + // Check state + assert_eq!(Pools::::get(pool_id).unwrap().expiry_block, new_expiry_block); + assert_eq!( + *events().last().unwrap(), + Event::::PoolExpiryBlockModified { pool_id, new_expiry_block } + ); + }); + } + + #[test] + fn success_signed_admin() { + new_test_ext().execute_with(|| { + let admin = 1; + let pool_id = 0; + let new_expiry_block = System::block_number() + DEFAULT_EXPIRE_AFTER + 1u64; + create_default_pool(); + + assert_ok!(StakingRewards::set_pool_expiry_block( + RuntimeOrigin::signed(admin), + pool_id, + DispatchTime::At(new_expiry_block) + )); + + // Check state + assert_eq!(Pools::::get(pool_id).unwrap().expiry_block, new_expiry_block); + assert_eq!( + *events().last().unwrap(), + Event::::PoolExpiryBlockModified { pool_id, new_expiry_block } + ); + }); + } + + #[test] + fn extends_reward_accumulation() { + new_test_ext().execute_with(|| { + let admin = 1; + let staker = 2; + let pool_id = 0; + let new_expiry_block = 300u64; + System::set_block_number(10); + create_default_pool(); + + // Regular reward accumulation + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker), pool_id, 1000)); + System::set_block_number(20); + assert_hypothetically_earned( + staker, + DEFAULT_REWARD_RATE_PER_BLOCK * 10, + pool_id, + NativeOrWithId::::Native, + ); + + // Expiry was block 210, so earned 200 at block 250 + System::set_block_number(250); + assert_hypothetically_earned( + staker, + DEFAULT_REWARD_RATE_PER_BLOCK * 200, + pool_id, + NativeOrWithId::::Native, + ); + + // Extend expiry 50 more blocks + assert_ok!(StakingRewards::set_pool_expiry_block( + RuntimeOrigin::signed(admin), + pool_id, + DispatchTime::At(new_expiry_block) + )); + System::set_block_number(350); + + // Staker has been in pool with rewards active for 250 blocks total + assert_hypothetically_earned( + staker, + DEFAULT_REWARD_RATE_PER_BLOCK * 250, + pool_id, + NativeOrWithId::::Native, + ); + }); + } + + #[test] + fn fails_to_cutback_expiration() { + new_test_ext().execute_with(|| { + let admin = 1; + let pool_id = 0; + create_default_pool(); + + assert_noop!( + StakingRewards::set_pool_expiry_block( + RuntimeOrigin::signed(admin), + pool_id, + DispatchTime::After(30) + ), + Error::::ExpiryCut + ); + }); + } + + #[test] + fn fails_for_non_existent_pool() { + new_test_ext().execute_with(|| { + let admin = 1; + let non_existent_pool_id = 999; + let new_expiry_block = 200u64; + + assert_err!( + StakingRewards::set_pool_expiry_block( + RuntimeOrigin::signed(admin), + non_existent_pool_id, + DispatchTime::After(new_expiry_block) + ), + Error::::NonExistentPool + ); + }); + } + + #[test] + fn fails_for_non_admin() { + new_test_ext().execute_with(|| { + let non_admin = 2; + let pool_id = 0; + let new_expiry_block = 200u64; + create_default_pool(); + + assert_err!( + StakingRewards::set_pool_expiry_block( + RuntimeOrigin::signed(non_admin), + pool_id, + DispatchTime::After(new_expiry_block) + ), + BadOrigin + ); + }); + } + + #[test] + fn fails_for_expiry_block_in_the_past() { + new_test_ext().execute_with(|| { + let admin = 1; + let pool_id = 0; + create_default_pool(); + System::set_block_number(50); + assert_err!( + StakingRewards::set_pool_expiry_block( + RuntimeOrigin::signed(admin), + pool_id, + DispatchTime::At(40u64) + ), + Error::::ExpiryBlockMustBeInTheFuture + ); + }); + } +} + +mod set_pool_reward_rate_per_block { + use super::*; + + #[test] + fn success_signed_admin() { + new_test_ext().execute_with(|| { + let pool_id = 0; + let new_reward_rate = 200; + create_default_pool(); + + // Pool Admin can modify + assert_ok!(StakingRewards::set_pool_reward_rate_per_block( + RuntimeOrigin::signed(DEFAULT_ADMIN), + pool_id, + new_reward_rate + )); + + // Check state + assert_eq!( + Pools::::get(pool_id).unwrap().reward_rate_per_block, + new_reward_rate + ); + + // Check event + assert_eq!( + *events().last().unwrap(), + Event::::PoolRewardRateModified { + pool_id, + new_reward_rate_per_block: new_reward_rate + } + ); + }); + } + + #[test] + fn success_permissioned_admin() { + new_test_ext().execute_with(|| { + let pool_id = 0; + let new_reward_rate = 200; + create_default_pool_permissioned_admin(); + + // Root can modify + assert_ok!(StakingRewards::set_pool_reward_rate_per_block( + RuntimeOrigin::root(), + pool_id, + new_reward_rate + )); + + // Check state + assert_eq!( + Pools::::get(pool_id).unwrap().reward_rate_per_block, + new_reward_rate + ); + + // Check event + assert_eq!( + *events().last().unwrap(), + Event::::PoolRewardRateModified { + pool_id, + new_reward_rate_per_block: new_reward_rate + } + ); + }); + } + + #[test] + fn staker_rewards_are_affected_correctly() { + new_test_ext().execute_with(|| { + let admin = 1; + let staker = 2; + let pool_id = 0; + let new_reward_rate = 150; + create_default_pool(); + + // Stake some tokens, and accumulate 10 blocks of rewards at the default pool rate (100) + System::set_block_number(10); + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker), pool_id, 1000)); + System::set_block_number(20); + + // Increase the reward rate + assert_ok!(StakingRewards::set_pool_reward_rate_per_block( + RuntimeOrigin::signed(admin), + pool_id, + new_reward_rate + )); + + // Accumulate 10 blocks of rewards at the new rate + System::set_block_number(30); + + // Check that rewards are calculated correctly with the updated rate + assert_hypothetically_earned( + staker, + 10 * 100 + 10 * new_reward_rate, + pool_id, + NativeOrWithId::::Native, + ); + }); + } + + #[test] + fn fails_for_non_existent_pool() { + new_test_ext().execute_with(|| { + let admin = 1; + let non_existent_pool_id = 999; + let new_reward_rate = 200; + + assert_err!( + StakingRewards::set_pool_reward_rate_per_block( + RuntimeOrigin::signed(admin), + non_existent_pool_id, + new_reward_rate + ), + Error::::NonExistentPool + ); + }); + } + + #[test] + fn fails_for_non_admin() { + new_test_ext().execute_with(|| { + let non_admin = 2; + let pool_id = 0; + let new_reward_rate = 200; + create_default_pool(); + + assert_err!( + StakingRewards::set_pool_reward_rate_per_block( + RuntimeOrigin::signed(non_admin), + pool_id, + new_reward_rate + ), + BadOrigin + ); + }); + } + + #[test] + fn fails_to_decrease() { + new_test_ext().execute_with(|| { + create_default_pool_permissioned_admin(); + + assert_noop!( + StakingRewards::set_pool_reward_rate_per_block( + RuntimeOrigin::root(), + 0, + DEFAULT_REWARD_RATE_PER_BLOCK - 1 + ), + Error::::RewardRateCut + ); + }); + } +} + +mod deposit_reward_tokens { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + let depositor = 1; + let pool_id = 0; + let amount = 1000; + let reward_asset_id = NativeOrWithId::::Native; + create_default_pool(); + let pool_account_id = StakingRewards::pool_account_id(&pool_id); + + let depositor_balance_before = + <::Assets>::balance(reward_asset_id.clone(), &depositor); + let pool_balance_before = <::Assets>::balance( + reward_asset_id.clone(), + &pool_account_id, + ); + assert_ok!(StakingRewards::deposit_reward_tokens( + RuntimeOrigin::signed(depositor), + pool_id, + amount + )); + let depositor_balance_after = + <::Assets>::balance(reward_asset_id.clone(), &depositor); + let pool_balance_after = + <::Assets>::balance(reward_asset_id, &pool_account_id); + + assert_eq!(pool_balance_after - pool_balance_before, amount); + assert_eq!(depositor_balance_before - depositor_balance_after, amount); + }); + } + + #[test] + fn fails_for_non_existent_pool() { + new_test_ext().execute_with(|| { + assert_err!( + StakingRewards::deposit_reward_tokens(RuntimeOrigin::signed(1), 999, 100), + Error::::NonExistentPool + ); + }); + } + + #[test] + fn fails_for_insufficient_balance() { + new_test_ext().execute_with(|| { + create_default_pool(); + assert_err!( + StakingRewards::deposit_reward_tokens(RuntimeOrigin::signed(1), 0, 100_000_000), + ArithmeticError::Underflow + ); + }); + } +} + +mod cleanup_pool { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + let pool_id = 0; + let admin = DEFAULT_ADMIN; + let admin_balance_before = >::balance(&admin); + + create_default_pool(); + assert!(Pools::::get(pool_id).is_some()); + + assert_ok!(StakingRewards::cleanup_pool(RuntimeOrigin::signed(admin), pool_id)); + + assert_eq!( + >::balance(&admin), + // `100_000` initial pool account balance from Genesis config + admin_balance_before + 100_000, + ); + assert_eq!(Pools::::get(pool_id), None); + assert_eq!(PoolStakers::::iter_prefix_values(pool_id).count(), 0); + assert_eq!(PoolCost::::get(pool_id), None); + }); + } + + #[test] + fn success_only_when_pool_empty() { + new_test_ext().execute_with(|| { + let pool_id = 0; + let staker = 20; + let admin = DEFAULT_ADMIN; + + create_default_pool(); + + // stake to prevent pool cleanup + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker), pool_id, 100)); + + assert_noop!( + StakingRewards::cleanup_pool(RuntimeOrigin::signed(admin), pool_id), + Error::::NonEmptyPool + ); + + // unstake partially + assert_ok!(StakingRewards::unstake(RuntimeOrigin::signed(staker), pool_id, 50, None)); + + assert_noop!( + StakingRewards::cleanup_pool(RuntimeOrigin::signed(admin), pool_id), + Error::::NonEmptyPool + ); + + // unstake all + assert_ok!(StakingRewards::unstake(RuntimeOrigin::signed(staker), pool_id, 50, None)); + + assert_ok!(StakingRewards::cleanup_pool(RuntimeOrigin::signed(admin), pool_id),); + + assert_eq!(Pools::::get(pool_id), None); + assert_eq!(PoolStakers::::iter_prefix_values(pool_id).count(), 0); + assert_eq!(PoolCost::::get(pool_id), None); + }); + } + + #[test] + fn fails_on_wrong_origin() { + new_test_ext().execute_with(|| { + let caller = 888; + let pool_id = 0; + create_default_pool(); + + assert_noop!( + StakingRewards::cleanup_pool(RuntimeOrigin::signed(caller), pool_id), + BadOrigin + ); + }); + } +} + +/// This integration test +/// 1. Considers 2 stakers each staking and unstaking at different intervals, asserts their +/// claimable rewards are adjusted as expected, and that harvesting works. +/// 2. Checks that rewards are correctly halted after the pool's expiry block, and resume when the +/// pool is extended. +/// 3. Checks that reward rates adjustment works correctly. +/// +/// Note: There are occasionally off by 1 errors due to rounding. In practice this is +/// insignificant. +#[test] +fn integration() { + new_test_ext().execute_with(|| { + let admin = 1; + let staker1 = 10u128; + let staker2 = 20; + let staked_asset_id = NativeOrWithId::::WithId(1); + let reward_asset_id = NativeOrWithId::::Native; + let reward_rate_per_block = 100; + let lifetime = 24u64.into(); + System::set_block_number(1); + assert_ok!(StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(staked_asset_id.clone()), + Box::new(reward_asset_id.clone()), + reward_rate_per_block, + DispatchTime::After(lifetime), + Some(admin) + )); + let pool_id = 0; + + // Block 7: Staker 1 stakes 100 tokens. + System::set_block_number(7); + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker1), pool_id, 100)); + // At this point + // - Staker 1 has earned 0 tokens. + // - Staker 1 is earning 100 tokens per block. + + // Check that Staker 1 has earned 0 tokens. + assert_hypothetically_earned(staker1, 0, pool_id, reward_asset_id.clone()); + + // Block 9: Staker 2 stakes 100 tokens. + System::set_block_number(9); + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker2), pool_id, 100)); + // At this point + // - Staker 1 has earned 200 (100*2) tokens. + // - Staker 2 has earned 0 tokens. + // - Staker 1 is earning 50 tokens per block. + // - Staker 2 is earning 50 tokens per block. + + // Check that Staker 1 has earned 200 tokens and Staker 2 has earned 0 tokens. + assert_hypothetically_earned(staker1, 200, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 0, pool_id, reward_asset_id.clone()); + + // Block 12: Staker 1 stakes an additional 100 tokens. + System::set_block_number(12); + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker1), pool_id, 100)); + // At this point + // - Staker 1 has earned 350 (200 + (50 * 3)) tokens. + // - Staker 2 has earned 150 (50 * 3) tokens. + // - Staker 1 is earning 66.66 tokens per block. + // - Staker 2 is earning 33.33 tokens per block. + + // Check that Staker 1 has earned 350 tokens and Staker 2 has earned 150 tokens. + assert_hypothetically_earned(staker1, 350, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 150, pool_id, reward_asset_id.clone()); + + // Block 22: Staker 1 unstakes 100 tokens. + System::set_block_number(22); + assert_ok!(StakingRewards::unstake(RuntimeOrigin::signed(staker1), pool_id, 100, None)); + // - Staker 1 has earned 1016 (350 + 66.66 * 10) tokens. + // - Staker 2 has earned 483 (150 + 33.33 * 10) tokens. + // - Staker 1 is earning 50 tokens per block. + // - Staker 2 is earning 50 tokens per block. + assert_hypothetically_earned(staker1, 1016, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 483, pool_id, reward_asset_id.clone()); + + // Block 23: Staker 1 unstakes 100 tokens. + System::set_block_number(23); + assert_ok!(StakingRewards::unstake(RuntimeOrigin::signed(staker1), pool_id, 100, None)); + // - Staker 1 has earned 1065 (1015 + 50) tokens. + // - Staker 2 has earned 533 (483 + 50) tokens. + // - Staker 1 is earning 0 tokens per block. + // - Staker 2 is earning 100 tokens per block. + assert_hypothetically_earned(staker1, 1066, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 533, pool_id, reward_asset_id.clone()); + + // Block 50: Stakers should only have earned 2 blocks worth of tokens (expiry is 25). + System::set_block_number(50); + // - Staker 1 has earned 1065 tokens. + // - Staker 2 has earned 733 (533 + 2 * 100) tokens. + // - Staker 1 is earning 0 tokens per block. + // - Staker 2 is earning 0 tokens per block. + assert_hypothetically_earned(staker1, 1066, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 733, pool_id, reward_asset_id.clone()); + + // Block 51: Extend the pool expiry block to 60. + System::set_block_number(51); + // - Staker 1 is earning 0 tokens per block. + // - Staker 2 is earning 100 tokens per block. + assert_ok!(StakingRewards::set_pool_expiry_block( + RuntimeOrigin::signed(admin), + pool_id, + DispatchTime::At(60u64), + )); + assert_hypothetically_earned(staker1, 1066, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 733, pool_id, reward_asset_id.clone()); + + // Block 53: Check rewards are resumed. + // - Staker 1 has earned 1065 tokens. + // - Staker 2 has earned 933 (733 + 2 * 100) tokens. + // - Staker 2 is earning 100 tokens per block. + System::set_block_number(53); + assert_hypothetically_earned(staker1, 1066, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 933, pool_id, reward_asset_id.clone()); + + // Block 55: Increase the block reward. + // - Staker 1 has earned 1065 tokens. + // - Staker 2 has earned 1133 (933 + 2 * 100) tokens. + // - Staker 2 is earning 50 tokens per block. + System::set_block_number(55); + assert_ok!(StakingRewards::set_pool_reward_rate_per_block( + RuntimeOrigin::signed(admin), + pool_id, + 150 + )); + assert_hypothetically_earned(staker1, 1066, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 1133, pool_id, reward_asset_id.clone()); + + // Block 57: Staker2 harvests their rewards. + System::set_block_number(57); + // - Staker 2 has earned 1433 (1133 + 2 * 150) tokens. + assert_hypothetically_earned(staker2, 1433, pool_id, reward_asset_id.clone()); + // Get the pre-harvest balance. + let balance_before: ::Balance = + <::Assets>::balance(reward_asset_id.clone(), &staker2); + assert_ok!(StakingRewards::harvest_rewards(RuntimeOrigin::signed(staker2), pool_id, None)); + let balance_after = + <::Assets>::balance(reward_asset_id.clone(), &staker2); + assert_eq!(balance_after - balance_before, 1433u128); + + // Block 60: Check rewards were adjusted correctly. + // - Staker 1 has earned 1065 tokens. + // - Staker 2 has earned 450 (3 * 150) tokens. + System::set_block_number(60); + assert_hypothetically_earned(staker1, 1066, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 450, pool_id, reward_asset_id.clone()); + + // Finally, check events. + assert_eq!( + events(), + [ + Event::PoolCreated { + creator: PermissionedAccountId::get(), + pool_id, + staked_asset_id, + reward_asset_id, + reward_rate_per_block: 100, + expiry_block: 25, + admin, + }, + Event::Staked { staker: staker1, pool_id, amount: 100 }, + Event::Staked { staker: staker2, pool_id, amount: 100 }, + Event::Staked { staker: staker1, pool_id, amount: 100 }, + Event::Unstaked { caller: staker1, staker: staker1, pool_id, amount: 100 }, + Event::Unstaked { caller: staker1, staker: staker1, pool_id, amount: 100 }, + Event::PoolExpiryBlockModified { pool_id, new_expiry_block: 60 }, + Event::PoolRewardRateModified { pool_id, new_reward_rate_per_block: 150 }, + Event::RewardsHarvested { caller: staker2, staker: staker2, pool_id, amount: 1433 } + ] + ); + }); +} diff --git a/substrate/frame/asset-rewards/src/weights.rs b/substrate/frame/asset-rewards/src/weights.rs new file mode 100644 index 000000000000..c9e2d0fd251a --- /dev/null +++ b/substrate/frame/asset-rewards/src/weights.rs @@ -0,0 +1,368 @@ +// 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. + +//! Autogenerated weights for `pallet_asset_rewards` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` + +// Executed Command: +// target/production/substrate-node +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_asset_rewards +// --chain=dev +// --header=./substrate/HEADER-APACHE2 +// --output=./substrate/frame/asset-rewards/src/weights.rs +// --template=./substrate/.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for `pallet_asset_rewards`. +pub trait WeightInfo { + fn create_pool() -> Weight; + fn stake() -> Weight; + fn unstake() -> Weight; + fn harvest_rewards() -> Weight; + fn set_pool_reward_rate_per_block() -> Weight; + fn set_pool_admin() -> Weight; + fn set_pool_expiry_block() -> Weight; + fn deposit_reward_tokens() -> Weight; + fn cleanup_pool() -> Weight; +} + +/// Weights for `pallet_asset_rewards` using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: `Assets::Asset` (r:2 w:0) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::NextPoolId` (r:1 w:1) + /// Proof: `AssetRewards::NextPoolId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(373), added: 2848, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolCost` (r:0 w:1) + /// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::Pools` (r:0 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + fn create_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `495` + // Estimated: `6360` + // Minimum execution time: 62_655_000 picoseconds. + Weight::from_parts(63_723_000, 6360) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:1 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(105), added: 2580, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn stake() -> Weight { + // Proof Size summary in bytes: + // Measured: `935` + // Estimated: `3615` + // Minimum execution time: 54_463_000 picoseconds. + Weight::from_parts(55_974_000, 3615) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:1 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(105), added: 2580, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn unstake() -> Weight { + // Proof Size summary in bytes: + // Measured: `935` + // Estimated: `3615` + // Minimum execution time: 55_749_000 picoseconds. + Weight::from_parts(57_652_000, 3615) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:0) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + fn harvest_rewards() -> Weight { + // Proof Size summary in bytes: + // Measured: `1021` + // Estimated: `6208` + // Minimum execution time: 69_372_000 picoseconds. + Weight::from_parts(70_278_000, 6208) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + fn set_pool_reward_rate_per_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `347` + // Estimated: `3615` + // Minimum execution time: 19_284_000 picoseconds. + Weight::from_parts(19_791_000, 3615) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + fn set_pool_admin() -> Weight { + // Proof Size summary in bytes: + // Measured: `347` + // Estimated: `3615` + // Minimum execution time: 17_388_000 picoseconds. + Weight::from_parts(18_390_000, 3615) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + fn set_pool_expiry_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `347` + // Estimated: `3615` + // Minimum execution time: 19_780_000 picoseconds. + Weight::from_parts(20_676_000, 3615) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:0) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn deposit_reward_tokens() -> Weight { + // Proof Size summary in bytes: + // Measured: `840` + // Estimated: `6208` + // Minimum execution time: 57_746_000 picoseconds. + Weight::from_parts(59_669_000, 6208) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:0) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolCost` (r:1 w:1) + /// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(373), added: 2848, mode: `MaxEncodedLen`) + fn cleanup_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `1236` + // Estimated: `6208` + // Minimum execution time: 110_443_000 picoseconds. + Weight::from_parts(113_149_000, 6208) + .saturating_add(T::DbWeight::get().reads(9_u64)) + .saturating_add(T::DbWeight::get().writes(8_u64)) + } +} + +// For backwards compatibility and tests. +impl WeightInfo for () { + /// Storage: `Assets::Asset` (r:2 w:0) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::NextPoolId` (r:1 w:1) + /// Proof: `AssetRewards::NextPoolId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(373), added: 2848, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolCost` (r:0 w:1) + /// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::Pools` (r:0 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + fn create_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `495` + // Estimated: `6360` + // Minimum execution time: 62_655_000 picoseconds. + Weight::from_parts(63_723_000, 6360) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:1 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(105), added: 2580, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn stake() -> Weight { + // Proof Size summary in bytes: + // Measured: `935` + // Estimated: `3615` + // Minimum execution time: 54_463_000 picoseconds. + Weight::from_parts(55_974_000, 3615) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:1 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(105), added: 2580, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn unstake() -> Weight { + // Proof Size summary in bytes: + // Measured: `935` + // Estimated: `3615` + // Minimum execution time: 55_749_000 picoseconds. + Weight::from_parts(57_652_000, 3615) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:0) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + fn harvest_rewards() -> Weight { + // Proof Size summary in bytes: + // Measured: `1021` + // Estimated: `6208` + // Minimum execution time: 69_372_000 picoseconds. + Weight::from_parts(70_278_000, 6208) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + fn set_pool_reward_rate_per_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `347` + // Estimated: `3615` + // Minimum execution time: 19_284_000 picoseconds. + Weight::from_parts(19_791_000, 3615) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + fn set_pool_admin() -> Weight { + // Proof Size summary in bytes: + // Measured: `347` + // Estimated: `3615` + // Minimum execution time: 17_388_000 picoseconds. + Weight::from_parts(18_390_000, 3615) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + fn set_pool_expiry_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `347` + // Estimated: `3615` + // Minimum execution time: 19_780_000 picoseconds. + Weight::from_parts(20_676_000, 3615) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:0) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn deposit_reward_tokens() -> Weight { + // Proof Size summary in bytes: + // Measured: `840` + // Estimated: `6208` + // Minimum execution time: 57_746_000 picoseconds. + Weight::from_parts(59_669_000, 6208) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:0) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolCost` (r:1 w:1) + /// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(373), added: 2848, mode: `MaxEncodedLen`) + fn cleanup_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `1236` + // Estimated: `6208` + // Minimum execution time: 110_443_000 picoseconds. + Weight::from_parts(113_149_000, 6208) + .saturating_add(RocksDbWeight::get().reads(9_u64)) + .saturating_add(RocksDbWeight::get().writes(8_u64)) + } +} diff --git a/substrate/frame/support/src/traits.rs b/substrate/frame/support/src/traits.rs index 728426cc84c7..4a83c809a6a5 100644 --- a/substrate/frame/support/src/traits.rs +++ b/substrate/frame/support/src/traits.rs @@ -96,8 +96,9 @@ mod storage; #[cfg(feature = "experimental")] pub use storage::MaybeConsideration; pub use storage::{ - Consideration, Footprint, Incrementable, Instance, LinearStoragePrice, PartialStorageInfoTrait, - StorageInfo, StorageInfoTrait, StorageInstance, TrackedStorageKey, WhitelistedStorageKeys, + Consideration, ConstantStoragePrice, Footprint, Incrementable, Instance, LinearStoragePrice, + PartialStorageInfoTrait, StorageInfo, StorageInfoTrait, StorageInstance, TrackedStorageKey, + WhitelistedStorageKeys, }; mod dispatch; diff --git a/substrate/frame/support/src/traits/storage.rs b/substrate/frame/support/src/traits/storage.rs index 2b8e43707389..676b73e03d3c 100644 --- a/substrate/frame/support/src/traits/storage.rs +++ b/substrate/frame/support/src/traits/storage.rs @@ -200,6 +200,18 @@ where } } +/// Constant `Price` regardless of the given [`Footprint`]. +pub struct ConstantStoragePrice(PhantomData<(Price, Balance)>); +impl Convert for ConstantStoragePrice +where + Price: Get, + Balance: From + sp_runtime::Saturating, +{ + fn convert(_: Footprint) -> Balance { + Price::get() + } +} + /// Some sort of cost taken from account temporarily in order to offset the cost to the chain of /// holding some data [`Footprint`] in state. /// diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index 17a7c02e8259..fc0b2d5a140e 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -57,6 +57,7 @@ std = [ "pallet-asset-conversion-tx-payment?/std", "pallet-asset-conversion?/std", "pallet-asset-rate?/std", + "pallet-asset-rewards?/std", "pallet-asset-tx-payment?/std", "pallet-assets-freezer?/std", "pallet-assets?/std", @@ -256,6 +257,7 @@ runtime-benchmarks = [ "pallet-asset-conversion-tx-payment?/runtime-benchmarks", "pallet-asset-conversion?/runtime-benchmarks", "pallet-asset-rate?/runtime-benchmarks", + "pallet-asset-rewards?/runtime-benchmarks", "pallet-asset-tx-payment?/runtime-benchmarks", "pallet-assets-freezer?/runtime-benchmarks", "pallet-assets?/runtime-benchmarks", @@ -386,6 +388,7 @@ try-runtime = [ "pallet-asset-conversion-tx-payment?/try-runtime", "pallet-asset-conversion?/try-runtime", "pallet-asset-rate?/try-runtime", + "pallet-asset-rewards?/try-runtime", "pallet-asset-tx-payment?/try-runtime", "pallet-assets-freezer?/try-runtime", "pallet-assets?/try-runtime", @@ -543,7 +546,7 @@ with-tracing = [ "sp-tracing?/with-tracing", "sp-tracing?/with-tracing", ] -runtime-full = ["assets-common", "binary-merkle-tree", "bp-header-chain", "bp-messages", "bp-parachains", "bp-polkadot", "bp-polkadot-core", "bp-relayers", "bp-runtime", "bp-test-utils", "bp-xcm-bridge-hub", "bp-xcm-bridge-hub-router", "bridge-hub-common", "bridge-runtime-common", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", "cumulus-pallet-parachain-system-proc-macro", "cumulus-pallet-session-benchmarking", "cumulus-pallet-solo-to-para", "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", "cumulus-ping", "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-primitives-proof-size-hostfunction", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-timestamp", "cumulus-primitives-utility", "frame-benchmarking", "frame-benchmarking-pallet-pov", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-executive", "frame-metadata-hash-extension", "frame-support", "frame-support-procedural", "frame-support-procedural-tools-derive", "frame-system", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", "pallet-alliance", "pallet-asset-conversion", "pallet-asset-conversion-ops", "pallet-asset-conversion-tx-payment", "pallet-asset-rate", "pallet-asset-tx-payment", "pallet-assets", "pallet-assets-freezer", "pallet-atomic-swap", "pallet-aura", "pallet-authority-discovery", "pallet-authorship", "pallet-babe", "pallet-bags-list", "pallet-balances", "pallet-beefy", "pallet-beefy-mmr", "pallet-bounties", "pallet-bridge-grandpa", "pallet-bridge-messages", "pallet-bridge-parachains", "pallet-bridge-relayers", "pallet-broker", "pallet-child-bounties", "pallet-collator-selection", "pallet-collective", "pallet-collective-content", "pallet-contracts", "pallet-contracts-proc-macro", "pallet-contracts-uapi", "pallet-conviction-voting", "pallet-core-fellowship", "pallet-delegated-staking", "pallet-democracy", "pallet-dev-mode", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-glutton", "pallet-grandpa", "pallet-identity", "pallet-im-online", "pallet-indices", "pallet-insecure-randomness-collective-flip", "pallet-lottery", "pallet-membership", "pallet-message-queue", "pallet-migrations", "pallet-mixnet", "pallet-mmr", "pallet-multisig", "pallet-nft-fractionalization", "pallet-nfts", "pallet-nfts-runtime-api", "pallet-nis", "pallet-node-authorization", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", "pallet-paged-list", "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-ranked-collective", "pallet-recovery", "pallet-referenda", "pallet-remark", "pallet-revive", "pallet-revive-proc-macro", "pallet-revive-uapi", "pallet-root-offences", "pallet-root-testing", "pallet-safe-mode", "pallet-salary", "pallet-scheduler", "pallet-scored-pool", "pallet-session", "pallet-session-benchmarking", "pallet-skip-feeless-payment", "pallet-society", "pallet-staking", "pallet-staking-reward-curve", "pallet-staking-reward-fn", "pallet-staking-runtime-api", "pallet-state-trie-migration", "pallet-statement", "pallet-sudo", "pallet-timestamp", "pallet-tips", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-transaction-storage", "pallet-treasury", "pallet-tx-pause", "pallet-uniques", "pallet-utility", "pallet-verify-signature", "pallet-vesting", "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub", "pallet-xcm-bridge-hub-router", "parachains-common", "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-common", "polkadot-runtime-metrics", "polkadot-runtime-parachains", "polkadot-sdk-frame", "sc-chain-spec-derive", "sc-tracing-proc-macro", "slot-range-helper", "snowbridge-beacon-primitives", "snowbridge-core", "snowbridge-ethereum", "snowbridge-outbound-queue-merkle-tree", "snowbridge-outbound-queue-runtime-api", "snowbridge-pallet-ethereum-client", "snowbridge-pallet-ethereum-client-fixtures", "snowbridge-pallet-inbound-queue", "snowbridge-pallet-inbound-queue-fixtures", "snowbridge-pallet-outbound-queue", "snowbridge-pallet-system", "snowbridge-router-primitives", "snowbridge-runtime-common", "snowbridge-system-runtime-api", "sp-api", "sp-api-proc-macro", "sp-application-crypto", "sp-arithmetic", "sp-authority-discovery", "sp-block-builder", "sp-consensus-aura", "sp-consensus-babe", "sp-consensus-beefy", "sp-consensus-grandpa", "sp-consensus-pow", "sp-consensus-slots", "sp-core", "sp-crypto-ec-utils", "sp-crypto-hashing", "sp-crypto-hashing-proc-macro", "sp-debug-derive", "sp-externalities", "sp-genesis-builder", "sp-inherents", "sp-io", "sp-keyring", "sp-keystore", "sp-metadata-ir", "sp-mixnet", "sp-mmr-primitives", "sp-npos-elections", "sp-offchain", "sp-runtime", "sp-runtime-interface", "sp-runtime-interface-proc-macro", "sp-session", "sp-staking", "sp-state-machine", "sp-statement-store", "sp-std", "sp-storage", "sp-timestamp", "sp-tracing", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", "sp-version", "sp-version-proc-macro", "sp-wasm-interface", "sp-weights", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-bip39", "testnet-parachains-constants", "tracing-gum-proc-macro", "xcm-procedural", "xcm-runtime-apis"] +runtime-full = ["assets-common", "binary-merkle-tree", "bp-header-chain", "bp-messages", "bp-parachains", "bp-polkadot", "bp-polkadot-core", "bp-relayers", "bp-runtime", "bp-test-utils", "bp-xcm-bridge-hub", "bp-xcm-bridge-hub-router", "bridge-hub-common", "bridge-runtime-common", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", "cumulus-pallet-parachain-system-proc-macro", "cumulus-pallet-session-benchmarking", "cumulus-pallet-solo-to-para", "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", "cumulus-ping", "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-primitives-proof-size-hostfunction", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-timestamp", "cumulus-primitives-utility", "frame-benchmarking", "frame-benchmarking-pallet-pov", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-executive", "frame-metadata-hash-extension", "frame-support", "frame-support-procedural", "frame-support-procedural-tools-derive", "frame-system", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", "pallet-alliance", "pallet-asset-conversion", "pallet-asset-conversion-ops", "pallet-asset-conversion-tx-payment", "pallet-asset-rate", "pallet-asset-rewards", "pallet-asset-tx-payment", "pallet-assets", "pallet-assets-freezer", "pallet-atomic-swap", "pallet-aura", "pallet-authority-discovery", "pallet-authorship", "pallet-babe", "pallet-bags-list", "pallet-balances", "pallet-beefy", "pallet-beefy-mmr", "pallet-bounties", "pallet-bridge-grandpa", "pallet-bridge-messages", "pallet-bridge-parachains", "pallet-bridge-relayers", "pallet-broker", "pallet-child-bounties", "pallet-collator-selection", "pallet-collective", "pallet-collective-content", "pallet-contracts", "pallet-contracts-proc-macro", "pallet-contracts-uapi", "pallet-conviction-voting", "pallet-core-fellowship", "pallet-delegated-staking", "pallet-democracy", "pallet-dev-mode", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-glutton", "pallet-grandpa", "pallet-identity", "pallet-im-online", "pallet-indices", "pallet-insecure-randomness-collective-flip", "pallet-lottery", "pallet-membership", "pallet-message-queue", "pallet-migrations", "pallet-mixnet", "pallet-mmr", "pallet-multisig", "pallet-nft-fractionalization", "pallet-nfts", "pallet-nfts-runtime-api", "pallet-nis", "pallet-node-authorization", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", "pallet-paged-list", "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-ranked-collective", "pallet-recovery", "pallet-referenda", "pallet-remark", "pallet-revive", "pallet-revive-proc-macro", "pallet-revive-uapi", "pallet-root-offences", "pallet-root-testing", "pallet-safe-mode", "pallet-salary", "pallet-scheduler", "pallet-scored-pool", "pallet-session", "pallet-session-benchmarking", "pallet-skip-feeless-payment", "pallet-society", "pallet-staking", "pallet-staking-reward-curve", "pallet-staking-reward-fn", "pallet-staking-runtime-api", "pallet-state-trie-migration", "pallet-statement", "pallet-sudo", "pallet-timestamp", "pallet-tips", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-transaction-storage", "pallet-treasury", "pallet-tx-pause", "pallet-uniques", "pallet-utility", "pallet-verify-signature", "pallet-vesting", "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub", "pallet-xcm-bridge-hub-router", "parachains-common", "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-common", "polkadot-runtime-metrics", "polkadot-runtime-parachains", "polkadot-sdk-frame", "sc-chain-spec-derive", "sc-tracing-proc-macro", "slot-range-helper", "snowbridge-beacon-primitives", "snowbridge-core", "snowbridge-ethereum", "snowbridge-outbound-queue-merkle-tree", "snowbridge-outbound-queue-runtime-api", "snowbridge-pallet-ethereum-client", "snowbridge-pallet-ethereum-client-fixtures", "snowbridge-pallet-inbound-queue", "snowbridge-pallet-inbound-queue-fixtures", "snowbridge-pallet-outbound-queue", "snowbridge-pallet-system", "snowbridge-router-primitives", "snowbridge-runtime-common", "snowbridge-system-runtime-api", "sp-api", "sp-api-proc-macro", "sp-application-crypto", "sp-arithmetic", "sp-authority-discovery", "sp-block-builder", "sp-consensus-aura", "sp-consensus-babe", "sp-consensus-beefy", "sp-consensus-grandpa", "sp-consensus-pow", "sp-consensus-slots", "sp-core", "sp-crypto-ec-utils", "sp-crypto-hashing", "sp-crypto-hashing-proc-macro", "sp-debug-derive", "sp-externalities", "sp-genesis-builder", "sp-inherents", "sp-io", "sp-keyring", "sp-keystore", "sp-metadata-ir", "sp-mixnet", "sp-mmr-primitives", "sp-npos-elections", "sp-offchain", "sp-runtime", "sp-runtime-interface", "sp-runtime-interface-proc-macro", "sp-session", "sp-staking", "sp-state-machine", "sp-statement-store", "sp-std", "sp-storage", "sp-timestamp", "sp-tracing", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", "sp-version", "sp-version-proc-macro", "sp-wasm-interface", "sp-weights", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-bip39", "testnet-parachains-constants", "tracing-gum-proc-macro", "xcm-procedural", "xcm-runtime-apis"] runtime = [ "frame-benchmarking", "frame-benchmarking-pallet-pov", @@ -870,6 +873,11 @@ default-features = false optional = true path = "../substrate/frame/asset-rate" +[dependencies.pallet-asset-rewards] +default-features = false +optional = true +path = "../substrate/frame/asset-rewards" + [dependencies.pallet-asset-tx-payment] default-features = false optional = true diff --git a/umbrella/src/lib.rs b/umbrella/src/lib.rs index 3504f081f295..a132f16a2c33 100644 --- a/umbrella/src/lib.rs +++ b/umbrella/src/lib.rs @@ -312,6 +312,10 @@ pub use pallet_asset_conversion_tx_payment; #[cfg(feature = "pallet-asset-rate")] pub use pallet_asset_rate; +/// FRAME asset rewards pallet. +#[cfg(feature = "pallet-asset-rewards")] +pub use pallet_asset_rewards; + /// pallet to manage transaction payments in assets. #[cfg(feature = "pallet-asset-tx-payment")] pub use pallet_asset_tx_payment;