本文介绍了如何构建和优化一个以太坊上的MEV三明治机器人,包括代码分析、盈利能力测试以及竞争策略的讨论。
Fotor AI:“富有的吉娃娃聚会” 对不起,吉娃娃 #1 的耳朵被剪掉了
生成 AI 图像是一个有趣的过程。我坐下来写这篇文章,却发现自己不断生成随机的吉娃娃图像,直到找到了“那个。”这个过程涉及了很多的尝试和错误,我逐渐开始理解 AI 模型的工作原理,以及什么提示最适合我的目的。
同样,构建 MEV 机器人也很有趣,而且也需要不断磨练并了解我们的机器人为什么工作或不工作。但这与 AI 有些不同,因为在测试我们的策略时,我们实际上可能会赔钱。这就是为什么我们应该非常仔细地分析我们的竞争优势,并在开始运行系统之前测试所有内容。
今天,我们将从上周的进展继续:
构建三明治机器人的 100 小时 \ \ 从 A 到 Z:以正确的方式构建自己的三明治机器人\ \ medium.com
我们构建了一个可以从 Uniswap V2 分叉的 DEX 中检测三明治机会的系统。我们想看看通过将包发送到链上是否真的能获利。
对于还没有阅读代码的读者,这里是代码:
不过在开始之前,我要让你有点失望,提到这个 系统尚不盈利 还没有。但未来有可能。
在接下来的几周内,我们将深入研究该系统,并尝试为代码添加更多优化,使其更加具有竞争力。
整合这些附加功能将大大增加系统在实际交易中成功的可能性。
然而,必须注意的是,这次练习不应被视为在 MEV 市场中获利的捷径,因为确实没有这种捷径。当你成功实施所有功能并与我坚持到底时,你将意识到将你的系统转变为赢利系统所需的巨大努力。
我希望这些开源指南能够帮助个人理解 MEV 的本质,帮助他们在市场中发现真正的机会。
在我们继续之前,我想提到一件事。
我经常被很多人问到一个问题:
这个机器人有盈利吗?
而我从未给过他们一个明确的答案,因为在一个非常了解系统的人的手中,系统可能是有利可图的,而在另一个人手中则可能毫无用处。
在《海贼王》中,有一个我曾经非常喜欢的角色,罗布·路奇。
对那些不熟悉的人来说,罗布·路奇训练他的身体,使其本身成为一种武器。他的技能,指枪 👉,可以仅用食指致命地打击一个持有武器的人。
同样,我相信我们的所有系统都可以变得像指枪一样。如果你非常了解核心逻辑,它将变得具有竞争力。但如果你不为此付出努力,它就只是一个脆弱的手指。
今天的文章中我们将讨论一些有趣的话题。
如果你在某个地方遇到困难,请随时向他人询问他们是否遇到类似的问题,加入 Discord 服务器 🙏:
加入 Solid Quant Discord 服务器! \ \ 量化爱好者在做量化的事情 - 目前主要与 MEV 相关。 | 1535 位成员\ \ discord.com
让我们开始吧! 🏎
为了看看我们在真实三明治交易中是否有获胜的机会,我们将从上篇文章中我们停止的地方运行代码。
代码快照在 GitHub 仓库的不同分支上,可以通过 phase1 分支访问:
尝试通过以下命令运行代码:
cargo run
该程序将更新自我们上次运行以来在 Uniswap V2 上启动的新池和代币,并开始监控三明治机会。
我们让它运行一段时间 😴。
然后我们在 5 个区块后检测到了第一个三明治:
我们可以看到受害者的交易如下:
0xd1a41244a9aab38f41ce5fb54ce5ba3bcd20e07afb439bc968b720f5031feb80
这是一个使用 Universal Router 进行交易的交易。
优化后代币的金额为 2.0 WETH ,我们可以期望从三明治捆绑中获得 0.0516 WETH 的利润。
不过,这仅在不考虑Gas费用的情况下才成立。我们使用:
且基础费用为 22.27 gwei,因此我们的总Gas费用落在以下范围内:
0.00475 ~ 0.00498 ETH
_Gas费用可能会略有不同,因为存在Gas退款以及foundry-evm 如何计算这些费用,所以我们将采用模拟引擎计算出来的值,即0.00475 ETH。
然后我们的收入预计为:
让我们看看我们的三明治捆绑是否具有竞争力。
使用 Gambit Labs 的拍卖统计服务,我们可以弄清楚有多少人提交捆绑以捕获此机会,并查看他们向建筑商发送了多少贿赂。
在拍卖统计标签中粘贴交易哈希,看看我们有多具竞争力:
拍卖 - Gambit Labs \ \ 搜索者拍卖统计\ \ www.gmbit.co
你可以看到,很多同样的搜索者正在尝试捕获同样的机会:
我们当前的收入位于前 15~16 位
Gambit Labs 的最高贿赂者使用 0.058102 ETH 作为贿赂。而我们在考虑Gas费用后的收入为 0.0469 ETH,因此我们可以看到我们需要进一步优化我们的合约才能获胜。
这次,让我们去 Eigenphi ,看看哪位搜索者实际上赢得了这笔交易:
交易流程图 |… \ \ 一个交易可视化工具,生成代币流图、转账清单和平衡表,让你了解更多...\ \ eigenphi.io
而赢家是 Jared。没有意外,但我们想知道他是怎么做到的。
他的突袭交易看起来是这样的:
而他的接管交易看起来是这样的:
首先,我们检查我们的优化是否正确完成。看起来是的:
Jared 得到的优化金额为 2 余 WETH。
然而,这里是你开始感到困惑的部分。你会看到 Jared 在他的突袭和接管交易中有其他交易。正因为如此,他能够产生比其他三明治机器人更多的利润。
我们本可以预期的收入为:
0.0469 ETH * $2,300(当前 ETH 价格) = $107.87
而 Jared 能够产生 $160。
这是因为 Jared 还在他的突袭/接管交易中进行套利。
让我们简要看看他在突袭交易中做了什么:
你可以看到,Jared 在 Uniswap V3 和 V2 之间进行了一次两跳套利。他能够捕捉到两个 DEX 中 dogwifhat 池的套利机会,并将其添加到他的突袭交易中。
我们如何知道这是一种套利机会?
因为 Jared 在两个池中进行了交易:
它们都是与:
好吧,这真是倒霉……因为除非我们在系统中也实现套利,否则我们永远无法战胜 Jared。
现在还不需要太失望,因为一旦你看到他是如何退出他最初的 RSTK 代币(受害者试图通过 Universal Router 进行交易),情况只会更糟。
他的接管交易也相当复杂。不过,这与突袭交易一样,都是三明治和套利的组合。
现在,这很奇怪。我以为我们只需要使用初始的 Uniswap V2 池退出 RSTK 头寸,对吧?
并不是,因为如果你想想,任何与同一代币配对的不同池都可能存在价格差异,这意味着这些池之间可能存在套利机会。
而这正是 Jared 正在做的。Jared 的机器人检测到 Uniswap V3 和 V2 RSTK 池之间的价格差异并进行套利,从中获取额外利润。
因此,他能够在我们发现的同一机会中多赚至少 $50。
📍 Jared 实际上还有很多值得学习的地方。他还在做:
1. 在他的突袭交易中进行 JIT 流动性提供,2. 捕捉多个受害者交易进行三明治,3. 购买 meme 代币并使用这些代币执行非 WETH 三明治策略。但是这些都在我们掌握简单三明治 + 套利策略之后。
我希望这能让你对如今在以太坊三明治市场中获胜时需要多么具备竞争力有一个大致的了解。
让我们尝试让我们的三明治机器人运行更长的时间:
我们可以看到,我们每隔几个区块就会看到三明治机会。我希望你也能尝试同样的操作,并在以下方面进行比较:
这将给你一个非常清晰的理解,让你了解我们的代码还需要做多少优化。
在本部分中,我们终于将尝试向构建者端点发送一些真实的捆绑交易。正如你在前一部分所见,我们的三明治捆绑尚未具备竞争力,因此在优化代码之前,请不要指望你会获得捆绑交易。
我们仍然会查看如何将我们的捆绑交易广播到多个构建者,并查看此基本策略在 Gambit Labs 下的竞争力。我们将关注以下内容:
在提交我们的捆绑交易之前,第一步是将我们的智能合约部署到主网络。我们将使用 Foundry 来实现这一点。
在真实部署这里给定的合约之前:
我们将先快速运行一些测试,看看所有的功能是否按预期工作。
👉 首先,让我们看看是否可以向我们的合约发送 ETH 和 ERC-20 代币并将其找回。我在刚开始时犯的一个错误是忘记添加这个功能,结果我的 ETH 被锁在了合约中。希望我们知道在 Sandooo 合约中不会发生这种情况。
通过以下命令启动 Anvil 进程:
anvil --fork-url http://localhost:8545 --port 2000
我将在端口 2000 上启动主网络的分叉,并运行 Anvil。
接下来,在 sandooo/contracts/test/Sandooo.t.sol 中编写合约的测试功能,如下所示:
pragma solidity 0.8.20;
import "forge-std/Test.sol";
import "forge-std/console.sol";
import "../src/Sandooo.sol";
contract SandoooTest is Test {
Sandooo bot;
IWETH weth = IWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
receive() external payable {}
function test() public {
console.log("Sandooo bot test starting");
// 创建 Sandooo 实例
bot = new Sandooo();
uint256 amountIn = 100000000000000000; // 0.1 ETH
// 将 0.1 ETH 包裹为 0.1 WETH 并发送到 Sandooo 合约
weth.deposit{value: amountIn}();
weth.transfer(address(bot), amountIn);
// 检查 WETH 是否正确发送
uint256 botBalance = weth.balanceOf(address(bot));
console.log("Bot WETH balance: %s", botBalance);
// 检查我们是否可以找回 WETH
bot.recoverToken(address(weth), botBalance);
uint256 botBalanceAfterRecover = weth.balanceOf(address(bot));
console.log(
"Bot WETH balance after recover: %s",
botBalanceAfterRecover
); // 应为 0
// 检查我们是否可以找回 ETH
(bool s, ) = address(bot).call{value: amountIn}("");
console.log("ETH transfer: %s", s);
uint256 testEthBal = address(this).balance;
uint256 botEthBal = address(bot).balance;
console.log("Curr ETH balance: %s", testEthBal);
console.log("Bot ETH balance: %s", botEthBal);
// 向零地址发送以取回 ETH
bot.recoverToken(address(0), botEthBal);
uint256 testEthBalAfterRecover = address(this).balance;
uint256 botEthBalAfterRecover = address(bot).balance;
console.log("ETH balance after recover: %s", testEthBalAfterRecover);
console.log("Bot ETH balance after recover: %s", botEthBalAfterRecover);
console.log("============================");
}
}
并运行:
forge test --fork-url http://localhost:2000 --match-contract SandoooTest -vv
检查我们获得的日志:
[PASS] test() (gas: 265096)
Logs:
Sandooo bot test starting
Bot WETH balance: 100000000000000000
Bot WETH balance after recover: 0
ETH transfer: true
Curr ETH balance: 79228162514064337593543950335
Bot ETH balance: 100000000000000000
ETH balance after recover: 79228162514164337593543950335
Bot ETH balance after recover: 0
============================
你可以看到,在将资金发送到合约后,我们可以安全地找回我们的资金。
👉 接下来,我们将尝试在 Uniswap V2 对中进行模拟交换,以确认我们的合约确实有效。
尝试将以下内容添加到我们之前编写的测试函数中:
// 将 WETH 转移回合约
weth.transfer(address(bot), amountIn);
uint256 startingWethBalance = weth.balanceOf(address(bot));
console.log("Starting WETH balance: %s", startingWethBalance);
address usdt = 0xdAC17F958D2ee523a2206206994597C13D831ec7;
address wethUsdtV2 = 0x0d4a11d5EEaaC28EC3F61d100daF4d40471f1852;
IUniswapV2Pair pair = IUniswapV2Pair(wethUsdtV2);
address token0 = pair.token0();
address token1 = pair.token1();
// 我们将测试 WETH --> USDT
// 如果 WETH 是 token0,zeroForOne 为 0
uint8 zeroForOne = address(weth) == token0 ? 1 : 0;
// 使用储备量计算 amountOut
(uint112 reserve0, uint112 reserve1, ) = IUniswapV2Pair(address(pair))
.getReserves();
uint256 reserveIn;
uint256 reserveOut;
if (zeroForOne == 1) {
reserveIn = reserve0;
reserveOut = reserve1;
} else {
reserveIn = reserve1;
reserveOut = reserve0;
}
uint256 amountInWithFee = amountIn * 997;
uint256 numerator = amountInWithFee * reserveOut;
uint256 denominator = reserveIn * 1000 + amountInWithFee;
uint256 targetAmountOut = numerator / denominator;
console.log("Amount in: %s", amountIn);
console.log("Target amount out: %s", targetAmountOut);
bytes memory data = abi.encodePacked(
uint64(block.number), // blockNumber
uint8(zeroForOne), // zeroForOne
address(pair), // pair
address(weth), // tokenIn
uint256(amountIn), // amountIn
uint256(targetAmountOut) // amountOut
);
console.log("Calldata:");
console.logBytes(data);
uint gasBefore = gasleft();
(bool success, ) = address(bot).call(data);
uint gasAfter = gasleft();
uint gasUsed = gasBefore - gasAfter;
console.log("Swap success: %s", success);
console.log("Gas used: %s", gasUsed);
uint256 usdtBalance = IERC20(usdt).balanceOf(address(bot));
console.log("Bot USDT balance: %s", usdtBalance);
require(success, "FAILED");
我们将尝试使用 WETH 购买一些 USDT。
尝试使用以下命令运行测试:
forge test --fork-url http://localhost:2000 --match-contract SandoooTest -vv
我们将得到:
[PASS] test() (gas: 348846)
Logs:
Sandooo bot test starting
Bot WETH balance: 100000000000000000
Bot WETH balance after recover: 0
ETH transfer: true
Curr ETH balance: 79228162514064337593543950335
Bot ETH balance: 100000000000000000
ETH balance after recover: 79228162514164337593543950335
Bot ETH balance after recover: 0
============================
Starting WETH balance: 100000000000000000
Amount in: 100000000000000000
Target amount out: 229783289
Calldata:
0x0000000001244bcc010d4a11d5eeaac28ec3f61d100daf4d40471f1852c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000000000000000000000000000000000000db236f9
Swap success: true
Gas used: 82214
Bot USDT balance: 229783289
我们看到测试成功。
现在我们测试了合约,并看到所有功能都能正常工作,我们可以部署到主网络。
幸运的是,该合约非常简单,因此编译后的字节码如下:
0x6080604052600436101561001e575b361561001c5761001c61012d565b005b6000803560e01c80638da5cb5b146100d05763b29a814014610040575061000e565b3461009e57604036600319011261009e57806001600160a01b0360043581811681036100cc576100776024359284541633146100f5565b82811591826000146100a157505060011461008f5750f35b81808092335af11561009e5780f35b80fd5b60449250908093916040519263a9059cbb60e01b845233600485015260248401525af11561009e5780f35b5050fd5b503461009e578060031936011261009e57546001600160a01b03166080908152602090f35b156100fc57565b60405162461bcd60e51b81526020600482015260096024820152682727aa2fa7aba722a960b91b6044820152606490fd5b60008054610145906001600160a01b031633146100f5565b60405143823560c01c03610209576008600482019160248101925b36831061016e575050505050565b823560f81c926060906001810135821c916015820135901c9487806044878260298701359a6069604989013598019b63a9059cbb60e01b8452898b528d525af1156102055784888094819460a49463022c0d9f60e01b8552806000146101f9576001146101ee575b50306044840152608060648401525af1610160578480fd5b8288528a52386101d6565b508752818a52386101d6565b8780fd5b5080fdfea264697066735822122070cd8d8a51fe625e0f10f1ea26f94679859661cf1936f171d337a6616cfb19ad64736f6c63430008140033
这很短,不是吗?
我尝试运行以下命令在主网络上部署这个合约:
forge create --rpc-url <your_rpc_url> --private-key <your_private_key> src/Sandooo.sol:Sandooo
并且我用 181,016 的Gas费,基础费用为 10.6 gwei,最高费用为 21.57 gwei ,最高优先费用为 3 gwei,因此部署合约使用了 0.00246 ETH,即 $5.67。
尝试从 GitHub 中拉取代码,现在你将看到 phase2 分支已被合并到我们的 main 分支中。
在 sandooo/src 目录中找到了 execution.rs 文件:
如果你打算测试这个,请仔细检查所有逻辑并多加小心!在合约部署中操作的路径有很多,我总是格外小心以确保没有遗漏任何内容。 ( 🛑 同时,不要相信我在这篇文章中告诉你的任何内容,务必确认逻辑后再尝试在主网络上做任何事情。实际上,不要相信任何人 🛑 )**
修改 .env 文件中的 DEBUG 字段,以便代码能够在合约中使用真实的 WETH:
HTTPS_URL=http://localhost:8545
WSS_URL=ws://localhost:8546
BOT_ADDRESS=
PRIVATE_KEY=
IDENTITY_KEY=
TELEGRAM_TOKEN=
TELEGRAM_CHAT_ID=
USE_ALERT=false
DEBUG=false // <-- 将其改为 false 以使用真实机器人进行运行
RUST_BACKTRACE=1
确保添加真正用于部署机器人合约的地址的 BOT_ADDRESS 和 PRIVATE_KEY。
我尝试发送 0.5 WETH 到合约,以测试我的逻辑。
完成这些后,我们可以尝试运行我们的机器人:
cargo run
现在我们等着看会发生什么,祈祷一切顺利。 🙏
我在开始系统后 8 个区块发送了一个捆绑交易。
这次我们要关注日志中的 “捆绑交易已发送” 部分:
Bundle sent:
{
"gambit": SendBundleResponse { bundle_hash: 0x9b728ef4bb79af616b2aa9f49d703e2b2f1e28ce8a8115b6cb3622db4ec8ccaa },
"flashbots": SendBundleResponse { bundle_hash: 0x9b728ef4bb79af616b2aa9f49d703e2b2f1e28ce8a8115b6cb3622db4ec8ccaa },
"rsync": SendBundleResponse { bundle_hash: 0x0000000000000000000000000000000000000000000000000000000000000000 },
"penguinbuild": SendBundleResponse { bundle_hash: 0x0000000000000000000000000000000000000000000000000000000000000000 },
"titanbuilder": SendBundleResponse { bundle_hash: 0x63c4bbb135785635c9dde5b571a082800ede504597c09967d84614df7150f321 },
"builder0x69": SendBundleResponse { bundle_hash: 0x0000000000000000000000000000000000000000000000000000000000000000 },
"beaverbuild": SendBundleResponse { bundle_hash: 0x0d0a698ee45dae9a516582651e853f8426fb41ac74fe787f7b4b1059dcec5d95 }
}
我们看到我们成功向 Gambit Labs、Flashbots、Rsync、Penguin Build、Titan Builder、Builder0x69 和 Beaverbuild 发送了捆绑交易。
我们对 Gambit Labs 感兴趣,所以让我们前往他们的网站,查看交易哈希,看竞争情况是怎样的:
0x1924235bfe061560fd9725320cf4c26825422ea1aff42ec913a76e53370d7199
这是受害者的交易哈希:
拍卖 - Gambit Labs \ \ 搜索者拍卖统计\ \ www.gmbit.co
你可以在这里查看拍卖状态,我们的捆绑交易在竞争中位于底部:
将我们的贿赂金额与最高出价者相较,后者支付了 0.009547 ETH 的贿赂。最高出价者支付的贿赂是我们的两倍。
为了看看这可能是什么原因,接下来前往 Eigenphi,查看优化后价值的金额。我们的价值为 0.39728 WETH。
但正如你从这里看到的,我们的模拟速度仍然足够快:
我们的捆绑交易在一秒内发送,因此我们可以得出网络延迟不是问题。接下来让我们看看获胜的搜索者在做什么。
这与 Jared 得到的相同:
不过绝对不足以与他庞大的三明治竞争,看起来像这样:
所以,我们当然在前进,但我们确实需要找到提高机器人性能的方法。别担心,我们一定会找到解决方案。我们的系统目前只能进行 Uniswap V2 三明治,并且每个捆绑交易只能处理单个三明治,所以如果我们已经在赚钱,那么市场将会非常无聊。
在接下来的 2~3 篇文章中,我们将尽可能优化这个系统,添加:
并看看我们能否与这个系统竞争。
提示: 我们仍然无法做到。🤣 但我们会非常接近。我会给你一些建议,教你如何在这种市场中获胜。
如果你觉得这篇文章很有趣,并希望继续关注进展,请在 Twitter 上关注我! https://twitter.com/solidquant
- 原文链接: medium.com/@solidquant/l...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!