#45674 [SC-Insight] `executeMinting()` allows impersonation of minter during chain-reorg due to deterministic `crtId` and lack of minter binding
Submitted on May 18th 2025 at 23:06:32 UTC by @danvinci_20 for Audit Comp | Flare | FAssets
Report ID: #45674
Report Type: Smart Contract
Report severity: Insight
Target: https://github.com/flare-foundation/fassets/blob/main/contracts/assetManager/facets/MintingFacet.sol
Impacts:
Direct theft of any user funds, whether at-rest or in-motion, other than unclaimed yield
Description
Description
An attacker can front-run a legitimate collateral reservation during a reorg and copy the crtId by ensuring their reservation gets the same randomizedIdSkip() result. By setting the executor field in their reservation to the real user’s executor and other data, they create a situation where the real executor, when executing with a valid payment proof, unintentionally mints tokens for the attacker’s address.
function reserveCollateral(
address _minter,
address _agentVault,
uint64 _lots,
uint64 _maxMintingFeeBIPS,
address payable _executor,
string[] calldata _minterUnderlyingAddresses
)
internal
{
Agent.State storage agent = Agent.get(_agentVault);
Agents.requireWhitelistedAgentVaultOwner(agent);
Collateral.CombinedData memory collateralData = AgentCollateral.combinedData(agent);
AssetManagerState.State storage state = AssetManagerState.get();
...............
require(msg.value >= reservationFee, "inappropriate fee amount");
// create new crt id - pre-increment, so that id can never be 0
@>> state.newCrtId += PaymentReference.randomizedIdSkip();
uint64 crtId = state.newCrtId;
// create in-memory cr and then put it to storage to not go out-of-stack
CollateralReservation.Data memory cr;
cr.valueAMG = valueAMG;
cr.underlyingFeeUBA = Conversion.convertAmgToUBA(valueAMG).mulBips(agent.feeBIPS).toUint128();
cr.reservationFeeNatWei = reservationFee.toUint128();
// 1 is added for backward compatibility where 0 means "value not stored" - it is subtracted when used
cr.poolFeeShareBIPS = agent.poolFeeShareBIPS + 1;
cr.agentVault = _agentVault;
cr.minter = _minter;
cr.executor = _executor;
cr.executorFeeNatGWei = ((msg.value - reservationFee) / Conversion.GWEI).toUint64();
........
}The reserveCollateral() flow assigns a crtId using a weak randomization mechanism:
Since this logic is block-dependent and lacks any entropy from the caller, it is predictable during reorgs. This enables an attacker to force a collision with a legitimate crtId reservation.
Because executeMinting() trusts that the caller (minter, executor, or agent) corresponds to the original reservation, and does not validate the minter address against an expected value, the following sequence is possible:
Legitimate user submits a reservation.
Attacker front-runs in a reorged block and creates their own reservation with same
crtId(by controlling timing).Attacker sets executor to the known executor used by the real user.
Real executor (most likely an offchain relayer) calls
executeMinting()believing they're minting for the legit user, but ends up minting to attacker-controlled address.
Impact
Likelihood: Low – reorgs are rare, but deterministic ID generation enables predictability.
Impact: High - The legitimate minter loses funds permanently. The attacker gains tokens minted using their own reservation.
Recommendation
To prevent this attack, bind executeMinting() to a validated minter identity:
Add an
expectedMinterparameter toexecuteMinting().Add
require(crt.minter == expectedMinter)to validate against impersonation.
This ensures the executor cannot be tricked into executing a minting for a spoofed reservation.
References
https://github.com/flare-foundation/fassets/blob/fc727ee70a6d36a3d8dec81892d76d01bb22e7f1/contracts/assetManager/facets/CollateralReservationsFacet.sol#L36-L51
https://github.com/flare-foundation/fassets/blob/fc727ee70a6d36a3d8dec81892d76d01bb22e7f1/contracts/assetManager/facets/MintingFacet.sol#L24-L32
Proof of Concept
Proof of Concept
The attacker can follow this part:
Legitimate user submits a reservation.
Attacker front-runs in a reorged block and creates their own reservation with same
crtId(by controlling timing).Attacker sets executor to the known executor used by the real user.
Real executor (most likely an offchain relayer) calls
executeMinting()believing they're minting for the legit user, but ends up minting to attacker-controlled address.
Was this helpful?