#36999 [SC-Insight] Incomplete Adjustment of `globalAmountInDynamicUse` During LOC Liquidation Cause

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: ```

  1. 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.

Last updated

Was this helpful?