#43135 [BC-High] `epilogue_gas_payer` Silently Drops Excess Storage Fee Refunds Under Governed Gas Pool
Submitted on Apr 2nd 2025 at 15:36:07 UTC by @jovi for Attackathon | Movement Labs
Report ID: #43135
Report Type: Blockchain/DLT
Report severity: High
Target: https://github.com/immunefi-team/attackathon-movement-aptos-core/tree/main
Impacts:
Direct loss of funds
Modification of transaction fees outside of design parameters
Description
Summary
When governed_gas_pool_enabled() is active, epilogue_gas_payer fails to refund excess storage fees (storage_fee_refunded - amount_to_burn), resulting in silent, unavoidable loss of user funds.
Vulnerability Details
Location
File:
aptos_framework::transaction_validation::sources::transaction_validation.moveFunction:
epilogue_gas_payerRelevant Code:
} else if (amount_to_burn < storage_fee_refunded) { let mint_amount = storage_fee_refunded - amount_to_burn; if (!features::governed_gas_pool_enabled()) { transaction_fee::mint_and_refund(gas_payer, mint_amount); } // No refund when governed_gas_pool_enabled() is true };
Description
The epilogue_gas_payer function finalizes transaction fee accounting. When storage_fee_refunded exceeds amount_to_burn, the surplus (mint_amount) should be returned to the gas_payer. This works when governed_gas_pool_enabled() is false, but when true, the surplus is discarded due to the missing else branch. Users lose entitled funds without notification.
Impact
Significant Fund Loss: Users lose all excess refunds in a widespread fashion, whenever the condition triggers, potentially affecting thousands of transactions .
Economic Instability: Cumulative losses disrupt the fee model, deterring users/dApps that would be expecting refunds.
Exploitable Harm: Attackers may maximize losses via storage-heavy transactions targeting refunds.
Recommended Mitigation
} else if (amount_to_burn < storage_fee_refunded) {
let mint_amount = storage_fee_refunded - amount_to_burn;
if (!features::governed_gas_pool_enabled()) {
transaction_fee::mint_and_refund(gas_payer, mint_amount);
} else {
transaction_fee::mint_and_refund(gas_payer, mint_amount); // Direct refund
// OR: coin::deposit(governed_gas_pool_address, mint_amount); // Pool redirect
}
};Proof of Concept
Setup: Deploy with
governed_gas_pool_enabled()set totrue.Transaction: Execute storage cleanup:
storage_fee_refunded = 100 APTamount_to_burn = 60 APTExpected Refund: 40 APT (not issued).
Observation: Toggling the flag to
falserestores refunds.
Was this helpful?