Skip to content

Commit 299abd6

Browse files
identity: Don't let subs be re-registered (#6667)
* Fixes and tests * Don't set subs be re-registered. Also allow subs to de-register themselves and collect the deposit. Also allow individual registering and removal of subs. * Make it build * Update frame/identity/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Tests * Add benchmarks * Add some reasonable weights * Docs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>
1 parent 3c13142 commit 299abd6

File tree

2 files changed

+283
-7
lines changed

2 files changed

+283
-7
lines changed

src/benchmarking.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,48 @@ benchmarks! {
172172

173173
}: _(RawOrigin::Signed(caller), subs)
174174

175+
add_sub {
176+
let caller = account::<T>("caller", 0);
177+
178+
// Give them p many previous sub accounts.
179+
let p in 1 .. T::MaxSubAccounts::get() - 1 => {
180+
let _ = add_sub_accounts::<T>(&caller, p)?;
181+
};
182+
let sub = account::<T>("new_sub", 0);
183+
let data = Data::Raw(vec![0; 32]);
184+
}: _(RawOrigin::Signed(caller), T::Lookup::unlookup(sub), data)
185+
186+
rename_sub {
187+
let caller = account::<T>("caller", 0);
188+
189+
let p in 1 .. T::MaxSubAccounts::get();
190+
191+
// Give them p many previous sub accounts.
192+
let (sub, _) = add_sub_accounts::<T>(&caller, p)?.remove(0);
193+
let data = Data::Raw(vec![1; 32]);
194+
195+
}: _(RawOrigin::Signed(caller), T::Lookup::unlookup(sub), data)
196+
197+
remove_sub {
198+
let caller = account::<T>("caller", 0);
199+
200+
// Give them p many previous sub accounts.
201+
let p in 1 .. T::MaxSubAccounts::get();
202+
let (sub, _) = add_sub_accounts::<T>(&caller, p)?.remove(0);
203+
}: _(RawOrigin::Signed(caller), T::Lookup::unlookup(sub))
204+
205+
quit_sub {
206+
let caller = account::<T>("caller", 0);
207+
let sup = account::<T>("super", 0);
208+
209+
// Give them p many previous sub accounts.
210+
let p in 1 .. T::MaxSubAccounts::get() - 1 => {
211+
let _ = add_sub_accounts::<T>(&sup, p)?;
212+
};
213+
let sup_origin = RawOrigin::Signed(sup).into();
214+
Identity::<T>::add_sub(sup_origin, T::Lookup::unlookup(caller.clone()), Data::Raw(vec![0; 32]))?;
215+
}: _(RawOrigin::Signed(caller))
216+
175217
clear_identity {
176218
let caller = account::<T>("caller", 0);
177219
let caller_origin = <T as frame_system::Trait>::Origin::from(RawOrigin::Signed(caller.clone()));

src/lib.rs

Lines changed: 241 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,17 @@
4747
//! #### For general users
4848
//! * `set_identity` - Set the associated identity of an account; a small deposit is reserved if not
4949
//! already taken.
50-
//! * `set_subs` - Set the sub-accounts of an identity.
5150
//! * `clear_identity` - Remove an account's associated identity; the deposit is returned.
5251
//! * `request_judgement` - Request a judgement from a registrar, paying a fee.
5352
//! * `cancel_request` - Cancel the previous request for a judgement.
5453
//!
54+
//! #### For general users with sub-identities
55+
//! * `set_subs` - Set the sub-accounts of an identity.
56+
//! * `add_sub` - Add a sub-identity to an identity.
57+
//! * `remove_sub` - Remove a sub-identity of an identity.
58+
//! * `rename_sub` - Rename a sub-identity of an identity.
59+
//! * `quit_sub` - Remove a sub-identity of an identity (called by the sub-identity).
60+
//!
5561
//! #### For registrars
5662
//! * `set_fee` - Set the fee required to be paid for a judgement to be given by the registrar.
5763
//! * `set_fields` - Set the fields that a registrar cares about in their judgements.
@@ -70,8 +76,8 @@ use sp_std::prelude::*;
7076
use sp_std::{fmt::Debug, ops::Add, iter::once};
7177
use enumflags2::BitFlags;
7278
use codec::{Encode, Decode};
73-
use sp_runtime::{DispatchError, RuntimeDebug};
74-
use sp_runtime::traits::{StaticLookup, Zero, AppendZerosInput};
79+
use sp_runtime::{DispatchError, RuntimeDebug, DispatchResult};
80+
use sp_runtime::traits::{StaticLookup, Zero, AppendZerosInput, Saturating};
7581
use frame_support::{
7682
decl_module, decl_event, decl_storage, ensure, decl_error,
7783
dispatch::DispatchResultWithPostInfo,
@@ -97,6 +103,10 @@ pub trait WeightInfo {
97103
fn set_fields(r: u32, ) -> Weight;
98104
fn provide_judgement(r: u32, x: u32, ) -> Weight;
99105
fn kill_identity(r: u32, s: u32, x: u32, ) -> Weight;
106+
fn add_sub(p: u32, ) -> Weight;
107+
fn rename_sub() -> Weight;
108+
fn remove_sub(p: u32, ) -> Weight;
109+
fn quit_sub(p: u32, ) -> Weight;
100110
}
101111

102112
impl WeightInfo for () {
@@ -111,6 +121,10 @@ impl WeightInfo for () {
111121
fn set_fields(_r: u32, ) -> Weight { 1_000_000_000 }
112122
fn provide_judgement(_r: u32, _x: u32, ) -> Weight { 1_000_000_000 }
113123
fn kill_identity(_r: u32, _s: u32, _x: u32, ) -> Weight { 1_000_000_000 }
124+
fn add_sub(_p: u32, ) -> Weight { 1_000_000_000 }
125+
fn rename_sub() -> Weight { 1_000_000_000 }
126+
fn remove_sub(_p: u32, ) -> Weight { 1_000_000_000 }
127+
fn quit_sub(_p: u32, ) -> Weight { 1_000_000_000 }
114128
}
115129

116130
pub trait Trait: frame_system::Trait {
@@ -462,6 +476,13 @@ decl_event!(
462476
JudgementGiven(AccountId, RegistrarIndex),
463477
/// A registrar was added.
464478
RegistrarAdded(RegistrarIndex),
479+
/// A sub-identity (first) was added to an identity (second) and the deposit paid.
480+
SubIdentityAdded(AccountId, AccountId, Balance),
481+
/// A sub-identity (first) was removed from an identity (second) and the deposit freed.
482+
SubIdentityRemoved(AccountId, AccountId, Balance),
483+
/// A sub-identity (first arg) was cleared, and the given deposit repatriated from the
484+
/// main identity account (second arg) to the sub-identity account.
485+
SubIdentityRevoked(AccountId, AccountId, Balance),
465486
}
466487
);
467488

@@ -494,7 +515,13 @@ decl_error! {
494515
TooManyFields,
495516
/// Maximum amount of registrars reached. Cannot add any more.
496517
TooManyRegistrars,
497-
}
518+
/// Account ID is already named.
519+
AlreadyClaimed,
520+
/// Sender is not a sub-account.
521+
NotSub,
522+
/// Sub-account isn't owned by sender.
523+
NotOwned
524+
}
498525
}
499526

500527
/// Functions for calcuating the weight of dispatchables.
@@ -620,6 +647,36 @@ mod weight_for {
620647
+ 2_600_000 * subs // S
621648
+ 900_000 * extra_fields // X
622649
}
650+
651+
/// Weight calculation for `add_sub`.
652+
pub(crate) fn add_sub<T: Trait>(
653+
subs: Weight,
654+
) -> Weight {
655+
let db = T::DbWeight::get();
656+
db.reads_writes(4, 3) + 124_000_000 + 156_000 * subs
657+
}
658+
659+
/// Weight calculation for `rename_sub`.
660+
pub(crate) fn rename_sub<T: Trait>() -> Weight {
661+
let db = T::DbWeight::get();
662+
db.reads_writes(2, 1) + 30_000_000
663+
}
664+
665+
/// Weight calculation for `remove_sub`.
666+
pub(crate) fn remove_sub<T: Trait>(
667+
subs: Weight,
668+
) -> Weight {
669+
let db = T::DbWeight::get();
670+
db.reads_writes(4, 3) + 86_000_000 + 50_000 * subs
671+
}
672+
673+
/// Weight calculation for `quit_sub`.
674+
pub(crate) fn quit_sub<T: Trait>(
675+
subs: Weight,
676+
) -> Weight {
677+
let db = T::DbWeight::get();
678+
db.reads_writes(3, 2) + 63_000_000 + 230_000 * subs
679+
}
623680
}
624681

