我在以太坊 mempool 亲历了超级掠食者「套利机器人」。

撰文:Dan Robinson 与 Paradigm 研究合伙人 Georgios Konstantopoulos,专注于区块链可扩展性、信息安全的独立研究员
编译:Perry Wang

这是一个恐怖故事。

挑战

像普通人一样,我在 Uniswap Discord 的#support 频道中花费了大量时间。 (信息披露:Uniswap 是 Paradigm 参与投资的公司之一。)

周三下午,有人提问:如果把代币误发送给对智能合约本身的代币交易对,是否有可能把代币拿回来?

我最初的想法是这些代币将永远被锁定。但是那天深夜,我突然意识到,如果代币仍然在那儿,它们可能会被拿走——被任何人拿走。

当任何人在 Uniswap 核心合同上调用焚毁 burn 功能时,该合同将衡量自身的流动性代币余额并进行 burn ,将提取的代币返给调用方指定的地址。这是 Uniswap v2 预期行为的核心部分(基本机制在 Uniswap v2 白皮书的第 3 章 第 2 节中进行了描述)。

我找到了该合同。流动性代币仍然存在,价值约 12,000 美元。

这意味着三件事:

  • 存在一个滴答作响的时钟。即使没有其他人注意到这笔免费的资金,任何人都可以随时撤走自己的流动资金,意外地从合同中收到代币。
  • 我可以扮演白帽黑客,尝试为将代币误发送到 Uniswap 合约的人找回代币。非常简单,只需要在资金池中调用 burn 函数,并将代币传递到自己的地址。
  • 除非……我知道它不是那么简单。

黑暗森林

众所周知以太坊区块链是一个危机四伏的环境。如果能通过抓住某一智能合约的漏洞牟利,总会有人这么干的。新黑客攻击的频率表明,一些非常聪明的人花费大量时间来寻找有漏洞的合约。

但是与内存池 mempool (待处理、未确认的交易集)相比,这种暗藏杀机的环境不值一提。如果这条区块链本身是战场,那么 mempool 更糟糕:那就是一片黑暗森林。

《黑暗森林》The Dark Forest 是我个人最喜欢的科幻小说。正是因为这本书而有了「黑暗森林」这一概念——在这环境中高级掠食者不断制造杀戮。在这种环境中,暴露某人的藏身之处无异于直接毁掉他 / 她。(这一概念也是以太坊测试网中黑暗森林 Dark Forest 游戏的灵感来源。)

在以太坊 mempool 中,这些超级掠食者是以「套利机器人」形态存在。套利机器人监视着待处理交易,试图从这些交易造成的每个牟利机会中敲骨吸髓。最深谙这些机器人的白帽黑客是 Phil Daian,这位智能合约研究者与其同事撰写了 Flash Boys 2.0 论文,创造了「矿工可提取价值」(MEV)这一概念。

Phil 曾对我描述过一个宇宙级的恐怖杀手,他称其为「广义抢跑者」。套利机器人通常会在 mempool 中查找特定类型的交易(例如去中心化交易所 DEX 的交易或预言机更新),并尝试根据预定算法抢先截胡。广义抢跑者寻找能够从中赚钱的任何交易,迅速复制其交易并用自己的地址替换原交易中的地址。它们甚至可以执行交易并复制由其执行轨迹生成的有利可图的内部交易。

这就是为什么我说要救回上述那笔误发送的资金并非如此简单的原因。任何人都可以调用这一 burn  功能。如果我提交了一项 burn  交易,那就像是霓虹灯般的「免费资金」标志,直接向外界宣告了这个获利机会。如果这些恐怖杀手确实在 mempool 中,就会看到这一交易,迅速复制,移花接木并接管我的交易,在我的交易之前拿走了钱。

请注意,这种环境比以太坊区块链状态本身还要残酷得多。这些免费资金已经在以太坊区块链上呆了大约八个小时而未被发现,静待某个 burn  者席卷。但是任何试图拿走这笔钱的尝试都会迅速在空中被准确狙击。

拯救

要想不打草惊蛇、不惊动机器人的情况下提取资金,我需要对交易进行瞒天过海的处理,以便机器人无法检测到对 Uniswap 交易对的调用。这涉及编码和部署定制合约。我是 DeFi 领域专业的思想领袖,我以前从未向以太坊实际部署任何合约。

我需要援手,而当时美国时间已经过了午夜。幸运的是,我认识的一些最好的智能合约工程师住在欧洲时区。我的 Paradigm 同事 Georgios Konstantopoulos 同意协助部署合约和提交交易。我们投资的另一家公司 Yield 的首席工程师 Alberto CuestaCañada 自告奋勇执行合约。

一些优秀的以太坊安全工程师帮助我们制定了一个瞒天过海的计划。除了将调用隐藏为内部交易之外,我们还将交易分为两部分:一个激活我们合约的 set 交易,以及一个拯救激活(如果合约已激活)资金的 get 交易。以如下路径实现:

  1. 部署一个 Getter 合约,由其主人调用,只有在激活后才会做出 burn 调用,否则恢复原状。
  2. 部署一个 Setter 合约,由其主人调用,将激活 Getter 合约。
  3. 在同一区块提交 set 交易和 get 交易。

我们智能合约的代码

如果攻击者只试图执行这一 get 交易,就会在不调用 burn 功能的情况下让合约恢复原状。我们原本希望的是在攻击者先后执行 set 和 get 交易,发现内部调用 pool.burn 的指令,然后试图对我们超车时,我们已经完成了交易。

我们拯救这笔钱的代码脚本:

 1 2 3 4 5 6 7 8 91011121314151617181920212223242526272829303132333435363738394041