深入理解可升级智能合约

news2024/11/26 0:23:38

准备

为了理解可升级合约,必须理解proxy机制,要理解proxy机制,必须理解solidity的sstoresload,以及关于以太坊架构和存储结构(数据结构)。

关于Solidity中的sstoresload深入理解:

  • 非常好的一篇剖析: https://learnblockchain.cn/article/4172

简单概括一下:

  • sstore将一对key-value存入storage
  • sload按照key取出storage中的value
  • 一笔交易中可以多次sstoresload
  • key一般用slot(槽)代替,是32bytes的哈希
  • 以上的storage是某一个合约下面的storage

合约代码分析

基于solidity ^0.4.24

  • openzeppelin的实现: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/Proxy.sol

  • USDC合约代码: https://etherscan.io/token/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48#code

其中Proxy:

// 抽象合约
contract Proxy {

    // fallback函数
    function () payable external {
        _fallback();
    }

    // 虚函数,需要子类实现
    function _implementation() internal view returns (address);

    // 以下是proxy合约通用代码,
    function _delegate(address implementation) internal {
        assembly {
            // 将msg.data,即交易的所有数据,复制到内存
            calldatacopy(0, 0, calldatasize)

            // 调用实现合约
            let result := delegatecall(gas, implementation, 0, calldatasize, 0, 0)

            // 将返回数据拷贝到内存
            returndatacopy(0, 0, returndatasize)

            switch result
            case 0 { revert(0, returndatasize) } // 0,失败
            default { return(0, returndatasize) } // 1, 成功
        }
    }

  // 子类可以重写次函数
  function _willFallback() internal {
  }

  // fallback函数实现
  function _fallback() internal {
    _willFallback();
    _delegate(_implementation());
  }
}
  • calldatacopy(t, f, s):将calldata(输入数据)从位置f开始复制s字节到mem(内存)的位置t。
  • delegatecall(g, a, in, insize, out, outsize):调用地址a的合约,输入为mem[in…(in+insize)) ,输出为mem[out…(out+outsize)), 提供g的gas 和v wei的以太坊。这个操作码在错误时返回0,在成功时返回1。
  • returndatacopy(t, f, s):将returndata(输出数据)从位置f开始复制s字节到mem(内存)的位置t。
  • switch:基础版if/else,不同的情况case返回不同值。可以有一个默认的default情况。
  • return(p, s):终止函数执行, 返回数据mem[p…(p+s))。
  • revert(p, s):终止函数执行, 回滚状态,返回数据mem[p…(p+s))。

参考代理合约: https://blog.csdn.net/weixin_30230009/article/details/127312438

示例

理解了代理(可升级)合约机制之后,我们动手实践一下

实现合约(implement):

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

contract Storage {

    uint256 number;

    function store(uint256 num) public {
        number = num;
    }

    function retrieve() public view returns (uint256){
        return number;
    }
}

代理合约:

// SPDX-License-Identifier: None

pragma solidity ^0.8.0;

