fix: OptimizerV3Push3 as IOptimizer always returns bear defaults — integration risk (#1063)

- getLiquidityParams() now reverts with "OptimizerV3Push3: not for production use" instead
  of silently returning zeroed bear-mode defaults; LiquidityManager.recenter() already has
  a try/catch fallback so backtesting is unaffected
- Added @custom:experimental NatSpec annotation to the contract marking it as a transpiler
  harness / backtesting stub only
- DeployBase.sol now validates any pre-existing optimizer address by calling getLiquidityParams()
  and reverting if it fails, blocking accidental wiring of OptimizerV3Push3 as a live optimizer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
johba 2026-03-20 20:13:18 +00:00
parent 351d4813e6
commit 6f7b6c4254
2 changed files with 14 additions and 7 deletions

View file

@ -93,6 +93,10 @@ contract DeployBase is Script {
} else { } else {
optimizerAddress = optimizer; optimizerAddress = optimizer;
console.log("Using existing optimizer at:", optimizerAddress); console.log("Using existing optimizer at:", optimizerAddress);
// Guard: ensure the pre-existing optimizer is not a stub (e.g. OptimizerV3Push3).
// OptimizerV3Push3.getLiquidityParams() always reverts; a live optimizer must not.
(bool ok,) = optimizerAddress.staticcall(abi.encodeWithSignature("getLiquidityParams()"));
require(ok, "DeployBase: optimizer getLiquidityParams() reverted - stub or misconfigured address");
} }
// Deploy LiquidityManager // Deploy LiquidityManager

View file

@ -10,18 +10,21 @@ import { OptimizerV3Push3Lib } from "./OptimizerV3Push3Lib.sol";
* The actual calculation logic lives in OptimizerV3Push3Lib and is * The actual calculation logic lives in OptimizerV3Push3Lib and is
* shared with OptimizerV3 so that transpiler changes require only * shared with OptimizerV3 so that transpiler changes require only
* one edit point. * one edit point.
* @custom:experimental This contract is a transpiler harness / backtesting stub only.
* It must NOT be wired as the live optimizer in any production deployment.
* Use OptimizerV3 (UUPS proxy) for production.
*/ */
contract OptimizerV3Push3 is IOptimizer { contract OptimizerV3Push3 is IOptimizer {
/** /**
* @inheritdoc IOptimizer * @inheritdoc IOptimizer
* @dev Calls calculateParams with zeroed inputs (percentageStaked=0, averageTaxRate=0), * @dev Always reverts this contract is a transpiler harness, not a production optimizer.
* producing bear-mode defaults: (ci=0, anchorShare=0.3e18, anchorWidth=100, discoveryDepth=0.3e18). * Calling getLiquidityParams() on this stub is a deployment misconfiguration.
* This contract is a standalone transpiler output without access to on-chain stake data; * LiquidityManager.recenter() has a try/catch that falls back to bear-mode defaults,
* use OptimizerV3 (which inherits Optimizer) for a live deployment with real inputs. * so accidental wiring produces a permanent bear-mode lock rather than a silent failure.
* Use OptimizerV3 (which inherits Optimizer) for a live deployment with real inputs.
*/ */
function getLiquidityParams() external view returns (uint256 capitalInefficiency, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth) { function getLiquidityParams() external pure returns (uint256, uint256, uint24, uint256) {
OptimizerInput[8] memory inputs; revert("OptimizerV3Push3: not for production use");
return calculateParams(inputs);
} }
/** /**