fix: feat: Push3 evolution — gas limit as fitness pressure (#637)
- 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 <noreply@anthropic.com>
This commit is contained in:
parent
f1ed0e4fdc
commit
c9c0ce5e95
2 changed files with 58 additions and 2 deletions
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue