智能合约迁移实战指南:从数据恢复到成本控制

本文详细解析智能合约迁移的两个核心步骤:数据恢复与数据写入,探讨迁移成本计算、交易所协作策略,并对比可升级合约的优缺点,为开发者提供完整的迁移方案设计指南。

智能合约迁移的工作原理

智能合约可能遭受攻击:可能存在漏洞、所有者钱包可能被盗,或因错误设置而陷入困境。如果您为企业开发智能合约,必须准备好应对此类事件。在许多情况下,唯一可用的解决方案是部署合约的新实例并将数据迁移到其中。

如果您计划开发可升级合约,迁移程序将使您免于可升级机制带来的风险。

您需要合约迁移能力

即使是无漏洞的合约也可能因私钥被盗而被劫持。最近的 Bancor 和 KICKICO 黑客攻击表明,攻击者可以入侵智能合约钱包。在此类攻击中,即使合约具有可升级机制,也可能无法修复已部署的智能合约。需要部署合约的新实例并正确初始化,以恢复用户的功能。

因此,所有智能合约开发者必须在合约设计阶段集成迁移程序,公司必须准备好在其受损时运行迁移。

迁移有两个步骤:

  1. 恢复要迁移的数据
  2. 将数据写入新合约

让我们详细了解细节、成本和操作后果。

如何执行迁移

步骤 1:数据恢复

您需要从区块链上的特定区块读取数据。为了从事件(黑客攻击或故障)中恢复,您需要使用事件发生前的区块或过滤攻击者的操作。

如果可能,暂停您的合约。这对您的用户来说更透明,并防止攻击者利用不了解迁移的用户。

数据的恢复将取决于您的数据结构。

对于简单类型的公共变量(如 uint 或 address),通过其 getter 检索值是微不足道的。对于私有变量,您可以依赖事件或计算变量在内存中的偏移量,然后使用 getStorageAt 函数检索其值。

数组也很容易恢复,因为元素的数量是已知的。您可以使用上述技术。

对于映射,情况稍微复杂一些。映射的键不存储。您需要恢复它们以访问值。为了简化链下跟踪,我们建议在值存储在映射中时发出事件。

对于 ERC20 代币合约,您可以通过跟踪 Transfer 事件的地址来找到所有持有者的列表。这个过程很困难。我们准备了两个选项来帮助:首先,您可以扫描区块链并自己检索持有者;其次,您可以依赖公开可用的以太坊区块链 Google BigTable 存档。

如果您不熟悉从区块链提取信息的 web3 API,可以使用 ethereum-etl,它提供了一组脚本来简化数据提取。

如果您没有同步的区块链,可以使用 Google BigQuery API。图 1 显示了如何通过 BigQuery 收集给定代币的所有地址:

1
2
3
SELECT from_address FROM `bigquery-public-data.ethereum_blockchain.token_transfers` AS token_transfers WHERE token_transfers.token_address = 0x41424344
Union DISTINCT
SELECT to_address FROM `bigquery-public-data.ethereum_blockchain.token_transfers` AS token_transfers WHERE token_transfers.token_address = 0x41424344

图 1:使用 Google BigQuery 恢复地址 0x41424344 代币所有 Transfer 事件中存在的地址

BigQuery 提供对区块号的访问,因此您可以调整此查询以返回到特定区块的交易。

一旦您恢复了所有持有者的地址,您可以离线查询 balanceOf 函数以恢复与每个持有者相关的余额。过滤余额为空的账户。

现在我们知道了如何检索要迁移的数据,让我们将数据写入新合约。

步骤 2:数据写入

一旦您收集了数据,就需要初始化您的新合约。

对于简单变量,您可以通过合约的构造函数设置值。

如果您的数据不能保存在单个交易中,情况会稍微复杂且成本更高。每个交易都包含在一个区块中,这限制了其交易可以使用的总 gas 量(所谓的“GasLimit”)。如果交易的 gas 成本接近或超过此限制,矿工不会将其包含在区块中。因此,如果您有大量数据要迁移,必须将迁移分成多个交易。

解决方案是向合约添加初始化状态,其中只有所有者可以更改状态变量,用户不能采取任何操作。

对于 ERC20 代币,该过程将采取以下步骤:

  1. 在初始化状态下部署合约
  2. 迁移余额
  3. 将合约状态移至生产

初始化状态可以通过 Pausable 功能和指示初始化状态的布尔值来实现。

