#36999 [SC-Insight] Incomplete Adjustment of `globalAmountInDynamicUse` During LOC Liquidation Causes Accumulated Dust and DoS Risk
Was this helpful?
Was this helpful?
Submitted on Nov 21st 2024 at 21:15:09 UTC by @jovi for
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)
The `globalAmountInDynamicUse` is not decremented by the correct amount when insolvent LOCs are liquidated due to discrepancies in `creditedTokenAmountToReceive` calculations.
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);
```
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.
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.