Michael.W基于Foundry精读Openzeppelin第3期——Arrays.sol

news2024/11/24 5:34:47

Michael.W基于Foundry精读Openzeppelin第3期——Arrays.sol

      • 0. 版本
        • 0.1 Arrays.sol
      • 1. 补充:关于storage的定长数组和动态数组的layout
      • 2. 目标合约
      • 3. 代码精读
        • 3.1 unsafeAccess(address[] storage, uint256)
        • 3.2 unsafeAccess(bytes32[] storage, uint256)
        • 3.3 unsafeAccess(uint256[] storage, uint256)
        • 3.4 findUpperBound(uint256[] storage array, uint256 element)

0. 版本

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

0.1 Arrays.sol

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

Arrays库是一个专门作用于uint256[] storage / address[] storage / bytes32[] storage的工具库。

1. 补充:关于storage的定长数组和动态数组的layout

直接观察demo合约的slot排布:

contract ArrayLayoutChecker {
	// slot0
    uint public a = 2;
    // slot1
    uint[] public arr = [0xdddd, 0xeeee, 0xffff];
    // slot2
    address public addr = address(1024);
    // slot3 ~ slot6
    address[4] public addrs = [address(0xa), address(0xb), address(0xc), address(0xd)];
}

说明:

  • storage的定长数组address[4] public addrs的本位slot为3,那么其元素依次占据slot3 ~ slot6

  • storage的动态数组uint[] public arr的本位slot为2,那么slot2中仅存储该动态数组的长度。其元素将按照以下算法依次存储于对应slot中:bytes32(uint(keccake256(动态数组本位slot号)) + 元素索引)

可见storage的动态数组和定长数组的元素都是按数组内顺序依次存储于slot之中,只是起始的slot号不一样。动态数组的本位slot存储的是动态数组的长度,而定长数组的本位slot存储的是第一个元素。

foundry代码验证

contract ArraysTest is Test {
    ArrayLayoutChecker alc = new ArrayLayoutChecker();

    function test_LayoutForDynamicAndStaticArrays() external {
        // 向动态数组内增添新的元素
        alc.pushArr(0xabcd);

        // 通过slot号读取对应slot中存储的值
        // slot0: 状态变量a的值——2
        uint valueSlot0 = uint(vm.load(address(alc), bytes32(0)));
        assertEq(alc.a(), valueSlot0);
        // slot1: 存放的是动态数组arr中的元素数量,即arr.length
        uint valueSlot1 = uint(vm.load(address(alc), bytes32(uint(1))));
        assertEq(alc.getArrLength(), valueSlot1);
        // slot2: 状态变量addr的值——address(1024)
        address valueSlot2 = address(uint160(uint(vm.load(address(alc), bytes32(uint(2))))));
        assertEq(alc.addr(), valueSlot2);
        // slot3~slot6: 静态数组address[4] addrs 按顺序排布的四个元素
        address valueSlot3 = address(uint160(uint(vm.load(address(alc), bytes32(uint(3))))));
        address valueSlot4 = address(uint160(uint(vm.load(address(alc), bytes32(uint(4))))));
        address valueSlot5 = address(uint160(uint(vm.load(address(alc), bytes32(uint(5))))));
        address valueSlot6 = address(uint160(uint(vm.load(address(alc), bytes32(uint(6))))));
        assertEq(alc.addrs(0), valueSlot3);
        assertEq(alc.addrs(1), valueSlot4);
        assertEq(alc.addrs(2), valueSlot5);
        assertEq(alc.addrs(3), valueSlot6);

        // 动态数组的元素存储的slot号:keccak256(动态数组本位的slot号) + 索引值
        // 本案例中动态数组的本位slot为slot1,即本位slot号为bytes32(uint(1))
        bytes32 startSlot = keccak256(abi.encodePacked(uint(1)));
        // 动态数组的第1个元素的slot号,即startSlotNumber + 0
        assertEq(alc.arr(0), uint(vm.load(address(alc), bytes32(uint(startSlot) + 0))));
        // 动态数组的第2个元素的slot号,即startSlotNumber + 1
        assertEq(alc.arr(1), uint(vm.load(address(alc), bytes32(uint(startSlot) + 1))));
        // 动态数组的第3个元素的slot号,即startSlotNumber + 2
        assertEq(alc.arr(2), uint(vm.load(address(alc), bytes32(uint(startSlot) + 2))));
        // 动态数组的第4个元素的slot号,即startSlotNumber + 3
        assertEq(alc.arr(3), uint(vm.load(address(alc), bytes32(uint(startSlot) + 3))));
        // 注: 动态数组和静态数组的元素在slot中都是按照顺序依次紧密地向后存储在slot中
    }
}

