【区块链安全 | 第十九篇】类型之映射类型

文章目录

  • 映射类型
      • 可迭代映射

在这里插入图片描述

映射类型

映射类型使用语法 mapping(KeyType KeyName? => ValueType ValueName?),映射类型的变量声明使用语法 mapping(KeyType KeyName? => ValueType ValueName?) VariableName

KeyType 可以是任何内置值类型、bytesstring 或任何合约类型或枚举类型。其他用户定义的复杂类型,如映射、结构体或数组类型是不允许的。

ValueType 可以是任何类型,包括映射、数组和结构体。

KeyNameValueName 是可选的(因此 mapping(KeyType => ValueType) 也是有效的),它们可以是任何有效的标识符,但不能是类型。

你可以将映射看作哈希表,它在内部初始化,每个可能的键都会映射到一个值,该值的字节表示是全零,即类型的默认值。相似之处仅限于此,键数据不会存储在映射中,只有它的 keccak256 哈希值用于查找值。

由于这个原因,映射没有长度或键值是否已设置的概念,因此无法在没有额外信息的情况下删除映射。

映射只能有存储数据位置,因此只能作为状态变量、作为函数中的存储引用类型,或者作为库函数的参数。它们不能作为合约函数的公共参数或返回参数。这些限制也适用于包含映射的数组和结构体。

你可以将映射类型的状态变量标记为公共的,Solidity 会为你自动创建一个 getter。KeyType 变为 getter 的一个参数,并使用 KeyName(如果指定的话)。如果 ValueType 是值类型或结构体,getter 返回与该类型匹配的 ValueType(如果指定了 ValueName)。如果 ValueType 是数组或映射,则 getter 会有一个参数对应每个 KeyType,并递归处理。

在下面的示例中,MappingExample 合约定义了一个公共的 balances 映射,键类型为 address,值类型为 uint,将以太坊地址映射到无符号整数值。由于 uint 是值类型,getter 返回一个与该类型匹配的值,在 MappingUser 合约中,你可以看到它返回指定地址的值。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;// 定义一个包含地址和余额的映射的合约
contract MappingExample {// 声明一个公共的映射,address => uint,记录每个地址的余额mapping(address => uint) public balances;// 更新函数:允许发送者更新他们的余额function update(uint newBalance) public {balances[msg.sender] = newBalance;  // 将调用者的余额更新为 newBalance}
}// 定义一个合约用于与 MappingExample 合约进行交互
contract MappingUser {// 一个函数,用来调用 MappingExample 合约的 update 方法,并返回当前合约地址的余额function f() public returns (uint) {// 创建 MappingExample 合约的实例MappingExample m = new MappingExample();// 调用 update 函数,将余额设置为 100m.update(100);// 返回当前合约地址的余额return m.balances(address(this));  // 返回 MappingExample 合约中当前合约地址的余额}
}

下面的示例是一个简化版的 ERC20 代币。_allowances 是一个映射类型,嵌套在另一个映射类型内部。我们为映射提供了可选的 KeyNameValueName。这不会影响合约的功能或字节码,它仅仅是在 ABI 中为映射的 getter 输入和输出设置了名称字段。

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.18;// 定义一个包含映射的智能合约
contract MappingExampleWithNames {// 定义一个 public 映射,映射地址(address)到余额(uint)。映射的键为 `user`,值为 `balance`。// 这个映射会自动生成一个 getter 函数,可以根据地址(address)查询对应的余额。mapping(address user => uint balance) public balances;// 更新余额的函数,接受一个 `newBalance` 参数。// 这个函数会将调用者(msg.sender)的余额更新为 `newBalance`。function update(uint newBalance) public {// 使用调用者的地址(msg.sender)作为键,更新其对应的余额值。balances[msg.sender] = newBalance;}
}

