#46122 [SC-Insight] Incorrect Minimum Lots Validation in CoreVault Redemption
Submitted on May 25th 2025 at 07:18:51 UTC by @aman for Audit Comp | Flare | FAssets
Report ID: #46122
Report Type: Smart Contract
Report severity: Insight
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
Brief/Intro
The redeemFromCoreVault
function in CoreVault.sol
incorrectly validates the minimum lots requirement when the available lots in the core vault are less than the coreVaultMinimumRedeemLots
setting. This allows redeemers to create redemption requests with fewer lots than the minimum requirement.
Vulnerability Details
The vulnerable code is in CoreVault.sol
:
function redeemFromCoreVault(
uint64 _lots,
string memory _redeemerUnderlyingAddress
)
internal
onlyEnabled
{
State storage state = getState();
require(state.coreVaultManager.isDestinationAddressAllowed(_redeemerUnderlyingAddress),
"underlying address not allowed by core vault");
AssetManagerSettings.Data storage settings = Globals.getSettings();
uint64 availableLots = getCoreVaultAmountLots();
uint64 minimumRedeemLots = SafeMath64.min64(state.minimumRedeemLots, availableLots);
require(_lots >= minimumRedeemLots, "requested amount too small");
// ... rest of the function
}
The issue is that the code:
Gets the available lots in the core vault
Sets
minimumRedeemLots
as the minimum ofstate.minimumRedeemLots
andavailableLots
This means if
availableLots
is less thanminimumRedeemLots
, the requirement is loweredThis contradicts the documentation which states that lots must be larger than
coreVaultMinimumRedeemLots
Impact Details
Impact: Allows redemption requests with fewer lots than the minimum requirement
Likelihood: Low - Only occurs when core vault has insufficient funds
References
File:
contracts/assetManager/library/CoreVault.sol
Function:
redeemFromCoreVault
Recommendations
Fix the minimum lots validation to always enforce the minimum requirement:
function redeemFromCoreVault(
uint64 _lots,
string memory _redeemerUnderlyingAddress
)
internal
onlyEnabled
{
State storage state = getState();
require(state.coreVaultManager.isDestinationAddressAllowed(_redeemerUnderlyingAddress),
"underlying address not allowed by core vault");
require(_lots >= state.minimumRedeemLots, "requested amount too small");
uint64 availableLots = getCoreVaultAmountLots();
require(_lots <= availableLots, "not enough available on core vault");
// ... rest of the function
}
Proof of Concept
Proof of Concept
Add the Following test Case to CoreVault.ts
:
it.only("request direct redemption from core vault less than min Redeem lots", async () => { // @audit-issue : redeem less than min lots
const agent = await Agent.createTest(context, agentOwner1, underlyingAgent1);
const minter = await Minter.createTest(context, minterAddress1, underlyingMinter1, context.underlyingAmount(10000000));
const redeemer = await Redeemer.create(context, redeemerAddress1, underlyingRedeemer1);
await prefundCoreVault(minter.underlyingAddress, 1e6);
// allow CV manager addresses
await coreVaultManager.addAllowedDestinationAddresses([redeemer.underlyingAddress], { from: governance });
// make agent available
await agent.depositCollateralLotsAndMakeAvailable(100);
// mint
const [minted] = await minter.performMinting(agent.vaultAddress, 2);
await minter.transferFAsset(redeemer.address, minted.mintedAmountUBA);
// agent requests transfer for some backing to core vault
const transferAmount = context.convertLotsToUBA(2);
await agent.transferToCoreVault(transferAmount);
let res = await context.assetManager.setCoreVaultMinimumRedeemLots(3, { from: context.governance });
expectEvent(res, "SettingChanged", { name: "coreVaultMinimumRedeemLots", value: "3" })
const minLots = await context.assetManager.getCoreVaultMinimumRedeemLots();
assertWeb3Equal(minLots, 3);
const lots = 2;
// redeemer requests direct redemption from CV
const [paymentAmount1, paymentReference1] = await testRedeemFromCV(redeemer, lots);
const trigRes = await coreVaultManager.triggerInstructions({ from: triggeringAccount });
const paymentReqs = filterEvents(trigRes, "PaymentInstructions");
assertWeb3Equal(paymentReqs[0].args.account, coreVaultUnderlyingAddress);
assertWeb3Equal(paymentReqs[0].args.destination, redeemer.underlyingAddress);
assertWeb3Equal(paymentReqs[0].args.amount, paymentAmount1);
expect(toBN(minLots).gt(toBN(lots))).to.be.true;
});
Run with command yarn testHH
.
Was this helpful?