contract ArrayLayoutChecker {
    uint public a = 2;
    uint[] public arr = [0xdddd, 0xeeee, 0xffff];
    address public addr = address(1024);
    address[4] public addrs = [address(0xa), address(0xb), address(0xc), address(0xd)];

    function pushArr(uint v) external {
        arr.push(v);
    }

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

2. 目标合约

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

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

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

import "openzeppelin-contracts/contracts/utils/Arrays.sol";

contract MockArrays {
    using Arrays for uint[];
    using Arrays for bytes32[];
    using Arrays for address[];

    uint[] public arrUint = [1, 2, 11, 19, 21, 22, 100, 201, 224, 999];
    bytes32[] public arrBytes32 = [bytes32('a'), bytes32('b'), bytes32('c'), bytes32('d'), bytes32('e')];
    address[] public arrAddress = [address(0xff), address(0xee), address(0xdd), address(0xcc), address(0xbb), address(0xaa)];

    function findUpperBound(uint element) external view returns (uint){
        return arrUint.findUpperBound(element);
    }

    function unsafeAccessUintArrays(uint pos) external view returns (uint){
        return arrUint.unsafeAccess(pos).value;
    }

    function unsafeAccessBytes32Arrays(uint pos) external view returns (bytes32){
        return arrBytes32.unsafeAccess(pos).value;
    }

    function unsafeAccessAddressArrays(uint pos) external view returns (address){
        return arrAddress.unsafeAccess(pos).value;
    }

    function clearArrUint() external {
        delete arrUint;
    }

    function addArrUint(uint element) external {
        arrUint.push(element);
    }

    function getLength(uint slotNumber) external view returns (uint){
        if (slotNumber == 0) {
            return arrUint.length;
        } else if (slotNumber == 1) {
            return arrBytes32.length;
        } else if (slotNumber == 2) {
            return arrAddress.length;
        } else {
            return 0;
        }
    }
}

全部foundry测试合约:

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

3. 代码精读

3.1 unsafeAccess(address[] storage, uint256)

返回动态address数组中指定索引的元素值。该方法节约gas,但是不进行数组索引越界检查。所以只有当你确定你要取的索引值小于动态address数组长度时才去使用该方法。

function unsafeAccess(address[] storage arr, uint256 pos) internal pure returns (StorageSlot.AddressSlot storage) {
		// 声明一个slot变量,用于计算arr[pos]的slot号
        bytes32 slot;
        assembly {
        	// 在memory的0~32字节的位置存储动态数组arr的slot号
            mstore(0, arr.slot)
            // keccak256(0, 0x20): 将memory中0~32字节的内容(即动态数组arr的slot号)求keccak256
            // slot赋值为arr的slot号的hash值+偏移值pos的和,即在layout中存储的arr[pos]的slot号
            slot := add(keccak256(0, 0x20), pos)
        }
        // 直接用arr[pos]的slot号从storage中取出值
        return slot.getAddressSlot();
}

foundry代码验证

contract ArraysTest is Test {
    MockArrays ma = new MockArrays();

    function test_UnsafeAccess() external {
        uint l = ma.getLength(0);
        for (uint i = 0; i < l; ++i) {
            assertEq(ma.arrUint(i), ma.unsafeAccessUintArrays(i));
        }

        // revert if out of index with []
        vm.expectRevert();
        ma.arrUint(l);
        // not revert with unsafeAccess(), but get zero value
        assertEq(0, ma.unsafeAccessUintArrays(l));

        l = ma.getLength(1);
        for (uint i = 0; i < l; ++i) {
            assertEq(ma.arrBytes32(i), ma.unsafeAccessBytes32Arrays(i));
        }

        // revert if out of index with []
        vm.expectRevert();
        ma.arrBytes32(l);
        // not revert with unsafeAccess(), but get zero value
        assertEq(0, ma.unsafeAccessBytes32Arrays(l));

        l = ma.getLength(2);
        for (uint i = 0; i < l; ++i) {
            assertEq(ma.arrAddress(i), ma.unsafeAccessAddressArrays(i));
        }

        // revert if out of index with []
        vm.expectRevert();
        ma.arrAddress(l);
        // not revert with unsafeAccess(), but get zero value
        assertEq(address(0), ma.unsafeAccessAddressArrays(l));
    }
}

3.2 unsafeAccess(bytes32[] storage, uint256)

返回动态bytes32数组中指定索引的元素值。该方法节约gas,但是不进行数组索引越界检查。所以只有当你确定你要取的索引值小于动态bytes32数组长度时才去使用该方法。

function unsafeAccess(bytes32[] storage arr, uint256 pos) internal pure returns (StorageSlot.Bytes32Slot storage) {
    // 声明一个slot变量,用于计算arr[pos]的slot号
    bytes32 slot;
    // 在memory的0~32字节的位置存储动态数组arr的slot号
    assembly {
        // 在memory的0~32字节的位置存储动态数组arr的slot号
        mstore(0, arr.slot)
        // keccak256(0, 0x20): 将memory中0~32字节的内容(即动态数组arr的slot号)求keccak256
        // slot赋值为arr的slot号的hash值+偏移值pos的和,即在layout中存储的arr[pos]的slot号
        slot := add(keccak256(0, 0x20), pos)
    }
    // 直接用arr[pos]的slot号从storage中取出值
    return slot.getBytes32Slot();
}

foundry代码验证:见3.1

3.3 unsafeAccess(uint256[] storage, uint256)

返回动态uint256数组中指定索引的元素值。该方法节约gas,但是不进行数组索引越界检查。所以只有当你确定你要取的索引值小于动态uint256数组长度时才去使用该方法。

function unsafeAccess(uint256[] storage arr, uint256 pos) internal pure returns (StorageSlot.Uint256Slot storage) {
    // 声明一个slot变量,用于计算arr[pos]的slot号
    bytes32 slot;
    // 在memory的0~32字节的位置存储动态数组arr的slot号
    assembly {
        // 在memory的0~32字节的位置存储动态数组arr的slot号
        mstore(0, arr.slot)
        // keccak256(0, 0x20): 将memory中0~32字节的内容(即动态数组arr的slot号)求keccak256
        // slot赋值为arr的slot号的hash值+偏移值pos的和,即在layout中存储的arr[pos]的slot号
        slot := add(keccak256(0, 0x20), pos)
    }
    // 直接用arr[pos]的slot号从storage中取出值
    return slot.getUint256Slot();
}

foundry代码验证:见3.1

3.4 findUpperBound(uint256[] storage array, uint256 element)

从一个排序好的数组array中,返回第一个大于或等于element的元素的索引值。如果整个数组array中都没有符合条件的元素,则返回整个数组array的长度。

这个操作的时间复杂度为O(log n)

前提条件array为升序排列且其中没有重复的元素值

function findUpperBound(uint256[] storage array, uint256 element) internal view returns (uint256) {
	// 如果是空数组,返回0
    if (array.length == 0) {
        return 0;
    }
	// 两个边界flag
    uint256 low = 0;
    uint256 high = array.length;

	// 开始二分法查找,直到low>=high时停止
    while (low < high) {
    	// 如果low<high,mid为low和high的均值
        uint256 mid = Math.average(low, high);
        // 注:Math.average()如果均值为小数,则向下取整
        if (unsafeAccess(array, mid).value > element) {
        	// 如果索引为mid的元素大于目标值element,缩小范围:令high=mid
            high = mid;
        } else {
        	// 如果索引为mid的元素小于等于目标值element,缩小范围:令low=mid+1
            low = mid + 1;
        }
    }

    // 如果此时low>0,说明此时的low已经是唯一的上界(因为low只有大于等于high才会跳出循环)
    if (low > 0 && unsafeAccess(array, low - 1).value == element) {
    	// 如果array[low-1]等于目标值element,则直接返回索引low-1,即等于目标值的索引
        return low - 1;
    } else {
    	// 如果array[low-1]不等于目标值element,说明array中所有元素都小于目标值element,那么直接返回low(即array的长度)
        return low;
    }
}

foundry代码验证

contract ArraysTest is Test {
    MockArrays ma = new MockArrays();
		
	// 目标数组元素个数为偶数
    function test_FindUpperBound_WithEvenLength() external {
        // arrUint: [1, 2, 11, 19, 21, 22, 100, 201, 224, 999]
        assertEq(ma.getLength(0), 10);
        assertEq(0, ma.findUpperBound(0));
        assertEq(0, ma.findUpperBound(1));
        assertEq(1, ma.findUpperBound(2));
        assertEq(2, ma.findUpperBound(3));
        assertEq(2, ma.findUpperBound(10));
        assertEq(2, ma.findUpperBound(11));
        assertEq(3, ma.findUpperBound(12));
        assertEq(3, ma.findUpperBound(19));
        assertEq(4, ma.findUpperBound(21));
        assertEq(5, ma.findUpperBound(22));
        assertEq(6, ma.findUpperBound(100));
        assertEq(7, ma.findUpperBound(201));
        assertEq(8, ma.findUpperBound(224));
        assertEq(9, ma.findUpperBound(999));
        // greater than all elements in the array, it will return the length of the array
        assertEq(10, ma.findUpperBound(1000));
    }

	// 目标数组元素个数为奇数
    function test_FindUpperBound_WithOddLength() external {
        ma.addArrUint(2000);
        // arrUint: [1, 2, 11, 19, 21, 22, 100, 201, 224, 999, 2000]
        assertEq(ma.getLength(0), 11);
        assertEq(0, ma.findUpperBound(0));
        assertEq(0, ma.findUpperBound(1));
        assertEq(1, ma.findUpperBound(2));
        assertEq(2, ma.findUpperBound(3));
        assertEq(2, ma.findUpperBound(10));
        assertEq(2, ma.findUpperBound(11));
        assertEq(3, ma.findUpperBound(12));
        assertEq(3, ma.findUpperBound(19));
        assertEq(4, ma.findUpperBound(21));
        assertEq(5, ma.findUpperBound(22));
        assertEq(6, ma.findUpperBound(100));
        assertEq(7, ma.findUpperBound(201));
        assertEq(8, ma.findUpperBound(224));
        assertEq(9, ma.findUpperBound(999));
        assertEq(10, ma.findUpperBound(2000));
        // greater than all elements in the array, it will return the length of the array
        assertEq(11, ma.findUpperBound(2001));
    }

	// 目标数组元素个数为0
    function test_FindUpperBound_WithZeroLength() external {
        ma.clearArrUint();
        assertEq(ma.getLength(0), 0);
        // return 0 when the target array is empty
        assertEq(0, ma.findUpperBound(0));
        assertEq(0, ma.findUpperBound(1));
    }
}

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

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

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

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

相关文章

限时等待的互斥量

本文结束一种新的锁&#xff0c;称为 timed_mutex 代码如下&#xff1a; #include<iostream> #include<mutex> #include<thread> #include<string> #include<chrono>using namespace std;timed_mutex tmx;void fun1(int id, const string&a…

MySql入门操作

一.前节回顾 1.web项目环境配置 2.通用增删改&#xff0c;通用查询方法 3.前台&#xff0c;后台代码显示效果 所有你都理解了吗&#xff1f; 二.Mysql数据库介绍 1.什么是MySQL&#xff1f; MySQL是一种开源的关系型数据库管理系统。它是目前最流行和广泛使用的数据库之一&…

【Java|golang】2679. 矩阵中的和

给你一个下标从 0 开始的二维整数数组 nums 。一开始你的分数为 0 。你需要执行以下操作直到矩阵变为空&#xff1a; 矩阵中每一行选取最大的一个数&#xff0c;并删除它。如果一行中有多个最大的数&#xff0c;选择任意一个并删除。 在步骤 1 删除的所有数字中找到最大的一个…

NodeJS 后端返回Base64格式数据显示图片 ⑩⑨ (一篇就够了)

文章目录 ✨文章有误请指正&#xff0c;如果觉得对你有用&#xff0c;请点三连一波&#xff0c;蟹蟹支持&#x1f618;前言Base64前端服务器总结 ✨文章有误请指正&#xff0c;如果觉得对你有用&#xff0c;请点三连一波&#xff0c;蟹蟹支持&#x1f618; ⡖⠒⠒⠒⠤⢄⠀⠀⠀ …

【Java语法小记】求字符串中某个字符的数量——IntStream流的使用

文章目录 引入需求代码原理解读s.chars()IntStream filter​(IntPredicate predicate)long count()补充&#xff1a;IntStream peek​(IntConsumer action) 流操作和管道 引入需求 从一段代码引入 return s.length() - (int) s.chars().filter(c -> c S).count(); 其中 (…

文心一言 VS 讯飞星火 VS chatgpt (54)-- 算法导论6.2 6题

文心一言 VS 讯飞星火 VS chatgpt &#xff08;53&#xff09;-- 算法导论6.2 5题 六、证明:对一个大小为 n的堆&#xff0c;MAX-HEAPIFY 的最坏情况运行时间为 Ω(Ign)。(提示对于n个结点的堆&#xff0c;可以通过对每个结点设定恰当的值&#xff0c;使得从根结点到叶结点路径…

2023年房地产投资退出途径研究报告

第一章 房地产投资概况 房地产&#xff08;Real Estate&#xff09;是一个涵盖了土地及其上的永久性建筑&#xff08;如建筑物和房屋&#xff09;和自然资源&#xff08;如矿产&#xff0c;水源&#xff0c;作物&#xff09;的经济学概念。它可以分为四类&#xff1a;住宅房地…

大数据面试题-场景题

1.手写Flink的UV 手写Flink的UV 2.Flink的分组TopN Flink的分组TopN 3.Spark的分组TopN 1&#xff09;方法1&#xff1a; &#xff08;1&#xff09;按照key对数据进行聚合&#xff08;groupByKey&#xff09; &#xff08;2&#xff09;将value转换为数组&#xff0c;利…

2023如何自学网络安全

自学网络安全可以按照以下步骤进行&#xff1a; 学习基础知识&#xff1a;开始之前&#xff0c;建议先学习计算机网络和操作系统的基础知识&#xff0c;了解网络通信的原理和常见的网络攻击方式。可以通过阅读相关的书籍、在线教程或参加网络安全相关的课程来学习。 学习网络安…

Sanic、uvloop及Asyncio的局限

Sanic sanic使用基于libuv的uvloop事件循环替代python asnycio自带的事件循环替代&#xff0c;以此提高异步性能。Flask和Django是同步框架&#xff0c;Tornado、FastApi和Sanic均为异步框架&#xff0c;Sanic的性能最好。Sanic入门教程&#xff1a;Sanic&#xff1a;一款号称…

阶段小作业:基于docker安装mysql

1.在docker hub 搜索Mysql镜像 docker search --limit 5 Mtsql 2.拉取Mysql 5.7 镜像 docker pull mysql 注意mysql是小写哦 3.创建mysql容器&#xff0c;主机3306端口号映射到容器3306端口 docker run -d -p 3306:3306 --privilegedtrue -v /tmp/mysql/log:/var/log/mysql …

OpenVRLoader 与UnityXR Interaction ToolKit不兼容

1、游戏的VR设备监听与输入都是基于UnityXR,但是当接入OpenVRLoader 时无法正常通过Openvr_xr_plugin去获取设备的输入输出。 2、Openxr 和OpenVRLoader同时打开也还是会没有输入信息。 3、我们需要修改com.unity.xr.interaction.toolkit插件代码,不能直接用packmanage的将插…

从零开始的职场攻略,我是如何成为优秀活动策划的?

想要提升活动策划能力&#xff0c;成为活动操盘手&#xff0c;除了避免踩坑之外&#xff0c;你还需要额外掌握以下 4 项能力。当你持续向着这几个方向提高的时候&#xff0c;你可以感受到作为活动策划带来的成就感&#xff0c;甚至你的整个职业生涯都能够因此迎来一个飞跃。 1…

突破数据边界,开启探索之旅!隐语开源Meetup一周年专场7月22日上海见

小伙伴们&#xff0c;&#x1f4e2;「隐语开源一周年 Meetup 」即将来袭&#xff01;&#x1f389;在一周年 Meetup 上&#xff0c;不仅会对隐语 1.0 版本进行详解&#xff0c;还有新鲜出炉的隐语 MVP 部署体验包&#xff0c;让你秒变高手&#xff01;更有机会与隐私计算行业的…

10年软件测试工程师感悟——写给还在迷茫的朋友

这两天和朋友谈到软件测试的发展&#xff0c;其实软件测试已经在不知不觉中发生了非常大的改变&#xff0c;前几年的软件测试行业还是一个风口&#xff0c;随着不断地转行人员以及毕业的大学生疯狂地涌入软件测试行业&#xff0c;目前软件测试行业“缺口”已经基本饱和。当然&a…

若依前端项目理解

官网&#xff1a;RuoYi 一、目录结构 一级目录&#xff1a;通过vue3.0及以后版本创建的项目文件 二级目录&#xff08;src文件&#xff09;&#xff1a; ruoyi-ui&#xff08;前端文件夹&#xff09; bin文件夹&#xff08;批处理文件&#xff0c;打包、运行&#xff09; bui…

k8s部署springboot

前言 首先以SpringBoot应用为例介绍一下k8s的部署步骤。 1.从代码仓库下载代码&#xff0c;比如GitLab&#xff1b; 2.接着是进行打包&#xff0c;比如使用Maven&#xff1b; 3.编写Dockerfile文件&#xff0c;把步骤2产生的包制作成镜像&#xff1b; 4.上传步骤3的镜像到远程…

MySql基础知识及数据查询

目录 第一章 数据库概述 1.为什么要学习数据库&#xff1f; 2.数据库的相关概念 3.ORM(Object Relational Mapping)思想 4.表与表的记录之间存在哪些关联关系 第二章 基本的SELECT语句 1.SQL的分类 2. SQL基本规则 3.导入现有的数据表、表的数据 4.最基本的…

全网最牛,打通接口自动化测试框架详细,一篇足够

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 接口自动化测试开…

原型设计用得最多的是PS吗?该如何画原型?

在产品开发的前期工作中&#xff0c;产品经理或设计师通常需要进行原型设计工作&#xff0c;创建一个可交互和可视化的原型&#xff0c;以更准确地表达他们的设计构思和想法&#xff0c;并为项目顺利递交给开发人员做好准备。进行原型设计时&#xff0c;使用设计工具来画原型图…