• 智能合约--如何实现可升级的智能合约


    一. 什么是智能合约

    智能合约通俗点说就是写在区块链上面的代码,代码里面编写着严谨完善的规则,一旦某个用户满足了合约里面的规则条件,就会触发里面的代码,执行某个方法。

     

     二. 为什么要使智能合约达到可升级

    智能合约的特点之一就是部署到链上之后不能修改,这一机制使得合约的交互方都可以信任合约。但也带来了一系列的问题,并且如果已部署的合约发现漏洞,也是无法修复的。假如发现了bug,致命性的,必须修复,那如何处理? 就是使用合约达到可升级优化才能满足需求

     

    三. 升级合约的机制原理

    1.   什么是合约升级

    使已经部署上链的合约做到可优化可更改,例如链上的业务逻辑代码和状态变量达到可增删改的功能.

       2.  合约升级的实现机制原理

     目前实现的方式根据存储区分有各种各样的模式,但是都离不开一个最底层的机制,就是使用delegatecall的特性去实现可升级的合约,达到合约可持续优化更改的效果.

    delegatecall 介绍

    目前调用合约的方式主要有三种

    • call
    • delegateCall
    • staticCall

    共同点:都是去调用执行目标合约地址的方法

    区别:delegateCall的执行环境和call和staticCall相反,正因为这样所以可利用这种特性实现可升级,在用户层面上无感知。

    具体的delegateCall的介绍可以看我另外一篇文章

    Solidity--call、delegatecall 和 callcode 的区别_Zeke Luo的博客-CSDN博客

     

    四. 实现可升级的ERC20合约

    代码概述

    • 编写InitializedProxy代理合约,此合约主要作用是转发和存储数据.

    继承openzeppelin的StorageSlotUpgradeable合约,用于插槽工具类。

    1. // SPDX-License-Identifier: GPL-3.0
    2. import "@openzeppelin/contracts-upgradeable/utils/StorageSlotUpgradeable.sol";
    3. pragma solidity >=0.7.0 <0.9.0;
    4. contract InitializedProxy {
    5. // address of logic contract
    6. // slot bytes32(uint256(keccak256('EIP1967.PROXY.CONFTI.IMPLEMENTATION')) - 1)
    7. bytes32 internal constant _IMPLEMENTATION_SLOT = 0x5f62ce3c9aebd463c7a36ab1b244d2bb94f07a2c13889b3b687940ebc467b9b3;
    8. // ======== Constructor =========
    9. constructor(
    10. address logic,
    11. bytes memory initializationCalldata
    12. ) {
    13. require(logic != address(0),"Proxy :: Wrong proxy contract address");
    14. StorageSlotUpgradeable.getAddressSlot(_IMPLEMENTATION_SLOT).value = logic;
    15. // Delegatecall into the logic contract, supplying initialization calldata
    16. (bool _ok, bytes memory returnData) =
    17. logic.delegatecall(initializationCalldata);
    18. // Revert if delegatecall to implementation reverts
    19. require(_ok, string(returnData));
    20. }
    21. // ======== Fallback =========
    22. fallback() external payable {
    23. address _impl = StorageSlotUpgradeable.getAddressSlot(_IMPLEMENTATION_SLOT).value;
    24. assembly {
    25. let ptr := mload(0x40)
    26. calldatacopy(ptr, 0, calldatasize())
    27. let result := delegatecall(gas(), _impl, ptr, calldatasize(), 0, 0)
    28. let size := returndatasize()
    29. returndatacopy(ptr, 0, size)
    30. switch result
    31. case 0 {
    32. revert(ptr, size)
    33. }
    34. default {
    35. return(ptr, size)
    36. }
    37. }
    38. }
    39. // ======== Receive ===
    40. receive() external payable {} // solhint-disable-line no-empty-blocks
    41. function upgradeVersion(address newAddress_) public{
    42. StorageSlotUpgradeable.getAddressSlot(_IMPLEMENTATION_SLOT).value = newAddress_;
    43. }
    44. }

    1.constructor构造函数拥有初始化数据,并且保存指向的业务逻辑合约

    2.fallback转发接收所有业务逻辑合约的方法,

    3.upgradVersion 用于升级的方法

    替换指定插槽的旧逻辑合约地址,更换新的逻辑合约

    • 实现自己的业务逻辑合约(可升级的erc20)
    1. import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
    2. contract logicA is ERC20Upgradeable{
    3. function initialize(string memory tokenName_ ,string memory symbol_) initializer external {
    4. __ERC20_init(tokenName_, symbol_);
    5. }
    6. function mint(address account,uint256 amount) external {
    7. if(account != address(0) && amount > 0){
    8. _mint(account,amount);
    9. }
    10. }
    11. function burn(address account,uint256 amount) external {
    12. if(account != address(0) && amount > 0){
    13. _burn(account,amount);
    14. }
    15. }
    16. }
    • 部署工厂合约

    此合约的主要作用是,创建可升级的逻辑合约,并且管理升级等.

    1. contract testtFactory{
    2. address public logicProxy;
    3. function createProxy(address logiAddress_,string memory tokenName_,string memory symbol_) public {
    4. bytes memory _initializationCalldata = abi.encodeWithSignature(
    5. "initialize(string,string)",
    6. tokenName_,
    7. symbol_
    8. );
    9. logicProxy = address (new InitializedProxy(logiAddress_,_initializationCalldata));
    10. }
    11. function updateLogicProxy(address updataTemplate_) public {
    12. (bool _ok, bytes memory returnData) = logicProxy.call(abi.encodeWithSignature(
    13. "upgradeVersion(address)",
    14. updataTemplate_
    15. ));
    16. require(_ok, string(returnData));
    17. }
    18. }

    createProxy  : 生成可升级的代理合约

    updateLogicProxy :  升级合约

    • 部署V2合约也就是升级之后的合约
    1. contract logicA2 is ERC20Upgradeable{
    2. function mint(address account,uint256 amount) external {
    3. require (amount <= 10 ,"must be <= 10" );
    4. if(account != address(0) && amount > 0){
    5. _mint(account,amount);
    6. }
    7. }
    8. function burn(address account,uint256 amount) external {
    9. if(account != address(0) && amount > 0){
    10. _burn(account,amount);
    11. }
    12. }
    13. }

    此合约修改了mint的金额必须需要小于等于10,用于升级之后的逻辑检验。

     

    五. 以上代码的使用逻辑介绍

    以remix做案例使用:

    一.部署业务逻辑合约(可升级erc20合约)

    7b2d9257688e4bd99cf252e64456a0cc.png第二步.部署工厂合约 

    e0b0b45f7eb34fef91b8a16110056722.png

    第三步调用工厂合约创建可升级的erc20Token合约

    调用createProxy传入第一步创建的可升级erc20合约地址

    创建成功之后,点击logicProxy查看生成之后的代理地址

    3f1b788ee44b423994f987f59fe162f0.png

     

    然后调用at方法,并且选择相应的逻辑合约即可调用.(at使用方式和原理可自行查看)

     

    第四步 升级当前的erc20合约

    打开工厂合约调用updateLogicProxy传入新合约的地址,即可完成升级. 

    (用户无感升级)

     

    五. 升级逻辑

    56c87094b0c44b8cb0c293a7a19deae1.png

     

    ⚠️ 升级注意事项

    1.插槽的冲突风险

    2.升级之后继承关系


    总结

    合约升级风险会比较大,尽量严谨,并且升级要做到只增不减不修改.

    以上就是今天要讲的内容,本文仅仅简单介绍了delegateCall的升级使用,关于安全方面还是需要自行根据业务去加限制,如有其他不正确的欢迎指出,或者DM

     

     

  • 相关阅读:
    P5960【模板】差分约束
    3.rsync备份案例
    什么是I/O内存?
    wkeOnDownload2与mbOnDownloadInBlinkThread
    MySQL进阶实战 3,mysql索引详解,上篇
    中集集团飞瞳全球港口航运人工智能领军企业中集飞瞳,成熟港航人工智能AI为港口船公司大幅提效降本,新一代智慧港口智慧船公司解决方案
    centos安装onlyoffice协作空间报错找不到repositroy
    【无标题】
    网络安全(黑客)——2024自学
    Three.js shadow阴影被剪切
  • 原文地址:https://blog.csdn.net/qq_33842966/article/details/126681657