{
 "item": "v2-audit",
 "batch": "e01-metering",
 "summary": "V2 reproduces bit-exactly across two independent runs on seeds 10000..10127 (all 128 paired diffs exactly 0.0), but V2 itself hits OutOfGas in afterSwap 4 times (seeds 10043 x2, 10044 x1, 10057 x1), identically in both runs. Highest successful afterSwap gas is 243,554 of the 250,000 limit; afterInitialize peaks at 236,377. Under the rule 'any OOG invalidates its scored run', both V2 runs are invalid, so valid=false. The engine swallows afterSwap errors and keeps the previous fees (cfmm.rs update_fees), so edges are still produced; without the counters these OOGs would go unseen.",
 "code": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.24;\n\nimport {AMMStrategyBase} from \"./AMMStrategyBase.sol\";\nimport {TradeInfo} from \"./IAMMStrategy.sol\";\n\ncontract Strategy is AMMStrategyBase {\n    uint256 constant ELAPSED_CAP = 8;\n    uint256 constant SIGNAL_THRESHOLD = WAD / 650;\n    uint256 constant DIR_DECAY = 750000000000000000;\n    uint256 constant ACT_DECAY = 530000000000000000;\n    uint256 constant SIZE_DECAY = 530000000000000000;\n    uint256 constant TOX_DECAY = 910000000000000000;\n    uint256 constant SIGMA_DECAY = 690000000000000000;\n    uint256 constant LAMBDA_DECAY = 990000000000000000;\n    uint256 constant SIZE_BLEND_DECAY = 828000000000000000;\n    uint256 constant TOX_BLEND_DECAY = 51000000000000000;\n    uint256 constant ACT_BLEND_DECAY = 985000000000000000;\n    uint256 constant PHAT_ALPHA = 270000000000000000;\n    uint256 constant PHAT_ALPHA_RETAIL = 60000000000000000;\n    uint256 constant PHAT_ALPHA_SAME = 120000000000000000;\n    uint256 constant REVERT_U = 0;\n    uint256 constant DIR_IMPACT_MULT = 4;\n    uint256 constant GATE_SIGMA_MULT = 7200000000000000000;\n    uint256 constant MIN_GATE = 30000000000000000;\n    uint256 constant TRADE_TOX_BOOST = 3500 * BPS;\n    uint256 constant STALE_ATTRACT_FRAC = 1050000000000000000;\n    uint256 constant RET_CAP = WAD / 35;\n    uint256 constant TOX_CAP = WAD / 5;\n    uint256 constant TRADE_RATIO_CAP = WAD / 5;\n    uint256 constant LAMBDA_CAP = 1820000000000000000;\n    uint256 constant STEP_COUNT_CAP = 64;\n    uint256 constant BASE_FEE = 4 * BPS;\n    uint256 constant SIGMA_COEF = 200000000000000000;\n    uint256 constant LAMBDA_COEF = 12 * BPS;\n    uint256 constant FLOW_SIZE_COEF = 4842 * BPS;\n    uint256 constant TOX_COEF = 300 * BPS;\n    uint256 constant TOX_QUAD_COEF = 15000 * BPS;\n    uint256 constant ACT_COEF = 91843 * BPS;\n    uint256 constant DIR_COEF = 20 * BPS;\n    uint256 constant DIR_TOX_COEF = 100 * BPS;\n    uint256 constant STALE_DIR_COEF = 6850 * BPS;\n    uint256 constant SIGMA_TOX_COEF = 500 * BPS;\n    uint256 constant TAIL_KNEE = 600 * BPS;\n    uint256 constant TAIL_SLOPE_PROTECT = 930000000000000000;\n    uint256 constant TAIL_SLOPE_ATTRACT = 850000000000000000;\n\n    function afterInitialize(uint256 initialX, uint256 initialY) external override returns (uint256, uint256) {\n        slots[0] = BASE_FEE; slots[1] = BASE_FEE; slots[2] = 0; slots[3] = WAD; slots[4] = 0;\n        slots[5] = initialX > 0 ? wdiv(initialY, initialX) : 100 * WAD;\n        slots[6] = 950000000000000; slots[7] = 800000000000000000; slots[8] = 2000000000000000;\n        slots[13] = 902500000000; slots[14] = slots[5]; slots[15] = 0; slots[9] = 0; slots[10] = 0; slots[11] = 0; slots[12] = 0;\n        return (BASE_FEE, BASE_FEE);\n    }\n\n    function afterSwap(TradeInfo calldata trade) external override returns (uint256, uint256) {\n        uint256 prevBidFee = slots[0]; uint256 prevAskFee = slots[1]; uint256 lastTs = slots[2];\n        uint256 dirState = slots[3]; uint256 actEma = slots[4]; uint256 pHat = slots[5];\n        uint256 sigmaHat = slots[6]; uint256 lambdaHat = slots[7]; uint256 sizeHat = slots[8];\n        uint256 toxEma = slots[9]; uint256 stepTradeCount = slots[10];\n\n        uint256 sigmaForFast = sigmaHat;\n        uint256 fastHat = slots[14];\n        uint256 uncertainty = slots[13];\n        uint256 silent = trade.timestamp > lastTs ? trade.timestamp - lastTs : 0;\n        bool isNewStep = trade.timestamp > lastTs;\n        if (isNewStep) {\n            uint256 elapsedRaw = trade.timestamp - lastTs;\n            uint256 elapsed = elapsedRaw > ELAPSED_CAP ? ELAPSED_CAP : elapsedRaw;\n            dirState = _decayCentered(dirState, DIR_DECAY, elapsed);\n            actEma = wmul(actEma, _powWad(ACT_DECAY, elapsed));\n            sizeHat = wmul(sizeHat, _powWad(SIZE_DECAY, elapsed));\n            toxEma = wmul(toxEma, _powWad(TOX_DECAY, elapsed));\n            if (stepTradeCount > 0 && elapsedRaw > 0) {\n                uint256 lambdaInst = (stepTradeCount * WAD) / elapsedRaw;\n                if (lambdaInst > LAMBDA_CAP) lambdaInst = LAMBDA_CAP;\n                lambdaHat = wmul(lambdaHat, LAMBDA_DECAY) + wmul(lambdaInst, WAD - LAMBDA_DECAY);\n            }\n            uncertainty += wmul(sigmaForFast, sigmaForFast) * elapsedRaw;\n            if (uncertainty > 100000000000000) uncertainty = 100000000000000;\n            stepTradeCount = 0;\n        }\n\n        bool firstInStep = stepTradeCount == 0;\n        uint256 spot = trade.reserveX > 0 ? wdiv(trade.reserveY, trade.reserveX) : pHat;\n        if (pHat == 0) pHat = spot;\n\n        uint256 feeUsed = trade.isBuy ? prevBidFee : prevAskFee;\n        uint256 gamma = feeUsed < WAD ? WAD - feeUsed : 0;\n        uint256 pImplied;\n        if (gamma == 0) { pImplied = spot; }\n        else { pImplied = trade.isBuy ? wmul(spot, gamma) : wdiv(spot, gamma); }\n\n        // S: infer a no-trade interval center from the last post-trade spot.\n        uint256 silenceScale = WAD;\n        if (firstInStep && silent > 1) {\n            uint256 g = silent - 1;\n            if (g > 1) g = 1;\n            uint256 w = WAD - _powWad(800000000000000000, g);\n            uint256 s = slots[16];\n            if (s == 0) s = pHat;\n            uint256 c = (wmul(s, WAD - prevBidFee) + wdiv(s, WAD - prevAskFee)) / 2;\n            if (c >= pHat) pHat += wmul(w, c - pHat);\n            else pHat -= wmul(w, pHat - c);\n            silenceScale = WAD - wmul(300000000000000000, w);\n        }\n\n        {\n            uint256 ret = pHat > 0 ? wdiv(absDiff(pImplied, pHat), pHat) : 0;\n            uint256 alpha = firstInStep ? PHAT_ALPHA : PHAT_ALPHA_RETAIL;\n            uint256 prePhat = pHat;\n            bool secondPrint = !firstInStep && stepTradeCount == 1;\n            bool sameDir = false; bool oppDir = false;\n            if (secondPrint) {\n                bool up2 = pImplied >= pHat;\n                uint256 d1 = slots[12];\n                if (d1 != 0) { sameDir = (d1 == 1) == up2; oppDir = !sameDir; }\n                if (sameDir) alpha = PHAT_ALPHA_SAME;\n            }\n            if (firstInStep) { slots[11] = pHat; slots[12] = pImplied >= pHat ? 1 : 2; }\n            uint256 adaptiveGate = wmul(sigmaHat, GATE_SIGMA_MULT);\n            if (adaptiveGate < MIN_GATE) adaptiveGate = MIN_GATE;\n            alpha = wmul(alpha, silenceScale);\n            adaptiveGate = wmul(adaptiveGate, silenceScale);\n            if (ret <= adaptiveGate) { pHat = wmul(pHat, WAD - alpha) + wmul(pImplied, alpha); }\n            if (oppDir && REVERT_U > 0) {\n                uint256 pre = slots[11];\n                if (pre > pHat) pHat = pHat + wmul(pre - pHat, REVERT_U);\n                else pHat = pHat - wmul(pHat - pre, REVERT_U);\n            }\n            if (firstInStep) {\n                if (ret > RET_CAP) ret = RET_CAP;\n                sigmaHat = wmul(sigmaHat, SIGMA_DECAY) + wmul(ret, WAD - SIGMA_DECAY);\n            }\n        }\n\n        // Interior retail routing equalizes fee-adjusted marginal prices.\n        // Subsequent trades in a step cannot be the initial arbitrage trade.\n        if (!firstInStep && feeUsed > 30 * BPS && gamma > 0) {\n            uint256 normalSpot = trade.isBuy\n                ? wdiv(wmul(spot, gamma), WAD - 30 * BPS)\n                : wdiv(wmul(spot, WAD - 30 * BPS), gamma);\n            pHat = wmul(pHat, 90 * WAD / 100) + wmul(normalSpot, 10 * WAD / 100);\n        }\n\n        {\n            uint256 ret = fastHat > 0 ? wdiv(absDiff(pImplied, fastHat), fastHat) : 0;\n            uint256 alpha;\n            if (firstInStep) {\n                uint256 processSigma = sigmaForFast;\n                uint256 obsVar = uncertainty + wmul(processSigma, processSigma);\n                uint256 probArb;\n                uint256 z2 = wdiv(wmul(ret, ret), obsVar);\n                uint256 density = _powWad(wdiv(WAD, WAD + z2 / 16), 8);\n                uint256 sigmaObs = sqrt(obsVar * WAD);\n                uint256 odds = wmul(density, wdiv(6700000000000000, sigmaObs));\n                bool arbFlag = ret <= wmul(sigmaForFast, 1220000000000000000);\n                uint256 prior = arbFlag ? 650000000000000000 : 400000000000000000;\n                uint256 tr = trade.reserveY > 0 ? wdiv(trade.amountY, trade.reserveY) : 0;\n                if (tr > 3000000000000000) prior = wmul(prior, 6 * WAD / 10);\n                if (silent > 1) prior = wmul(prior, 8 * WAD / 10);\n                odds = wmul(odds, wdiv(prior, WAD - prior));\n                probArb = wdiv(odds, WAD + odds);\n                alpha = wmul(probArb, 600000000000000000) + wmul(WAD - probArb, 80000000000000000);\n                slots[15] = pImplied >= fastHat ? 1 : 2;\n                uncertainty = wmul(uncertainty, WAD - alpha);\n            } else {\n                if ((slots[15] == 1) == (pImplied >= fastHat)) alpha = PHAT_ALPHA_RETAIL;\n            }\n            uint256 gate = wmul(sigmaHat, GATE_SIGMA_MULT);\n            if (gate < MIN_GATE) gate = MIN_GATE;\n            if (ret <= gate) fastHat = wmul(fastHat, WAD - alpha) + wmul(pImplied, alpha);\n            slots[13] = uncertainty;\n        }\n\n        if (!firstInStep && feeUsed > 30 * BPS && gamma > 0) {\n            uint256 normalObs = trade.isBuy\n                ? wdiv(wmul(spot, gamma), WAD - 30 * BPS)\n                : wdiv(wmul(spot, WAD - 30 * BPS), gamma);\n            if ((slots[15] == 1) == (normalObs >= fastHat)) {\n                fastHat = wmul(fastHat, 9 * WAD / 10) + wmul(normalObs, WAD / 10);\n            }\n        }\n        slots[14] = fastHat;\n        uint256 tradeRatio = trade.reserveY > 0 ? wdiv(trade.amountY, trade.reserveY) : 0;\n        if (tradeRatio > TRADE_RATIO_CAP) tradeRatio = TRADE_RATIO_CAP;\n\n        if (tradeRatio > SIGNAL_THRESHOLD) {\n            uint256 push = tradeRatio * DIR_IMPACT_MULT;\n            if (push > WAD / 4) push = WAD / 4;\n            if (trade.isBuy) {\n                dirState = dirState + push;\n                if (dirState > 2 * WAD) dirState = 2 * WAD;\n            } else {\n                dirState = dirState > push ? dirState - push : 0;\n            }\n            actEma = wmul(actEma, ACT_BLEND_DECAY) + wmul(tradeRatio, WAD - ACT_BLEND_DECAY);\n            sizeHat = wmul(sizeHat, SIZE_BLEND_DECAY) + wmul(tradeRatio, WAD - SIZE_BLEND_DECAY);\n            if (sizeHat > WAD) sizeHat = WAD;\n        }\n\n        uint256 tox = pHat > 0 ? wdiv(absDiff(spot, pHat), pHat) : 0;\n        if (tox > TOX_CAP) tox = TOX_CAP;\n        toxEma = wmul(toxEma, TOX_BLEND_DECAY) + wmul(tox, WAD - TOX_BLEND_DECAY);\n        uint256 toxSignal = toxEma;\n\n        stepTradeCount = stepTradeCount + 1;\n        if (stepTradeCount > STEP_COUNT_CAP) stepTradeCount = STEP_COUNT_CAP;\n\n        uint256 flowSize = wmul(lambdaHat, sizeHat);\n        uint256 fBase = BASE_FEE + wmul(SIGMA_COEF, sigmaHat) + wmul(LAMBDA_COEF, lambdaHat) + wmul(FLOW_SIZE_COEF, flowSize);\n        uint256 fMid = fBase + wmul(TOX_COEF, toxSignal) + wmul(TOX_QUAD_COEF, wmul(toxSignal, toxSignal)) + wmul(ACT_COEF, actEma);\n        fMid = fMid + wmul(SIGMA_TOX_COEF, wmul(sigmaHat, toxSignal));\n\n        uint256 dirDev; bool sellPressure;\n        if (dirState >= WAD) { dirDev = dirState - WAD; sellPressure = true; }\n        else { dirDev = WAD - dirState; sellPressure = false; }\n\n        uint256 skew = wmul(DIR_COEF, dirDev) + wmul(DIR_TOX_COEF, wmul(dirDev, toxSignal));\n        uint256 bidFee; uint256 askFee;\n        if (sellPressure) {\n            bidFee = fMid + skew;\n            askFee = fMid > skew ? fMid - skew : 0;\n        } else {\n            askFee = fMid + skew;\n            bidFee = fMid > skew ? fMid - skew : 0;\n        }\n\n        {\n            uint256 staleHat = wmul(pHat, 700000000000000000) + wmul(fastHat, 300000000000000000);\n            uint256 staleTox = wdiv(absDiff(spot, staleHat), staleHat);\n            if (staleTox > TOX_CAP) staleTox = TOX_CAP;\n            uint256 staleShift = wmul(STALE_DIR_COEF, staleTox);\n            uint256 attractShift = wmul(staleShift, STALE_ATTRACT_FRAC);\n            if (spot >= staleHat) {\n                bidFee = bidFee + staleShift;\n                askFee = askFee > attractShift ? askFee - attractShift : 0;\n            } else {\n                askFee = askFee + staleShift;\n                bidFee = bidFee > attractShift ? bidFee - attractShift : 0;\n            }\n        }\n\n        {\n            bool tradeAligned = (trade.isBuy && spot >= pHat) || (!trade.isBuy && spot < pHat);\n            if (tradeAligned) {\n                uint256 tradeBoost = wmul(TRADE_TOX_BOOST, tradeRatio);\n                if (trade.isBuy) { bidFee = bidFee + tradeBoost; }\n                else { askFee = askFee + tradeBoost; }\n            }\n        }\n\n        if (sellPressure) {\n            bidFee = clampFee(_compressTailWithSlope(bidFee, TAIL_SLOPE_PROTECT));\n            askFee = clampFee(_compressTailWithSlope(askFee, TAIL_SLOPE_ATTRACT));\n        } else {\n            askFee = clampFee(_compressTailWithSlope(askFee, TAIL_SLOPE_PROTECT));\n            bidFee = clampFee(_compressTailWithSlope(bidFee, TAIL_SLOPE_ATTRACT));\n        }\n\n        slots[0] = bidFee; slots[1] = askFee; slots[2] = trade.timestamp;\n        slots[3] = dirState; slots[4] = actEma; slots[5] = pHat;\n        slots[6] = sigmaHat; slots[7] = lambdaHat; slots[8] = sizeHat;\n        slots[16] = spot;\n        slots[9] = toxEma; slots[10] = stepTradeCount;\n\n        return (bidFee, askFee);\n    }\n\n    function _compressTailWithSlope(uint256 fee, uint256 slope) internal pure returns (uint256) {\n        if (fee <= TAIL_KNEE) return fee;\n        return TAIL_KNEE + wmul(fee - TAIL_KNEE, slope);\n    }\n\n    function _powWad(uint256 factor, uint256 exp) internal pure returns (uint256 result) {\n        result = WAD;\n        while (exp > 0) {\n            if (exp & 1 == 1) result = wmul(result, factor);\n            factor = wmul(factor, factor);\n            exp >>= 1;\n        }\n    }\n\n    function _decayCentered(uint256 centered, uint256 decayFactor, uint256 elapsed) internal pure returns (uint256) {\n        uint256 mul = _powWad(decayFactor, elapsed);\n        if (centered >= WAD) { return WAD + wmul(centered - WAD, mul); }\n        uint256 below = wmul(WAD - centered, mul);\n        return below < WAD ? WAD - below : 0;\n    }\n\n    function getName() external pure override returns (string memory) { return \"no_cubic\"; }\n}\n",
 "sha256": "28417e116f57f1029fdd1803832e852574902c1ae7253c05657f317db76da4ab",
 "sha256_matches_brief": true,
 "paired": {
  "label": "training reproducibility (V2 run A minus independent V2 run B, same seeds); NOT a gain or confirmation estimate. Both runs contain OOG, so it is labelled invalid.",
  "invalid_due_to_oog": true,
  "n": 128,
  "mean_diff": 0.0,
  "se": 0.0,
  "sd": 0.0,
  "n_nonzero": 0,
  "max_abs_diff": 0.0,
  "worst": [
   {
    "seed": 10000,
    "diff": 0.0,
    "edge": 489.4875846678468,
    "v2_edge": 489.4875846678468
   },
   {
    "seed": 10001,
    "diff": 0.0,
    "edge": 552.3376592263003,
    "v2_edge": 552.3376592263003
   },
   {
    "seed": 10002,
    "diff": 0.0,
    "edge": 443.04632640588295,
    "v2_edge": 443.04632640588295
   },
   {
    "seed": 10003,
    "diff": 0.0,
    "edge": 651.7063161382114,
    "v2_edge": 651.7063161382114
   },
   {
    "seed": 10004,
    "diff": 0.0,
    "edge": 484.2388697905094,
    "v2_edge": 484.2388697905094
   }
  ],
  "note": "se = sample SD(diff)/sqrt(n). All diffs are 0.0, so the five 'worst' entries are simply the first five seeds (ties)."
 },
 "gas": {
  "max_ok": 243554,
  "max_ok_afterSwap": 243554,
  "max_ok_afterInitialize": 236377,
  "oog": 4,
  "oog_repeat_run": 4,
  "limit_afterSwap": 250000,
  "limit_afterInitialize": 250000,
  "oog_by_seed": [
   {
    "seed": 10043,
    "oog": 2,
    "afterSwap_calls": 10570,
    "max_ok_gas": 243554
   },
   {
    "seed": 10044,
    "oog": 1,
    "afterSwap_calls": 11010,
    "max_ok_gas": 243052
   },
   {
    "seed": 10057,
    "oog": 1,
    "afterSwap_calls": 9734,
    "max_ok_gas": 240786
   }
  ],
  "headroom_vs_limit": 6446,
  "accounting_note": "gas_used is revm's per-transaction gas_used, which includes the 21,000 intrinsic cost plus calldata cost, measured against the engine's tx gas_limit of 250,000. Per run: the Submission afterSwap had 1,313,471 calls (4 OOG); Baseline (Vanilla) afterSwap had 1,361,264 calls (0 OOG, max 22,592)."
 },
 "valid": false,
 "valid_reason": "Harness and method work. V2's own scored runs have 4 afterSwap OutOfGas halts each, and the brief says any OOG invalidates a result. A candidate built on V2's estimators therefore needs gas below V2's, not equal to it.",
 "blocked": null,
 "baseline_score": {
  "mean_v2_edge_seeds_10000_10127": 518.873107971501,
  "mean_vanilla_edge_same_markets": 354.6223822042462,
  "note": "Training-seed mean over 128 seeds, measured with the OOGs above present; not comparable to the board's 519.68 figure."
 },
 "coverage": {
  "seeds": "10000..10127 (128 seeds, all of them scored in both runs)",
  "n_steps": 10000,
  "callbacks_instrumented": [
   "afterInitialize",
   "afterSwap",
   "getName"
  ],
  "roles": [
   "Submission",
   "Baseline"
  ],
  "counters": [
   "calls",
   "ok",
   "max_ok_gas",
   "sum_ok_gas",
   "oog",
   "revert",
   "other_halt",
   "tx_error",
   "bad_return"
  ],
  "not_instrumented": "Contract deployment (CREATE, 10M gas limit, runs once per simulation; a failure there raises and aborts the batch)",
  "per_seed_edges_crosscheck": "Running each seed in its own run_batch (perseed.py) gives the same mean edge as the batch run: 518.873107971501"
 },
 "failure_propagation": {
  "afterSwap_error": "cfmm.rs update_fees swallows Err and keeps the current fees, so the simulation continues. The counters record OOG, revert, other halt, tx error and bad return, and any nonzero count in the Submission role marks the run invalid (the harness exits with code 2).",
  "afterInitialize_error": "Propagates as a SimulationError; run_batch raises RuntimeError('EVM error: Out of gas'). The harness catches it, records it in run.error, and sets valid=false with paired.n=0.",
  "stats_scope": "The counters are process-global and are reset before each run_batch call, so every run's counts belong to that run only. Each strategy flushes its counts when it is dropped, at the end of each simulation."
 },
 "diagnostic": {
  "description": "Forced-failure strategies run in a separate process on seeds 0..1 with n_steps=500, the same 250k limits and Vanilla as the opponent. None of this is part of any scored run.",
  "results": {
   "OOGSwap": {
    "valid": false,
    "error": "",
    "submission_errors": {
     "oog": 1224,
     "revert": 0,
     "other_halt": 0,
     "tx_error": 0,
     "bad_return": 0
    },
    "submission": {
     "afterInitialize": {
      "calls": 2,
      "ok": 2,
      "oog": 0,
      "revert": 0,
      "max_ok_gas": 21801
     },
     "afterSwap": {
      "calls": 1224,
      "ok": 0,
      "oog": 1224,
      "revert": 0,
      "max_ok_gas": 0
     },
     "getName": {
      "calls": 2,
      "ok": 2,
      "oog": 0,
      "revert": 0,
      "max_ok_gas": 21536
     }
    }
   },
   "RevertSwap": {
    "valid": false,
    "error": "",
    "submission_errors": {
     "oog": 0,
     "revert": 1224,
     "other_halt": 0,
     "tx_error": 0,
     "bad_return": 0
    },
    "submission": {
     "afterInitialize": {
      "calls": 2,
      "ok": 2,
      "oog": 0,
      "revert": 0,
      "max_ok_gas": 21796
     },
     "afterSwap": {
      "calls": 1224,
      "ok": 0,
      "oog": 0,
      "revert": 1224,
      "max_ok_gas": 0
     },
     "getName": {
      "calls": 2,
      "ok": 2,
      "oog": 0,
      "revert": 0,
      "max_ok_gas": 21532
     }
    }
   },
   "OOGInit": {
    "valid": false,
    "error": "RuntimeError: EVM error: Out of gas",
    "submission_errors": {
     "oog": 2,
     "revert": 0,
     "other_halt": 0,
     "tx_error": 0,
     "bad_return": 0
    },
    "submission": {
     "afterInitialize": {
      "calls": 2,
      "ok": 0,
      "oog": 2,
      "revert": 0,
      "max_ok_gas": 0
     },
     "getName": {
      "calls": 2,
      "ok": 2,
      "oog": 0,
      "revert": 0,
      "max_ok_gas": 21539
     }
    }
   },
   "n_steps": 500,
   "seeds": [
    0,
    1
   ],
   "checks": {
    "oog_swap_detected": true,
    "revert_swap_detected": true,
    "oog_init_detected": true
   }
  }
 },
 "config": {
  "simulator_repo": "https://github.com/benedictbrady/amm-challenge",
  "simulator_commit": "6b6921f8d463314aa7c96234e1be25a25245f704",
  "opponent": "contracts/src/VanillaStrategy.sol (30 bps)",
  "call": "amm_sim_rs.run_batch(list(V2_bytecode), list(vanilla_bytecode), configs, 8), one strategy per call, two separate calls",
  "configs": "MatchRunner(n_simulations=seed_end, config=build_base_config(seed=None), n_workers=8, variance=BASELINE_VARIANCE)._build_configs()[seed_start:seed_end]: default_rng(seed=i) draws uniform size(19,21), rate(0.6,1.0), sigma(0.000882,0.001008); cfg.seed=i; the engine seeds the price with seed and retail with seed+1",
  "base": {
   "n_steps": 10000,
   "initial_price": 100.0,
   "initial_x": 100.0,
   "initial_y": 10000.0,
   "gbm_mu": 0.0,
   "gbm_dt": 1.0,
   "retail_size_sigma": 1.2,
   "retail_buy_prob": 0.5
  },
  "compiler": {
   "solc": "0.8.24 (py-solc-x)",
   "optimizer": {
    "enabled": true,
    "runs": 200
   },
   "viaIR": true,
   "evmVersion": "paris",
   "via": "amm_competition.evm.compiler.SolidityCompiler (unchanged)",
   "note": "The artifact's own config lists optimizer_runs 20. This harness uses the challenge's own compiler settings (runs 200) as found at the pinned commit, so gas figures are for that bytecode."
  },
  "gas_limits_unchanged": {
   "GAS_LIMIT_INIT": 250000,
   "GAS_LIMIT_TRADE": 250000,
   "GAS_LIMIT_NAME": 50000
  },
  "engine_economics_unchanged": "The patch touches only evm/strategy.rs (counters) and lib.rs (two new pyfunctions, gas_stats and gas_stats_reset). Control flow and return values are unchanged.",
  "machine": "Linux x86_64, 6 cores; run_batch uses 8 workers"
 },
 "v2_derivation": {
  "source": "https://api.imd.fun/artifacts/1f4f14f040b309e004904672ec6e2bb051f97b8d1195602d36b35a54c05d6c0f field 'code' (artifact sha256 field a9830b41...; candidate 'B + S only')",
  "replacements": [
   [
    "if (g > 4) g = 4;",
    "if (g > 1) g = 1;"
   ],
   [
    "_powWad(925000000000000000, g)",
    "_powWad(800000000000000000, g)"
   ]
  ],
  "each_occurs_once": true,
  "validate": "amm-match validate scratch/V2.sol -> Strategy 'no_cubic' validated successfully!"
 },
 "source_files": [
  {
   "path": "scratch/setup.sh",
   "content": "set -eu;R=$PWD/scratch;mkdir -p $R/tmp $R/bin $R/solcx\necho \"export TMPDIR=$R/tmp XDG_CACHE_HOME=$R/xc UV_CACHE_DIR=$R/uvc UV_PYTHON_INSTALL_DIR=$R/uvpy RUSTUP_HOME=$R/rustup CARGO_HOME=$R/cargo SOLCX_BINARY_PATH=$R/solcx VIRTUAL_ENV=$PWD/.venv PATH=$PWD/.venv/bin:$R/bin:$R/cargo/bin:\\$PATH\" >$R/env.sh;. $R/env.sh\ncommand -v uv >/dev/null||{ curl -LsSf https://github.com/astral-sh/uv/releases/latest/download/uv-x86_64-unknown-linux-gnu.tar.gz|tar --no-same-owner -xz -C $R/tmp;cp $R/tmp/uv-*/uv $R/bin/;}\ncc --version >/dev/null 2>&1||{ curl -Ls https://micro.mamba.pm/api/micromamba/linux-64/latest -o $R/tmp/mm.tbz;python3 -c \"import sys,tarfile;tarfile.open(sys.argv[1]).extract('bin/micromamba',sys.argv[2])\" $R/tmp/mm.tbz $R;MAMBA_ROOT_PREFIX=$R/mamba $R/bin/micromamba create -yq -p $R/cenv -c conda-forge gcc;ln -sf $(ls $R/cenv/bin/*gcc|head -n1) $R/bin/cc;}\ncommand -v cargo >/dev/null||{ curl -sSf https://sh.rustup.rs|sh -s -- -y --profile minimal --no-modify-path;}\nuv venv --python 3.12 .venv;uv pip install -e . maturin\nmaturin develop --release --uv --manifest-path amm_sim_rs/Cargo.toml\namm-match validate contracts/src/StarterStrategy.sol\n"
  },
  {
   "path": "scratch/instrumentation.patch",
   "content": "diff --git a/amm_sim_rs/src/evm/strategy.rs b/amm_sim_rs/src/evm/strategy.rs\nindex 1d06af1..c7e48aa 100644\n--- a/amm_sim_rs/src/evm/strategy.rs\n+++ b/amm_sim_rs/src/evm/strategy.rs\n@@ -9,6 +9,46 @@ use revm::{\n };\n use thiserror::Error;\n \n+// ---- IMD gas instrumentation (audit only; does not change gas limits or economics) ----\n+use std::collections::BTreeMap;\n+use std::sync::Mutex;\n+\n+/// Per (role, callback) call statistics.\n+#[derive(Clone, Default, Debug)]\n+pub struct CallStats {\n+    pub calls: u64,\n+    pub ok: u64,\n+    pub max_ok_gas: u64,\n+    pub sum_ok_gas: u128,\n+    pub oog: u64,\n+    pub revert: u64,\n+    pub other_halt: u64,\n+    pub tx_error: u64,\n+    pub bad_return: u64,\n+}\n+\n+impl CallStats {\n+    fn merge(&mut self, o: &CallStats) {\n+        self.calls += o.calls; self.ok += o.ok; self.max_ok_gas = self.max_ok_gas.max(o.max_ok_gas);\n+        self.sum_ok_gas += o.sum_ok_gas; self.oog += o.oog; self.revert += o.revert;\n+        self.other_halt += o.other_halt; self.tx_error += o.tx_error; self.bad_return += o.bad_return;\n+    }\n+}\n+\n+pub static GAS_STATS: Mutex<BTreeMap<(String, String), CallStats>> = Mutex::new(BTreeMap::new());\n+\n+pub fn gas_stats_snapshot() -> Vec<(String, String, CallStats)> {\n+    GAS_STATS.lock().unwrap().iter().map(|((r, c), v)| (r.clone(), c.clone(), v.clone())).collect()\n+}\n+\n+pub fn gas_stats_reset() { GAS_STATS.lock().unwrap().clear(); }\n+\n+const CB_INIT: usize = 0;\n+const CB_SWAP: usize = 1;\n+const CB_NAME: usize = 2;\n+const CB_NAMES: [&str; 3] = [\"afterInitialize\", \"afterSwap\", \"getName\"];\n+// ---- end instrumentation header ----\n+\n use crate::types::trade_info::{encode_after_initialize, decode_fee_pair, TradeInfo, SELECTOR_GET_NAME};\n use crate::types::wad::Wad;\n \n@@ -56,16 +96,33 @@ pub struct EVMStrategy {\n     db: InMemoryDB,\n     /// Pre-allocated calldata buffer for after_swap (196 bytes)\n     trade_calldata: [u8; 196],\n+    /// Instrumentation: role label given at construction (\"Submission\"/\"Baseline\").\n+    role: String,\n+    /// Instrumentation: local per-callback stats, flushed to GAS_STATS on drop.\n+    stats: [CallStats; 3],\n+}\n+\n+impl Drop for EVMStrategy {\n+    fn drop(&mut self) {\n+        let mut g = GAS_STATS.lock().unwrap();\n+        for (i, st) in self.stats.iter().enumerate() {\n+            if st.calls > 0 {\n+                g.entry((self.role.clone(), CB_NAMES[i].to_string())).or_default().merge(st);\n+            }\n+        }\n+    }\n }\n \n impl EVMStrategy {\n     /// Create a new EVM strategy from compiled bytecode.\n     pub fn new(bytecode: Vec<u8>, default_name: String) -> Result<Self, EVMError> {\n         let mut strategy = Self {\n-            name: default_name,\n+            name: default_name.clone(),\n             bytecode: bytecode.clone(),\n             db: InMemoryDB::default(),\n             trade_calldata: [0u8; 196],\n+            role: default_name,\n+            stats: Default::default(),\n         };\n \n         strategy.deploy()?;\n@@ -137,7 +194,7 @@ impl EVMStrategy {\n \n     /// Fetch the strategy name from the contract.\n     fn fetch_name(&mut self) -> Result<(), EVMError> {\n-        let result = self.call(&SELECTOR_GET_NAME, GAS_LIMIT_NAME)?;\n+        let result = self.call(&SELECTOR_GET_NAME, GAS_LIMIT_NAME, CB_NAME)?;\n \n         // Decode string return value\n         // String is encoded as: offset (32 bytes) + length (32 bytes) + data\n@@ -166,10 +223,11 @@ impl EVMStrategy {\n     /// Returns (bid_fee, ask_fee) in WAD.\n     pub fn after_initialize(&mut self, initial_x: Wad, initial_y: Wad) -> Result<(Wad, Wad), EVMError> {\n         let calldata = encode_after_initialize(initial_x, initial_y);\n-        let result = self.call(&calldata, GAS_LIMIT_INIT)?;\n+        let result = self.call(&calldata, GAS_LIMIT_INIT, CB_INIT)?;\n \n-        decode_fee_pair(&result)\n-            .ok_or_else(|| EVMError::InvalidReturnData(\"Failed to decode fee pair\".into()))\n+        let d = decode_fee_pair(&result);\n+        if d.is_none() { self.stats[CB_INIT].bad_return += 1; }\n+        d.ok_or_else(|| EVMError::InvalidReturnData(\"Failed to decode fee pair\".into()))\n     }\n \n     /// Handle a trade event and return updated fees.\n@@ -182,10 +240,11 @@ impl EVMStrategy {\n \n         // Copy calldata to avoid borrow conflict\n         let calldata = self.trade_calldata;\n-        let result = self.call(&calldata, GAS_LIMIT_TRADE)?;\n+        let result = self.call(&calldata, GAS_LIMIT_TRADE, CB_SWAP)?;\n \n-        decode_fee_pair(&result)\n-            .ok_or_else(|| EVMError::InvalidReturnData(\"Failed to decode fee pair\".into()))\n+        let d = decode_fee_pair(&result);\n+        if d.is_none() { self.stats[CB_SWAP].bad_return += 1; }\n+        d.ok_or_else(|| EVMError::InvalidReturnData(\"Failed to decode fee pair\".into()))\n     }\n \n     /// Reset the strategy for a new simulation.\n@@ -194,7 +253,8 @@ impl EVMStrategy {\n     }\n \n     /// Make a call to the contract.\n-    fn call(&mut self, calldata: &[u8], gas_limit: u64) -> Result<Vec<u8>, EVMError> {\n+    fn call(&mut self, calldata: &[u8], gas_limit: u64, cb: usize) -> Result<Vec<u8>, EVMError> {\n+        self.stats[cb].calls += 1;\n         let mut evm = Evm::builder()\n             .with_db(&mut self.db)\n             .modify_tx_env(|tx| {\n@@ -206,11 +266,16 @@ impl EVMStrategy {\n             })\n             .build();\n \n-        let result = evm.transact_commit()\n-            .map_err(|e| EVMError::ExecutionFailed(format!(\"{:?}\", e)))?;\n+        let result = match evm.transact_commit() {\n+            Ok(r) => r,\n+            Err(e) => { self.stats[cb].tx_error += 1; return Err(EVMError::ExecutionFailed(format!(\"{:?}\", e))); }\n+        };\n \n         match result {\n-            ExecutionResult::Success { output, .. } => {\n+            ExecutionResult::Success { output, gas_used, .. } => {\n+                let st = &mut self.stats[cb];\n+                st.ok += 1; st.sum_ok_gas += gas_used as u128;\n+                if gas_used > st.max_ok_gas { st.max_ok_gas = gas_used; }\n                 match output {\n                     Output::Call(data) => Ok(data.to_vec()),\n                     Output::Create(_, _) => {\n@@ -219,12 +284,15 @@ impl EVMStrategy {\n                 }\n             }\n             ExecutionResult::Revert { output, .. } => {\n+                self.stats[cb].revert += 1;\n                 Err(EVMError::ExecutionFailed(format!(\"Reverted: {:?}\", output)))\n             }\n             ExecutionResult::Halt { reason, .. } => {\n                 if matches!(reason, revm::primitives::HaltReason::OutOfGas(_)) {\n+                    self.stats[cb].oog += 1;\n                     Err(EVMError::OutOfGas)\n                 } else {\n+                    self.stats[cb].other_halt += 1;\n                     Err(EVMError::ExecutionFailed(format!(\"Halted: {:?}\", reason)))\n                 }\n             }\ndiff --git a/amm_sim_rs/src/lib.rs b/amm_sim_rs/src/lib.rs\nindex 35d9e59..b3e66f9 100644\n--- a/amm_sim_rs/src/lib.rs\n+++ b/amm_sim_rs/src/lib.rs\n@@ -65,11 +65,35 @@ fn run_single(\n         .map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(e.to_string()))\n }\n \n+/// Instrumentation: snapshot of per-(role, callback) EVM call stats as dicts.\n+#[pyfunction]\n+fn gas_stats(py: Python<'_>) -> PyResult<Vec<PyObject>> {\n+    use pyo3::types::PyDict;\n+    let mut out = Vec::new();\n+    for (role, cb, st) in crate::evm::strategy::gas_stats_snapshot() {\n+        let d = PyDict::new_bound(py);\n+        d.set_item(\"role\", role)?; d.set_item(\"callback\", cb)?;\n+        d.set_item(\"calls\", st.calls)?; d.set_item(\"ok\", st.ok)?;\n+        d.set_item(\"max_ok_gas\", st.max_ok_gas)?; d.set_item(\"sum_ok_gas\", st.sum_ok_gas as u64)?;\n+        d.set_item(\"oog\", st.oog)?; d.set_item(\"revert\", st.revert)?;\n+        d.set_item(\"other_halt\", st.other_halt)?; d.set_item(\"tx_error\", st.tx_error)?;\n+        d.set_item(\"bad_return\", st.bad_return)?;\n+        out.push(d.into_any().unbind());\n+    }\n+    Ok(out)\n+}\n+\n+/// Instrumentation: clear stats.\n+#[pyfunction]\n+fn gas_stats_reset() { crate::evm::strategy::gas_stats_reset(); }\n+\n /// Python module definition\n #[pymodule]\n fn amm_sim_rs(m: &Bound<'_, PyModule>) -> PyResult<()> {\n     m.add_function(wrap_pyfunction!(run_batch, m)?)?;\n     m.add_function(wrap_pyfunction!(run_single, m)?)?;\n+    m.add_function(wrap_pyfunction!(gas_stats, m)?)?;\n+    m.add_function(wrap_pyfunction!(gas_stats_reset, m)?)?;\n     m.add_class::<SimulationConfig>()?;\n     m.add_class::<LightweightSimResult>()?;\n     m.add_class::<BatchSimulationResult>()?;\n"
  },
  {
   "path": "scratch/harness.py",
   "content": "\"\"\"V2 paired-edge / gas audit harness for amm-challenge @ 6b6921f8.\n\nUsage (from checkout root, after scratch/setup.sh and the instrumentation patch):\n  . scratch/env.sh && python scratch/harness.py STRATEGY.sol SEED_START SEED_END OUT.json [--other OTHER.sol]\n\nWithout --other: runs STRATEGY twice independently (reproducibility check).\nWith --other: runs STRATEGY and OTHER separately (each vs VanillaStrategy, never in one\nmarket) and reports paired diff = edge(STRATEGY) - edge(OTHER) per seed.\nEach run is one amm_sim_rs.run_batch(X, Vanilla, configs, 8) call; EVM gas stats are\nreset before and read after each run. Any OutOfGas / revert / halt / bad-return in the\nstrategy's callbacks marks the run invalid; an invalid run makes the result invalid.\n\"\"\"\nimport hashlib, json, math, sys, time\nfrom pathlib import Path\n\nimport amm_sim_rs\nfrom amm_competition.competition.config import (\n    BASELINE_SETTINGS, BASELINE_VARIANCE, build_base_config, resolve_n_workers)\nfrom amm_competition.competition.match import MatchRunner\nfrom amm_competition.evm.baseline import get_vanilla_bytecode_and_abi\nfrom amm_competition.evm.compiler import SolidityCompiler\n\nN_WORKERS = 8\nERR_KEYS = (\"oog\", \"revert\", \"other_halt\", \"tx_error\", \"bad_return\")\n\n\ndef build_configs(seed_start, seed_end):\n    \"\"\"Exactly MatchRunner._build_configs (default_rng(seed) draws size, rate, sigma; seed=i),\n    built for 0..seed_end-1 and sliced, so seed i gets the identical config the runner makes.\"\"\"\n    runner = MatchRunner(n_simulations=seed_end, config=build_base_config(seed=None),\n                         n_workers=N_WORKERS, variance=BASELINE_VARIANCE)\n    cfgs = runner._build_configs()[seed_start:seed_end]\n    assert [c.seed for c in cfgs] == list(range(seed_start, seed_end))\n    return cfgs\n\n\ndef compile_src(path):\n    src = Path(path).read_text()\n    t = time.time()\n    res = SolidityCompiler().compile(src)\n    dt = time.time() - t\n    if not res.success:\n        raise RuntimeError(\"compile failed: \" + \"; \".join(res.errors or []))\n    return src, list(res.bytecode), dt\n\n\ndef run(label, bytecode, vanilla, cfgs):\n    amm_sim_rs.gas_stats_reset()\n    t = time.time()\n    err = None\n    try:\n        br = amm_sim_rs.run_batch(bytecode, vanilla, cfgs, N_WORKERS)\n        edges = {r.seed: (r.edges[\"submission\"], r.edges[\"normalizer\"]) for r in br.results}\n    except Exception as e:  # e.g. afterInitialize OOG/revert propagates as error\n        err, edges = f\"{type(e).__name__}: {e}\", {}\n    dt = time.time() - t\n    stats = amm_sim_rs.gas_stats()\n    sub = [s for s in stats if s[\"role\"] == \"Submission\"]\n    errs = {k: sum(s[k] for s in sub) for k in ERR_KEYS}\n    swap = [s for s in sub if s[\"callback\"] == \"afterSwap\"]\n    valid = err is None and not any(errs.values()) and len(edges) == len(cfgs)\n    return {\"label\": label, \"seconds\": round(dt, 3), \"n_sims\": len(cfgs), \"error\": err,\n            \"valid\": valid, \"submission_errors\": errs,\n            \"max_ok_gas_afterSwap\": max((s[\"max_ok_gas\"] for s in swap), default=0),\n            \"max_ok_gas_all\": max((s[\"max_ok_gas\"] for s in sub), default=0),\n            \"stats\": stats}, edges\n\n\ndef paired(ea, eb, seeds):\n    rows = [(s, ea[s][0] - eb[s][0], ea[s][0], eb[s][0]) for s in seeds]\n    d = [r[1] for r in rows]\n    n = len(d)\n    m = sum(d) / n\n    sd = math.sqrt(sum((x - m) ** 2 for x in d) / (n - 1)) if n > 1 else 0.0\n    worst = sorted(rows, key=lambda r: r[1])[:5]\n    return {\"n\": n, \"mean_diff\": m, \"se\": sd / math.sqrt(n), \"sd\": sd,\n            \"n_nonzero\": sum(1 for x in d if x != 0.0), \"max_abs_diff\": max(abs(x) for x in d),\n            \"worst\": [{\"seed\": s, \"diff\": x, \"edge\": a, \"v2_edge\": b} for s, x, a, b in worst]}\n\n\ndef main():\n    a = sys.argv[1:]\n    other = None\n    if \"--other\" in a:\n        i = a.index(\"--other\"); other = a[i + 1]; del a[i:i + 2]\n    path, s0, s1, out = a[0], int(a[1]), int(a[2]), a[3]\n    src, bc, t_comp = compile_src(path)\n    vanilla = list(get_vanilla_bytecode_and_abi()[0])\n    cfgs = build_configs(s0, s1)\n    seeds = list(range(s0, s1))\n    ra, ea = run(\"A\", bc, vanilla, cfgs)\n    if other:\n        _, bc2, t2 = compile_src(other)\n        rb, eb = run(\"B\", bc2, vanilla, cfgs)\n    else:\n        rb, eb = run(\"A_repeat\", bc, vanilla, cfgs)\n    ok = ra[\"valid\"] and rb[\"valid\"]\n    res = {\"sha256\": hashlib.sha256(src.encode()).hexdigest(), \"seeds\": [s0, s1],\n           \"compile_seconds\": round(t_comp, 3), \"runs\": [ra, rb], \"valid\": ok,\n           # paired is computed whenever both runs completed all seeds; it is labelled\n           # invalid (valid=false) if any OOG/revert occurred, never silently dropped.\n           \"paired\": paired(ea, eb, seeds) if (len(ea) == len(eb) == len(seeds)) else {\"n\": 0},\n           \"mean_edge_A\": sum(ea[s][0] for s in seeds) / len(seeds) if ea else None,\n           \"mean_vanilla_edge_A\": sum(ea[s][1] for s in seeds) / len(seeds) if ea else None}\n    Path(out).write_text(json.dumps(res, indent=1))\n    print(json.dumps({k: v for k, v in res.items() if k != \"runs\"}, indent=1)[:3000])\n    for r in (ra, rb):\n        print(r[\"label\"], r[\"seconds\"], r[\"valid\"], r[\"submission_errors\"], r[\"max_ok_gas_all\"])\n    sys.exit(0 if ok else 2)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
   "path": "scratch/diag.py",
   "content": "\"\"\"Forced-failure diagnostic for the gas counters. Excluded from scored runs (separate\nprocess, seeds 0..1, n_steps=500 to bound cost; same 250k gas limits).\"\"\"\nimport json, sys\nsys.path.insert(0, \"scratch\")\nimport amm_sim_rs\nfrom harness import compile_src, run, build_configs\nfrom amm_competition.evm.baseline import get_vanilla_bytecode_and_abi\nvan = list(get_vanilla_bytecode_and_abi()[0])\ncfgs = build_configs(0, 2)\nfor c in cfgs: c.n_steps = 500\nout = {}\nfor name in (\"OOGSwap\", \"RevertSwap\", \"OOGInit\"):\n    _, bc, _ = compile_src(f\"scratch/diag/{name}.sol\")\n    r, e = run(name, bc, van, cfgs)\n    sub = {s[\"callback\"]: {k: s[k] for k in (\"calls\", \"ok\", \"oog\", \"revert\", \"max_ok_gas\")} for s in r[\"stats\"] if s[\"role\"] == \"Submission\"}\n    out[name] = {\"valid\": r[\"valid\"], \"error\": (r[\"error\"] or \"\")[:160], \"submission_errors\": r[\"submission_errors\"], \"submission\": sub}\nv2 = compile_src(\"scratch/V2.sol\")[2]\nout[\"n_steps\"] = 500; out[\"seeds\"] = [0, 1]\nout[\"checks\"] = {\n \"oog_swap_detected\": out[\"OOGSwap\"][\"submission\"][\"afterSwap\"][\"oog\"] == out[\"OOGSwap\"][\"submission\"][\"afterSwap\"][\"calls\"] > 0 and not out[\"OOGSwap\"][\"valid\"],\n \"revert_swap_detected\": out[\"RevertSwap\"][\"submission\"][\"afterSwap\"][\"revert\"] == out[\"RevertSwap\"][\"submission\"][\"afterSwap\"][\"calls\"] > 0 and out[\"RevertSwap\"][\"submission\"][\"afterSwap\"][\"oog\"] == 0 and not out[\"RevertSwap\"][\"valid\"],\n \"oog_init_detected\": out[\"OOGInit\"][\"submission\"][\"afterInitialize\"][\"oog\"] >= 1 and out[\"OOGInit\"][\"error\"] != \"\" and not out[\"OOGInit\"][\"valid\"],\n}\njson.dump(out, open(\"scratch/diag.json\", \"w\"), indent=1)\nprint(json.dumps(out, indent=1))\n"
  },
  {
   "path": "scratch/perseed.py",
   "content": "\"\"\"Per-seed OOG attribution: one run_batch per seed in its own worker process, so the\nprocess-global gas counters belong to exactly one simulation. Diagnostic only.\"\"\"\nimport json, sys\nfrom multiprocessing import get_context\nsys.path.insert(0, \"scratch\")\n\ndef one(args):\n    path, seed = args\n    import amm_sim_rs\n    from harness import compile_src, build_configs\n    from amm_competition.evm.baseline import get_vanilla_bytecode_and_abi\n    global _bc, _van\n    if \"_bc\" not in globals():\n        _bc = compile_src(path)[1]; _van = list(get_vanilla_bytecode_and_abi()[0])\n    cfg = build_configs(seed, seed + 1)\n    amm_sim_rs.gas_stats_reset()\n    r = amm_sim_rs.run_batch(_bc, _van, cfg, 1).results[0]\n    st = [s for s in amm_sim_rs.gas_stats() if s[\"role\"] == \"Submission\" and s[\"callback\"] == \"afterSwap\"][0]\n    return {\"seed\": seed, \"edge\": r.edges[\"submission\"], \"calls\": st[\"calls\"], \"oog\": st[\"oog\"], \"max_ok_gas\": st[\"max_ok_gas\"]}\n\nif __name__ == \"__main__\":\n    path, s0, s1, out = sys.argv[1], int(sys.argv[2]), int(sys.argv[3]), sys.argv[4]\n    with get_context(\"spawn\").Pool(6) as p:\n        rows = p.map(one, [(path, s) for s in range(s0, s1)], chunksize=4)\n    json.dump(rows, open(out, \"w\"))\n    bad = [r for r in rows if r[\"oog\"]]\n    print(\"oog seeds:\", bad); print(\"total oog\", sum(r[\"oog\"] for r in rows), \"max_ok\", max(r[\"max_ok_gas\"] for r in rows))\n"
  },
  {
   "path": "scratch/diag/OOGSwap.sol",
   "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.24;\nimport {AMMStrategyBase} from \"./AMMStrategyBase.sol\";\nimport {TradeInfo} from \"./IAMMStrategy.sol\";\ncontract Strategy is AMMStrategyBase {\n    function afterInitialize(uint256, uint256) external pure override returns (uint256, uint256) { return (30 * BPS, 30 * BPS); }\n    function afterSwap(TradeInfo calldata t) external pure override returns (uint256, uint256) {\n        uint256 x = t.timestamp;\n        while (true) { x = uint256(keccak256(abi.encode(x))); }\n        return (x, x);\n    }\n    function getName() external pure override returns (string memory) { return \"DiagOOGSwap\"; }\n}\n"
  },
  {
   "path": "scratch/diag/RevertSwap.sol",
   "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.24;\nimport {AMMStrategyBase} from \"./AMMStrategyBase.sol\";\nimport {TradeInfo} from \"./IAMMStrategy.sol\";\ncontract Strategy is AMMStrategyBase {\n    function afterInitialize(uint256, uint256) external pure override returns (uint256, uint256) { return (30 * BPS, 30 * BPS); }\n    function afterSwap(TradeInfo calldata t) external pure override returns (uint256, uint256) {\n        uint256 x = t.timestamp;\n        require(x == type(uint256).max, \"forced\");\n        return (x, x);\n    }\n    function getName() external pure override returns (string memory) { return \"DiagRevertSwap\"; }\n}\n"
  },
  {
   "path": "scratch/diag/OOGInit.sol",
   "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.24;\nimport {AMMStrategyBase} from \"./AMMStrategyBase.sol\";\nimport {TradeInfo} from \"./IAMMStrategy.sol\";\ncontract Strategy is AMMStrategyBase {\n    function afterInitialize(uint256 a, uint256) external pure override returns (uint256, uint256) {\n        uint256 x = a;\n        while (true) { x = uint256(keccak256(abi.encode(x))); }\n        return (x, x);\n    }\n    function afterSwap(TradeInfo calldata) external pure override returns (uint256, uint256) { return (30 * BPS, 30 * BPS); }\n    function getName() external pure override returns (string memory) { return \"DiagOOGInit\"; }\n}\n"
  }
 ],
 "commands": [
  "git clone https://github.com/benedictbrady/amm-challenge && cd amm-challenge && git checkout 6b6921f8d463314aa7c96234e1be25a25245f704",
  "sh scratch/setup.sh",
  "git apply scratch/instrumentation.patch && . scratch/env.sh && maturin develop --release --uv --manifest-path amm_sim_rs/Cargo.toml",
  ". scratch/env.sh && amm-match validate scratch/V2.sol",
  ". scratch/env.sh && python scratch/harness.py scratch/V2.sol 10000 10128 scratch/v2_repro.json   # exit 2 = an invalid run (here: OOG)",
  ". scratch/env.sh && python scratch/diag.py",
  ". scratch/env.sh && python scratch/perseed.py scratch/V2.sol 10000 10128 scratch/perseed.json",
  "candidate vs V2 (separate runs, paired by seed): . scratch/env.sh && python scratch/harness.py CANDIDATE.sol S0 S1 out.json --other scratch/V2.sol"
 ],
 "timing": {
  "setup_sh_wall_s": 73.7,
  "maturin_rebuild_after_patch_s": 28.0,
  "compile_v2_s": 2.297,
  "amm_match_validate_v2_s": 2.5,
  "run_128_seeds_s": [
   23.276,
   23.569
  ],
  "wall_s_per_sim": 0.183,
  "projection_2000_seed_candidate_plus_v2": {
   "sims": 4000,
   "est_wall_s": 737,
   "est_wall_min": 12.3,
   "basis": "this 6-core machine, 8 workers, linear in seed count"
  },
  "perseed_attribution_128_s": 35.0,
  "diagnostic_s": 4.2
 }
}