devnet-ready ← proxy-real-pays
opened 04:52PM - 27 Feb 26 UTC
## Motivation
With the upcoming 10x fee increase shipping in the same release…, delegates operating on behalf of real accounts would need to maintain significantly larger balances just to cover fees. This change lets the real account absorb those costs directly.
## Summary
- **Proxy pallet**: adds `set_real_pays_fee` extrinsic and `RealPaysFee` storage, letting a real account opt in to paying fees for a delegate. Benchmarked.
- **Transaction payment wrapper**: extends `ChargeTransactionPaymentWrapper` to inspect `proxy` calls during `validate` and charge the opted-in real account instead of the signer. Supports nested proxy and batch-of-proxy patterns up to 2 levels deep. Extension weight accounts for the additional storage reads.
Today, the delegate (signer) always pays the fee for proxy calls. With this change, the fee can be pushed to the real account — or even deeper into nested proxy structures — based on the `RealPaysFee` opt-in.
## How it works
When a `proxy(real, call)` extrinsic enters `validate`, the wrapper inspects the call to determine who should pay the fee:
| Scenario | Who pays |
|----------|----------|
| `proxy(real=A, call)` with `RealPaysFee<A, signer>` set | A |
| `proxy(real=A, call)` without opt-in | signer |
| `proxy(real=B, proxy(real=A, call))` with both `<B, signer>` and `<A, B>` set | A |
| `proxy(real=B, proxy(real=A, call))` with only `<B, signer>` set | B |
| `proxy(real=B, batch([proxy(real=A, ..), ..]))` with `<B, signer>` and `<A, B>` set, all batch items sharing the same real A | A |
| `proxy(real=B, batch([proxy(real=A, ..), ..]))` with only `<B, signer>` set | B |
The resolved fee payer is used to create a synthetic origin for the inner `ChargeTransactionPayment::validate` call. The original signer is always preserved as the dispatch origin.
## Design decisions
- **Only `proxy` calls are handled** — `proxy_announced` is intentionally excluded to keep the logic simple. Fee propagation relies on the delegate being the implicit caller, which is always the case for `proxy` but not for `proxy_announced`.
- **Nesting is capped at 2 levels** — the wrapper inspects the outer proxy and one level of inner proxy or batch. Deeper nesting is not followed.
- **Batch items must all share the same real** — for the fee to be pushed into a batch, every item must be a `proxy` call with the same real account. Mixed reals or non-proxy items cause the fee to stay at the outer real.
- **`RealPaysFee` is checked once per batch** — since the delegate is always the outer real for inner `proxy` calls, a single storage read on the first batch item covers the entire batch.
## Test plan
- 20 integration tests in `runtime/tests/transaction_payment_wrapper.rs` covering:
- Non-proxy calls (signer pays)
- Simple proxy with and without opt-in
- `proxy_announced` always charges signer
- Nested proxy: both opted in, only outer, neither, only inner
- Batch: all opted in (batch/batch_all/force_batch), only outer, mixed reals, non-proxy items, empty batch, inner not opted in
- Priority override for Normal, Operational, and Mandatory dispatch classes
## Type of Change
- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Documentation update
- [ ] Other (please describe):
## Breaking Change
If this PR introduces a breaking change, please provide a detailed description of the impact and the migration path for existing applications.
## Checklist
- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have run `./scripts/fix_rust.sh` to ensure my code is formatted and linted correctly
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream modules
## Screenshots (if applicable)
Please include any relevant screenshots or GIFs that demonstrate the changes made.
## Additional Notes
Please provide any additional information or context that may be helpful for reviewers.