当前位置: 首页 > article >正文

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

文章目录

  • 映射类型
      • 可迭代映射

在这里插入图片描述

映射类型

映射类型使用语法 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; // 将当前值累加到总和}}
}

相关文章:

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

文章目录 映射类型可迭代映射 映射类型 映射类型使用语法 mapping(KeyType KeyName? > ValueType ValueName?)&#xff0c;映射类型的变量声明使用语法 mapping(KeyType KeyName? > ValueType ValueName?) VariableName。 KeyType 可以是任何内置值类型、bytes、st…...

Flask与 FastAPI 对比:哪个更适合你的 Web 开发?

在开发 Web 应用时&#xff0c;Python 中有许多流行的 Web 框架可以选择&#xff0c;其中 Flask 和 FastAPI 是两款广受欢迎的框架。它们各有特色&#xff0c;适用于不同的应用场景。本文将从多个角度对比这两个框架&#xff0c;帮助你更好地选择适合的框架来构建你的 Web 应用…...

QT 中的元对象系统(五):QMetaObject::invokeMethod的使用和实现原理

目录 1.简介 2.原理概述 3.实现分析 3.1.通过方法名调用方法的实现分析 3.2.通过可调用对象调用方法的实现分析 4.使用场景 5.总结 1.简介 QMetaObject::invokeMethod 是 Qt 框架中的一个静态方法&#xff0c;用于在运行时调用对象的成员函数。这个方法提供了一种动态调…...

Linux进程管理与进程间通信

一、进程基础知识 1. 进程的定义与特性 **定义**&#xff1a;进程是程序的一次执行过程&#xff0c;是系统资源分配的基本单位 **特性**&#xff1a; - 动态性&#xff1a;进程是程序的动态执行过程 - 并发性&#xff1a;多个进程可以并发执行 - 独立性&#xff1a;进…...

【无人机】无人机PX4飞控系统高级软件架构

目录 1、概述&#xff08;图解&#xff09; 一、数据存储层&#xff08;Storage&#xff09; 二、外部通信层&#xff08;External Connectivity&#xff09; 三、核心通信枢纽&#xff08;Message Bus&#xff09; 四、硬件驱动层&#xff08;Drivers&#xff09; 五、飞…...

启动arthas-boot.jar端口占用

问题 [rootlocalhost arthas-4.0.4]# java -jar arthas-boot.jar [ERROR] The telnet port 3658 is used by process 7066 instead of target process 6155, you will connect to an unexpected process. [ERROR] 1. Try to restart arthas-boot, select process 7066, shutdow…...

JSVMP逆向实战:原理分析与破解思路详解

引言 在当今Web安全领域&#xff0c;JavaScript虚拟机保护&#xff08;JSVMP&#xff09;技术被广泛应用于前端代码的保护和反爬机制中。作为前端逆向工程师&#xff0c;掌握JSVMP逆向技术已成为必备技能。本文将深入剖析JSVMP的工作原理&#xff0c;并分享实用的逆向破解思路…...

【SPP】蓝牙链路控制(LC)在SPP中互操作性深度解析

在蓝牙协议栈的精密分层体系中&#xff0c;其链路控制&#xff08;Link Control, LC&#xff09;层作为基带层的核心组件&#xff0c;承载着物理信道管理、连接建立与维护等关键任务。其互操作性要求直接决定了不同厂商设备能否实现无缝通信。本文将以蓝牙技术规范中的LC互操作…...

单片机学习之定时器

定时器是用来定时的机器&#xff0c;是存在于STM32单片机中的一个外设。STM32一般总共有8个定时器&#xff0c;分别是2个高级定时器&#xff08;TIM1、TIM8&#xff09;&#xff0c;4个通用定时器&#xff08;TIM2、TIM3、TIM4、TIM5&#xff09;和2个基本定时器&#xff08;TI…...

供应链管理:计算题 / 倒扣法

一、理解倒扣法 在供应链管理中&#xff0c;倒扣法是一种常用的成本计算方法&#xff0c;主要用于确定商品的成本和销售价格&#xff0c;以确保特定的毛利率。倒扣法的基本原理是在已知售价和期望毛利率的情况下&#xff0c;逆推计算出供货价或成本价。 二、倒扣法的计算公式…...

