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";
}
}