Skip to content

Pay gas fees in selected FAs - #413

Draft
seanyoung wants to merge 6 commits into
m1from
sean/paygasanyfa
Draft

Pay gas fees in selected FAs#413
seanyoung wants to merge 6 commits into
m1from
sean/paygasanyfa

Conversation

@seanyoung

@seanyoung seanyoung commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds the ability for a transaction to pay its gas in a selected fungible asset instead of the native token, governed end-to-end by on-chain configuration. A transaction opts in by naming an FA in the new versioned payload; the VM validates it in the prologue, charges the fee in that FA in the epilogue, and routes it into a per-FA governed gas pool. Fully gated behind a new feature flag and inert until governance enables it, so it's a no-op for existing traffic.

What's included

Transaction format

  • New TransactionExtraConfig::V2 variant carrying gas_fa_coin: Option<AccountAddress> (the FA metadata object address to pay gas in), plus accessors (gas_fa_coin(), has_gas_fa_coin()) and handling in api/types conversion and TransactionMetadata.

Feature gate

  • New GAS_PAYABLE_FA feature flag (Rust FeatureFlag, Move features.move, release-builder mapping). The VM (validate_signed_transaction) rejects any transaction carrying gas_fa_coin with FEATURE_UNDER_GATING unless the flag is enabled.

Surfacing the choice to Move

  • New transaction_context::gas_payment_fungible_asset(): Option<address> accessor + native, with gas_fa_coin threaded through UserTransactionContext and a gas-schedule entry for the native.

Governed gas pool (per-FA pools + pricing)

  • governed_gas_pool now holds a governance-managed registry of accepted gas FAs, each with its own gas price (FA base units per gas unit) and its own pool (the pool account's primary store for that FA — separate balance/accounting per asset).
  • Entry points: add_accepted_gas_fungible_asset(metadata, gas_price), set_gas_fungible_asset_price, remove_accepted_gas_fungible_asset (all governance-gated), plus views (is_accepted_…, accepted_…, get_gas_fungible_asset_price, gas_fee_in_fa, pool balance) and events.

VM wiring (transaction_validation.move)

  • Prologue: when a txn elects gas_fa_coin, require the FA is accepted and the payer holds enough of it (gas_used_max × price).
  • Epilogue: charge gas_used × price in the FA into its governed gas pool. Wired into both the unified epilogue and the legacy epilogue_gas_payer_extended / epilogue_extended paths, so it works whether or not account abstraction is enabled (the latter matters on Movement, where AA is off).

Design notes

  • FA-only, by necessity. The gas asset is identified by an FA metadata address, so charging uses the privileged FA store path. Legacy Coin<T> types that aren't FA-migrated (e.g. MOVE on Movement) live in a typed CoinStore that no address-keyed path can reach, and runtime coin dispatch would need ENABLE_FUNCTION_VALUES (off on Movement). So accepted gas coins must be fungible assets. The native token keeps its existing default gas path.
  • Flat per-FA price. The fee is gas_used × gas_price, with gas_price set by governance per FA — deterministic, no oracle/DEX dependency in the gas hot path. (A keeper-updated market rate can layer on top later without touching the epilogue.)
  • Backward compatible. Additive only — new payload variant, new feature flag, new functions, gated branches. Prologue/epilogue call ABIs are unchanged and behavior is identical to today until GAS_PAYABLE_FA is enabled and an FA is accepted.

Testing

  • Framework Move unit tests (governed_gas_pool, transaction_context): registry add/remove, governance authorization, zero-price rejection, per-FA price get/set, gas_fee_in_fa, multi-FA pool isolation, deposit-when-not-accepted abort, feature-gate abort. Full suite green.
  • e2e (e2e-move-tests): FA gas routed to its pool (exact gas_used × price) on both the unified and legacy fee-payer/regular-sender epilogue paths; unaccepted-FA and insufficient-balance transactions discarded by the prologue; gas charged even on an aborted-but-kept transaction; feature-gating and the transaction_context accessor (Some/None/disabled).

Limitations / follow-ups

  • FA-only (see design note); legacy non-FA coins are out of scope on the current Movement config.
  • No storage-fee-refund netting for FA payers (APT-refund semantics don't translate).
  • Flat governance-set price; no market/oracle pricing yet.
  • Lighter coverage for multisig+FA, orderless(nonce)+FA, and simulation paths.
  • MISSING: re-distributing the governed gas pool in FA is not implemented

🤖 Generated with Claude Code

Key Areas to Review

Type of Change

  • New feature
  • Bug fix
  • Breaking change
  • Performance improvement
  • Refactoring
  • Dependency update
  • Documentation update
  • Tests

Which Components or Systems Does This Change Impact?

  • Validator Node
  • Full Node (API, Indexer, etc.)
  • Move/Aptos Virtual Machine
  • Aptos Framework
  • Aptos CLI/SDK
  • Developer Infrastructure
  • Move Compiler
  • Other (specify)

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant