Skip to content

Commit f52e5f8

Browse files
committed
feat(permission0): bulk namespace creation
1 parent f53955d commit f52e5f8

File tree

5 files changed

+190
-0
lines changed

5 files changed

+190
-0
lines changed

pallets/faucet/tests/faucet.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ impl pallet_permission0::Config for Test {
263263
type MaxNamespacesPerPermission = ConstU32<0>;
264264
type MaxChildrenPerPermission = ConstU32<0>;
265265
type MaxCuratorSubpermissionsPerPermission = ConstU32<0>;
266+
type MaxBulkOperationsPerCall = ConstU32<20>;
266267
}
267268

268269
impl pallet_balances::Config for Test {

pallets/permission0/src/lib.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ pub mod pallet {
8787
/// Maximum number of children a single permission can have.
8888
#[pallet::constant]
8989
type MaxChildrenPerPermission: Get<u32>;
90+
91+
/// Max operations a bulk extrinsic can perform per extrinsic call.
92+
#[pallet::constant]
93+
type MaxBulkOperationsPerCall: Get<u32>;
9094
}
9195

9296
pub type BalanceOf<T> =
@@ -487,6 +491,40 @@ pub mod pallet {
487491
Ok(())
488492
}
489493

494+
/// Delegate a permission over namespaces to multiple recipients.
495+
/// Note: this extrinsic creates _multiple_ permissions with the same
496+
/// properties.
497+
#[pallet::call_index(10)]
498+
#[pallet::weight({
499+
T::WeightInfo::delegate_namespace_permission()
500+
.saturating_mul(recipients.len() as u64)
501+
})]
502+
pub fn bulk_delegate_namespace_permission(
503+
origin: OriginFor<T>,
504+
recipients: BoundedBTreeSet<T::AccountId, T::MaxBulkOperationsPerCall>,
505+
paths: BoundedBTreeMap<
506+
Option<PermissionId>,
507+
BoundedBTreeSet<NamespacePathInner, T::MaxNamespacesPerPermission>,
508+
T::MaxNamespacesPerPermission,
509+
>,
510+
duration: PermissionDuration<T>,
511+
revocation: RevocationTerms<T>,
512+
instances: u32,
513+
) -> DispatchResult {
514+
for recipient in recipients {
515+
ext::namespace_impl::delegate_namespace_permission_impl::<T>(
516+
origin.clone(),
517+
recipient,
518+
paths.clone(),
519+
duration.clone(),
520+
revocation.clone(),
521+
instances,
522+
)?;
523+
}
524+
525+
Ok(())
526+
}
527+
490528
/// Allows Delegator/Recipient to edit stream permission
491529
#[pallet::call_index(8)]
492530
#[pallet::weight(T::WeightInfo::delegate_curator_permission())]

