name: defi-mev-battletest description: DeFi/MEV实战测试专家技能
DeFi/MEV 实战测试专家技能
强制性咨询: 任何DeFi机器人开发、MEV策略实施或自动交易系统都必须咨询这项技能。这里总结的真实世界失败案例和经验教训可以防止灾难性损失。
触发关键词
- 套利、MEV、搜索者、机器人、自动交易
- 回测、模拟、纸面交易
- 闪电贷、三明治攻击、抢先交易
- 滑点、价格影响、执行
- 重组、竞态条件、内存池
- 做市、流动性提供
1. 严重误区1:“套利无风险”神话
现实: 理论上0%风险,实际尾部风险=死亡
“无风险”套利中隐藏的风险:
❌ 执行风险
- 交易在消耗气体后被撤销
- 部分成交让你留下不想要的库存
- 目标协议中的合约漏洞
❌ 重组风险(严重)
- 你的利润交易可能被叔块
- 以太坊上1-2个区块重组每天都在发生
- 你的“利润”消失,气体成本仍然存在
❌ 气体价格飙升风险
- 基础费用在执行中途可能增加10倍
- 优先费拍卖消耗利润
- 失败的交易仍然需要全额气体
❌ 延迟风险
- 区块在你交易落地前已经被挖出
- 状态在模拟和执行之间发生变化
- 其他搜索者抢先你一步
真实数字:
// 你在回测中看到的:
const theoreticalProfit = 0.05; // 5%的清洁利润
// 实际上发生的:
const executionCosts = {
gasOnSuccess: 0.01, // 1%的气体
failureRate: 0.30, // 30%的交易失败
gasOnFailure: 0.01, // 仍然支付气体
reorgRate: 0.02, // 2%被重组
slippageSlip: 0.005, // 0.5%意外的滑点
};
// 真实期望值:
// 0.70 * (0.05 - 0.01) - 0.30 * 0.01 - 0.02 * 0.04 = 0.0238
// 理论利润的47%在MEV竞争之前就已经消失
2. 严重误区2:过度自信的回测
80%在生产中失败的机器人在回测中看起来很棒
为什么回测会撒谎:
❌ 历史状态≠未来区块状态
- 你正在模拟已知状态
- 实时:状态在区块之间变化
- 内存池竞争在历史数据中不可见
❌ 气体和延迟是事后未知的
- 你用实际的气体价格回测
- 实时:你必须预测气体价格
- 优先费拍卖是对抗性游戏
❌ 生存者偏差
- 你只看到成功的历史上的套利
- 失败的尝试没有记录在链上
- “发现”的机会可能已经被争夺
❌ 市场影响被忽视
- 你自己的交易改变了市场
- 流动性在你最需要的时候枯竭
- 大型交易对你不利地移动价格
正确的方法:
// 糟糕:用完美信息回测
async function badBacktest(historicalData) {
for (const block of historicalData) {
const profit = simulateWithPerfectState(block);
totalProfit += profit;
}
return totalProfit; // 撒谎
}
// 好的:用现实条件进行区块模拟
async function realisticTest(pendingBlock) {
// 1. 对PENDING状态进行模拟(未经确认)
// 2. 添加现实延迟(50-200毫秒)
// 3. 假设30%的失败率
// 4. 假设20%的“机会”是诱饵
// 5. 添加气体价格不确定性(±30%)
const simResult = await simulateOnPendingState(pendingBlock);
const adjustedProfit = simResult.profit
* 0.70 // 成功率
* 0.80 // 不是诱饵
- estimatedGas * 1.30; // 气体不确定性
return adjustedProfit;
}
3. 严重误区3:AMM≠订单簿
错误的滑点模型=悄悄流血
Uniswap V3特定陷阱:
// V3 Tick流动性是非均匀的
// 两个Tick之间的流动性可以是零!
interface V3Reality {
// 你期望的:
linearSlippage: false,
// 实际上发生的:
tickCrossing: '每个Tick=单独的费用支付',
liquidityGaps: '可以跳过0流动性的Tick',
concentratedLiquidity: '大部分流动性在狭窄范围内',
// 价格影响是阶梯函数而不是曲线:
// 小型交易: 0.01%的影响
// 中型交易: 0.5%的影响(穿过Tick)
// 大型交易: 5%的影响(穿过多个Ticks)
}
// 费用等级选择非常重要
const feeTiers = {
'0.01%': '仅限稳定币,超紧密的价差',
'0.05%': '相关对(ETH/stETH)',
'0.30%': '大多数对,默认选择',
'1.00%': '异国情调对,流动性低',
};
// 错误:使用0.3%池进行稳定币交换
// 你支付的费用比必要的多6倍
// 错误:使用0.05%池进行波动对
// 池不存在或没有流动性
Curve特定陷阱:
// Curve StableSwap有不同的数学
// A因子决定曲线形状
interface CurveReality {
amplificationFactor: number, // A = 100典型
// 低A:更像是恒定产品(Uniswap V2)
// 高A:更像是恒定总和(1:1交换)
// 对稳定币的价格影响要低得多
// 但在脱钩时会高得多
depegRisk: 'Curve池可以在脱钩期间困住你',
}
// USDC脱钩示例(2023年3月):
// 预期:交换USDC→DAI在0.99
// 现实:池被排空,10%以上的滑点
4. 严重误区4:MEV低估
公共内存池=免费Alpha捐赠
MEV食物链:
你的交易 → 公共内存池 → 搜索者看到它
↓
三明治攻击(你是肉)
↓
你的“利润”成为他们的利润
私人订单流是桌面赌注:
// 如果你不使用私人提交,你没有优势
const submissionMethods = {
// 公共(你将被提取)
publicRPC: 'eth_sendRawTransaction', // 套利永远不要用
// 私人(最低可行)
flashbotsProtect: 'protect.flashbots.net', // 免费,基础
mevBlocker: 'rpc.mevblocker.io', // 免费,好
// 竞争(对严肃的搜索者)
flashbotsBundle: 'relay.flashbots.net', // 捆绑提交
mevShare: '与用户共享MEV', // 对某些流程是必需的
// 构建者直接(高级)
builderAPI: '直接到区块构建者', // 最低延迟
};
MEV-Share现实:
// 新范式:用户获得回扣
// 搜索者必须分享利润
interface MEVShareEconomics {
userShare: '50-90%的MEV',
searcherShare: '10-50%的MEV',
// 这意味着:
// 你的套利机会更小
// 竞争更高
// 只有超高效的搜索者才能生存
}
5. 必读资源(10篇文章=1年经验)
第1层:基础(首先阅读)
📚 Paradigm Research
- "Liquidity Book" - 从第一原理出发的AMM数学
- "MEV... Wat Do?" - MEV分类
- research.paradigm.xyz上的每篇文章
📚 Flashbots Docs
- "MEV-Share" - 订单流拍卖设计
- "Searching Post-Merge" - 新的MEV景观
- docs.flashbots.net(整个网站)
第2层:实际失败(从他人的损失中学习)
在Twitter/X上搜索:
- "post-mortem"
- "we lost money because"
- "unexpected behavior"
- "exploit" + 协议名称
真正的教训来自损失的钱。
第3层:代码学习(跳过星标计数,检查内容)
在GitHub上搜索:
- MEV搜索机器人(带重组处理)
- Uniswap V3数学库
- 捆绑模拟代码
README关键词表明质量:
✅ "reorg handling"
✅ "race condition"
✅ "bundle simulation"
✅ "private mempool"
❌ "simple arbitrage"
❌ "guaranteed profit"
❌ "no risk"
第4层:关注这些账户
@bertcmiller - MEV搜索者,实用见解
@hasufl - DeFi经济学,机制设计
@samczsun - 安全,漏洞,真实失败
@0xfoobar - 技术MEV,搜索者视角
@barnabe_monnot - PBS,MEV-Boost内部
6. 架构原则(不容商量)
关心分离:
// 至关重要:执行引擎与策略分开
class Architecture {
// 策略层(做什么)
strategyEngine: {
findOpportunities(): Opportunity[],
evaluateRisk(): RiskAssessment,
calculateSize(): PositionSize,
};
// 执行层(怎么做)
executionEngine: {
buildTransaction(): Transaction,
simulateBundle(): SimResult,
submitPrivate(): TxHash,
handleReorg(): void,
};
// 风险层(何时停止)
riskEngine: {
killSwitch(): void, // 必须存在
capitalAtRiskLimit(): USD, // 必须设置
maxLossPerHour(): USD, // 断路器
maxConsecutiveLosses(): number,
};
}
杀开关要求:
// 非谈判:每个机器人都需要这些
interface KillSwitchConfig {
// 自动触发器
maxDrawdown: '5%的资本',
maxHourlyLoss: '$100',
maxDailyLoss: '$500',
consecutiveLosses: 5,
gasSpike: '10倍正常',
// 手动覆盖
emergencyStop: '硬件按钮或单独的过程',
// 状态保留
onKill: '记录状态,关闭位置,通知',
}
// 糟糕:“我稍后会添加杀开关”
// 好:杀开关是第一个实现的功能
7. 模拟优先开发
不是纸面交易 - 区块模拟:
// 纸面交易:对真实市场的假订单
// 区块模拟:对模拟状态的真实订单
interface SimulationApproach {
// 1级:单元测试数学
testAMMFormulas(): void,
testSlippageCalc(): void,
// 2级:状态分叉模拟
forkMainnet(): LocalFork,
simulateTrade(fork): SimResult,
// 3级:待处理区块模拟
getPendingBlock(): Block,
simulateInPending(): SimResult,
// 4级:捆绑模拟
buildBundle(): Bundle,
simulateBundle(): BundleSimResult,
// 5级:竞争模拟
assumeCompetitors(): number,
simulateAuction(): AuctionResult,
}
Foundry/Anvil分叉测试:
# 在特定区块分叉主网
anvil --fork-url $ETH_RPC --fork-block-number 18500000
# 运行模拟
forge script SimulateArb --rpc-url http://localhost:8545
8. 真实失败模式(来自生产)
失败模式1:状态陈旧
// 你模拟的是区块N
// 你提交到区块N+1
// 状态变化→交易回滚→气体丢失
// 解决方案:
const maxStateAge = 1; // 区块
const stateCheck = async () => {
const currentBlock = await getBlockNumber();
if (currentBlock > simulationBlock + maxStateAge) {
return ABORT; // 不要提交陈旧的交易
}
};
失败模式2:三明治诱饵
// 搜索者放置的“机会”
// 你接受它→被三明治攻击→损失超过“利润”
// 解决方案:
const isBait = (opportunity) => {
// 检查机会是否最近出现在内存池中
// 检查流动性是否可疑
// 检查利润是否“太好”
return suspiciousScore > THRESHOLD;
};
失败模式3:气体价格预测
// 你出价10 gwei优先费
// 区块以50 gwei最低价格落地
// 你的交易未被包含→机会消失
// 解决方案:
const dynamicGas = async () => {
const pending = await getPendingBlock();
const competitorBids = analyzeCompetitorGas(pending);
const minViableBid = percentile(competitorBids, 80);
if (minViableBid > profitableThreshold) {
return SKIP; // 不值得竞争
}
return minViableBid * 1.1; // 轻微超额出价
};
失败模式4:部分执行
// 多腿套利:腿1执行,腿2回滚
// 你被卡住了不想要的代币
// 解决方案:
const atomicExecution = {
// 所有腿都在一个交易中
useFlashLoan: true, // 如果无利可图则回滚整个交易
// 或者:使用智能合约检查最终状态
checkInvariant: 'finalBalance >= initialBalance + minProfit',
};
9. 上线前的清单
□ 杀开关已实现并测试
□ 资本风险限制已设置
□ 私人内存池提交已配置
□ 重组处理已实现
□ 状态陈旧性检查已添加
□ 气体价格预测已测试
□ 失败率已计入期望值
□ 模拟与生产匹配(在20%以内)
□ 日志捕获所有失败模式
□ 异常警报系统
□ 手动紧急停止可访问
□ 用真钱测试(小额)1周
10. 期望值计算(现实)
// 实际上重要的公式:
function realExpectedValue(opportunity: Opportunity): number {
const {
grossProfit,
gasOnSuccess,
failureRate,
gasOnFailure,
reorgRate,
competitionRate,
baitRate,
} = analyzeOpportunity(opportunity);
// 成功案例
const successProfit = grossProfit - gasOnSuccess;
const successProb = (1 - failureRate) * (1 - reorgRate) * (1 - competitionRate) * (1 - baitRate);
// 失败案例
const failureCost = gasOnFailure;
const failureProb = failureRate;
const reorgCost = gasOnSuccess; // 已经支付的气体
const reorgProb = reorgRate * (1 - failureRate);
// 期望值
const EV = (successProb * successProfit)
- (failureProb * failureCost)
- (reorgProb * reorgCost);
// 如果EV < 0, 不要执行
// 如果EV < minThreshold, 可能不值得冒险
return EV;
}
// 用现实数字的例子:
// 毛利润: $100
// 气体(成功): $5
// 气体(失败): $5
// 失败率: 30%
// 重组率: 2%
// 竞争率: 50%
// 诱饵率: 5%
// 成功率: 0.70 * 0.98 * 0.50 * 0.95 = 0.326
// EV = 0.326 * $95 - 0.30 * $5 - 0.014 * $5 = $29.27
// 你的“$100机会”实际上值~$29
// 这还是在没有计算你的基础设施成本之前
11. 嵌入式知识:MEV-Share技术深入
这些知识已经嵌入 - 不需要获取外部文档。
MEV-Share实际上是如何工作的
// MEV-Share显示提示,而不是完整交易
// 这是与公共内存池的关键区别
interface MEVShareHints {
// 你可以看到:
logs?: Log[], // 事件日志(部分)
calldata?: string, // 函数选择器只有(前4个字节)
contractAddress?: Address,
functionSelector?: string,
// 你不能看到的:
fullCalldata: 'HIDDEN', // 没有参数
value: 'HIDDEN', // 没有ETH金额
from: 'HIDDEN', // 没有发送者地址
}
// 策略转变所需:
// 旧的(公共内存池):看到完整交易→计算确切的三明治
// 新的(MEV-Share):看到提示→概率性回溯只有
const mevShareStrategy = {
// 仍然有效的:
backrunning: true, // 等待交易,用你的套利回溯
// 更难的:
sandwiching: 'limited', // 不能计算确切的前跑
// 关键见解:
// 你在部分信息上出价
// 必须与用户分享利润(退款机制)
};
MEV-Share客户端实现
import { MevShareClient } from '@flashbots/mev-share-client';
// 连接到MEV-Share SSE流
const mevShareClient = new MevShareClient({
authSigner: wallet,
networkConfig: {
streamUrl: 'https://mev-share.flashbots.net',
bundleSubmitUrl: 'https://relay.flashbots.net',
},
});
// 监听待处理交易(只有提示)
mevShareClient.on('transaction', async (tx) => {
// tx.hash - 待处理交易哈希
// tx.logs - 部分事件日志
// tx.to - 目标合约
// tx.functionSelector - 调用数据的前4个字节
// 你得不到:完整的调用数据,从地址,价值
const backrunTx = await buildBackrun(tx);
// 捆绑必须包括原始交易哈希
await mevShareClient.sendBundle({
inclusion: { block: currentBlock + 1 },
body: [
{ hash: tx.hash }, // 原始交易(通过哈希引用)
{ tx: backrunTx }, // 你的回溯
],
privacy: { hints: ['calldata', 'logs'] },
});
});
12. 嵌入式知识:AMM价格影响数学
恒定产品公式(Uniswap V2风格):
// 基本不变: x * y = k
// x = token0储备
// y = token1储备
// k = 常数(随着费用增加)
// 价格影响公式(记住这个):
// 对于Δx大小的交易:
// 价格影响≈ 2 * Δx / x
//
// 示例:在有100 ETH的池中交易1 ETH
// 影响≈ 2 * 1 / 100 = 2%
function calculatePriceImpact(
tradeSize: bigint,
reserveIn: bigint
): number {
// 经验法则:影响=你的订单相对于池的2倍
return (2 * Number(tradeSize)) / Number(reserveIn);
}
// 为什么是2倍?
// 因为你正在移动价格从现货到执行
// 平均执行价格在开始和结束之间
// 这创造了~2倍的“天真”计算
// 确切的金额公式:
function getAmountOut(
amountIn: bigint,
reserveIn: bigint,
reserveOut: bigint,
feeBps: number = 30 // 0.3% = 30 bps
): bigint {
const amountInWithFee = amountIn * BigInt(10000 - feeBps);
const numerator = amountInWithFee * reserveOut;
const denominator = reserveIn * 10000n + amountInWithFee;
return numerator / denominator;
}
滑点与价格影响(常见混淆)
// 价格影响:确定性的,基于你的交易大小
// 滑点:不确定的,报价和执行之间的价格移动
interface TradeExecution {
// 在报价时:
spotPrice: number,
expectedOutput: bigint,
// 你的设置:
maxSlippageBps: 50, // 允许的滑点0.5%
// 在执行时:
priceImpact: '你的交易移动池',
slippage: '其他交易从报价移动池',
// 总成本=价格影响+滑点
// 如果总数> maxSlippageBps → 交易回滚
}
// 最佳实践:
// 1. 从池数学估计价格影响
// 2. 为滑点添加缓冲区(取决于波动性)
// 3. 除非你知道为什么,否则不要将滑点设置> 1%
// 4. 如果滑点很高,监控三明治攻击
13. 嵌入式知识:Uniswap V3 Tick机制
为什么是1.0001?基点标准:
// 每个Tick代表1个基点(0.01%)价格变化
// tick_spacing决定了哪些Ticks是可用的
const TICK_BASE = 1.0001; // 每个Tick的价格乘数
// 在Tick i的价格:
// price(i) = 1.0001^i
// 示例:
// Tick 0: price = 1.0001^0 = 1.000
// Tick 100: price = 1.0001^100 ≈ 1.0101(高1.01%)
// Tick 1000: price = 1.0001^1000 ≈ 1.1052(高10.52%)
function tickToPrice(tick: number): number {
return Math.pow(1.0001, tick);
}
function priceToTick(price: number): number {
return Math.floor(Math.log(price) / Math.log(1.0001));
}
费用等级和Tick间距
// 至关重要:Tick间距因费用等级而异
// 这影响了流动性的粒度
const V3_FEE_TIERS = {
100: { // 0.01%费用
tickSpacing: 1,
useCase: '稳定币(USDC/USDT)',
typicalSpread: '0.01-0.02%',
},
500: { // 0.05%费用
tickSpacing: 10,
useCase: '相关对(ETH/stETH,WBTC/renBTC)',
typicalSpread: '0.05-0.10%',
},
3000: { // 0.30%费用
tickSpacing: 60,
useCase: '大多数对(ETH/USDC等)',
typicalSpread: '0.20-0.50%',
},
10000: { // 1.00%费用
tickSpacing: 200,
useCase: '异国情调/低流动性对',
typicalSpread: '0.50-2.00%',
},
};
// 这对MEV为什么重要:
// 1. 集中流动性意味着不连续的价格影响
// 2. 穿过Tick边界=在该Tick的流动性上支付费用
// 3. 大型交易可以“穿透”低流动性Ticks
读取V3池状态
// slot0调用给你当前状态
interface Slot0 {
sqrtPriceX96: bigint, // sqrt(price) * 2^96
tick: number, // 当前Tick
observationIndex: number,
observationCardinality: number,
observationCardinalityNext: number,
feeProtocol: number,
unlocked: boolean,
}
// 将sqrtPriceX96转换为人类可读的价格:
function sqrtPriceToPrice(
sqrtPriceX96: bigint,
decimals0: number,
decimals1: number
): number {
const Q96 = 2n ** 96n;
const price = (sqrtPriceX96 * sqrtPriceX96) / (Q96 * Q96);
const decimalAdjustment = 10 ** (decimals0 - decimals1);
return Number(price) * decimalAdjustment;
}
// 特定Ticks的流动性:
// 使用ticks(tickIndex)获取liquidityNet
// liquidityNet = 穿过这个Tick时流动性的变化
// 正数=价格上涨时增加流动性
// 负数=价格上涨时移除流动性
14. 嵌入式知识:Flashbots捆绑提交
捆绑=原子序列交易
import { FlashbotsBundleProvider } from '@flashbots/ethers-provider-bundle';
// 捆绑提交流程:
// 1. 构建交易
// 2. 对待处理状态进行模拟
// 3. 提交到Flashbots中继
// 4. 等待包含或拒绝
const flashbotsProvider = await FlashbotsBundleProvider.create(
provider,
authSigner,
'https://relay.flashbots.net'
);
// 构建捆绑
const bundle = [
{
signer: wallet,
transaction: {
to: targetContract,
data: calldata,
gasLimit: 500000,
maxFeePerGas: parseGwei('50'),
maxPriorityFeePerGas: parseGwei('3'),
type: 2,
},
},
];
// 提交前模拟
const simulation = await flashbotsProvider.simulate(
bundle,
targetBlock
);
if (simulation.firstRevert) {
console.log('Bundle would revert:', simulation.firstRevert);
return; // 不要提交失败的捆绑
}
// 计算盈利能力
const profit = simulation.results[0].value - simulation.totalGasUsed * gasPrice;
if (profit <= 0) {
return; // 气体之后无利可图
}
// 提交捆绑
const bundleSubmission = await flashbotsProvider.sendBundle(
bundle,
targetBlock
);
// 等待解决
const resolution = await bundleSubmission.wait();
if (resolution === FlashbotsBundleResolution.BundleIncluded) {
console.log('Bundle included!');
} else if (resolution === FlashbotsBundleResolution.BlockPassedWithoutInclusion) {
console.log('Bundle not included - outbid or block full');
} else {
console.log('Bundle rejected by relay');
}
捆绑优先费拍卖
// Flashbots使用有效优先费排序
// effectiveGasPrice = min(maxFeePerGas, baseFee + maxPriorityFeePerGas)
// Coinbase转账技巧:
// 而不是高优先费,直接支付构建者
// 这隐藏了你的出价,不让竞争对手看到
const bundleWithCoinbasePayment = [
// 你的有利可图的交易
{
signer: wallet,
transaction: arbTx,
},
// 支付区块构建者
{
signer: wallet,
transaction: {
to: 'builder.coinbase', // 特殊:给区块构建者
value: parseEther('0.01'), // 你的“出价”
},
},
];
// 为什么Coinbase支付?
// 1. 优先费在内存池模拟中可见
// 2. 竞争对手可以看到并出价
// 3. Coinbase支付在区块落地前是私有的
15. 快速参考备忘单
// === 价格影响 ===
// V2风格:影响≈ 2 * 交易大小 / 池储备
// V3风格:取决于流动性分布,检查每个Tick
// === MEV-SHARE ===
// 你可以看到:日志,函数选择器,目标合约
// 你看不到:调用数据参数,价值,发送者
// 策略:只回溯,分享利润
// === 气体估计 ===
// 简单交换:100-150k气体
// V3多跳:200-400k气体
// 闪电贷+套利:400-800k气体
// 总是增加20%的缓冲区
// === TICK数学 ===
// price(tick) = 1.0001^tick
// tick(price) = log(price) / log(1.0001)
// 费用等级→Tick间距:0.01%→1, 0.05%→10, 0.3%→60, 1%→200
// === 捆绑提交 ===
// 1. 先模拟
// 2. 检查气体后的利润
// 3. 用Coinbase支付进行竞争性出价
// 4. 优雅地处理BlockPassedWithoutInclusion
// === 红旗(中止) ===
// - 价格影响> 1%在“小”交易上
// - 机会利润< 2倍气体成本
// - 未知代币未经验证
// - 池创建< 24小时前
// - 单边流动性(地毯设置)
记住: DeFi机器人的墓地里充满了认为自己找到了优势但没有考虑到这些现实的开发者。阅读事后分析。从他人的损失中学习。市场是对抗性的 - 假设每个人都在试图从你那里提取价值。