算法每日一练 (25)

&#x1f4a2;欢迎来到张翊尘的技术站 &#x1f4a5;技术如江河&#xff0c;汇聚众志成。代码似星辰&#xff0c;照亮行征程。开源精神长&#xff0c;传承永不忘。携手共前行&#xff0c;未来更辉煌&#x1f4a5; 文章目录 算法每日一练 (25)四数之和题目描述解题思路解题代码c…...

【大模型基础_毛玉仁】6.4 生成增强

目录 6.4 生成增强6.4.1 何时增强1&#xff09;外部观测法2&#xff09;内部观测法 6.4.2 何处增强6.4.3 多次增强6.4.4 降本增效1&#xff09;去除冗余文本2&#xff09;复用计算结果 6.4 生成增强 检索器得到相关信息后&#xff0c;将其传递给大语言模型以期增强模型的生成能…...

Zephyr实时操作系统初步介绍

一、概述 Zephyr是由Linux基金会托管的开源实时操作系统&#xff08;RTOS&#xff09;&#xff0c;专为资源受限的物联网设备设计。其核心特性包括模块化架构、跨平台兼容性、安全性优先以及丰富的连接协议支持。基于Apache 2.0协议&#xff0c;Zephyr允许商业和非商业用途的自…...

【GCC警告报错4】warning: format not a string literal and no format arguments

文章主本文根据笔者个人工作/学习经验整理而成&#xff0c;如有错误请留言。 文章为付费内容&#xff0c;已加入原创保护&#xff0c;禁止私自转载。 文章发布于&#xff1a;《C语言编译报错&警告合集》 如图所示&#xff1a; 原因&#xff1a; snprintf的函数原型&#x…...

【落羽的落羽 C++】模板简介

文章目录 一、模板的引入二、函数模板1. 函数模板的使用2. 函数模板的原理3. 函数模板的实例化4. 函数模板的匹配 三、类模板 一、模板的引入 假如我们想写一个Swap函数&#xff0c;针对每一种类型&#xff0c;都要函数重载写一次&#xff0c;但它们的实现原理是几乎一样的。在…...

USB(通用串行总线)数据传输机制和包结构简介

目录 1. USB的物理连接电缆结构时钟恢复技术 2. USB的数据传输方式包&#xff08;Packet&#xff09; 3. 包的传输规则帧和微帧 4. 包的结构1. 同步字段&#xff08;Sync&#xff09;2. 包标识符字段&#xff08;PID&#xff09;3. 数据字段4. 循环冗余校验字段&#xff08;CRC…...

【目标检测】【深度学习】【Pytorch版本】YOLOV3模型算法详解

【目标检测】【深度学习】【Pytorch版本】YOLOV3模型算法详解 文章目录 【目标检测】【深度学习】【Pytorch版本】YOLOV3模型算法详解前言YOLOV3的模型结构YOLOV3模型的基本执行流程YOLOV3模型的网络参数 YOLOV3的核心思想前向传播阶段反向传播阶段 总结 前言 YOLOV3是由华盛顿…...

【前端扫盲】postman介绍及使用

Postman 是一款专为 API 开发与测试设计的 全流程协作工具&#xff0c;程序员可通过它高效完成接口调试、自动化测试、文档管理等工作。以下是针对程序员的核心功能介绍和应用场景说明&#xff1a; 一、核心功能亮点 接口请求构建与调试 支持所有 HTTP 方法&#xff08;GET/POS…...

每日c/c++题 备战蓝桥杯(全排列问题)

题目描述 按照字典序输出自然数 1 到 n 所有不重复的排列&#xff0c;即 n 的全排列&#xff0c;要求所产生的任一数字序列中不允许出现重复的数字。 输入格式 一个整数 n。 输出格式 由 1∼n 组成的所有不重复的数字序列&#xff0c;每行一个序列。 每个数字保留 5 个场…...

IdeaVim-AceJump

‌AceJump 是一款专为IntelliJ IDEA平台打造的开源插件&#xff0c;旨在通过简单的快捷键操作帮助用户快速跳转到编辑器中的任何符号位置&#xff0c;如变量名、方法调用或特定的字符串‌。无论是大型项目还是日常编程&#xff0c;AceJump 都能显著提升你的代码导航速度和效率。…...

