2025-07-08 11:59:26 +02:00
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
pragma solidity ^0.8.19;
|
|
|
|
|
|
|
|
|
|
import "../VWAPTracker.sol";
|
2025-10-04 15:17:09 +02:00
|
|
|
import "../libraries/UniswapMath.sol";
|
|
|
|
|
import { LiquidityAmounts } from "@aperture/uni-v3-lib/LiquidityAmounts.sol";
|
|
|
|
|
import "@aperture/uni-v3-lib/TickMath.sol";
|
|
|
|
|
import { Math } from "@openzeppelin/utils/math/Math.sol";
|
|
|
|
|
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
|
2025-07-08 11:59:26 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @title ThreePositionStrategy
|
|
|
|
|
* @notice Abstract contract implementing the three-position liquidity strategy (Floor, Anchor, Discovery)
|
|
|
|
|
* @dev Provides the core logic for anti-arbitrage asymmetric slippage profile
|
2025-10-04 15:17:09 +02:00
|
|
|
*
|
2025-08-18 00:16:09 +02:00
|
|
|
* Three-Position Strategy:
|
2026-03-15 08:20:13 +00:00
|
|
|
* - ANCHOR: Near current price, fast price discovery (width enforced by LiquidityManager)
|
2025-08-18 00:16:09 +02:00
|
|
|
* - DISCOVERY: Borders anchor, captures fees (11000 tick spacing)
|
|
|
|
|
* - FLOOR: Deep liquidity at VWAP-adjusted prices
|
2025-10-04 15:17:09 +02:00
|
|
|
*
|
2025-08-18 00:16:09 +02:00
|
|
|
* The asymmetric slippage profile prevents profitable arbitrage by making
|
|
|
|
|
* buys progressively more expensive while sells remain liquid
|
2025-07-08 11:59:26 +02:00
|
|
|
*/
|
|
|
|
|
abstract contract ThreePositionStrategy is UniswapMath, VWAPTracker {
|
|
|
|
|
using Math for uint256;
|
|
|
|
|
|
2025-08-18 00:16:09 +02:00
|
|
|
/// @notice Tick spacing for the pool (base spacing)
|
2025-07-08 11:59:26 +02:00
|
|
|
int24 internal constant TICK_SPACING = 200;
|
2025-08-18 00:16:09 +02:00
|
|
|
/// @notice Discovery spacing (3x current price in ticks - 11000 ticks = ~3x price)
|
2025-10-04 15:17:09 +02:00
|
|
|
int24 internal constant DISCOVERY_SPACING = 11_000;
|
2025-07-08 11:59:26 +02:00
|
|
|
/// @notice Minimum discovery depth multiplier
|
|
|
|
|
uint128 internal constant MIN_DISCOVERY_DEPTH = 200;
|
|
|
|
|
|
|
|
|
|
/// @notice The three liquidity position types
|
|
|
|
|
enum Stage {
|
|
|
|
|
FLOOR,
|
|
|
|
|
ANCHOR,
|
|
|
|
|
DISCOVERY
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// @notice Structure representing a liquidity position
|
|
|
|
|
struct TokenPosition {
|
|
|
|
|
uint128 liquidity;
|
|
|
|
|
int24 tickLower;
|
|
|
|
|
int24 tickUpper;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// @notice Parameters for position strategy
|
|
|
|
|
struct PositionParams {
|
|
|
|
|
uint256 capitalInefficiency;
|
|
|
|
|
uint256 anchorShare;
|
|
|
|
|
uint24 anchorWidth;
|
|
|
|
|
uint256 discoveryDepth;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// @notice Storage for the three positions
|
|
|
|
|
mapping(Stage => TokenPosition) public positions;
|
|
|
|
|
|
feat: OptimizerV3 with direct 2D staking-to-LP parameter mapping
Core protocol changes for launch readiness:
- OptimizerV3: binary bear/bull mapping from (staking%, avgTax) — avoids
exploitable AW 30-90 kill zone. Bear: AS=30%, AW=100, CI=0, DD=0.3e18.
Bull: AS=100%, AW=20, CI=0, DD=1e18. UUPS upgradeable with __gap[48].
- Directional VWAP: only records prices on ETH inflow (buys), preventing
sell-side dilution of price memory
- Floor formula: unified max(scarcity, mirror, clamp) — VWAP mirror uses
distance from adjusted VWAP as floor distance, no branching
- PriceOracle (M-1 fix): correct fallback TWAP divisor (60000s, not 300s)
- Access control (M-2 fix): deployer-only guard on one-time setters
- Recenter rate limit (M-3 fix): 60-second cooldown for open recenters
- Safe fallback params: recenter() optimizer-failure defaults changed from
exploitable CI=50%/AW=50 to safe bear-mode CI=0/AW=100
- Recentered event for monitoring and indexing
- VERSION bump to 2, kraiken-lib COMPATIBLE_CONTRACT_VERSIONS updated
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:21:18 +00:00
|
|
|
/// @notice Deprecated — was floor high-water mark. Kept for storage layout compatibility.
|
|
|
|
|
int24 public __deprecated_floorHighWaterMark;
|
|
|
|
|
|
2025-07-08 11:59:26 +02:00
|
|
|
/// @notice Events for tracking ETH abundance/scarcity scenarios
|
|
|
|
|
event EthScarcity(int24 currentTick, uint256 ethBalance, uint256 outstandingSupply, uint256 vwap, int24 vwapTick);
|
|
|
|
|
event EthAbundance(int24 currentTick, uint256 ethBalance, uint256 outstandingSupply, uint256 vwap, int24 vwapTick);
|
|
|
|
|
/// @notice Abstract functions that must be implemented by inheriting contracts
|
feat: OptimizerV3 with direct 2D staking-to-LP parameter mapping
Core protocol changes for launch readiness:
- OptimizerV3: binary bear/bull mapping from (staking%, avgTax) — avoids
exploitable AW 30-90 kill zone. Bear: AS=30%, AW=100, CI=0, DD=0.3e18.
Bull: AS=100%, AW=20, CI=0, DD=1e18. UUPS upgradeable with __gap[48].
- Directional VWAP: only records prices on ETH inflow (buys), preventing
sell-side dilution of price memory
- Floor formula: unified max(scarcity, mirror, clamp) — VWAP mirror uses
distance from adjusted VWAP as floor distance, no branching
- PriceOracle (M-1 fix): correct fallback TWAP divisor (60000s, not 300s)
- Access control (M-2 fix): deployer-only guard on one-time setters
- Recenter rate limit (M-3 fix): 60-second cooldown for open recenters
- Safe fallback params: recenter() optimizer-failure defaults changed from
exploitable CI=50%/AW=50 to safe bear-mode CI=0/AW=100
- Recentered event for monitoring and indexing
- VERSION bump to 2, kraiken-lib COMPATIBLE_CONTRACT_VERSIONS updated
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:21:18 +00:00
|
|
|
|
2025-08-19 11:05:08 +02:00
|
|
|
function _getKraikenToken() internal view virtual returns (address);
|
2025-07-08 11:59:26 +02:00
|
|
|
function _getWethToken() internal view virtual returns (address);
|
|
|
|
|
function _isToken0Weth() internal view virtual returns (bool);
|
|
|
|
|
function _mintPosition(Stage stage, int24 tickLower, int24 tickUpper, uint128 liquidity) internal virtual;
|
|
|
|
|
function _getEthBalance() internal view virtual returns (uint256);
|
|
|
|
|
function _getOutstandingSupply() internal view virtual returns (uint256);
|
|
|
|
|
|
|
|
|
|
/// @notice Sets all three positions according to the asymmetric slippage strategy
|
|
|
|
|
/// @param currentTick The current market tick
|
|
|
|
|
/// @param params Position parameters from optimizer
|
|
|
|
|
function _setPositions(int24 currentTick, PositionParams memory params) internal {
|
|
|
|
|
uint256 ethBalance = _getEthBalance();
|
2025-10-04 15:17:09 +02:00
|
|
|
|
2025-07-08 11:59:26 +02:00
|
|
|
// Calculate floor ETH allocation (75% to 95% of total)
|
|
|
|
|
uint256 floorEthBalance = (19 * ethBalance / 20) - (2 * params.anchorShare * ethBalance / 10 ** 19);
|
|
|
|
|
|
|
|
|
|
// Step 1: Set ANCHOR position (shallow liquidity for fast price movement)
|
2025-08-19 11:05:08 +02:00
|
|
|
(uint256 pulledKraiken, uint128 anchorLiquidity) = _setAnchorPosition(currentTick, ethBalance - floorEthBalance, params);
|
2025-07-08 11:59:26 +02:00
|
|
|
|
2025-08-16 16:45:24 +02:00
|
|
|
// Step 2: Set DISCOVERY position (depends on anchor's liquidity)
|
|
|
|
|
uint256 discoveryAmount = _setDiscoveryPosition(currentTick, anchorLiquidity, params);
|
2025-07-08 11:59:26 +02:00
|
|
|
|
|
|
|
|
// Step 3: Set FLOOR position (deep liquidity, uses VWAP for historical memory)
|
2025-08-19 11:05:08 +02:00
|
|
|
_setFloorPosition(currentTick, floorEthBalance, pulledKraiken, discoveryAmount, params);
|
2025-07-08 11:59:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// @notice Sets the anchor position around current price (shallow liquidity)
|
|
|
|
|
/// @param currentTick Current market tick
|
|
|
|
|
/// @param anchorEthBalance ETH allocated to anchor position
|
|
|
|
|
/// @param params Position parameters
|
2025-08-19 11:05:08 +02:00
|
|
|
/// @return pulledKraiken Amount of KRAIKEN pulled for this position
|
2025-08-16 16:45:24 +02:00
|
|
|
/// @return anchorLiquidity The liquidity amount for the anchor position
|
2025-07-08 11:59:26 +02:00
|
|
|
function _setAnchorPosition(
|
|
|
|
|
int24 currentTick,
|
|
|
|
|
uint256 anchorEthBalance,
|
|
|
|
|
PositionParams memory params
|
2025-10-04 15:17:09 +02:00
|
|
|
)
|
|
|
|
|
internal
|
|
|
|
|
returns (uint256 pulledKraiken, uint128 anchorLiquidity)
|
|
|
|
|
{
|
2025-07-08 11:59:26 +02:00
|
|
|
// Enforce anchor range of 1% to 100% of the price
|
|
|
|
|
int24 anchorSpacing = TICK_SPACING + (34 * int24(params.anchorWidth) * TICK_SPACING / 100);
|
2025-10-04 15:17:09 +02:00
|
|
|
|
2025-07-08 11:59:26 +02:00
|
|
|
int24 tickLower = _clampToTickSpacing(currentTick - anchorSpacing, TICK_SPACING);
|
|
|
|
|
int24 tickUpper = _clampToTickSpacing(currentTick + anchorSpacing, TICK_SPACING);
|
2025-10-04 15:17:09 +02:00
|
|
|
|
2025-07-08 11:59:26 +02:00
|
|
|
uint160 sqrtRatioX96 = TickMath.getSqrtRatioAtTick(currentTick);
|
|
|
|
|
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
|
|
|
|
|
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
|
|
|
|
|
|
|
|
|
|
bool token0isWeth = _isToken0Weth();
|
2025-10-04 15:17:09 +02:00
|
|
|
|
2025-07-08 11:59:26 +02:00
|
|
|
if (token0isWeth) {
|
|
|
|
|
anchorLiquidity = LiquidityAmounts.getLiquidityForAmount0(sqrtRatioX96, sqrtRatioBX96, anchorEthBalance);
|
2025-08-19 11:05:08 +02:00
|
|
|
pulledKraiken = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioX96, anchorLiquidity);
|
2025-07-08 11:59:26 +02:00
|
|
|
} else {
|
|
|
|
|
anchorLiquidity = LiquidityAmounts.getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioX96, anchorEthBalance);
|
2025-08-19 11:05:08 +02:00
|
|
|
pulledKraiken = LiquidityAmounts.getAmount0ForLiquidity(sqrtRatioX96, sqrtRatioBX96, anchorLiquidity);
|
2025-07-08 11:59:26 +02:00
|
|
|
}
|
2025-10-04 15:17:09 +02:00
|
|
|
|
2025-07-08 11:59:26 +02:00
|
|
|
_mintPosition(Stage.ANCHOR, tickLower, tickUpper, anchorLiquidity);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// @notice Sets the discovery position (deep edge liquidity)
|
|
|
|
|
/// @param currentTick Current market tick (normalized to tick spacing)
|
2025-08-16 16:45:24 +02:00
|
|
|
/// @param anchorLiquidity Liquidity amount from anchor position
|
2025-07-08 11:59:26 +02:00
|
|
|
/// @param params Position parameters
|
2025-08-19 11:05:08 +02:00
|
|
|
/// @return discoveryAmount Amount of KRAIKEN used for discovery
|
2025-10-04 15:17:09 +02:00
|
|
|
function _setDiscoveryPosition(int24 currentTick, uint128 anchorLiquidity, PositionParams memory params) internal returns (uint256 discoveryAmount) {
|
2025-07-08 11:59:26 +02:00
|
|
|
currentTick = currentTick / TICK_SPACING * TICK_SPACING;
|
|
|
|
|
bool token0isWeth = _isToken0Weth();
|
2025-10-04 15:17:09 +02:00
|
|
|
|
2025-07-08 11:59:26 +02:00
|
|
|
// Calculate anchor spacing (same as in anchor position)
|
|
|
|
|
int24 anchorSpacing = TICK_SPACING + (34 * int24(params.anchorWidth) * TICK_SPACING / 100);
|
2025-10-04 15:17:09 +02:00
|
|
|
|
|
|
|
|
int24 tickLower = _clampToTickSpacing(token0isWeth ? currentTick - DISCOVERY_SPACING - anchorSpacing : currentTick + anchorSpacing, TICK_SPACING);
|
|
|
|
|
int24 tickUpper = _clampToTickSpacing(token0isWeth ? currentTick - anchorSpacing : currentTick + DISCOVERY_SPACING + anchorSpacing, TICK_SPACING);
|
|
|
|
|
|
2025-07-08 11:59:26 +02:00
|
|
|
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
|
|
|
|
|
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
|
|
|
|
|
|
2025-08-16 16:45:24 +02:00
|
|
|
// Calculate discovery liquidity to ensure X times more liquidity per tick than anchor
|
|
|
|
|
// Discovery should have 2x to 10x more liquidity per tick (not just total liquidity)
|
|
|
|
|
uint256 discoveryMultiplier = 200 + (800 * params.discoveryDepth / 10 ** 18);
|
2025-10-04 15:17:09 +02:00
|
|
|
|
2025-08-16 16:45:24 +02:00
|
|
|
// Calculate anchor width in ticks
|
|
|
|
|
int24 anchorWidth = 2 * anchorSpacing;
|
2025-10-04 15:17:09 +02:00
|
|
|
|
2025-08-16 16:45:24 +02:00
|
|
|
// Adjust for width difference: discovery liquidity = anchor liquidity * multiplier * (discovery width / anchor width)
|
2025-10-04 15:17:09 +02:00
|
|
|
uint128 liquidity = uint128(uint256(anchorLiquidity) * discoveryMultiplier * uint256(int256(DISCOVERY_SPACING)) / (100 * uint256(int256(anchorWidth))));
|
|
|
|
|
|
2025-08-16 16:45:24 +02:00
|
|
|
// Calculate discoveryAmount for floor position calculation
|
2025-07-08 11:59:26 +02:00
|
|
|
if (token0isWeth) {
|
2025-08-16 16:45:24 +02:00
|
|
|
discoveryAmount = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity);
|
2025-08-18 17:05:32 +02:00
|
|
|
} else {
|
|
|
|
|
discoveryAmount = LiquidityAmounts.getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity);
|
2025-07-08 11:59:26 +02:00
|
|
|
}
|
2025-10-04 15:17:09 +02:00
|
|
|
|
2025-07-08 11:59:26 +02:00
|
|
|
_mintPosition(Stage.DISCOVERY, tickLower, tickUpper, liquidity);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// @notice Sets the floor position using VWAP for historical price memory (deep edge liquidity)
|
2025-08-18 00:16:09 +02:00
|
|
|
/// @dev Floor position placement depends on ETH scarcity vs abundance:
|
|
|
|
|
/// - Scarcity: Floor moves to extreme ticks (140k+) where KRAIKEN is very cheap
|
|
|
|
|
/// - Abundance: Floor placed near VWAP-adjusted price
|
|
|
|
|
/// Extreme floor positions are CORRECT behavior protecting protocol solvency
|
2025-07-08 11:59:26 +02:00
|
|
|
/// @param currentTick Current market tick
|
2025-08-18 00:16:09 +02:00
|
|
|
/// @param floorEthBalance ETH allocated to floor position (75% of total)
|
2025-08-19 11:05:08 +02:00
|
|
|
/// @param pulledKraiken KRAIKEN amount from anchor position
|
|
|
|
|
/// @param discoveryAmount KRAIKEN amount from discovery position
|
2025-08-18 00:16:09 +02:00
|
|
|
/// @param params Position parameters including capital inefficiency
|
2025-07-08 11:59:26 +02:00
|
|
|
function _setFloorPosition(
|
|
|
|
|
int24 currentTick,
|
|
|
|
|
uint256 floorEthBalance,
|
2025-08-19 11:05:08 +02:00
|
|
|
uint256 pulledKraiken,
|
2025-07-08 11:59:26 +02:00
|
|
|
uint256 discoveryAmount,
|
|
|
|
|
PositionParams memory params
|
2025-10-04 15:17:09 +02:00
|
|
|
)
|
|
|
|
|
internal
|
|
|
|
|
{
|
2025-07-08 11:59:26 +02:00
|
|
|
bool token0isWeth = _isToken0Weth();
|
2025-10-04 15:17:09 +02:00
|
|
|
|
2025-07-08 11:59:26 +02:00
|
|
|
// Calculate outstanding supply after position minting
|
|
|
|
|
uint256 outstandingSupply = _getOutstandingSupply();
|
2025-08-19 11:05:08 +02:00
|
|
|
outstandingSupply -= pulledKraiken;
|
2025-07-08 11:59:26 +02:00
|
|
|
outstandingSupply -= (outstandingSupply >= discoveryAmount) ? discoveryAmount : outstandingSupply;
|
2025-10-04 15:17:09 +02:00
|
|
|
|
feat: OptimizerV3 with direct 2D staking-to-LP parameter mapping
Core protocol changes for launch readiness:
- OptimizerV3: binary bear/bull mapping from (staking%, avgTax) — avoids
exploitable AW 30-90 kill zone. Bear: AS=30%, AW=100, CI=0, DD=0.3e18.
Bull: AS=100%, AW=20, CI=0, DD=1e18. UUPS upgradeable with __gap[48].
- Directional VWAP: only records prices on ETH inflow (buys), preventing
sell-side dilution of price memory
- Floor formula: unified max(scarcity, mirror, clamp) — VWAP mirror uses
distance from adjusted VWAP as floor distance, no branching
- PriceOracle (M-1 fix): correct fallback TWAP divisor (60000s, not 300s)
- Access control (M-2 fix): deployer-only guard on one-time setters
- Recenter rate limit (M-3 fix): 60-second cooldown for open recenters
- Safe fallback params: recenter() optimizer-failure defaults changed from
exploitable CI=50%/AW=50 to safe bear-mode CI=0/AW=100
- Recentered event for monitoring and indexing
- VERSION bump to 2, kraiken-lib COMPATIBLE_CONTRACT_VERSIONS updated
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:21:18 +00:00
|
|
|
// Floor placement: max of (scarcity, VWAP mirror, clamp) toward KRK-cheap side.
|
|
|
|
|
// VWAP mirror uses distance from VWAP as floor distance — during selling, price moves
|
|
|
|
|
// away from VWAP so floor retreats automatically. No sell-pressure detection needed.
|
2026-02-18 00:19:05 +01:00
|
|
|
(int24 vwapTick, bool isScarcity) = _computeFloorTickWithSignal(currentTick, floorEthBalance, outstandingSupply, token0isWeth, params);
|
|
|
|
|
|
|
|
|
|
// Emit ETH reserve event for indexers (Ponder, subgraphs)
|
|
|
|
|
{
|
|
|
|
|
uint256 vwapX96 = getAdjustedVWAP(params.capitalInefficiency);
|
|
|
|
|
if (isScarcity) {
|
|
|
|
|
emit EthScarcity(currentTick, floorEthBalance, outstandingSupply, vwapX96, vwapTick);
|
|
|
|
|
} else {
|
|
|
|
|
emit EthAbundance(currentTick, floorEthBalance, outstandingSupply, vwapX96, vwapTick);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-08 11:59:26 +02:00
|
|
|
|
|
|
|
|
// Normalize and create floor position
|
|
|
|
|
vwapTick = _clampToTickSpacing(vwapTick, TICK_SPACING);
|
2025-10-04 15:17:09 +02:00
|
|
|
int24 floorTick = _clampToTickSpacing(token0isWeth ? vwapTick + TICK_SPACING : vwapTick - TICK_SPACING, TICK_SPACING);
|
|
|
|
|
|
2025-07-18 19:37:30 +02:00
|
|
|
// Use planned floor ETH balance, but fallback to remaining if insufficient
|
|
|
|
|
uint256 remainingEthBalance = _getEthBalance();
|
|
|
|
|
uint256 actualFloorEthBalance = (remainingEthBalance >= floorEthBalance) ? floorEthBalance : remainingEthBalance;
|
2025-10-04 15:17:09 +02:00
|
|
|
|
2025-07-18 19:37:30 +02:00
|
|
|
uint128 liquidity;
|
2025-07-08 11:59:26 +02:00
|
|
|
if (token0isWeth) {
|
2025-09-23 11:46:57 +02:00
|
|
|
// floor leg sits entirely above current tick when WETH is token0, so budget is token0
|
2025-10-04 15:17:09 +02:00
|
|
|
liquidity =
|
|
|
|
|
LiquidityAmounts.getLiquidityForAmount0(TickMath.getSqrtRatioAtTick(vwapTick), TickMath.getSqrtRatioAtTick(floorTick), actualFloorEthBalance);
|
2025-07-15 11:46:25 +02:00
|
|
|
} else {
|
2025-10-04 15:17:09 +02:00
|
|
|
liquidity =
|
|
|
|
|
LiquidityAmounts.getLiquidityForAmount1(TickMath.getSqrtRatioAtTick(vwapTick), TickMath.getSqrtRatioAtTick(floorTick), actualFloorEthBalance);
|
2025-07-08 11:59:26 +02:00
|
|
|
}
|
2025-10-04 15:17:09 +02:00
|
|
|
|
2025-07-08 11:59:26 +02:00
|
|
|
_mintPosition(Stage.FLOOR, token0isWeth ? vwapTick : floorTick, token0isWeth ? floorTick : vwapTick, liquidity);
|
|
|
|
|
}
|
feat: OptimizerV3 with direct 2D staking-to-LP parameter mapping
Core protocol changes for launch readiness:
- OptimizerV3: binary bear/bull mapping from (staking%, avgTax) — avoids
exploitable AW 30-90 kill zone. Bear: AS=30%, AW=100, CI=0, DD=0.3e18.
Bull: AS=100%, AW=20, CI=0, DD=1e18. UUPS upgradeable with __gap[48].
- Directional VWAP: only records prices on ETH inflow (buys), preventing
sell-side dilution of price memory
- Floor formula: unified max(scarcity, mirror, clamp) — VWAP mirror uses
distance from adjusted VWAP as floor distance, no branching
- PriceOracle (M-1 fix): correct fallback TWAP divisor (60000s, not 300s)
- Access control (M-2 fix): deployer-only guard on one-time setters
- Recenter rate limit (M-3 fix): 60-second cooldown for open recenters
- Safe fallback params: recenter() optimizer-failure defaults changed from
exploitable CI=50%/AW=50 to safe bear-mode CI=0/AW=100
- Recentered event for monitoring and indexing
- VERSION bump to 2, kraiken-lib COMPATIBLE_CONTRACT_VERSIONS updated
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:21:18 +00:00
|
|
|
|
|
|
|
|
/// @notice Computes floor tick from three signals: scarcity, VWAP mirror, and anti-overlap clamp.
|
|
|
|
|
/// @dev Takes the one furthest into KRK-cheap territory (highest tick when token0isWeth, lowest when not).
|
2026-02-18 00:19:05 +01:00
|
|
|
/// @return floorTarget The computed floor tick
|
|
|
|
|
/// @return isScarcity True if scarcity signal dominated (ETH scarcity), false if mirror/clamp (ETH abundance)
|
|
|
|
|
function _computeFloorTickWithSignal(
|
feat: OptimizerV3 with direct 2D staking-to-LP parameter mapping
Core protocol changes for launch readiness:
- OptimizerV3: binary bear/bull mapping from (staking%, avgTax) — avoids
exploitable AW 30-90 kill zone. Bear: AS=30%, AW=100, CI=0, DD=0.3e18.
Bull: AS=100%, AW=20, CI=0, DD=1e18. UUPS upgradeable with __gap[48].
- Directional VWAP: only records prices on ETH inflow (buys), preventing
sell-side dilution of price memory
- Floor formula: unified max(scarcity, mirror, clamp) — VWAP mirror uses
distance from adjusted VWAP as floor distance, no branching
- PriceOracle (M-1 fix): correct fallback TWAP divisor (60000s, not 300s)
- Access control (M-2 fix): deployer-only guard on one-time setters
- Recenter rate limit (M-3 fix): 60-second cooldown for open recenters
- Safe fallback params: recenter() optimizer-failure defaults changed from
exploitable CI=50%/AW=50 to safe bear-mode CI=0/AW=100
- Recentered event for monitoring and indexing
- VERSION bump to 2, kraiken-lib COMPATIBLE_CONTRACT_VERSIONS updated
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:21:18 +00:00
|
|
|
int24 currentTick,
|
|
|
|
|
uint256 floorEthBalance,
|
|
|
|
|
uint256 outstandingSupply,
|
|
|
|
|
bool token0isWeth,
|
|
|
|
|
PositionParams memory params
|
|
|
|
|
)
|
|
|
|
|
internal
|
|
|
|
|
view
|
2026-02-18 00:19:05 +01:00
|
|
|
returns (int24 floorTarget, bool isScarcity)
|
feat: OptimizerV3 with direct 2D staking-to-LP parameter mapping
Core protocol changes for launch readiness:
- OptimizerV3: binary bear/bull mapping from (staking%, avgTax) — avoids
exploitable AW 30-90 kill zone. Bear: AS=30%, AW=100, CI=0, DD=0.3e18.
Bull: AS=100%, AW=20, CI=0, DD=1e18. UUPS upgradeable with __gap[48].
- Directional VWAP: only records prices on ETH inflow (buys), preventing
sell-side dilution of price memory
- Floor formula: unified max(scarcity, mirror, clamp) — VWAP mirror uses
distance from adjusted VWAP as floor distance, no branching
- PriceOracle (M-1 fix): correct fallback TWAP divisor (60000s, not 300s)
- Access control (M-2 fix): deployer-only guard on one-time setters
- Recenter rate limit (M-3 fix): 60-second cooldown for open recenters
- Safe fallback params: recenter() optimizer-failure defaults changed from
exploitable CI=50%/AW=50 to safe bear-mode CI=0/AW=100
- Recentered event for monitoring and indexing
- VERSION bump to 2, kraiken-lib COMPATIBLE_CONTRACT_VERSIONS updated
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:21:18 +00:00
|
|
|
{
|
|
|
|
|
// 1. Scarcity tick: at what price can our ETH buy back the adjusted supply?
|
|
|
|
|
uint256 balancedCapital = (7 * outstandingSupply / 10) + (outstandingSupply * params.capitalInefficiency / 10 ** 18);
|
|
|
|
|
int24 scarcityTick = currentTick;
|
|
|
|
|
if (outstandingSupply > 0 && floorEthBalance > 0) {
|
|
|
|
|
scarcityTick = _tickAtPrice(token0isWeth, balancedCapital, floorEthBalance);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. Mirror tick: VWAP distance mirrored to KRK-cheap side
|
|
|
|
|
// Uses adjusted VWAP (CI controls distance → CI is the risk lever).
|
|
|
|
|
int24 mirrorTick = currentTick;
|
|
|
|
|
{
|
|
|
|
|
uint256 vwapX96 = getAdjustedVWAP(params.capitalInefficiency);
|
|
|
|
|
if (vwapX96 > 0) {
|
|
|
|
|
int24 rawVwapTick = _tickAtPriceRatio(int128(int256(vwapX96 >> 32)));
|
|
|
|
|
rawVwapTick = token0isWeth ? -rawVwapTick : rawVwapTick;
|
|
|
|
|
int24 vwapDistance = currentTick - rawVwapTick;
|
|
|
|
|
if (vwapDistance < 0) vwapDistance = -vwapDistance;
|
|
|
|
|
mirrorTick = token0isWeth ? currentTick + vwapDistance : currentTick - vwapDistance;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. Clamp tick: minimum distance (anti-overlap with anchor)
|
|
|
|
|
int24 anchorSpacing = TICK_SPACING + (34 * int24(params.anchorWidth) * TICK_SPACING / 100);
|
|
|
|
|
int24 clampTick = token0isWeth ? currentTick + anchorSpacing : currentTick - anchorSpacing;
|
|
|
|
|
|
|
|
|
|
// Take the one furthest into KRK-cheap territory
|
2026-02-18 00:19:05 +01:00
|
|
|
// Track whether scarcity signal dominates (for event emission)
|
|
|
|
|
isScarcity = true;
|
feat: OptimizerV3 with direct 2D staking-to-LP parameter mapping
Core protocol changes for launch readiness:
- OptimizerV3: binary bear/bull mapping from (staking%, avgTax) — avoids
exploitable AW 30-90 kill zone. Bear: AS=30%, AW=100, CI=0, DD=0.3e18.
Bull: AS=100%, AW=20, CI=0, DD=1e18. UUPS upgradeable with __gap[48].
- Directional VWAP: only records prices on ETH inflow (buys), preventing
sell-side dilution of price memory
- Floor formula: unified max(scarcity, mirror, clamp) — VWAP mirror uses
distance from adjusted VWAP as floor distance, no branching
- PriceOracle (M-1 fix): correct fallback TWAP divisor (60000s, not 300s)
- Access control (M-2 fix): deployer-only guard on one-time setters
- Recenter rate limit (M-3 fix): 60-second cooldown for open recenters
- Safe fallback params: recenter() optimizer-failure defaults changed from
exploitable CI=50%/AW=50 to safe bear-mode CI=0/AW=100
- Recentered event for monitoring and indexing
- VERSION bump to 2, kraiken-lib COMPATIBLE_CONTRACT_VERSIONS updated
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:21:18 +00:00
|
|
|
if (token0isWeth) {
|
|
|
|
|
floorTarget = scarcityTick;
|
fix: Backtesting #5: Position tracking + P&L metrics (#319)
- Add PositionTracker.sol: tracks position lifecycle (open/close per
recenter), records tick ranges, liquidity, entry/exit blocks/timestamps,
token amounts (via LiquidityAmounts math), fees (proportional to
liquidity share), IL (LP exit value − HODL value at exit price), and
net P&L per position. Aggregates total fees, cumulative IL, net P&L,
rebalance count, Anchor time-in-range, and capital efficiency accumulators.
Logs with [TRACKER][TYPE] prefix; emits cumulative P&L every 500 blocks.
- Modify StrategyExecutor.sol: add IUniswapV3Pool + token0isWeth to
constructor (creates PositionTracker internally), call
tracker.notifyBlock() on every block for time-in-range, and call
tracker.recordRecenter() on each successful recenter. logSummary()
now delegates to tracker.logFinalSummary().
- Modify BacktestRunner.s.sol: pass sp.pool and token0isWeth to
StrategyExecutor constructor; log tracker address.
- forge fmt: reformat all backtesting scripts and affected src/test files
to project style (number_underscore=thousands, multiline_func_header=all).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 11:23:18 +00:00
|
|
|
if (mirrorTick > floorTarget) {
|
|
|
|
|
floorTarget = mirrorTick;
|
|
|
|
|
isScarcity = false;
|
|
|
|
|
}
|
|
|
|
|
if (clampTick > floorTarget) {
|
|
|
|
|
floorTarget = clampTick;
|
|
|
|
|
isScarcity = false;
|
|
|
|
|
}
|
feat: OptimizerV3 with direct 2D staking-to-LP parameter mapping
Core protocol changes for launch readiness:
- OptimizerV3: binary bear/bull mapping from (staking%, avgTax) — avoids
exploitable AW 30-90 kill zone. Bear: AS=30%, AW=100, CI=0, DD=0.3e18.
Bull: AS=100%, AW=20, CI=0, DD=1e18. UUPS upgradeable with __gap[48].
- Directional VWAP: only records prices on ETH inflow (buys), preventing
sell-side dilution of price memory
- Floor formula: unified max(scarcity, mirror, clamp) — VWAP mirror uses
distance from adjusted VWAP as floor distance, no branching
- PriceOracle (M-1 fix): correct fallback TWAP divisor (60000s, not 300s)
- Access control (M-2 fix): deployer-only guard on one-time setters
- Recenter rate limit (M-3 fix): 60-second cooldown for open recenters
- Safe fallback params: recenter() optimizer-failure defaults changed from
exploitable CI=50%/AW=50 to safe bear-mode CI=0/AW=100
- Recentered event for monitoring and indexing
- VERSION bump to 2, kraiken-lib COMPATIBLE_CONTRACT_VERSIONS updated
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:21:18 +00:00
|
|
|
} else {
|
|
|
|
|
floorTarget = scarcityTick;
|
fix: Backtesting #5: Position tracking + P&L metrics (#319)
- Add PositionTracker.sol: tracks position lifecycle (open/close per
recenter), records tick ranges, liquidity, entry/exit blocks/timestamps,
token amounts (via LiquidityAmounts math), fees (proportional to
liquidity share), IL (LP exit value − HODL value at exit price), and
net P&L per position. Aggregates total fees, cumulative IL, net P&L,
rebalance count, Anchor time-in-range, and capital efficiency accumulators.
Logs with [TRACKER][TYPE] prefix; emits cumulative P&L every 500 blocks.
- Modify StrategyExecutor.sol: add IUniswapV3Pool + token0isWeth to
constructor (creates PositionTracker internally), call
tracker.notifyBlock() on every block for time-in-range, and call
tracker.recordRecenter() on each successful recenter. logSummary()
now delegates to tracker.logFinalSummary().
- Modify BacktestRunner.s.sol: pass sp.pool and token0isWeth to
StrategyExecutor constructor; log tracker address.
- forge fmt: reformat all backtesting scripts and affected src/test files
to project style (number_underscore=thousands, multiline_func_header=all).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 11:23:18 +00:00
|
|
|
if (mirrorTick < floorTarget) {
|
|
|
|
|
floorTarget = mirrorTick;
|
|
|
|
|
isScarcity = false;
|
|
|
|
|
}
|
|
|
|
|
if (clampTick < floorTarget) {
|
|
|
|
|
floorTarget = clampTick;
|
|
|
|
|
isScarcity = false;
|
|
|
|
|
}
|
feat: OptimizerV3 with direct 2D staking-to-LP parameter mapping
Core protocol changes for launch readiness:
- OptimizerV3: binary bear/bull mapping from (staking%, avgTax) — avoids
exploitable AW 30-90 kill zone. Bear: AS=30%, AW=100, CI=0, DD=0.3e18.
Bull: AS=100%, AW=20, CI=0, DD=1e18. UUPS upgradeable with __gap[48].
- Directional VWAP: only records prices on ETH inflow (buys), preventing
sell-side dilution of price memory
- Floor formula: unified max(scarcity, mirror, clamp) — VWAP mirror uses
distance from adjusted VWAP as floor distance, no branching
- PriceOracle (M-1 fix): correct fallback TWAP divisor (60000s, not 300s)
- Access control (M-2 fix): deployer-only guard on one-time setters
- Recenter rate limit (M-3 fix): 60-second cooldown for open recenters
- Safe fallback params: recenter() optimizer-failure defaults changed from
exploitable CI=50%/AW=50 to safe bear-mode CI=0/AW=100
- Recentered event for monitoring and indexing
- VERSION bump to 2, kraiken-lib COMPATIBLE_CONTRACT_VERSIONS updated
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:21:18 +00:00
|
|
|
}
|
|
|
|
|
}
|
2025-09-23 11:46:57 +02:00
|
|
|
}
|