Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ members = [
exclude = ["contracts", "integration", "metadata-tools"]

[workspace.package]
version = "8.1.2"
version = "8.1.3"
authors = ["PolymeshAssociation"]
license-file = "./LICENSE"
edition = "2021"
Expand Down
2 changes: 1 addition & 1 deletion pallets/asset/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3525,7 +3525,7 @@ impl<T: AssetConfig> Pallet<T> {
}

/// Ensure asset metadata `value` is within the global limit.
fn ensure_asset_metadata_value_limited(value: &AssetMetadataValue) -> DispatchResult {
pub fn ensure_asset_metadata_value_limited(value: &AssetMetadataValue) -> DispatchResult {
ensure!(
value.len() <= T::AssetMetadataValueMaxLength::get() as usize,
Error::<T>::AssetMetadataValueMaxLengthExceeded
Expand Down
150 changes: 88 additions & 62 deletions pallets/compliance-manager/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,12 @@ use pallet_identity::Config as IdentityConfig;
use polymesh_primitives::agent::AgentGroup;
use polymesh_primitives::traits::AssetFnTrait;
use polymesh_primitives::{
asset::AssetType, AssetHolderKind, AuthorizationData, ClaimType, CountryCode, Scope,
TargetIdentity, TrustedFor, TrustedIssuer, WeightMeter,
asset::AssetType, AssetHolderKind, AuthorizationData, ClaimType, CountryCode,
CustomClaimTypeId, Scope, TargetIdentity, TrustedFor, TrustedIssuer, WeightMeter,
};

use crate::*;

const MAX_DEFAULT_TRUSTED_CLAIM_ISSUERS: u32 = 3;
const MAX_TRUSTED_ISSUER_PER_CONDITION: u32 = 3;
const MAX_SENDER_CONDITIONS_PER_COMPLIANCE: u32 = 3;
const MAX_RECEIVER_CONDITIONS_PER_COMPLIANCE: u32 = 3;
const MAX_CONDITIONS_PER_COMPLIANCE: u32 =
Expand All @@ -38,21 +36,26 @@ 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,
];

/// Create a token issuer trusted for `Any`.
const MAX_CONDITION_ISSUER_CLAIM_TYPES: u32 = 16;

/// Returns the maximum number of trusted issuers allowed by the runtime.
fn max_trusted_issuers<T: Config>() -> u32 {
T::MaximumNumberOfTrustedIssuers::get()
}

/// Returns the maximum number of claim types a trusted issuer can be trusted for.
fn max_trusted_issuer_claim_types<T: Config>() -> usize {
T::MaxTrustedIssuerClaimTypes::get() as usize
}

/// Returns the maximum number of trusted issuers each condition can have, such that
/// `total_conditions` conditions (with one claim each) stay within `MaxConditionComplexity`.
fn max_issuers_per_condition<T: Config>(total_conditions: u32) -> u32 {
let by_complexity = T::MaxConditionComplexity::get() / total_conditions.max(1);
max_trusted_issuers::<T>().min(by_complexity).max(1)
}

/// Create a token issuer trusted for `Any`, or for `claim_type_len` distinct custom claim types.
pub fn make_issuer<T: IdentityConfig>(id: u32, claim_type_len: Option<usize>) -> TrustedIssuer {
let u = UserBuilder::<T>::default()
.generate_did()
Expand All @@ -63,9 +66,9 @@ pub fn make_issuer<T: IdentityConfig>(id: u32, claim_type_len: Option<usize>) ->
trusted_for: match claim_type_len {
None => TrustedFor::Any,
Some(len) => TrustedFor::Specific(
// Custom claim types have the largest encoding and are distinct, so they are not removed by dedup.
(0..len)
.into_iter()
.map(|idx| CLAIM_TYPES[idx % CLAIM_TYPES.len()])
.map(|idx| ClaimType::Custom(CustomClaimTypeId(idx as u32 + 1)))
.collect(),
),
},
Expand Down Expand Up @@ -149,15 +152,18 @@ struct ComplianceRequirementInfo<T: Config> {
}

impl<T: Config> ComplianceRequirementInfo<T> {
/// Adds `i` default trusted issuers, each trusted for the maximum number of claim types.
pub fn add_default_trusted_claim_issuer(self: &Self, i: u32) {
make_issuers::<T>(i, None).into_iter().for_each(|issuer| {
Pallet::<T>::add_default_trusted_claim_issuer(
self.owner.origin.clone().into(),
self.asset_id,
issuer,
)
.unwrap();
});
make_issuers::<T>(i, Some(max_trusted_issuer_claim_types::<T>()))
.into_iter()
.for_each(|issuer| {
Pallet::<T>::add_default_trusted_claim_issuer(
self.owner.origin.clone().into(),
self.asset_id,
issuer,
)
.unwrap();
});
}
}

Expand Down Expand Up @@ -294,15 +300,9 @@ where
});

if read_trusted_issuers_storage {
// Adds all trusted issuers as the default for the asset_id
trusted_issuers.into_iter().for_each(|trusted_issuer| {
Pallet::<T>::base_add_default_trusted_claim_issuer(
sender.did(),
asset_id,
trusted_issuer,
)
.unwrap();
});
// Adds all trusted issuers as the default for the asset_id.
// NB: Storage is written directly, since `n_issuers` can exceed `MaximumNumberOfTrustedIssuers`.
TrustedClaimIssuer::<T>::insert(asset_id, trusted_issuers);
let condition = Condition::new(ConditionType::IsNoneOf(claims), Vec::new());
return condition;
}
Expand All @@ -311,22 +311,38 @@ where
}

/// Adds `n` requirements for `asset_id` and pauses compliance if `pause_compliance` is true.
///
/// The requirements are built for the worst case (each has complexity 1):
/// - The first `n - 1` requirements fail (no claims are issued, so `IsAnyOf` is false),
/// forcing all requirements to be evaluated. The last one uses `IsNoneOf`, so the asset is still compliant.
/// - Each requirement's claim scope is unique per asset, so no claim read is shared (cached) between assets.
pub fn setup_asset_compliance<T: Config>(
caller_did: IdentityId,
asset_id: AssetId,
n: u32,
pause_compliance: bool,
) {
let claim_types: Vec<ClaimType> = (1..T::MaxTrustedIssuerClaimTypes::get())
.map(|i| ClaimType::Custom(CustomClaimTypeId(i)))
.chain(core::iter::once(ClaimType::Jurisdiction))
.collect();

(0..n).for_each(|i| {
let trusted_issuers = vec![TrustedIssuer::from(IdentityId::from(i as u128))];
let claims = vec![Claim::Jurisdiction(
CountryCode::BR,
Scope::Custom(vec![i as u8]),
)];
let sender_conditions = vec![Condition::new(
ConditionType::IsNoneOf(claims),
trusted_issuers,
)];
let trusted_issuers = vec![TrustedIssuer {
issuer: IdentityId::from(i as u128),
trusted_for: TrustedFor::Specific(claim_types.clone()),
}];
// The scope includes `asset_id` so that each asset reads unique `Identity::Claims` keys.
let mut scope = vec![0u8; 32];
scope[..16].copy_from_slice(asset_id.as_ref());
scope[16..20].copy_from_slice(&i.to_le_bytes());
let claims = vec![Claim::Jurisdiction(CountryCode::BR, Scope::Custom(scope))];
let condition_type = if i + 1 < n {
ConditionType::IsAnyOf(claims)
} else {
ConditionType::IsNoneOf(claims)
};
let sender_conditions = vec![Condition::new(condition_type, trusted_issuers)];
Pallet::<T>::base_add_compliance_requirement(
caller_did,
asset_id,
Expand Down Expand Up @@ -357,7 +373,10 @@ benchmarks! {
// INTERNAL: This benchmark only evaluate the adding operation. Its execution should be measured in another module.
let c in 1..MAX_CONDITIONS_PER_COMPLIANCE;

let d = ComplianceRequirementBuilder::<T>::new(MAX_TRUSTED_ISSUER_PER_CONDITION, c).build();
let d = ComplianceRequirementBuilder::<T>::new(
max_issuers_per_condition::<T>(MAX_CONDITIONS_PER_COMPLIANCE),
c
).build();

}: _(d.owner.origin, d.asset_id, d.sender_conditions.clone(), d.receiver_conditions.clone())
verify {
Expand All @@ -369,7 +388,7 @@ benchmarks! {
remove_compliance_requirement {
// Add the compliance requirement.
let d = ComplianceRequirementBuilder::<T>::new(
MAX_TRUSTED_ISSUER_PER_CONDITION,
max_issuers_per_condition::<T>(MAX_CONDITIONS_PER_COMPLIANCE),
MAX_CONDITIONS_PER_COMPLIANCE)
.add_compliance_requirement().build();

Expand All @@ -388,7 +407,7 @@ benchmarks! {

pause_asset_compliance {
let d = ComplianceRequirementBuilder::<T>::new(
MAX_TRUSTED_ISSUER_PER_CONDITION,
max_issuers_per_condition::<T>(MAX_CONDITIONS_PER_COMPLIANCE),
MAX_CONDITIONS_PER_COMPLIANCE)
.add_compliance_requirement().build();
}: _(d.owner.origin, d.asset_id)
Expand All @@ -410,13 +429,15 @@ benchmarks! {

add_default_trusted_claim_issuer {
// Create and add the compliance requirement.
let d = ComplianceRequirementBuilder::<T>::new(1, 1)
// The conditions have no issuers, so the complexity check depends on the default trusted issuers.
let d = ComplianceRequirementBuilder::<T>::new(0, MAX_CONDITIONS_PER_COMPLIANCE)
.add_compliance_requirement()
.build();
d.add_default_trusted_claim_issuer(MAX_DEFAULT_TRUSTED_CLAIM_ISSUERS -1);
let max_issuers = max_trusted_issuers::<T>();
d.add_default_trusted_claim_issuer(max_issuers - 1);

// Add one more for benchmarking.
let new_issuer = make_issuer::<T>(MAX_DEFAULT_TRUSTED_CLAIM_ISSUERS, None);
let new_issuer = make_issuer::<T>(max_issuers, Some(max_trusted_issuer_claim_types::<T>()));
}: _(d.owner.origin, d.asset_id, new_issuer.clone())
verify {
let trusted_issuers = TrustedClaimIssuer::<T>::get(d.asset_id);
Expand All @@ -430,8 +451,8 @@ benchmarks! {
let d = ComplianceRequirementBuilder::<T>::new(2, 1)
.add_compliance_requirement().build();

// Generate some trusted issuer.
d.add_default_trusted_claim_issuer(MAX_DEFAULT_TRUSTED_CLAIM_ISSUERS);
// Generate the maximum number of trusted issuers.
d.add_default_trusted_claim_issuer(max_trusted_issuers::<T>());

// Delete the latest trusted issuer.
let issuer = TrustedClaimIssuer::<T>::get(d.asset_id).pop().unwrap();
Expand All @@ -449,7 +470,7 @@ benchmarks! {

// Add maximum size compliance requirements.
let d = ComplianceRequirementBuilder::<T>::new(
MAX_TRUSTED_ISSUER_PER_CONDITION,
max_issuers_per_condition::<T>(MAX_CONDITIONS_PER_COMPLIANCE),
MAX_CONDITIONS_PER_COMPLIANCE)
.add_compliance_requirement().build();

Expand All @@ -458,7 +479,10 @@ benchmarks! {

// Build a new set of compliance requirements.
let (sender_count, receiver_count) = split_conditions(c);
let issuers = make_issuers::<T>(MAX_TRUSTED_ISSUER_PER_CONDITION, None);
let issuers = make_issuers::<T>(
max_issuers_per_condition::<T>(MAX_CONDITIONS_PER_COMPLIANCE),
None
);
let new_req = ComplianceRequirement {
id,
sender_conditions: make_conditions(sender_count, None, &issuers),
Expand All @@ -478,13 +502,15 @@ benchmarks! {
replace_asset_compliance {
let c in 0..MAX_COMPLIANCE_REQUIREMENTS;

// The issuers are limited so that all requirements stay within `MaxConditionComplexity`.
let issuers_count =
max_issuers_per_condition::<T>(MAX_COMPLIANCE_REQUIREMENTS * MAX_CONDITIONS_PER_COMPLIANCE);

// Always add at least one compliance requirement.
let d = ComplianceRequirementBuilder::<T>::new(
MAX_TRUSTED_ISSUER_PER_CONDITION,
MAX_CONDITIONS_PER_COMPLIANCE)
let d = ComplianceRequirementBuilder::<T>::new(issuers_count, MAX_CONDITIONS_PER_COMPLIANCE)
.add_compliance_requirement().build();

let issuers = make_issuers::<T>(MAX_TRUSTED_ISSUER_PER_CONDITION, None);
let issuers = make_issuers::<T>(issuers_count, 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);

Expand Down Expand Up @@ -513,7 +539,7 @@ benchmarks! {
reset_asset_compliance {
// Add the compliance requirement.
let d = ComplianceRequirementBuilder::<T>::new(
MAX_TRUSTED_ISSUER_PER_CONDITION,
max_issuers_per_condition::<T>(MAX_CONDITIONS_PER_COMPLIANCE),
MAX_CONDITIONS_PER_COMPLIANCE)
.add_compliance_requirement().build();
}: _(d.owner.origin, d.asset_id)
Expand Down
34 changes: 30 additions & 4 deletions pallets/compliance-manager/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ use frame_support::weights::Weight;
use frame_system::pallet_prelude::OriginFor;
use sp_std::{convert::From, prelude::*};

use pallet_base::ensure_length_ok;
use pallet_external_agents::{Config as EAConfig, GroupOfAgent};
use polymesh_primitives::asset::AssetId;
use polymesh_primitives::compliance_manager::{
Expand Down Expand Up @@ -126,6 +125,15 @@ pub mod pallet {
/// The maximum claim reads that are allowed to happen in worst case of a condition resolution
#[pallet::constant]
type MaxConditionComplexity: Get<u32>;

/// The maximum number of trusted issuers allowed in the default trusted claim issuers of an asset
/// and in the `issuers` list of each condition.
#[pallet::constant]
type MaximumNumberOfTrustedIssuers: Get<u32>;

/// The maximum number of claim types a trusted issuer can be trusted for (`TrustedFor::Specific`).
#[pallet::constant]
type MaxTrustedIssuerClaimTypes: Get<u32>;
}

#[pallet::event]
Expand Down Expand Up @@ -260,6 +268,10 @@ pub mod pallet {
ComplianceRequirementTooComplex,
/// The maximum weight limit for executing the function was exceeded.
WeightLimitExceeded,
/// The number of trusted issuers exceeds `MaximumNumberOfTrustedIssuers`.
TooManyTrustedIssuers,
/// The number of claim types of a trusted issuer exceeds `MaxTrustedIssuerClaimTypes`.
TooManyTrustedIssuerClaimTypes,
}

#[pallet::pallet]
Expand Down Expand Up @@ -591,7 +603,7 @@ impl<T: Config> Pallet<T> {
TrustedClaimIssuer::<T>::try_mutate(asset_id, |issuers| {
// Ensure we don't have too many issuers now in total.
let new_count = issuers.len().saturating_add(1);
ensure_length_ok::<T>(new_count)?;
Self::ensure_trusted_issuers_count_limited(new_count)?;

// Ensure the new issuer is new.
ensure!(
Expand Down Expand Up @@ -815,17 +827,31 @@ impl<T: Config> Pallet<T> {

fn ensure_issuers_in_req_limited(req: &ComplianceRequirement) -> DispatchResult {
req.conditions().try_for_each(|cond| {
ensure_length_ok::<T>(cond.issuers.len())?;
Self::ensure_trusted_issuers_count_limited(cond.issuers.len())?;
cond.issuers
.iter()
.try_for_each(Self::ensure_issuer_limited)
})
}

fn ensure_trusted_issuers_count_limited(count: usize) -> DispatchResult {
ensure!(
count <= T::MaximumNumberOfTrustedIssuers::get() as usize,
Error::<T>::TooManyTrustedIssuers
);
Ok(())
}

fn ensure_issuer_limited(issuer: &TrustedIssuer) -> DispatchResult {
match &issuer.trusted_for {
TrustedFor::Any => Ok(()),
TrustedFor::Specific(cts) => ensure_length_ok::<T>(cts.len()),
TrustedFor::Specific(cts) => {
ensure!(
cts.len() <= T::MaxTrustedIssuerClaimTypes::get() as usize,
Error::<T>::TooManyTrustedIssuerClaimTypes
);
Ok(())
}
}
}

Expand Down
Loading