Skip to content

Proxy real can pay fee on behalf of delegate#2470

Open
l0r1s wants to merge 11 commits intodevnet-readyfrom
proxy-real-pays
Open

Proxy real can pay fee on behalf of delegate#2470
l0r1s wants to merge 11 commits intodevnet-readyfrom
proxy-real-pays

Conversation

@l0r1s
Copy link
Collaborator

@l0r1s l0r1s commented Feb 27, 2026

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 handledproxy_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)
  • 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

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have run ./scripts/fix_rust.sh to ensure my code is formatted and linted correctly
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • 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.

@l0r1s l0r1s changed the title Proxy real pays Proxy real pays fee on behalf of delegate Feb 27, 2026
@l0r1s l0r1s changed the title Proxy real pays fee on behalf of delegate Proxy real can pays fee on behalf of delegate Feb 27, 2026
@l0r1s l0r1s changed the title Proxy real can pays fee on behalf of delegate Proxy real can pay fee on behalf of delegate Feb 27, 2026
@l0r1s l0r1s added the skip-cargo-audit This PR fails cargo audit but needs to be merged anyway label Feb 27, 2026
sam0x17
sam0x17 previously approved these changes Feb 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-cargo-audit This PR fails cargo audit but needs to be merged anyway

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants