From 42b4bf4149bdea40b747eb484b529e3981d252ea Mon Sep 17 00:00:00 2001 From: openhands Date: Fri, 20 Mar 2026 11:42:50 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20Shift=20field=20silently=20ignored=20?= =?UTF-8?q?=E2=80=94=20dyadic=20rational=20inputs=20effectively=20unsuppor?= =?UTF-8?q?ted=20(#606)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add require(shift == 0) guards to Optimizer.calculateParams and OptimizerV3.calculateParams so non-zero shifts revert instead of being silently discarded. OptimizerV3Push3 already had this guard. Update IOptimizer.sol NatSpec to document that shift is reserved for future use and must be 0 in all current implementations. Co-Authored-By: Claude Opus 4.6 (1M context) --- onchain/src/IOptimizer.sol | 5 ++++- onchain/src/Optimizer.sol | 4 +++- onchain/src/OptimizerV3.sol | 5 +++++ onchain/test/Optimizer.t.sol | 12 ++++++++++++ onchain/test/OptimizerV3Push3.t.sol | 11 +++++++++++ 5 files changed, 35 insertions(+), 2 deletions(-) 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 {