diff --git a/pallets/common/src/traits/compliance_manager.rs b/pallets/common/src/traits/compliance_manager.rs index 7b0b69dacb..ac958d14de 100644 --- a/pallets/common/src/traits/compliance_manager.rs +++ b/pallets/common/src/traits/compliance_manager.rs @@ -15,7 +15,11 @@ use core::result::Result; use frame_support::{dispatch::DispatchError, weights::Weight}; -use polymesh_primitives::{compliance_manager::AssetComplianceResult, Balance, IdentityId, Ticker}; +use polymesh_primitives::{ + compliance_manager::{AssetComplianceResult, ComplianceRequirement}, + condition::{conditions_total_counts, Condition}, + Balance, IdentityId, Ticker, +}; pub trait Config { fn verify_restriction( @@ -42,4 +46,33 @@ pub trait WeightInfo { fn change_compliance_requirement(s: u32, r: u32) -> Weight; fn replace_asset_compliance(c: u32) -> Weight; fn reset_asset_compliance() -> Weight; + + fn condition_costs(conditions: u32, claims: u32, issuers: u32, claim_types: u32) -> Weight; + + fn add_compliance_requirement_full(sender: &[Condition], receiver: &[Condition]) -> Weight { + let (_, claims, issuers, claim_types) = + conditions_total_counts(sender.iter().chain(receiver.iter())); + Self::add_compliance_requirement(sender.len() as u32, receiver.len() as u32) + .saturating_add(Self::condition_costs(0, claims, issuers, claim_types)) + } + + fn change_compliance_requirement_full(req: &ComplianceRequirement) -> Weight { + let (_, claims, issuers, claim_types) = req.counts(); + Self::change_compliance_requirement( + req.sender_conditions.len() as u32, + req.receiver_conditions.len() as u32, + ) + .saturating_add(Self::condition_costs(0, claims, issuers, claim_types)) + } + + fn replace_asset_compliance_full(reqs: &[ComplianceRequirement]) -> Weight { + let (conditions, claims, issuers, claim_types) = + conditions_total_counts(reqs.iter().flat_map(|req| req.conditions())); + Self::replace_asset_compliance(reqs.len() as u32).saturating_add(Self::condition_costs( + conditions, + claims, + issuers, + claim_types, + )) + } } diff --git a/pallets/compliance-manager/src/benchmarking.rs b/pallets/compliance-manager/src/benchmarking.rs index 58f6b8d925..3dfb14dbb2 100644 --- a/pallets/compliance-manager/src/benchmarking.rs +++ b/pallets/compliance-manager/src/benchmarking.rs @@ -21,7 +21,8 @@ use polymesh_common_utilities::{ benchs::{AccountIdOf, User, UserBuilder}, TestUtilsFn, }; -use polymesh_primitives::{asset::AssetType, TrustedFor, TrustedIssuer}; +use polymesh_primitives::{asset::AssetType, ClaimType, Scope, TrustedFor, TrustedIssuer}; +use sp_std::convert::TryFrom; const MAX_DEFAULT_TRUSTED_CLAIM_ISSUERS: u32 = 3; const MAX_TRUSTED_ISSUER_PER_CONDITION: u32 = 3; @@ -29,15 +30,46 @@ const MAX_SENDER_CONDITIONS_PER_COMPLIANCE: u32 = 3; const MAX_RECEIVER_CONDITIONS_PER_COMPLIANCE: u32 = 3; const MAX_COMPLIANCE_REQUIREMENTS: u32 = 2; +const MAX_CONDITIONS: u32 = 10; +const MAX_CONDITION_TYPE_CLAIMS: u32 = 10; +const MAX_CONDITION_ISSUERS: u32 = 10; +const MAX_CONDITION_ISSUER_CLAIM_TYPES: u32 = 10; + +const CLAIM_TYPES: &[ClaimType] = &[ + ClaimType::Accredited, + ClaimType::Affiliate, + ClaimType::BuyLockup, + ClaimType::SellLockup, + ClaimType::CustomerDueDiligence, + ClaimType::KnowYourCustomer, + ClaimType::Jurisdiction, + ClaimType::Exempted, + ClaimType::Blocked, + ClaimType::InvestorUniqueness, + ClaimType::NoType, + ClaimType::InvestorUniquenessV2, +]; + /// Create a token issuer trusted for `Any`. -pub fn make_issuer>>(id: u32) -> TrustedIssuer { +pub fn make_issuer>>( + id: u32, + claim_type_len: Option, +) -> TrustedIssuer { let u = UserBuilder::::default() .generate_did() .seed(id) .build("ISSUER"); TrustedIssuer { issuer: IdentityId::from(u.did.unwrap()), - trusted_for: TrustedFor::Any, + trusted_for: match claim_type_len { + None => TrustedFor::Any, + Some(len) => TrustedFor::Specific( + (0..len) + .into_iter() + .map(|idx| CLAIM_TYPES[idx % CLAIM_TYPES.len()]) + .collect(), + ), + }, } } @@ -46,16 +78,29 @@ pub fn make_issuer>>(id: u32) -> /// - It could have more complexity if `TrustedIssuer::trusted_for` is a vector but not on /// benchmarking of add/remove. That could be useful for benchmarking executions/evaluation of /// complience requiriments. -pub fn make_issuers>>(s: u32) -> Vec { - (0..s).map(|i| make_issuer::(i)).collect::>() +pub fn make_issuers>>( + s: u32, + claim_type_len: Option, +) -> Vec { + (0..s) + .map(|i| make_issuer::(i, claim_type_len)) + .collect() } /// Create simple conditions with a variable number of `issuers`. -pub fn make_conditions(s: u32, issuers: &Vec) -> Vec { +pub fn make_conditions(s: u32, claims: Option, issuers: &[TrustedIssuer]) -> Vec { (0..s) .map(|_| Condition { - condition_type: ConditionType::IsPresent(Claim::NoData), - issuers: issuers.clone(), + condition_type: match claims { + None => ConditionType::IsPresent(Claim::NoData), + Some(len) => ConditionType::IsAnyOf( + (0..len) + .into_iter() + .map(|_| Claim::Blocked(Scope::Custom(vec![0]))) + .collect(), + ), + }, + issuers: issuers.to_vec(), }) .collect() } @@ -103,7 +148,7 @@ struct ComplianceRequirementInfo { impl>> ComplianceRequirementInfo { pub fn add_default_trusted_claim_issuer(self: &Self, i: u32) { - make_issuers::(i).into_iter().for_each(|issuer| { + make_issuers::(i, None).into_iter().for_each(|issuer| { Module::::add_default_trusted_claim_issuer( self.owner.origin.clone().into(), self.ticker.clone(), @@ -130,9 +175,9 @@ impl>> ComplianceRequirementBuilder { let ticker = make_token::(&owner, b"1".to_vec()); // Create issuers (i) and conditions(s & r). - let issuers = make_issuers::(trusted_issuer_count); - let sender_conditions = make_conditions(sender_conditions_count, &issuers); - let receiver_conditions = make_conditions(receiver_conditions_count, &issuers); + let issuers = make_issuers::(trusted_issuer_count, None); + let sender_conditions = make_conditions(sender_conditions_count, None, &issuers); + let receiver_conditions = make_conditions(receiver_conditions_count, None, &issuers); let info = ComplianceRequirementInfo { owner, @@ -168,9 +213,40 @@ impl ComplianceRequirementBuilder { } } +fn setup_conditions_bench>>( + conditions: u32, + claims: u32, + issuers: u32, + claim_types: u32, +) -> Vec { + let issuers = make_issuers::(issuers, Some(claim_types as usize)); + let conditions = make_conditions(conditions, Some(claims as usize), &issuers); + conditions +} + +fn conditions_bench(conditions: Vec) { + let encoded = conditions.encode(); + let decoded = Vec::::decode(&mut encoded.as_slice()) + .expect("This shouldn't fail since we just encoded a `Vec` value."); + if !conditions.eq(&decoded) { + panic!("This shouldn't fail."); + } +} + benchmarks! { where_clause { where T: TestUtilsFn> } + condition_costs { + let a in 1..MAX_CONDITIONS; + let b in 1..MAX_CONDITION_TYPE_CLAIMS; + let c in 1..MAX_CONDITION_ISSUERS; + let d in 1..MAX_CONDITION_ISSUER_CLAIM_TYPES; + + let conditions = setup_conditions_bench::(a, b, c, d); + }: { + conditions_bench(conditions); + } + add_compliance_requirement { // INTERNAL: This benchmark only evaluate the adding operation. Its execution should be measured in another module. let s in 1..MAX_SENDER_CONDITIONS_PER_COMPLIANCE; @@ -237,7 +313,7 @@ benchmarks! { d.add_default_trusted_claim_issuer(MAX_DEFAULT_TRUSTED_CLAIM_ISSUERS -1); // Add one more for benchmarking. - let new_issuer = make_issuer::(MAX_DEFAULT_TRUSTED_CLAIM_ISSUERS); + let new_issuer = make_issuer::(MAX_DEFAULT_TRUSTED_CLAIM_ISSUERS, None); }: _(d.owner.origin, d.ticker, new_issuer.clone()) verify { let trusted_issuers = Module::::trusted_claim_issuer(d.ticker); @@ -300,9 +376,9 @@ benchmarks! { MAX_RECEIVER_CONDITIONS_PER_COMPLIANCE) .add_compliance_requirement().build(); - let issuers = make_issuers::(MAX_TRUSTED_ISSUER_PER_CONDITION); - let sender_conditions = make_conditions(MAX_SENDER_CONDITIONS_PER_COMPLIANCE, &issuers); - let receiver_conditions = make_conditions(MAX_RECEIVER_CONDITIONS_PER_COMPLIANCE, &issuers); + let issuers = make_issuers::(MAX_TRUSTED_ISSUER_PER_CONDITION, None); + let sender_conditions = make_conditions(MAX_SENDER_CONDITIONS_PER_COMPLIANCE, None, &issuers); + let receiver_conditions = make_conditions(MAX_RECEIVER_CONDITIONS_PER_COMPLIANCE, None, &issuers); // Add more requirements to the asset, if `c > 1`. (1..c).for_each( |_i| { diff --git a/pallets/compliance-manager/src/lib.rs b/pallets/compliance-manager/src/lib.rs index aec87cb0bc..da5e754c95 100644 --- a/pallets/compliance-manager/src/lib.rs +++ b/pallets/compliance-manager/src/lib.rs @@ -103,10 +103,7 @@ use polymesh_primitives::{ proposition, storage_migration_ver, Balance, Claim, Condition, ConditionType, Context, IdentityId, Ticker, TrustedFor, TrustedIssuer, }; -use sp_std::{ - convert::{From, TryFrom}, - prelude::*, -}; +use sp_std::{convert::From, prelude::*}; type ExternalAgents = pallet_external_agents::Module; type Identity = pallet_identity::Module; @@ -190,7 +187,7 @@ decl_module! { /// /// # Permissions /// * Asset - #[weight = ::WeightInfo::add_compliance_requirement(sender_conditions.len() as u32, receiver_conditions.len() as u32)] + #[weight = ::WeightInfo::add_compliance_requirement_full(&sender_conditions, &receiver_conditions)] pub fn add_compliance_requirement(origin, ticker: Ticker, sender_conditions: Vec, receiver_conditions: Vec) { let did = >::ensure_perms(origin, ticker)?; @@ -257,7 +254,7 @@ decl_module! { /// /// # Permissions /// * Asset - #[weight = ::WeightInfo::replace_asset_compliance(asset_compliance.len() as u32)] + #[weight = ::WeightInfo::replace_asset_compliance_full(&asset_compliance)] pub fn replace_asset_compliance(origin, ticker: Ticker, asset_compliance: Vec) { let did = >::ensure_perms(origin, ticker)?; @@ -392,10 +389,7 @@ decl_module! { /// /// # Permissions /// * Asset - #[weight = ::WeightInfo::change_compliance_requirement( - new_req.sender_conditions.len() as u32, - new_req.receiver_conditions.len() as u32, - )] + #[weight = ::WeightInfo::change_compliance_requirement_full(&new_req)] pub fn change_compliance_requirement(origin, ticker: Ticker, new_req: ComplianceRequirement) { let did = >::ensure_perms(origin, ticker)?; @@ -601,17 +595,15 @@ impl Module { let complexity = asset_compliance .iter() .flat_map(|req| req.conditions()) - .fold(0usize, |complexity, condition| { - let (claims, issuers) = condition.complexity(); - complexity.saturating_add(claims.saturating_mul(match issuers { - 0 => default_issuer_count, - _ => issuers, - })) - }); - if let Ok(complexity_u32) = u32::try_from(complexity) { - if complexity_u32 <= T::MaxConditionComplexity::get() { - return Ok(()); - } + .fold(0u32, |total, condition| { + let complexity = condition.complexity(default_issuer_count); + total.saturating_add(complexity) + }) + // NB: If the compliance requirements are empty (0 complexity), + // then use the count of requirements. + .max(asset_compliance.len() as u32); + if complexity <= T::MaxConditionComplexity::get() { + return Ok(()); } Err(Error::::ComplianceRequirementTooComplex.into()) } diff --git a/pallets/weights/src/pallet_compliance_manager.rs b/pallets/weights/src/pallet_compliance_manager.rs index 9ca72a9d82..8147acea98 100644 --- a/pallets/weights/src/pallet_compliance_manager.rs +++ b/pallets/weights/src/pallet_compliance_manager.rs @@ -52,6 +52,17 @@ use polymesh_runtime_common::{RocksDbWeight as DbWeight, Weight}; /// Weights for pallet_compliance_manager using the Substrate node and recommended hardware. pub struct WeightInfo; impl pallet_compliance_manager::WeightInfo for WeightInfo { + fn condition_costs(a: u32, b: u32, c: u32, d: u32) -> Weight { + (0 as Weight) + // Standard Error: 154_000 + .saturating_add((13_470_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 154_000 + .saturating_add((5_972_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 154_000 + .saturating_add((6_144_000 as Weight).saturating_mul(c as Weight)) + // Standard Error: 154_000 + .saturating_add((906_000 as Weight).saturating_mul(d as Weight)) + } fn add_compliance_requirement(s: u32, r: u32) -> Weight { (98_941_000 as Weight) // Standard Error: 740_000 diff --git a/primitives/src/compliance_manager.rs b/primitives/src/compliance_manager.rs index eeb77d1df5..ef796b6cc4 100644 --- a/primitives/src/compliance_manager.rs +++ b/primitives/src/compliance_manager.rs @@ -13,7 +13,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::Condition; +use crate::condition::{conditions_total_counts, Condition}; use codec::{Decode, Encode}; #[cfg(feature = "std")] use sp_runtime::{Deserialize, Serialize}; @@ -51,6 +51,13 @@ impl ComplianceRequirement { .iter() .chain(self.receiver_conditions.iter()) } + + /// Return the total number of conditions, claims, issuers, and claim_types. + /// + /// This is used for weight calculation. + pub fn counts(&self) -> (u32, u32, u32, u32) { + conditions_total_counts(self.conditions()) + } } /// A compliance requirement along with its evaluation result diff --git a/primitives/src/condition.rs b/primitives/src/condition.rs index 9bbe6f8364..712dfd1e1a 100644 --- a/primitives/src/condition.rs +++ b/primitives/src/condition.rs @@ -19,6 +19,7 @@ use core::iter; use either::Either; #[cfg(feature = "std")] use sp_runtime::{Deserialize, Serialize}; +use sp_std::convert::TryInto; use sp_std::prelude::*; /// Defines a static / dynamic identity. @@ -49,7 +50,8 @@ pub enum ConditionType { } impl ConditionType { - fn complexity(&self) -> usize { + /// Return the number of `Claim` or `TargetIdentity`. + fn count(&self) -> usize { match self { ConditionType::IsIdentity(..) | ConditionType::IsPresent(..) @@ -99,6 +101,16 @@ impl TrustedIssuer { TrustedFor::Specific(ok_types) => ok_types.contains(&ty), } } + + /// Count number of claim types this issuers is trusted for. + /// + /// Returns `1` for `TrustedFor::Any`. + fn count(&self) -> usize { + match &self.trusted_for { + TrustedFor::Any => 1, + TrustedFor::Specific(types) => types.len(), + } + } } /// Create a `TrustedIssuer` trusted for any claim type. @@ -139,8 +151,38 @@ impl Condition { } /// Returns worst case complexity of a condition. - pub fn complexity(&self) -> (usize, usize) { - (self.condition_type.complexity(), self.issuers.len()) + pub fn complexity(&self, default_issuer_count: usize) -> u32 { + let issuers = match self.issuers.len() { + 0 => default_issuer_count, + count => count, + }; + self.condition_type + .count() + // NB: `max(1)` makes sure issuer count is not zero. + .saturating_mul(issuers.max(1)) + .try_into() + .unwrap_or(u32::MAX) + } + + /// Return number of claims, issuers, and claim_types. + /// + /// This is used for weight calculation. + /// + /// Returns: `(claims_count, issuer_count, claim_type_count)` + fn counts(&self) -> (u32, u32, u32) { + // Count the number of claims. + let claims = self.condition_type.count().try_into().unwrap_or(u32::MAX); + // Count the number of issuers. + let issuers = self.issuers.len().try_into().unwrap_or(u32::MAX); + // Count the total number of claim types in all issuers. + let claim_types = self + .issuers + .iter() + .fold(0usize, |count, issuer| count.saturating_add(issuer.count())) + .try_into() + .unwrap_or(u32::MAX); + + (claims, issuers, claim_types) } /// Returns all the claims in the condition. @@ -153,6 +195,29 @@ impl Condition { } } +/// Return the total number of condtions, claims, issuers, and claim_types. +/// +/// This is used for weight calculation. +/// +/// Returns: `(condition_count, claims_count, issuer_count, claim_type_count)` +pub fn conditions_total_counts<'a>( + conditions: impl IntoIterator, +) -> (u32, u32, u32, u32) { + // Count the total number of claims, issuers, and claim_types in all conditions. + conditions.into_iter().fold( + (0u32, 0u32, 0u32, 0u32), + |(count, total_claims, total_issuers, total_claim_types), condition| { + let (claims, issuers, claim_types) = condition.counts(); + ( + count.saturating_add(1), + total_claims.saturating_add(claims), + total_issuers.saturating_add(issuers), + total_claim_types.saturating_add(claim_types), + ) + }, + ) +} + impl From for Condition { fn from(condition_type: ConditionType) -> Self { Condition::new(condition_type, Vec::new())