Michael.W基于Foundry精读Openzeppelin第18期——DoubleEndedQueue.sol

news2024/11/23 21:12:11

Michael.W基于Foundry精读Openzeppelin第18期——DoubleEndedQueue.sol

      • 0. 版本
        • 0.1 DoubleEndedQueue.sol
      • 1. 目标合约
      • 2. 代码精读
        • 2.1 结构体Bytes32Deque
        • 2.2 length(Bytes32Deque storage deque) && empty(Bytes32Deque storage deque)
        • 2.3 at(Bytes32Deque storage deque, uint256 index)
        • 2.4 clear(Bytes32Deque storage deque)
        • 2.5 pushBack(Bytes32Deque storage deque, bytes32 value) && popBack(Bytes32Deque storage deque) && back(Bytes32Deque storage deque)
        • 2.6 pushFront(Bytes32Deque storage deque, bytes32 value) && popFront(Bytes32Deque storage deque) && front(Bytes32Deque storage deque)

0. 版本

[openzeppelin]:v4.8.3,[forge-std]:v1.5.6

0.1 DoubleEndedQueue.sol

Github: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.3/contracts/utils/structs/DoubleEndedQueue.sol

DoubleEndedQueue库提供了双向队列的数据结构及对应操作库函数,提供了队头或队尾插入及弹出元素值等逻辑功能。

本库采用优化过的storage存储且所有操作的时间复杂度都是O(1)。特别要注意的是库中的clear操作仅仅将队头和队尾指针清零,而之前队列中的元素值依然留存在storage中。

1. 目标合约

封装DoubleEndedQueue library成为一个可调用合约:

Github: https://github.com/RevelationOfTuring/foundry-openzeppelin-contracts/blob/master/src/utils/structs/MockDoubleEndedQueue.sol

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "openzeppelin-contracts/contracts/utils/structs/DoubleEndedQueue.sol";

contract MockDoubleEndedQueue {
    using DoubleEndedQueue for DoubleEndedQueue.Bytes32Deque;

    DoubleEndedQueue.Bytes32Deque _deque;

    function pushBack(bytes32 value) external {
        _deque.pushBack(value);
    }

    function popBack() external returns (bytes32){
        return _deque.popBack();
    }

    function pushFront(bytes32 value) external {
        _deque.pushFront(value);
    }

    function popFront() external returns (bytes32) {
        return _deque.popFront();
    }

    function front() external view returns (bytes32){
        return _deque.front();
    }

    function back() external view returns (bytes32){
        return _deque.back();
    }

    function at(uint index) external view returns (bytes32){
        return _deque.at(index);
    }

    function clear() external {
        _deque.clear();
    }

    function length() external view returns (uint){
        return _deque.length();
    }

    function empty() external view returns (bool){
        return _deque.empty();
    }
}

全部foundry测试合约:

Github: https://github.com/RevelationOfTuring/foundry-openzeppelin-contracts/blob/master/test/utils/structs/DoubleEndedQueue.t.sol

2. 代码精读

2.1 结构体Bytes32Deque

双向队列的内部结构由两个指针和一个存储元素内容的mapping(int128 => bytes32)构成:

    struct Bytes32Deque {
    	// 指向队头元素的index
        int128 _begin;
        // 指向队尾下一个元素的index
        int128 _end;
        // 用于存储队列中元素值的mapping
        mapping(int128 => bytes32) _data;
    }

指向队头和队尾的指针值被定义成int128使得队列可以进行安全的头尾双向延展——在队列头部和尾部增添元素时不会出现指针运算溢出的情况。同时,两个int128正好占用一个slot,节约存储。

需要注意的是,队列中的有效元素位于[Bytes32Deque._begin, Bytes32Deque._end)的范围中,也就是说队列第一个元素值为Bytes32Deque._data[Bytes32Deque._begin],队列最后一个元素值为Bytes32Deque._data[Bytes32Deque._end-1]

