diff --git a/onchain/src/IOptimizer.sol b/onchain/src/IOptimizer.sol index 047bcdd..0a4d537 100644 --- a/onchain/src/IOptimizer.sol +++ b/onchain/src/IOptimizer.sol @@ -3,7 +3,10 @@ pragma solidity ^0.8.19; /** * @notice Dyadic rational input: mantissa × 2^(-shift). - * For shift == 0 (current usage via _toDyadic), value == mantissa. + * shift is reserved for future use and MUST be 0. All current + * Optimizer implementations (Optimizer, OptimizerV3, OptimizerV3Push3) + * require shift == 0 and revert otherwise. When shift == 0 (as + * produced by _toDyadic), value == mantissa. */ struct OptimizerInput { int256 mantissa; diff --git a/onchain/src/Optimizer.sol b/onchain/src/Optimizer.sol index 7c596e7..e520456 100644 --- a/onchain/src/Optimizer.sol +++ b/onchain/src/Optimizer.sol @@ -356,8 +356,10 @@ contract Optimizer is Initializable, UUPSUpgradeable, IOptimizer { virtual returns (uint256 capitalInefficiency, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth) { - // Guard against negative mantissa — uint256() cast silently wraps negatives. + // Guard against non-zero shift and negative mantissa. + // shift is reserved for future use; uint256() cast silently wraps negatives. for (uint256 k; k < 8; k++) { + require(inputs[k].shift == 0, "shift not yet supported"); require(inputs[k].mantissa >= 0, "negative mantissa"); } // Extract slots 0 and 1 (shift=0 assumed — mantissa IS the value) diff --git a/onchain/src/OptimizerV3.sol b/onchain/src/OptimizerV3.sol index 8fa995e..ecfc30e 100644 --- a/onchain/src/OptimizerV3.sol +++ b/onchain/src/OptimizerV3.sol @@ -22,6 +22,11 @@ contract OptimizerV3 is Optimizer { override returns (uint256 ci, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth) { + // Guard against non-zero shift (reserved for future use). + for (uint256 k; k < 8; k++) { + require(inputs[k].shift == 0, "shift not yet supported"); + } + // ── BEGIN TRANSPILER OUTPUT (optimizer_v3.push3) ── // Do NOT edit by hand — regenerate via: npx tsx tools/push3-transpiler/transpile-cli.ts diff --git a/onchain/test/Optimizer.t.sol b/onchain/test/Optimizer.t.sol index 18e0728..57daa62 100644 --- a/onchain/test/Optimizer.t.sol +++ b/onchain/test/Optimizer.t.sol @@ -359,6 +359,18 @@ contract OptimizerTest is Test { } } + /** + * @notice calculateParams reverts when any slot has shift != 0 + */ + function testCalculateParamsRevertsOnNonZeroShift() public { + for (uint256 k = 0; k < 8; k++) { + OptimizerInput[8] memory inputs; + inputs[k] = OptimizerInput({ mantissa: 0, shift: 1 }); + vm.expectRevert("shift not yet supported"); + optimizer.calculateParams(inputs); + } + } + /** * @notice Non-admin calling upgradeTo should revert with UnauthorizedAccount */ diff --git a/onchain/test/OptimizerV3Push3.t.sol b/onchain/test/OptimizerV3Push3.t.sol index bae7169..f606c41 100644 --- a/onchain/test/OptimizerV3Push3.t.sol +++ b/onchain/test/OptimizerV3Push3.t.sol @@ -224,6 +224,17 @@ contract OptimizerV3Push3Test is Test { push3.calculateParams(inp); } + // ---- Shift guard ---- + + function testNonZeroShiftReverts() public { + for (uint256 k = 0; k < 8; k++) { + OptimizerInput[8] memory inp; + inp[k] = OptimizerInput({mantissa: 0, shift: 1}); + vm.expectRevert("shift not yet supported"); + push3.calculateParams(inp); + } + } + // ---- Fuzz ---- function testFuzzNeverReverts(uint256 percentageStaked, uint256 averageTaxRate) public view {