# 如何救援冲错不同链合约的资金？

By [MoneyStory](https://paragraph.com/@blurwatch) · 2023-02-26

---

今天在批量 Mint 以太坊主网上的 Base NFT 的时候，有小伙伴误连上了 Optimism 网络，将 Mint 资金打到了 Optimism 链上和以太坊主网相同的合约地址中；当然，最后通过 [#MoneyStory](https://twitter.com/moneystorylabs) 团队的努力，也成功将资金找回返还。

这就引出了一个问题：在以太坊上控制的合约地址，同样的地址在其他链上能被控制吗？这个问题和之前慢雾披露的 [2000 万 OP 代币被盗关键：交易重放](https://mp.weixin.qq.com/s/MjvCChjEGuSTr9cELQlW_A) 有相似之处，本质上都是如何在另一条链上创建出相同的合约地址。

这次遇到的问题还算是一个比较通用的问题，所以从技术的视角，给大家解析一下如何将这笔资金解救出来。可以将这篇文章收藏起来，下次发生类似的错误时，也不失为是一种可行的解决办法。

在讲解之前，我们需要先了解部署合约生成地址的规则。通常有两种方式。

**第一种方式**： 通过创建者（交易发起者）和创建者发送过的交易数量（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](https://twitter.com/moneystorylabs) 私信联系我们，我们会尽力回答您的问题。

---

*Originally published on [MoneyStory](https://paragraph.com/@blurwatch/kgyh0bhVnSCSPPrHWyXK)*