625682
decl_module! {
@@ -774,6 +831,9 @@ decl_module! {
774831
let (old_deposit, old_ids) = <SubsOf<T>>::get(&sender);
775832
let new_deposit = T::SubAccountDeposit::get() * <BalanceOf<T>>::from(subs.len() as u32);
776833

834+
let not_other_sub = subs.iter().filter_map(|i| SuperOf::<T>::get(&i.0)).all(|i| &i.0 == &sender);
835+
ensure!(not_other_sub, Error::<T>::AlreadyClaimed);
836+
777837
if old_deposit < new_deposit {
778838
T::Currency::reserve(&sender, new_deposit - old_deposit)?;
779839
}
@@ -831,8 +891,7 @@ decl_module! {
831891

832892
let (subs_deposit, sub_ids) = <SubsOf<T>>::take(&sender);
833893
let id = <IdentityOf<T>>::take(&sender).ok_or(Error::<T>::NotNamed)?;
834-
let deposit = id.total_deposit()
835-
+ subs_deposit;
894+
let deposit = id.total_deposit() + subs_deposit;
836895
for sub in sub_ids.iter() {
837896
<SuperOf<T>>::remove(sub);
838897
}
@@ -1159,6 +1218,103 @@ decl_module! {
11591218
id.info.additional.len() as Weight // X
11601219
)).into())
11611220
}
1221+
1222+
/// Add the given account to the sender's subs.
1223+
///
1224+
/// Payment: Balance reserved by a previous `set_subs` call for one sub will be repatriated
1225+
/// to the sender.
1226+
///
1227+
/// The dispatch origin for this call must be _Signed_ and the sender must have a registered
1228+
/// sub identity of `sub`.
1229+
#[weight = weight_for::add_sub::<T>(
1230+
T::MaxSubAccounts::get().into(), // S
1231+
)]
1232+
fn add_sub(origin, sub: <T::Lookup as StaticLookup>::Source, data: Data) -> DispatchResult {
1233+
let sender = ensure_signed(origin)?;
1234+
let sub = T::Lookup::lookup(sub)?;
1235+
ensure!(IdentityOf::<T>::contains_key(&sender), Error::<T>::NoIdentity);
1236+
1237+
// Check if it's already claimed as sub-identity.
1238+
ensure!(!SuperOf::<T>::contains_key(&sub), Error::<T>::AlreadyClaimed);
1239+
1240+
SubsOf::<T>::try_mutate(&sender, |(ref mut subs_deposit, ref mut sub_ids)| {
1241+
// Ensure there is space and that the deposit is paid.
1242+
ensure!(sub_ids.len() < T::MaxSubAccounts::get() as usize, Error::<T>::TooManySubAccounts);
1243+
let deposit = T::SubAccountDeposit::get();
1244+
T::Currency::reserve(&sender, deposit)?;
1245+
1246+
SuperOf::<T>::insert(&sub, (sender.clone(), data));
1247+
sub_ids.push(sub.clone());
1248+
*subs_deposit = subs_deposit.saturating_add(deposit);
1249+
1250+
Self::deposit_event(RawEvent::SubIdentityAdded(sub, sender.clone(), deposit));
1251+
Ok(())
1252+
})
1253+
}
1254+
1255+
/// Alter the associated name of the given sub-account.
1256+
///
1257+
/// The dispatch origin for this call must be _Signed_ and the sender must have a registered
1258+
/// sub identity of `sub`.
1259+
#[weight = weight_for::rename_sub::<T>()]
1260+
fn rename_sub(origin, sub: <T::Lookup as StaticLookup>::Source, data: Data) {
1261+
let sender = ensure_signed(origin)?;
1262+
let sub = T::Lookup::lookup(sub)?;
1263+
ensure!(IdentityOf::<T>::contains_key(&sender), Error::<T>::NoIdentity);
1264+
ensure!(SuperOf::<T>::get(&sub).map_or(false, |x| x.0 == sender), Error::<T>::NotOwned);
1265+
SuperOf::<T>::insert(&sub, (sender, data));
1266+
}
1267+
1268+
/// Remove the given account from the sender's subs.
1269+
///
1270+
/// Payment: Balance reserved by a previous `set_subs` call for one sub will be repatriated
1271+
/// to the sender.
1272+
///
1273+
/// The dispatch origin for this call must be _Signed_ and the sender must have a registered
1274+
/// sub identity of `sub`.
1275+
#[weight = weight_for::remove_sub::<T>(
1276+
T::MaxSubAccounts::get().into(), // S
1277+
)]
1278+
fn remove_sub(origin, sub: <T::Lookup as StaticLookup>::Source) {
1279+
let sender = ensure_signed(origin)?;
1280+
ensure!(IdentityOf::<T>::contains_key(&sender), Error::<T>::NoIdentity);
1281+
let sub = T::Lookup::lookup(sub)?;
1282+
let (sup, _) = SuperOf::<T>::get(&sub).ok_or(Error::<T>::NotSub)?;
1283+
ensure!(sup == sender, Error::<T>::NotOwned);
1284+
SuperOf::<T>::remove(&sub);
1285+
SubsOf::<T>::mutate(&sup, |(ref mut subs_deposit, ref mut sub_ids)| {
1286+
sub_ids.retain(|x| x != &sub);
1287+
let deposit = T::SubAccountDeposit::get().min(*subs_deposit);
1288+
*subs_deposit -= deposit;
1289+
let _ = T::Currency::unreserve(&sender, deposit);
1290+
Self::deposit_event(RawEvent::SubIdentityRemoved(sub, sender, deposit));
1291+
});
1292+
}
1293+
1294+
/// Remove the sender as a sub-account.
1295+
///
1296+
/// Payment: Balance reserved by a previous `set_subs` call for one sub will be repatriated
1297+
/// to the sender (*not* the original depositor).
1298+
///
1299+
/// The dispatch origin for this call must be _Signed_ and the sender must have a registered
1300+
/// super-identity.
1301+
///
1302+
/// NOTE: This should not normally be used, but is provided in the case that the non-
1303+
/// controller of an account is maliciously registered as a sub-account.
1304+
#[weight = weight_for::quit_sub::<T>(
1305+
T::MaxSubAccounts::get().into(), // S
1306+
)]
1307+
fn quit_sub(origin) {
1308+
let sender = ensure_signed(origin)?;
1309+
let (sup, _) = SuperOf::<T>::take(&sender).ok_or(Error::<T>::NotSub)?;
1310+
SubsOf::<T>::mutate(&sup, |(ref mut subs_deposit, ref mut sub_ids)| {
1311+
sub_ids.retain(|x| x != &sender);
1312+
let deposit = T::SubAccountDeposit::get().min(*subs_deposit);
1313+
*subs_deposit -= deposit;
1314+
let _ = T::Currency::repatriate_reserved(&sup, &sender, deposit, BalanceStatus::Free);
1315+
Self::deposit_event(RawEvent::SubIdentityRevoked(sender, sup.clone(), deposit));
1316+
});
1317+
}
11621318
}
11631319
}
11641320