pallets/permission0/tests/namespace.rs

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1903,3 +1903,151 @@ fn update_namespace_permission_smaller_instances() {
19031903
assert_eq!(updated.max_instances, 5);
19041904
});
19051905
}
1906+
1907+
#[test]
1908+
fn bulk_delegate_namespace_permission_fails_when_redelegation_exceeds_parent_instances() {
1909+
new_test_ext().execute_with(|| {
1910+
zero_min_burn();
1911+
let alice = 0;
1912+
let bob = 1;
1913+
let charlie = 2;
1914+
let dave = 3;
1915+
1916+
register_agent(alice);
1917+
register_agent(bob);
1918+
register_agent(charlie);
1919+
register_agent(dave);
1920+
1921+
// Alice creates a namespace and delegates to Bob with 3 instances
1922+
let namespace = register_namespace(alice, b"agent.alice.compute");
1923+
let alice_paths = paths_map!(None => [namespace.clone()]);
1924+
1925+
assert_ok!(Permission0::delegate_namespace_permission(
1926+
get_origin(alice),
1927+
bob,
1928+
alice_paths,
1929+
PermissionDuration::Indefinite,
1930+
RevocationTerms::Irrevocable,
1931+
3 // Bob gets 3 instances
1932+
));
1933+
1934+
let bob_permission_id = get_last_delegated_permission_id(alice);
1935+
1936+
// Bob tries to redelegate to Charlie and Dave with 2 instances each (total 4)
1937+
// This should fail because Bob only has 3 instances available
1938+
let bob_paths = paths_map!(Some(bob_permission_id) => [namespace]);
1939+
1940+
let mut recipients = BoundedBTreeSet::new();
1941+
recipients.try_insert(charlie).unwrap();
1942+
recipients.try_insert(dave).unwrap();
1943+
1944+
// This should fail because 2 recipients × 2 instances = 4 instances needed,
1945+
// but Bob only has 3 instances available
1946+
assert_err!(
1947+
Permission0::bulk_delegate_namespace_permission(
1948+
get_origin(bob),
1949+
recipients,
1950+
bob_paths,
1951+
PermissionDuration::Indefinite,
1952+
RevocationTerms::Irrevocable,
1953+
2 // 2 instances per recipient
1954+
),
1955+
Error::<Test>::NotEnoughInstances
1956+
);
1957+
1958+
// Verify no permissions were created
1959+
assert_eq!(
1960+
PermissionsByRecipient::<Test>::get(charlie).len(),
1961+
0,
1962+
"Charlie should have no permissions"
1963+
);
1964+
assert_eq!(
1965+
PermissionsByRecipient::<Test>::get(dave).len(),
1966+
0,
1967+
"Dave should have no permissions"
1968+
);
1969+
1970+
// Verify Bob's permission still has all instances available
1971+
let bob_permission = Permissions::<Test>::get(bob_permission_id).unwrap();
1972+
assert_eq!(bob_permission.available_instances(), 3);
1973+
assert_eq!(bob_permission.children.len(), 0);
1974+
});
1975+
}
1976+
1977+
#[test]
1978+
fn bulk_delegate_namespace_permission_succeeds_within_parent_instance_limit() {
1979+
new_test_ext().execute_with(|| {
1980+
zero_min_burn();
1981+
let alice = 0;
1982+
let bob = 1;
1983+
let charlie = 2;
1984+
let dave = 3;
1985+
1986+
register_agent(alice);
1987+
register_agent(bob);
1988+
register_agent(charlie);
1989+
register_agent(dave);
1990+
1991+
// Alice creates a namespace and delegates to Bob with 4 instances
1992+
let namespace = register_namespace(alice, b"agent.alice.compute");
1993+
let alice_paths = paths_map!(None => [namespace.clone()]);
1994+
1995+
assert_ok!(Permission0::delegate_namespace_permission(
1996+
get_origin(alice),
1997+
bob,
1998+
alice_paths,
1999+
PermissionDuration::Indefinite,
2000+
RevocationTerms::Irrevocable,
2001+
4 // Bob gets 4 instances
2002+
));
2003+
2004+
let bob_permission_id = get_last_delegated_permission_id(alice);
2005+
2006+
// Bob redelegates to Charlie and Dave with 2 instances each (total 4)
2007+
// This should succeed because Bob has exactly 4 instances available
2008+
let bob_paths = paths_map!(Some(bob_permission_id) => [namespace]);
2009+
2010+
let mut recipients = BoundedBTreeSet::new();
2011+
recipients.try_insert(charlie).unwrap();
2012+
recipients.try_insert(dave).unwrap();
2013+
2014+
assert_ok!(Permission0::bulk_delegate_namespace_permission(
2015+
get_origin(bob),
2016+
recipients,
2017+
bob_paths,
2018+
PermissionDuration::Indefinite,
2019+
RevocationTerms::Irrevocable,
2020+
2 // 2 instances per recipient
2021+
));
2022+
2023+
// Verify permissions were created
2024+
assert_eq!(
2025+
PermissionsByRecipient::<Test>::get(charlie).len(),
2026+
1,
2027+
"Charlie should have 1 permission"
2028+
);
2029+
assert_eq!(
2030+
PermissionsByRecipient::<Test>::get(dave).len(),
2031+
1,
2032+
"Dave should have 1 permission"
2033+
);
2034+
2035+
// Get the created permission IDs using helper function
2036+
let charlie_permission_id = get_permission_id_for_recipient(charlie);
2037+
let dave_permission_id = get_permission_id_for_recipient(dave);
2038+
2039+
// Verify each permission has 2 instances
2040+
let charlie_permission = Permissions::<Test>::get(charlie_permission_id).unwrap();
2041+
assert_eq!(charlie_permission.max_instances, 2);
2042+
2043+
let dave_permission = Permissions::<Test>::get(dave_permission_id).unwrap();
2044+
assert_eq!(dave_permission.max_instances, 2);
2045+
2046+
// Verify Bob's permission has no instances left available
2047+
let bob_permission = Permissions::<Test>::get(bob_permission_id).unwrap();
2048+
assert_eq!(bob_permission.available_instances(), 0); // 4 - (2+2) = 0
2049+
assert_eq!(bob_permission.children.len(), 2);
2050+
assert!(bob_permission.children.contains(&charlie_permission_id));
2051+
assert!(bob_permission.children.contains(&dave_permission_id));
2052+
});
2053+
}

runtime/src/configs.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,7 @@ parameter_types! {
471471
pub const MaxNamespacesPerPermission: u32 = 16;
472472
pub const MaxChildrenPerPermission: u32 = 16;
473473
pub const MaxCuratorSubpermissionsPerPermission: u32 = 16;
474+
pub const MaxBulkOperationsPerCall: u32 = 20;
474475
}
475476

476477
impl pallet_permission0::Config for Runtime {
@@ -494,6 +495,7 @@ impl pallet_permission0::Config for Runtime {
494495
type MaxNamespacesPerPermission = MaxNamespacesPerPermission;
495496
type MaxChildrenPerPermission = MaxChildrenPerPermission;
496497
type MaxCuratorSubpermissionsPerPermission = MaxCuratorSubpermissionsPerPermission;
498+
type MaxBulkOperationsPerCall = MaxBulkOperationsPerCall;
497499
}
498500

499501
impl pallet_faucet::Config for Runtime {

test-utils/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ impl pallet_permission0::Config for Test {
270270
type MaxNamespacesPerPermission = ConstU32<10>;
271271
type MaxChildrenPerPermission = ConstU32<10>;
272272
type MaxCuratorSubpermissionsPerPermission = ConstU32<10>;
273+
type MaxBulkOperationsPerCall = ConstU32<20>;
273274
}
274275

275276
impl pallet_balances::Config for Test {

0 commit comments

Comments
 (0)