library AddressUtils {

  /**
   * Returns whether the target address is a contract
   * @dev This function will return false if invoked during the constructor of a contract,
   * as the code is not actually created until after the constructor finishes.
   * @param addr address to check
   * @return whether the target address is a contract
   */
  function isContract(address addr) internal view returns (bool) {
    uint256 size;
    // XXX Currently there is no better way to check if there is a contract in an address
    // than to check the size of the code at that address.
    // See https://ethereum.stackexchange.com/a/14016/36603
    // for more details about how this works.
    // TODO Check this again before the Serenity release, because all addresses will be
    // contracts then.
    // solium-disable-next-line security/no-inline-assembly
    assembly { size := extcodesize(addr) }
    return size > 0;
  }

}
abstract contract Proxy {
  /**
   * @dev Fallback function.
   * Implemented entirely in `_fallback`.
   */
   fallback() payable external {
    _fallback();
  }

  receive() payable external {

  }

  /**
   * @return The Address of the implementation.
   */
  function _implementation() public virtual view returns (address);

  /**
   * @dev Delegates execution to an implementation contract.
   * This is a low level function that doesn't return to its internal call site.
   * It will return to the external caller whatever the implementation returns.
   * @param implementation Address to delegate.
   */
  function _delegate(address implementation) internal {
    assembly {
      // Copy msg.data. We take full control of memory in this inline assembly
      // block because it will not return to Solidity code. We overwrite the
      // Solidity scratch pad at memory position 0.
      calldatacopy(0, 0, calldatasize())

      // Call the implementation.
      // out and outsize are 0 because we don't know the size yet.
      let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)

      // Copy the returned data.
      returndatacopy(0, 0, returndatasize())

      switch result
      // delegatecall returns 0 on error.
      case 0 { revert(0, returndatasize()) }
      default { return(0, returndatasize()) }
    }
  }

  /**
   * @dev Function that is run as the first thing in the fallback function.
   * Can be redefined in derived contracts to add functionality.
   * Redefinitions must call super._willFallback().
   */
  function _willFallback() internal {
  }

  /**
   * @dev fallback implementation.
   * Extracted to enable manual triggering.
   */
  function _fallback() internal {
    _willFallback();
    _delegate(_implementation());
  }
}

contract TestSstore is Proxy {

    bytes32 private constant IMPLEMENTATION_SLOT = 0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3;

     constructor(address implementation_)  {
        assert(IMPLEMENTATION_SLOT == keccak256("org.zeppelinos.proxy.implementation"));

        setImplementation(implementation_);
    }

    function _implementation() public override view returns (address impl) {
        bytes32 slot = IMPLEMENTATION_SLOT;
        assembly {
            impl := sload(slot)
        }
    }


    function setImplementation(address newImplementation) public {
        require(AddressUtils.isContract(newImplementation), "Cannot set a proxy implementation to a non-contract address");

        bytes32 slot = IMPLEMENTATION_SLOT;

        assembly {
            sstore(slot, newImplementation)
        }
    }

}
  • 部署代理合约(Proxy)时候, 需要填写实现合约(implement)的地址
  • 为了获得调用代理合约的数据,可以先在remix里面调用实现合约,在Metamask中拿到数据,然后粘贴到代理合约调用处

示例:

  • 部署实现合约: https://goerli.etherscan.io/tx/0x6a26cf6ff348bc32831d8dbd00ed4aae807591613e48435407fa41cb73b78c02

  • 部署代理合约: https://goerli.etherscan.io/tx/0xcba8a55c22a31285185190170cf6d2a150a84b87f0c31d041023e64da3a5d83c

  • 通过代理合约调用实现合约的store函数,写入12345:https://goerli.etherscan.io/tx/0x3ac401e00f917898d6d47765132667414b4a5f290e599df7e5af537297fba78b

  • 直接读取实现合约,结果是0

  • 读取代理合约, 结果是 12345

  • 我们再“升级”一下实现合约:

    contract Storage {
    
     uint256 public number;
     uint256 public nb;
    
    /**
     * @dev Store value in variable
     * @param num value to store
     */
    function store(uint256 num) public {
        number = num ;
        nb  = number * 2;
    }
    
    /**
     * @dev Return value
     * @return value of 'number'
     */
    function retrieve() public view returns (uint256){
        return number;
    }
    }
    
  • 部署新的实现合约:https://goerli.etherscan.io/tx/0x4b5287ac0fd8756ac70cf50d4b66565310467f53c931489eaf6cf65014da116b

  • 设置新实现合约地址: https://goerli.etherscan.io/tx/0x1f074c4a25f46fa7d6010a9e169847706aece07107b5a889918c20e84ea5d876

  • 调用代理写入12345

  • 读取代理合约,
    请添加图片描述

总结

通过delegatecall进行调用实现合约,数据是存放在代理合约中,因此当“升级”实现合约合约后,不会影响现有的数据。

delegatecall很像“动态库”


关于delegatecallcall的对比:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/92684.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SAP ABAP 散装笔记20220825

SAP ABAP 散装笔记20220825 引言&#xff1a; 散装笔记的标题系列中记录了我常用、备忘、易错的知识点。 关键字&#xff1a;SAP ABAP 散装笔记 知识点 文章目录SAP ABAP 散装笔记202208251. 获取域值和域值描述的两种方式2. 打开新的会话窗口&#xff08;并执行可能的事务码…

西妥昔单抗丨艾美捷西妥昔单抗Cetuximab方案

西妥昔单抗Cetuximab是针对人表皮生长因子受体的一种单克隆抗体&#xff0c;主要作用就是与表皮生长因子受体结合&#xff0c;阻断表皮生长因子受体与其它配体的结合而达到抗肿瘤的目的。各种恶性肿瘤细胞&#xff0c;例如直肠癌细胞、胃癌细胞&#xff0c;表面都高表达表皮生长…

WebStorm pull(拉取)项目commit(提交)、push(推送)代码

1.打开WebStrom&#xff0c;找到Git---克隆... 2.输入需要克隆的项目地址&#xff0c;将其项目克隆下来 3.pull&#xff08;拉取&#xff09;、commit&#xff08;提交&#xff09;、push&#xff08;推送&#xff09; 4.在你本地新建一个和你git分支一样名字的分支 5.改完代码…

Eslint

1. 配置文件 配置文件由很多种写法&#xff1a; .eslintrc.*&#xff1a;新建文件&#xff0c;位于项目根目录 .eslintrc.eslintrc.js.eslintrc.json区别在于配置格式不一样package.json 中 eslintConfig&#xff1a;不需要创建文件&#xff0c;在原有文件基础上写。 2. 具体…

什么是Python?Python有什么特性?

什么是Python&#xff0c;相信第一次接触Python的同学会有一些疑问。我们通过百度百科得到Python的定义为&#xff1a;   Python是一种跨平台的计算机程序设计语言。是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。最初被设计用于编写自动化脚本&#xff…

UDS入门至精通系列:诊断时间参数

文章目录 前言一、诊断时间参数 P2二、诊断时间参数 P2*三、诊断时间参数 S3四、上述参数在应用中的汇总总结前言 在职场中,对于自己领导吩咐的事情,事事有响应,是你在领导面前彰显自己能力的机会。并且这其中的响应时间就是你职业能力的时刻。 同样在汽车电子诊断领域,因…

忆享科技受邀亮相CIS2022网络安全创新大会

12月14日&#xff0c;忆享科技受邀亮相第八届网络安全创新大会&#xff08;简称“CIS”&#xff09;多维时空专场&#xff0c;与行业内资深专家及众多头部企业交流对话&#xff0c;深度了解网络安全行业新理念、新技术、新思路和新实践&#xff0c;共同探讨行业技术创新与突破。…

Java+Swing实现的五子棋游戏

JavaSwing实现的五子棋游戏一、系统介绍二、功能展示1.游戏展示三、系统实现1.ChessFrame .java四、其它1.其他系统实现2.获取源码一、系统介绍 五子棋游戏实现人机对战、人人对战两个模式。 二、功能展示 1.游戏展示 三、系统实现 1.ChessFrame .java package five;impor…

【图像处理】图像拼接原理介绍

问题 图像拼接是图像处理的基础之一&#xff0c;虽然自己并没有直接做图像拼接方面的研究&#xff0c;但在面试中却多次被问到这方面的内容&#xff0c;可见这个知识点还是很重要的。事实上&#xff0c;很多场景都会用到图像拼接的知识&#xff0c;例如运动检测与跟踪、游戏画…

HJL-E6/A DC220V数字式【电流继电器】

1&#xff0e;用途 HJL系列数字式交流电流继电器为瞬时动作特性&#xff0c;用于发电机&#xff0c;变压器和输电线路的过负荷和短路保护&#xff0c;作为启动元件。 2&#xff0e;安装结构 导轨安装9&#xff0c;导轨安装E 两种壳体尺寸&#xff0c;具体尺寸请参考外型尺寸…

