{"content":{"title":"变形智能合约：EVM代码真的不可更改吗？","body":"作者：Sergey Boogerwooger - MixBytes的安全研究员\r\n\r\n![](https://img.learnblockchain.cn/2025/03/09/36.jpg)\r\n\r\n人们普遍认为以太坊上的智能合约代码是不可更改的，一旦部署就无法更改。然而，这仅在合约是通过标准程序部署的情况下才成立。\r\n\r\n本文将讨论允许在特定地址创建智能合约，然后通过修改处理用户数据的字节码来更改其内部逻辑的技术。\r\n\r\n## CREATE2 + CREATE + SELFDESTRUCT\r\n\r\n在以太坊中，更改固定账户的字节码可以通过一系列CREATE/CREATE2和SELFDESTRUCT调用来实现。每个合约在此序列中部署并销毁其继承者，理解这些操作对于继续阅读本文至关重要。虽然这些操作在文档中被充分描述，但我们将提供一个简要概述：\r\n\r\nCREATE 和 CREATE2 是独特的EVM操作，用于创建新账户。只有当账户是“空”的时（“空”意味着代码大小 == 0 且 nonce == 0），才能创建合约。新创建的智能合约的代码大小 > 0 且 nonce == 1。虽然这个信息众所周知，但值得提及关于CREATE/CREATE2的以下细节：\r\n\r\n- CREATE \\- 从keccak256(\\_deployer\\_addr, deployer\\_nonce\\_)计算新合约的地址（实际上是: keccak256(rlp(\\[\\_deployer\\_addr, deployer\\_nonce\\_\\]))\\[12:\\]）\r\n- CREATE2 \\- 从keccak256(\\_0xFF, deployer\\_addr, salt, bytecode)计算新合约的地址（实际上是: keccak256(\\_0xFF, deployer\\_addr, salt, bytecode)\\[12:\\]）\r\n\r\n相反，SELFDESTRUCT \"清除\"账户，通过重置其字节码和nonce。对我们来说，重要的是要注意SELFDESTRUCT重置账户的nonce，使我们能够使用相同的nonce从同一地址多次调用CREATE。\r\n\r\n## 这是如何实现的\r\n\r\n如果你使用CREATE -> SELFDESTRUCT -> CREATE -> SELFDESTRUCT -> ...，并且使用相同字节码从相同地址，每个新的CREATE将部署到一个新地址，因为deployer\\_nonce将在每个新交易中增加。\r\n\r\n如果使用CREATE2 -> SELFDESTRUCT -> CREATE2 -> SELFDESTRUCT -> ...，并且使用相同的字节码和salt，结果地址在每次迭代中将保持不变。\r\n\r\n然而，正如你可能已经注意到的，在第一种情况下（使用CREATE），如果“deployer\\_nonce”未更改，则部署地址可以是相同的。这是诀窍：通过SELFDESTRUCT，我们可以重置合约所处地址的nonce，从而允许其重新部署。然后，CREATE可以从同一地址部署一个新合约，但使用不同的代码。\r\n\r\n结合这一切，我们可以实现以下示例场景：\r\n\r\n1. 使用CREATE2部署MutDeployer合约。新合约账户的nonce == 1（[EIP161](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-161.md)）。\r\n2. MutDeployer使用CREATE部署MutableV1（可变代码的第一个版本）。新合约被部署在地址 = keccak256(MutDeployerAddr, MutDeployerNonce == 1)。\r\n3. 用户与MutableV1交互，认为其代码是常量。\r\n4. 所有者SELFDESTRUCT MutableV1，然后在同一地址部署MutableV2。\r\n5. 所有者SELFDESTRUCT MutDeployer，使其账户变为空（codesize == 0 && nonce == 0）。\r\n6. 用户重复第1步，在同一地址用CREATE2部署相同的MutDeployer合约。但现在MutDeployer有nonce == 1，正如在第1步中一样。\r\n7. MutDeployer使用CREATE部署新的字节码MutableV2（如第2步）。新合约在同一地址 == keccak256(MutDeployerAddr, MutDeployerNonce == 1)被部署。\r\n8. 用户使用与MutableV1相同的地址与MutableV2交互。\r\n\r\n该场景的代码：\r\n\r\n![](https://img.learnblockchain.cn/2025/03/09/iy5C5IN.png)\r\n\r\n展示此模式工作原理的代码和测试可以在[这里](https://github.com/mixbytes/hardhat-farm/blob/499c7e238924d4e394ae3ea8813c3fe7d28f1f28/contracts/Metamorph.sol#L10)找到。你可以通过运行[npx hardhat test test/Metamorph.js](https://github.com/mixbytes/hardhat-farm/blob/499c7e238924d4e394ae3ea8813c3fe7d28f1f28/test/Metamorph.js#L23)在这个教育库中进行实验。\r\n\r\n这类构造的另一个重要点是DELEGATECALL，允许一个合约在其上下文中调用SELFDESTRUCT。虽然SELFDSECTRUCT指令存在于外部合约的字节码中，但它增加了检测可变代码的复杂性。\r\n\r\n## 如何防御\r\n\r\n对变形合约的检测在这个[检测器和文章](https://metamorphic.a16zcrypto.com/)中有详细描述，它允许你检查给定地址是否可以具有可变代码。简而言之，为了检测这种情况，你需要检查该合约是否不是由另一个合约部署的，是否不包含SELFDESTRUCT操作码，或是否使用DELEGATECALL到包含SELFDESTRUCT的合约，并且在部署时是否使用了CREATE2等。\r\n\r\n因此，如果这些检查都被执行，你可以确定合约的代码是不可改变的。然而，在安全场景中，避免检测似乎是可能的，通过构建DELEGATECALL->DELEGATECALL->DELEGATECALL->...的调用链或其他技巧，所以要小心。\r\n\r\n我们非常尊重a16z团队在这个重要主题上的工作。Coinmonks关于此主题的另一篇好文章可以在[这里](https://learnblockchain.cn/article/12436)找到。\r\n\r\n## 销毁 SELFDESTRUCT\r\n\r\nSELFDESTRUCT在解决上述问题中的作用极为重要，我们还必须提及以太坊社区中关于SELFDESTRUCT的持续讨论。对于EVM来说，SELFDESTRUCT是一个“非常特殊”的操作，因为它是由EVM中的操作码从合约代码中发起的。后续操作在状态数据库中在EVM上下文之外执行，因为在SELFDESTRUCT后，节点必须销毁代码和账户状态，并清除其存储和所有相关字段。因此，除了与创建/销毁相关的问题外，还有关于区块链整体性能的担忧。\r\n\r\n此外，SELFDESTRUCT的主要目的是清理状态数据库中的空间，这在现实世界的场景中并不实用。协议不会在其代码中包含SELFDESTRUCT，因为用户不愿意将自己的资金投入可以被销毁的合约中。此外，SELFDESTRUCT使任何合约分析脚本不一致，因为当你从过去的特定区块下载合约字节码时，你不能确定该代码是否会在以后保持在同一地址。\r\n\r\nVitalik已提出[强有力的理由](https://hackmd.io/@vbuterin/selfdestruct#SELFDESTRUCT-is-the-only-opcode-that-breaks-important-invariants)来将SELFDESTRUCT指令从EVM中移除。我们同意此举将极大地提高智能合约的安全性，并不会显著影响区块链的行为。SELFDESTRUCT所解决的问题可以通过其他机制来缓解，例如清理和压缩不活跃的账户、数据和共识层的分离以及其他机制。因此，我们在等待SELFDESTRUCT的自我销毁 :)\r\n\r\n- MixBytes是谁？\r\n\r\n[MixBytes](https://mixbytes.io/)是一个专家区块链审计和安全研究团队，专注于为兼容EVM和基于Substrate的项目提供全面的智能合约审计和技术咨询服务。请关注我们[X](https://twitter.com/MixBytes)，以获取最新行业趋势和见解。\r\n\r\n>- 原文链接： [mixbytes.io/blog/metamor...](https://mixbytes.io/blog/metamorphic-smart-contracts-is-evm-code-truly-immutable)\r\n>- 登链社区 AI 助手，为大家转译优秀英文文章，如有翻译不通的地方，还请包涵～"},"author":{"user":"https://learnblockchain.cn/people/24680","address":null},"history":null,"timestamp":1741499225,"version":1}