From c9c0ce5e95144ef06442d80f3328145fe093fec5 Mon Sep 17 00:00:00 2001 From: openhands Date: Fri, 13 Mar 2026 00:25:49 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20feat:=20Push3=20evolution=20=E2=80=94=20?= =?UTF-8?q?gas=20limit=20as=20fitness=20pressure=20(#637)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Optimizer.getLiquidityParams() now forwards calculateParams through a staticcall capped at 200 000 gas. Programs that exceed the budget or revert fall back to bear defaults (CI=0, AS=30%, AW=100, DD=0.3e18), so a bloated evolved optimizer can never OOG-revert inside recenter(). - FitnessEvaluator.t.sol measures gas used by calculateParams against fixed representative inputs (50% staked, 5% avg tax) after each bootstrap. A soft penalty of GAS_PENALTY_FACTOR (1e13 wei/gas) is subtracted from total fitness before the JSON score line is emitted. Leaner programs win ties; gas_used is included in the output for observability. At ~15k gas (current seed) the penalty is ~1.5e17 wei; at the 200k hard cap boundary it reaches ~2e18 wei. Co-Authored-By: Claude Sonnet 4.6 --- onchain/src/Optimizer.sol | 28 ++++++++++++++++++++++++- onchain/test/FitnessEvaluator.t.sol | 32 ++++++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/onchain/src/Optimizer.sol b/onchain/src/Optimizer.sol index 54db899..e849323 100644 --- a/onchain/src/Optimizer.sol +++ b/onchain/src/Optimizer.sol @@ -131,6 +131,13 @@ contract Optimizer is Initializable, UUPSUpgradeable { lastRecenterTimestamp = block.timestamp; } + /// @dev Gas budget forwarded to calculateParams via staticcall. + /// Evolved programs that exceed this are treated as crashes — same outcome + /// as a revert — and getLiquidityParams() returns bear defaults instead. + /// 200 000 gives ~13x headroom over the current seed (~15 k gas) while + /// keeping unbounded growth from ever blocking recenter(). + uint256 internal constant CALCULATE_PARAMS_GAS_LIMIT = 200_000; + // ---- Dyadic rational helpers ---- /** @@ -141,6 +148,18 @@ contract Optimizer is Initializable, UUPSUpgradeable { return OptimizerInput({mantissa: value, shift: 0}); } + /** + * @notice Safe bear-mode defaults returned when calculateParams exceeds its + * gas budget or reverts. Mirrors the catch block in LiquidityManager. + */ + function _bearDefaults() + internal + pure + returns (uint256 capitalInefficiency, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth) + { + return (0, 3e17, 100, 3e17); + } + // ---- Core computation ---- /** @@ -321,6 +340,13 @@ contract Optimizer is Initializable, UUPSUpgradeable { // Slots 6-7: 0 (future) - return calculateParams(inputs); + // Call calculateParams with a fixed gas budget. Evolved programs that grow + // too large hit the cap and fall back to bear defaults — preventing any + // buggy or bloated optimizer from blocking recenter() with an OOG revert. + (bool ok, bytes memory ret) = address(this).staticcall{gas: CALCULATE_PARAMS_GAS_LIMIT}( + abi.encodeCall(this.calculateParams, (inputs)) + ); + if (!ok) return _bearDefaults(); + return abi.decode(ret, (uint256, uint256, uint24, uint256)); } } diff --git a/onchain/test/FitnessEvaluator.t.sol b/onchain/test/FitnessEvaluator.t.sol index 7382231..e726872 100644 --- a/onchain/test/FitnessEvaluator.t.sol +++ b/onchain/test/FitnessEvaluator.t.sol @@ -36,6 +36,7 @@ import "forge-std/Test.sol"; import { Kraiken } from "../src/Kraiken.sol"; import { Stake } from "../src/Stake.sol"; import { Optimizer } from "../src/Optimizer.sol"; +import { OptimizerInput } from "../src/IOptimizer.sol"; import { LiquidityManager } from "../src/LiquidityManager.sol"; import { ERC1967Proxy } from "@openzeppelin/proxy/ERC1967/ERC1967Proxy.sol"; import { UUPSUpgradeable } from "@openzeppelin/proxy/utils/UUPSUpgradeable.sol"; @@ -147,6 +148,13 @@ contract FitnessEvaluator is Test { /// Chosen to be deterministic and not collide with real Base addresses. address internal constant IMPL_SLOT = address(uint160(uint256(keccak256("fitness.impl.slot")))); + /// @dev Soft gas penalty: wei deducted from fitness per gas unit used by calculateParams. + /// Creates selection pressure toward leaner programs while keeping gas as a + /// secondary criterion (ETH retention still dominates). + /// At 15 k gas (current seed): ~1.5e17 wei penalty. + /// At 200 k gas (hard cap boundary): ~2e18 wei penalty. + uint256 internal constant GAS_PENALTY_FACTOR = 1e13; + // ─── Anvil test accounts (deterministic mnemonic) ──────────────────────── /// @dev Account 8 — adversary (10 000 ETH in Anvil; funded via vm.deal here) @@ -242,6 +250,16 @@ contract FitnessEvaluator is Test { continue; } + // Measure gas used by calculateParams with fixed representative inputs. + // Fixed inputs ensure fair, reproducible comparison across all candidates. + // Uses slot 0 = 50% staked, slot 1 = 5% avg tax rate; remaining slots = 0. + OptimizerInput[8] memory sampleInputs; + sampleInputs[0] = OptimizerInput({ mantissa: 5e17, shift: 0 }); + sampleInputs[1] = OptimizerInput({ mantissa: 5e16, shift: 0 }); + uint256 gasBefore = gasleft(); + try Optimizer(optProxy).calculateParams(sampleInputs) returns (uint256, uint256, uint24, uint256) { } catch { } + uint256 gasForCalcParams = gasBefore - gasleft(); + // Score: sum lm_eth_total across all attack sequences. uint256 totalFitness = 0; Vm.DirEntry[] memory entries = vm.readDir(attacksDir); @@ -254,8 +272,20 @@ contract FitnessEvaluator is Test { vm.revertTo(atkSnap); } + // Apply soft gas penalty: fitness = score - (gasUsed * GAS_PENALTY_FACTOR). + // Leaner programs win ties; programs at the hard-cap boundary incur ~2 ETH penalty. + uint256 gasPenalty = gasForCalcParams * GAS_PENALTY_FACTOR; + uint256 adjustedFitness = totalFitness > gasPenalty ? totalFitness - gasPenalty : 0; + // Emit score as a JSON line (parsed by batch-eval.sh). - console.log(string.concat('{"candidate_id":"', candidateId, '","fitness":', _uint2str(totalFitness), "}")); + console.log( + string.concat( + '{"candidate_id":"', candidateId, + '","fitness":', _uint2str(adjustedFitness), + ',"gas_used":', _uint2str(gasForCalcParams), + "}" + ) + ); } // Close manifest files.