-
Notifications
You must be signed in to change notification settings - Fork 38
The account abstraction pallet #130
base: polkadot-v0.9.39
Are you sure you want to change the base?
Conversation
|
* added unit tests * added meta transactions
let who = ensure_signed(origin)?; | ||
|
||
let magic = T::ChainMagic::get(); | ||
let nonce = frame_system::Pallet::<T>::account_nonce(signer.clone()); |
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.
Signer usually doesn't have enough balance, and sometimes doesn't have it at all.
How do we hold nonce of accounts without Existential Deposit (ED)?
And because of ED, account info can be cleaned up, which means account nonce can be reset to 0, so I think it cannot ensure replay attack when that account is recreated later.
Being discussed here https://github.com/paritytech/substrate/issues/11988
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.
Agree with Shunsuke, meta_call
should be left for some other PR in the future, potentially.
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.
Temporary removed
pub enum NativeAndEVMKind { | ||
Native, | ||
H160, | ||
} |
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.
Perhaps adding impl Into
for enum NativeAndEVM
that converts into NativeAndEVMKind
is a good idea - you can test the conversion & ensure it's exhaustive.
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.
Probably yes, but may be we can generate *Kind structure from custom origin somehow. I'll investigate later.
#[pallet::pallet] | ||
#[pallet::generate_store(pub(super) trait Store)] | ||
#[pallet::storage_version(STORAGE_VERSION)] | ||
#[pallet::without_storage_info] |
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.
This should be removed and pallet should correctly be bounded in relations to PoV size.
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.
Rewritten to make it bounded.
/// Account origins | ||
#[pallet::storage] | ||
pub type AccountOrigin<T: Config> = | ||
StorageMap<_, Blake2_128Concat, T::AccountId, Vec<T::CustomOrigin>, ValueQuery>; |
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.
Vec<_>
should not be used in storage. Instead, you should use BoundedVec
. That way you can also get rid of the without_storage_info
macro you used above. This item cannot be unlimited.
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.
Used double-map instead of vector.
origin: T::CustomOrigin, | ||
}, | ||
ProxyCall { | ||
payer: T::AccountId, |
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.
Why is payer
needed here?
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.
Removed
let who = ensure_signed(origin)?; | ||
|
||
let magic = T::ChainMagic::get(); | ||
let nonce = frame_system::Pallet::<T>::account_nonce(signer.clone()); |
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.
Agree with Shunsuke, meta_call
should be left for some other PR in the future, potentially.
Self::deposit_event(Event::NewOrigin { | ||
account: who, | ||
origin: new_origin, | ||
}); |
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.
Just wondering - why do we even need to store the derived address?
If user would specify Origin kind & index, we could always derive the same address. Is there some security risk that comes with such approach?
Not sure whether it's more computationally expensive to derive the address or read the DB entry.
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.
In case of store derived address we can charge origin creation fee, it looks more secure because it prevents address derivation manipulation using GPU, etc.
frame/pallet-account/Cargo.toml
Outdated
authors = ["Stake Technologies"] | ||
edition = "2021" |
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 use workspace inheritance for relevant package info values.
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.
Added
// You should have received a copy of the GNU General Public License | ||
// along with Astar. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
use frame_support::weights::Weight; |
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 don't see any TODO for this, but you should also add benchmarking code.
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.
@akru this comment is unresolved.
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.
Added TODO, I'll add weights in next PR.
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.
Added benchmarks, next step generate weights
Co-authored-by: Dmitry <[email protected]>
Co-authored-by: PierreOssun <[email protected]>
Co-authored-by: Dmitry <[email protected]>
Co-authored-by: Dmitry <[email protected]>
Co-authored-by: Dmitry <[email protected]>
@Dinonard please check changes, I'll happy to merge it soon |
if origin == NativeAndEVM::Native(ALICE_D1_NATIVE.into()) | ||
); | ||
}) | ||
} |
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.
You should also add some negative tests to cover the possible errors.
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.
Added
// You should have received a copy of the GNU General Public License | ||
// along with Astar. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
use frame_support::weights::Weight; |
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.
@akru this comment is unresolved.
let new_origin = origin_kind.derive(&who, next_index as u32); | ||
|
||
origins.push(new_origin.clone()); | ||
AccountOrigin::<T>::insert(who.clone(), origins); |
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.
@akru This comment is still relevant.
(T::WeightInfo::proxy_call() | ||
.saturating_add(T::DbWeight::get().reads_writes(1, 1)) |
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.
@akru This comment is still relevant.
.saturating_add(T::DbWeight::get().reads_writes(1, 2)) | ||
)] | ||
#[pallet::call_index(0)] | ||
pub fn new_origin( |
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.
As the origin derivation algorithm is deterministic, could we skip the origin registering? The derived origin could be derived just in time in a proxy_call
, like:
Account::proxy_call(alice, derive_kind, index, call);
If we do need to cache the derived origin to reduce gas costs, the cache should be cleaned on kill account.
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 believe derivation algorithm is just a sample, it not restricted to be deterministic. Account registration helps to prevent spamming and account address manipulation.
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.
@akru what is the usecase behind allowing single account to have multiple derived address of the same type?
The way I see it, it just adds unnecessary complexity.
If we only allow one account type to be derived from the origin, then it's much simpler.
If an account wants to control multiple different derived accounts, it can use proxy to achieve that.
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.
Shaun also commented above(here) about what happens when account is killed. Without any locked or reserved funds, these storage items will remain in the DB even without an owner.
fn derive(&self, source: &AccountId32, index: u32) -> NativeAndEVM { | ||
let salted_source = [source.as_ref(), &index.encode()[..]].concat(); | ||
let derived = sp_io::hashing::blake2_256(&salted_source); | ||
match self { | ||
NativeAndEVMKind::Native => NativeAndEVM::Native(derived.into()), | ||
NativeAndEVMKind::H160 => NativeAndEVM::H160(sp_core::H160::from_slice(&derived[..20])), | ||
} | ||
} |
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.
Dumb question: is our custom origin deriving meant to derive between H160 and Native? The impl here seems only to derive sub-accounts from their own type.
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.
Yes, to create new origin the call origin must be substrate-compatible.
|
||
impl OriginDeriving<AccountId32, NativeAndEVM> for NativeAndEVMKind { | ||
fn derive(&self, source: &AccountId32, index: u32) -> NativeAndEVM { | ||
let salted_source = [source.as_ref(), &index.encode()[..]].concat(); |
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 can add module id as entropy, probably same to module id in pallet-utilities, to be compatible. https://github.com/paritytech/substrate/blob/f4a2e84ee5974b219f2a03cd195105060c41e3cd/frame/utility/src/lib.rs#LL506C28-L506C28
EDIT: probably not a good idea to be compatible with pallet-utilities. Compatibility with main stream derivation standard is more important
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.
Honestly, at first stage I prefer to give some sample of usage and no more. What’s about derivation standard, it could be easily implemented outside of the pallet.
Co-authored-by: Shaun Wang <[email protected]>
Minimum allowed line rate is |
AccountOrigin::<T>::insert(&who, next_index, new_origin.clone()); | ||
AccountLastOrigin::<T>::insert(&who, next_index + 1); |
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.
This is still free - so my comment from 2 months ago is still valid.
You should charge rent fee for these two items since you're inflating the database.
Check how pallet-assets
is doing it, or any other pallet in substrate
that creates some storage items (e.g. pallet-identity
, pallet-multisig
, etc.).
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.
Technically, you pay transaction fee to cover it (prevent spam), it’s not required at all, just one of ways to do it. I’m not sure it’s required for the prototype.
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.
My personal opinion - better charge fee than just lock token forever. Charged fee much more controllable, it could be burn, redistributed or whatever you want. But locked deposit is just locked.
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.
The way it's implemented now - sure, that's viable option. It still needs to be added though since it's missing now.
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.
Not sure something is missing now, what precisely you are talking about?
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'm talking about payment for the created storage.
Technically, you pay transaction fee to cover it (prevent spam), it’s not required at all, just one of ways to do it. I’m not sure it’s required for the prototype.
Given that our handling of fees is copy/paste of Polkadot, it is how we do things, isn't it?
Don't understand why this pallet should be any different.
Are you proposing we mix it up, some pallets charging for rent fee, while some don't? 🤷♂️
It's been on review for months, and better to do it right immediately then have someone else fix it later, add storage migration logic, etc. Doesn't matter if it's prototype or not, IMO.
Thank you for your feedback, @shaunxw! If you have no more concerns, then let’s approve and merge this PR. |
Pull Request Summary
Account abstraction pallet (
pallet-account
) makes possible to manage multiple origins and proxy call using desired account origin.Check list