2.2 length(Bytes32Deque storage deque) && empty(Bytes32Deque storage deque)

  • length(Bytes32Deque storage deque):返回处于队列中的元素个数;
  • empty(Bytes32Deque storage deque):判断队列中是否有元素。如果队列中无元素返回true,队列中有元素返回false。
    function length(Bytes32Deque storage deque) internal view returns (uint256) {
        // 关闭solidity 0.8的整数运算溢出检查
        unchecked {
        	// 将队尾指针和队头指针都提升到int256的维度作差,并将结果转成uint256返回
        	// 由于前面的各种操作方法都可以严格保证deque._end不会小于deque._begin,此处减法不会发生溢出。并且这里有个前提假设:在该双端队列中最多的元素个数为type(int256).max
            return uint256(int256(deque._end) - int256(deque._begin));
        }
    }

    function empty(Bytes32Deque storage deque) internal view returns (bool) {
        // 如果队尾指针deque._end > 对头指针deque._begin,说明队列中有元素;否则,队列中无元素
        return deque._end <= deque._begin;
    }

2.3 at(Bytes32Deque storage deque, uint256 index)

返回队列中第index+1个元素的值(即索引为index的元素值,队头元素的index为0)。

    function at(Bytes32Deque storage deque, uint256 index) internal view returns (bytes32 value) {
        // idx为队头指针指向的位置索引+相对索引index。由于index为uint256,队头指针为int128,所以通过SafeCast库将二者统一到int256的维度进行加法运算。最后再将结果从int256转换成int128给idx变量,如果这个类型转换的过程产生溢出,SafeCast.toInt128()内部会revert
        int128 idx = SafeCast.toInt128(int256(deque._begin) + SafeCast.toInt256(index));
        // 如果idx的值>=队尾指针,说明输入的index已经越界。抛出OutOfBounds错误
        if (idx >= deque._end) revert OutOfBounds();
        // 如果idx的值<队尾指针,从deque._data中获取对应位置的元素值并返回
        return deque._data[idx];
    }

2.4 clear(Bytes32Deque storage deque)

清空队列。

注:该方法仅仅是将队头和队尾指针清零,原来的deque._data中的元素值并未delete。这种操作不会影响该双端队列的功能,只是无法获得删除已有slot的值而返还的gas。

    function clear(Bytes32Deque storage deque) internal {
    	// 队头指针清零
        deque._begin = 0;
        // 队尾指针清零
        deque._end = 0;
    }

foundry代码验证

contract DoubleEndedQueueTest is Test {
    MockDoubleEndedQueue mdeq = new MockDoubleEndedQueue();

    function test_Clear() external {
        for (uint16 i; i < type(uint16).max; ++i) {
            bytes32 content = bytes32(uint(i));
            mdeq.pushFront(content);
            mdeq.pushBack(content);
        }

        assertEq(mdeq.length(), uint(type(uint16).max) * 2);
        assertFalse(mdeq.empty());
        // clear
        mdeq.clear();

        // check
        assertEq(mdeq.length(), 0);
        assertTrue(mdeq.empty());
    }
}