在下面的示例中,_allowances 映射用于记录某人被授权从你的账户中提取的金额:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.9.0;contract MappingExample {// 定义一个私有映射 `_balances`,将每个地址映射到一个无符号整数(余额)。mapping(address => uint256) private _balances;// 定义一个私有映射 `_allowances`,它是一个二层映射,记录每个地址(owner)允许另一个地址(spender)提取的金额。mapping(address => mapping(address => uint256)) private _allowances;// 定义事件,当转账发生时触发。event Transfer(address indexed from, address indexed to, uint256 value);// 定义事件,当批准时触发,表明某个地址被授权从另一个地址提取一定金额。event Approval(address indexed owner, address indexed spender, uint256 value);// 查询某个地址被授权的提取金额function allowance(address owner, address spender) public view returns (uint256) {return _allowances[owner][spender];}// 从一个账户向另一个账户转账,同时检查授权金额是否足够function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {// 确保 sender 允许 msg.sender(调用者)提取足够的金额require(_allowances[sender][msg.sender] >= amount, "ERC20: Allowance not high enough.");// 减少授权金额_allowances[sender][msg.sender] -= amount;// 执行转账_transfer(sender, recipient, amount);return true;}// 批准另一个地址(spender)从调用者的账户中提取指定金额(amount)function approve(address spender, uint256 amount) public returns (bool) {// 确保 spender 地址不是零地址require(spender != address(0), "ERC20: approve to the zero address");// 设置授权金额_allowances[msg.sender][spender] = amount;// 触发批准事件emit Approval(msg.sender, spender, amount);return true;}// 内部转账函数,用于更新账户余额,并触发转账事件function _transfer(address sender, address recipient, uint256 amount) internal {// 确保 sender 和 recipient 不是零地址require(sender != address(0), "ERC20: transfer from the zero address");require(recipient != address(0), "ERC20: transfer to the zero address");// 确保 sender 有足够的余额require(_balances[sender] >= amount, "ERC20: Not enough funds.");// 执行转账:从 sender 减去金额,给 recipient 增加金额_balances[sender] -= amount;_balances[recipient] += amount;// 触发转账事件emit Transfer(sender, recipient, amount);}
}

可迭代映射

你不能直接迭代映射,即不能枚举它们的键。不过,你可以在其基础上实现一个数据结构并对其进行迭代。例如,下面的代码实现了一个 IterableMapping 库,User 合约将数据添加到该库中,并且 sum 函数会迭代这个数据结构以求和所有值。

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.8;// 定义 IndexValue 结构体,用于存储每个键对应的索引和数值
struct IndexValue { uint keyIndex; // 键的索引位置uint value;    // 键对应的值
}// 定义 KeyFlag 结构体,用于标记键是否被删除
struct KeyFlag { uint key;      // 键的值bool deleted;  // 是否已删除标志
}// 定义 itmap 结构体,包含了一个映射和一个存储键的数组,以及当前大小
struct itmap {mapping(uint => IndexValue) data; // 存储键值对的映射KeyFlag[] keys;                   // 存储键的数组uint size;                         // 数据大小
}// 定义一个类型 Iterator,实质上是 uint 类型,用于遍历
type Iterator is uint;// 定义 IterableMapping 库,提供操作 itmap 类型数据的函数
library IterableMapping {// 插入数据到 itmap 中,如果已存在则更新数据function insert(itmap storage self, uint key, uint value) internal returns (bool replaced) {uint keyIndex = self.data[key].keyIndex; // 获取当前键的索引self.data[key].value = value;            // 更新该键对应的值if (keyIndex > 0) {return true; // 如果键已经存在,返回 true,表示数据已替换} else {// 如果键不存在,分配一个新的索引keyIndex = self.keys.length;self.keys.push(); // 在数组末尾添加一个新元素self.data[key].keyIndex = keyIndex + 1; // 设置新键的索引self.keys[keyIndex].key = key; // 将键添加到键数组中self.size++; // 增加数据大小return false; // 返回 false,表示插入了新的键值对}}// 从 itmap 中删除指定的键function remove(itmap storage self, uint key) internal returns (bool success) {uint keyIndex = self.data[key].keyIndex; // 获取该键的索引if (keyIndex == 0) {return false; // 如果键不存在,返回 false}delete self.data[key]; // 删除数据映射中的键值对self.keys[keyIndex - 1].deleted = true; // 将对应的 KeyFlag 标记为已删除self.size--; // 减小数据大小return true; // 返回 true,表示删除成功}// 检查 itmap 中是否包含指定的键function contains(itmap storage self, uint key) internal view returns (bool) {return self.data[key].keyIndex > 0; // 如果该键存在,返回 true}// 初始化遍历,返回一个迭代器function iterateStart(itmap storage self) internal view returns (Iterator) {return iteratorSkipDeleted(self, 0); // 跳过已删除的项,返回起始迭代器}// 检查当前迭代器是否有效function iterateValid(itmap storage self, Iterator iterator) internal view returns (bool) {return Iterator.unwrap(iterator) < self.keys.length; // 如果迭代器位置小于键数组长度,则有效}// 获取下一个迭代器,跳过已删除的项function iterateNext(itmap storage self, Iterator iterator) internal view returns (Iterator) {return iteratorSkipDeleted(self, Iterator.unwrap(iterator) + 1); // 跳到下一个有效项}// 获取当前迭代器对应的键和值function iterateGet(itmap storage self, Iterator iterator) internal view returns (uint key, uint value) {uint keyIndex = Iterator.unwrap(iterator); // 获取迭代器的索引key = self.keys[keyIndex].key;             // 获取键value = self.data[key].value;              // 获取值}// 跳过已删除的项,返回有效的迭代器位置function iteratorSkipDeleted(itmap storage self, uint keyIndex) private view returns (Iterator) {while (keyIndex < self.keys.length && self.keys[keyIndex].deleted) // 如果该项被标记为删除,则跳过keyIndex++;return Iterator.wrap(keyIndex); // 返回跳过已删除项后的迭代器}
}// User 合约使用 IterableMapping 库进行数据操作
contract User {itmap data; // 声明一个 itmap 类型的变量来保存数据// 使用 IterableMapping 库来操作 itmap 类型的数据using IterableMapping for itmap;// 插入数据到 itmap 中function insert(uint k, uint v) public returns (uint size) {// 调用 IterableMapping 库的 insert 函数插入数据data.insert(k, v);// 返回当前数据的大小return data.size;}// 计算所有存储数据的总和function sum() public view returns (uint s) {// 遍历 itmap 中的所有数据并计算总和for (Iterator i = data.iterateStart(); // 初始化迭代器data.iterateValid(i); // 检查迭代器是否有效i = data.iterateNext(i) // 获取下一个有效项) {(, uint value) = data.iterateGet(i); // 获取当前项的值s += value; // 将当前值累加到总和}}
}

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

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

