#36999 [SC-Insight] Incomplete Adjustment of `globalAmountInDynamicUse` During LOC Liquidation Causes Accumulated Dust and DoS Risk
Submitted on Nov 21st 2024 at 21:15:09 UTC by @jovi for Audit Comp | Anvil: Letters of Credit
Report ID: #36999
Report Type: Smart Contract
Report severity: Insight
Target: https://github.com/AcronymFoundation/anvil-contracts/blob/main/contracts/LetterOfCredit.sol
Impacts:
Temporary freezing of funds set to 48 hrs within the LetterOfCredit contract
Griefing (e.g. no profit motive for an attacker, but damage to the users or the protocol)
Description
Brief/Intro
The `globalAmountInDynamicUse` is not decremented by the correct amount when insolvent LOCs are liquidated due to discrepancies in `creditedTokenAmountToReceive` calculations.
Vulnerability Details
The issue arises in the `_calculateLiquidationContext` function, due to how insolvent LOCs are handled. When converting the LOC, the following code snippet will be executed: ```solidity if (maxCreditedTokenAmountToReceive < _loc.creditedTokenAmount) { if (_requiredCreditedAmount != _loc.creditedTokenAmount) revert PartialRedeemInsolvent(); return _createLiquidationContextUsingAllClaimableCollateral(_loc, maxCreditedTokenAmountToReceive); } ``` When we call this function, the `creditedTokenAmountToReceive` value returned is the second argument. In this case, this argument is `maxCreditedTokenAmountToReceive`.
Since the LOC is insolvent, `maxCreditedTokenAmountToReceive` will never be equal to the originally credited token amount of the dynamic LOC. By returning a value for `liquidationContext.creditedTokenAmountToReceive `that is smaller than the original creditedTokenAmount at this call, the insolvent LOC is effectively turned into a static one. The issue is that the difference `(originalCreditedTokenAmount - maxCreditedTokenAmountToReceive)` is not removed from the `globalAmountInDynamicUse` storage due to the following snippet in the `_liquidateLOCCollateral` function: ```solidity creditedTokens[creditedTokenAddress].globalAmountInDynamicUse -= liquidationContext .creditedTokenAmountToReceive; ```
When the dynamic LOC was created, `globalAmountInDynamicUse` was incremented by the credited token amount, but during liquidation, the full credited token amount is not being removed. Since the LOC has become static, it no longer affects `globalAmountInDynamicUse`.
Over time, these dust amounts accumulate and can cause a Denial of Service (DoS) on the creation of new LOCs because of how the creation of new dynamic LOCs is processed. The `_validateAndUpdateCreditedTokenUsageForDynamicLOCCreation` function ensures the credited amount in use is within certain bounds: ```solidity uint256 newCreditedAmountInUse = creditedToken.globalAmountInDynamicUse + _creditedTokenAmount; if (newCreditedAmountInUse > creditedToken.globalMaxInDynamicUse) revert GlobalCreditedTokenMaxInUseExceeded(creditedToken.globalMaxInDynamicUse, newCreditedAmountInUse);
```
Impact Details
The `globalAmountInDynamicUse` tracking is not correctly updated during liquidations. This can lead to a DoS on the creation of new dynamic LOCs as the `globalAmountInDynamicUse` value will incorrectly exceed the allowed limit, preventing new LOCs from being created.
Proof of Concept
Proof of concept
The following POC first executes multiple liquidations in order to accumulate gather dust at the `globalAmountInDynamicUse` storage. It logs the amounts of dust accumulated through each iteration. The following POC is structured in iterations that incrementally adjust the `globalAmountInDynamicUse` by summing dust amounts. Each iteration is comprised of the following steps:
Set the price to an initial value.
Create a dynamic LOC with a specific collateral and credited amount.
Modify the price oracle to make the LOC insolvent.
Trigger liquidation using `convertLOC`.
Observe the unadjusted `globalAmountInDynamicUse`. The POC finishes by emitting a `GlobalCreditedTokenMaxInUseExceeded` error, as the dust amounts accumulated become so big users cannot create new LOCs anymore at iteration 184. The output should look like this: ```
LetterOfCredit Should demonstrate the globalAmountInDynamicUse is not fully removed during LOC conversions: Error: VM Exception while processing transaction: reverted with custom error 'GlobalCreditedTokenMaxInUseExceeded(100000000000000000000000, 100238621759259259259416)' ```
Paste the following code as a test at the createDynamicLOC.ts file: ```typescript it('Should demonstrate the globalAmountInDynamicUse is not fully removed during LOC conversions', async function () { // Load the initial setup const { creator, beneficiary, vault, letterOfCredit, creditedToken, collateralToken, priceOracle, other, } = await loadFixture(baseSetup); // loop this 10 times for (let i = 0; i< 200; i++) { // Mint credited tokens for `other` and approve the `letterOfCredit` const creditedTokenAmount = 1_000_000n * 10n ** BigInt(await creditedToken.decimals()); await creditedToken.mint(await beneficiary.getAddress(), creditedTokenAmount); await creditedToken.connect(beneficiary).approve(await letterOfCredit.getAddress(), creditedTokenAmount);
}); ```
Run the test with: ```shell npm run test ```
Of course, this POC displays a DOS impact when there aren't multiple parties using the system. Given that the protocol will handle many LOCs, it is fair to assume a much smaller amount of liquidations would need to happen to reach a similar state -> as there will be a bigger legitimate values for the globalAmountInDynamicUse storage value.