2.5 pushBack(Bytes32Deque storage deque, bytes32 value) && popBack(Bytes32Deque storage deque) && back(Bytes32Deque storage deque)

  • pushBack(Bytes32Deque storage deque, bytes32 value):在队尾添加元素;
  • popBack(Bytes32Deque storage deque):弹出队尾元素。如果队列中无元素,则抛出Empty错误;
  • back(Bytes32Deque storage deque):返回队尾元素值(非弹出)。如果队列中无元素,则抛出Empty错误。
    function pushBack(Bytes32Deque storage deque, bytes32 value) internal {
        // 将deque._end的值复制给内存中的backIndex变量
        int128 backIndex = deque._end;
        // 将以backIndex为key,将元素value添加到内置mapping中(_data)
        deque._data[backIndex] = value;
        // 关闭solidity 0.8的整数运算溢出检查
        unchecked {
        	// 队尾指针deque._end自增1
            deque._end = backIndex + 1;
        }
    }

    function popBack(Bytes32Deque storage deque) internal returns (bytes32 value) {
        // 如果此时队列中无元素,以Empty错误revert
        if (empty(deque)) revert Empty();
        // 声明变量backIndex
        int128 backIndex;
        // 关闭solidity 0.8的整数运算溢出检查
        unchecked {
        	// 将队尾指针前移一个位置(此时指向队尾的最后一个元素),并将位移后的位置存到变量backIndex中
            backIndex = deque._end - 1;
        }
        // 获取此时队列中最后一个元素的值
        value = deque._data[backIndex];
        // 删除队列中最后一个元素的值
        delete deque._data[backIndex];
        // 更新队尾指针,即照比之前前移一个位置
        deque._end = backIndex;
    }
    
    function back(Bytes32Deque storage deque) internal view returns (bytes32 value) {
        // 如果此时队列中无元素,以Empty错误revert
        if (empty(deque)) revert Empty();
        // 声明变量backIndex
        int128 backIndex;
        // 关闭solidity 0.8的整数运算溢出检查
        unchecked {
        	// backIndex设置为队尾指针deque._end-1,即指向队列中最后一个元素
            backIndex = deque._end - 1;
        }
        // 返回队列中最后一个元素的值
        return deque._data[backIndex];
    }

foundry代码验证

contract DoubleEndedQueueTest is Test {
    MockDoubleEndedQueue mdeq = new MockDoubleEndedQueue();

    function test_PushBackAndPopBackAndBack() external {
        // empty deque
        assertTrue(mdeq.empty());
        assertEq(mdeq.length(), 0);
        // revert if back()/popBack() from an empty deque
        vm.expectRevert(DoubleEndedQueue.Empty.selector);
        mdeq.back();
        vm.expectRevert(DoubleEndedQueue.Empty.selector);
        mdeq.popBack();

        // push back a,b,c
        mdeq.pushBack('a');
        assertEq(mdeq.length(), 1);
        assertEq(mdeq.back(), 'a');
        mdeq.pushBack('b');
        assertEq(mdeq.length(), 2);
        assertEq(mdeq.back(), 'b');
        mdeq.pushBack('c');
        assertEq(mdeq.length(), 3);
        assertEq(mdeq.back(), 'c');
        assertFalse(mdeq.empty());

        // pop back
        assertEq(mdeq.popBack(), 'c');
        assertEq(mdeq.length(), 2);
        assertEq(mdeq.popBack(), 'b');
        assertEq(mdeq.length(), 1);
        assertEq(mdeq.popBack(), 'a');
        assertEq(mdeq.length(), 0);
        assertTrue(mdeq.empty());
    }
}

2.6 pushFront(Bytes32Deque storage deque, bytes32 value) && popFront(Bytes32Deque storage deque) && front(Bytes32Deque storage deque)

  • pushFront(Bytes32Deque storage deque, bytes32 value):在队头添加元素;
  • popFront(Bytes32Deque storage deque):弹出队头元素。如果队列中无元素,则抛出Empty错误;
  • front(Bytes32Deque storage deque):返回队头元素值(非弹出)。如果队列中无元素,则抛出Empty错误。
    function pushFront(Bytes32Deque storage deque, bytes32 value) internal {
        // 声明变量frontIndex
        int128 frontIndex;
        // 关闭solidity 0.8的整数运算溢出检查
        unchecked {
        	// 将队头指针前移一个位置(此时指向队头第一个元素前面的一个位置),并将位移后的位置存到变量frontIndex中
            frontIndex = deque._begin - 1;
        }
        // 向队头前一个位置添加元素value
        deque._data[frontIndex] = value;
        // 更新队头指针,即照比之前前移一个位置
        deque._begin = frontIndex;
    }

    function popFront(Bytes32Deque storage deque) internal returns (bytes32 value) {
        // 如果此时队列中无元素,以Empty错误revert
        if (empty(deque)) revert Empty();
        // 将队头元素指针复制给变量frontIndex
        int128 frontIndex = deque._begin;
        // 获取队头的元素值
        value = deque._data[frontIndex];
        // 删除队列中第一个元素的值
        delete deque._data[frontIndex];
        // 关闭solidity 0.8的整数运算溢出检查
        unchecked {
        	// 更新队头指针,即队头指针自增1
            deque._begin = frontIndex + 1;
        }
    }

    function front(Bytes32Deque storage deque) internal view returns (bytes32 value) {
        // 如果此时队列中无元素,以Empty错误revert
        if (empty(deque)) revert Empty();
        // 将队头元素指针复制给变量frontIndex 
        int128 frontIndex = deque._begin;
        // 返回队列中队头元素指针指向的值
        return deque._data[frontIndex];
    }

