Skip to content

Latest commit

 

History

History
162 lines (134 loc) · 6.51 KB

083.md

File metadata and controls

162 lines (134 loc) · 6.51 KB

Powerful Honeysuckle Anteater

High

omniChainData will always be inacurate due to cross-chain message collisions

Summary

The current design of cross-messaging results in overriding omniChain data, which could lead to catastrophic inconsistencies.

Root Cause

This issue occurs in multiple parts of the codebase, specifically every time omniChain data is transferred. The data is simply overridden, leading to potential conflicts.

We have two chains, X and Y (in reality, Optimism and Mode L2). Initially, both chains start with the same omniChain data.

Scenario I:

  • X gets a new dCDS deposit and needs to send Y the updated omniChain data.
  • X --> LayerZero
  • LayerZero --> Y
  • Now both chains have the same updated omniChain data.

Scenario II (Bad Scenario):

  • Both chains frequently interact and simultaneously send LayerZero requests to update omniChain data on the other chain. This creates a situation similar to race conditions in concurrency, where atomic operations are not used.
  • X --> LayerZero
  • Y --> LayerZero

Collisions occur because X will receive Y's data, and Y will receive X's data, but neither will have the combined updated state.

For example, assume both X and Y have a totalDeposit value of 1.

  • X updates it to 2 and sends a request via LayerZero to Y.
  • At the same time, Y updates its totalDeposit to 4 and sends a request via LayerZero to X.

The resulting state:

  • X has totalDeposit set to 4.
  • Y has totalDeposit set to 2.

The correct state should have been totalDeposit set to 6 on both chains, reflecting the total increase of 4. This issue occurs because values are directly overridden instead of tracking deltas in the changes.

Example & code reference:

    function _lzReceive(
        Origin calldata /*_origin*/,
        bytes32 /*_guid*/,
        bytes calldata payload,
        address /*_executor*/,
        bytes calldata /*_extraData*/
    ) internal override {
        // Decoding the message from src
        OAppData memory oappData = abi.decode(payload, (OAppData));

....Skipping Code....

        // Update the global omnichain data struct
@>>     omniChainData = oappData.message;
        // Update the individual collateral data
        s_collateralData[oappData.assetName] = oappData.collateralData;
    }
  • We are sending a message for every state-changing interaction, such as Borrowers deposit/withdraw/renew/liquidate and dCDS users deposit/withdraw/redeem. Example: At the end of a deposit() call in CDSLib.sol#L594 here we just override the global data, before sending the cross-chain message.
    function deposit(
        CDSInterface.DepositUserParams memory params,
        mapping(address => CDSInterface.CdsDetails) storage cdsDetails,
        CDSInterface.Interfaces memory interfaces
    ) public returns (CDSInterface.DepositResult memory) {
.....Skipping code.....

@>>     interfaces.globalVariables.setOmniChainData(omniChainData);

        return CDSInterface.DepositResult(
                params.usdtAmountDepositedTillNow,
                params.totalCdsDepositedAmount,
                params.totalCdsDepositedAmountWithOptionFees,
                params.totalAvailableLiquidationAmount,
                params.cdsCount
            );
    }

and then after the call to the library, in deposit() function in CDS.sol we call the globalVariables to send the omnichain message:

    function deposit(
        uint128 usdtAmount,
        uint128 usdaAmount,
        bool liquidate,
        uint128 liquidationAmount,
        uint128 lockingPeriod
    ) public payable nonReentrant whenNotPaused(IMultiSign.Functions(4)) {
        // Get eth price
        uint128 ethPrice = getLatestData();

        // Check the eth price is non zero
        if (ethPrice == 0) revert CDS_ETH_PriceFeed_Failed();

        DepositResult memory result = CDSLib.deposit(
            DepositUserParams(
                usdtAmount,
                usdaAmount,
                liquidate,
                liquidationAmount,
                ethPrice,
                lastEthPrice,
                usdaLimit,
                usdtLimit,
                usdtAmountDepositedTillNow,
                totalCdsDepositedAmount,
                totalCdsDepositedAmountWithOptionFees,
                totalAvailableLiquidationAmount,
                cdsCount,
                lockingPeriod
            ),
            cdsDetails,
            Interfaces(
                treasury,
                globalVariables,
                usda,
                usdt,
                borrowing,
                CDSInterface(address(this))
            )
        );

...Skipping Code...

        // getting options since,the src don't know the dst state
        bytes memory _options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(400000, 0);

        // calculting fee
        MessagingFee memory fee = globalVariables.quote(
            IGlobalVariables.FunctionToDo(1),
            IBorrowing.AssetName(0),
            _options,
            false
        );

        // Calling Omnichain send function
@>>     globalVariables.send{value: fee.nativeFee}(
            IGlobalVariables.FunctionToDo(1),
            IBorrowing.AssetName(0),
            fee,
            _options,
            msg.sender
        );
    }

Internal pre-conditions

No response

External pre-conditions

No response

Attack Path

The issue described in the scenarios above can occur through normal interactions. Additionally, a malicious party could exploit this to take arbitrage opportunities or deliberately disrupt the state, which could have catastrophic consequences.

Impact

Given the protocol manages numerous cross-chain interactions, including transferring ETH tokens and tracking global variables, failing to maintain a consistent state and instead overriding it could easily result in loss of funds, protocol insolvency, and other severe issues.

Mitigation

Instead of directly overriding the omniChain data, implement a system to send only deltas. This approach would prevent collisions and ensure state consistency.