为了降低成本,余额的迁移可以通过批量传输函数实现,让您在单个交易中设置多个账户:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
/**
* @dev 使用 values[i] 初始化 destinations[i] 的账户。该函数必须仅在任何代币传输之前调用(在初始化期间)。调用者必须检查目标是唯一地址。
* 对于大量目标,将余额初始化分离到不同的 batchTransfer 调用中。
* @param destinations 要设置值的地址列表
* @param values 要设置的值的列表
*/
function batchTransfer(address[] destinations, uint256[] values) duringInitialization onlyOwner external{
    require(destinations.length == values.length);

    uint256 length = destinations.length;
    uint i;

    for(i=0; i < length; i++){
        balances[destinations[i]] = values[i];
        emit Transfer(0x0, destinations[i], values[i]);
    }
}

图 2:batchTransfer 函数示例

迁移关注点

迁移合约时,会出现两个主要关注点:

  1. 迁移将花费多少?
  2. 对交易所有什么影响?

迁移成本

数据的恢复是链下完成的,因此是免费的。Ethereum-etl 可以在本地使用。Google 的 BigQuery API 提供足够的免费信用来覆盖其使用。

然而,发送到网络的每个交易和新合约存储的每个字节都有成本。

使用图 2 的 batchTransfer 函数,传输 200 个账户的成本约为 2.4M gas,在本文撰写时(使用 ETH Gas Station 重新计算今天的价格),平均 gas 价格(10 Gwei)下为 5.04 美元。粗略地说,迁移一个余额需要 0.025 美元。

如果我们查看按市值排名的前五个 ERC20 代币的持有者数量,我们有:

代币 持有者 迁移成本
BNB 300,000 $7,500
VEN 45,000 $1,200
MKR 5,000 $125
OMG 660,000 $16,500
ZRX 60,000 $1,500

如果您迁移其他信息(如津贴),成本会更高。即便如此,这些金额与这些代币所代表的资金量以及升级失败的潜在成本相比是很低的。

交易所

新合约的部署可能会产生操作后果。对于基于代币的合约,在迁移过程中与交易所合作非常重要,以确保新合约上市而旧合约被丢弃。

幸运的是,以前的代币迁移事件(如 Augur、Vechain 和 Tron)表明交易所可能会合作。

合约迁移与可升级合约

在我们之前的博客文章中,我们讨论了智能合约设计的一个趋势:向合约添加可升级机制。

我们看到了可升级合约的几个缺点:

  • 需要 EVM 和 Solidity 的详细低级专业知识。基于 Delegatecall 的代理要求开发者掌握 EVM 和 Solidity 内部原理。
  • 增加复杂性和代码大小。合约更难审查,更可能包含错误和安全问题。
  • 增加要处理的密钥数量。合约将需要多个授权用户(所有者、升级者)。授权用户越多,攻击面越大。
  • 增加每个交易的 gas 成本。合约变得比没有升级机制的相同版本竞争力更低。
  • 它们鼓励在部署后解决问题。如果开发者知道合约不能轻易更新,他们倾向于更彻底地测试和审查合约。
  • 它们降低用户对合约的信任。用户需要信任合约的所有者,这阻碍了真正去中心化系统的实现。

只有在有充分理由的情况下,合约才应具有可升级机制,例如:

  • 合约需要频繁更新。如果合约旨在定期修改,定期迁移的成本可能高到足以证明可升级机制的合理性。
  • 合约需要固定地址。合约的迁移需要使用新地址,这可能会破坏与第三方(如其他合约)的交互。

合约迁移实现了升级的好处,而缺点很少。升级相对于迁移的主要优势是升级成本更低。然而,这种成本并不能证明所有缺点的合理性。

建议

  • 在合约部署之前准备迁移程序。
  • 使用事件以促进数据跟踪。
  • 如果您选择可升级合约,还必须准备迁移程序,因为您的密钥可能被盗,或者您的合约可能遭受错误和不可逆的操作。

智能合约带来了新的开发范式。其不可变性质要求用户重新思考构建应用程序的方式,并需要彻底的设计和开发程序。

如果您在创建、验证或应用迁移程序时需要帮助,请联系我们。

同时,如果您对合约安全有任何疑问,请加入我们的免费以太坊安全办公时间。

如果您喜欢这篇文章,请分享: Twitter LinkedIn GitHub Mastodon Hacker News

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计