基于openzeppelin编写solidity可升级的智能合约

这篇具有很好参考价值的文章主要介绍了基于openzeppelin编写solidity可升级的智能合约。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

概述

        现代软件的设计原则是“敏捷开发,迅速迭代”,功能升级或bug修复是所有软件系统都要面对的问题。甚至可以说软件质量在很大程度上依赖于升级和修补源代码的能力。当然Dapp(去中心化应用)也不例外,尤其Dapp一切都是透明的,这使得任何级别的bug都会被成倍的放大,因此可升级的智能合约成为所有Dapp的必然选择。

        本文主要以openzeppelin为基础来阐述构建可升级智能合约的一般流程和注意事项。

原理

openzeppelin通过在用户与智能合约中间加入一个代理来实现合约的透明升级,用户直接与代理交互,代理将用户的请求转发到实际合约,同时将合约的执行结果响应给用户。

示意图

基于openzeppelin编写solidity可升级的智能合约,区块链,智能合约,区块链,去中心化,javascript,开发语言

如上图所示,升级时只需要让Proxy指向Implementation合约即可。

上图有如下三种类型合约:

  • Proxy
  1.  用户直接也该合约交互;
  2. 所有的状态变量都在该合约中维护;
  3. 该合约符合EIP1967标准;
  4. 该合约将用户请求透明的转发到Implementation合约,同时将Implementation合约的返回响应给用户;
  • Implementation

        该合约被称为逻辑合约,Dapp的所有逻辑都在该合约中完成,Proxy以delegatecall的形式调用该合约中的方法。

  • ProxyAdmin

        在介绍该合约前我们先考虑一个问题——我们如何调用Proxy本身的方法?比如Proxy与Implementation都有一个方法upgradeTo(address),那么当用户调用该方法时,Proxy是该调用其自身方法还是以delegatecall的形式调用Implementation?

        OpenZeppelin是通过”透明代理“(transparent proxy )的模式来解决这个问题的。该模式通过发起调用的地址来决定如何调用方法。

  • 发起调用的地址为Proxy的管理地址(部署Proxy的地址)时,Proxy将执行自己的方法。
  • 发起调用的地址为其它地址时,Proxy将以delegatecall的形式向Implementation发起调用。

假设Proxy有owner()和upgradeTo()方法,Implementation有owner()和transfer()方法,则不同用户发起调用时具体调用方法如下:

msg.sender owner() upgradeto() transfer()

Owner

returns proxy.owner()

returns proxy.upgradeTo()

fails

Other

returns Implementation.owner()

fails

returns Implementation.transfer()

        通过上面的讨论我们可以看出,部署Proxy合约的账户无法调用Implementation合约中的方法,为此OpenZeppelin用ProxyAdmin来管理部署Proxy,此时Proxy的部署者为ProxyAdmin,这样用户就不用担心本地账户无法调用Implementation的情况,当然OpenZeppelin也提供了专门的接口用于更改Proxy的管理者。

执行流程

  1. 用户向Proxy发起调用。
  2. Proxy捕获用户请求,同时以delegatecall的形式向Implementation发起调用。此处理解delegatecall与call的区别尤其重要。
  3. Implementation收到请求后执行相关逻辑,并将结果返回给Proxy。值得注意的是由于上步中以delegatecall形式调用,因此该合约中逻辑是在Proxy的上下文中执行。
  4. Proxy将收到的返回数据响应给用户。

示例

        下面在hardhat本地节点,以自动售货机的合约升级为例来说明合约升级流程。

启动本地节点

npx hardhat node

合约版本1

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

// import "hardhat/console.sol";

contract VendingMachineV1 is Initializable {
    // these state variables and their values
    // will be preserved forever, regardless of upgrading
    uint public numSodas;
    address public owner;

    function initialize(uint _numSodas) public initializer {
        numSodas = _numSodas;
        owner = msg.sender;
    }

    function purchaseSoda() public payable {
        require(msg.value >= 1000 wei, "You must pay 1000 wei for a soda!");
        numSodas--;
    }

    function withdrawProfits() public onlyOwner {
        require(
            address(this).balance > 0,
            "Profits must be greater than 0 in order to withdraw!"
        );
        (bool sent, ) = owner.call{value: address(this).balance}("");
        require(sent, "Failed to send ether");
    }

    function setNewOwner(address _newOwner) public onlyOwner {
        owner = _newOwner;
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "Only owner can call this function.");
        _;
    }
}

部署脚本

const { ethers, upgrades } = require('hardhat');

