Skip to main content
Cream Finance suffered a 130M USD hack in October 2021. Detailed Analysis: https://mudit.blog/cream-hack-analysis/

Description

The attack stem from a sudden price manipulation of a supported asset (yUSDVault) in the protocol. The attacker built up leveraged positions and then manipulated the price of the asset by donating the underlying into the yUSDVault.

Solution

One possible solution is to check price deviations from touched assets in the protocol during the transaction. This would avoid flashloan price manipulation attacks.
uint256 constant MAX_DEVIATION_BPS = 500; // 5% max deviation

function assertion_priceDeviation() external view {
    // Get pre-transaction prices first
    ph.forkPreTx();
    PhEvm.Log[] memory logs = ph.getLogs();

    // Count relevant logs to size our array
    uint256 count = 0;
    for (uint256 i = 0; i < logs.length; i++) {
        bytes32 topic = logs[i].topics[0];
        if (topic == CTokenInterface.Mint.selector ||
            topic == CTokenInterface.Redeem.selector ||
            topic == CTokenInterface.Borrow.selector ||
            topic == CTokenInterface.RepayBorrow.selector) {
            count++;
        }
    }

    // Collect touched assets and their pre-prices
    address[] memory touchedAssets = new address[](count);
    uint256[] memory prePrices = new uint256[](count);
    uint256 idx = 0;

    for (uint256 i = 0; i < logs.length; i++) {
        bytes32 topic = logs[i].topics[0];
        address asset;

        if (topic == CTokenInterface.Mint.selector) {
            asset = getAssetFromLog(logs[i]);
        } else if (topic == CTokenInterface.Redeem.selector) {
            asset = getAssetFromLog(logs[i]);
        } else if (topic == CTokenInterface.Borrow.selector) {
            asset = getAssetFromLog(logs[i]);
        } else if (topic == CTokenInterface.RepayBorrow.selector) {
            asset = getAssetFromLog(logs[i]);
        } else {
            continue;
        }

        touchedAssets[idx] = asset;
        prePrices[idx] = priceOracle.getPrice(asset);
        idx++;
    }

    // Get post-transaction prices and compare
    ph.forkPostTx();

    for (uint256 i = 0; i < touchedAssets.length; i++) {
        uint256 postPrice = priceOracle.getPrice(touchedAssets[i]);
        uint256 prePrice = prePrices[i];

        // Check deviation in both directions
        uint256 deviation;
        if (postPrice > prePrice) {
            deviation = ((postPrice - prePrice) * 10000) / prePrice;
        } else {
            deviation = ((prePrice - postPrice) * 10000) / prePrice;
        }

        require(deviation <= MAX_DEVIATION_BPS, "Price deviation too high");
    }
}

// Helper to extract asset address from log (implementation depends on log structure)
function getAssetFromLog(PhEvm.Log memory log) internal pure returns (address) {
    // Decode asset address from log topics or data
    // Implementation depends on the specific event structure
    return abi.decode(log.data, (address));
}