BMS电池关键参数及其含义

BMS概述 BMS的定义与功能 BMS&#xff0c;即电池管理系统&#xff0c;是电池系统的核心控制设备&#xff0c;充当着电池的“状态观测器”。它通过传感器采集电池的单体电压、温度、电流等关键参数&#xff0c;并利用电子控制单元&#xff08;ECU&#xff09;进行数据处理和分…...

DataFrame行索引操作以及重置索引

一.DataFrame行索引操作 1.1 获取数据 1.1.1 loc 选取数据 df.loc[ ] 只能使用标签索引&#xff0c;不能使用整数索引。 当通过标签索引的切片方式来筛选数据时&#xff0c;它的取值前闭后闭。 传参&#xff1a; 1.如果选择单行或单列&#xff0c;返回的数据类型为 Series…...

DayDreamer: World Models forPhysical Robot Learning

DayDreamer&#xff1a;用于物理机器人学习的世界模型 Philipp Wu* Alejandro Escontrela* Danijar Hafner* Ken Goldberg Pieter Abbeel 加州大学伯克利分校 *贡献相同 摘要&#xff1a;为了在复杂环境中完成任务&#xff0c;机器人需要从经验中学习。深度强化学习是机器人学…...

线性欧拉筛

线性筛&#xff1a;高效求解素数 在数论中&#xff0c;素数的筛选是一个经典的问题。最常见的素数筛选方法是埃拉托斯特尼筛法&#xff0c;其时间复杂度为 O ( n log ⁡ log ⁡ n ) O(n\log \log n) O(nloglogn)&#xff0c;非常适合求解小范围内的素数。随着问题规模的增大&…...

Flutter vs React Native:跨平台移动开发框架对比

文章目录 前言1. 框架概述什么是 Flutter&#xff1f;什么是 React Native&#xff1f; 2. 性能对比Flutter 的性能表现React Native 的性能表现总结&#xff1a; 3. 开发体验对比3.1 开发效率3.2 UI 组件库 4. 生态系统对比5. 适用场景分析6. 结论&#xff1a;如何选择&#x…...

用matlab搭建一个简单的图像分类网络

文章目录 1、数据集准备2、网络搭建3、训练网络4、测试神经网络5、进行预测6、完整代码 1、数据集准备 首先准备一个包含十个数字文件夹的DigitsData&#xff0c;每个数字文件夹里包含1000张对应这个数字的图片&#xff0c;图片的尺寸都是 28281 像素的&#xff0c;如下图所示…...

AI辅助开发插件

适合Java程序员的AI辅助开发插件&#xff0c;按功能和适用场景分类&#xff1a; 1. 飞算JavaAI • 特点&#xff1a;从需求分析到代码生成的全流程智能引导&#xff0c;支持Maven、Gradle等主流工具&#xff0c;一键生成完整工程代码&#xff0c;包括配置文件、源代码和测试资…...

【AI4CODE】5 Trae 锤一个基于百度Amis的Crud应用

【AI4CODE】目录 【AI4CODE】1 Trae CN 锥安装配置与迁移 【AI4CODE】2 Trae 锤一个 To-Do-List 【AI4CODE】3 Trae 锤一个贪吃蛇的小游戏 【AI4CODE】4 Trae 锤一个数据搬运工的小应用 1 百度 Amis 简介 百度 Amis 是一个低代码前端框架&#xff0c;由百度开源。它通过 J…...

npm webpack打包缓存 导致css引用地址未更新

问题如下&#xff1a; 测试环境配置&#xff1a; publicPath: /chat/,生产环境配置&#xff1a; publicPath: /,css中引用背景图片 background-image: url(/assets/images/calendar/arrow-left.png);先打包测试环境&#xff0c;观察打包后的css文件引用的背景图片地址 可以全…...

ollama导入huggingface下载的大模型并量化

1. 导入GGUF 类型的模型 1.1 先在huggingface 下载需要ollama部署的大模型 1.2 编写modelfile 在ollama 里面输入 ollama show --modelfile <你有的模型名称> eg: ollama show --modelfile qwen2.5:latest修改其中的from 路径为自己的模型下载路径 FROM /Users/lzx/A…...