From 6dd4a71f702c1ce0afd4141b6d1b14809b809ba7 Mon Sep 17 00:00:00 2001
From: clangenb <37865735+clangenb@users.noreply.github.com>
Date: Wed, 18 Dec 2024 10:16:28 +0100
Subject: [PATCH 1/2] Migrate pallet-xcm benchmarks to benchmark v2 syntax
(#6501)
Migrates pallet-xcm benchmarks to benchmark v2 syntax
* Part of #6202
---
polkadot/xcm/pallet-xcm/src/benchmarking.rs | 446 ++++++++++++++------
1 file changed, 314 insertions(+), 132 deletions(-)
diff --git a/polkadot/xcm/pallet-xcm/src/benchmarking.rs b/polkadot/xcm/pallet-xcm/src/benchmarking.rs
index dd3c58c5dc77..3ca048057ee4 100644
--- a/polkadot/xcm/pallet-xcm/src/benchmarking.rs
+++ b/polkadot/xcm/pallet-xcm/src/benchmarking.rs
@@ -15,7 +15,7 @@
// along with Polkadot. If not, see .
use super::*;
-use frame_benchmarking::{benchmarks, whitelisted_caller, BenchmarkError, BenchmarkResult};
+use frame_benchmarking::v2::*;
use frame_support::{assert_ok, weights::Weight};
use frame_system::RawOrigin;
use xcm::latest::prelude::*;
@@ -83,32 +83,41 @@ pub trait Config: crate::Config {
fn get_asset() -> Asset;
}
-benchmarks! {
- send {
+#[benchmarks]
+mod benchmarks {
+ use super::*;
+
+ #[benchmark]
+ fn send() -> Result<(), BenchmarkError> {
let send_origin =
T::SendXcmOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
if T::SendXcmOrigin::try_origin(send_origin.clone()).is_err() {
return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))
}
let msg = Xcm(vec![ClearOrigin]);
- let versioned_dest: VersionedLocation = T::reachable_dest().ok_or(
- BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)),
- )?
- .into();
+ let versioned_dest: VersionedLocation = T::reachable_dest()
+ .ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?
+ .into();
let versioned_msg = VersionedXcm::from(msg);
- // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...)
+ // Ensure that origin can send to destination
+ // (e.g. setup delivery fees, ensure router setup, ...)
T::DeliveryHelper::ensure_successful_delivery(
&Default::default(),
&versioned_dest.clone().try_into().unwrap(),
FeeReason::ChargeFees,
);
- }: _>(send_origin, Box::new(versioned_dest), Box::new(versioned_msg))
- teleport_assets {
- let (asset, destination) = T::teleportable_asset_and_dest().ok_or(
- BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)),
- )?;
+ #[extrinsic_call]
+ _(send_origin as RuntimeOrigin, Box::new(versioned_dest), Box::new(versioned_msg));
+
+ Ok(())
+ }
+
+ #[benchmark]
+ fn teleport_assets() -> Result<(), BenchmarkError> {
+ let (asset, destination) = T::teleportable_asset_and_dest()
+ .ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
let assets: Assets = asset.clone().into();
@@ -116,11 +125,13 @@ benchmarks! {
let send_origin = RawOrigin::Signed(caller.clone());
let origin_location = T::ExecuteXcmOrigin::try_origin(send_origin.clone().into())
.map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
- if !T::XcmTeleportFilter::contains(&(origin_location.clone(), assets.clone().into_inner())) {
+ if !T::XcmTeleportFilter::contains(&(origin_location.clone(), assets.clone().into_inner()))
+ {
return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))
}
- // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...)
+ // Ensure that origin can send to destination
+ // (e.g. setup delivery fees, ensure router setup, ...)
let (_, _) = T::DeliveryHelper::ensure_successful_delivery(
&origin_location,
&destination,
@@ -134,18 +145,23 @@ benchmarks! {
&Asset { fun: Fungible(*amount), id: asset.id },
&origin_location,
None,
- ).map_err(|error| {
- tracing::error!("Fungible asset couldn't be deposited, error: {:?}", error);
- BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))
+ )
+ .map_err(|error| {
+ tracing::error!("Fungible asset couldn't be deposited, error: {:?}", error);
+ BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))
+ })?;
+ },
+ NonFungible(_instance) => {
+ ::AssetTransactor::deposit_asset(
+ &asset,
+ &origin_location,
+ None,
+ )
+ .map_err(|error| {
+ tracing::error!("Nonfungible asset couldn't be deposited, error: {:?}", error);
+ BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))
})?;
},
- NonFungible(instance) => {
- ::AssetTransactor::deposit_asset(&asset, &origin_location, None)
- .map_err(|error| {
- tracing::error!("Nonfungible asset couldn't be deposited, error: {:?}", error);
- BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))
- })?;
- }
};
let recipient = [0u8; 32];
@@ -153,12 +169,23 @@ benchmarks! {
let versioned_beneficiary: VersionedLocation =
AccountId32 { network: None, id: recipient.into() }.into();
let versioned_assets: VersionedAssets = assets.into();
- }: _>(send_origin.into(), Box::new(versioned_dest), Box::new(versioned_beneficiary), Box::new(versioned_assets), 0)
- reserve_transfer_assets {
- let (asset, destination) = T::reserve_transferable_asset_and_dest().ok_or(
- BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)),
- )?;
+ #[extrinsic_call]
+ _(
+ send_origin,
+ Box::new(versioned_dest),
+ Box::new(versioned_beneficiary),
+ Box::new(versioned_assets),
+ 0,
+ );
+
+ Ok(())
+ }
+
+ #[benchmark]
+ fn reserve_transfer_assets() -> Result<(), BenchmarkError> {
+ let (asset, destination) = T::reserve_transferable_asset_and_dest()
+ .ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
let assets: Assets = asset.clone().into();
@@ -166,12 +193,16 @@ benchmarks! {
let send_origin = RawOrigin::Signed(caller.clone());
let origin_location = T::ExecuteXcmOrigin::try_origin(send_origin.clone().into())
.map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
- if !T::XcmReserveTransferFilter::contains(&(origin_location.clone(), assets.clone().into_inner())) {
+ if !T::XcmReserveTransferFilter::contains(&(
+ origin_location.clone(),
+ assets.clone().into_inner(),
+ )) {
return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))
}
- // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...)
- T::DeliveryHelper::ensure_successful_delivery(
+ // Ensure that origin can send to destination
+ // (e.g. setup delivery fees, ensure router setup, ...)
+ let (_, _) = T::DeliveryHelper::ensure_successful_delivery(
&origin_location,
&destination,
FeeReason::ChargeFees,
@@ -184,18 +215,23 @@ benchmarks! {
&Asset { fun: Fungible(*amount), id: asset.id.clone() },
&origin_location,
None,
- ).map_err(|error| {
- tracing::error!("Fungible asset couldn't be deposited, error: {:?}", error);
- BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))
+ )
+ .map_err(|error| {
+ tracing::error!("Fungible asset couldn't be deposited, error: {:?}", error);
+ BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))
+ })?;
+ },
+ NonFungible(_instance) => {
+ ::AssetTransactor::deposit_asset(
+ &asset,
+ &origin_location,
+ None,
+ )
+ .map_err(|error| {
+ tracing::error!("Nonfungible asset couldn't be deposited, error: {:?}", error);
+ BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))
})?;
},
- NonFungible(instance) => {
- ::AssetTransactor::deposit_asset(&asset, &origin_location, None)
- .map_err(|error| {
- tracing::error!("Nonfungible asset couldn't be deposited, error: {:?}", error);
- BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))
- })?;
- }
};
let recipient = [0u8; 32];
@@ -203,8 +239,16 @@ benchmarks! {
let versioned_beneficiary: VersionedLocation =
AccountId32 { network: None, id: recipient.into() }.into();
let versioned_assets: VersionedAssets = assets.into();
- }: _>(send_origin.into(), Box::new(versioned_dest), Box::new(versioned_beneficiary), Box::new(versioned_assets), 0)
- verify {
+
+ #[extrinsic_call]
+ _(
+ send_origin,
+ Box::new(versioned_dest),
+ Box::new(versioned_beneficiary),
+ Box::new(versioned_assets),
+ 0,
+ );
+
match &asset.fun {
Fungible(amount) => {
assert_ok!(::AssetTransactor::withdraw_asset(
@@ -213,20 +257,22 @@ benchmarks! {
None,
));
},
- NonFungible(instance) => {
+ NonFungible(_instance) => {
assert_ok!(::AssetTransactor::withdraw_asset(
&asset,
&destination,
None,
));
- }
+ },
};
+
+ Ok(())
}
- transfer_assets {
- let (assets, fee_index, destination, verify) = T::set_up_complex_asset_transfer().ok_or(
- BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)),
- )?;
+ #[benchmark]
+ fn transfer_assets() -> Result<(), BenchmarkError> {
+ let (assets, _fee_index, destination, verify_fn) = T::set_up_complex_asset_transfer()
+ .ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
let caller: T::AccountId = whitelisted_caller();
let send_origin = RawOrigin::Signed(caller.clone());
let recipient = [0u8; 32];
@@ -235,19 +281,31 @@ benchmarks! {
AccountId32 { network: None, id: recipient.into() }.into();
let versioned_assets: VersionedAssets = assets.into();
- // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...)
+ // Ensure that origin can send to destination
+ // (e.g. setup delivery fees, ensure router setup, ...)
T::DeliveryHelper::ensure_successful_delivery(
&Default::default(),
&versioned_dest.clone().try_into().unwrap(),
FeeReason::ChargeFees,
);
- }: _>(send_origin.into(), Box::new(versioned_dest), Box::new(versioned_beneficiary), Box::new(versioned_assets), 0, WeightLimit::Unlimited)
- verify {
+
+ #[extrinsic_call]
+ _(
+ send_origin,
+ Box::new(versioned_dest),
+ Box::new(versioned_beneficiary),
+ Box::new(versioned_assets),
+ 0,
+ WeightLimit::Unlimited,
+ );
+
// run provided verification function
- verify();
+ verify_fn();
+ Ok(())
}
- execute {
+ #[benchmark]
+ fn execute() -> Result<(), BenchmarkError> {
let execute_origin =
T::ExecuteXcmOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
let origin_location = T::ExecuteXcmOrigin::try_origin(execute_origin.clone())
@@ -257,39 +315,59 @@ benchmarks! {
return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))
}
let versioned_msg = VersionedXcm::from(msg);
- }: _>(execute_origin, Box::new(versioned_msg), Weight::MAX)
- force_xcm_version {
- let loc = T::reachable_dest().ok_or(
- BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)),
- )?;
+ #[extrinsic_call]
+ _(execute_origin as RuntimeOrigin, Box::new(versioned_msg), Weight::MAX);
+
+ Ok(())
+ }
+
+ #[benchmark]
+ fn force_xcm_version() -> Result<(), BenchmarkError> {
+ let loc = T::reachable_dest()
+ .ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
let xcm_version = 2;
- }: _(RawOrigin::Root, Box::new(loc), xcm_version)
- force_default_xcm_version {}: _(RawOrigin::Root, Some(2))
+ #[extrinsic_call]
+ _(RawOrigin::Root, Box::new(loc), xcm_version);
+
+ Ok(())
+ }
- force_subscribe_version_notify {
- let versioned_loc: VersionedLocation = T::reachable_dest().ok_or(
- BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)),
- )?
- .into();
+ #[benchmark]
+ fn force_default_xcm_version() {
+ #[extrinsic_call]
+ _(RawOrigin::Root, Some(2))
+ }
- // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...)
+ #[benchmark]
+ fn force_subscribe_version_notify() -> Result<(), BenchmarkError> {
+ let versioned_loc: VersionedLocation = T::reachable_dest()
+ .ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?
+ .into();
+
+ // Ensure that origin can send to destination
+ // (e.g. setup delivery fees, ensure router setup, ...)
T::DeliveryHelper::ensure_successful_delivery(
&Default::default(),
&versioned_loc.clone().try_into().unwrap(),
FeeReason::ChargeFees,
);
- }: _(RawOrigin::Root, Box::new(versioned_loc))
+ #[extrinsic_call]
+ _(RawOrigin::Root, Box::new(versioned_loc));
+
+ Ok(())
+ }
- force_unsubscribe_version_notify {
- let loc = T::reachable_dest().ok_or(
- BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)),
- )?;
+ #[benchmark]
+ fn force_unsubscribe_version_notify() -> Result<(), BenchmarkError> {
+ let loc = T::reachable_dest()
+ .ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
let versioned_loc: VersionedLocation = loc.clone().into();
- // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...)
+ // Ensure that origin can send to destination
+ // (e.g. setup delivery fees, ensure router setup, ...)
T::DeliveryHelper::ensure_successful_delivery(
&Default::default(),
&versioned_loc.clone().try_into().unwrap(),
@@ -297,123 +375,227 @@ benchmarks! {
);
let _ = crate::Pallet::::request_version_notify(loc);
- }: _(RawOrigin::Root, Box::new(versioned_loc))
- force_suspension {}: _(RawOrigin::Root, true)
+ #[extrinsic_call]
+ _(RawOrigin::Root, Box::new(versioned_loc));
+
+ Ok(())
+ }
- migrate_supported_version {
+ #[benchmark]
+ fn force_suspension() {
+ #[extrinsic_call]
+ _(RawOrigin::Root, true)
+ }
+
+ #[benchmark]
+ fn migrate_supported_version() {
let old_version = XCM_VERSION - 1;
let loc = VersionedLocation::from(Location::from(Parent));
SupportedVersion::::insert(old_version, loc, old_version);
- }: {
- crate::Pallet::::check_xcm_version_change(VersionMigrationStage::MigrateSupportedVersion, Weight::zero());
+
+ #[block]
+ {
+ crate::Pallet::::check_xcm_version_change(
+ VersionMigrationStage::MigrateSupportedVersion,
+ Weight::zero(),
+ );
+ }
}
- migrate_version_notifiers {
+ #[benchmark]
+ fn migrate_version_notifiers() {
let old_version = XCM_VERSION - 1;
let loc = VersionedLocation::from(Location::from(Parent));
VersionNotifiers::::insert(old_version, loc, 0);
- }: {
- crate::Pallet::::check_xcm_version_change(VersionMigrationStage::MigrateVersionNotifiers, Weight::zero());
+
+ #[block]
+ {
+ crate::Pallet::::check_xcm_version_change(
+ VersionMigrationStage::MigrateVersionNotifiers,
+ Weight::zero(),
+ );
+ }
}
- already_notified_target {
- let loc = T::reachable_dest().ok_or(
- BenchmarkError::Override(BenchmarkResult::from_weight(T::DbWeight::get().reads(1))),
- )?;
+ #[benchmark]
+ fn already_notified_target() -> Result<(), BenchmarkError> {
+ let loc = T::reachable_dest().ok_or(BenchmarkError::Override(
+ BenchmarkResult::from_weight(T::DbWeight::get().reads(1)),
+ ))?;
let loc = VersionedLocation::from(loc);
let current_version = T::AdvertisedXcmVersion::get();
- VersionNotifyTargets::::insert(current_version, loc, (0, Weight::zero(), current_version));
- }: {
- crate::Pallet::::check_xcm_version_change(VersionMigrationStage::NotifyCurrentTargets(None), Weight::zero());
+ VersionNotifyTargets::::insert(
+ current_version,
+ loc,
+ (0, Weight::zero(), current_version),
+ );
+
+ #[block]
+ {
+ crate::Pallet::::check_xcm_version_change(
+ VersionMigrationStage::NotifyCurrentTargets(None),
+ Weight::zero(),
+ );
+ }
+
+ Ok(())
}
- notify_current_targets {
- let loc = T::reachable_dest().ok_or(
- BenchmarkError::Override(BenchmarkResult::from_weight(T::DbWeight::get().reads_writes(1, 3))),
- )?;
+ #[benchmark]
+ fn notify_current_targets() -> Result<(), BenchmarkError> {
+ let loc = T::reachable_dest().ok_or(BenchmarkError::Override(
+ BenchmarkResult::from_weight(T::DbWeight::get().reads_writes(1, 3)),
+ ))?;
let loc = VersionedLocation::from(loc);
let current_version = T::AdvertisedXcmVersion::get();
let old_version = current_version - 1;
VersionNotifyTargets::::insert(current_version, loc, (0, Weight::zero(), old_version));
- }: {
- crate::Pallet::::check_xcm_version_change(VersionMigrationStage::NotifyCurrentTargets(None), Weight::zero());
+
+ #[block]
+ {
+ crate::Pallet::::check_xcm_version_change(
+ VersionMigrationStage::NotifyCurrentTargets(None),
+ Weight::zero(),
+ );
+ }
+
+ Ok(())
}
- notify_target_migration_fail {
+ #[benchmark]
+ fn notify_target_migration_fail() {
let newer_xcm_version = xcm::prelude::XCM_VERSION;
let older_xcm_version = newer_xcm_version - 1;
- let bad_location: Location = Plurality {
- id: BodyId::Unit,
- part: BodyPart::Voice,
- }.into();
+ let bad_location: Location = Plurality { id: BodyId::Unit, part: BodyPart::Voice }.into();
let bad_location = VersionedLocation::from(bad_location)
.into_version(older_xcm_version)
.expect("Version convertion should work");
let current_version = T::AdvertisedXcmVersion::get();
- VersionNotifyTargets::::insert(current_version, bad_location, (0, Weight::zero(), current_version));
- }: {
- crate::Pallet::::check_xcm_version_change(VersionMigrationStage::MigrateAndNotifyOldTargets, Weight::zero());
+ VersionNotifyTargets::::insert(
+ current_version,
+ bad_location,
+ (0, Weight::zero(), current_version),
+ );
+
+ #[block]
+ {
+ crate::Pallet::::check_xcm_version_change(
+ VersionMigrationStage::MigrateAndNotifyOldTargets,
+ Weight::zero(),
+ );
+ }
}
- migrate_version_notify_targets {
+ #[benchmark]
+ fn migrate_version_notify_targets() {
let current_version = T::AdvertisedXcmVersion::get();
let old_version = current_version - 1;
let loc = VersionedLocation::from(Location::from(Parent));
VersionNotifyTargets::::insert(old_version, loc, (0, Weight::zero(), current_version));
- }: {
- crate::Pallet::::check_xcm_version_change(VersionMigrationStage::MigrateAndNotifyOldTargets, Weight::zero());
+
+ #[block]
+ {
+ crate::Pallet::::check_xcm_version_change(
+ VersionMigrationStage::MigrateAndNotifyOldTargets,
+ Weight::zero(),
+ );
+ }
}
- migrate_and_notify_old_targets {
- let loc = T::reachable_dest().ok_or(
- BenchmarkError::Override(BenchmarkResult::from_weight(T::DbWeight::get().reads_writes(1, 3))),
- )?;
+ #[benchmark]
+ fn migrate_and_notify_old_targets() -> Result<(), BenchmarkError> {
+ let loc = T::reachable_dest().ok_or(BenchmarkError::Override(
+ BenchmarkResult::from_weight(T::DbWeight::get().reads_writes(1, 3)),
+ ))?;
let loc = VersionedLocation::from(loc);
let old_version = T::AdvertisedXcmVersion::get() - 1;
VersionNotifyTargets::::insert(old_version, loc, (0, Weight::zero(), old_version));
- }: {
- crate::Pallet::::check_xcm_version_change(VersionMigrationStage::MigrateAndNotifyOldTargets, Weight::zero());
+
+ #[block]
+ {
+ crate::Pallet::::check_xcm_version_change(
+ VersionMigrationStage::MigrateAndNotifyOldTargets,
+ Weight::zero(),
+ );
+ }
+
+ Ok(())
}
- new_query {
+ #[benchmark]
+ fn new_query() {
let responder = Location::from(Parent);
let timeout = 1u32.into();
let match_querier = Location::from(Here);
- }: {
- crate::Pallet::::new_query(responder, timeout, match_querier);
+
+ #[block]
+ {
+ crate::Pallet::::new_query(responder, timeout, match_querier);
+ }
}
- take_response {
+ #[benchmark]
+ fn take_response() {
let responder = Location::from(Parent);
let timeout = 1u32.into();
let match_querier = Location::from(Here);
let query_id = crate::Pallet::::new_query(responder, timeout, match_querier);
- let infos = (0 .. xcm::v3::MaxPalletsInfo::get()).map(|_| PalletInfo::new(
- u32::MAX,
- (0..xcm::v3::MaxPalletNameLen::get()).map(|_| 97u8).collect::>().try_into().unwrap(),
- (0..xcm::v3::MaxPalletNameLen::get()).map(|_| 97u8).collect::>().try_into().unwrap(),
- u32::MAX,
- u32::MAX,
- u32::MAX,
- ).unwrap()).collect::>();
- crate::Pallet::::expect_response(query_id, Response::PalletsInfo(infos.try_into().unwrap()));
- }: {
- as QueryHandler>::take_response(query_id);
+ let infos = (0..xcm::v3::MaxPalletsInfo::get())
+ .map(|_| {
+ PalletInfo::new(
+ u32::MAX,
+ (0..xcm::v3::MaxPalletNameLen::get())
+ .map(|_| 97u8)
+ .collect::>()
+ .try_into()
+ .unwrap(),
+ (0..xcm::v3::MaxPalletNameLen::get())
+ .map(|_| 97u8)
+ .collect::>()
+ .try_into()
+ .unwrap(),
+ u32::MAX,
+ u32::MAX,
+ u32::MAX,
+ )
+ .unwrap()
+ })
+ .collect::>();
+ crate::Pallet::::expect_response(
+ query_id,
+ Response::PalletsInfo(infos.try_into().unwrap()),
+ );
+
+ #[block]
+ {
+ as QueryHandler>::take_response(query_id);
+ }
}
- claim_assets {
+ #[benchmark]
+ fn claim_assets() -> Result<(), BenchmarkError> {
let claim_origin = RawOrigin::Signed(whitelisted_caller());
- let claim_location = T::ExecuteXcmOrigin::try_origin(claim_origin.clone().into()).map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
+ let claim_location = T::ExecuteXcmOrigin::try_origin(claim_origin.clone().into())
+ .map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
let asset: Asset = T::get_asset();
// Trap assets for claiming later
crate::Pallet::::drop_assets(
&claim_location,
asset.clone().into(),
- &XcmContext { origin: None, message_id: [0u8; 32], topic: None }
+ &XcmContext { origin: None, message_id: [0u8; 32], topic: None },
);
let versioned_assets = VersionedAssets::from(Assets::from(asset));
- }: _>(claim_origin.into(), Box::new(versioned_assets), Box::new(VersionedLocation::from(claim_location)))
+
+ #[extrinsic_call]
+ _(
+ claim_origin,
+ Box::new(versioned_assets),
+ Box::new(VersionedLocation::from(claim_location)),
+ );
+
+ Ok(())
+ }
impl_benchmark_test_suite!(
Pallet,
From d8df46c7a1488f2358e69368813fd772164c4dac Mon Sep 17 00:00:00 2001
From: Ludovic_Domingues
Date: Wed, 18 Dec 2024 10:59:55 +0100
Subject: [PATCH 2/2] Improve pallet claims file structure (#6779)
Linked to issue #590.
I moved the mod, tests, mock and benchmarking to their own seperate file
to reduce the bloat inside claims.rs
---------
Co-authored-by: Guillaume Thiolliere
---
polkadot/runtime/common/src/claims.rs | 1758 -----------------
.../runtime/common/src/claims/benchmarking.rs | 318 +++
polkadot/runtime/common/src/claims/mock.rs | 129 ++
polkadot/runtime/common/src/claims/mod.rs | 723 +++++++
polkadot/runtime/common/src/claims/tests.rs | 666 +++++++
5 files changed, 1836 insertions(+), 1758 deletions(-)
delete mode 100644 polkadot/runtime/common/src/claims.rs
create mode 100644 polkadot/runtime/common/src/claims/benchmarking.rs
create mode 100644 polkadot/runtime/common/src/claims/mock.rs
create mode 100644 polkadot/runtime/common/src/claims/mod.rs
create mode 100644 polkadot/runtime/common/src/claims/tests.rs
diff --git a/polkadot/runtime/common/src/claims.rs b/polkadot/runtime/common/src/claims.rs
deleted file mode 100644
index 1ee80dd76e2d..000000000000
--- a/polkadot/runtime/common/src/claims.rs
+++ /dev/null
@@ -1,1758 +0,0 @@
-// Copyright (C) Parity Technologies (UK) Ltd.
-// This file is part of Polkadot.
-
-// Polkadot 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.
-
-// Polkadot 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 Polkadot. If not, see .
-
-//! Pallet to process claims from Ethereum addresses.
-
-#[cfg(not(feature = "std"))]
-use alloc::{format, string::String};
-use alloc::{vec, vec::Vec};
-use codec::{Decode, Encode, MaxEncodedLen};
-use core::fmt::Debug;
-use frame_support::{
- ensure,
- traits::{Currency, Get, IsSubType, VestingSchedule},
- weights::Weight,
- DefaultNoBound,
-};
-pub use pallet::*;
-use polkadot_primitives::ValidityError;
-use scale_info::TypeInfo;
-use serde::{self, Deserialize, Deserializer, Serialize, Serializer};
-use sp_io::{crypto::secp256k1_ecdsa_recover, hashing::keccak_256};
-use sp_runtime::{
- impl_tx_ext_default,
- traits::{
- AsSystemOriginSigner, AsTransactionAuthorizedOrigin, CheckedSub, DispatchInfoOf,
- Dispatchable, TransactionExtension, Zero,
- },
- transaction_validity::{
- InvalidTransaction, TransactionSource, TransactionValidity, TransactionValidityError,
- ValidTransaction,
- },
- RuntimeDebug,
-};
-
-type CurrencyOf = <::VestingSchedule as VestingSchedule<
- ::AccountId,
->>::Currency;
-type BalanceOf = as Currency<::AccountId>>::Balance;
-
-pub trait WeightInfo {
- fn claim() -> Weight;
- fn mint_claim() -> Weight;
- fn claim_attest() -> Weight;
- fn attest() -> Weight;
- fn move_claim() -> Weight;
- fn prevalidate_attests() -> Weight;
-}
-
-pub struct TestWeightInfo;
-impl WeightInfo for TestWeightInfo {
- fn claim() -> Weight {
- Weight::zero()
- }
- fn mint_claim() -> Weight {
- Weight::zero()
- }
- fn claim_attest() -> Weight {
- Weight::zero()
- }
- fn attest() -> Weight {
- Weight::zero()
- }
- fn move_claim() -> Weight {
- Weight::zero()
- }
- fn prevalidate_attests() -> Weight {
- Weight::zero()
- }
-}
-
-/// The kind of statement an account needs to make for a claim to be valid.
-#[derive(
- Encode,
- Decode,
- Clone,
- Copy,
- Eq,
- PartialEq,
- RuntimeDebug,
- TypeInfo,
- Serialize,
- Deserialize,
- MaxEncodedLen,
-)]
-pub enum StatementKind {
- /// Statement required to be made by non-SAFT holders.
- Regular,
- /// Statement required to be made by SAFT holders.
- Saft,
-}
-
-impl StatementKind {
- /// Convert this to the (English) statement it represents.
- fn to_text(self) -> &'static [u8] {
- match self {
- StatementKind::Regular =>
- &b"I hereby agree to the terms of the statement whose SHA-256 multihash is \
- Qmc1XYqT6S39WNp2UeiRUrZichUWUPpGEThDE6dAb3f6Ny. (This may be found at the URL: \
- https://statement.polkadot.network/regular.html)"[..],
- StatementKind::Saft =>
- &b"I hereby agree to the terms of the statement whose SHA-256 multihash is \
- QmXEkMahfhHJPzT3RjkXiZVFi77ZeVeuxtAjhojGRNYckz. (This may be found at the URL: \
- https://statement.polkadot.network/saft.html)"[..],
- }
- }
-}
-
-impl Default for StatementKind {
- fn default() -> Self {
- StatementKind::Regular
- }
-}
-
-/// An Ethereum address (i.e. 20 bytes, used to represent an Ethereum account).
-///
-/// This gets serialized to the 0x-prefixed hex representation.
-#[derive(
- Clone, Copy, PartialEq, Eq, Encode, Decode, Default, RuntimeDebug, TypeInfo, MaxEncodedLen,
-)]
-pub struct EthereumAddress([u8; 20]);
-
-impl Serialize for EthereumAddress {
- fn serialize(&self, serializer: S) -> Result
- where
- S: Serializer,
- {
- let hex: String = rustc_hex::ToHex::to_hex(&self.0[..]);
- serializer.serialize_str(&format!("0x{}", hex))
- }
-}
-
-impl<'de> Deserialize<'de> for EthereumAddress {
- fn deserialize(deserializer: D) -> Result
- where
- D: Deserializer<'de>,
- {
- let base_string = String::deserialize(deserializer)?;
- let offset = if base_string.starts_with("0x") { 2 } else { 0 };
- let s = &base_string[offset..];
- if s.len() != 40 {
- Err(serde::de::Error::custom(
- "Bad length of Ethereum address (should be 42 including '0x')",
- ))?;
- }
- let raw: Vec = rustc_hex::FromHex::from_hex(s)
- .map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?;
- let mut r = Self::default();
- r.0.copy_from_slice(&raw);
- Ok(r)
- }
-}
-
-#[derive(Encode, Decode, Clone, TypeInfo, MaxEncodedLen)]
-pub struct EcdsaSignature(pub [u8; 65]);
-
-impl PartialEq for EcdsaSignature {
- fn eq(&self, other: &Self) -> bool {
- &self.0[..] == &other.0[..]
- }
-}
-
-impl core::fmt::Debug for EcdsaSignature {
- fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
- write!(f, "EcdsaSignature({:?})", &self.0[..])
- }
-}
-
-#[frame_support::pallet]
-pub mod pallet {
- use super::*;
- use frame_support::pallet_prelude::*;
- use frame_system::pallet_prelude::*;
-
- #[pallet::pallet]
- pub struct Pallet(_);
-
- /// Configuration trait.
- #[pallet::config]
- pub trait Config: frame_system::Config {
- /// The overarching event type.
- type RuntimeEvent: From> + IsType<::RuntimeEvent>;
- type VestingSchedule: VestingSchedule>;
- #[pallet::constant]
- type Prefix: Get<&'static [u8]>;
- type MoveClaimOrigin: EnsureOrigin;
- type WeightInfo: WeightInfo;
- }
-
- #[pallet::event]
- #[pallet::generate_deposit(pub(super) fn deposit_event)]
- pub enum Event {
- /// Someone claimed some DOTs.
- Claimed { who: T::AccountId, ethereum_address: EthereumAddress, amount: BalanceOf },
- }
-
- #[pallet::error]
- pub enum Error {
- /// Invalid Ethereum signature.
- InvalidEthereumSignature,
- /// Ethereum address has no claim.
- SignerHasNoClaim,
- /// Account ID sending transaction has no claim.
- SenderHasNoClaim,
- /// There's not enough in the pot to pay out some unvested amount. Generally implies a
- /// logic error.
- PotUnderflow,
- /// A needed statement was not included.
- InvalidStatement,
- /// The account already has a vested balance.
- VestedBalanceExists,
- }
-
- #[pallet::storage]
- pub type Claims = StorageMap<_, Identity, EthereumAddress, BalanceOf>;
-
- #[pallet::storage]
- pub type Total = StorageValue<_, BalanceOf, ValueQuery>;
-
- /// Vesting schedule for a claim.
- /// First balance is the total amount that should be held for vesting.
- /// Second balance is how much should be unlocked per block.
- /// The block number is when the vesting should start.
- #[pallet::storage]
- pub type Vesting =
- StorageMap<_, Identity, EthereumAddress, (BalanceOf, BalanceOf, BlockNumberFor)>;
-
- /// The statement kind that must be signed, if any.
- #[pallet::storage]
- pub(super) type Signing = StorageMap<_, Identity, EthereumAddress, StatementKind>;
-
- /// Pre-claimed Ethereum accounts, by the Account ID that they are claimed to.
- #[pallet::storage]
- pub(super) type Preclaims = StorageMap<_, Identity, T::AccountId, EthereumAddress>;
-
- #[pallet::genesis_config]
- #[derive(DefaultNoBound)]
- pub struct GenesisConfig {
- pub claims:
- Vec<(EthereumAddress, BalanceOf, Option, Option)>,
- pub vesting: Vec<(EthereumAddress, (BalanceOf, BalanceOf, BlockNumberFor))>,
- }
-
- #[pallet::genesis_build]
- impl BuildGenesisConfig for GenesisConfig {
- fn build(&self) {
- // build `Claims`
- self.claims.iter().map(|(a, b, _, _)| (*a, *b)).for_each(|(a, b)| {
- Claims::::insert(a, b);
- });
- // build `Total`
- Total::::put(
- self.claims
- .iter()
- .fold(Zero::zero(), |acc: BalanceOf, &(_, b, _, _)| acc + b),
- );
- // build `Vesting`
- self.vesting.iter().for_each(|(k, v)| {
- Vesting::::insert(k, v);
- });
- // build `Signing`
- self.claims
- .iter()
- .filter_map(|(a, _, _, s)| Some((*a, (*s)?)))
- .for_each(|(a, s)| {
- Signing::::insert(a, s);
- });
- // build `Preclaims`
- self.claims.iter().filter_map(|(a, _, i, _)| Some((i.clone()?, *a))).for_each(
- |(i, a)| {
- Preclaims::::insert(i, a);
- },
- );
- }
- }
-
- #[pallet::hooks]
- impl Hooks> for Pallet {}
-
- #[pallet::call]
- impl Pallet {
- /// Make a claim to collect your DOTs.
- ///
- /// The dispatch origin for this call must be _None_.
- ///
- /// Unsigned Validation:
- /// A call to claim is deemed valid if the signature provided matches
- /// the expected signed message of:
- ///
- /// > Ethereum Signed Message:
- /// > (configured prefix string)(address)
- ///
- /// and `address` matches the `dest` account.
- ///
- /// Parameters:
- /// - `dest`: The destination account to payout the claim.
- /// - `ethereum_signature`: The signature of an ethereum signed message matching the format
- /// described above.
- ///
- ///
- /// The weight of this call is invariant over the input parameters.
- /// Weight includes logic to validate unsigned `claim` call.
- ///
- /// Total Complexity: O(1)
- ///
- #[pallet::call_index(0)]
- #[pallet::weight(T::WeightInfo::claim())]
- pub fn claim(
- origin: OriginFor,
- dest: T::AccountId,
- ethereum_signature: EcdsaSignature,
- ) -> DispatchResult {
- ensure_none(origin)?;
-
- let data = dest.using_encoded(to_ascii_hex);
- let signer = Self::eth_recover(ðereum_signature, &data, &[][..])
- .ok_or(Error::::InvalidEthereumSignature)?;
- ensure!(Signing::::get(&signer).is_none(), Error::::InvalidStatement);
-
- Self::process_claim(signer, dest)?;
- Ok(())
- }
-
- /// Mint a new claim to collect DOTs.
- ///
- /// The dispatch origin for this call must be _Root_.
- ///
- /// Parameters:
- /// - `who`: The Ethereum address allowed to collect this claim.
- /// - `value`: The number of DOTs that will be claimed.
- /// - `vesting_schedule`: An optional vesting schedule for these DOTs.
- ///
- ///
- /// The weight of this call is invariant over the input parameters.
- /// We assume worst case that both vesting and statement is being inserted.
- ///
- /// Total Complexity: O(1)
- ///
- #[pallet::call_index(1)]
- #[pallet::weight(T::WeightInfo::mint_claim())]
- pub fn mint_claim(
- origin: OriginFor,
- who: EthereumAddress,
- value: BalanceOf,
- vesting_schedule: Option<(BalanceOf, BalanceOf, BlockNumberFor)>,
- statement: Option,
- ) -> DispatchResult {
- ensure_root(origin)?;
-
- Total::::mutate(|t| *t += value);
- Claims::::insert(who, value);
- if let Some(vs) = vesting_schedule {
- Vesting::::insert(who, vs);
- }
- if let Some(s) = statement {
- Signing::::insert(who, s);
- }
- Ok(())
- }
-
- /// Make a claim to collect your DOTs by signing a statement.
- ///
- /// The dispatch origin for this call must be _None_.
- ///
- /// Unsigned Validation:
- /// A call to `claim_attest` is deemed valid if the signature provided matches
- /// the expected signed message of:
- ///
- /// > Ethereum Signed Message:
- /// > (configured prefix string)(address)(statement)
- ///
- /// and `address` matches the `dest` account; the `statement` must match that which is
- /// expected according to your purchase arrangement.
- ///
- /// Parameters:
- /// - `dest`: The destination account to payout the claim.
- /// - `ethereum_signature`: The signature of an ethereum signed message matching the format
- /// described above.
- /// - `statement`: The identity of the statement which is being attested to in the
- /// signature.
- ///
- ///
- /// The weight of this call is invariant over the input parameters.
- /// Weight includes logic to validate unsigned `claim_attest` call.
- ///
- /// Total Complexity: O(1)
- ///
- #[pallet::call_index(2)]
- #[pallet::weight(T::WeightInfo::claim_attest())]
- pub fn claim_attest(
- origin: OriginFor,
- dest: T::AccountId,
- ethereum_signature: EcdsaSignature,
- statement: Vec,
- ) -> DispatchResult {
- ensure_none(origin)?;
-
- let data = dest.using_encoded(to_ascii_hex);
- let signer = Self::eth_recover(ðereum_signature, &data, &statement)
- .ok_or(Error::::InvalidEthereumSignature)?;
- if let Some(s) = Signing::::get(signer) {
- ensure!(s.to_text() == &statement[..], Error::::InvalidStatement);
- }
- Self::process_claim(signer, dest)?;
- Ok(())
- }
-
- /// Attest to a statement, needed to finalize the claims process.
- ///
- /// WARNING: Insecure unless your chain includes `PrevalidateAttests` as a
- /// `TransactionExtension`.
- ///
- /// Unsigned Validation:
- /// A call to attest is deemed valid if the sender has a `Preclaim` registered
- /// and provides a `statement` which is expected for the account.
- ///
- /// Parameters:
- /// - `statement`: The identity of the statement which is being attested to in the
- /// signature.
- ///
- ///
- /// The weight of this call is invariant over the input parameters.
- /// Weight includes logic to do pre-validation on `attest` call.
- ///
- /// Total Complexity: O(1)
- ///
- #[pallet::call_index(3)]
- #[pallet::weight((
- T::WeightInfo::attest(),
- DispatchClass::Normal,
- Pays::No
- ))]
- pub fn attest(origin: OriginFor, statement: Vec) -> DispatchResult {
- let who = ensure_signed(origin)?;
- let signer = Preclaims::::get(&who).ok_or(Error::::SenderHasNoClaim)?;
- if let Some(s) = Signing::::get(signer) {
- ensure!(s.to_text() == &statement[..], Error::::InvalidStatement);
- }
- Self::process_claim(signer, who.clone())?;
- Preclaims::::remove(&who);
- Ok(())
- }
-
- #[pallet::call_index(4)]
- #[pallet::weight(T::WeightInfo::move_claim())]
- pub fn move_claim(
- origin: OriginFor,
- old: EthereumAddress,
- new: EthereumAddress,
- maybe_preclaim: Option,
- ) -> DispatchResultWithPostInfo {
- T::MoveClaimOrigin::try_origin(origin).map(|_| ()).or_else(ensure_root)?;
-
- Claims::::take(&old).map(|c| Claims::::insert(&new, c));
- Vesting::::take(&old).map(|c| Vesting::::insert(&new, c));
- Signing::::take(&old).map(|c| Signing::::insert(&new, c));
- maybe_preclaim.map(|preclaim| {
- Preclaims::::mutate(&preclaim, |maybe_o| {
- if maybe_o.as_ref().map_or(false, |o| o == &old) {
- *maybe_o = Some(new)
- }
- })
- });
- Ok(Pays::No.into())
- }
- }
-
- #[pallet::validate_unsigned]
- impl ValidateUnsigned for Pallet {
- type Call = Call;
-
- fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
- const PRIORITY: u64 = 100;
-
- let (maybe_signer, maybe_statement) = match call {
- //
- // The weight of this logic is included in the `claim` dispatchable.
- //
- Call::claim { dest: account, ethereum_signature } => {
- let data = account.using_encoded(to_ascii_hex);
- (Self::eth_recover(ðereum_signature, &data, &[][..]), None)
- },
- //
- // The weight of this logic is included in the `claim_attest` dispatchable.
- //
- Call::claim_attest { dest: account, ethereum_signature, statement } => {
- let data = account.using_encoded(to_ascii_hex);
- (
- Self::eth_recover(ðereum_signature, &data, &statement),
- Some(statement.as_slice()),
- )
- },
- _ => return Err(InvalidTransaction::Call.into()),
- };
-
- let signer = maybe_signer.ok_or(InvalidTransaction::Custom(
- ValidityError::InvalidEthereumSignature.into(),
- ))?;
-
- let e = InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into());
- ensure!(Claims::::contains_key(&signer), e);
-
- let e = InvalidTransaction::Custom(ValidityError::InvalidStatement.into());
- match Signing::::get(signer) {
- None => ensure!(maybe_statement.is_none(), e),
- Some(s) => ensure!(Some(s.to_text()) == maybe_statement, e),
- }
-
- Ok(ValidTransaction {
- priority: PRIORITY,
- requires: vec![],
- provides: vec![("claims", signer).encode()],
- longevity: TransactionLongevity::max_value(),
- propagate: true,
- })
- }
- }
-}
-
-/// Converts the given binary data into ASCII-encoded hex. It will be twice the length.
-fn to_ascii_hex(data: &[u8]) -> Vec {
- let mut r = Vec::with_capacity(data.len() * 2);
- let mut push_nibble = |n| r.push(if n < 10 { b'0' + n } else { b'a' - 10 + n });
- for &b in data.iter() {
- push_nibble(b / 16);
- push_nibble(b % 16);
- }
- r
-}
-
-impl Pallet {
- // Constructs the message that Ethereum RPC's `personal_sign` and `eth_sign` would sign.
- fn ethereum_signable_message(what: &[u8], extra: &[u8]) -> Vec {
- let prefix = T::Prefix::get();
- let mut l = prefix.len() + what.len() + extra.len();
- let mut rev = Vec::new();
- while l > 0 {
- rev.push(b'0' + (l % 10) as u8);
- l /= 10;
- }
- let mut v = b"\x19Ethereum Signed Message:\n".to_vec();
- v.extend(rev.into_iter().rev());
- v.extend_from_slice(prefix);
- v.extend_from_slice(what);
- v.extend_from_slice(extra);
- v
- }
-
- // Attempts to recover the Ethereum address from a message signature signed by using
- // the Ethereum RPC's `personal_sign` and `eth_sign`.
- fn eth_recover(s: &EcdsaSignature, what: &[u8], extra: &[u8]) -> Option {
- let msg = keccak_256(&Self::ethereum_signable_message(what, extra));
- let mut res = EthereumAddress::default();
- res.0
- .copy_from_slice(&keccak_256(&secp256k1_ecdsa_recover(&s.0, &msg).ok()?[..])[12..]);
- Some(res)
- }
-
- fn process_claim(signer: EthereumAddress, dest: T::AccountId) -> sp_runtime::DispatchResult {
- let balance_due = Claims::::get(&signer).ok_or(Error::::SignerHasNoClaim)?;
-
- let new_total =
- Total::::get().checked_sub(&balance_due).ok_or(Error::::PotUnderflow)?;
-
- let vesting = Vesting::::get(&signer);
- if vesting.is_some() && T::VestingSchedule::vesting_balance(&dest).is_some() {
- return Err(Error::::VestedBalanceExists.into())
- }
-
- // We first need to deposit the balance to ensure that the account exists.
- let _ = CurrencyOf::::deposit_creating(&dest, balance_due);
-
- // Check if this claim should have a vesting schedule.
- if let Some(vs) = vesting {
- // This can only fail if the account already has a vesting schedule,
- // but this is checked above.
- T::VestingSchedule::add_vesting_schedule(&dest, vs.0, vs.1, vs.2)
- .expect("No other vesting schedule exists, as checked above; qed");
- }
-
- Total::::put(new_total);
- Claims::::remove(&signer);
- Vesting::::remove(&signer);
- Signing::::remove(&signer);
-
- // Let's deposit an event to let the outside world know this happened.
- Self::deposit_event(Event::::Claimed {
- who: dest,
- ethereum_address: signer,
- amount: balance_due,
- });
-
- Ok(())
- }
-}
-
-/// Validate `attest` calls prior to execution. Needed to avoid a DoS attack since they are
-/// otherwise free to place on chain.
-#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
-#[scale_info(skip_type_params(T))]
-pub struct PrevalidateAttests(core::marker::PhantomData);
-
-impl Debug for PrevalidateAttests
-where
- ::RuntimeCall: IsSubType>,
-{
- #[cfg(feature = "std")]
- fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
- write!(f, "PrevalidateAttests")
- }
-
- #[cfg(not(feature = "std"))]
- fn fmt(&self, _: &mut core::fmt::Formatter) -> core::fmt::Result {
- Ok(())
- }
-}
-
-impl PrevalidateAttests
-where
- ::RuntimeCall: IsSubType>,
-{
- /// Create new `TransactionExtension` to check runtime version.
- pub fn new() -> Self {
- Self(core::marker::PhantomData)
- }
-}
-
-impl TransactionExtension for PrevalidateAttests
-where
- ::RuntimeCall: IsSubType>,
- <::RuntimeCall as Dispatchable>::RuntimeOrigin:
- AsSystemOriginSigner + AsTransactionAuthorizedOrigin + Clone,
-{
- const IDENTIFIER: &'static str = "PrevalidateAttests";
- type Implicit = ();
- type Pre = ();
- type Val = ();
-
- fn weight(&self, call: &T::RuntimeCall) -> Weight {
- if let Some(Call::attest { .. }) = call.is_sub_type() {
- T::WeightInfo::prevalidate_attests()
- } else {
- Weight::zero()
- }
- }
-
- fn validate(
- &self,
- origin: ::RuntimeOrigin,
- call: &T::RuntimeCall,
- _info: &DispatchInfoOf,
- _len: usize,
- _self_implicit: Self::Implicit,
- _inherited_implication: &impl Encode,
- _source: TransactionSource,
- ) -> Result<
- (ValidTransaction, Self::Val, ::RuntimeOrigin),
- TransactionValidityError,
- > {
- if let Some(Call::attest { statement: attested_statement }) = call.is_sub_type() {
- let who = origin.as_system_origin_signer().ok_or(InvalidTransaction::BadSigner)?;
- let signer = Preclaims::::get(who)
- .ok_or(InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()))?;
- if let Some(s) = Signing::::get(signer) {
- let e = InvalidTransaction::Custom(ValidityError::InvalidStatement.into());
- ensure!(&attested_statement[..] == s.to_text(), e);
- }
- }
- Ok((ValidTransaction::default(), (), origin))
- }
-
- impl_tx_ext_default!(T::RuntimeCall; prepare);
-}
-
-#[cfg(any(test, feature = "runtime-benchmarks"))]
-mod secp_utils {
- use super::*;
-
- pub fn public(secret: &libsecp256k1::SecretKey) -> libsecp256k1::PublicKey {
- libsecp256k1::PublicKey::from_secret_key(secret)
- }
- pub fn eth(secret: &libsecp256k1::SecretKey) -> EthereumAddress {
- let mut res = EthereumAddress::default();
- res.0.copy_from_slice(&keccak_256(&public(secret).serialize()[1..65])[12..]);
- res
- }
- pub fn sig(
- secret: &libsecp256k1::SecretKey,
- what: &[u8],
- extra: &[u8],
- ) -> EcdsaSignature {
- let msg = keccak_256(&super::Pallet::::ethereum_signable_message(
- &to_ascii_hex(what)[..],
- extra,
- ));
- let (sig, recovery_id) = libsecp256k1::sign(&libsecp256k1::Message::parse(&msg), secret);
- let mut r = [0u8; 65];
- r[0..64].copy_from_slice(&sig.serialize()[..]);
- r[64] = recovery_id.serialize();
- EcdsaSignature(r)
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use hex_literal::hex;
- use secp_utils::*;
- use sp_runtime::transaction_validity::TransactionSource::External;
-
- use codec::Encode;
- // The testing primitives are very useful for avoiding having to work with signatures
- // or public keys. `u64` is used as the `AccountId` and no `Signature`s are required.
- use crate::claims;
- use claims::Call as ClaimsCall;
- use frame_support::{
- assert_err, assert_noop, assert_ok, derive_impl,
- dispatch::{GetDispatchInfo, Pays},
- ord_parameter_types, parameter_types,
- traits::{ExistenceRequirement, WithdrawReasons},
- };
- use pallet_balances;
- use sp_runtime::{
- traits::{DispatchTransaction, Identity},
- transaction_validity::TransactionLongevity,
- BuildStorage,
- DispatchError::BadOrigin,
- TokenError,
- };
-
- type Block = frame_system::mocking::MockBlock;
-
- frame_support::construct_runtime!(
- pub enum Test
- {
- System: frame_system,
- Balances: pallet_balances,
- Vesting: pallet_vesting,
- Claims: claims,
- }
- );
-
- #[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
- impl frame_system::Config for Test {
- type RuntimeOrigin = RuntimeOrigin;
- type RuntimeCall = RuntimeCall;
- type Block = Block;
- type RuntimeEvent = RuntimeEvent;
- type AccountData = pallet_balances::AccountData;
- type MaxConsumers = frame_support::traits::ConstU32<16>;
- }
-
- #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
- impl pallet_balances::Config for Test {
- type AccountStore = System;
- }
-
- parameter_types! {
- pub const MinVestedTransfer: u64 = 1;
- pub UnvestedFundsAllowedWithdrawReasons: WithdrawReasons =
- WithdrawReasons::except(WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE);
- }
-
- impl pallet_vesting::Config for Test {
- type RuntimeEvent = RuntimeEvent;
- type Currency = Balances;
- type BlockNumberToBalance = Identity;
- type MinVestedTransfer = MinVestedTransfer;
- type WeightInfo = ();
- type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons;
- type BlockNumberProvider = System;
- const MAX_VESTING_SCHEDULES: u32 = 28;
- }
-
- parameter_types! {
- pub Prefix: &'static [u8] = b"Pay RUSTs to the TEST account:";
- }
- ord_parameter_types! {
- pub const Six: u64 = 6;
- }
-
- impl Config for Test {
- type RuntimeEvent = RuntimeEvent;
- type VestingSchedule = Vesting;
- type Prefix = Prefix;
- type MoveClaimOrigin = frame_system::EnsureSignedBy;
- type WeightInfo = TestWeightInfo;
- }
-
- fn alice() -> libsecp256k1::SecretKey {
- libsecp256k1::SecretKey::parse(&keccak_256(b"Alice")).unwrap()
- }
- fn bob() -> libsecp256k1::SecretKey {
- libsecp256k1::SecretKey::parse(&keccak_256(b"Bob")).unwrap()
- }
- fn dave() -> libsecp256k1::SecretKey {
- libsecp256k1::SecretKey::parse(&keccak_256(b"Dave")).unwrap()
- }
- fn eve() -> libsecp256k1::SecretKey {
- libsecp256k1::SecretKey::parse(&keccak_256(b"Eve")).unwrap()
- }
- fn frank() -> libsecp256k1::SecretKey {
- libsecp256k1::SecretKey::parse(&keccak_256(b"Frank")).unwrap()
- }
-
- // This function basically just builds a genesis storage key/value store according to
- // our desired mockup.
- pub fn new_test_ext() -> sp_io::TestExternalities {
- let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap();
- // We use default for brevity, but you can configure as desired if needed.
- pallet_balances::GenesisConfig::::default()
- .assimilate_storage(&mut t)
- .unwrap();
- claims::GenesisConfig:: {
- claims: vec![
- (eth(&alice()), 100, None, None),
- (eth(&dave()), 200, None, Some(StatementKind::Regular)),
- (eth(&eve()), 300, Some(42), Some(StatementKind::Saft)),
- (eth(&frank()), 400, Some(43), None),
- ],
- vesting: vec![(eth(&alice()), (50, 10, 1))],
- }
- .assimilate_storage(&mut t)
- .unwrap();
- t.into()
- }
-
- fn total_claims() -> u64 {
- 100 + 200 + 300 + 400
- }
-
- #[test]
- fn basic_setup_works() {
- new_test_ext().execute_with(|| {
- assert_eq!(claims::Total::::get(), total_claims());
- assert_eq!(claims::Claims::::get(ð(&alice())), Some(100));
- assert_eq!(claims::Claims::::get(ð(&dave())), Some(200));
- assert_eq!(claims::Claims::::get(ð(&eve())), Some(300));
- assert_eq!(claims::Claims::::get(ð(&frank())), Some(400));
- assert_eq!(claims::Claims::::get(&EthereumAddress::default()), None);
- assert_eq!(claims::Vesting::::get(ð(&alice())), Some((50, 10, 1)));
- });
- }
-
- #[test]
- fn serde_works() {
- let x = EthereumAddress(hex!["0123456789abcdef0123456789abcdef01234567"]);
- let y = serde_json::to_string(&x).unwrap();
- assert_eq!(y, "\"0x0123456789abcdef0123456789abcdef01234567\"");
- let z: EthereumAddress = serde_json::from_str(&y).unwrap();
- assert_eq!(x, z);
- }
-
- #[test]
- fn claiming_works() {
- new_test_ext().execute_with(|| {
- assert_eq!(Balances::free_balance(42), 0);
- assert_ok!(Claims::claim(
- RuntimeOrigin::none(),
- 42,
- sig::(&alice(), &42u64.encode(), &[][..])
- ));
- assert_eq!(Balances::free_balance(&42), 100);
- assert_eq!(Vesting::vesting_balance(&42), Some(50));
- assert_eq!(claims::Total::::get(), total_claims() - 100);
- });
- }
-
- #[test]
- fn basic_claim_moving_works() {
- new_test_ext().execute_with(|| {
- assert_eq!(Balances::free_balance(42), 0);
- assert_noop!(
- Claims::move_claim(RuntimeOrigin::signed(1), eth(&alice()), eth(&bob()), None),
- BadOrigin
- );
- assert_ok!(Claims::move_claim(
- RuntimeOrigin::signed(6),
- eth(&alice()),
- eth(&bob()),
- None
- ));
- assert_noop!(
- Claims::claim(
- RuntimeOrigin::none(),
- 42,
- sig::(&alice(), &42u64.encode(), &[][..])
- ),
- Error::::SignerHasNoClaim
- );
- assert_ok!(Claims::claim(
- RuntimeOrigin::none(),
- 42,
- sig::(&bob(), &42u64.encode(), &[][..])
- ));
- assert_eq!(Balances::free_balance(&42), 100);
- assert_eq!(Vesting::vesting_balance(&42), Some(50));
- assert_eq!(claims::Total::::get(), total_claims() - 100);
- });
- }
-
- #[test]
- fn claim_attest_moving_works() {
- new_test_ext().execute_with(|| {
- assert_ok!(Claims::move_claim(
- RuntimeOrigin::signed(6),
- eth(&dave()),
- eth(&bob()),
- None
- ));
- let s = sig::(&bob(), &42u64.encode(), StatementKind::Regular.to_text());
- assert_ok!(Claims::claim_attest(
- RuntimeOrigin::none(),
- 42,
- s,
- StatementKind::Regular.to_text().to_vec()
- ));
- assert_eq!(Balances::free_balance(&42), 200);
- });
- }
-
- #[test]
- fn attest_moving_works() {
- new_test_ext().execute_with(|| {
- assert_ok!(Claims::move_claim(
- RuntimeOrigin::signed(6),
- eth(&eve()),
- eth(&bob()),
- Some(42)
- ));
- assert_ok!(Claims::attest(
- RuntimeOrigin::signed(42),
- StatementKind::Saft.to_text().to_vec()
- ));
- assert_eq!(Balances::free_balance(&42), 300);
- });
- }
-
- #[test]
- fn claiming_does_not_bypass_signing() {
- new_test_ext().execute_with(|| {
- assert_ok!(Claims::claim(
- RuntimeOrigin::none(),
- 42,
- sig::(&alice(), &42u64.encode(), &[][..])
- ));
- assert_noop!(
- Claims::claim(
- RuntimeOrigin::none(),
- 42,
- sig::(&dave(), &42u64.encode(), &[][..])
- ),
- Error::::InvalidStatement,
- );
- assert_noop!(
- Claims::claim(
- RuntimeOrigin::none(),
- 42,
- sig::(&eve(), &42u64.encode(), &[][..])
- ),
- Error::::InvalidStatement,
- );
- assert_ok!(Claims::claim(
- RuntimeOrigin::none(),
- 42,
- sig::(&frank(), &42u64.encode(), &[][..])
- ));
- });
- }
-
- #[test]
- fn attest_claiming_works() {
- new_test_ext().execute_with(|| {
- assert_eq!(Balances::free_balance(42), 0);
- let s = sig::(&dave(), &42u64.encode(), StatementKind::Saft.to_text());
- let r = Claims::claim_attest(
- RuntimeOrigin::none(),
- 42,
- s.clone(),
- StatementKind::Saft.to_text().to_vec(),
- );
- assert_noop!(r, Error::::InvalidStatement);
-
- let r = Claims::claim_attest(
- RuntimeOrigin::none(),
- 42,
- s,
- StatementKind::Regular.to_text().to_vec(),
- );
- assert_noop!(r, Error::::SignerHasNoClaim);
- // ^^^ we use ecdsa_recover, so an invalid signature just results in a random signer id
- // being recovered, which realistically will never have a claim.
-
- let s = sig::(&dave(), &42u64.encode(), StatementKind::Regular.to_text());
- assert_ok!(Claims::claim_attest(
- RuntimeOrigin::none(),
- 42,
- s,
- StatementKind::Regular.to_text().to_vec()
- ));
- assert_eq!(Balances::free_balance(&42), 200);
- assert_eq!(claims::Total::::get(), total_claims() - 200);
-
- let s = sig::(&dave(), &42u64.encode(), StatementKind::Regular.to_text());
- let r = Claims::claim_attest(
- RuntimeOrigin::none(),
- 42,
- s,
- StatementKind::Regular.to_text().to_vec(),
- );
- assert_noop!(r, Error::::SignerHasNoClaim);
- });
- }
-
- #[test]
- fn attesting_works() {
- new_test_ext().execute_with(|| {
- assert_eq!(Balances::free_balance(42), 0);
- assert_noop!(
- Claims::attest(RuntimeOrigin::signed(69), StatementKind::Saft.to_text().to_vec()),
- Error::::SenderHasNoClaim
- );
- assert_noop!(
- Claims::attest(
- RuntimeOrigin::signed(42),
- StatementKind::Regular.to_text().to_vec()
- ),
- Error::::InvalidStatement
- );
- assert_ok!(Claims::attest(
- RuntimeOrigin::signed(42),
- StatementKind::Saft.to_text().to_vec()
- ));
- assert_eq!(Balances::free_balance(&42), 300);
- assert_eq!(claims::Total::::get(), total_claims() - 300);
- });
- }
-
- #[test]
- fn claim_cannot_clobber_preclaim() {
- new_test_ext().execute_with(|| {
- assert_eq!(Balances::free_balance(42), 0);
- // Alice's claim is 100
- assert_ok!(Claims::claim(
- RuntimeOrigin::none(),
- 42,
- sig::(&alice(), &42u64.encode(), &[][..])
- ));
- assert_eq!(Balances::free_balance(&42), 100);
- // Eve's claim is 300 through Account 42
- assert_ok!(Claims::attest(
- RuntimeOrigin::signed(42),
- StatementKind::Saft.to_text().to_vec()
- ));
- assert_eq!(Balances::free_balance(&42), 100 + 300);
- assert_eq!(claims::Total::::get(), total_claims() - 400);
- });
- }
-
- #[test]
- fn valid_attest_transactions_are_free() {
- new_test_ext().execute_with(|| {
- let p = PrevalidateAttests::::new();
- let c = RuntimeCall::Claims(ClaimsCall::attest {
- statement: StatementKind::Saft.to_text().to_vec(),
- });
- let di = c.get_dispatch_info();
- assert_eq!(di.pays_fee, Pays::No);
- let r = p.validate_only(Some(42).into(), &c, &di, 20, External, 0);
- assert_eq!(r.unwrap().0, ValidTransaction::default());
- });
- }
-
- #[test]
- fn invalid_attest_transactions_are_recognized() {
- new_test_ext().execute_with(|| {
- let p = PrevalidateAttests::::new();
- let c = RuntimeCall::Claims(ClaimsCall::attest {
- statement: StatementKind::Regular.to_text().to_vec(),
- });
- let di = c.get_dispatch_info();
- let r = p.validate_only(Some(42).into(), &c, &di, 20, External, 0);
- assert!(r.is_err());
- let c = RuntimeCall::Claims(ClaimsCall::attest {
- statement: StatementKind::Saft.to_text().to_vec(),
- });
- let di = c.get_dispatch_info();
- let r = p.validate_only(Some(69).into(), &c, &di, 20, External, 0);
- assert!(r.is_err());
- });
- }
-
- #[test]
- fn cannot_bypass_attest_claiming() {
- new_test_ext().execute_with(|| {
- assert_eq!(Balances::free_balance(42), 0);
- let s = sig::(&dave(), &42u64.encode(), &[]);
- let r = Claims::claim(RuntimeOrigin::none(), 42, s.clone());
- assert_noop!(r, Error::::InvalidStatement);
- });
- }
-
- #[test]
- fn add_claim_works() {
- new_test_ext().execute_with(|| {
- assert_noop!(
- Claims::mint_claim(RuntimeOrigin::signed(42), eth(&bob()), 200, None, None),
- sp_runtime::traits::BadOrigin,
- );
- assert_eq!(Balances::free_balance(42), 0);
- assert_noop!(
- Claims::claim(
- RuntimeOrigin::none(),
- 69,
- sig::(&bob(), &69u64.encode(), &[][..])
- ),
- Error::::SignerHasNoClaim,
- );
- assert_ok!(Claims::mint_claim(RuntimeOrigin::root(), eth(&bob()), 200, None, None));
- assert_eq!(claims::Total::::get(), total_claims() + 200);
- assert_ok!(Claims::claim(
- RuntimeOrigin::none(),
- 69,
- sig::(&bob(), &69u64.encode(), &[][..])
- ));
- assert_eq!(Balances::free_balance(&69), 200);
- assert_eq!(Vesting::vesting_balance(&69), None);
- assert_eq!(claims::Total::::get(), total_claims());
- });
- }
-
- #[test]
- fn add_claim_with_vesting_works() {
- new_test_ext().execute_with(|| {
- assert_noop!(
- Claims::mint_claim(
- RuntimeOrigin::signed(42),
- eth(&bob()),
- 200,
- Some((50, 10, 1)),
- None
- ),
- sp_runtime::traits::BadOrigin,
- );
- assert_eq!(Balances::free_balance(42), 0);
- assert_noop!(
- Claims::claim(
- RuntimeOrigin::none(),
- 69,
- sig::(&bob(), &69u64.encode(), &[][..])
- ),
- Error::::SignerHasNoClaim,
- );
- assert_ok!(Claims::mint_claim(
- RuntimeOrigin::root(),
- eth(&bob()),
- 200,
- Some((50, 10, 1)),
- None
- ));
- assert_ok!(Claims::claim(
- RuntimeOrigin::none(),
- 69,
- sig::(&bob(), &69u64.encode(), &[][..])
- ));
- assert_eq!(Balances::free_balance(&69), 200);
- assert_eq!(Vesting::vesting_balance(&69), Some(50));
-
- // Make sure we can not transfer the vested balance.
- assert_err!(
- >::transfer(
- &69,
- &80,
- 180,
- ExistenceRequirement::AllowDeath
- ),
- TokenError::Frozen,
- );
- });
- }
-
- #[test]
- fn add_claim_with_statement_works() {
- new_test_ext().execute_with(|| {
- assert_noop!(
- Claims::mint_claim(
- RuntimeOrigin::signed(42),
- eth(&bob()),
- 200,
- None,
- Some(StatementKind::Regular)
- ),
- sp_runtime::traits::BadOrigin,
- );
- assert_eq!(Balances::free_balance(42), 0);
- let signature = sig::(&bob(), &69u64.encode(), StatementKind::Regular.to_text());
- assert_noop!(
- Claims::claim_attest(
- RuntimeOrigin::none(),
- 69,
- signature.clone(),
- StatementKind::Regular.to_text().to_vec()
- ),
- Error::::SignerHasNoClaim
- );
- assert_ok!(Claims::mint_claim(
- RuntimeOrigin::root(),
- eth(&bob()),
- 200,
- None,
- Some(StatementKind::Regular)
- ));
- assert_noop!(
- Claims::claim_attest(RuntimeOrigin::none(), 69, signature.clone(), vec![],),
- Error::::SignerHasNoClaim
- );
- assert_ok!(Claims::claim_attest(
- RuntimeOrigin::none(),
- 69,
- signature.clone(),
- StatementKind::Regular.to_text().to_vec()
- ));
- assert_eq!(Balances::free_balance(&69), 200);
- });
- }
-
- #[test]
- fn origin_signed_claiming_fail() {
- new_test_ext().execute_with(|| {
- assert_eq!(Balances::free_balance(42), 0);
- assert_err!(
- Claims::claim(
- RuntimeOrigin::signed(42),
- 42,
- sig::(&alice(), &42u64.encode(), &[][..])
- ),
- sp_runtime::traits::BadOrigin,
- );
- });
- }
-
- #[test]
- fn double_claiming_doesnt_work() {
- new_test_ext().execute_with(|| {
- assert_eq!(Balances::free_balance(42), 0);
- assert_ok!(Claims::claim(
- RuntimeOrigin::none(),
- 42,
- sig::(&alice(), &42u64.encode(), &[][..])
- ));
- assert_noop!(
- Claims::claim(
- RuntimeOrigin::none(),
- 42,
- sig::(&alice(), &42u64.encode(), &[][..])
- ),
- Error::::SignerHasNoClaim
- );
- });
- }
-
- #[test]
- fn claiming_while_vested_doesnt_work() {
- new_test_ext().execute_with(|| {
- CurrencyOf::::make_free_balance_be(&69, total_claims());
- assert_eq!(Balances::free_balance(69), total_claims());
- // A user is already vested
- assert_ok!(::VestingSchedule::add_vesting_schedule(
- &69,
- total_claims(),
- 100,
- 10
- ));
- assert_ok!(Claims::mint_claim(
- RuntimeOrigin::root(),
- eth(&bob()),
- 200,
- Some((50, 10, 1)),
- None
- ));
- // New total
- assert_eq!(claims::Total::::get(), total_claims() + 200);
-
- // They should not be able to claim
- assert_noop!(
- Claims::claim(
- RuntimeOrigin::none(),
- 69,
- sig::(&bob(), &69u64.encode(), &[][..])
- ),
- Error::::VestedBalanceExists,
- );
- });
- }
-
- #[test]
- fn non_sender_sig_doesnt_work() {
- new_test_ext().execute_with(|| {
- assert_eq!(Balances::free_balance(42), 0);
- assert_noop!(
- Claims::claim(
- RuntimeOrigin::none(),
- 42,
- sig::(&alice(), &69u64.encode(), &[][..])
- ),
- Error::::SignerHasNoClaim
- );
- });
- }
-
- #[test]
- fn non_claimant_doesnt_work() {
- new_test_ext().execute_with(|| {
- assert_eq!(Balances::free_balance(42), 0);
- assert_noop!(
- Claims::claim(
- RuntimeOrigin::none(),
- 42,
- sig::(&bob(), &69u64.encode(), &[][..])
- ),
- Error::::SignerHasNoClaim
- );
- });
- }
-
- #[test]
- fn real_eth_sig_works() {
- new_test_ext().execute_with(|| {
- // "Pay RUSTs to the TEST account:2a00000000000000"
- let sig = hex!["444023e89b67e67c0562ed0305d252a5dd12b2af5ac51d6d3cb69a0b486bc4b3191401802dc29d26d586221f7256cd3329fe82174bdf659baea149a40e1c495d1c"];
- let sig = EcdsaSignature(sig);
- let who = 42u64.using_encoded(to_ascii_hex);
- let signer = Claims::eth_recover(&sig, &who, &[][..]).unwrap();
- assert_eq!(signer.0, hex!["6d31165d5d932d571f3b44695653b46dcc327e84"]);
- });
- }
-
- #[test]
- fn validate_unsigned_works() {
- use sp_runtime::traits::ValidateUnsigned;
- let source = sp_runtime::transaction_validity::TransactionSource::External;
-
- new_test_ext().execute_with(|| {
- assert_eq!(
- Pallet::::validate_unsigned(
- source,
- &ClaimsCall::claim {
- dest: 1,
- ethereum_signature: sig::(&alice(), &1u64.encode(), &[][..])
- }
- ),
- Ok(ValidTransaction {
- priority: 100,
- requires: vec![],
- provides: vec![("claims", eth(&alice())).encode()],
- longevity: TransactionLongevity::max_value(),
- propagate: true,
- })
- );
- assert_eq!(
- Pallet::::validate_unsigned(
- source,
- &ClaimsCall::claim { dest: 0, ethereum_signature: EcdsaSignature([0; 65]) }
- ),
- InvalidTransaction::Custom(ValidityError::InvalidEthereumSignature.into()).into(),
- );
- assert_eq!(
- Pallet::