Skip to content

Commit 8957865

Browse files
authored
MESH-1722 fix dedup in compliance requirements (#1142)
* Add test for `change_compliance_requirement` bug. * Fix bug with `change_compliance_requirement` * Add missing `ensure_issuers_in_req_limited` check. `change_compliance_requirement` was missing a check that the new compliance requirement had a limited number of issuers. * Test for dedup bug in `replace_asset_compliance` * Fix dedup in `replace_asset_compliance` * Add comment about sorting. * Use binary search. * Improve benchmarks for `replace_asset_compliance`
1 parent aaf8385 commit 8957865

File tree

3 files changed

+111
-24
lines changed

3 files changed

+111
-24
lines changed

pallets/compliance-manager/src/benchmarking.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -291,9 +291,9 @@ benchmarks! {
291291
}
292292

293293
replace_asset_compliance {
294-
let c in 0..(MAX_COMPLIANCE_REQUIREMENTS-1);
294+
let c in 0..MAX_COMPLIANCE_REQUIREMENTS;
295295

296-
// Add the compliance requirement.
296+
// Always add at least one compliance requirement.
297297
let d = ComplianceRequirementBuilder::<T>::new(
298298
MAX_TRUSTED_ISSUER_PER_CONDITION,
299299
MAX_SENDER_CONDITIONS_PER_COMPLIANCE,
@@ -304,8 +304,8 @@ benchmarks! {
304304
let sender_conditions = make_conditions(MAX_SENDER_CONDITIONS_PER_COMPLIANCE, &issuers);
305305
let receiver_conditions = make_conditions(MAX_RECEIVER_CONDITIONS_PER_COMPLIANCE, &issuers);
306306

307-
// Add new requirements to the asset.
308-
(0..c).for_each( |_i| {
307+
// Add more requirements to the asset, if `c > 1`.
308+
(1..c).for_each( |_i| {
309309
let _ = Module::<T>::add_compliance_requirement(
310310
d.owner.origin.clone().into(),
311311
d.ticker.clone(),

pallets/compliance-manager/src/lib.rs

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -201,10 +201,9 @@ decl_module! {
201201
// Bundle as a requirement.
202202
let id = Self::get_latest_requirement_id(ticker) + 1u32;
203203
let mut new_req = ComplianceRequirement { sender_conditions, receiver_conditions, id };
204-
new_req.dedup();
205204

206-
// Ensure issuers are limited in length.
207-
Self::ensure_issuers_in_req_limited(&new_req)?;
205+
// Dedup `ClaimType`s and ensure issuers are limited in length.
206+
Self::dedup_and_ensure_requirement_limited(&mut new_req)?;
208207

209208
// Add to existing requirements, and place a limit on the total complexity.
210209
let mut asset_compliance = AssetCompliances::get(ticker);
@@ -245,6 +244,9 @@ decl_module! {
245244

246245
/// Replaces an asset's compliance by ticker with a new compliance.
247246
///
247+
/// Compliance requirements will be sorted (ascending by id) before
248+
/// replacing the current requirements.
249+
///
248250
/// # Arguments
249251
/// * `ticker` - the asset ticker,
250252
/// * `asset_compliance - the new asset compliance.
@@ -265,14 +267,14 @@ decl_module! {
265267
// Ensure there are no duplicate requirement ids.
266268
let mut asset_compliance = asset_compliance;
267269
let start_len = asset_compliance.len();
270+
asset_compliance.sort_by_key(|r| r.id);
268271
asset_compliance.dedup_by_key(|r| r.id);
269272
ensure!(start_len == asset_compliance.len(), Error::<T>::DuplicateComplianceRequirements);
270273

271-
// Dedup `ClaimType`s in `TrustedFor::Specific`.
272-
asset_compliance.iter_mut().for_each(|r| r.dedup());
274+
// Dedup `ClaimType`s and ensure issuers are limited in length.
275+
asset_compliance.iter_mut().try_for_each(Self::dedup_and_ensure_requirement_limited)?;
273276

274-
// Ensure issuers are limited in length + limit the complexity.
275-
asset_compliance.iter().try_for_each(Self::ensure_issuers_in_req_limited)?;
277+
// Ensure the complexity is limited.
276278
Self::verify_compliance_complexity(&asset_compliance, ticker, 0)?;
277279

278280
// Commit changes to storage + emit event.
@@ -400,19 +402,24 @@ decl_module! {
400402
// Ensure `Scope::Custom(..)`s are limited.
401403
Self::ensure_custom_scopes_limited(new_req.conditions())?;
402404

403-
ensure!(Self::get_latest_requirement_id(ticker) >= new_req.id, Error::<T>::InvalidComplianceRequirementId);
404-
405405
let mut asset_compliance = AssetCompliances::get(ticker);
406406
let reqs = &mut asset_compliance.requirements;
407-
if let Some(req) = reqs.iter_mut().find(|req| req.id == new_req.id) {
408-
let mut new_req = new_req;
409-
new_req.dedup();
410-
411-
*req = new_req.clone();
412-
Self::verify_compliance_complexity(&reqs, ticker, 0)?;
413-
AssetCompliances::insert(&ticker, asset_compliance);
414-
Self::deposit_event(Event::ComplianceRequirementChanged(did, ticker, new_req));
415-
}
407+
408+
// If the compliance requirement is not found, throw an error.
409+
let pos = reqs.binary_search_by_key(&new_req.id, |req| req.id)
410+
.map_err(|_| Error::<T>::InvalidComplianceRequirementId)?;
411+
412+
// Dedup `ClaimType`s and ensure issuers are limited in length.
413+
let mut new_req = new_req;
414+
Self::dedup_and_ensure_requirement_limited(&mut new_req)?;
415+
416+
// Update asset compliance and verify complexity is limited.
417+
reqs[pos] = new_req.clone();
418+
Self::verify_compliance_complexity(&reqs, ticker, 0)?;
419+
420+
// Store updated asset compliance.
421+
AssetCompliances::insert(&ticker, asset_compliance);
422+
Self::deposit_event(Event::ComplianceRequirementChanged(did, ticker, new_req));
416423
}
417424
}
418425
}
@@ -617,6 +624,14 @@ impl<T: Config> Module<T> {
617624
.try_for_each(Identity::<T>::ensure_custom_scopes_limited)
618625
}
619626

627+
fn dedup_and_ensure_requirement_limited(req: &mut ComplianceRequirement) -> DispatchResult {
628+
// Dedup `ClaimType`s in `TrustedFor::Specific`.
629+
req.dedup();
630+
631+
// Ensure issuers are limited in length.
632+
Self::ensure_issuers_in_req_limited(req)
633+
}
634+
620635
fn ensure_issuers_in_req_limited(req: &ComplianceRequirement) -> DispatchResult {
621636
req.conditions().try_for_each(|cond| {
622637
ensure_length_ok::<T>(cond.issuers.len())?;

pallets/runtime/tests/src/compliance_manager_test.rs

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@ macro_rules! assert_add_claim {
7373
};
7474
}
7575

76+
fn get_latest_requirement_id(ticker: Ticker) -> u32 {
77+
ComplianceManager::asset_compliance(ticker)
78+
.requirements
79+
.last()
80+
.map(|r| r.id)
81+
.unwrap_or(0)
82+
}
83+
7684
#[test]
7785
fn should_add_and_verify_compliance_requirement() {
7886
ExtBuilder::default()
@@ -244,19 +252,35 @@ fn should_add_and_verify_compliance_requirement_we() {
244252
receiver_condition2
245253
);
246254

255+
let comp_req = ComplianceRequirement {
256+
sender_conditions: vec![sender_condition.clone()],
257+
receiver_conditions: vec![receiver_condition1.clone(), receiver_condition2.clone()],
258+
id: 1,
259+
};
260+
261+
// Add two more compliance requirements.
247262
for _ in 0..2 {
248263
assert_ok!(ComplianceManager::add_compliance_requirement(
249264
owner.origin(),
250265
ticker,
251-
vec![sender_condition.clone()],
252-
vec![receiver_condition1.clone(), receiver_condition2.clone()],
266+
comp_req.sender_conditions.clone(),
267+
comp_req.receiver_conditions.clone(),
253268
));
254269
}
270+
assert_eq!(get_latest_requirement_id(ticker), 3);
255271
assert_ok!(ComplianceManager::remove_compliance_requirement(
256272
owner.origin(),
257273
ticker,
258274
1
259275
)); // OK; latest == 3
276+
assert_eq!(get_latest_requirement_id(ticker), 3);
277+
278+
// Try changing the removed compliance requirement.
279+
assert_noop!(
280+
ComplianceManager::change_compliance_requirement(owner.origin(), ticker, comp_req),
281+
CMError::<TestStorage>::InvalidComplianceRequirementId
282+
); // BAD OK; latest == 3, but 1 was just removed.
283+
260284
assert_noop!(
261285
ComplianceManager::remove_compliance_requirement(owner.origin(), ticker, 1),
262286
CMError::<TestStorage>::InvalidComplianceRequirementId
@@ -265,6 +289,7 @@ fn should_add_and_verify_compliance_requirement_we() {
265289
ComplianceManager::remove_compliance_requirement(owner.origin(), ticker, 1),
266290
CMError::<TestStorage>::InvalidComplianceRequirementId
267291
);
292+
assert_eq!(get_latest_requirement_id(ticker), 3);
268293
}
269294

270295
#[test]
@@ -309,6 +334,53 @@ fn should_replace_asset_compliance_we() {
309334
assert_eq!(asset_compliance.requirements, new_asset_compliance);
310335
}
311336

337+
#[test]
338+
fn test_dedup_replace_asset_compliance() {
339+
ExtBuilder::default()
340+
.build()
341+
.execute_with(test_dedup_replace_asset_compliance_we);
342+
}
343+
344+
fn test_dedup_replace_asset_compliance_we() {
345+
let owner = User::new(AccountKeyring::Alice);
346+
347+
// Create & mint token
348+
let (ticker, _) = create_token(owner);
349+
350+
Balances::make_free_balance_be(&owner.acc(), 1_000_000);
351+
352+
allow_all_transfers(ticker, owner);
353+
354+
let asset_compliance = ComplianceManager::asset_compliance(ticker);
355+
assert_eq!(asset_compliance.requirements.len(), 1);
356+
357+
let make_req = |id: u32| ComplianceRequirement {
358+
sender_conditions: vec![],
359+
receiver_conditions: vec![],
360+
id,
361+
};
362+
363+
// Replace should throw an error if there are duplicate requirement ids.
364+
assert_noop!(
365+
ComplianceManager::replace_asset_compliance(
366+
owner.origin(),
367+
ticker,
368+
vec![make_req(1), make_req(2), make_req(2),],
369+
),
370+
CMError::<TestStorage>::DuplicateComplianceRequirements
371+
);
372+
373+
// Test with mixed duplicate ids. To test sorting.
374+
assert_noop!(
375+
ComplianceManager::replace_asset_compliance(
376+
owner.origin(),
377+
ticker,
378+
vec![make_req(2), make_req(1), make_req(2),],
379+
),
380+
CMError::<TestStorage>::DuplicateComplianceRequirements
381+
);
382+
}
383+
312384
#[test]
313385
fn should_reset_asset_compliance() {
314386
ExtBuilder::default()

0 commit comments

Comments
 (0)