This repository is a standalone simulation and stress-testing suite for Yolo Markets.
It focuses on two technical areas:
- Pricing/accounting/solvency under an LMSR-style market maker and proportional settlement.
- Termination timing robustness under adversarial behavior (ordering games, trade splitting, and last-mover strategies).
The suite is organized so results are reproducible from seeded runs and directly inspectable in CSV + chart outputs.
The simulation suites map to common review questions as follows:
-
Pricing mechanics, slippage, and execution behavior:
scripts/mev_sandwich.pyscripts/mev_competition.pyscripts/trade_splitting.py
-
Solvency, worst-case loss, and bond sufficiency:
scripts/solvency_checks.pysim/runner.pysim/lmsr.py
-
Termination randomness and manipulation resistance:
sim/termination.pyscripts/manipulation_curves.pyscripts/trade_splitting.pyscripts/last_mover.pyscripts/last_mover_normal_only.py
-
Sensitivity and manipulation cost curves:
scripts/manipulation_curves.py
simulations/
βββ requirements.txt
βββ README.md
βββ sim/
β βββ lmsr.py # core LMSR cost/price/trade primitives
β βββ termination.py # stochastic termination clock
β βββ fees.py # protocol fee model (base + convex surcharge, capped)
β βββ agents.py # honest + attacker trade generators
β βββ runner.py # end-to-end market lifecycle simulator
β βββ experiments.py # config builders for generic experiment sweeps
βββ scripts/
β βββ mev_sandwich.py # ordering + slippage sandwich scenarios
β βββ mev_competition.py # two-attacker competition and profit compression
β βββ trade_splitting.py # split invariance + split slippage scenarios
β βββ manipulation_curves.py # manipulation cost and end-prob sensitivity curves
β βββ solvency_checks.py # bound checks and sponsor-bond residuals
β βββ last_mover.py # broad last-mover scenario sweeps
β βββ last_mover_normal_only.py # one-shot last-mover profile used for reviewer-facing outputs
β βββ run_mechanism_analysis.py # convenience wrapper for manipulation+solvency suites
βββ out/
βββ results_*.csv
βββ summary_*.csv
βββ plots/*.png
Binary LMSR is used:
- Cost function:
C(qY, qN) = b * ln(exp(qY/b) + exp(qN/b))
- Marginal YES price:
pY = exp(qY/b) / (exp(qY/b) + exp(qN/b))
- Trade cost:
ΞC = C(after) - C(before)
b is calibrated from configured max-loss:
b = max_loss / ln(2)- LMSR worst-case bound:
worst_case_loss = b * ln(2) = max_loss
Implementation uses a numerically stable log-sum-exp path to avoid overflow.
Termination threshold:
T ~ Exponential(alpha)via inverse-CDF sampling.
Running clock:
S += max(cost_paid, 0) / bafter each trade.- Market terminates when
S >= T.
Important properties:
- Termination depends on economic spend, not raw trade count.
- Splitting one spend amount into many small trades does not change the expected
Sincrement materially. - The stochastic source in this simulation is seeded PRNG for reproducibility; the same statistical model is compatible with onchain VRF-driven termination in production.
At termination, settlement uses final price p*:
- YES payout per share:
p* - NO payout per share:
1 - p* - Total payout:
p* * QY + (1 - p*) * QN
This gives deterministic proportional settlement from terminal market state.
Current protocol fee model:
- Base fee:
0.10%of positive-spend trade cost. - Convex surcharge (enabled in all active suites):
- turns on after
progress > 0.85, - scales with how much the trade pushes price farther from 0.5,
- scales with per-trade termination intensity proxy
1 - exp(-alpha * dS).
- turns on after
- Hard cap:
- total fee rate capped at
7.00%of trade spend.
- total fee rate capped at
Observed effective fee rates in current outputs:
- min:
0.10% - median:
0.10% - p95:
7.00% - max:
7.00%
Honest flow:
- Random side (YES/NO).
- Random direction (buy/sell).
- Lognormal size, depth-scaled to
~2% of b.
Attacker flows:
- Last-mover directional pushes.
- Sandwich-style front-run/back-run.
- Multi-attacker competition.
run_single tracks:
- Collateral in/out from trades.
- Protocol fee accumulation.
- Termination state and final price.
- Settlement payout and net without bond.
- Bound checks and attacker PnL fields.
This is the accounting backbone used by solvency and stress scripts.
cd simulations
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
export MPLCONFIGDIR=.mpl_cacheMPLCONFIGDIR avoids matplotlib cache permission warnings and improves run stability in restricted environments.
cd simulations
source .venv/bin/activate
export MPLCONFIGDIR=.mpl_cache
python scripts/mev_sandwich.py --quick --seeds 300 --no-progress --out-dir out
python scripts/mev_competition.py --quick --seeds 300 --include-no-termination --no-progress --out-dir out
python scripts/trade_splitting.py --quick --seeds 300 --no-progress --out-dir out
python scripts/run_mechanism_analysis.py --out-dir out/mechanism_analysis --solvency-seeds 300
python scripts/last_mover_normal_only.py --seeds 200 --no-progress --out-dir out/last_mover_normal_onlypython scripts/mev_sandwich.py --quick --seeds 10 --out-dir out/smoke
python scripts/mev_competition.py --quick --seeds 10 --include-no-termination --out-dir out/smoke
python scripts/trade_splitting.py --quick --seeds 10 --out-dir out/smoke
python scripts/run_mechanism_analysis.py --quick --solvency-seeds 20 --out-dir out/smoke_mech
python scripts/last_mover_normal_only.py --seeds 20 --out-dir out/smoke_last_moverWhat it tests:
- Front-run + victim execution + unwind path.
- Victim slippage protection behavior.
- Impact of probabilistic termination on attacker ability to unwind.
Key outputs:
out/results_mev_sandwich.csvout/summary_mev_sandwich.csvout/plots/mev_sandwich_rates_vs_slippage.png
Quick grid (used in current output):
max_loss:[500, 2000]alpha:[0.01, 0.05]mode:no_termination,terminationstart_progress(termination):[0.95]victim_kind:[YES, NO]victim_size_pct_b:[0.01, 0.02, 0.05]slippage_tol:[0, 0.001, 0.0025, 0.005, 0.01]attacker_ratio:[0.5, 1.0, 2.0]seeds:300
Total runs: 216,000.
What it tests:
- Single attacker vs coordinated attacker vs two competing attackers.
- Profit compression from competition in the same opportunity window.
Key outputs:
out/results_mev_competition.csvout/summary_mev_competition.csvout/plots/mev_competition_profit_compression.png
Quick grid (used in current output):
max_loss:[500, 2000]alpha:[0.01, 0.05]mode:termination+no_termination(via--include-no-termination)start_progress(termination):[0.95]victim_kind:[YES, NO]victim_size_pct_b:[0.01, 0.02, 0.05]slippage_tol:[0.0025, 0.005, 0.01]attacker_ratio_each:[0.5, 1.0]seeds:300
Total runs: 86,400.
What it tests:
- Split invariance for termination and post-trade state.
- Split behavior in slippage/sandwich scenarios.
Key outputs:
out/results_trade_split_invariance.csvout/summary_trade_split_invariance.csvout/deltas_trade_split_invariance.csvout/plots/trade_split_invariance_deltas.pngout/results_trade_split_slippage.csvout/summary_trade_split_slippage.csv
Quick grid (used in current output):
Invariance leg:
max_loss:[500, 2000]alpha:[0.005, 0.05]start_progress:[0.0, 0.95]trade_kind:[YES, NO]total_shares_pct_b:[0.01, 0.02]split_n:[1, 20]seeds:300
Slippage leg:
max_loss:[500, 2000]alpha:[0.01, 0.05]start_progress:[0.95]victim_kind:[YES, NO]victim_size_pct_b:[0.01, 0.02]slippage_tol:[0.0025, 0.005, 0.01]attacker_ratio:[0.5, 1.0]split_n:[1, 20]seeds:300
Run counts:
- Invariance:
19,200 - Slippage:
57,600
What it tests:
- Cost to push price by target
dp. - Sensitivity to market depth (
max_loss -> b) andalpha. - Per-trade termination probability contribution from a manipulation trade.
Key outputs:
out/mechanism_analysis/results_manipulation_cost.csvout/mechanism_analysis/summary_manipulation_cost.csvout/mechanism_analysis/plots/manipulation_cost_curve_by_depth.pngout/mechanism_analysis/plots/manipulation_endprob_curve_by_alpha.png
Full grid (used in current output):
max_loss:[250, 500, 1000, 2000, 5000]alpha:[0.005, 0.01, 0.05]p0:[0.4, 0.5, 0.6]dp:[0.01, 0.02, 0.05, 0.10, 0.15, 0.20]direction:[up, down]split_n:[1, 5, 20, 100]progress:[0.85, 0.95, 0.99]
Total runs: 6,480.
What it tests:
- Whether realized deficits stay within LMSR bound.
- Whether sponsor bond equal to
max_lossclears all payouts. - Baseline vs attacker-stress scenario margins.
Key outputs:
out/mechanism_analysis/results_solvency_checks.csvout/mechanism_analysis/summary_solvency_checks.csvout/mechanism_analysis/plots/solvency_deficit_over_bound_cdf.pngout/mechanism_analysis/plots/solvency_bond_residual_by_scenario.png
Full grid (used in current output):
max_loss:[250, 500, 1000, 2000, 5000]alpha:[0.005, 0.01, 0.05]scenario:[baseline, attacker_stress]seeds:300
Total runs: 9,000.
last_mover.py:
- Broad grid, supports split attacks and configurable reaction/fee settings.
last_mover_normal_only.py:
- Fixed one-shot attack (
split_n = 1) profile for a cleaner baseline. - Uses the same convex fee policy and stricter reactive flow.
Current output generated with:
max_loss:[500, 2000]alpha:[0.01, 0.05]start_progress:[0.95, 0.99]hold_kind:[YES, NO]hold_shares_pct_b:0.05attack_budget_pct_b:[0.005, 0.01, 0.02, 0.05]split_n:1reaction_steps:25reaction_strength:1.3reaction_trigger_abs_dp:0.001seeds:200
Total runs: 12,800.
Outputs:
out/last_mover_normal_only/results_last_mover_normal_only.csvout/last_mover_normal_only/summary_last_mover_normal_only.csvout/last_mover_normal_only/plots/last_mover_normal_only_budget.png
mev_sandwich:216,000mev_competition:86,400trade_split_invariance:19,200trade_split_slippage:57,600manipulation_cost:6,480solvency_checks:9,000last_mover_normal_only:12,800
-
out/plots/mev_sandwich_rates_vs_slippage.png- X-axis: victim slippage tolerance.
- Curves: victim revert rate and attacker profitable rate.
- Current behavior:
- tighter slippage -> high victim reverts;
- looser slippage -> more attacker opportunities;
- mean attacker profit remains negative across buckets (~
-2.15to-2.11).
-
out/plots/mev_competition_profit_compression.png- X-axis: slippage tolerance.
- Curves: single attacker, coordinated size, two competing attackers.
- Current behavior:
- all mean profit curves are below zero;
- competition remains net-unprofitable.
-
out/plots/trade_split_invariance_deltas.png- X-axis: split factor
split_n. - Y-axis: delta vs
split_n=1. - Current behavior:
delta_terminated_rate = 0.0;delta_panddelta_Sare near zero;- supports trade-splitting resistance for termination mechanics.
- X-axis: split factor
-
out/mechanism_analysis/plots/manipulation_cost_curve_by_depth.png- X-axis: target move
dp. - Y-axis: total manipulation cost.
- Current behavior:
- cost grows nonlinearly with
dp; - higher depth (
max_loss) raises manipulation cost.
- cost grows nonlinearly with
- X-axis: target move
-
out/mechanism_analysis/plots/manipulation_endprob_curve_by_alpha.png- X-axis:
dp. - Y-axis: per-trade termination probability increment.
- Current behavior:
- monotonic in
dp; - higher
alphagives higher per-trade end probability.
- monotonic in
- X-axis:
-
out/mechanism_analysis/plots/solvency_deficit_over_bound_cdf.png- X-axis:
deficit / LMSR bound. - Current behavior:
- bound check passes on terminated runs:
bound_ok_rate = 1.0solvency_with_bond_rate = 1.0deficit_over_bound_max = 0.94398
- bound check passes on terminated runs:
- X-axis:
-
out/mechanism_analysis/plots/solvency_bond_residual_by_scenario.png- Bars show p05/mean/p95 bond residual.
- Current behavior:
- residual remains positive in both baseline and stress;
- indicates bond coverage margin after payout.
-
out/last_mover_normal_only/plots/last_mover_normal_only_budget.png- X-axis: attack budget as % of
b. - Curves: mean incremental attacker profit and profitable rate.
- Current behavior:
- mean incremental attacker profit is negative at every tested budget:
0.5% b: -0.0851.0% b: -0.3352.0% b: -1.2245.0% b: -2.412
- mean incremental attacker profit is negative at every tested budget:
- X-axis: attack budget as % of
Every script uses explicit integer seeds and deterministic scenario generation.
Implications:
- Re-running with identical commands reproduces row-level CSV outputs.
- Changing seed counts primarily tightens confidence intervals.
- Grid expansion changes runtime linearly with scenario count.
- This repository is simulation-only and chain-agnostic.
- It models mechanism-level behavior (pricing, termination, settlement, solvency, adversarial strategies).
- It does not model production infra details (RPC latency, mempool specifics, or execution relayers).
- Termination randomness in simulation is seeded PRNG; production deployment can source randomness from verifiable onchain randomness while keeping the same hazard-style termination model.