async function main() {
  const VendingMachineV1 = await ethers.getContractFactory('VendingMachineV1');
  const proxy = await upgrades.deployProxy(VendingMachineV1, [100]);
  await proxy.waitForDeployment();

  const proxyAddr = await proxy.getAddress();
  const implementationAddress = await upgrades.erc1967.getImplementationAddress(
    proxyAddr
  );

  console.log('Proxy contract address: ' + proxyAddr);

  console.log('Implementation contract address: ' + implementationAddress);
}

main();

 合约部署

执行部署脚本

npx hardhat run scripts/deployProxy.js --network localhost

部署脚本执行后会打印出代理地址和VendingMachineV1合约的地址,如下:

基于openzeppelin编写solidity可升级的智能合约,区块链,智能合约,区块链,去中心化,javascript,开发语言

合约版本2

该版本较版本1添加了supplySoda方法用于补充库存,为了缩短篇幅这处省略版本1中相同的代码部分。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

contract VendingMachineV2 is Initializable {

    //....
    //....旧版本相同的代码

    function supplySoda(uint _num) public {
        require(_num > 0);
        numSodas += _num;
    }

}

合约升级

         为了更清晰的说明升级过程,此处我们在hardhat终端进行升级。

  • 开启hardhat终端
npx hardhat console --network localhost
  • 在终端中我们先观察版本1合约的当前状态,如下图:

基于openzeppelin编写solidity可升级的智能合约,区块链,智能合约,区块链,去中心化,javascript,开发语言

从图中我们可以看到,Proxy指向的Implementation的地址与前面部署版本1时输出的地址一致。

注:此处箭头1是Proxy的地址,箭头2是Implementation的地址

  • 部署版本2

基于openzeppelin编写solidity可升级的智能合约,区块链,智能合约,区块链,去中心化,javascript,开发语言

由上图可知,更新后Proxy已指向了新版本的地址(见红框)。执行新版本的方法补充库存后结果如下:

基于openzeppelin编写solidity可升级的智能合约,区块链,智能合约,区块链,去中心化,javascript,开发语言

要点

构造函数

        通过OpenZeppelin编写可升级合约时,在合约中不能有构造函数,一般将初始化的操作放在一个initialize的普通函数中(当然可以是任意的函数名,此时只需要调用部署API时指定该函数即可),升级过程中OpenZeppelin组件会主动执行该函数。

        构造函数与普通函数最大的区别是,构造函数只在部署时执行一次,而普通函数可以多次执行,因此需要向initialize函数加上initializer修饰符,该modifer只允许该函数执行一次。

        在执行上面升级版本2时,我们发现更新版本时虽然我们新版本传入了构造函数的参数但新版本的initialize并未执行。

upgrades.upgradeProxy('0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512', V2, {opts:{constructorArgs:[300]}});

添加变量

        在编写合约的新版本时,无论是由于新特性还是由于bug修复,都有一个额外的限制需要遵守:您不能更改合约状态变量声明的顺序,也不能更改它们的类型。具体原因看这里。

        一般在写可升级合约时我们需要预留一些空间,以允许该合约的未来版本在不影响子合约的存储布局的情况下使用这些槽。通用的做法是在基础合约中预先定义固定大小的uint256数组(由于EVM以槽为单位执行操作,而槽大小为32字节),该数组一般定义为__gap或以__gap_为前缀,以便OpenZeppelin升级可以识别该数组为预留空间,当版本升级需要新加变量时可以释放该数组的空间,如下:

//升级前合约
contract Base {
    uint256 base1;
    uint256[50] __gap;
}

contract Child is Base {
    uint256 child;
}

//升级后合约
contract Base {
    uint256 base1;
    uint256 base2;
    uint256[49] __gap;
}

或者
contract Base {
    uint256 base1;
    uint128 base2a;
    uint128 base2b;
    uint256[49] __gap;
}

Proxy透明转发的原理

        其实质是在proxy的fallback函数中添加如下逻辑。

// This code is for "illustration" purposes. To implement this functionality in production it
// is recommended to use the `Proxy` contract from the `@openzeppelin/contracts` library.
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.2/contracts/proxy/Proxy.sol

assembly {
  // (1) copy incoming call data
  calldatacopy(0, 0, calldatasize())

  // (2) forward call to logic contract
  let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)

  // (3) retrieve return data
  returndatacopy(0, 0, returndatasize())

  // (4) forward return data back to caller
  switch result
  case 0 {
      revert(0, returndatasize())
  }
  default {
      return(0, returndatasize())
  }
}

Proxy与implementation状态变量冲突

        通过Unstructured Storage Proxies来解决

参考文档:

Proxy Upgrade Pattern - OpenZeppelin Docs

