code-423n4 / 2024-07-basin-validation

0 stars 0 forks source link

Divergence between swap and liquidity pricing functions #119

Open c4-bot-5 opened 1 month ago

c4-bot-5 commented 1 month ago

Lines of code

https://github.com/code-423n4/2024-07-basin/blob/main/src/functions/StableLUT/Stable2LUT1.sol#L27

Vulnerability details

Impact

Lets go with Alice and Bob, who are liquidity providers in a pool using the Stable2LUT1.sol. Alice adds liquidity using the getRatiosFromPriceLiquidity() function, so Bob swaps tokens using getRatiosFromPriceSwap(). Due to the divergence of price between these functions:

Bug is in these functions:

function getRatiosFromPriceLiquidity(uint256 price) external pure returns (PriceData memory) {}
// And here 
function getRatiosFromPriceSwap(uint256 price) external pure returns (PriceData memory) {}

Proof of concept

Place in LookupTable.t.sol to run:

     function test_consistencyVulnerability() public {
        uint256[] memory prices = new uint256[](4);
        prices[0] = 0.001083e6;  // Minimum valid price
        prices[1] = 10.37089e6;  // Maximum valid price
        prices[2] = 1e6;         // Equal price
        prices[3] = 5e6;         // Middle price

        for (uint256 i = 0; i < prices.length; i++) {
            try lookupTable.getRatiosFromPriceSwap(prices[i]) returns (Stable2LUT1.PriceData memory swapData) {
                try lookupTable.getRatiosFromPriceLiquidity(prices[i]) returns (Stable2LUT1.PriceData memory liqData) {
                    console.log("Price:", prices[i]);
                    console.log("Swap - High Price:", swapData.highPrice);
                    console.log("Liquidity - High Price:", liqData.highPrice);
                    console.log("Swap - Low Price:", swapData.lowPrice);
                    console.log("Liquidity - Low Price:", liqData.lowPrice);

                    // Check for significant differences
                    if (percentDifference(swapData.highPrice, liqData.highPrice) > 1e16 ||
                        percentDifference(swapData.lowPrice, liqData.lowPrice) > 1e16) {
                        console.log("Significant difference detected");
                        assertTrue(false, "Divergence between swap and liquidity functions");
                    }
                } catch Error(string memory reason) {
                    console.log("Liquidity function reverted:", reason);
                    assertTrue(false, "Liquidity function reverted unexpectedly");
                }
            } catch Error(string memory reason) {
                console.log("Swap function reverted:", reason);
                // This is expected for the maximum valid price
                if (prices[i] != 10.37089e6) {
                    assertTrue(false, "Swap function reverted unexpectedly");
                }
            }
        }
    }

    function percentDifference(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a > b) {
            return ((a - b) * 1e18) / b;
        } else {
            return ((b - a) * 1e18) / a;
        }
    }

Proof of test showing bug:

[FAIL. Reason: Divergence between swap and liquidity functions] test_consistencyVulnerability() (gas: 19266)

Tools Used

Foundry, Manual, Vs-code, Audit Wizard

Recommended Mitigation Steps

function getAParameter() external pure returns (uint256) {
        return 100;
    }

    function getRatiosFromPrice(uint256 price) internal pure returns (PriceData memory) {
        if (price < 0.001083e6) revert("LUT: Invalid price");
        if (price > 10.37089e6) revert("LUT: Invalid price");

        // use single, pricing logic here
        // this will need to be more complex
        uint256 basePrice = 1e6; // $1
        uint256 ratio = (price * 1e18) / basePrice;
        uint256 highPrice = (price * 101) / 100; // 1% higher
        uint256 lowPrice = (price * 99) / 100;   // 1% lower

        return PriceData(
            highPrice,
            ratio,
            (2e18 * basePrice) / price,
            lowPrice,
            (ratio * 99) / 100,
            (2e18 * basePrice) / lowPrice,
            1e18
        );
    }

    function getRatiosFromPriceSwap(uint256 price) external pure returns (PriceData memory) {
        return getRatiosFromPrice(price);
    }

    function getRatiosFromPriceLiquidity(uint256 price) external pure returns (PriceData memory) {
        return getRatiosFromPrice(price);
    }
}

Above mitigation prevents this divergence but will need to be more precise:

Assessed type

Context

nevillehuang commented 1 month ago

Could be related to #34