#45379 [SC-Low] Frontrunning Vulnerability in createAgentVault Suffix Reservation
Submitted on May 13th 2025 at 13:41:06 UTC by @EFCCWEB3 for Audit Comp | Flare | FAssets
Report ID: #45379
Report Type: Smart Contract
Report severity: Low
Target: https://github.com/flare-labs-ltd/fassets/blob/main/docs/ImmunefiScope.md
Impacts:
Griefing (e.g. no profit motive for an attacker, but damage to the users or the protocol)
Description
Brief/Intro
The createAgentVault
function in the FAsset system is vulnerable to a frontrunning griefing attack, where a malicious whitelisted agent can reserve a pool token suffix before a legitimate user’s transaction, causing the user’s vault creation to revert and waste gas (~40,000–50,000 gas). If exploited on mainnet, this could disrupt user experience, delay agent vault creation, and impose financial costs due to repeated failed transactions, particularly for users targeting desirable suffixes.
Vulnerability Details
The createAgentVault
function, an external entry point for creating agent vaults, delegates to an internal AgentsCreateDestroy.createAgentVault
function, which reserves a pool token suffix using _reserveAndValidatePoolTokenSuffix
. The suffix is checked for uniqueness early to minimize gas waste, as noted in the inline comment: // reserve suffix quickly to prevent griefing attacks by frontrunning agent creation with same suffix
, wasting agent owner gas. However, the function remains vulnerable to frontrunning by whitelisted agents. The external createAgentVault
function:
function createAgentVault(
IAddressValidity.Proof calldata _addressProof,
AgentSettings.Data calldata _settings
)
external
onlyAttached
returns (address _agentVault)
{
return AgentsCreateDestroy.createAgentVault(IIAssetManager(address(this)), _addressProof, _settings);
}
The internal createAgentVault
function:
function createAgentVault(
IIAssetManager _assetManager,
IAddressValidity.Proof calldata _addressProof,
AgentSettings.Data calldata _settings
)
internal
returns (address)
{
AssetManagerState.State storage state = AssetManagerState.get();
_reserveAndValidatePoolTokenSuffix(_settings.poolTokenSuffix);
address ownerManagementAddress = _getManagementAddress(msg.sender);
Agents.requireWhitelisted(ownerManagementAddress);
// ... (rest of the function)
}
The _reserveAndValidatePoolTokenSuffix
function:
function _reserveAndValidatePoolTokenSuffix(string memory _suffix)
private
{
AssetManagerState.State storage state = AssetManagerState.get();
require(!state.reservedPoolTokenSuffixes[_suffix], "suffix already reserved");
state.reservedPoolTokenSuffixes[_suffix] = true;
bytes memory suffixb = bytes(_suffix);
uint256 len = suffixb.length;
require(len < MAX_SUFFIX_LEN, "suffix too long");
// ... (character validation)
}
Vuln Details
A legitimate whitelisted user submits a transaction to
createAgentVault
with a desired suffix (e.g., "TOKEN-123" in _settings.poolTokenSuffix).A malicious whitelisted agent monitors the mempool, extracts the suffix from the transaction’s calldata, and submits their own
createAgentVault
transaction with the same suffix and higher gas fees.
NB: The so-called malicious whitelisted will act maliciously after getting whitelisted because the setter can never know the whitelisted user.
The attacker’s transaction is mined first, reserving the suffix in
state.reservedPoolTokenSuffixes
via_reserveAndValidatePoolTokenSuffix
.The legitimate user’s transaction reverts at
require(!state.reservedPoolTokenSuffixes[_suffix], "suffix already reserved")
, wasting gas due to the early check.
Why It Possible
Mempool Visibility: The suffix is visible in the transaction’s calldata, allowing attackers to target it.
Whitelist Limitation: While
Agents.requireWhitelisted
restricts callers to whitelisted management addresses, a malicious whitelisted agent can still frontrun.No Frontrunning Protection: The early suffix reservation reduces gas waste but doesn’t hide or protect the suffix from mempool-based attacks.
Impact Details
The attack above causes damage to users (gas waste, delays) without a profit motive for the attacker.
References
https://github.com/flare-labs-ltd/fassets/blob/fc727ee70a6d36a3d8dec81892d76d01bb22e7f1/contracts/assetManager/library/AgentsCreateDestroy.sol#L68-L70
https://github.com/flare-labs-ltd/fassets/blob/fc727ee70a6d36a3d8dec81892d76d01bb22e7f1/contracts/assetManager/library/AgentsCreateDestroy.sol#L244
Rcommendation
Add a two-step process to hide the suffix before calling createAgentVault
, and don't forget to unreserve suffixes on vault destruction when needed.
Proof of Concept
Proof of Concept
The comment appears in the internal createAgentVault
function:
// reserve suffix quickly to prevent griefing attacks by frontrunning agent creation
// with same suffix, wasting agent owner gas
_reserveAndValidatePoolTokenSuffix(_settings.poolTokenSuffix);
What It Says:
Purpose: The comment explains that
_reserveAndValidatePoolTokenSuffix
is called early in the function to reserve the pool token suffix (_settings.poolTokenSuffix
) as soon as possible.Goal: To prevent griefing attacks where an attacker frontruns a legitimate user’s transaction to reserve the same suffix, causing the user’s transaction to revert and waste gas.
Mechanism: By checking and reserving the suffix early (before expensive operations like vault creation), the function minimizes the gas wasted if the suffix is already taken.
Implication: The developers recognized the frontrunning risk and implemented an optimization to reduce its impact (gas waste). However, the comment does not claim to eliminate the attack, only to mitigate its consequences by reserving the suffix “quickly.”
Did they solve the front-running attack?
No, the attack involves a malicious whitelisted agent (e.g., Bob) frontrunning a legitimate user (e.g., Alice) to reserve the same pool token suffix, causing Alice’s createAgentVault
transaction to revert.
Here’s how it works:
Alice’s Transaction: Alice, a whitelisted agent, calls createAgentVault
with _settings.poolTokenSuffix = "GOLD"
. Her transaction enters the Ethereum mempool, exposing "GOLD" in the calldata.
Bob’s Attack: Bob, another whitelisted agent, monitors the mempool, sees Alice’s suffix, and submits his own createAgentVault
transaction with "GOLD" and a higher gas price (e.g., 50 gwei vs. Alice’s 20 gwei).
Execution: Bob’s transaction is mined first, calling _reserveAndValidatePoolTokenSuffix("GOLD")
:
require(!state.reservedPoolTokenSuffixes[_suffix], "suffix already reserved");
state.reservedPoolTokenSuffixes[_suffix] = true;
This reserves "GOLD" by setting state.reservedPoolTokenSuffixes["GOLD"] = true.
Alice’s transaction is processed next but reverts at the require
check because "GOLD" is now reserved, wasting her gas.
What the Early Suffix Reservation Does
The comment’s strategy of reserving the suffix “quickly” refers to calling _reserveAndValidatePoolTokenSuffix
at the start of the internal createAgentVault
function, before other operations like:
Vault creation (agentVaultFactory.create
).
Storage writes ( agent.status = Agent.Status.NORMAL
).
External calls ( TransactionAttestation.verifyAddressValidity
).
function createAgentVault(
IIAssetManager _assetManager,
IAddressValidity.Proof calldata _addressProof,
AgentSettings.Data calldata _settings
)
internal
returns (address)
{
AssetManagerState.State storage state = AssetManagerState.get();
// reserve suffix quickly to prevent griefing attacks
_reserveAndValidatePoolTokenSuffix(_settings.poolTokenSuffix);
address ownerManagementAddress = _getManagementAddress(msg.sender);
Agents.requireWhitelisted(ownerManagementAddress);
// ... (vault creation, address validation, collateral setup, etc.)
}
Was this helpful?