foundry代码验证

contract DoubleEndedQueueTest is Test {
    MockDoubleEndedQueue mdeq = new MockDoubleEndedQueue();

    function test_PushFrontAndPopFrontAndFront() external {
        // empty deque
        assertTrue(mdeq.empty());
        assertEq(mdeq.length(), 0);
        // revert if front()/popFront() from an empty deque
        vm.expectRevert(DoubleEndedQueue.Empty.selector);
        mdeq.front();
        vm.expectRevert(DoubleEndedQueue.Empty.selector);
        mdeq.popFront();

        // push front a,b,c
        mdeq.pushFront('a');
        assertEq(mdeq.length(), 1);
        assertEq(mdeq.front(), 'a');
        mdeq.pushFront('b');
        assertEq(mdeq.length(), 2);
        assertEq(mdeq.front(), 'b');
        mdeq.pushFront('c');
        assertEq(mdeq.length(), 3);
        assertEq(mdeq.front(), 'c');
        assertFalse(mdeq.empty());

        // pop back
        assertEq(mdeq.popFront(), 'c');
        assertEq(mdeq.length(), 2);
        assertEq(mdeq.popFront(), 'b');
        assertEq(mdeq.length(), 1);
        assertEq(mdeq.popFront(), 'a');
        assertEq(mdeq.length(), 0);
        assertTrue(mdeq.empty());
    }

    function test_DoubleDirectionsPushAndAt() external {
        mdeq.pushFront('a');
        mdeq.pushBack('b');
        mdeq.pushFront('c');
        mdeq.pushBack('e');
        mdeq.pushBack('f');
        mdeq.pushFront('g');

        // status in deque [g,c,a,b,e,f]
        assertEq(mdeq.length(), 6);
        assertEq(mdeq.at(0), 'g');
        assertEq(mdeq.at(1), 'c');
        assertEq(mdeq.at(2), 'a');
        assertEq(mdeq.at(3), 'b');
        assertEq(mdeq.at(4), 'e');
        assertEq(mdeq.at(5), 'f');
        vm.expectRevert(DoubleEndedQueue.OutOfBounds.selector);
        mdeq.at(5 + 1);

        // pop front and pop back in turn
        assertEq(mdeq.front(), 'g');
        assertEq(mdeq.popFront(), 'g');
        assertEq(mdeq.length(), 5);

        assertEq(mdeq.back(), 'f');
        assertEq(mdeq.popBack(), 'f');
        assertEq(mdeq.length(), 4);

        assertEq(mdeq.front(), 'c');
        assertEq(mdeq.popFront(), 'c');
        assertEq(mdeq.length(), 3);

        assertEq(mdeq.back(), 'e');
        assertEq(mdeq.popBack(), 'e');
        assertEq(mdeq.length(), 2);

        assertEq(mdeq.front(), 'a');
        assertEq(mdeq.popFront(), 'a');
        assertEq(mdeq.length(), 1);

        assertEq(mdeq.back(), 'b');
        assertEq(mdeq.popBack(), 'b');
        assertEq(mdeq.length(), 0);
    }
}

ps:
本人热爱图灵,热爱中本聪,热爱V神。
以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。
同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下!
如果需要转发,麻烦注明作者。十分感谢!

在这里插入图片描述

公众号名称:后现代泼痞浪漫主义奠基人

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

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

相关文章

【Java可执行命令】(十三)策略工具policytool:界面化创建、编辑和管理策略文件中的权限和配置 ~

