今天要分享的是前阵子两千万枚OP 代币被攻击者盗取的事件。这个事件源自于Wintermute 造市商提供错误的地址给Optimism,导致代币转入没有人拥有的地址,后来却被攻击者抢先拥有这个地址。这篇内容我们将以这个事件为背景,介绍create 和create2 的概念。

事件

起初,Optimism 基金会聘请Wintermute为其在中心化交易所上市的OP 代币提供流动性,协议的一部分是Wintermute 获得两千万枚OP 代币作为贷款(Wintermute 也提供50M USDC 作为抵押品)。这笔贷款应该是要转入Wintermute 在Optimism 链上的地址,但他们却提供已经部署在以太坊上许久的多签(multi-sig)Gnosis 金库地址:0x4f3a120E72C76c22ae802D129F599BFDbc31cb81。当Optimism 团队分别转移1 OP 和1M OP 作为测试时,Wintermute 却没有确认真的收到那些代币就和Optimism 团队确认了,所以剩下的19M 代币就又在5/27 转入了这个钱包地址。

Optimism 团队先转了1 OP 和1M OP 作为测试,最后才转剩下的19M 代币。资料来源:Optimistic Explorer

Wintermute 提供的地址和普通的钱包地址(EOA, externally owned account)不同,他是一个合约地址,因此拥有在以太坊上这个合约的所有权不代表拥有其他EVM-compatible 链上相同地址的所有权。而这个地址当时在Optimism 链上还没有所有人。5/30 Wintermute 发现了这个错误,并通知了Gnosis 金库和Optimism。经过咨询后他们宣称只有他们自己能够复原那些资金,并计划在6/7 将资金取回。

Wintermute 的Gnosis 金库地址是2020 年由ProxyFactory 合约(0x76E2)的createProxy 这个function 创造的。这个ProxyFactory 合约在部署时没有指定的chainId,因此这个ProxyFactory 合约在每一条链上的合约地址都一样。此外创建proxy 合约时,创建出的proxy 合约其地址只受sender, nonce 这两个变数影响,所以在不同的链上只要用一样的sender 和nonce 就能得到相同地址的合约。

创建Gnosis 金库合约的Factory 合约function。

攻击者(0x8BcF)在Optimism 链上也创立一个合约(0xE714)去呼叫Optimism 链上的ProxyFactory(0x76E2)的createProxy 创造地址直到找到0x4f3a 的那个地址。下图可以看到攻击者在一笔交易一次创建162 个地址,不断的重复这样的交易直到找到符合的地址,最后在这笔交易中创建了与Wintermute 在以太坊上一模一样的地址(0x4f3a)。

攻击者一次创建162 个地址直到找到与Wintermute 一模一样的地址(0x4f3a)。

攻击者将1M OP 代币转入自己的地址,卖掉后获得大约720 ETH。几天后又将1M 代币转入Vitalik 的钱包地址(0x8da6)。最后将17M 代币以一次1M 的方式归还到Wintermute 指定的地址(0x2501),剩余的1M Optimism 团队也同意将总共2M 的OP 代币留给攻击者作为赏金。

攻击后先将1M 代币转回自己的地址,4 天后将1M 代币转给Vitalik。

技术点create/create2

这边可以介绍两个以太坊在创建合约时可以用到的opcode — create 和create2。

create: new_address = hash(sender, nonce) create2: new_address = hash(0xFF, sender, salt, bytecode)

create2 可以说是create 的升级版,在EIP-1014时被提出。create 的缺点是下一个被创建的合约地址是可以被预测的,因为nonce 会照顺序增加。而create2 则是使部署的地址独立于未来事件,可以在预先计算出的地址上部署合约。create2 确保如果sender 用这个opcode 部署bytecode 和提供的salt,它将存储在new_address。

上述事件的ProxyFactory 用的就是create 而不是create2,导致攻击者只要用相同的地址(0x76E2)作为input 去反覆创建地址就能找到与当初再以太坊上创建给Wintermute 一样的合约地址(0x4f3a)。如果这个ProxyFactory 用的是create2 的话则需要被部署的合约bytecode,而因为被部署的是多签金库合约,合约内容会有这些共同拥有者(进行多签的地址们)的地址,因此攻击者产出的bytecode 自然会因为与原先的不同,也就无法用createProxy 去产出相同的地址。

以上的介绍希望有帮助大家了解这个OP 代币损失的事件,以及create 和create2 这两个opcode 的不同。