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

一. 什么是智能合约

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

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

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

三. 升级合约的机制原理

  1.   什么是合约升级

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

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

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

delegatecall 介绍

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

  • call
  • delegateCall
  • staticCall

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

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

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

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

四. 实现可升级的ERC20合约

代码概述

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

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

// SPDX-License-Identifier: GPL-3.0
import "@openzeppelin/contracts-upgradeable/utils/StorageSlotUpgradeable.sol";
pragma solidity >=0.7.0 <0.9.0;

contract InitializedProxy {
    // address of logic contract
    // slot bytes32(uint256(keccak256('EIP1967.PROXY.CONFTI.IMPLEMENTATION')) - 1)
    bytes32 internal constant _IMPLEMENTATION_SLOT = 0x5f62ce3c9aebd463c7a36ab1b244d2bb94f07a2c13889b3b687940ebc467b9b3;

    // ======== Constructor =========
    constructor(
        address logic,
        bytes memory initializationCalldata
    ) { 
        require(logic != address(0),"Proxy :: Wrong proxy contract address");
        StorageSlotUpgradeable.getAddressSlot(_IMPLEMENTATION_SLOT).value = logic;
        // Delegatecall into the logic contract, supplying initialization calldata
        (bool _ok, bytes memory returnData) =
            logic.delegatecall(initializationCalldata);
        // Revert if delegatecall to implementation reverts
        require(_ok, string(returnData));
    }
     

    // ======== Fallback =========
    fallback() external payable {
        address _impl = StorageSlotUpgradeable.getAddressSlot(_IMPLEMENTATION_SLOT).value;
        assembly {
            let ptr := mload(0x40)
            calldatacopy(ptr, 0, calldatasize())
            let result := delegatecall(gas(), _impl, ptr, calldatasize(), 0, 0)
            let size := returndatasize()
            returndatacopy(ptr, 0, size)

            switch result
                case 0 {
                    revert(ptr, size)
                }
                default {
                    return(ptr, size)
                }
        }
    }

    // ======== Receive ===
     receive() external payable {} // solhint-disable-line no-empty-blocks

       function upgradeVersion(address newAddress_) public{ 
        StorageSlotUpgradeable.getAddressSlot(_IMPLEMENTATION_SLOT).value = newAddress_;
    }
}

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

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

3.upgradVersion 用于升级的方法

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

  • 实现自己的业务逻辑合约(可升级的erc20)
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
contract logicA  is ERC20Upgradeable{
    
    function initialize(string memory tokenName_ ,string memory symbol_) initializer external {
        __ERC20_init(tokenName_, symbol_);
    }
    
    function mint(address account,uint256 amount)  external  {
        if(account != address(0) && amount > 0){
            _mint(account,amount);
        }
    }

    function burn(address account,uint256 amount)  external  {
        if(account != address(0) && amount > 0){
            _burn(account,amount);
        }
    } 
  
}
  • 部署工厂合约

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

contract  testtFactory{
    
    address public logicProxy;
    
    function createProxy(address logiAddress_,string memory tokenName_,string memory symbol_) public {
            bytes memory _initializationCalldata = abi.encodeWithSignature(
            "initialize(string,string)",
            tokenName_,
            symbol_
        );
         logicProxy = address (new InitializedProxy(logiAddress_,_initializationCalldata));
    }
        
    function updateLogicProxy(address updataTemplate_) public { 

        (bool _ok, bytes memory returnData) = logicProxy.call(abi.encodeWithSignature(
            "upgradeVersion(address)",
             updataTemplate_
        ));

        require(_ok, string(returnData));
    } 

}

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

updateLogicProxy :  升级合约

  • 部署V2合约也就是升级之后的合约
contract logicA2 is ERC20Upgradeable{
    
    function mint(address account,uint256 amount)  external  {
        require (amount <= 10 ,"must be <= 10" );  
        if(account != address(0) && amount > 0){
            _mint(account,amount);
        }
    }

    function burn(address account,uint256 amount)  external  {
        if(account != address(0) && amount > 0){
            _burn(account,amount);
        }
    } 
  
}

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

 

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

以remix做案例使用:

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

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

e0b0b45f7eb34fef91b8a16110056722.png

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

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

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

3f1b788ee44b423994f987f59fe162f0.png

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

第四步 升级当前的erc20合约

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

(用户无感升级)

五. 升级逻辑

56c87094b0c44b8cb0c293a7a19deae1.png

⚠️ 升级注意事项

1.插槽的冲突风险

2.升级之后继承关系


总结

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

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

猜你喜欢

转载自blog.csdn.net/qq_33842966/article/details/126681657
今日推荐