Spark 3.0 - 12.ML GBDT 梯度提升树理论与实战

目录 一.引言 二.GBDT 理论 1.集成学习 2.分类 & 回归问题 3.梯度提升 4.GBDT 生成 三.GBDT 实战 1.数据准备 2.构建 GBDT Pipeline 3.预测与评估 四.总结 一.引言 关于决策树前面已经介绍了常规决策树与随机森林两种类型的知识&#xff0c;本文主要介绍梯度提…

小游戏赛道如何加速流量增长?

小游戏是指设计极简的轻量级游戏。它构造简单&#xff0c;但却给人带来了娱乐性和重复参与的欲望。 近年来&#xff0c;小游戏在抖音、微信小游戏等平台拥有着疯狂裂变的可能性&#xff0c;出现了例如“羊了个羊”“跳一跳”、“合成大西瓜”等风靡一时的小游戏。 这些爆火的小…

「微服务系列」统一网关Gateway

为什么需要网关 网关功能&#xff1a; 身份认证和权限校验服务路由、负载均衡请求限流在SpringCloud中网关的实现包括两种&#xff1a; Zuul&#xff1a;基于Servlet的实现&#xff0c;属于阻塞式编程。SpringCloudGateway&#xff1a;是基于Spring5中提供的WebFlux&#xf…

关注渐冻症|菌群助力探索其发病机理及相关干预措施

最杰出的物理学家之一的斯蒂芬威廉霍金想必大家都知道&#xff0c;以及曾经风靡全网的“冰桶挑战”&#xff0c;它们都与一种罕见疾病有关&#xff0c;那就是渐冻症。 媒体的宣传让渐冻症成为了较为“知名”罕见病之一&#xff1b;2000年丹麦举行的国际病友大会上正式确定6月21…

【Redis】数据类型操作二 (Set/Hash/Zset)

文章目录3、Redis集合(Set)4、 Redis哈希(Hash)5、Redis有序集合Zset(sorted set)实操3、Redis集合(Set)4、 Redis哈希(Hash)5、Redis有序集合Zset(sorted set)3、Redis集合(Set) Redis Set 是String类型的无序集合。一个key集合可以对应多个value元素。Redis Set 可以自动排重…

[附源码]Python计算机毕业设计高校篮球训练管理系统Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

Python如何做自动化测试?

众做周知&#xff0c;自动测试的优势是显而易见的&#xff0c;它可以大大节省我们的时间&#xff0c;提高我们的工作效率。那么Python如何做自动化测试呢&#xff1f;本文将用Python编写一个简单的测试用例&#xff0c;并指导大家写做自动化测试的代码。如果大家对这个内容感兴…

基于java+springboot+mybatis+vue+mysql的会员制医疗预约服务管理信息系统

项目介绍 会员制医疗预约服务管理信息系统是针对会员制医疗预约服务管理方面必不可少的一个部分。在会员制医疗预约服务管理的整个过程中&#xff0c;会员制医疗预约服务管理系统担负着最重要的角色。为满足如今日益复杂的管理需求&#xff0c;各类的管理系统也在不断改进。系…

[计算机网络微课]第三章 数据链路层

数据链路层 概述 数据链路层在网络体系结构中的地位 主机 H1 给主机 H2 发送数据&#xff0c;中间要经过 3 个路由器和电话网、局域网以及广域网等多种网络。从五层协议原理体系结构角度来看 为了专注数据链路层内容&#xff0c;这里我们只考虑数据链路层&#xff0c;而不考…

体外诊断丨艾美捷游离维多珠单抗ADA水平检测试剂盒

introduction: Crohns disease in patients with moderate to severe active ulcerative colitis, routine treatment or tumor necrosis factor α &#xff08;TNF α&#xff09; Antagonists can also be treated with vidolizumab. Vedolizumab is a humanized monoclona…