Contract detail
SingleUniformStrategy
SingleUniformStrategy is a verified Robinhood Chain smart contract at 0x2f393aa3…4ca20701 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
- 0x4ab270d4…7d95886a
- Created at block
- #1051676
- 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 {TickMath} from "v4-core/libraries/TickMath.sol";
import {FullMath} from "v4-core/libraries/FullMath.sol";
/**
* @title SingleUniformStrategy
* @notice Unified Uniform distribution strategy supporting all options
* @dev Supports standard, full-range floor, weighted, and weighted+floor variants
*/
contract SingleUniformStrategy is ILiquidityStrategy {
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 Allows explicit control over floor position generation
*/
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 minUsable = TickMath.minUsableTick(tickSpacing);
int24 maxUsable = TickMath.maxUsableTick(tickSpacing);
centerTick = _roundDownTick(centerTick, tickSpacing);
if (centerTick < minUsable) centerTick = minUsable;
if (centerTick > maxUsable) centerTick = maxUsable;
int24 leftBound;
int24 rightBound;
int256 tempLeft = int256(centerTick) - int256(uint256(ticksLeft));
if (tempLeft < int256(minUsable)) {
leftBound = minUsable;
} else {
leftBound = _roundDownTick(int24(tempLeft), tickSpacing);
}
int256 tempRight = int256(centerTick) + int256(uint256(ticksRight));
if (tempRight > int256(maxUsable)) {
rightBound = maxUsable;
} else {
rightBound = _roundUpTick(int24(tempRight), tickSpacing);
}
(int24[] memory baseLowers, int24[] memory baseUppers) = _generateStandardRanges(leftBound, rightBound);
if (!useCarpet) {
return (baseLowers, baseUppers);
}
return _prependFullRangeFloor(baseLowers, baseUppers, tickSpacing);
}
function _generateStandardRanges(int24 leftBound, int24 rightBound)
private
pure
returns (int24[] memory lowerTicks, int24[] memory upperTicks)
{
// SingleUniform always creates one position covering the snapped range.
lowerTicks = new int24[](1);
upperTicks = new int24[](1);
lowerTicks[0] = leftBound;
upperTicks[0] = rightBound;
}
function _roundDownTick(int24 tick, int24 tickSpacing) private pure returns (int24) {
int24 compressed = tick / tickSpacing;
if (tick < 0 && tick % tickSpacing != 0) {
compressed -= 1;
}
return compressed * tickSpacing;
}
function _roundUpTick(int24 tick, int24 tickSpacing) private pure returns (int24) {
int24 roundedDown = _roundDownTick(tick, tickSpacing);
if (roundedDown < tick) {
return roundedDown + tickSpacing;
}
return roundedDown;
}
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 Uniform 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;
// Calculate range boundaries
int24 leftBound = params.centerTick - int24(params.ticksLeft);
int24 rightBound = params.centerTick + int24(params.ticksRight);
// Calculate base uniform weights
uint256[] memory baseWeights = new uint256[](numRanges);
uint256 totalBaseWeight;
for (uint256 i = 0; i < numRanges;) {
int256 posCenterInt = (int256(params.lowerTicks[i]) + int256(params.upperTicks[i])) / 2;
int24 posCenter = int24(posCenterInt);
// Check if position is within the specified range
if (posCenter >= leftBound && posCenter <= rightBound) {
baseWeights[i] = LiquidityDistributionLibrary.WAD;
} else {
baseWeights[i] = 1; // Minimum weight for positions outside range
}
unchecked {
totalBaseWeight += baseWeights[i];
++i;
}
}
// Apply weight preferences: for proportional weights, use base distribution
// For explicit preferences, apply strong filtering
if (params.useAssetWeights) {
// Proportional weights: just 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) {
// Pure token1 range
adjustedWeights[i] = FullMath.mulDiv(baseWeights[i] * params.weight1, 2, 1e18);
} else if (params.lowerTicks[i] >= params.currentTick) {
// Pure token0 range
adjustedWeights[i] = FullMath.mulDiv(baseWeights[i] * params.weight0, 2, 1e18);
} else {
// Mixed range
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;
}
/**
* @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 "Uniform";
}
/**
* @notice Get human-readable description
*/
function getDescription() external pure override returns (string memory) {
return "Uniform distribution with optional full-range floor and weight preferences";
}
}