写在前面: 本文是 https://github.com/fuzzland/fourmeme-god.git 的学习笔记
三明治机器人原理
有人使用较大的滑点进行交易, 称此交易为 victim tx ,后面简称为 vtx
滑点代表的是对价格上升的容忍程度,较大的滑点导致在 vtx 买入代币,并且在 vtx 之后卖出代币的模式变得有利可图
三明治机器人构造 buy tx 和 sell tx(可能还有额外的 approve tx,以保证卖出交易的成功),并且将这些交易与原始的 vtx 按照如下顺序排列:[buy tx, vtx, approve tx(optional), sell tx]
找一个类似 flashbots 的交易捆绑服务商,将上面的交易 bundle(即上述的 tx 数组) 提交
构造一个三明治机器人的主要难点:
检测到有利可图的交易、协议
在合理的时间范围内构造出利润最大化的 bundle
个人感觉第 1 点,尤其对于短暂出现的 alpha 机会, 才是真正的难点。比如本例中的 meme 协议,合并并未开源,如何从一份未开源合约中提取到完成套利机器人的相关信息,是很复杂的.如何完成这一点在本文中不再展开,我们只需要知道 meme 协议是一个常量乘积 AMM 算法:即
x * y = k 并且会收取一定的交易手续费(体现在后续代码中的 fee_rate 和 min_fee)
下面主要讲套利交易构造部分: fourmeme-god 是基于 burberry (一个模块化的 mev 开发框架) 开发的。burberry 中, 一个 mev 项目被模块化的分解为:
collector(s), 负责搜集数据。支持多方数据源,异构数据源等
strategy, 负责解析搜集的数据,并且根据套利算法进行套利交易的构造
executor, 负责执行套利交易的最终执行
在本项目中, collectors 采用了两个 burberry 内置的数据采集类,主要负责采集最新的区块信息和 mempool pending tx 信息.两个数据源的主要作用分别为:
block 信息用于探测其他机器人竞争者,并且在后续的三明治套利中避免处理这些机器人地址的相关交易
pending tx 信息用于识别潜在的 victim tx.
在 strategy 中整体流程如下:
检查交易是不是一个 meme::Buy tx (通过解析 tx.data)
对于 buy 交易,根据 meme 当前池子信息和这个 buy tx 信息,计算最优的三明治交易组合(即计算提前买入多少)
计算一下加上 gas 消耗以后是否还有利可图(gas 根据链上交易设定固定值进行估算)
构造 bundle 提交到区块链上完成交易(未实现,可以自己选择 bundle 服务提供商)
这里重点讲一下 search::go
的逻辑。在不考虑任何数学和代码之前,很容易得到一个感性的结论:
在一个临界点之前, 三明治机器人的利润肯定是随着买入 mev 机器人提前买入的代币数量的增加而增加。
这个临界点存在的原因是买入太多会导致滑点太大,进而让 victim tx 失败 那如何找到这个临界点呢? 这里简单使用了一个类似二分搜索法的搜索: 将输入区间标记为 4 个点
起点----起点+m(A)----起点+2m(B)----终点
我们只需要反复比较买入量为 A 和 B 时候,机器人的利润,然后再进行区间的收缩即可。即如果 Porfit(B) < Profit(A) ,则代表 B 已经超过了上面提到的临界点,最优解必然在起点到 B 之间的一个点, 我们重新对这部分输入进行等分搜索。通过反复迭代,我们可以找到最优解。(你可以问问 ai 看看有什么更好的寻求解的办法)需要指出的是,因为 mev 本质上还是需要和时间赛跑,而且最优解也没有那么重要,所以迭代次数被设置为 100,这个数据可以根据你的 CPU 性能进行调优(比如机器差,就改小点,机器好就调大一点).
至于如何计算利润也比较简单,流程也是比较明朗的,给定你希望买入的 eth 数量(也就是 amount_in):
先计算可以 buy tx 的 amount_out(买入的代币数量)
模拟更新池子信息(内存中的数据)
按照 victim tx 进行计算, 得到 victim tx 真正可以得到的代币
模拟更新池子信息
构造卖出交易(卖出数量为 1 中的 amount_out), 得到卖出获取的 eth 数量
计算 mev 机器人的整体收益,即最终卖出 ETH 的数量减去 amount_in + fee
至此主要的逻辑我已经梳理完成,不过在读代码的过程中,我也遇到一些不理解的地方:
trial_ultimate(&mut context, lower_bound + m)
函数调用使用的相同的 context 引用,这会持续修改 context(也就是内存中虚拟池子的信息),但是这个修改其实是不应该发生的,也就是计算不同点的利润的时候,输入都应该是相同的 context. 不确定是代码确实有问题还是我的理解有误. 后面希望可以和作者请教讨论