Writing Upgradeable Contracts - OpenZeppelin Docs文章来源地址https://www.toymoban.com/news/detail-802558.html

到了这里,关于基于openzeppelin编写solidity可升级的智能合约的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【区块链技术开发】OpenZeppelin智能合约库:提高智能合约的安全性和可靠性,加速去中心化应用DApp的开发与部署。

    专栏:【区块链技术开发】 前期文章: 【区块链技术开发】使用Infura连接以太坊节点和OpenZeppelin库来构建安全、可靠的智能合约 【区块链技术开发】 Solidity使用Truffle Box工具实现预构建模板、自动化部署、创建智能合约示例代码 【区块链技术开发】 Solidity使用truffle工具创建

    2023年04月24日
    浏览(63)
  • 2.DApp-编写和运行solidity智能合约

    题记         演示如何编写solidity智能合约,以及在remix中运行solidity代码。 准备Remix环境         在浏览器中搜索remix,找到remix官网,并打开         由于是国内网络,所以访问国外网站较慢,可以耐心等待加载完成,或者科学上网。          加载完成是这样:    编写

    2024年02月03日
    浏览(46)
  • 区块链2——Solidity智能合约开发

    区块链 索引目录 智能合约是一种以代码形式编写的自动执行合约,它们运行在区块链上。这些合约定义了在特定条件下发生的事件以及相应的行为。 1.1 智能合约结构 版本声明(Version Declaration): 智能合约通常以声明版本开始,指定合约应该使用的Solidity编译器版本。例如

    2024年02月05日
    浏览(69)
  • 区块链智能合约编程语言 Solidity

    上文介绍了区块链生态发展,我们知道以太坊的到来可以使开发人员基于区块链开发DApp,本文介绍 Solidity 编程语言的使用,然后基于 Solidity 编写一个简单的智能合约。 Solidity 是以太坊开发人员使用的编程语言,用来编写智能合约,运行在以太坊虚拟机(EVM)上。 有开发经

    2024年02月12日
    浏览(61)
  • 【区块链实战】Solidity 智能合约如何给账户充值

    目录 一、实战场景 二、知识点 智能合约 智能合约函数 智能合约充值 payable 智能合约部署地址 智能合约的运行 合约 this 对象 三、菜鸟实战 四、运行结果 Solidity 智能合约如何给账户充值 1、充值金额 2、充值并查看结果

    2024年02月09日
    浏览(50)
  • 【区块链-智能合约工程师】第二篇:Solidity入门

    参考文章:一文速览2022十大智能合约开发工具 资料地址:WTF学院 HelloWorld remix:在线智能合约开发IDE(Integrated Development Environment,集成开发环境),可以在浏览器中快速部署测试智能合约。 合约HelloWorld: 事项 说明 代码所用的软件许可(license) 不写许可的话编译时会警告

    2024年02月09日
    浏览(55)
  • 【区块链-智能合约工程师】第三篇:Solidity进阶(一)

    学习资料地址:WTF学院 库合约一般都是一些好用的函数合集(库函数),为了提升solidity代码的复用性和减少gas而存在。他和普通合约主要有以下几点不同: 不能存在状态变量 不能够继承或被继承 不能接收以太币 不可以被销毁 String库 String库合约是将uint256(大正整数)类型

    2024年02月06日
    浏览(54)
  • 区块链智能合约solidity的中的一些关键字

    目  录 pragma mapping msg对象 block对象 contract constructor struct 数据地址 地址类型 address payable revert 以下场景使用 revert() : require 以下场景使用 require() : assert 以下场景使用 assert(): 访问权限 internal public private external function returns return view pure constant event emit modifier pragma   

    2024年01月16日
    浏览(77)
  • 区块链web3智能合约Solidity学习资源整理

    Solidity 是一门面向合约的、为实现智能合约而创建的高级编程语言。这门语言受到了 C++,Python 和 Javascript 语言的影响,设计的目的是能在以太坊虚拟机(EVM)上运行。 Solidity中文官方文档: https://solidity-cn.readthedocs.io/zh/develop/ https://learnblockchain.cn/docs/solidity/index.html 在线rem

    2024年03月19日
    浏览(73)
  • 部署OpenZeppelin可升级合约

    使用OpenZeppelin升级插件部署的合约具备可升级的特性:可以升级以修改其代码,同时保留其地址,状态和余额。 可以迭代地向项目中添加新功能,或修复在线上版本中可能发现的任何错误。 创建一个新的npm项目 安装并初始化Truffle 安装Truffle升级插件 注意,可升级合约使用

    2023年04月08日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包