{"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 fair = wmul(pHat, 70 * WAD / 100) + wmul(fastHat, 30 * WAD / 100);\n        int256 gap = int256(spot * 10000 / fair) - 10000;\n        uint256 vb = sigmaHat * 10000 / WAD;\n        uint256 bidFee = _tableFee(gap, vb);\n        uint256 askFee = _tableFee(-gap, vb);\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\n    function _cell(bytes memory t, uint256 r, uint256 c) internal pure returns (uint256) {\n        uint256 k = 2 * (21 * r + c);\n        return (uint256(uint8(t[k])) << 8) | uint256(uint8(t[k+1]));\n    }\n    function _tableFee(int256 gap, uint256 vb) internal pure returns (uint256) {\n        bytes memory t = hex\"0000000000000000000000000000000000000006001a002e004000540068007c009000a400b800cc00de0000000000000000000000000000000000000006001a002e004000540068007c009000a400b800cc00de000000000000000000000000000000000004000a001a002e00420056006a007e009200a400b800cc00e000060006000600060006000600060006000c0014001e00320046005a006e0082009600aa00bc00d000e4\";\n        if (gap < -200) gap = -200;\n        if (gap > 199) gap = 199;\n        if (vb > 29) vb = 29;\n        uint256 x = uint256(gap + 200);\n        uint256 c = x / 20; uint256 dx = x % 20;\n        uint256 r = vb / 10; uint256 dv = vb % 10;\n        uint256 lo = _cell(t,r,c)*(20-dx)+_cell(t,r,c+1)*dx;\n        uint256 hi = _cell(t,r+1,c)*(20-dx)+_cell(t,r+1,c+1)*dx;\n        return (lo*(10-dv)+hi*dv)*BPS/200;\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 \"routing_table\"; }\n}\n","sha256":"83181f5c4bd06665d6d13167a018397ef31d3bf31720bd75e2f0002f2f984849","paired":{"n":0,"mean_diff":null,"se":null,"worst":[]},"gas":{"max_ok":247115,"oog":4,"reverts":0,"other_halts":0,"successful_calls":406571,"init_max_ok":236404,"deploy_max_ok":1511158,"name_max_ok":21573,"bytecode_keccak256":"b158cb7a0d7d62ff7de07930eb7ec9fc65e7432151f66fe5f8cb35b71b430a1a","limit":250000},"baseline_gas":{"max_ok":243554,"oog":4,"reverts":0,"other_halts":0,"successful_calls":652794,"init_max_ok":236377,"deploy_max_ok":1560600,"name_max_ok":21545,"bytecode_keccak256":"369eada8e43393968ef8975d4e4570434e3308edfd7da9c50d901de7c2ff71a2","limit":250000},"valid":false,"blocked":{"step":"64-seed instrumented gas eligibility screen","error":"Exact V2 and candidate each halted OutOfGas 4 times at unchanged 250000 gas limits. Failed calls can be swallowed by CFMM; returned edges are ineligible. No valid paired comparison or confirmation was performed."},"tuning":{"variants_generated":6,"variants_measured":1,"selected":"C0 (initial experiment; not a validated winner)","parameters":{"uncertainty_multiplier":1.0,"retail_rate":0.8,"fair_blend_slow_percent":70,"gap_bps":[-200,-180,-160,-140,-120,-100,-80,-60,-40,-20,0,20,40,60,80,100,120,140,160,180,200],"sigma_bps":[0,10,20,30],"fees_bps":[[0,0,0,0,0,0,0,0,0,6,26,46,64,84,104,124,144,164,184,204,222],[0,0,0,0,0,0,0,0,0,6,26,46,64,84,104,124,144,164,184,204,222],[0,0,0,0,0,0,0,0,4,10,26,46,66,86,106,126,146,164,184,204,224],[6,6,6,6,6,6,6,6,12,20,30,50,70,90,110,130,150,170,188,208,228]]},"attempted_runs":[{"strategy":"V2","sha256":"28417e116f57f1029fdd1803832e852574902c1ae7253c05657f317db76da4ab","n":64,"start":10000,"steps":10000,"seconds":9.771050696028396,"eligible":false,"gas":{"max_ok":243554,"oog":4,"reverts":0,"other_halts":0,"successful_calls":652794,"init_max_ok":236377,"deploy_max_ok":1560600,"name_max_ok":21545,"bytecode_keccak256":"369eada8e43393968ef8975d4e4570434e3308edfd7da9c50d901de7c2ff71a2","limit":250000}},{"strategy":"C0","sha256":"83181f5c4bd06665d6d13167a018397ef31d3bf31720bd75e2f0002f2f984849","n":64,"start":10000,"steps":10000,"seconds":7.285770017886534,"eligible":false,"gas":{"max_ok":247115,"oog":4,"reverts":0,"other_halts":0,"successful_calls":406571,"init_max_ok":236404,"deploy_max_ok":1511158,"name_max_ok":21573,"bytecode_keccak256":"b158cb7a0d7d62ff7de07930eb7ec9fc65e7432151f66fe5f8cb35b71b430a1a","limit":250000}}],"other_variants":"C1..C5 were generated with uncertainty multiplier 1/1.5/2 and retail rate .8/1.2, but never simulated. No selection or retuning used their outcomes.","top2":null,"confirmation":null},"seeds":{"screen":[10000,10001,10002,10003,10004,10005,10006,10007,10008,10009,10010,10011,10012,10013,10014,10015,10016,10017,10018,10019,10020,10021,10022,10023,10024,10025,10026,10027,10028,10029,10030,10031,10032,10033,10034,10035,10036,10037,10038,10039,10040,10041,10042,10043,10044,10045,10046,10047,10048,10049,10050,10051,10052,10053,10054,10055,10056,10057,10058,10059,10060,10061,10062,10063],"top2_unused":[11000,11255],"confirmation_unused":[4020000,4021999]},"reproduce":{"repository":"https://github.com/benedictbrady/amm-challenge","commit":"6b6921f8d463314aa7c96234e1be25a25245f704","commands":["git clone https://github.com/benedictbrady/amm-challenge test/scratch/amm-challenge","cd test/scratch/amm-challenge","git checkout 6b6921f8d463314aa7c96234e1be25a25245f704","Save setup_sh as scratch/setup.sh; sh scratch/setup.sh (once)","Prefix every subsequent command with: . scratch/env.sh &&","Fetch baseline artifact with curl -sS -A Mozilla/5.0; replace the two strings from the task; save scratch/V2.sol and verify its SHA256","Save instrument_py as scratch/instrument.py; python scratch/instrument.py","maturin develop --release --uv --manifest-path amm_sim_rs/Cargo.toml","Save table_py as scratch/table.py; python scratch/table.py","Save run_py as scratch/run.py; python scratch/run.py V2 10000 64","python scratch/run.py C0 10000 64","amm-match validate scratch/C0.sol"],"setup_sh":"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","instrument_py":"from pathlib import Path\np=Path('amm_sim_rs/src/evm/strategy.rs');s=p.read_text();insert='''\nuse std::sync::{Arc, Mutex, OnceLock};\nuse std::sync::atomic::{AtomicU64, Ordering};\ntype GasCounters = [AtomicU64; 8];\nstatic GAS: OnceLock<Mutex<std::collections::HashMap<String, Arc<GasCounters>>>> = OnceLock::new();\npub fn gas_stats() -> Vec<(String, Vec<u64>)> {\n    GAS.get_or_init(Default::default).lock().unwrap().iter()\n       .map(|(k,v)| (k.clone(), v.iter().map(|a| a.load(Ordering::Relaxed)).collect())).collect()\n}\nfn counters(bytecode: &[u8]) -> Arc<GasCounters> {\n    let key = format!(\"{:x}\", revm::primitives::keccak256(bytecode));\n    GAS.get_or_init(Default::default).lock().unwrap().entry(key)\n       .or_insert_with(|| Arc::new(std::array::from_fn(|_| AtomicU64::new(0)))).clone()\n}\nfn observe(c: &GasCounters, r: &ExecutionResult, kind: usize) {\n    match r {\n       ExecutionResult::Success { gas_used, .. } => {\n           c[kind].fetch_max(*gas_used, Ordering::Relaxed);\n           c[4].fetch_add(1, Ordering::Relaxed);\n       },\n       ExecutionResult::Revert { .. } => { c[2].fetch_add(1, Ordering::Relaxed); },\n       ExecutionResult::Halt { reason, .. } => {\n           let i = if matches!(reason, revm::primitives::HaltReason::OutOfGas(_)) {1} else {3};\n           c[i].fetch_add(1, Ordering::Relaxed);\n       }\n    }\n}\n''';s=s.replace('/// Errors that can occur',insert+'\\n/// Errors that can occur',1);s=s.replace('    name: String,','    name: String,\\n    gas_counters: Arc<GasCounters>,',1);s=s.replace('            name: default_name,','            name: default_name,\\n            gas_counters: counters(&bytecode),',1)\ns=s.replace('            match result {','            observe(&self.gas_counters, &result, 6);\\n            match result {',1)\ns=s.replace('        match result {\\n            ExecutionResult::Success','        observe(&self.gas_counters, &result, if gas_limit == GAS_LIMIT_NAME {7} else if calldata.len() == 196 {0} else {5});\\n        match result {\\n            ExecutionResult::Success',1)\np.write_text(s)\np=Path('amm_sim_rs/src/lib.rs');s=p.read_text();s=s.replace('/// Python module definition','#[pyfunction]\\nfn gas_stats() -> Vec<(String, Vec<u64>)> { crate::evm::strategy::gas_stats() }\\n\\n/// Python module definition');s=s.replace('    m.add_function(wrap_pyfunction!(run_batch, m)?)?;','    m.add_function(wrap_pyfunction!(run_batch, m)?)?;\\n    m.add_function(wrap_pyfunction!(gas_stats, m)?)?;');p.write_text(s)\n","counter_patch":"diff --git a/amm_sim_rs/src/evm/strategy.rs b/amm_sim_rs/src/evm/strategy.rs\nindex 1d06af1..b336e1b 100644\n--- a/amm_sim_rs/src/evm/strategy.rs\n+++ b/amm_sim_rs/src/evm/strategy.rs\n@@ -12,6 +12,34 @@ use thiserror::Error;\n use crate::types::trade_info::{encode_after_initialize, decode_fee_pair, TradeInfo, SELECTOR_GET_NAME};\n use crate::types::wad::Wad;\n \n+\n+use std::sync::{Arc, Mutex, OnceLock};\n+use std::sync::atomic::{AtomicU64, Ordering};\n+type GasCounters = [AtomicU64; 8];\n+static GAS: OnceLock<Mutex<std::collections::HashMap<String, Arc<GasCounters>>>> = OnceLock::new();\n+pub fn gas_stats() -> Vec<(String, Vec<u64>)> {\n+    GAS.get_or_init(Default::default).lock().unwrap().iter()\n+       .map(|(k,v)| (k.clone(), v.iter().map(|a| a.load(Ordering::Relaxed)).collect())).collect()\n+}\n+fn counters(bytecode: &[u8]) -> Arc<GasCounters> {\n+    let key = format!(\"{:x}\", revm::primitives::keccak256(bytecode));\n+    GAS.get_or_init(Default::default).lock().unwrap().entry(key)\n+       .or_insert_with(|| Arc::new(std::array::from_fn(|_| AtomicU64::new(0)))).clone()\n+}\n+fn observe(c: &GasCounters, r: &ExecutionResult, kind: usize) {\n+    match r {\n+       ExecutionResult::Success { gas_used, .. } => {\n+           c[kind].fetch_max(*gas_used, Ordering::Relaxed);\n+           c[4].fetch_add(1, Ordering::Relaxed);\n+       },\n+       ExecutionResult::Revert { .. } => { c[2].fetch_add(1, Ordering::Relaxed); },\n+       ExecutionResult::Halt { reason, .. } => {\n+           let i = if matches!(reason, revm::primitives::HaltReason::OutOfGas(_)) {1} else {3};\n+           c[i].fetch_add(1, Ordering::Relaxed);\n+       }\n+    }\n+}\n+\n /// Errors that can occur during EVM execution.\n #[derive(Error, Debug)]\n pub enum EVMError {\n@@ -50,6 +78,7 @@ const CALLER_ADDRESS: Address = Address::new([\n pub struct EVMStrategy {\n     /// Strategy name (cached after first call)\n     name: String,\n+    gas_counters: Arc<GasCounters>,\n     /// Compiled bytecode (for reset)\n     bytecode: Vec<u8>,\n     /// In-memory database for EVM state\n@@ -63,6 +92,7 @@ impl EVMStrategy {\n     pub fn new(bytecode: Vec<u8>, default_name: String) -> Result<Self, EVMError> {\n         let mut strategy = Self {\n             name: default_name,\n+            gas_counters: counters(&bytecode),\n             bytecode: bytecode.clone(),\n             db: InMemoryDB::default(),\n             trade_calldata: [0u8; 196],\n@@ -104,6 +134,7 @@ impl EVMStrategy {\n             let result = evm.transact_commit()\n                 .map_err(|e| EVMError::DeploymentFailed(format!(\"{:?}\", e)))?;\n \n+            observe(&self.gas_counters, &result, 6);\n             match result {\n                 ExecutionResult::Success { output, .. } => {\n                     match output {\n@@ -209,6 +240,7 @@ impl EVMStrategy {\n         let result = evm.transact_commit()\n             .map_err(|e| EVMError::ExecutionFailed(format!(\"{:?}\", e)))?;\n \n+        observe(&self.gas_counters, &result, if gas_limit == GAS_LIMIT_NAME {7} else if calldata.len() == 196 {0} else {5});\n         match result {\n             ExecutionResult::Success { output, .. } => {\n                 match output {\ndiff --git a/amm_sim_rs/src/lib.rs b/amm_sim_rs/src/lib.rs\nindex 35d9e59..b13bd46 100644\n--- a/amm_sim_rs/src/lib.rs\n+++ b/amm_sim_rs/src/lib.rs\n@@ -65,10 +65,14 @@ fn run_single(\n         .map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(e.to_string()))\n }\n \n+#[pyfunction]\n+fn gas_stats() -> Vec<(String, Vec<u64>)> { crate::evm::strategy::gas_stats() }\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!(gas_stats, m)?)?;\n     m.add_function(wrap_pyfunction!(run_single, m)?)?;\n     m.add_class::<SimulationConfig>()?;\n     m.add_class::<LightweightSimResult>()?;\n","table_py":"import numpy as np\nfrom numpy.polynomial.hermite import hermgauss\nfrom pathlib import Path\nimport json\nz,w=hermgauss(15);z=z*np.sqrt(2);w=w/np.sqrt(np.pi)\n# Bid: pool buys X. Normalize estimated fair to 1 and reserve Y to 1.\n# Nuisance distribution: retail mean 20/10000, log-size sigma 1.2,\n# lambda .8. A next-step arb precedes independently routed retail.\ndef table(unc,rate):\n    rows=[]\n    for vb in [0,10,20,30]:\n        row=[]\n        for gb in range(-200,201,20):\n            spot=np.exp(gb/10000); rx=1/spot; ry=1.;k=rx*ry\n            fair=np.exp(z*max(vb,1)/10000*unc);fee=np.arange(0,301,2)/10000;g=1-fee\n            # optimal sell-X arb into the pool\n            dx=np.maximum(0,(np.sqrt(k*g[:,None]/fair)-rx)/g[:,None]);dy=ry-k/(rx+g[:,None]*dx)\n            arb=dy-dx*fair\n            ax=rx+dx;ay=ry-dy\n            # Opposite-side arb uses fixed 30bps here only in offline objective.\n            ox=np.maximum(0,ax-np.sqrt(ax*ay/(.997*fair)));oy=ay*ox/np.maximum(ax-ox,1e-12)/.997\n            ax=ax-ox;ay=ay+oy\n            # normalizer spot after next-step arb, bounded by its fee band\n            ns=np.clip(np.ones_like(fair),fair*.997,fair/.997);nx=1/np.sqrt(ns);ny=np.sqrt(ns)\n            total=np.exp(z*1.2-.5*1.2**2)*.002\n            X=total[None,None,:]/fair[None,:,None]\n            r=np.sqrt(ay*ax*g[:,None]/(nx*ny*.997))\n            q=np.clip((r[:,:,None]*(nx[None,:,None]+.997*X)-ax[:,:,None])/(g[:,None,None]+r[:,:,None]*.997),0,X)\n            out=ay[:,:,None]*g[:,None,None]*q/(ax[:,:,None]+g[:,None,None]*q)\n            retail=q*fair[None,:,None]-out\n            objective=rate*.5*np.sum(retail*w[None,None,:]*w[None,:,None],axis=(1,2))-np.sum(arb*w,axis=1)\n            row.append(int(round(fee[np.argmax(objective)]*10000)))\n        rows.append(row)\n    return rows\nbase=Path('scratch/V2.sol').read_text()\n# Replace the entire mapping, preserve all estimator updates for a controlled experiment.\na=base.index('        uint256 flowSize =');b=base.index('        slots[0] = bidFee;',a)\nfor i,(unc,rate,blend) in enumerate([(1.,.8,70),(1.5,.8,70),(2.,.8,70),(1.,1.2,70),(1.5,1.2,70),(2.,1.2,70)]):\n    rows=table(unc,rate);packed=bytes(v for row in rows for f in row for v in [f>>8,f&255]).hex()\n    mapping=f'''        uint256 fair = wmul(pHat, {blend} * WAD / 100) + wmul(fastHat, {100-blend} * WAD / 100);\n        int256 gap = int256(spot * 10000 / fair) - 10000;\n        uint256 vb = sigmaHat * 10000 / WAD;\n        uint256 bidFee = _tableFee(gap, vb);\n        uint256 askFee = _tableFee(-gap, vb);\n\n'''\n    helper=f'''\n    function _cell(bytes memory t, uint256 r, uint256 c) internal pure returns (uint256) {{\n        uint256 k = 2 * (21 * r + c);\n        return (uint256(uint8(t[k])) << 8) | uint256(uint8(t[k+1]));\n    }}\n    function _tableFee(int256 gap, uint256 vb) internal pure returns (uint256) {{\n        bytes memory t = hex\"{packed}\";\n        if (gap < -200) gap = -200;\n        if (gap > 199) gap = 199;\n        if (vb > 29) vb = 29;\n        uint256 x = uint256(gap + 200);\n        uint256 c = x / 20; uint256 dx = x % 20;\n        uint256 r = vb / 10; uint256 dv = vb % 10;\n        uint256 lo = _cell(t,r,c)*(20-dx)+_cell(t,r,c+1)*dx;\n        uint256 hi = _cell(t,r+1,c)*(20-dx)+_cell(t,r+1,c+1)*dx;\n        return (lo*(10-dv)+hi*dv)*BPS/200;\n    }}\n'''\n    code=base[:a]+mapping+base[b:];code=code.replace('    function _compressTailWithSlope',helper+'\\n    function _compressTailWithSlope');code=code.replace('return \"no_cubic\"','return \"routing_table\"');Path(f'scratch/C{i}.sol').write_text(code)\n    Path(f'scratch/C{i}-params.json').write_text(json.dumps(dict(uncertainty_multiplier=unc,retail_rate=rate,fair_blend_slow_percent=blend,gap_bps=list(range(-200,201,20)),sigma_bps=[0,10,20,30],fees_bps=rows)))\n","run_py":"import json,sys,hashlib,time\nfrom pathlib import Path\nimport numpy as np\nimport amm_sim_rs\nfrom amm_competition.evm.compiler import SolidityCompiler\nfrom amm_competition.evm.baseline import get_vanilla_bytecode_and_abi\nfrom amm_competition.competition.config import build_config\nname,start,n=sys.argv[1],int(sys.argv[2]),int(sys.argv[3]);compiler=SolidityCompiler();code=Path('scratch/'+name+'.sol').read_text();c=compiler.compile(code);assert c.success,c.errors\nvanilla,_=get_vanilla_bytecode_and_abi()\nconfigs=[]\nfor seed in range(start,start+n):\n    rng=np.random.default_rng(seed);size=rng.uniform(19,21);rate=rng.uniform(.6,1);sigma=rng.uniform(.000882,.001008)\n    configs.append(build_config(seed=seed,gbm_sigma=sigma,retail_arrival_rate=rate,retail_mean_size=size))\nt=time.monotonic()\nr=amm_sim_rs.run_batch(list(c.bytecode),list(vanilla),configs,8)\nresult=dict(name=name,sha256=hashlib.sha256(code.encode()).hexdigest(),start=start,n=n,seconds=time.monotonic()-t,edges=[dict(seed=x.seed,edge=x.edges['submission']) for x in r.results],gas=amm_sim_rs.gas_stats())\nPath(f'scratch/{name}-{start}-{n}.json').write_text(json.dumps(result));print(json.dumps({k:v for k,v in result.items() if k!='edges'}),flush=True)\n","counter_layout":["max successful afterSwap gas including transaction intrinsic gas","all OutOfGas halts across deploy/init/name/swap","reverts across all EVM transactions","non-OOG halts across all EVM transactions","successful transaction count","max successful initialize gas","max successful deployment gas","max successful name gas"],"coverage":"observe() executes immediately after both transact_commit sites and before matching ExecutionResult. Shared atomic counters keyed by creation-bytecode keccak persist across all workers and simulations in one process. Returned gas snapshots include swallowed afterSwap failures, deploy, init and name. Runs use separate processes and are aggregated per source SHA256. Forbidden external CALL/DELEGATECALL/STATICCALL/CREATE opcodes make top-level transaction outcomes sufficient for these compiler-accepted strategies. Limits unchanged. CLI validation separately executed deployment/name/initialize successfully; CLI has no swaps; its Python executor is not part of the Rust counters.","validation":"Validating strategy...\nCompiling strategy...\nStrategy 'routing_table' validated successfully!"},"notes":["Experimental code only: both baseline and candidate fail the mandatory gas criterion. No valid performance metrics, top-two selection or 2000-seed confirmation. Nothing fabricated.","sha256 is computed from exact UTF-8 Strategy.sol content. Baseline source was verified against the specified hash before any run.","Reuses all V2 observable estimator update code; replaces only the old fee mapping. Retains unused estimator updates/constants, so gas savings are insufficient in this first implementation. No true simulator fair price is fed to the deployed policy.","Table bid axis is signed (spot/estimatedFair-1) in bps; ask uses its negative. Estimated fair = .7*pHat+.3*fastHat. sigmaHat supplies volatility bps. Bilinear interpolation uses integer bps coordinates; actual clipping is gap [-200,199] and volatility [0,29].","Offline objective is a simplified one-step model, not an exact conditional simulator expectation: deterministic 15-node Gaussian/lognormal quadrature, retail size ratio .002, size log-sigma 1.2, retail arrival .8, 30bps normalizer; candidate bid fee grid 0..300 bps by 2 bps; expected retail mark-to-fair edge minus sell-side arb profit. Opposite-side arb uses fixed 30bps and normalizer spot is clipped to its no-arb band. Estimation residual is approximated as lognormal uncertainty, multiplier 1. These are stated modeling assumptions, not measured conditional distributions.","All simulations used full 10000 steps, eight workers, and each submission versus VanillaStrategy in its own market. Config draws follow default_rng(seed) size, rate, sigma; price seed=seed; retail seed=seed+1.","Reported gas max_ok covers successful afterSwap calls, not deployments. Init/name/deployment maxima are separate. Successful calls exclude 4 OOG halts for each tested source. No reverts or non-OOG halts were observed in either instrumented run.","Method failure takes precedence over returning contaminated edge differences: paired.n=0 and all paired statistics are null/empty. Diagnostic run counts are 64 each, not valid paired observations."]}