当前位置: 首页 > news >正文

智能合约的更新与迭代

智能合约部署成功后是不可篡改的,那么如果我们希望迭代升级原有的合约,我们应该怎么做呢?

我们可以通过 ERC1967 来创建一个代理合约,指向我们真实的逻辑合约,我们每个更新逻辑合约其实都是部署一个新的合约,然后将代理合约中的逻辑合约指向这个新合约

ERC1967

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/ERC1967/ERC1967Utils.sol

ERC1967 是一个 以太坊标准(EIP-1967),它专门为 代理合约 (Proxy Contract) 设计,
目的是 规定代理合约的存储槽 (storage slots),避免 存储冲突 (storage collision)。

背景

在升级合约(Upgradable Contracts)里,我们通常使用 代理模式 (Proxy Pattern):

用户始终调用 代理合约 (Proxy)。

代理合约把调用转发给 实现合约 (Implementation/Logic Contract)。

当需要升级时,只需要修改代理合约里记录的逻辑合约地址。

但是:
合约的状态变量是按 存储槽 (slot) 顺序存储的,如果代理合约和逻辑合约定义的变量冲突,就会导致 数据错乱。

解决方案

它规定了几个关键的 storage slot 地址(通过 keccak 哈希算出来,减少冲突概率):

  • 实现合约地址 (implementation slot) 存放当前逻辑合约地址
    bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
    
  • 管理员地址 (admin slot) 存放代理管理员(有权升级的人)
    bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
    
  • Beacon 地址 (beacon slot)(可选) 如果是 Beacon Proxy 模式,存放 Beacon 地址
       bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
    

所有遵循 ERC1967 标准的代理合约,都会把数据放在相同、不会冲突的位置,避免了存储覆盖问题。

代码实践

https://github.com/Cyfrin/foundry-upgrades-cu.git

我们定义一个符合UUPSUpgradeable的合约,将其版本号设置为1

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";contract BoxV1 is Initializable, OwnableUpgradeable, UUPSUpgradeable {uint256 internal value;/// @custom:oz-upgrades-unsafe-allow constructorconstructor() {_disableInitializers();}function initialize() public initializer {__Ownable_init();__UUPSUpgradeable_init();}function getValue() public view returns (uint256) {return value;}function version() public pure returns (uint256) {return 1;}function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}

接下来我们再定义一个新的合约,作为上面合约的升级版,我们设定其版本号为2

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";contract BoxV2 is Initializable, OwnableUpgradeable, UUPSUpgradeable {uint256 internal value;/// @custom:oz-upgrades-unsafe-allow constructorconstructor() {_disableInitializers();}function initialize() public initializer {__Ownable_init();__UUPSUpgradeable_init();}function setValue(uint256 newValue) public {value = newValue;}function getValue() public view returns (uint256) {return value;}function version() public pure returns (uint256) {return 2;}function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}

我们先部署第一个合约,然后调用其version函数检查是否部署成功

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;import {Script} from "forge-std/Script.sol";
import {BoxV1} from "../src/BoxV1.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";contract DeployBox is Script {function run() external returns (address) {address proxy = deployBox();return proxy;}function deployBox() public returns (address) {vm.startBroadcast();BoxV1 box = new BoxV1();ERC1967Proxy proxy = new ERC1967Proxy(address(box), "");BoxV1(address(proxy)).initialize();vm.stopBroadcast();return address(proxy);}
}

我们可以通过下面的指令获取版本内容

cast call <PROXY_CONTRACT_ADDRESS> "version()" \--rpc-url <SEPOLIA_RPC_URL_FROM_ALCHEMY> \--private-key <YOUR_PRIVATE_KEY>

然后我们升级合约,用来替换上述的版本1

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";contract BoxV2 is Initializable, OwnableUpgradeable, UUPSUpgradeable {uint256 internal value;/// @custom:oz-upgrades-unsafe-allow constructorconstructor() {_disableInitializers();}function initialize() public initializer {__Ownable_init();__UUPSUpgradeable_init();}function setValue(uint256 newValue) public {value = newValue;}function getValue() public view returns (uint256) {return value;}function version() public pure returns (uint256) {return 2;}function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}

接下来,我们使用如下脚本更新合约

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;import {Script} from "forge-std/Script.sol";
import {BoxV1} from "../src/BoxV1.sol";
import {BoxV2} from "../src/BoxV2.sol";
import {BoxV3} from "../src/BoxV3.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import {DevOpsTools} from "lib/foundry-devops/src/DevOpsTools.sol";contract UpgradeBox is Script {function run() external returns (address) {address mostRecentlyDeployedProxy = DevOpsTools.get_most_recent_deployment("ERC1967Proxy", block.chainid);vm.startBroadcast();BoxV3 newBox = new BoxV3();vm.stopBroadcast();address proxy = upgradeBox(mostRecentlyDeployedProxy, address(newBox));return proxy;}function upgradeBox(address proxyAddress,address newBox) public returns (address) {vm.startBroadcast();BoxV1 proxy = BoxV1(payable(proxyAddress));proxy.upgradeTo(address(newBox));vm.stopBroadcast();return address(proxy);}
}

我们继续请求version函数

cast call <PROXY_CONTRACT_ADDRESS> "version()" \--rpc-url <SEPOLIA_RPC_URL_FROM_ALCHEMY> \--private-key <YOUR_PRIVATE_KEY>

在这里插入图片描述
我们成功升级了合约

http://www.dtcms.com/a/419697.html

相关文章:

  • C语言实战项目:贪吃蛇(2)
  • 南头专业外贸网站建设公司中国建设银行官网首页登录入口
  • 如何做微信网站防封网站建设 用ftp上传文件
  • C++ STL学习笔记: Vector
  • CSS中 min() max() clamp()函数
  • 如何做免费企业网站小程序在建网站吗
  • sourcefare从入门到实战(2) - 创建第一个扫描项目(服务端Git方式)
  • 用html做网站源代码龙岩北京网站建设
  • Qt常用控件之QComboBox
  • 钢铁舞者:当机械臂成为机器人的“双手”,世界正被重塑
  • 从云端到终端,从大模型到机器人:智源众智FlagOS 1.5引领开放计算生态迈向成熟
  • 舆情网站直接打开的软件第三方商城网站建设
  • 网站seo收录工具北京建设银行纪念钞预定官方网站
  • 盐山县招聘网站建设上海建设工程安全监理网站
  • 1g做网站空间网络搭建安全分析
  • vue3边学边做系列(3)-路由缓存接口封装
  • 输入法网站设计网站seo在线诊断分析
  • 页面设计模板网站wordpress 免费好用主题
  • 提升准确率的处理
  • 透明水印logo在线制作东莞市seo网络推广报价
  • App 上架服务全流程解析,iOS 应用代上架、ipa 文件上传工具、TestFlight 测试与苹果审核实战经验
  • 织梦网站版权银行营销活动方案
  • 自己做视频网站会不会追究版权做网站界面一般用什么来做
  • less和sass
  • 单片机开发---RP2040数据手册之PIO功能
  • 怎么免费做网站视频教学网站不收录 域名问题
  • 青海省城乡建设厅网站首页网站缩放代码
  • 学习2025.9.28
  • C++协程
  • 模电基础:多级放大电路与集成运放的认识