{"item":"control","batch":"e04-harness-repair","summary":"Corrected harness built and run as a control. V2 run twice independently vs Vanilla on seeds 10000..10255 (256 seeds, 10000 steps, run_batch(V2, Vanilla, configs, 8)): every edge bit-identical (total, retail, arb, both roles), paired mean_diff 0.0, se 0.0; V2 OOG 7 in each run (afterSwap only; seeds 10043:2, 10044:1, 10057:1, 10150:2, 10231:1), matching the prior result of 7; Vanilla 0 failures; no reverts/tx errors/bad returns/other halts. Under the paired OOG rule the control is valid. retail+arb reconciles with total to 7.7e-12 per seed (max). No discrepancies found; no confirmation seeds used.","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,"audit":{"artifact":"https://api.imd.fun/artifacts/02c134d9ed51858f06d02b6dec25e6922016490f11e3a8174af512a6a77b6569","code_is_exact_v2":true,"check":"1f4f14f0... code with the two replacements (each occurs once) is byte-equal to the audited code; both hash to 28417e11...","source_files_is_list_of_path_content":true,"extracted":["scratch/setup.sh (byte-identical to the brief's script, unchanged)","scratch/instrumentation.patch (base for the corrected patch)","scratch/harness.py (replaced)"],"old_harness_defects_fixed":["validity: any OOG anywhere invalidated the run -> now paired rule: candidate OOG <= V2 OOG on the same seeds; revert/tx_error/other_halt/bad_return in either run, any Vanilla failure, or incomplete seeds still invalidate","build_configs: built MatchRunner configs for 0..seed_end and sliced (allocation scales with seed value, ~4M configs for the confirmation block) -> direct per-seed default_rng(seed) draws then build_config","only process-global counters per run -> per-seed per-role per-callback counters returned on each result, cross-checked equal to the global counters","no retail/arb split -> exact per-trade split from the engine"],"attribution_source":"engine.rs and types/result.rs hunks of https://api.imd.fun/artifacts/27b38b4fd30bae05991e8f1925f83d2fe75ec097a7a93d3ac0af270cce06d5ad: retail_edges += trade_edge at the same place edges is updated; arb_edges -= arb_result.profit. Its competing gas tuple (strategy.gas, cfmm.gas_stats, result.gas_stats) and realised_sigma were NOT copied; per-seed gas comes from the existing CallStats instrumentation via a new EVMStrategy::call_stats / CFMM::call_stats."},"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","sha256":"8c5c335305666ad768d7ebbeeb6467c545c69d1079b8c32fe8c93479123a161d"},{"path":"scratch/instrumentation.patch","content":"diff --git a/amm_sim_rs/src/amm/cfmm.rs b/amm_sim_rs/src/amm/cfmm.rs\nindex 02e6645..def0e06 100644\n--- a/amm_sim_rs/src/amm/cfmm.rs\n+++ b/amm_sim_rs/src/amm/cfmm.rs\n@@ -84,6 +84,9 @@ impl CFMM {\n         Ok(())\n     }\n \n+    /// Instrumentation: per-callback EVM call counters of this AMM's strategy.\n+    pub fn call_stats(&self) -> std::collections::HashMap<String, Vec<u64>> { self.strategy.call_stats() }\n+\n     /// Get current reserves.\n     pub fn reserves(&self) -> (f64, f64) {\n         (self.reserve_x, self.reserve_y)\ndiff --git a/amm_sim_rs/src/evm/strategy.rs b/amm_sim_rs/src/evm/strategy.rs\nindex 1d06af1..ff75a2c 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,19 @@ 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+        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-        decode_fee_pair(&result)\n-            .ok_or_else(|| EVMError::InvalidReturnData(\"Failed to decode fee pair\".into()))\n+    /// Instrumentation: this instance's per-callback counters\n+    /// [calls, ok, max_ok_gas, sum_ok_gas, oog, revert, other_halt, tx_error, bad_return].\n+    pub fn call_stats(&self) -> std::collections::HashMap<String, Vec<u64>> {\n+        self.stats.iter().enumerate().map(|(i, s)| (CB_NAMES[i].to_string(), vec![\n+            s.calls, s.ok, s.max_ok_gas, s.sum_ok_gas as u64, s.oog, s.revert,\n+            s.other_halt, s.tx_error, s.bad_return])).collect()\n     }\n \n     /// Reset the strategy for a new simulation.\n@@ -194,7 +261,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 +274,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 +292,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>()?;\ndiff --git a/amm_sim_rs/src/simulation/engine.rs b/amm_sim_rs/src/simulation/engine.rs\nindex 6834c43..2c55852 100644\n--- a/amm_sim_rs/src/simulation/engine.rs\n+++ b/amm_sim_rs/src/simulation/engine.rs\n@@ -113,6 +113,9 @@ impl SimulationEngine {\n         edges.insert(submission_name.clone(), 0.0);\n         edges.insert(baseline_name.clone(), 0.0);\n \n+        let mut retail_edges = edges.clone();\n+        let mut arb_edges = edges.clone();\n+\n         // Run simulation steps\n         let mut steps = Vec::with_capacity(self.config.n_steps as usize);\n \n@@ -141,6 +144,7 @@ impl SimulationEngine {\n             for amm in amms.iter_mut() {\n                 if let Some(arb_result) = arbitrageur.execute_arb(amm, fair_price, t as u64) {\n                     *arb_volume_y.get_mut(&arb_result.amm_name).unwrap() += arb_result.amount_y;\n+                    *arb_edges.get_mut(&arb_result.amm_name).unwrap() -= arb_result.profit;\n                     let entry = edges.entry(arb_result.amm_name).or_insert(0.0);\n                     // AMM edge is the negative of arbitrageur profit at true price\n                     *entry += -arb_result.profit;\n@@ -157,6 +161,7 @@ impl SimulationEngine {\n                 } else {\n                     trade.amount_y - trade.amount_x * fair_price\n                 };\n+                *retail_edges.get_mut(&trade.amm_name).unwrap() += trade_edge;\n                 let entry = edges.entry(trade.amm_name).or_insert(0.0);\n                 *entry += trade_edge;\n             }\n@@ -205,6 +210,9 @@ impl SimulationEngine {\n         }\n \n         Ok(LightweightSimResult {\n+            retail_edges,\n+            arb_edges,\n+            call_stats: amms.iter().map(|amm| (amm.name.clone(), amm.call_stats())).collect(),\n             seed,\n             strategies: vec![submission_name, baseline_name],\n             pnl,\ndiff --git a/amm_sim_rs/src/types/result.rs b/amm_sim_rs/src/types/result.rs\nindex 0257335..bb80a94 100644\n--- a/amm_sim_rs/src/types/result.rs\n+++ b/amm_sim_rs/src/types/result.rs\n@@ -58,6 +58,16 @@ pub struct LightweightSimResult {\n     #[pyo3(get)]\n     pub edges: HashMap<String, f64>,\n \n+    /// Instrumentation: retail-only and arbitrage-only parts of edges (same terms, split).\n+    #[pyo3(get)]\n+    pub retail_edges: HashMap<String, f64>,\n+    #[pyo3(get)]\n+    pub arb_edges: HashMap<String, f64>,\n+    /// Instrumentation: amm name -> callback -> [calls, ok, max_ok_gas, sum_ok_gas, oog,\n+    /// revert, other_halt, tx_error, bad_return] for this simulation's strategy instance.\n+    #[pyo3(get)]\n+    pub call_stats: HashMap<String, HashMap<String, Vec<u64>>>,\n+\n     /// Initial fair price\n     #[pyo3(get)]\n     pub initial_fair_price: f64,\n","sha256":"2e2e6bf198b5fd8d0e7b8fde7c0c21431dc1b1dddf83b371a74d3613eb1f1b04"},{"path":"scratch/harness.py","content":"\"\"\"Corrected paired-edge / gas harness for amm-challenge @ 6b6921f8 (e04-harness-repair/control).\n\nUsage (from checkout root, after scratch/setup.sh, git apply scratch/instrumentation.patch and\na release rebuild):\n  . scratch/env.sh && python scratch/harness.py CANDIDATE.sol S0 S1 OUT.json --ref V2.sol\n  . scratch/env.sh && python scratch/harness.py V2.sol S0 S1 OUT.json --control\n\nSeeds are the half-open range S0..S1-1. Each strategy is measured on its own:\namm_sim_rs.run_batch(X, VanillaStrategy, configs, 8), never two strategies in one market.\n--ref: run A = CANDIDATE, run B = REF (V2); paired diff = edge_A - edge_B per seed.\n--control: run A and run B are two independent runs of the same strategy (expect diff 0).\n\nValidity (replaces the old \"any OOG is invalid\" rule):\n  * every requested seed returns exactly once, with finite edges, in both runs;\n  * no revert / tx_error / other_halt / bad_return in any role or callback, in either run;\n  * Vanilla (Baseline role) has no failure of any kind, OOG included;\n  * candidate OutOfGas count <= reference OutOfGas count on the same seeds (V2 itself has\n    rare OOG halts, so the reference's OOG alone never invalidates a result).\nGas limits (250000 init/swap, 50000 name) and the engine's keep-previous-fees fallback on a\nfailed afterSwap are unchanged; failures are only counted.\nExit 0 = valid, 2 = invalid, 1 = runtime error (exception before a result could be written).\n\"\"\"\nimport hashlib, json, math, sys, time\nfrom pathlib import Path\n\nimport numpy as np\nimport amm_sim_rs\nfrom amm_competition.competition.config import BASELINE_VARIANCE as V, build_config\nfrom amm_competition.evm.baseline import get_vanilla_bytecode_and_abi\nfrom amm_competition.evm.compiler import SolidityCompiler\n\nN_WORKERS = 8\nCHUNK = 64  # seeds per run_batch call (bounds memory; each call is still run_batch(X, Vanilla, cfgs, 8))\nCALLBACKS = (\"afterInitialize\", \"afterSwap\", \"getName\")\nFIELDS = (\"calls\", \"ok\", \"max_ok_gas\", \"sum_ok_gas\", \"oog\", \"revert\", \"other_halt\", \"tx_error\", \"bad_return\")\nFAIL_KEYS = (\"revert\", \"other_halt\", \"tx_error\", \"bad_return\")\nROLES = {\"submission\": \"Submission\", \"normalizer\": \"Baseline\"}  # result name -> EVMStrategy role\n\n\ndef config_for_seed(seed):\n    \"\"\"MatchRunner._build_configs for one seed, drawn directly: default_rng(seed) then size, rate,\n    sigma in that order. The engine uses seed for the price process and seed+1 for retail.\"\"\"\n    rng = np.random.default_rng(seed=seed)\n    size = rng.uniform(V.retail_mean_size_min, V.retail_mean_size_max)\n    rate = rng.uniform(V.retail_arrival_rate_min, V.retail_arrival_rate_max)\n    sigma = rng.uniform(V.gbm_sigma_min, V.gbm_sigma_max)\n    return build_config(seed=seed, gbm_sigma=sigma, retail_arrival_rate=rate, retail_mean_size=size)\n\n\ndef build_configs(seed_start, seed_end):\n    \"\"\"Configs for seeds seed_start..seed_end-1 only; work and memory scale with the count.\"\"\"\n    return [config_for_seed(s) for s in range(seed_start, seed_end)]\n\n\ndef compile_src(path):\n    src = Path(path).read_text()\n    res = SolidityCompiler().compile(src)\n    if not res.success:\n        raise RuntimeError(\"compile failed: \" + \"; \".join(res.errors or []))\n    return src, list(res.bytecode)\n\n\ndef _zero():\n    return {k: 0 for k in FIELDS}\n\n\ndef _add(acc, st):\n    for k in FIELDS:\n        acc[k] = max(acc[k], st[k]) if k == \"max_ok_gas\" else acc[k] + st[k]\n\n\ndef run(label, bytecode, vanilla, cfgs, chunk=CHUNK):\n    \"\"\"One strategy vs Vanilla over cfgs. Returns (summary, rows by seed).\"\"\"\n    t = time.time()\n    rows, err = {}, None\n    glob = {(r, c): _zero() for r in ROLES.values() for c in CALLBACKS}\n    for i in range(0, len(cfgs), chunk):\n        part = cfgs[i:i + chunk]\n        amm_sim_rs.gas_stats_reset()\n        try:\n            br = amm_sim_rs.run_batch(bytecode, vanilla, part, N_WORKERS)\n        except Exception as e:  # e.g. afterInitialize OOG/revert propagates as SimulationError\n            err = f\"{type(e).__name__}: {e} (chunk seeds {part[0].seed}..{part[-1].seed})\"\n            br = None\n        for s in amm_sim_rs.gas_stats():  # process-global, includes strategies of failed sims\n            _add(glob[(s[\"role\"], s[\"callback\"])], s)\n        if br is None:\n            break\n        for r in br.results:\n            row = {\"seed\": r.seed, \"dup\": r.seed in rows}\n            for name in ROLES:\n                row[name] = {\"total\": r.edges[name], \"retail\": r.retail_edges[name], \"arb\": r.arb_edges[name],\n                             \"stats\": {cb: dict(zip(FIELDS, r.call_stats[name][cb])) for cb in CALLBACKS}}\n            rows[r.seed] = row\n        del br\n    dt = time.time() - t\n    # per-seed counters (exact attribution) summed, cross-checked against the global counters\n    per_seed = {(ROLES[n], cb): _zero() for n in ROLES for cb in CALLBACKS}\n    for row in rows.values():\n        for n in ROLES:\n            for cb in CALLBACKS:\n                _add(per_seed[(ROLES[n], cb)], row[n][\"stats\"][cb])\n    counters_agree = err is None and all(per_seed[k] == glob[k] for k in glob)\n    gas = {}\n    for n, role in ROLES.items():\n        g = {cb: dict(glob[(role, cb)]) for cb in CALLBACKS}\n        gas[n] = {\"by_callback\": g,\n                  \"max_ok\": max(g[cb][\"max_ok_gas\"] for cb in CALLBACKS),\n                  \"oog\": sum(g[cb][\"oog\"] for cb in CALLBACKS),\n                  \"other_errors\": sum(g[cb][k] for cb in CALLBACKS for k in FAIL_KEYS),\n                  \"oog_seeds\": sorted([[s, sum(rows[s][n][\"stats\"][cb][\"oog\"] for cb in CALLBACKS)] for s in rows\n                                       if any(rows[s][n][\"stats\"][cb][\"oog\"] for cb in CALLBACKS)])}\n    want = [c.seed for c in cfgs]\n    complete = (err is None and len(rows) == len(want) and set(rows) == set(want)\n                and not any(r[\"dup\"] for r in rows.values())\n                and all(math.isfinite(r[n][k]) for r in rows.values() for n in ROLES for k in (\"total\", \"retail\", \"arb\")))\n    recon = max((abs(r[n][\"total\"] - r[n][\"retail\"] - r[n][\"arb\"]) for r in rows.values() for n in ROLES), default=None)\n    return {\"label\": label, \"seconds\": round(dt, 3), \"n_requested\": len(want), \"n_returned\": len(rows),\n            \"error\": err, \"complete\": complete, \"counters_per_seed_match_global\": counters_agree,\n            \"gas\": gas, \"max_abs_reconciliation_error\": recon}, rows\n\n\ndef validity(ra, rb, mode):\n    \"\"\"Paired validity. A = candidate (or control run 1), B = reference V2 (or control run 2).\"\"\"\n    reasons = []\n    for r in (ra, rb):\n        if not r[\"complete\"]:\n            reasons.append(f\"{r['label']}: incomplete seeds or error ({r['error']})\")\n        if r[\"gas\"][\"submission\"][\"other_errors\"]:\n            reasons.append(f\"{r['label']}: strategy revert/tx_error/other_halt/bad_return\")\n        if r[\"gas\"][\"normalizer\"][\"oog\"] or r[\"gas\"][\"normalizer\"][\"other_errors\"]:\n            reasons.append(f\"{r['label']}: Vanilla failure\")\n    oa, ob = ra[\"gas\"][\"submission\"][\"oog\"], rb[\"gas\"][\"submission\"][\"oog\"]\n    if mode == \"ref\" and oa > ob:\n        reasons.append(f\"candidate OOG {oa} > V2 OOG {ob}\")\n    if mode == \"control\" and oa != ob:\n        reasons.append(f\"control OOG mismatch {oa} != {ob}\")\n    return not reasons, reasons\n\n\ndef summary(xs):\n    n = len(xs)\n    m = sum(xs) / n\n    sd = math.sqrt(sum((x - m) ** 2 for x in xs) / (n - 1)) if n > 1 else 0.0\n    return {\"n\": n, \"mean\": m, \"se\": sd / math.sqrt(n), \"sd\": sd}\n\n\ndef paired(ea, eb, seeds, label):\n    d = [(s, ea[s][\"submission\"][\"total\"] - eb[s][\"submission\"][\"total\"]) for s in seeds]\n    st = summary([x for _, x in d])\n    worst = sorted(d, key=lambda r: r[1])[:5]\n    return {\"label\": label, \"n\": st[\"n\"], \"mean_diff\": st[\"mean\"], \"se\": st[\"se\"], \"sd\": st[\"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\": ea[s][\"submission\"][\"total\"],\n                       \"edge_b\": eb[s][\"submission\"][\"total\"]} for s, x in worst]}\n\n\ndef decomposition(rows, seeds):\n    return {n: {k: summary([rows[s][n][k] for s in seeds]) for k in (\"total\", \"retail\", \"arb\")} for n in ROLES}\n\n\ndef main():\n    a = sys.argv[1:]\n    mode, ref = None, None\n    if \"--control\" in a:\n        a.remove(\"--control\"); mode = \"control\"\n    if \"--ref\" in a:\n        i = a.index(\"--ref\"); ref = a[i + 1]; del a[i:i + 2]; mode = \"ref\"\n    if mode is None:\n        sys.exit(\"need --ref V2.sol or --control\")\n    path, s0, s1, out = a[0], int(a[1]), int(a[2]), a[3]\n    src, bc = 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 mode == \"ref\":\n        src_b, bc_b = compile_src(ref)\n        rb, eb = run(\"B_ref\", bc_b, vanilla, cfgs)\n    else:\n        src_b = src\n        rb, eb = run(\"B_repeat\", bc, vanilla, cfgs)\n    ok, reasons = validity(ra, rb, mode)\n    both = ra[\"complete\"] and rb[\"complete\"]\n    res = {\"mode\": mode, \"sha256_a\": hashlib.sha256(src.encode()).hexdigest(),\n           \"sha256_b\": hashlib.sha256(src_b.encode()).hexdigest(), \"seeds\": [s0, s1], \"valid\": ok,\n           \"invalid_reasons\": reasons, \"runs\": [ra, rb],\n           # computed whenever both runs completed every seed; labelled with valid/invalid_reasons\n           \"paired\": paired(ea, eb, seeds, \"control\" if mode == \"control\" else \"candidate_minus_V2\") if both else {\"n\": 0},\n           \"bit_identical_rows\": both and all(ea[s][\"submission\"][\"total\"] == eb[s][\"submission\"][\"total\"]\n                                              and ea[s][\"normalizer\"][\"total\"] == eb[s][\"normalizer\"][\"total\"] for s in seeds),\n           \"decomposition_a\": decomposition(ea, seeds) if both else None,\n           \"decomposition_b\": decomposition(eb, seeds) if both else None,\n           \"per_seed\": [{\"seed\": s, \"a\": {n: [ea[s][n][k] for k in (\"total\", \"retail\", \"arb\")] for n in ROLES},\n                         \"b\": {n: [eb[s][n][k] for k in (\"total\", \"retail\", \"arb\")] for n in ROLES}}\n                        for s in seeds] if both else []}\n    Path(out).write_text(json.dumps(res, indent=1))\n    print(json.dumps({k: v for k, v in res.items() if k not in (\"runs\", \"per_seed\")}, indent=1)[:4000])\n    for r in (ra, rb):\n        print(r[\"label\"], r[\"seconds\"], r[\"complete\"], r[\"counters_per_seed_match_global\"],\n              {n: (g[\"max_ok\"], g[\"oog\"], g[\"other_errors\"]) for n, g in r[\"gas\"].items()})\n    sys.exit(0 if ok else 2)\n\n\nif __name__ == \"__main__\":\n    main()\n","sha256":"3dad487bbbbb873609bc5681487c2ec8c7b153b57a4f95f73e1e4a2514d6ae37"},{"path":"scratch/tests.py","content":"\"\"\"Harness self-tests (no scored runs): config builder equality and scaling, validity rule.\"\"\"\nimport sys, time\nsys.path.insert(0, \"scratch\")\nfrom harness import build_configs, config_for_seed, validity\nfrom amm_competition.competition.config import BASELINE_VARIANCE, build_base_config\nfrom amm_competition.competition.match import MatchRunner\n\nF = (\"n_steps\", \"initial_price\", \"initial_x\", \"initial_y\", \"gbm_mu\", \"gbm_sigma\", \"gbm_dt\",\n     \"retail_arrival_rate\", \"retail_mean_size\", \"retail_size_sigma\", \"retail_buy_prob\", \"seed\")\nout = {}\n# 1. equality with MatchRunner._build_configs on a small training slice 10000..10031\nmr = MatchRunner(n_simulations=10032, config=build_base_config(seed=None), n_workers=8,\n                 variance=BASELINE_VARIANCE)._build_configs()[10000:10032]\nmine = build_configs(10000, 10032)\nout[\"matchrunner_equal_10000_10031\"] = len(mr) == len(mine) == 32 and all(\n    getattr(a, f) == getattr(b, f) for a, b in zip(mr, mine) for f in F)\n# 2. allocation scales with requested n, not with the seed value\nt = time.time(); far = build_configs(4000000, 4000004); dt_far = time.time() - t\nout[\"far_block_len\"] = len(far); out[\"far_block_seconds\"] = round(dt_far, 4)\nout[\"allocation_scales_with_n\"] = (len(far) == 4 and [c.seed for c in far] == list(range(4000000, 4000004))\n                                   and len(build_configs(10000, 10000)) == 0 and len(build_configs(10000, 10256)) == 256\n                                   and dt_far < 0.5)\nout[\"far_seed_equals_single\"] = all(getattr(far[0], f) == getattr(config_for_seed(4000000), f) for f in F)\n# 3. validity rule (synthetic run summaries)\ndef R(label, oog=0, other=0, voog=0, vother=0, complete=True):\n    return {\"label\": label, \"complete\": complete, \"error\": None,\n            \"gas\": {\"submission\": {\"oog\": oog, \"other_errors\": other},\n                    \"normalizer\": {\"oog\": voog, \"other_errors\": vother}}}\ncases = {\n    \"cand_oog_equal_v2_valid\": validity(R(\"A\", 7), R(\"B\", 7), \"ref\")[0] is True,\n    \"cand_oog_less_valid\": validity(R(\"A\", 0), R(\"B\", 7), \"ref\")[0] is True,\n    \"cand_oog_more_invalid\": validity(R(\"A\", 8), R(\"B\", 7), \"ref\")[0] is False,\n    \"v2_oog_alone_not_invalid\": validity(R(\"A\", 0), R(\"B\", 3), \"ref\")[0] is True,\n    \"revert_invalid\": validity(R(\"A\", 0, 1), R(\"B\", 7), \"ref\")[0] is False,\n    \"ref_revert_invalid\": validity(R(\"A\", 0), R(\"B\", 7, 1), \"ref\")[0] is False,\n    \"vanilla_oog_invalid\": validity(R(\"A\", 0, voog=1), R(\"B\", 0), \"ref\")[0] is False,\n    \"vanilla_other_invalid\": validity(R(\"A\", 0), R(\"B\", 0, vother=1), \"ref\")[0] is False,\n    \"incomplete_invalid\": validity(R(\"A\", complete=False), R(\"B\"), \"ref\")[0] is False,\n    \"control_oog_match_valid\": validity(R(\"A\", 7), R(\"B\", 7), \"control\")[0] is True,\n    \"control_oog_mismatch_invalid\": validity(R(\"A\", 7), R(\"B\", 6), \"control\")[0] is False,\n}\nout[\"validity_cases\"] = cases\nout[\"all_pass\"] = all(v for k, v in out.items() if isinstance(v, bool)) and all(cases.values())\nimport json; print(json.dumps(out, indent=1))\nsys.exit(0 if out[\"all_pass\"] else 1)\n","sha256":"a87a42897cfb307c63183999c2b0716e4960db2abd52b468ffbe374c5202cc57"},{"path":"scratch/diag.py","content":"\"\"\"Forced-failure diagnostics for the counters and the validity rule. Separately labelled and\nexcluded from scored runs: own process, seeds 0..1, n_steps=500, same 250k gas limits.\nEach forced strategy is run as candidate A against V2 as reference B on the same seeds.\"\"\"\nimport json, sys\nsys.path.insert(0, \"scratch\")\nfrom harness import compile_src, run, build_configs, validity\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\nrb, _ = run(\"V2_ref\", compile_src(\"scratch/V2.sol\")[1], van, cfgs)\nout = {\"label\": \"diagnostic_forced_failures_not_scored\", \"seeds\": [0, 1], \"n_steps\": 500,\n       \"v2_ref\": {\"complete\": rb[\"complete\"], \"gas\": {n: {k: g[k] for k in (\"max_ok\", \"oog\", \"other_errors\")} for n, g in rb[\"gas\"].items()}}}\nfor name in (\"OOGSwap\", \"RevertSwap\", \"BadReturnSwap\", \"OOGInit\"):\n    ra, _ = run(name, compile_src(f\"scratch/diag/{name}.sol\")[1], van, cfgs)\n    ok, reasons = validity(ra, rb, \"ref\")\n    out[name] = {\"valid\": ok, \"invalid_reasons\": reasons, \"complete\": ra[\"complete\"], \"error\": (ra[\"error\"] or \"\")[:120],\n                 \"submission\": {cb: {k: v for k, v in s.items() if k != \"sum_ok_gas\"} for cb, s in ra[\"gas\"][\"submission\"][\"by_callback\"].items()},\n                 \"vanilla_failures\": ra[\"gas\"][\"normalizer\"][\"oog\"] + ra[\"gas\"][\"normalizer\"][\"other_errors\"]}\nsw = lambda n: out[n][\"submission\"][\"afterSwap\"]\nout[\"checks\"] = {\n    \"oog_swap_counted_and_invalid\": sw(\"OOGSwap\")[\"oog\"] == sw(\"OOGSwap\")[\"calls\"] > 0 and not out[\"OOGSwap\"][\"valid\"],\n    \"revert_swap_counted_not_as_oog_and_invalid\": sw(\"RevertSwap\")[\"revert\"] == sw(\"RevertSwap\")[\"calls\"] > 0 and sw(\"RevertSwap\")[\"oog\"] == 0 and not out[\"RevertSwap\"][\"valid\"],\n    \"bad_return_counted_and_invalid\": sw(\"BadReturnSwap\")[\"bad_return\"] == sw(\"BadReturnSwap\")[\"calls\"] > 0 and not out[\"BadReturnSwap\"][\"valid\"],\n    \"oog_init_counted_and_invalid\": out[\"OOGInit\"][\"submission\"][\"afterInitialize\"][\"oog\"] >= 1 and out[\"OOGInit\"][\"error\"] != \"\" and not out[\"OOGInit\"][\"valid\"],\n    \"vanilla_clean_in_all\": all(out[n][\"vanilla_failures\"] == 0 for n in (\"OOGSwap\", \"RevertSwap\", \"BadReturnSwap\", \"OOGInit\")),\n}\nprint(json.dumps(out, indent=1))\nsys.exit(0 if all(out[\"checks\"].values()) else 1)\n","sha256":"e2ea5732846a1724f5bd64782061041c2111a55e29daedf1fc5cb418538aca00"},{"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","sha256":"28976d8f6e39042da9cb5fdccfe57917d51c6bef75e3e3c6d6a204ceffb6c690"},{"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","sha256":"3d53c35ffdd02c5d9f68c25f5bec14b56230c621c20fbaf3d969b8e7e848683f"},{"path":"scratch/diag/BadReturnSwap.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) external pure override returns (uint256, uint256) {\n        assembly { mstore(0, 1) return(0, 32) }\n    }\n    function getName() external pure override returns (string memory) { return \"DiagBadReturnSwap\"; }\n}\n","sha256":"3bd5ebb95ae2388374e45bf015012363652f700c10615dd7deef215578a7d593"},{"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","sha256":"1bc6cea3108124476f41576c9fc211c5a7c9396d93bfc0991479bcfecc423df4"}],"commands":["git clone https://github.com/benedictbrady/amm-challenge && cd amm-challenge && git checkout 6b6921f8d463314aa7c96234e1be25a25245f704   # inside the workspace, never /tmp","mkdir -p scratch && (write source_files into place) && 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 && python scratch/tests.py          # config builder equality/scaling + validity-rule cases, exit 0 = all pass",". scratch/env.sh && python scratch/harness.py scratch/V2.sol 10000 10256 scratch/control.json --control   # this control; exit 0 valid, 2 invalid, 1 runtime error",". scratch/env.sh && python scratch/diag.py           # forced-failure diagnostics, not scored","candidate: . scratch/env.sh && python scratch/harness.py CANDIDATE.sol S0 S1 out.json --ref scratch/V2.sol"],"tests":{"tests_py":{"matchrunner_equal_10000_10031":true,"far_block_len":4,"far_block_seconds":0.0,"allocation_scales_with_n":true,"far_seed_equals_single":true,"validity_cases":{"cand_oog_equal_v2_valid":true,"cand_oog_less_valid":true,"cand_oog_more_invalid":true,"v2_oog_alone_not_invalid":true,"revert_invalid":true,"ref_revert_invalid":true,"vanilla_oog_invalid":true,"vanilla_other_invalid":true,"incomplete_invalid":true,"control_oog_match_valid":true,"control_oog_mismatch_invalid":true},"all_pass":true},"patch_applies_to_clean_checkout":true,"patch_reverse_applies_to_build_tree":true,"gas_limits_unchanged":"GAS_LIMIT_INIT 250_000, GAS_LIMIT_TRADE 250_000, GAS_LIMIT_NAME 50_000; cfmm.rs update_fees keep-previous-fees fallback unchanged","smoke_seed_10000_matches_prior_artifact":"edge 489.4875846678468 equal to 02c134d9 paired.worst[0]"},"paired":{"label":"control (V2 run A minus independent V2 run B, same seeds 10000..10255; reproducibility, not a gain)","n":256,"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_a":489.4875846678468,"edge_b":489.4875846678468},{"seed":10001,"diff":0.0,"edge_a":552.3376592263003,"edge_b":552.3376592263003},{"seed":10002,"diff":0.0,"edge_a":443.04632640588295,"edge_b":443.04632640588295},{"seed":10003,"diff":0.0,"edge_a":651.7063161382114,"edge_b":651.7063161382114},{"seed":10004,"diff":0.0,"edge_a":484.2388697905094,"edge_b":484.2388697905094}]},"bit_identical_rows":true,"valid":true,"invalid_reasons":[],"gas":{"limit_afterSwap":250000,"limit_afterInitialize":250000,"limit_getName":50000,"run_A":{"v2":{"max_ok":243554,"oog":7,"other_errors":0,"oog_seeds":[[10043,2],[10044,1],[10057,1],[10150,2],[10231,1]],"by_callback":{"afterInitialize":{"calls":256,"ok":256,"max_ok_gas":236377,"oog":0,"revert":0,"other_halt":0,"tx_error":0,"bad_return":0},"afterSwap":{"calls":2644187,"ok":2644180,"max_ok_gas":243554,"oog":7,"revert":0,"other_halt":0,"tx_error":0,"bad_return":0},"getName":{"calls":256,"ok":256,"max_ok_gas":21545,"oog":0,"revert":0,"other_halt":0,"tx_error":0,"bad_return":0}}},"vanilla":{"max_ok":22592,"oog":0,"other_errors":0,"oog_seeds":[],"by_callback":{"afterInitialize":{"calls":256,"ok":256,"max_ok_gas":21796,"oog":0,"revert":0,"other_halt":0,"tx_error":0,"bad_return":0},"afterSwap":{"calls":2749671,"ok":2749671,"max_ok_gas":22592,"oog":0,"revert":0,"other_halt":0,"tx_error":0,"bad_return":0},"getName":{"calls":256,"ok":256,"max_ok_gas":21526,"oog":0,"revert":0,"other_halt":0,"tx_error":0,"bad_return":0}}}},"run_B":{"v2":{"max_ok":243554,"oog":7,"other_errors":0,"oog_seeds":[[10043,2],[10044,1],[10057,1],[10150,2],[10231,1]],"by_callback":{"afterInitialize":{"calls":256,"ok":256,"max_ok_gas":236377,"oog":0,"revert":0,"other_halt":0,"tx_error":0,"bad_return":0},"afterSwap":{"calls":2644187,"ok":2644180,"max_ok_gas":243554,"oog":7,"revert":0,"other_halt":0,"tx_error":0,"bad_return":0},"getName":{"calls":256,"ok":256,"max_ok_gas":21545,"oog":0,"revert":0,"other_halt":0,"tx_error":0,"bad_return":0}}},"vanilla":{"max_ok":22592,"oog":0,"other_errors":0,"oog_seeds":[],"by_callback":{"afterInitialize":{"calls":256,"ok":256,"max_ok_gas":21796,"oog":0,"revert":0,"other_halt":0,"tx_error":0,"bad_return":0},"afterSwap":{"calls":2749671,"ok":2749671,"max_ok_gas":22592,"oog":0,"revert":0,"other_halt":0,"tx_error":0,"bad_return":0},"getName":{"calls":256,"ok":256,"max_ok_gas":21526,"oog":0,"revert":0,"other_halt":0,"tx_error":0,"bad_return":0}}}},"max_ok":243554,"oog":{"run_A":7,"run_B":7,"prior_result":7},"other_errors":0,"counters_per_seed_match_global":[true,true],"note":"gas is revm per-transaction gas_used (includes 21000 intrinsic + calldata). max_ok over all callbacks; afterSwap max_ok 243554, afterInitialize 236377. Deployment is not counted (fails loudly)."},"decomposition":{"label":"run A, seeds 10000..10255, per-seed mean/se/sd; total = retail + arb per seed","summaries":{"v2":{"total":{"mean":523.2153137188661,"se":6.000605427974867,"sd":96.00968684759788},"retail":{"mean":551.3340836925894,"se":6.71710130298193,"sd":107.47362084771088},"arb":{"mean":-28.118769973723158,"se":1.6449404509677565,"sd":26.319047215484105}},"vanilla":{"total":{"mean":359.4148377705289,"se":4.044875739463588,"sd":64.71801183141741},"retail":{"mean":597.2350636183056,"se":7.886945915024518,"sd":126.19113464039229},"arb":{"mean":-237.82022584777675,"se":4.462075563307358,"sd":71.39320901291772}}},"run_B_identical":true,"max_abs_reconciliation_error":7.673861546209082e-12,"definition":"retail = sum over routed retail fills of (amount_x*p - amount_y if AMM buys X else amount_y - amount_x*p) at that step's fair price p; arb = -sum of arbitrageur profit at p. These are the exact terms the engine adds to edges, so retail+arb = total up to float rounding. A time-mean fee times retail volume is not retail edge and is not used.","oog_present":"V2's 7 OOG afterSwap calls fall back to previous fees as in the scorer; the numbers include them, as V2 on the board would."},"discrepancies":[],"diagnostics":{"label":"diagnostic_forced_failures_not_scored","seeds":[0,1],"n_steps":500,"v2_ref":{"complete":true,"gas":{"submission":{"max_ok":236377,"oog":0,"other_errors":0},"normalizer":{"max_ok":22592,"oog":0,"other_errors":0}}},"OOGSwap":{"valid":false,"invalid_reasons":["candidate OOG 1224 > V2 OOG 0"],"complete":true,"error":"","submission":{"afterInitialize":{"calls":2,"ok":2,"max_ok_gas":21801,"oog":0,"revert":0,"other_halt":0,"tx_error":0,"bad_return":0},"afterSwap":{"calls":1224,"ok":0,"max_ok_gas":0,"oog":1224,"revert":0,"other_halt":0,"tx_error":0,"bad_return":0},"getName":{"calls":2,"ok":2,"max_ok_gas":21536,"oog":0,"revert":0,"other_halt":0,"tx_error":0,"bad_return":0}},"vanilla_failures":0},"RevertSwap":{"valid":false,"invalid_reasons":["RevertSwap: strategy revert/tx_error/other_halt/bad_return"],"complete":true,"error":"","submission":{"afterInitialize":{"calls":2,"ok":2,"max_ok_gas":21796,"oog":0,"revert":0,"other_halt":0,"tx_error":0,"bad_return":0},"afterSwap":{"calls":1224,"ok":0,"max_ok_gas":0,"oog":0,"revert":1224,"other_halt":0,"tx_error":0,"bad_return":0},"getName":{"calls":2,"ok":2,"max_ok_gas":21532,"oog":0,"revert":0,"other_halt":0,"tx_error":0,"bad_return":0}},"vanilla_failures":0},"BadReturnSwap":{"valid":false,"invalid_reasons":["BadReturnSwap: strategy revert/tx_error/other_halt/bad_return"],"complete":true,"error":"","submission":{"afterInitialize":{"calls":2,"ok":2,"max_ok_gas":21795,"oog":0,"revert":0,"other_halt":0,"tx_error":0,"bad_return":0},"afterSwap":{"calls":1224,"ok":1224,"max_ok_gas":22561,"oog":0,"revert":0,"other_halt":0,"tx_error":0,"bad_return":1224},"getName":{"calls":2,"ok":2,"max_ok_gas":21453,"oog":0,"revert":0,"other_halt":0,"tx_error":0,"bad_return":0}},"vanilla_failures":0},"OOGInit":{"valid":false,"invalid_reasons":["OOGInit: incomplete seeds or error (RuntimeError: EVM error: Out of gas (chunk seeds 0..1))","candidate OOG 2 > V2 OOG 0"],"complete":false,"error":"RuntimeError: EVM error: Out of gas (chunk seeds 0..1)","submission":{"afterInitialize":{"calls":2,"ok":0,"max_ok_gas":0,"oog":2,"revert":0,"other_halt":0,"tx_error":0,"bad_return":0},"afterSwap":{"calls":0,"ok":0,"max_ok_gas":0,"oog":0,"revert":0,"other_halt":0,"tx_error":0,"bad_return":0},"getName":{"calls":2,"ok":2,"max_ok_gas":21539,"oog":0,"revert":0,"other_halt":0,"tx_error":0,"bad_return":0}},"vanilla_failures":0},"checks":{"oog_swap_counted_and_invalid":true,"revert_swap_counted_not_as_oog_and_invalid":true,"bad_return_counted_and_invalid":true,"oog_init_counted_and_invalid":true,"vanilla_clean_in_all":true}},"timing_s":{"run_A_256_seeds":45.247,"run_B_256_seeds":41.486,"setup_rebuild_after_patch":20},"confirmation_seeds_used":false,"blocked":null}