相关文章

动态循环表单+动态判断表单类型+动态判断表单是否必填方法

页面效果&#xff1a; 接口请求到的数据格式&#xff1a; list: [{demandType: "设备辅助功能要求",demandSettingList: [{id: "1907384788664963074",name: "测试表单",fieldType: 0,contentValue: "",vaildStatus: 0, // 0 非必填&a…

蓝桥杯DFS算法题(java)

最大连通 链接&#xff1a; https://www.lanqiao.cn/problems/2410/learning/ 问题描述 小蓝有一个 30 行 60 列的数字矩阵&#xff0c;矩阵中的每个数都是 0 或 1 。 1100100000111111101010010010011010101110110110111010011111100100000000010100011011000000100101100011…

解锁物种分布模拟新技能:MaxEnt 模型与 R 语言的奇妙融合

技术点目录 第二章、常用数据检索与R语言自动化下载及可视化方法第三章、R语言数据清洗与特征变量筛选第四章、基于ArcGIS、R数据处理与进阶第五章、基于Maxent的物种分布建模与预测第六章、基于R语言的模型参数优化第七章、物种分布模型结果分析与论文写作 —————————…

三轴云台之相机技术篇

一、结构设计 三轴云台通常由空间上三个互相垂直的框架构成&#xff0c;包括内框&#xff08;俯仰框&#xff09;、中框&#xff08;方位框&#xff09;和外框&#xff08;横滚框&#xff09;。这些框架分别负责控制相机的俯仰运动、方位运动和横滚运动&#xff0c;从而实现对目…

全文 - MLIR Toy Tutorial Chapter 3 :高层次上语言特定的分析和变换

使用 C 风格的模式匹配和重写来优化转置运算 使用 DRR 优化 reshape 运算 创建一种贴近输入语言的语义表示的方言&#xff0c;可以在 MLIR 中分析、变换和优化&#xff0c;这些过程中需要用到高级语言的信息&#xff0c;而且通常是在语言的 AST 上执行的这些过程。…

js逆向入门图灵爬虫练习平台 第四题学习