@@ -1188,7 +1344,7 @@ mod tests {
11881344
};
11891345

11901346
impl_outer_origin! {
1191-
pub enum Origin for Test where system = frame_system {}
1347+
pub enum Origin for Test where system = frame_system {}
11921348
}
11931349

11941350
#[derive(Clone, Eq, PartialEq)]
@@ -1300,6 +1456,84 @@ mod tests {
13001456
}
13011457
}
13021458

1459+
fn twenty() -> IdentityInfo {
1460+
IdentityInfo {
1461+
display: Data::Raw(b"twenty".to_vec()),
1462+
legal: Data::Raw(b"The Right Ordinal Twenty, Esq.".to_vec()),
1463+
.. Default::default()
1464+
}
1465+
}
1466+
1467+
#[test]
1468+
fn editing_subaccounts_should_work() {
1469+
new_test_ext().execute_with(|| {
1470+
let data = |x| Data::Raw(vec![x; 1]);
1471+
1472+
assert_noop!(Identity::add_sub(Origin::signed(10), 20, data(1)), Error::<Test>::NoIdentity);
1473+
1474+
assert_ok!(Identity::set_identity(Origin::signed(10), ten()));
1475+
1476+
// first sub account
1477+
assert_ok!(Identity::add_sub(Origin::signed(10), 1, data(1)));
1478+
assert_eq!(SuperOf::<Test>::get(1), Some((10, data(1))));
1479+
assert_eq!(Balances::free_balance(10), 80);
1480+
1481+
// second sub account
1482+
assert_ok!(Identity::add_sub(Origin::signed(10), 2, data(2)));
1483+
assert_eq!(SuperOf::<Test>::get(1), Some((10, data(1))));
1484+
assert_eq!(SuperOf::<Test>::get(2), Some((10, data(2))));
1485+
assert_eq!(Balances::free_balance(10), 70);
1486+
1487+
// third sub account is too many
1488+
assert_noop!(Identity::add_sub(Origin::signed(10), 3, data(3)), Error::<Test>::TooManySubAccounts);
1489+
1490+
// rename first sub account
1491+
assert_ok!(Identity::rename_sub(Origin::signed(10), 1, data(11)));
1492+
assert_eq!(SuperOf::<Test>::get(1), Some((10, data(11))));
1493+
assert_eq!(SuperOf::<Test>::get(2), Some((10, data(2))));
1494+
assert_eq!(Balances::free_balance(10), 70);
1495+
1496+
// remove first sub account
1497+
assert_ok!(Identity::remove_sub(Origin::signed(10), 1));
1498+
assert_eq!(SuperOf::<Test>::get(1), None);
1499+
assert_eq!(SuperOf::<Test>::get(2), Some((10, data(2))));
1500+
assert_eq!(Balances::free_balance(10), 80);
1501+
1502+
// add third sub account
1503+
assert_ok!(Identity::add_sub(Origin::signed(10), 3, data(3)));
1504+
assert_eq!(SuperOf::<Test>::get(1), None);
1505+
assert_eq!(SuperOf::<Test>::get(2), Some((10, data(2))));
1506+
assert_eq!(SuperOf::<Test>::get(3), Some((10, data(3))));
1507+
assert_eq!(Balances::free_balance(10), 70);
1508+
});
1509+
}
1510+
1511+
#[test]
1512+
fn resolving_subaccount_ownership_works() {
1513+
new_test_ext().execute_with(|| {
1514+
let data = |x| Data::Raw(vec![x; 1]);
1515+
1516+
assert_ok!(Identity::set_identity(Origin::signed(10), ten()));
1517+
assert_ok!(Identity::set_identity(Origin::signed(20), twenty()));
1518+
1519+
// 10 claims 1 as a subaccount
1520+
assert_ok!(Identity::add_sub(Origin::signed(10), 1, data(1)));
1521+
assert_eq!(Balances::free_balance(1), 10);
1522+
assert_eq!(Balances::free_balance(10), 80);
1523+
assert_eq!(Balances::reserved_balance(10), 20);
1524+
// 20 cannot claim 1 now
1525+
assert_noop!(Identity::add_sub(Origin::signed(20), 1, data(1)), Error::<Test>::AlreadyClaimed);
1526+
// 1 wants to be with 20 so it quits from 10
1527+
assert_ok!(Identity::quit_sub(Origin::signed(1)));
1528+
// 1 gets the 10 that 10 paid.
1529+
assert_eq!(Balances::free_balance(1), 20);
1530+
assert_eq!(Balances::free_balance(10), 80);
1531+
assert_eq!(Balances::reserved_balance(10), 10);
1532+
// 20 can claim 1 now
1533+
assert_ok!(Identity::add_sub(Origin::signed(20), 1, data(1)));
1534+
});
1535+
}
1536+
13031537
#[test]
13041538
fn trailing_zeros_decodes_into_default_data() {
13051539
let encoded = Data::Raw(b"Hello".to_vec()).encode();

0 commit comments

Comments
 (0)