今天在批量 Mint 以太坊主网上的 Base NFT 的时候,有小伙伴误连上了 Optimism 网络,将 Mint 资金打到了 Optimism 链上和以太坊主网相同的合约地址中;当然,最后通过 #MoneyStory 团队的努力,也成功将资金找回返还。
这就引出了一个问题:在以太坊上控制的合约地址,同样的地址在其他链上能被控制吗?这个问题和之前慢雾披露的 2000 万 OP 代币被盗关键:交易重放 有相似之处,本质上都是如何在另一条链上创建出相同的合约地址。
这次遇到的问题还算是一个比较通用的问题,所以从技术的视角,给大家解析一下如何将这笔资金解救出来。可以将这篇文章收藏起来,下次发生类似的错误时,也不失为是一种可行的解决办法。
在讲解之前,我们需要先了解部署合约生成地址的规则。通常有两种方式。
第一种方式: 通过创建者(交易发起者)和创建者发送过的交易数量(nonce)编号后进行 hash 计算确定合约地址。例如,当nonce为1时,计算合约地址的公式为:address(keccak256(0xd6, 0x94, sender, nonce))。大多数情况下,这种方式是部署合约的常用方式。
第二种方式: 使用 CREATE2 创建合约,需要的关键参数为合约的创建字节码和 salt 值。计算合约地址的公式为:keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, keccak256(type(Target).creationCode)))。例如,需要计算以 0x000000 开头的这类合约地址、UniSwap 使用工厂合约创建币对合约,或者需要在合约部署前使用合约地址的情况(如 CoinList 充值地址)。
值得注意的是,由于第一种部署合约的方式只与部署者的地址和部署者地址的 nonce 有关,因此部署者可以在不同的链上部署出相同的合约地址,但代码逻辑完全不同的合约。
我们查看了以太坊主网上部署合约地址的交易 nonce 为 10,然后编写了一个权限受控的取款合约。通过 Self 转账,将该地址的交易 nonce 递增至 9,并使用 nonce 为 10 的交易部署取款合约。这样,我们就能得到一个和以太坊主网上相同的合约地址。
下面是权限受控的取款合约的代码:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;
contract Support {
address public owner;
modifier onlyOwner() {
require(msg.sender == owner, "only owner");
_;
}
constructor() {
owner = msg.sender;
}
function withdraw() external onlyOwner {
payable(owner).transfer(address(this).balance);
}
}
最后,合约部署成功后,调用该合约的 withdraw 函数,就可以达到取款的目的。这样,我们就完成了对不同链合约内的资金救援工作。
如果您在阅读本文时有任何不理解的地方,欢迎通过 Twitter 私信联系我们,我们会尽力回答您的问题。