Java可执行命令之policytool 1️⃣ 概念2️⃣ 优势和缺点3️⃣ 使用3.1 使用方式3.2 使用技巧3.3 注意事项 4️⃣ 应用场景&#x1f33e; 总结 1️⃣ 概念 在Java平台上&#xff0c;安全性是至关重要的。为了提供细粒度的安全管理机制&#xff0c;Java引入了policytool命令。p…

LuckyFrameweb LuckyFrameClient 自动化测试平台 安装部署 使用教程

LuckyFrameweb 自动化测试平台 jdk安装 maven安装 LuckyFrameweb安装 仓库地址 使用maven 打包jar包 docker-compose安装mysql #cat mysql-start.yml version: "3" services:mysql:image: mysql:5.7restart: alwaysenvironment:- TZAsia/Shanghaiports:- 3306:3…

分享:交流负载箱 0~9.999A 可调 步进1mA

前言 最近去客户那边&#xff0c;发现一个问题&#xff0c;他们的交流供电单元 测试很不方便。 需求 供电单元输出&#xff1a; AC220V 50HZ&#xff1b;漏电保护保护功能过载报警功能&#xff1b;超载保护功能&#xff1b; 总而言之&#xff0c;他们需要一台 交流的电子负…

Unity 画线OnPopulateMesh函数VertexHelper

一个画图表&#xff08;折线图&#xff0c;树状图&#xff0c;饼状图&#xff0c;雷达图&#xff09;的插件。 底层使用UGUI中的重写了OnPopulateMesh这个方法&#xff0c; 用来实现鼠标画线的功能。 OnPopulateMesh(VertexHelper vh) {} using System; using System.Collec…

Elisp之buffer-substring-no-properties用法(二十七)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

冲突域 和 广播域

冲突域&#xff1a; 在传统的以粗同轴电缆为传输介质的以太网中&#xff0c;同一介质上的多个节点共享链路的带宽&#xff0c;争用链路的使用权&#xff0c;这样就会发生冲突&#xff0c;CSMA/CD机制中当冲突发生时&#xff0c;网络就要进行回退&#xff0c;这段回退的时间内链…

SpringBoot复习:(15)Spring容器的核心方法refresh是在哪里被调用的?

在SpringApplication的run方法&#xff1a; refreshContext代码如下&#xff1a; 其中调用的refresh方法代码如下&#xff1a; 其中调用的refresh方法代码如下&#xff1a; 其中调用的fresh方法代码如下&#xff1a; 其中调用了super.refresh();而这个super.refresh()就是…

AP5179 高端电流采样降压恒流驱动IC SOP8 LED车灯电源驱动

产品描述 AP5179是一款连续电感电流导通模式的降压恒流源&#xff0c;用于驱动一颗或多颗串联LED输入电压范围从 5 V 到 60V&#xff0c;输出电流 最大可达 2.0A 。根据不同的输入电压和外部器件&#xff0c; 可以驱动高达数十瓦的 LED。内置功率开关&#xff0c;采用高端电流…

AI赋能下的“数字人”与“数智人”:异同解析

由于人工智能技术的快速发展&#xff0c;我们逐渐进入了一个数字化的时代。在这个时代中&#xff0c;两个概念引起了广泛的关注和讨论&#xff0c;那就是“数字人”和“数智人”。虽然这两个概念都与人工智能有关&#xff0c;但它们在含义和应用上存在一些不同之处。在本文中&a…

使用elementplus实现文本框的粘贴复制

需求&#xff1a; 文本框仅用于显示展示数据并且用户可以进行复制&#xff0c;并不会进行修改和编辑&#xff0c; 注意点&#xff1a; 1.首先且文本为多行。所以不能使用普通的el-input&#xff0c;这种一行超出就会隐藏了&#xff0c;如果多行超出行数也会隐藏&#xff08;…

《评论文章-无线纳米技术可以降低脊髓刺激成本和并发症,传统设备与无线刺激设备费用相比的回顾》

