Searching and Ordering Sandwich Attacks and Arbitrage Opportunities
Miner/Maximal Extractable Value (MEV) is the additional profit a block producer or searcher can capture by choosing which transactions to include in a block and in what order to execute them. Two of the most studied MEV patterns are sandwich attacks against a victim’s AMM swap and arbitrage transaction ordering, where the profitability of each trade depends on the state left behind by the trades executed before it. Both problems are, at their core, optimization problems: one is a continuous sizing problem, the other is a combinatorial ordering problem. This article walks through both, with full working Python code for Google Colaboratory.
Mathematical Foundations
Constant-product AMM
For a pool holding reserves $x$ and $y$ of two tokens under the invariant $x \cdot y = k$, a swap of $\Delta x$ into the pool (with fee $\phi$) returns:
$$
\Delta y = \frac{y \cdot \Delta x (1-\phi)}{x + \Delta x (1-\phi)}
$$
Sandwich attack profit
Given a frontrun size $f$, a victim trade size $v$, and a victim slippage tolerance $s$, the attacker’s profit is:
$$
\Pi(f,s) =
\begin{cases}
x_{\text{unwind}}(f) - f - 2g & \text{if } I(f) > s \ \text{(victim tx reverts)} \
x_{\text{back}}(f,v) - f - 2g & \text{if } I(f) \le s \ \text{(victim tx executes)}
\end{cases}
$$
where $g$ is the gas cost per transaction and the price impact of the frontrun is:
$$
I(f) = \frac{p_0 - p_1(f)}{p_0}, \qquad p_0 = \frac{y}{x}
$$
Transaction ordering objective
For $n$ candidate arbitrage bundles sharing common pools, the profit of bundle $i$ depends on the pool state left by all bundles executed before it. The searcher wants to find the permutation $\sigma$ of execution order that maximizes total extracted value:
$$
\max_{\sigma \in S_n} ; \sum_{i=1}^{n} \Big[ \Pi_{\sigma(i)}\big(\text{state}(\sigma(1),\dots,\sigma(i-1))\big) - g \Big]
$$
Since $n!$ grows explosively, this is solved with simulated annealing, accepting a worse ordering with probability:
$$
P(\text{accept}) = \exp\left(\frac{\Delta \Pi}{T}\right), \qquad \Delta\Pi < 0
$$
as the temperature $T$ cools geometrically, $T_{k+1} = \alpha T_k$.
The Source Code
1 | import numpy as np |
Code Walkthrough
The Pool class implements a constant-product AMM. swap_x_for_y and swap_y_for_x apply the fee-adjusted output formula and mutate the reserves in place; clone() produces an independent copy so we can simulate hypothetical execution paths without touching the “real” pool state.
sandwich_profit is the core of the attack model. It clones the pool, applies the attacker’s frontrun, and measures how much the exchange rate degraded (impact). If that degradation exceeds the victim’s slippage tolerance s, the victim’s transaction would revert on-chain — the attacker is forced to immediately unwind the frontrun at a loss (fees plus gas, no arbitrage gain). Otherwise, the victim’s trade executes inside the sandwich, and the attacker’s backrun captures the price they pushed the market to. This branching behavior is what gives the profit landscape its characteristic cliff.
The grid scan (F, S, Z) evaluates sandwich_profit across a 40×40 grid of frontrun sizes and slippage tolerances. This is deliberately kept as an explicit double loop rather than vectorized, because the branching logic (revert vs. execute) does not vectorize cleanly with NumPy without significant complexity — at 1,600 evaluations of a cheap closed-form function, the loop runs in well under a second, so there is no practical need to optimize further.
minimize_scalar with method='bounded' finds the profit-maximizing frontrun size for each slippage tolerance value, turning the landscape into an actionable sizing curve.
The ordering half of the script models two DEX pools (poolA0, poolB0) holding the same token pair at slightly different prices — a textbook arbitrage setup. Eight independent arbitrage bundles are generated with random sizes and directions, simulating pending intents a searcher has detected in the mempool. Because every bundle touches one of only two shared pools, executing them in a different sequence changes the price gap each subsequent bundle sees, and therefore its profit — sometimes turning a profitable bundle into a losing one.
total_profit replays an entire ordering from a fresh clone of both pools and sums the net profit (trade proceeds minus gas) of every bundle in that sequence.
simulated_annealing searches permutation space by repeatedly swapping two positions in the current order, accepting improvements outright and accepting temporary regressions with probability $\exp(\Delta\Pi/T)$. The temperature T cools geometrically (cooling=0.997) over 3,000 iterations, which is enough for this problem size ($n=8$) to consistently escape local optima without needing an exhaustive $8! = 40{,}320$ search.
The gas cost gas is subtracted once per executed bundle, so the optimizer is not just maximizing gross arbitrage output — it is maximizing net miner-extractable value after transaction costs, exactly as a real searcher’s bundle-selection contract would.
On Performance
Both computational cores here are already fast by construction: the sandwich landscape is a $40\times40$ grid of closed-form AMM evaluations, and the annealing loop performs $3{,}000$ permutation evaluations over only $8$ bundles, each evaluation being $O(n)$ pool swaps. Total runtime on a standard Colab CPU runtime is on the order of one to two seconds end-to-end, so no further vectorization, caching, or parallelization is necessary — the bottleneck in real MEV searchers is network latency to the mempool, not this kind of local optimization.
Reading the Results
Figure 1 — Sandwich Profit Landscape. The 3D surface shows attacker profit as a function of frontrun size and the victim’s slippage tolerance. For a fixed slippage tolerance, profit rises with frontrun size up to a point and then collapses sharply — that cliff is exactly where the price impact crosses the victim’s tolerance threshold and the trade reverts, forcing the attacker into a lossy unwind.

Figure 2 — Optimal Frontrun Sizing Curve. This line chart shows how the profit-maximizing frontrun amount grows as the victim allows more slippage. Tighter slippage settings by end users directly translate into smaller extractable frontrun sizes — a concrete illustration of why slippage-tolerance defaults matter for MEV exposure.

Figure 3 — Simulated Annealing Convergence. The convergence curve shows the best total profit found climbing as the search escapes the initial ordering and settles into a near-optimal permutation, with the acceptance of temporarily worse moves early on (high temperature) giving way to a stable plateau as the temperature cools.
Naive (gas-price) order total profit : 7.37 SA-optimized order total profit : 7.92 Improvement : 0.55

Figure 4 — Naive vs. Optimized Ordering. The grouped bar chart compares per-bundle profit under a naive gas-price-priority ordering against the simulated-annealing-optimized ordering. Some bars flip from negative to positive between the two orderings, showing bundles that were unprofitable purely because of when they executed, not what they traded — the essence of transaction-ordering MEV.

Closing Thoughts
Sandwich sizing and transaction ordering look like very different problems at first glance — one continuous, one combinatorial — but both reduce to the same underlying question: given a shared, mutable piece of state (the AMM pool), how do you sequence and size your own transactions to maximize extracted value net of gas? The techniques shown here — bounded scalar optimization for sizing, simulated annealing for ordering — are the same general-purpose tools that show up across the rest of this optimization series, applied to a domain where the “state” happens to be a blockchain’s mempool and ledger rather than a physical or biological system.





























