#46826 [SC-Medium] transferFeeWei + Transfers.TRANSFER_GAS_ALLOWANCE` when `CoreVault::transferToCoreVault()` is called.
Submitted on Jun 5th 2025 at 00:18:03 UTC by @OxSCSamurai for Audit Comp | Flare | FAssets
Report ID: #46826
Report Type: Smart Contract
Report severity: Medium
Target: https://github.com/flare-foundation/fassets/blob/main/contracts/assetManager/library/CoreVault.sol
Impacts:
Contract fails to deliver promised returns, but doesn't lose value
Description
Agents can game the system by ensuring they always have msg.value > transferFeeWei + Transfers.TRANSFER_GAS_ALLOWANCE
when CoreVault::transferToCoreVault()
is called.
Summary:
Instead of paying the full fee of
transferFeeWei + Transfers.TRANSFER_GAS_ALLOWANCE
, agents can get a gas refund equivalent tomsg.value - transferFeeWei
which refunds them theTransfers.TRANSFER_GAS_ALLOWANCE
+ any surplus, instead of refunding them only the surplus or dust amount.The fact that the agent only needs his
msg.value
to be a dust amount more thantransferFeeWei + Transfers.TRANSFER_GAS_ALLOWANCE
in order to get this incorrect refund and pay less gas than the protocol intended, should draw some scrutiny to the implementation of this mechanism in this instance.In fact, an agent would be able to ensure 100% of the time that they pay less gas/fees than intended, by simply sending a dust amount of gas more the fee + gas allowance total.
Yet, if
msg.value
is exactly fee + gas allowance total, the fullmsg.value
is sent tostate.nativeAddress
.
Problem section:
// set the active request
_agent.activeTransferToCoreVault = redemptionRequestId;
// pay the transfer fee and return overpaid transfer fee when the difference is larger than gas use
// (all transfers are guarded by nonReentrant in the facet)
if (msg.value > transferFeeWei + Transfers.TRANSFER_GAS_ALLOWANCE) {
Transfers.transferNAT(state.nativeAddress, transferFeeWei);
Transfers.transferNATAllowFailure(payable(msg.sender), msg.value - transferFeeWei);
} else {
Transfers.transferNAT(state.nativeAddress, msg.value);
}
// send event
uint256 transferredUBA = Conversion.convertAmgToUBA(transferredAMG);
emit ICoreVault.TransferToCoreVaultStarted(agentVault, redemptionRequestId, transferredUBA);
}
To demonstrate what I mean:
Current code's issue:
if (msg.value > transferFeeWei + Transfers.TRANSFER_GAS_ALLOWANCE) {
Transfers.transferNAT(state.nativeAddress, transferFeeWei); // Takes only fee
Transfers.transferNATAllowFailure(payable(msg.sender), msg.value - transferFeeWei); // Returns too much
}
Condition checks for
fee + allowance
But only takes
fee
Returns everything above
fee
Protocol loses intended
allowance
Correct implementation:
if (msg.value > transferFeeWei + Transfers.TRANSFER_GAS_ALLOWANCE) {
Transfers.transferNAT(state.nativeAddress, transferFeeWei + Transfers.TRANSFER_GAS_ALLOWANCE); // Takes both
Transfers.transferNATAllowFailure(payable(msg.sender), msg.value - (transferFeeWei + Transfers.TRANSFER_GAS_ALLOWANCE)); // Returns true excess
}
Takes both
fee + allowance
Returns only true excess
Matches intended behavior
Protocol keeps what it should
Impacts:
The ramifications are limited but notable:
Easy to exploit by sending slightly more than fee + allowance
The protocol keeps only the
transferFeeWei
instead oftransferFeeWei + TRANSFER_GAS_ALLOWANCE
Agents get back more than they should
Protocol loses the intended gas allowance on each transaction, i.e. Protocol accumulates less gas than intended
Economic impact is bounded by
TRANSFER_GAS_ALLOWANCE * number of transactions
, i.e. (Protocol losesTRANSFER_GAS_ALLOWANCE
amount per transfer)Violates the intended economic design
If
TRANSFER_GAS_ALLOWANCE
is small (e.g., 100k gas), impact is minimalCould affect protocol's ability to perform operations
Impact: medium Likelihood: high Severity: medium
Impacts in scope:
Low: Contract fails to deliver promised returns, but doesn't lose value
Proof of Concept
Proof of Concept (PoC):
Step by step PoC:
// 1. Set up
transferFeeWei (e.g., 0.01 ETH)
TRANSFER_GAS_ALLOWANCE (0.001 ETH)
Add a small amount to trigger the bug (e.g., 0.0001 ETH)
// 2. Send transfer with excess
msg.value = 0.0111 ETH // This is > (0.01 + 0.001) ETH
// 3. Expected behavior:
Protocol keeps: 0.011 ETH (fee + allowance)
Refund: 0.0001 ETH
// 4. Actual behavior:
Protocol keeps: 0.01 ETH (only fee)
Refund: 0.0011 ETH (0.001 ETH extra returned)
This demonstrates how the protocol loses the gas allowance amount in every transfer where
msg.value > transferFeeWei + TRANSFER_GAS_ALLOWANCE
.
Recommended Fix:
This ensures the protocol keeps both the fee and the gas allowance as intended, while only returning the true excess amount to the user.
if (msg.value > transferFeeWei + Transfers.TRANSFER_GAS_ALLOWANCE) {
uint256 totalToKeep = transferFeeWei + Transfers.TRANSFER_GAS_ALLOWANCE;
Transfers.transferNAT(state.nativeAddress, totalToKeep);
Transfers.transferNATAllowFailure(payable(msg.sender), msg.value - totalToKeep);
}
Was this helpful?