Proxy Pallet On Steriods

This is to open the discussion around the current proxy pallet in Polkadot inherited from substrate, and potential updates we can have around it.

First, I think that the meta problem that we are trying to solve here is pre-authorizing transactions by other accounts.

That is, for example, allowing another account to submit a group of transactions on your behalf as long as certain conditions are met after the fact.

Or, imagine you know that you want to perform an action (unbond, withdraw and transfer of exactly x DOTs) sometime in the next year from an account kept in cold storage, and you want to authorize it once and trigger the action via another hot account. You could store the exact signature, but details like nonce and transaction mortality generally prevent you from doing this.

the proxy system is a subset of these use cases, where we specify a list of allowed calls, with no restrictions on the number of times the proxy can be used, post-conditions or arguments.

If we are to look at the broader problem, as already hinted at, I generally see 3 parameters to a generic pre-auth scenario:

  1. which calls, and how many times each.
  2. which arguments.
  3. post-conditions.

For example, for the above scenario, I would create a proxy that would be allowed to call unbond(x), wtihdraw(), transfer(x, dest) exactly once.

All of these are very easy to program as long as Polkadot provides a pre-defined list for each item in the list above, and the user can picks and combines them. The current proxy pallet is also working in a similar manner.

If you want to allow users to define the above set themselves, I think only the first one is possible. Setting a counter on a proxy is trivial, and each call is identifiable by an index.

Althgough then, the user is taking the risk that the call indices (or details of the logic) might change over time, which could lead to unexpected authorization. We can allow users to specify if they want their proxies to extend to the next runtime upgrade, or expire if the spec_version changed.

With all of this in mind, I think it is fair to say that the current proxy pallet is a conservative implementation. In fact, I once asked about this and received a fair answer about it.

That being said, I do think that we can think of having a more comprehensive pre-authorization/proxy pallet in FRAME, and maybe someday, in Polkadot.

If we are to push forward the current proxy pallet, from the list above, we can easily tackle the first item and:

  1. Allow proxies to have a decrementing counter, after which they cannot do anything.
  2. Create a new proxy type that lets the proxy creator specify a list of call indices they want to allow, optionally with a flag that says if the proxies expire after a spec_version change.

But I am not sure if this is a better approach, compared to creating a brand new pallet-pre-authorization and leaving the current proxy pallet as-is


One thing that makes me worried about Gav’s comment in the link above is that he seems to point out that allowing any type of flexibility with things such as proxies is extremely risky and could lead to various malicious backdoor being created. I don’t consider myself someone who follows the crypto space super deeply to know anecdotes about this, so if anyone knows a related story, please do share and perhaps the right decision is to in fact stick to the current simple proxy pallet.


(I do recall someone telling me about a team in our ecosystem that’s trying to solve making item 2 and 3 in my list programmable by end users, but I can’t remember their name).

3 Likes

This was my main concern about this, but I recently saw @OliverTY’s PR which has this logic:

impl<T: pallet::Config> Contains<T::Call> for Pallet<T>
where
	<T as frame_system::Config>::Call: GetCallMetadata,
{
	/// Return whether the call is allowed to be dispatched.
	fn contains(call: &T::Call) -> bool {
		let CallMetadata { pallet_name, function_name } = call.get_call_metadata();
		!Pallet::<T>::is_paused_unbound(pallet_name.into(), function_name.into())
	}
}
/// Return whether this call is paused.
pub fn is_paused_unbound(pallet: Vec<u8>, extrinsic: Vec<u8>) -> bool {
	let pallet = PalletNameOf::<T>::try_from(pallet);
	let extrinsic = ExtrinsicNameOf::<T>::try_from(extrinsic);

	match (pallet, extrinsic) {
		(Ok(pallet), Ok(extrinsic)) => Self::is_paused(&pallet, &extrinsic),
		_ => T::PauseTooLongNames::get(),
	}
}

So I think we could get around the call index problem by allowing users to match on strings, which is already quite unlikely to change, but if they do, would not accidentally allow access to other functions. “Safe by default”

1 Like

I think proxy pallet deserves revisiting its feature set and scope, whether the solution is adding extra features or writing new pallets it’s up for discussion.

One of the things that I would be more interested in tackling is to “fix anonymous proxies”. I’m developing Fido, an extensible noob friendly multi-wallet ecosystem that wants to abstract on-chain accounts as much as possible, anonymous proxies are a great(but a bit lacking) tool that I’d like to make the default for user’s accounts, for example we integrate with matrix so users can login just with a matrixID and their homeserver’s favorite credentials(e.g. WebAuthN), thanks to matrix each device already manages its own set of keys from which we can derive substrate accounts, there’s also a decent solution for secure backup of keys, cross device signing for verifying new devices, etc.
End goal is to make people forget about private seeds and have confidence that losing their phone doesn’t mean losing their life’s savings. Users will have many devices that can include hardware, web, mobile, desktop, hosted and more kind of wallets, that should just be proxies to a key-less on-chain account a.k.a. virtual account that can be managed in probably more ways than it is currently possible, e.g. depending on the kind of wallet users set much more granular restrictions to what calls are allowed, what amounts, max weight?, perhaps dynamic time delays, definitely paying fees from the target account(taking multi-asset scenarios into account) that could relate to fee-less transactions that enable things like authorizing a third-party to e.g. submit a vote on my behalf so I don’t need to pay for it.
To enable all this we might need to break up proxy pallet, virtual accounts might better live in the system pallet for example as noted by @xlc.

Curious to hear more ideas and use cases related to this pre-authorization topic :slight_smile:

4 Likes

I suspect you’re referring to OAK Network, the parachain built for cross-chain automation. XCM enables Kusama and Polkadot users to automate tasks on connected blockchains without ever taking custody of assets (or private keys). OAK recommends parachains use the proxy pallet to obtain user consent to future transactions. Users agree to future execution parameters that OAK stores on-chain and is now adding support for trigger conditions based on custom data streams (e.g. price).

3 Likes

This was my main concern about this, but I recently saw @OliverTY’s PR which has this logic :

Yeah, that’s also an option. Admittedly, the call name/index is the simpler issue to resolve here.

@olanod thanks for sharing the issues.

All in all, if I had infinite time, I would start by coding a new pallet-pre-auth from scratch and let it live next to the proxy pallet. Ideally, the API is a superset of the proxy-pallet, and some translation shims exist that help upgrading from one to the other. Perhaps it should actually be called pallet-proxy-v2, where it is mostly backwards compatible with the O.G. proxy pallet.