Skip to content
This repository has been archived by the owner on Sep 28, 2023. It is now read-only.

The account abstraction pallet #130

Open
wants to merge 26 commits into
base: polkadot-v0.9.39
Choose a base branch
from

Conversation

akru
Copy link
Contributor

@akru akru commented Feb 7, 2023

Pull Request Summary

Account abstraction pallet (pallet-account) makes possible to manage multiple origins and proxy call using desired account origin.

Check list

  • added unit tests
  • updated documentation

@akru akru changed the base branch from polkadot-v0.9.33 to polkadot-v0.9.37 February 20, 2023 08:29
@shunsukew
Copy link
Member

shunsukew commented Feb 28, 2023

Can I ask what are purposes and use cases of call_as call to dispatch by using derived account? I'm interested in it.

@akru akru requested review from shunsukew and 0x7CFE March 28, 2023 05:15
@akru akru marked this pull request as ready for review March 28, 2023 05:15
let who = ensure_signed(origin)?;

let magic = T::ChainMagic::get();
let nonce = frame_system::Pallet::<T>::account_nonce(signer.clone());
Copy link
Member

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

Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Temporary removed

frame/pallet-account/src/lib.rs Show resolved Hide resolved
frame/pallet-account/src/origins.rs Show resolved Hide resolved
Comment on lines +50 to +53
pub enum NativeAndEVMKind {
Native,
H160,
}
Copy link
Member

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.

Copy link
Contributor Author

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]
Copy link
Member

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.

Copy link
Contributor Author

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>;
Copy link
Member

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.

Copy link
Contributor Author

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,
Copy link
Member

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?

Copy link
Contributor Author

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());
Copy link
Member

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,
});
Copy link
Member

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.

Copy link
Contributor Author

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.

Comment on lines 3 to 4
authors = ["Stake Technologies"]
edition = "2021"
Copy link
Member

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.

Copy link
Contributor Author

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;
Copy link
Member

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.

Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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

frame/pallet-account/src/lib.rs Outdated Show resolved Hide resolved
frame/pallet-account/src/lib.rs Outdated Show resolved Hide resolved
frame/pallet-account/src/lib.rs Outdated Show resolved Hide resolved
frame/pallet-account/src/lib.rs Outdated Show resolved Hide resolved
@akru
Copy link
Contributor Author

akru commented Apr 7, 2023

@Dinonard please check changes, I'll happy to merge it soon

if origin == NativeAndEVM::Native(ALICE_D1_NATIVE.into())
);
})
}
Copy link
Member

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.

Copy link
Contributor Author

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;
Copy link
Member

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);
Copy link
Member

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.

Comment on lines 141 to 142
(T::WeightInfo::proxy_call()
.saturating_add(T::DbWeight::get().reads_writes(1, 1))
Copy link
Member

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.

@akru akru changed the base branch from polkadot-v0.9.37 to polkadot-v0.9.39 April 20, 2023 06:07
frame/pallet-account/src/lib.rs Outdated Show resolved Hide resolved
.saturating_add(T::DbWeight::get().reads_writes(1, 2))
)]
#[pallet::call_index(0)]
pub fn new_origin(
Copy link
Member

@shaunxw shaunxw May 18, 2023

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.

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Member

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.

Comment on lines +66 to +73
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])),
}
}
Copy link
Member

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.

Copy link
Contributor Author

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();
Copy link
Member

@shaunxw shaunxw May 18, 2023

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

Copy link
Contributor Author

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.

frame/pallet-account/src/tests.rs Show resolved Hide resolved
frame/pallet-account/src/tests.rs Show resolved Hide resolved
@github-actions
Copy link

Code Coverage

Package Line Rate Branch Rate Health
precompiles/dapps-staking/src 93% 0%
frame/collator-selection/src 76% 0%
frame/custom-signatures/src 52% 0%
frame/dapps-staking/src/pallet 87% 0%
primitives/xcm/src 62% 0%
frame/pallet-xvm/src 5% 0%
chain-extensions/dapps-staking/src 0% 0%
chain-extensions/types/dapps-staking/src 0% 0%
frame/pallet-xvm/src/pallet 8% 0%
precompiles/sr25519/src 79% 0%
precompiles/assets-erc20/src 76% 0%
precompiles/xcm/src 85% 0%
frame/dapps-staking/src 82% 0%
chain-extensions/types/assets/src 0% 0%
frame/contracts-migration/src 0% 0%
frame/xc-asset-config/src 50% 0%
chain-extensions/types/xvm/src 0% 0%
chain-extensions/xvm/src 0% 0%
frame/pallet-account/src 73% 0%
precompiles/substrate-ecdsa/src 78% 0%
precompiles/utils/macro/src 0% 0%
precompiles/utils/src 69% 0%
precompiles/utils/macro/tests 0% 0%
frame/block-reward/src 80% 0%
precompiles/xvm/src 61% 0%
frame/pallet-xcm/src 53% 0%
chain-extensions/pallet-assets/src 0% 0%
Summary 57% (2738 / 4831) 0% (0 / 0)

Minimum allowed line rate is 50%

Comment on lines +147 to +148
AccountOrigin::<T>::insert(&who, next_index, new_origin.clone());
AccountLastOrigin::<T>::insert(&who, next_index + 1);
Copy link
Member

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.).

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Contributor Author

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?

Copy link
Member

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.

@akru
Copy link
Contributor Author

akru commented May 31, 2023

Thank you for your feedback, @shaunxw! If you have no more concerns, then let’s approve and merge this PR.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants