<!-- canonical: https://0xsimao.com/findings/autonomint-manipulate-omni-data-breaking -->

# An attacker can manipulate `omniChainData.cdsPoolValue` by breaking protocol.

Medium · Sherlock · Hedged stablecoin · 4th December 2024

Finding M-6 of the Autonomint competition.

- Protocol: https://audits.sherlock.xyz/contests/569
- Report: /reports/autonomint
- Codebase: https://github.com/0xsimao/2024-11-autonomint/tree/0d324e04d4c0ca306e1ae4d4c65f0cb9d681751b
- Source: https://github.com/sherlock-audit/2024-11-autonomint-judging/issues/263

---

### Summary

Missing update of `lastEthprice` in `borrowing.sol#depositTokens()` will cause manipulation of `omniChainData.cdsPoolValue` as an attaker replays `borrowing.sol#depositTokens()` by breaking protocol.

https://github.com/sherlock-audit/2024-11-autonomint/blob/main/Blockchain/Blockchian/contracts/Core_logic/borrowing.sol#L258

### Root Cause

- In `borrowing.sol#depositTokens()`, `lastEthprice` is not updated.
```solidity
    function depositTokens(
        BorrowDepositParams memory depositParam
    ) external payable nonReentrant whenNotPaused(IMultiSign.Functions(0)) {
        // Assign options for lz contract, here the gas is hardcoded as 400000, we got this through testing by iteration
        bytes memory _options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(400000, 0);
        // calculting fee
        MessagingFee memory fee = globalVariables.quote(
            IGlobalVariables.FunctionToDo(1),
            depositParam.assetName,
            _options,
            false
        );
        // Get the exchange rate for a collateral and eth price
        (uint128 exchangeRate, uint128 ethPrice) = getUSDValue(assetAddress[depositParam.assetName]);

        totalNormalizedAmount = BorrowLib.deposit(
            BorrowLibDeposit_Params(
                LTV,
                APR,
                lastCumulativeRate,
                totalNormalizedAmount,
                exchangeRate,
                ethPrice,
                lastEthprice
            ),
            depositParam,
            Interfaces(treasury, globalVariables, usda, abond, cds, options),
            assetAddress
        );

        //Call calculateCumulativeRate() to get currentCumulativeRate
        calculateCumulativeRate();
        lastEventTime = uint128(block.timestamp);

        // Calling Omnichain send function
        globalVariables.send{value: fee.nativeFee}(
            IGlobalVariables.FunctionToDo(1),
            depositParam.assetName,
            fee,
            _options,
            msg.sender
        );
    }
```
- In BorrowLib.sol:661, omniChainData is updated with new cdsPoolValue.
```solidity
    (ratio, omniChainData) = calculateRatio(
        params.depositingAmount,
        uint128(libParams.ethPrice),
        libParams.lastEthprice,
        omniChainData.totalNoOfDepositIndices,
        omniChainData.totalVolumeOfBorrowersAmountinWei,
        omniChainData.totalCdsDepositedAmount - omniChainData.downsideProtected,
        omniChainData
    );
    // Check whether the cds have enough funds to give downside prottection to borrower
    if (ratio < (2 * RATIO_PRECISION)) revert IBorrowing.Borrow_NotEnoughFundInCDS();
```
- In `BorrowLib.sol#calculateRatio()`, cdsPoolValue is updated by difference between lastEthprice and currentEthprice.
```solidity
    function calculateRatio(
        uint256 amount,
        uint128 currentEthPrice,
        uint128 lastEthprice,
        uint256 noOfDeposits,
        uint256 totalCollateralInETH,
        uint256 latestTotalCDSPool,
        IGlobalVariables.OmniChainData memory previousData
    ) public pure returns (uint64, IGlobalVariables.OmniChainData memory) {
        uint256 netPLCdsPool;

        // Calculate net P/L of CDS Pool
        // if the current eth price is high
        if (currentEthPrice > lastEthprice) {
            // profit, multiply the price difference with total collateral
@>          netPLCdsPool = (((currentEthPrice - lastEthprice) * totalCollateralInETH) / USDA_PRECISION) / 100;
        } else {
            // loss, multiply the price difference with total collateral
@>          netPLCdsPool = (((lastEthprice - currentEthPrice) * totalCollateralInETH) / USDA_PRECISION) / 100;
        }

        uint256 currentVaultValue;
        uint256 currentCDSPoolValue;

        // Check it is the first deposit
        if (noOfDeposits == 0) {
            // Calculate the ethVault value
            previousData.vaultValue = amount * currentEthPrice;
            // Set the currentEthVaultValue to lastEthVaultValue for next deposit
            currentVaultValue = previousData.vaultValue;

            // Get the total amount in CDS
            // lastTotalCDSPool = cds.totalCdsDepositedAmount();
            previousData.totalCDSPool = latestTotalCDSPool;

            // BAsed on the eth prices, add or sub, profit and loss respectively
            if (currentEthPrice >= lastEthprice) {
@>              currentCDSPoolValue = previousData.totalCDSPool + netPLCdsPool;
            } else {
@>              currentCDSPoolValue = previousData.totalCDSPool - netPLCdsPool;
            }

            // Set the currentCDSPoolValue to lastCDSPoolValue for next deposit
@>          previousData.cdsPoolValue = currentCDSPoolValue;
@>          currentCDSPoolValue = currentCDSPoolValue * USDA_PRECISION;
        } else {
            // find current vault value by adding current depositing amount
            currentVaultValue = previousData.vaultValue + (amount * currentEthPrice);
            previousData.vaultValue = currentVaultValue;

            // BAsed on the eth prices, add or sub, profit and loss respectively
            if (currentEthPrice >= lastEthprice) {
                previousData.cdsPoolValue += netPLCdsPool;
            } else {
                previousData.cdsPoolValue -= netPLCdsPool;
            }
@>          previousData.totalCDSPool = latestTotalCDSPool;
@>          currentCDSPoolValue = previousData.cdsPoolValue * USDA_PRECISION;
        }

        // Calculate ratio by dividing currentEthVaultValue by currentCDSPoolValue,
        // since it may return in decimals we multiply it by 1e6
@>      uint64 ratio = uint64((currentCDSPoolValue * CUMULATIVE_PRECISION) / currentVaultValue);
        return (ratio, previousData);
    }
```

### Internal pre-conditions

_No response_

### External pre-conditions

- Eth price changed a little from borrowing.sol#lastEthprice.


### Attack Path

- We say that current ratio is bigger than `2 * RATIO_PRECISION`.
- An attaker continues to replay `borrowing.sol#depositTokens`.
- Then, `omniChainData.cdsPoolValue` continues to be increased or decreased.


### Impact

- In case of increasing, `cdsPoolValue` can be much bigger than normal. Then, borrowing is allowed even if protocol is really under water.
- In case of decreasing, `cdsPoolValue` can be decreased so that ratio is small enough to touch `(2 * RATIO_PRECISION)`. Then, borrowing can be DOSed even if cds have enough funds. And withdrawal from CDS can be DOSed.


### PoC

_No response_

### Mitigation

`borrowing.sol#depositTokens()` function has to be modified as follows.
```solidity
    function depositTokens(
        BorrowDepositParams memory depositParam
    ) external payable nonReentrant whenNotPaused(IMultiSign.Functions(0)) {
        // Assign options for lz contract, here the gas is hardcoded as 400000, we got this through testing by iteration
        bytes memory _options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(400000, 0);
        // calculting fee
        MessagingFee memory fee = globalVariables.quote(
            IGlobalVariables.FunctionToDo(1),
            depositParam.assetName,
            _options,
            false
        );
        // Get the exchange rate for a collateral and eth price
        (uint128 exchangeRate, uint128 ethPrice) = getUSDValue(assetAddress[depositParam.assetName]);

        totalNormalizedAmount = BorrowLib.deposit(
            BorrowLibDeposit_Params(
                LTV,
                APR,
                lastCumulativeRate,
                totalNormalizedAmount,
                exchangeRate,
                ethPrice,
                lastEthprice
            ),
            depositParam,
            Interfaces(treasury, globalVariables, usda, abond, cds, options),
            assetAddress
        );

        //Call calculateCumulativeRate() to get currentCumulativeRate
        calculateCumulativeRate();
        lastEventTime = uint128(block.timestamp);
++      lastEthprice = ethPrice;

        // Calling Omnichain send function
        globalVariables.send{value: fee.nativeFee}(
            IGlobalVariables.FunctionToDo(1),
            depositParam.assetName,
            fee,
            _options,
            msg.sender
        );
    }
```
