Robinhood MainnetChain ID: 4663
Contract detail

CamelStrategy

CamelStrategy is a verified Robinhood Chain smart contract at 0xbedba5fd…f0d06974 with 0 indexed transactions.

Address type
Smart contract
Transactions
0
History coverage
No indexed activity found
Verification
Verified
Compiler
v0.8.26+commit.8a97fa7a
Verification match
partial
Creation transaction
0xffba5b99…dedddb25
Created at block
#1051627
Language
solidity
EVM version
cancun
License
none
Compiler optimization
Enabled · 800 runs
Verified contract

Contract source code

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.26;

import "./ILiquidityStrategy.sol";
import "./LiquidityDistributionLibrary.sol";
import "./StrategyGridLibrary.sol";
import {TickMath} from "v4-core/libraries/TickMath.sol";
import {FullMath} from "v4-core/libraries/FullMath.sol";

/**
 * @title CamelStrategy
 * @notice Camel (double-peaked) distribution with a center-owned valley band
 * @dev Key features:
 *      1. Dynamic width targeting ~20 ranges
 *      2. Two-sided requests generate one band that owns centerTick
 *      3. Camel weights keep a single valley point at centerTick
 */
contract CamelStrategy is ILiquidityStrategy {
    using LiquidityDistributionLibrary for uint256[];
    using LiquidityDistributionLibrary for int24;

    uint256 public constant CARPET_WEIGHT = 0.00005e18; // 0.005% carpet weight

    struct DensityParams {
        int24[] lowerTicks;
        int24[] upperTicks;
        int24 currentTick;
        int24 centerTick;
        uint24 ticksLeft;
        uint24 ticksRight;
        uint256 weight0;
        uint256 weight1;
        bool useCarpet;
        int24 tickSpacing;
        bool useAssetWeights;
    }

    /**
     * @notice Generate ranges with optional full-range floor position
     * @dev Uses a center-owned equal-width grid for two-sided requests
     */
    function generateRanges(int24 centerTick, uint24 ticksLeft, uint24 ticksRight, int24 tickSpacing, bool useCarpet)
        external
        pure
        override
        returns (int24[] memory lowerTicks, int24[] memory upperTicks)
    {
        return _generateRanges(centerTick, ticksLeft, ticksRight, tickSpacing, useCarpet);
    }

    function _generateRanges(int24 centerTick, uint24 ticksLeft, uint24 ticksRight, int24 tickSpacing, bool useCarpet)
        internal
        pure
        returns (int24[] memory lowerTicks, int24[] memory upperTicks)
    {
        (int24[] memory baseLowers, int24[] memory baseUppers) =
            StrategyGridLibrary.generateCenterOwnedRanges(centerTick, ticksLeft, ticksRight, tickSpacing);

        if (!useCarpet) {
            return (baseLowers, baseUppers);
        }

        return _prependFullRangeFloor(baseLowers, baseUppers, tickSpacing);
    }

    function _prependFullRangeFloor(int24[] memory baseLowers, int24[] memory baseUppers, int24 tickSpacing)
        private
        pure
        returns (int24[] memory lowerTicks, int24[] memory upperTicks)
    {
        int24 minUsable = TickMath.minUsableTick(tickSpacing);
        int24 maxUsable = TickMath.maxUsableTick(tickSpacing);
        uint256 length = baseLowers.length;

        if (length == 0) {
            lowerTicks = new int24[](1);
            upperTicks = new int24[](1);
            lowerTicks[0] = minUsable;
            upperTicks[0] = maxUsable;
            return (lowerTicks, upperTicks);
        }

        uint256 floorIdx = type(uint256).max;
        for (uint256 i = 0; i < length; ++i) {
            if (_isFullRange(baseLowers[i], baseUppers[i], minUsable, maxUsable)) {
                floorIdx = i;
                break;
            }
        }

        if (floorIdx == 0) {
            return (baseLowers, baseUppers);
        }

        if (floorIdx == type(uint256).max) {
            lowerTicks = new int24[](length + 1);
            upperTicks = new int24[](length + 1);
            lowerTicks[0] = minUsable;
            upperTicks[0] = maxUsable;
            for (uint256 i = 0; i < length; ++i) {
                lowerTicks[i + 1] = baseLowers[i];
                upperTicks[i + 1] = baseUppers[i];
            }
            return (lowerTicks, upperTicks);
        }

        lowerTicks = new int24[](length);
        upperTicks = new int24[](length);
        lowerTicks[0] = minUsable;
        upperTicks[0] = maxUsable;

        uint256 idx = 1;
        for (uint256 i = 0; i < length; ++i) {
            if (i == floorIdx) {
                continue;
            }
            lowerTicks[idx] = baseLowers[i];
            upperTicks[idx] = baseUppers[i];
            unchecked {
                ++idx;
            }
        }
    }

    function _isFullRange(int24 lowerTick, int24 upperTick, int24 minUsable, int24 maxUsable)
        private
        pure
        returns (bool)
    {
        return lowerTick == minUsable && upperTick == maxUsable;
    }

    /**
     * @notice Calculate Camel density with all options
     */
    function calculateDensities(
        int24[] memory lowerTicks,
        int24[] memory upperTicks,
        int24 currentTick,
        int24 centerTick,
        uint24 ticksLeft,
        uint24 ticksRight,
        uint256 weight0,
        uint256 weight1,
        bool useCarpet,
        int24 tickSpacing,
        bool useAssetWeights
    ) public pure override returns (uint256[] memory weights) {
        DensityParams memory params = DensityParams({
            lowerTicks: lowerTicks,
            upperTicks: upperTicks,
            currentTick: currentTick,
            centerTick: centerTick,
            ticksLeft: ticksLeft,
            ticksRight: ticksRight,
            weight0: weight0,
            weight1: weight1,
            useCarpet: useCarpet,
            tickSpacing: tickSpacing,
            useAssetWeights: useAssetWeights
        });

        return _calculateDensitiesInternal(params);
    }

    function _calculateDensitiesInternal(DensityParams memory params) private pure returns (uint256[] memory weights) {
        uint256 numRanges = params.lowerTicks.length;
        weights = new uint256[](numRanges);

        if (numRanges == 0) return weights;

        if (params.useCarpet && numRanges > 1) {
            int24 minUsable = TickMath.minUsableTick(params.tickSpacing);
            int24 maxUsable = TickMath.maxUsableTick(params.tickSpacing);
            if (params.lowerTicks[0] == minUsable && params.upperTicks[0] == maxUsable) {
                int24[] memory baseLower = new int24[](numRanges - 1);
                int24[] memory baseUpper = new int24[](numRanges - 1);
                for (uint256 i = 1; i < numRanges; ++i) {
                    baseLower[i - 1] = params.lowerTicks[i];
                    baseUpper[i - 1] = params.upperTicks[i];
                }

                DensityParams memory baseParams = params;
                baseParams.lowerTicks = baseLower;
                baseParams.upperTicks = baseUpper;

                uint256[] memory baseWeights = new uint256[](numRanges - 1);
                _processWeightsNoCarpet(baseWeights, baseParams);
                weights[0] = 0;
                for (uint256 i = 0; i < baseWeights.length; ++i) {
                    weights[i + 1] = baseWeights[i];
                }
                return weights;
            }
        }

        // Validate weights sum to 1e18 (skip for proportional weights)
        if (!params.useAssetWeights) {
            require(params.weight0 + params.weight1 == 1e18, "Weights must sum to 1e18");
        }

        _processWeightsNoCarpet(weights, params);
        _applyFullRangeFloorWeight(weights, params);

        return weights;
    }

    function _processWeightsNoCarpet(uint256[] memory weights, DensityParams memory params) private pure {
        uint256 numRanges = params.lowerTicks.length;

        (uint256 effectiveTicksLeft, uint256 effectiveTicksRight) = StrategyGridLibrary.effectiveTickSpans(
            params.lowerTicks[0],
            params.upperTicks[numRanges - 1],
            params.centerTick,
            params.ticksLeft,
            params.ticksRight
        );
        if (effectiveTicksLeft == 0) effectiveTicksLeft = 1;
        if (effectiveTicksRight == 0) effectiveTicksRight = 1;

        // Calculate peak parameters using clamped values
        (int24 leftPeak, int24 rightPeak,) =
            _calculatePeakParamsClamped(params.centerTick, effectiveTicksLeft, effectiveTicksRight);

        int24 minimum = params.lowerTicks[0];
        int24 maximum = params.upperTicks[numRanges - 1];

        // Calculate base camel weights
        uint256[] memory baseWeights = new uint256[](numRanges);
        uint256 totalBaseWeight;

        for (uint256 i = 0; i < numRanges;) {
            baseWeights[i] = _calculateCamelWeightFixed(
                params.lowerTicks[i], params.upperTicks[i], params.centerTick, minimum, maximum, leftPeak, rightPeak
            );

            // Ensure minimum weight
            if (baseWeights[i] == 0) baseWeights[i] = 1;
            unchecked {
                totalBaseWeight += baseWeights[i];
                ++i;
            }
        }

        // Apply weight preferences
        if (params.useAssetWeights) {
            // Proportional weights: use base distribution (no filtering)
            if (totalBaseWeight != 0) {
                for (uint256 i = 0; i < numRanges;) {
                    weights[i] = FullMath.mulDiv(baseWeights[i], LiquidityDistributionLibrary.WAD, totalBaseWeight);
                    unchecked {
                        ++i;
                    }
                }
            }
        } else {
            // Explicit preferences: apply weight-based filtering
            uint256[] memory adjustedWeights = new uint256[](numRanges);
            uint256 totalAdjustedWeight;

            for (uint256 i = 0; i < numRanges;) {
                if (params.upperTicks[i] <= params.currentTick) {
                    adjustedWeights[i] = FullMath.mulDiv(baseWeights[i] * params.weight1, 2, 1e18);
                } else if (params.lowerTicks[i] >= params.currentTick) {
                    adjustedWeights[i] = FullMath.mulDiv(baseWeights[i] * params.weight0, 2, 1e18);
                } else {
                    adjustedWeights[i] = baseWeights[i];
                }
                unchecked {
                    totalAdjustedWeight += adjustedWeights[i];
                    ++i;
                }
            }

            // Normalize to sum to 1e18
            if (totalAdjustedWeight != 0) {
                for (uint256 i = 0; i < numRanges;) {
                    weights[i] =
                        FullMath.mulDiv(adjustedWeights[i], LiquidityDistributionLibrary.WAD, totalAdjustedWeight);
                    unchecked {
                        ++i;
                    }
                }
            }
        }
    }

    function _applyFullRangeFloorWeight(uint256[] memory weights, DensityParams memory params) private pure {
        if (!params.useCarpet || weights.length == 0) {
            return;
        }

        int24 minUsable = TickMath.minUsableTick(params.tickSpacing);
        int24 maxUsable = TickMath.maxUsableTick(params.tickSpacing);

        if (params.lowerTicks[0] != minUsable || params.upperTicks[0] != maxUsable) {
            return;
        }

        if (weights.length == 1) {
            weights[0] = LiquidityDistributionLibrary.WAD;
            return;
        }

        uint256 sum;
        for (uint256 i = 1; i < weights.length; ++i) {
            sum += weights[i];
        }

        if (sum == 0) {
            weights[0] = 0;
            return;
        }

        for (uint256 i = 1; i < weights.length; ++i) {
            weights[i] = FullMath.mulDiv(weights[i], LiquidityDistributionLibrary.WAD, sum);
        }
        weights[0] = 0;
    }

    function _processWeightsWithCarpet(uint256[] memory weights, DensityParams memory params) private pure {
        // Process in two phases to avoid stack depth
        _processWeightsWithCarpetPhase1(weights, params);
    }

    function _processWeightsWithCarpetPhase1(uint256[] memory weights, DensityParams memory params) private pure {
        uint256 numRanges = params.lowerTicks.length;

        // Use the tick spacing from params instead of inferring
        int24 tickSpacing = params.tickSpacing;

        (bool hasLeftCarpet, bool hasRightCarpet, uint256 startIdx, uint256 endIdx) =
            _getCarpetInfo(params.lowerTicks, params.upperTicks, numRanges, tickSpacing);

        // Calculate clamped tick spans and peak parameters
        (uint256 effectiveTicksLeft, uint256 effectiveTicksRight, int24 leftPeak, int24 rightPeak) =
            _getClampedParamsForCarpet(params, startIdx, endIdx);

        // Calculate base and adjusted weights
        (uint256[] memory adjustedWeights, uint256 totalAdjustedWeight) = _calculateCarpetedWeightsFixed(
            params, startIdx, endIdx, leftPeak, rightPeak, effectiveTicksLeft, effectiveTicksRight
        );

        // Store carpet info in a struct to reduce parameters
        CarpetInfo memory carpetInfo = CarpetInfo({
            hasLeftCarpet: hasLeftCarpet,
            hasRightCarpet: hasRightCarpet,
            startIdx: startIdx,
            endIdx: endIdx,
            numRanges: numRanges
        });

        // Finalize carpet weights
        _finalizeCarpetWeights(weights, adjustedWeights, totalAdjustedWeight, carpetInfo);
    }

    function _getClampedParamsForCarpet(DensityParams memory params, uint256 startIdx, uint256 endIdx)
        private
        pure
        returns (uint256 effectiveTicksLeft, uint256 effectiveTicksRight, int24 leftPeak, int24 rightPeak)
    {
        // For carpet mode, main ranges start at startIdx and end at endIdx
        int256 leftSpan = int256(params.centerTick) - int256(params.lowerTicks[startIdx]);
        uint256 actualLeftSpan = leftSpan > 0 ? uint256(leftSpan) : 0;
        effectiveTicksLeft = actualLeftSpan < uint256(params.ticksLeft) ? actualLeftSpan : uint256(params.ticksLeft);

        int256 rightSpan = int256(params.upperTicks[endIdx - 1]) - int256(params.centerTick);
        uint256 actualRightSpan = rightSpan > 0 ? uint256(rightSpan) : 0;
        effectiveTicksRight =
            actualRightSpan < uint256(params.ticksRight) ? actualRightSpan : uint256(params.ticksRight);

        if (effectiveTicksLeft == 0) effectiveTicksLeft = 1;
        if (effectiveTicksRight == 0) effectiveTicksRight = 1;

        // Calculate peak parameters using clamped values
        (leftPeak, rightPeak,) = _calculatePeakParamsClamped(params.centerTick, effectiveTicksLeft, effectiveTicksRight);
    }

    struct CarpetInfo {
        bool hasLeftCarpet;
        bool hasRightCarpet;
        uint256 startIdx;
        uint256 endIdx;
        uint256 numRanges;
    }

    function _getCarpetInfo(int24[] memory lowerTicks, int24[] memory upperTicks, uint256 numRanges, int24 tickSpacing)
        private
        pure
        returns (bool hasLeftCarpet, bool hasRightCarpet, uint256 startIdx, uint256 endIdx)
    {
        int24 minUsable = TickMath.minUsableTick(tickSpacing);
        int24 maxUsable = TickMath.maxUsableTick(tickSpacing);

        hasLeftCarpet = lowerTicks[0] == minUsable;
        hasRightCarpet = upperTicks[numRanges - 1] == maxUsable;

        startIdx = hasLeftCarpet ? 1 : 0;
        endIdx = hasRightCarpet ? numRanges - 1 : numRanges;
    }

    function _finalizeCarpetWeights(
        uint256[] memory weights,
        uint256[] memory adjustedWeights,
        uint256 totalAdjustedWeight,
        CarpetInfo memory carpetInfo
    ) private pure {
        uint256 numCarpetRanges;
        if (carpetInfo.hasLeftCarpet) {
            unchecked {
                ++numCarpetRanges;
            }
        }
        if (carpetInfo.hasRightCarpet) {
            unchecked {
                ++numCarpetRanges;
            }
        }

        // Only subtract CARPET_WEIGHT if we actually have carpet ranges
        uint256 carpetWeightPerRange = numCarpetRanges != 0 ? CARPET_WEIGHT / numCarpetRanges : 0;
        uint256 remainingWeight =
            numCarpetRanges != 0 ? LiquidityDistributionLibrary.WAD - CARPET_WEIGHT : LiquidityDistributionLibrary.WAD;

        if (carpetInfo.hasLeftCarpet) {
            weights[0] = carpetWeightPerRange;
        }
        if (carpetInfo.hasRightCarpet) {
            weights[carpetInfo.numRanges - 1] = carpetWeightPerRange;
        }

        // Normalize main range weights
        if (totalAdjustedWeight != 0) {
            for (uint256 i = carpetInfo.startIdx; i < carpetInfo.endIdx;) {
                weights[i] = FullMath.mulDiv(adjustedWeights[i], remainingWeight, totalAdjustedWeight);
                unchecked {
                    ++i;
                }
            }
        }
    }

    function _calculateCarpetedWeights(
        DensityParams memory params,
        uint256 startIdx,
        uint256 endIdx,
        int24 leftPeak,
        int24 rightPeak,
        uint256 /* sigma */
    ) private pure returns (uint256[] memory adjustedWeights, uint256 totalAdjustedWeight) {
        uint256 numRanges = params.lowerTicks.length;
        uint256[] memory baseWeights = new uint256[](numRanges);
        uint256 totalBaseWeight;

        // Calculate base camel weights for main ranges
        for (uint256 i = startIdx; i < endIdx;) {
            baseWeights[i] = _calculateCamelWeight(
                params, int24((int256(params.lowerTicks[i]) + int256(params.upperTicks[i])) / 2), leftPeak, rightPeak
            );

            if (baseWeights[i] == 0) baseWeights[i] = 1;
            unchecked {
                totalBaseWeight += baseWeights[i];
                ++i;
            }
        }

        // Apply weight preferences to main ranges
        adjustedWeights = new uint256[](numRanges);

        if (params.useAssetWeights) {
            // Proportional weights: use base distribution (no filtering)
            for (uint256 i = startIdx; i < endIdx;) {
                adjustedWeights[i] = baseWeights[i];
                unchecked {
                    totalAdjustedWeight += adjustedWeights[i];
                    ++i;
                }
            }
        } else {
            // Explicit preferences: apply weight-based filtering
            for (uint256 i = startIdx; i < endIdx;) {
                if (params.upperTicks[i] <= params.currentTick) {
                    adjustedWeights[i] = FullMath.mulDiv(baseWeights[i] * params.weight1, 2, 1e18);
                } else if (params.lowerTicks[i] >= params.currentTick) {
                    adjustedWeights[i] = FullMath.mulDiv(baseWeights[i] * params.weight0, 2, 1e18);
                } else {
                    adjustedWeights[i] = baseWeights[i];
                }
                unchecked {
                    totalAdjustedWeight += adjustedWeights[i];
                    ++i;
                }
            }
        }
    }

    function _calculateCamelWeight(DensityParams memory params, int24 posCenter, int24 leftPeak, int24 rightPeak)
        private
        pure
        returns (uint256)
    {
        // Calculate range boundaries for sigma calculations
        int24 minimum = params.centerTick - int24(params.ticksLeft);
        int24 maximum = params.centerTick + int24(params.ticksRight);

        // Left hump Gaussian (centered at leftPeak)
        uint256 leftHumpWeight = _calculateHumpWeight(posCenter, leftPeak, minimum, params.centerTick, true);

        // Right hump Gaussian (centered at rightPeak)
        uint256 rightHumpWeight = _calculateHumpWeight(posCenter, rightPeak, params.centerTick, maximum, false);

        // Python: density = np.where(x<center, density_1, density_2) + 0.5
        if (posCenter < params.centerTick) {
            return leftHumpWeight + LiquidityDistributionLibrary.HALF_WAD;
        } else {
            return rightHumpWeight + LiquidityDistributionLibrary.HALF_WAD;
        }
    }

    function _calculateHumpWeight(
        int24 posCenter,
        int24 peakCenter,
        int24 boundaryLeft,
        int24 boundaryRight,
        bool isLeftHump
    ) private pure returns (uint256) {
        int256 distance = int256(posCenter) - int256(peakCenter);
        uint256 absDistance = distance < 0 ? uint256(-distance) : uint256(distance);

        // Calculate sigmas based on hump type
        uint256 sigma;
        if (isLeftHump) {
            // Left hump: different sigmas for left/right of peak
            if (posCenter < peakCenter) {
                sigma = uint256(int256(peakCenter - boundaryLeft)) / 3;
            } else {
                sigma = uint256(int256(boundaryRight - peakCenter)) / 3;
            }
        } else {
            // Right hump: different sigmas for left/right of peak
            if (posCenter < peakCenter) {
                sigma = uint256(int256(peakCenter - boundaryLeft)) / 3;
            } else {
                sigma = uint256(int256(boundaryRight - peakCenter)) / 3;
            }
        }

        if (sigma == 0) sigma = 1;

        // Gaussian: exp(-0.5 * (distance/sigma)²)
        if (absDistance < sigma * 6) {
            int256 normalizedDist = int256((absDistance * 1e18) / sigma);
            int256 exponent = -(normalizedDist * normalizedDist) / 2e18;
            return LiquidityDistributionLibrary.exp(exponent);
        }

        return 0;
    }

    function _calculatePeakParams(int24 centerTick, uint24 ticksLeft, uint24 ticksRight)
        private
        pure
        returns (int24 leftPeak, int24 rightPeak, uint256 sigma)
    {
        // Match Python logic: peaks at center ± (range/5)
        uint24 leftOffset = ticksLeft / 5;
        uint24 rightOffset = ticksRight / 5;

        // Ensure minimum offset
        if (leftOffset < 20) leftOffset = 20;
        if (rightOffset < 20) rightOffset = 20;

        leftPeak = centerTick - int24(leftOffset);
        rightPeak = centerTick + int24(rightOffset);

        // Sigma: peak_distance / 3 (matching Python)
        uint256 peakDistance = uint256(leftOffset + rightOffset);
        sigma = (peakDistance / 3) * LiquidityDistributionLibrary.WAD / 1000;
        if (sigma < 10 * LiquidityDistributionLibrary.WAD / 1000) {
            sigma = 10 * LiquidityDistributionLibrary.WAD / 1000;
        }
    }

    function _calculatePeakParamsClamped(int24 centerTick, uint256 effectiveTicksLeft, uint256 effectiveTicksRight)
        private
        pure
        returns (int24 leftPeak, int24 rightPeak, uint256 sigma)
    {
        // Match Python logic: peaks at center ± (range/5) using clamped values
        uint256 leftOffset = effectiveTicksLeft / 5;
        uint256 rightOffset = effectiveTicksRight / 5;

        // Ensure minimum offset
        if (leftOffset < 20) leftOffset = 20;
        if (rightOffset < 20) rightOffset = 20;

        leftPeak = centerTick - int24(uint24(leftOffset));
        rightPeak = centerTick + int24(uint24(rightOffset));

        // Sigma: peak_distance / 3 (matching Python)
        uint256 peakDistance = leftOffset + rightOffset;
        sigma = (peakDistance / 3) * LiquidityDistributionLibrary.WAD / 1000;
        if (sigma < 10 * LiquidityDistributionLibrary.WAD / 1000) {
            sigma = 10 * LiquidityDistributionLibrary.WAD / 1000;
        }
    }

    function _calculateCamelWeightFixed(
        int24 lowerTick,
        int24 upperTick,
        int24 centerTick,
        int24 minimum,
        int24 maximum,
        int24 leftPeak,
        int24 rightPeak
    ) private pure returns (uint256) {
        if (StrategyGridLibrary.containsCenterTick(lowerTick, upperTick, centerTick)) {
            uint256 centerWeight = _calculateHumpWeight(centerTick, rightPeak, centerTick, maximum, false);
            return centerWeight + LiquidityDistributionLibrary.HALF_WAD;
        }

        int256 centerOffsetX2 = StrategyGridLibrary.midpointOffsetX2(lowerTick, upperTick, centerTick);
        bool isLeftOfCenter = centerOffsetX2 < 0;

        // Left hump Gaussian (centered at leftPeak)
        uint256 leftHumpWeight = _calculateHumpWeightFixed(lowerTick, upperTick, leftPeak, minimum, centerTick);

        // Right hump Gaussian (centered at rightPeak)
        uint256 rightHumpWeight = _calculateHumpWeightFixed(lowerTick, upperTick, rightPeak, centerTick, maximum);

        // Python: density = np.where(x<center, density_1, density_2) + 0.5
        if (isLeftOfCenter) {
            return leftHumpWeight + LiquidityDistributionLibrary.HALF_WAD;
        } else {
            return rightHumpWeight + LiquidityDistributionLibrary.HALF_WAD;
        }
    }

    function _calculateHumpWeightFixed(
        int24 lowerTick,
        int24 upperTick,
        int24 peakCenter,
        int24 boundaryLeft,
        int24 boundaryRight
    ) private pure returns (uint256) {
        int256 peakOffsetX2 = StrategyGridLibrary.midpointOffsetX2(lowerTick, upperTick, peakCenter);
        uint256 absDistanceX2 = peakOffsetX2 < 0 ? uint256(-peakOffsetX2) : uint256(peakOffsetX2);

        uint256 sigma = peakOffsetX2 < 0
            ? uint256(int256(peakCenter - boundaryLeft)) / 3
            : uint256(int256(boundaryRight - peakCenter)) / 3;

        if (sigma == 0) sigma = 1;

        uint256 sigmaX2 = 2 * sigma;
        if (absDistanceX2 < sigmaX2 * 6) {
            int256 normalizedDist = int256((absDistanceX2 * 1e18) / sigmaX2);
            int256 exponent = -(normalizedDist * normalizedDist) / 2e18;
            return LiquidityDistributionLibrary.exp(exponent);
        }

        return 0;
    }

    function _calculateCarpetedWeightsFixed(
        DensityParams memory params,
        uint256 startIdx,
        uint256 endIdx,
        int24 leftPeak,
        int24 rightPeak,
        uint256, /* effectiveTicksLeft */
        uint256 /* effectiveTicksRight */
    ) private pure returns (uint256[] memory adjustedWeights, uint256 totalAdjustedWeight) {
        uint256 numRanges = params.lowerTicks.length;
        uint256[] memory baseWeights = new uint256[](numRanges);
        uint256 totalBaseWeight;
        int24 minimum = params.lowerTicks[startIdx];
        int24 maximum = params.upperTicks[endIdx - 1];

        // Calculate base camel weights for main ranges
        for (uint256 i = startIdx; i < endIdx;) {
            baseWeights[i] = _calculateCamelWeightFixed(
                params.lowerTicks[i], params.upperTicks[i], params.centerTick, minimum, maximum, leftPeak, rightPeak
            );

            if (baseWeights[i] == 0) baseWeights[i] = 1;
            unchecked {
                totalBaseWeight += baseWeights[i];
                ++i;
            }
        }

        // Apply weight preferences to main ranges
        adjustedWeights = new uint256[](numRanges);

        if (params.useAssetWeights) {
            // Proportional weights: use base distribution (no filtering)
            for (uint256 i = startIdx; i < endIdx;) {
                adjustedWeights[i] = baseWeights[i];
                unchecked {
                    totalAdjustedWeight += adjustedWeights[i];
                    ++i;
                }
            }
        } else {
            // Explicit preferences: apply weight-based filtering
            for (uint256 i = startIdx; i < endIdx;) {
                if (params.upperTicks[i] <= params.currentTick) {
                    adjustedWeights[i] = FullMath.mulDiv(baseWeights[i] * params.weight1, 2, 1e18);
                } else if (params.lowerTicks[i] >= params.currentTick) {
                    adjustedWeights[i] = FullMath.mulDiv(baseWeights[i] * params.weight0, 2, 1e18);
                } else {
                    adjustedWeights[i] = baseWeights[i];
                }
                unchecked {
                    totalAdjustedWeight += adjustedWeights[i];
                    ++i;
                }
            }
        }
    }

    /**
     * @notice This strategy supports weighted distribution
     */
    function supportsWeights() external pure override returns (bool) {
        return true;
    }

    /**
     * @notice Get strategy type identifier
     */
    function getStrategyType() external pure override returns (string memory) {
        return "Camel";
    }

    /**
     * @notice Get human-readable description
     */
    function getDescription() external pure override returns (string memory) {
        return
        "Camel distribution (double-peaked) with a single center valley, optional full-range floor, and weight preferences";
    }
}