#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?