(base64解码&#xff09;地址:aHR0cHM6Ly9zdHUudHVsaW5ncHl0b24uY24vcHJvYmxlbS1kZXRhaWwvNC8 先找到请求接口带有加密参数&#xff1a; 全局搜索Sign,找到参数生成位置 看到这就一目了然塞&#xff0c;知道参数是怎么构造生成的&#xff0c;不知道这段 JavaScript 代码没关系…

【Flask开发】嘿马文学web完整flask项目第2篇:2.用户认证,Json Web Token(JWT)【附代码文档】

教程总体简介&#xff1a;2. 目标 1.1产品与开发 1.2环境配置 1.3 运行方式 1.4目录说明 1.5数据库设计 2.用户认证 Json Web Token(JWT) 3.书架 4.1分类列表 5.搜索 5.3搜索-精准&高匹配&推荐 6.小说 6.4推荐-同类热门推荐 7.浏览记录 8.1配置-阅读偏好 8.配置 9.1项目…

[dp5_多状态dp] 按摩师 | 打家劫舍 II | 删除并获得点数 | 粉刷房子

目录 1.面试题 17.16. 按摩师 题解 2.打家劫舍 II 题解 3.删除并获得点数 题解 4.粉刷房子 题解 一定要有这样的能力&#xff0c;碰到一个新题的时候&#xff0c;可以往之前做过的题方向靠&#xff01; 打家劫舍问题模型: 不能选择相邻的两个数&#xff0c;并且要最终…

基于javaweb的SSM羽毛球会员俱乐部系统场馆课程运动设计与实现(源码+文档+部署讲解)

技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论文…

windows下git bash安装SDKMan报错Looking for unzip...Not found

需要在jdk8和jdk17两个版本切换。最简单的是通过手动切换&#xff0c;但切换过程太繁琐&#xff0c;修改环境变量&#xff0c;达到切换目的。于是尝试其它解决方案&#xff0c;最终确实使用sdkman工具。 确保安装了git Git - Downloading Package 记住安装的路径&#xff0c;…

rnn的音频降噪背后技术原理

rnniose: 这个演示展示了 RNNoise 项目&#xff0c;说明了如何将深度学习应用于噪声抑制。其核心理念是将经典的信号处理方法与深度学习结合&#xff0c;打造一个小巧、快速的实时噪声抑制算法。它不需要昂贵的 GPU —— 在树莓派上就能轻松运行。 相比传统的噪声抑制系统&…

剑指Offer(数据结构与算法面试题精讲)C++版——day3

剑指Offer&#xff08;数据结构与算法面试题精讲&#xff09;C版——day3 题目一&#xff1a;数组中和为0的3个数字题目二&#xff1a;和大于或等于k的最短子数组题目三&#xff1a;乘积小于k的子数组 题目一&#xff1a;数组中和为0的3个数字 前面我们提到&#xff0c;在一个排…

全新UI好看404页面源码

源码介绍 全新UI好看404页面源码,源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行 效果预览 源码获取 全新UI好看404页面源码

递归典例---汉诺塔

https://ybt.ssoier.cn/problem_show.php?pid1205 #include<bits/stdc.h> #define endl \n #define pii pair<int,int>using namespace std; using ll long long;void move(int n,char a,char b,char c) // n 个盘子&#xff0c;通过 b&#xff0c;从 a 移动到 …

php的高速缓存

部署方法 在我们安装的nginx中默认不支持memc和srcache功能&#xff0c;需要借助第三方模块来让nginx支持此功能。 tar zxf srcache-nginx-module-0.33.tar.gz tar zxf memc-nginx-module-0.20.tar.gz 下载这俩个模块&#xff0c;然后编译安装的时候加进去 编译安装完成之后…

视频设备轨迹回放平台EasyCVR打造视频智能融合新平台,驱动智慧机场迈向数字新时代

一、行业背景​ 随着 5G、AI、物联网、大数据等前沿技术的不断更新换代&#xff0c;交通行业进入数字化转型的高速发展时期。航空业作为交通领域的重要部分&#xff0c;数字化进程从追求速度往注重质量的转变。但机场在数字化转型中面临许多严峻挑战&#xff0c;如现有运营模式…

【论文阅读】Anchor Graph Network for Incomplete Multiview Clustering

摘要 近年来&#xff0c;不完全多视图聚类&#xff08;IMVC&#xff09;受到广泛关注。然而&#xff0c;现有研究仍然存在以下几个不足之处&#xff1a;1) 部分方法忽略了样本对在全局结构分布中的关联性&#xff1b;2) 许多方法计算成本较高&#xff0c;因此无法应用于大规模…

15. 远程服务器运行jemter的GUI方式

1. 问题 在 linux 服务器或远程服务器上&#xff0c;安装 Jmeter&#xff0c;打不开 Jmeter 的 GUI 界面。 环境&#xff1a; linux 服务器mac 电脑 需求&#xff1a;在远程服务器中&#xff0c;启动 jmeter&#xff08;./bin/jmeter &&#xff09;后&#xff0c;在 ma…

Ansible:playbook的高级用法

文章目录 1. handlers与notify2. tags组件3. playbook中使用变量3.1使用 setup 模块中变量3.2在playbook 命令行中定义变量3.3在playbook文件中定义变量3.4使用变量文件3.5主机清单文件中定义变量主机变量组&#xff08;公共&#xff09;变量 1. handlers与notify Handlers&am…

什么是msvcp140.dll?msvcp140.dll丢失的解决方法又有哪些?

msvcp140.dll 是 Microsoft Visual C Redistributable 的核心动态链接库文件&#xff0c;许多软件和游戏依赖它来运行。当系统提示“msvcp140.dll丢失”时&#xff0c;意味着该文件无法被正确加载&#xff0c;导致程序崩溃或无法启动。本文将提供最全面的 msvcp140.dll丢失的解…