-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Keyless proxied account (a.k.a anonymous proxy v2) #11115
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -159,6 +159,10 @@ pub mod pallet { | |
#[pallet::constant] | ||
type MaxPending: Get<u32>; | ||
|
||
/// The maximum amount of characters a keyless vanity address can have. | ||
#[pallet::constant] | ||
type MaxVanity: Get<u8>; | ||
|
||
/// The type of hash used for hashing the call. | ||
type CallHasher: Hash; | ||
|
||
|
@@ -267,7 +271,7 @@ pub mod pallet { | |
/// | ||
/// The dispatch origin for this call must be _Signed_. | ||
/// | ||
/// WARNING: This may be called on accounts created by `anonymous`, however if done, then | ||
/// WARNING: This may be called on accounts created by `proxied_keyless`, however if done, then | ||
/// the unreserved fees will be inaccessible. **All access to this account will be lost.** | ||
/// | ||
/// # <weight> | ||
|
@@ -295,6 +299,8 @@ pub mod pallet { | |
/// want to use `0`. | ||
/// - `delay`: The announcement period required of the initial proxy. Will generally be | ||
/// zero. | ||
/// - `maybe_keyless`: An account to be used as the keyless proxied account instead of the | ||
/// generated one, it will be evaluated to determine if it's indeed a keyless account. | ||
/// | ||
/// Fails with `Duplicate` if this has already been called in this transaction, from the | ||
/// same sender, with the same parameters. | ||
|
@@ -305,17 +311,23 @@ pub mod pallet { | |
/// Weight is a function of the number of proxies the user has (P). | ||
/// # </weight> | ||
/// TODO: Might be over counting 1 read | ||
#[pallet::weight(T::WeightInfo::anonymous(T::MaxProxies::get().into()))] | ||
pub fn anonymous( | ||
#[pallet::weight(T::WeightInfo::proxied_keyless(T::MaxProxies::get().into()))] | ||
pub fn proxied_keyless( | ||
origin: OriginFor<T>, | ||
proxy_type: T::ProxyType, | ||
delay: T::BlockNumber, | ||
index: u16, | ||
maybe_keyless: Option<T::AccountId>, | ||
) -> DispatchResult { | ||
let who = ensure_signed(origin)?; | ||
|
||
let anonymous = Self::anonymous_account(&who, &proxy_type, index, None); | ||
ensure!(!Proxies::<T>::contains_key(&anonymous), Error::<T>::Duplicate); | ||
let keyless = if let Some(account) = maybe_keyless { | ||
ensure!(Self::is_vain_enough(&account), Error::<T>::NotKeyless); | ||
account | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what? you allow people to specify the account? that for sure is a security hole. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But is it though? I don't think it's sure, that's my main question here as this is based on an empirical assumption. There's gonna be a line to not cross when evaluating the "keylessness" of an account that might even get blurry at some point, but we can always be super conservative about it to stay on the safe side. The initial approach here is leaving part of the judgment to the implementer that should specify a safe There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No it is different thing. You allow people to pick any addresses that fits a requirement without many more complicated checking and that will allow reuse address attack etc. Allow anyone to pick their own address is almost always a bad idea. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What would a more complicated checking look like? perhaps make the process more expensive/permissioned? an auction? should governance be like a registar that sells/leases the right to have those accounts? Not to sound stubborn but I think it's a feature too cool to not make the effort to make it work somehow. wouldn't |
||
} else { | ||
Self::keyless_account(&who, &proxy_type, index, None) | ||
}; | ||
ensure!(!Proxies::<T>::contains_key(&keyless), Error::<T>::Duplicate); | ||
|
||
let proxy_def = | ||
ProxyDefinition { delegate: who.clone(), proxy_type: proxy_type.clone(), delay }; | ||
|
@@ -325,9 +337,9 @@ pub mod pallet { | |
let deposit = T::ProxyDepositBase::get() + T::ProxyDepositFactor::get(); | ||
T::Currency::reserve(&who, deposit)?; | ||
|
||
Proxies::<T>::insert(&anonymous, (bounded_proxies, deposit)); | ||
Self::deposit_event(Event::AnonymousCreated { | ||
anonymous, | ||
Proxies::<T>::insert(&keyless, (bounded_proxies, deposit)); | ||
Self::deposit_event(Event::KeylessCreated { | ||
keyless, | ||
who, | ||
proxy_type, | ||
disambiguation_index: index, | ||
|
@@ -336,28 +348,28 @@ pub mod pallet { | |
Ok(()) | ||
} | ||
|
||
/// Removes a previously spawned anonymous proxy. | ||
/// Removes a previously spawned keyless account. | ||
/// | ||
/// WARNING: **All access to this account will be lost.** Any funds held in it will be | ||
/// inaccessible. | ||
/// | ||
/// Requires a `Signed` origin, and the sender account must have been created by a call to | ||
/// `anonymous` with corresponding parameters. | ||
/// `proxied_keyless` with corresponding parameters. | ||
/// | ||
/// - `spawner`: The account that originally called `anonymous` to create this account. | ||
/// - `index`: The disambiguation index originally passed to `anonymous`. Probably `0`. | ||
/// - `proxy_type`: The proxy type originally passed to `anonymous`. | ||
/// - `height`: The height of the chain when the call to `anonymous` was processed. | ||
/// - `ext_index`: The extrinsic index in which the call to `anonymous` was processed. | ||
/// - `spawner`: The account that originally called `proxied_keyless` to create this account. | ||
/// - `index`: The disambiguation index originally passed to `proxied_keyless`. Probably `0`. | ||
/// - `proxy_type`: The proxy type originally passed to `proxied_keyless`. | ||
/// - `height`: The height of the chain when the call to `proxied_keyless` was processed. | ||
/// - `ext_index`: The extrinsic index in which the call to `proxied_keyless` was processed. | ||
/// | ||
/// Fails with `NoPermission` in case the caller is not a previously created anonymous | ||
/// account whose `anonymous` call has corresponding parameters. | ||
/// Fails with `NoPermission` in case the caller is not a previously created keyless | ||
/// account whose `proxied_keyless` call has corresponding parameters. | ||
/// | ||
/// # <weight> | ||
/// Weight is a function of the number of proxies the user has (P). | ||
/// # </weight> | ||
#[pallet::weight(T::WeightInfo::kill_anonymous(T::MaxProxies::get().into()))] | ||
pub fn kill_anonymous( | ||
#[pallet::weight(T::WeightInfo::kill_keyless(T::MaxProxies::get().into()))] | ||
pub fn kill_keyless( | ||
origin: OriginFor<T>, | ||
spawner: T::AccountId, | ||
proxy_type: T::ProxyType, | ||
|
@@ -368,7 +380,7 @@ pub mod pallet { | |
let who = ensure_signed(origin)?; | ||
|
||
let when = (height, ext_index); | ||
let proxy = Self::anonymous_account(&spawner, &proxy_type, index, Some(when)); | ||
let proxy = Self::keyless_account(&spawner, &proxy_type, index, Some(when)); | ||
ensure!(proxy == who, Error::<T>::NoPermission); | ||
|
||
let (_, deposit) = Proxies::<T>::take(&who); | ||
|
@@ -536,9 +548,9 @@ pub mod pallet { | |
let call_hash = T::CallHasher::hash_of(&call); | ||
let now = system::Pallet::<T>::block_number(); | ||
Self::edit_announcements(&delegate, |ann| { | ||
ann.real != real || | ||
ann.call_hash != call_hash || | ||
now.saturating_sub(ann.height) < def.delay | ||
ann.real != real | ||
|| ann.call_hash != call_hash | ||
|| now.saturating_sub(ann.height) < def.delay | ||
}) | ||
.map_err(|_| Error::<T>::Unannounced)?; | ||
|
||
|
@@ -555,8 +567,8 @@ pub mod pallet { | |
ProxyExecuted { result: DispatchResult }, | ||
/// Anonymous account has been created by new proxy with given | ||
/// disambiguation index and proxy type. | ||
AnonymousCreated { | ||
anonymous: T::AccountId, | ||
KeylessCreated { | ||
keyless: T::AccountId, | ||
who: T::AccountId, | ||
proxy_type: T::ProxyType, | ||
disambiguation_index: u16, | ||
|
@@ -601,6 +613,8 @@ pub mod pallet { | |
Unannounced, | ||
/// Cannot add self as proxy. | ||
NoSelfProxy, | ||
/// The provided keyless account is not vain enough. | ||
NotKeyless, | ||
} | ||
|
||
/// The set of account proxies. Maps the account which has delegated to the accounts | ||
|
@@ -634,7 +648,7 @@ pub mod pallet { | |
} | ||
|
||
impl<T: Config> Pallet<T> { | ||
/// Calculate the address of an anonymous account. | ||
/// Calculate the address of a keyless account. | ||
/// | ||
/// - `who`: The spawner account. | ||
/// - `proxy_type`: The type of the proxy that the sender will be registered as over the | ||
|
@@ -643,9 +657,9 @@ impl<T: Config> Pallet<T> { | |
/// - `index`: A disambiguation index, in case this is called multiple times in the same | ||
/// transaction (e.g. with `utility::batch`). Unless you're using `batch` you probably just | ||
/// want to use `0`. | ||
/// - `maybe_when`: The block height and extrinsic index of when the anonymous account was | ||
/// - `maybe_when`: The block height and extrinsic index of when the keyless account was | ||
/// created. None to use current block height and extrinsic index. | ||
pub fn anonymous_account( | ||
pub fn keyless_account( | ||
who: &T::AccountId, | ||
proxy_type: &T::ProxyType, | ||
index: u16, | ||
|
@@ -663,6 +677,16 @@ impl<T: Config> Pallet<T> { | |
.expect("infinite length input; no invalid inputs for type; qed") | ||
} | ||
|
||
fn is_vain_enough(account: &T::AccountId) -> bool { | ||
let vanity_len = T::MaxVanity::get() as usize; | ||
account | ||
.as_ref() | ||
.get(vanity_len..) | ||
.expect("MaxVanity shorter than account") | ||
.iter() | ||
.all(|b| b == &0) | ||
} | ||
|
||
/// Register a proxy account for the delegator that is able to make calls on its behalf. | ||
/// | ||
/// Parameters: | ||
|
@@ -799,8 +823,8 @@ impl<T: Config> Pallet<T> { | |
force_proxy_type: Option<T::ProxyType>, | ||
) -> Result<ProxyDefinition<T::AccountId, T::ProxyType, T::BlockNumber>, DispatchError> { | ||
let f = |x: &ProxyDefinition<T::AccountId, T::ProxyType, T::BlockNumber>| -> bool { | ||
&x.delegate == delegate && | ||
force_proxy_type.as_ref().map_or(true, |y| &x.proxy_type == y) | ||
&x.delegate == delegate | ||
&& force_proxy_type.as_ref().map_or(true, |y| &x.proxy_type == y) | ||
}; | ||
Ok(Proxies::<T>::get(real).0.into_iter().find(f).ok_or(Error::<T>::NotProxy)?) | ||
} | ||
|
@@ -818,19 +842,24 @@ impl<T: Config> Pallet<T> { | |
match c.is_sub_type() { | ||
// Proxy call cannot add or remove a proxy with more permissions than it already | ||
// has. | ||
Some(Call::add_proxy { ref proxy_type, .. }) | | ||
Some(Call::remove_proxy { ref proxy_type, .. }) | ||
Some(Call::add_proxy { ref proxy_type, .. }) | ||
| Some(Call::remove_proxy { ref proxy_type, .. }) | ||
if !def.proxy_type.is_superset(&proxy_type) => | ||
false, | ||
// Proxy call cannot remove all proxies or kill anonymous proxies unless it has full | ||
{ | ||
false | ||
}, | ||
// Proxy call cannot remove all proxies or kill keyless accounts unless it has full | ||
// permissions. | ||
Some(Call::remove_proxies { .. }) | Some(Call::kill_anonymous { .. }) | ||
Some(Call::remove_proxies { .. }) | Some(Call::kill_keyless { .. }) | ||
if def.proxy_type != T::ProxyType::default() => | ||
false, | ||
{ | ||
false | ||
}, | ||
_ => def.proxy_type.filter(c), | ||
} | ||
}); | ||
let e = call.dispatch(origin); | ||
Self::deposit_event(Event::ProxyExecuted { result: e.map(|_| ()).map_err(|e| e.error) }); | ||
} | ||
} | ||
use_vanitywith_vanity: use_vanitywith_vanity: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please avoid breaking changes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, this was more of a quick search and replace, I'll keep the previous "deprecated" method around. Any other things to consider when introducing this kind of API changes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should rename the functions though... Are you suggesting we should add a new function with a new name?
AFAIK this "breaking change" would only be breaking for JS integrations, but all txs and signers will be okay
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rename it is breaking for JS and ledger. let's avoid that unless absolutely necessary.
and new parameter is a transaction_version breaking change.