SCS治疗可能会出现并发症&#xff0c;并且管理这些并发症的费用很高。 慢性疼痛是促使人们寻求缓解的主要因素&#xff0c;也是阿片类药物研究的主要方向。 SCS治疗取得了突破性进展&#xff0c;在治疗背部手术失败综合征、神经性疼痛障碍、复杂区域疼痛综合征以及血管缺血引…

51单片机学习--蜂鸣器播放音乐

由原理图可知&#xff0c;蜂鸣器BEEP与P1_5 相关&#xff0c;但其实这个原理图有错&#xff0c;实测接的是P2_5 下面这个代码就是以500HZ的频率响500ms的例子 sbit Buzzer P2^5;unsigned char KeyNum; unsigned int i;void main() {while(1){KeyNum Key();if(KeyNum){for(i …

后端进阶之路——综述Spring Security认证,授权(一)

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 「推荐专栏」&#xff1a; ★java一站式服务 ★ ★前端炫酷代码分享 ★ ★ uniapp-从构建到提升★ ★ 从0到英雄&#xff0c;vue成神之路★ ★ 解决算法&#xff0c;一个专栏就够了★ ★ 架…

人脸检测之给照片加上眼镜

人脸检测 文章目录 人脸检测一、背景二、UV空间融合法三、总结与不足四、参考 一、背景 给人脸图像加眼镜在很多领域都有应用。比如修图换造型、眼镜店眼镜试戴、戴眼镜人脸识别等。 给人脸加眼镜的难点在于难以做到自然逼真&#xff0c;且人脸多种多样&#xff0c;角度多变&a…

HCIP——BGP综合实验

BGP综合实验 一、实验拓扑二、实验要求三、实验步骤1、配置接口IP地址与环回地址2、AS2配置OSPF3、配置BGP&#xff0c;建立对等体4、发布路由5、配置路由反射器6、做空接口、汇总以及宣告7、建立GRE隧道8、查看BGP路由表9、测试 一、实验拓扑 二、实验要求 1&#xff0c;AS1存…

关于ETL的两种架构(ETL架构和ELT架构) qt

&#xfeff;ETL&#xff0c;是英文 Extract-Transform-Load 的缩写&#xff0c;用来描述将数据从来源端经过抽取&#xff08;extract&#xff09;、转换&#xff08;transform&#xff09;、加载&#xff08;load&#xff09;至目的端的过程。ETL一词较常用在数据仓库&#xf…

Tomcat8安装并启动服务教程

目录 一、安装JDK 1.检查Linux版本信息 2.官网下载jdk 3.将下载的压缩包上传到Linux主机上 4.安装jdk到指定目录 5.配置环境变量 6.检测 二、安装tomcat 1.官网下载tomcat的安装包 2.将下载的包上传到自己的Linux主机上 3.安装tomcat到指定目录 4.为了方便&#xf…

uni-app 微信小程序自定义导航栏

一、效果图 二、导航栏的组成 上面的导航栏主要由状态栏&#xff08;就是手机电量显示栏&#xff09;和小程序的导航栏组成&#xff0c;android手机一般为48px&#xff0c;ios手机一般为44px 三、开发步骤 1、设置navigationStyle:custom {"path": "pages/v…

P2498 [SDOI2012] 拯救小云公主

题目 思路 伊艳二分 这个题比较难的地方就是如何判断在当前r的情况下能否到达终点 我们可以用并查集来判断两个点是否连接&#xff0c;再加两个点&#xff1a;0和n1 代码 #include<bits/stdc.h> using namespace std; #define _p(x) ((x)*(x)) const int maxn3005; co…

三言两语说透koa的洋葱模型

Koa是一个非常轻量化的Node.js web应用框架,其洋葱圈模型是它独特的设计理念和核心实现机制之一。本文将详细介绍Koa的洋葱圈模型背后的设计思想,以及它是如何实现的。 洋葱圈模型设计思想 Koa的洋葱圈模型主要是受函数式编程中的compose思想启发而来的。Compose函数可以将需…