【区块链安全 | 第十六篇】类型之值类型(三)
文章目录
- 函数类型
- 声明语法
- 转换
- 成员
- 合约更新时的值稳定性
- 示例

函数类型
函数类型是函数的类型。函数类型的变量可以通过函数进行赋值,函数类型的参数可以用来传递函数并返回函数。
函数类型有两种类型:内部函数和外部函数。
-
内部函数只能在当前合约内调用(更具体地说,在当前代码单元内调用,这也包括内部库函数和继承函数),因为它们无法在当前合约的上下文外执行。调用内部函数的方式是跳转到它的入口标签,就像在当前合约内部调用一个函数一样。
-
外部函数包含一个地址和一个函数签名,可以通过外部函数调用传递和返回。
需要注意的是,当前合约的公共函数可以同时作为内部和外部函数使用。要将 f 作为内部函数使用,只需使用 f,如果要将其作为外部函数使用,则使用 this.f。
如果函数类型的变量未初始化,调用该变量会导致 Panic 错误。如果在对其使用 delete 后调用函数,也会出现相同的错误。
声明语法
函数类型的声明语法如下:
function (<参数类型>) {internal|external} [pure|view|payable] [returns (<返回类型>)]
与参数类型不同,返回类型不能为空——如果函数类型不返回任何值,则整个 returns (<返回类型>) 部分必须省略。
默认情况下,函数类型是 internal,因此可以省略 internal 关键字。请注意,这仅适用于函数类型。对于在合约中定义的函数,必须显式指定可见性,它们没有默认值。
转换
当且仅当函数类型 A 和函数类型 B 的参数类型、返回类型、内部/外部属性相同,并且 A 的状态可变性比 B 更严格时,函数类型 A 可以隐式转换为函数类型 B。具体来说:
- pure 函数可以转换为 view 和非 payable 函数
- view 函数可以转换为非 payable 函数
- payable 函数可以转换为非 payable 函数
其他函数类型之间的转换是不可能的。
如果在 Solidity 的上下文之外使用外部函数类型,它们会被视为函数类型,该类型将地址和函数标识符一起编码在一个单一的 bytes24 类型中。
内部类型的函数可以被分配给一个内部函数类型的变量,无论它在哪里定义。这包括合约和库的 private、internal 和 public 函数,以及自由函数。另一方面,外部函数类型只能与公共和外部合约函数兼容。
注意
带有 calldata 参数的外部函数类型与带有 calldata 参数的外部函数类型不兼容。它们与相应的内存参数类型兼容。例如,没有一个函数可以被类型为 function (string calldata) external 的值指向,而 function (string memory) external 可以指向函数 f(string memory) external {} 和函数 g(string calldata) external {}。这是因为对于这两种位置,参数都以相同的方式传递给函数。调用方不能直接将其 calldata 传递给外部函数,并且始终将参数编码到内存中。将参数标记为 calldata 仅影响外部函数的实现,在调用方的函数指针中是没有意义的。
成员
外部(或公共)函数具有以下成员:
- .address 返回函数所在合约的地址。
- .selector 返回 ABI 函数选择器。
外部(或公共)函数曾经有额外的成员 .gas(uint) 和 .value(uint)。这些在 Solidity 0.6.2 中已被弃用,并在 Solidity 0.7.0 中移除。现在使用 {gas: …} 和 {value: …} 来指定发送给函数的 gas 数量或 wei 数量。有关更多信息,请参见外部函数调用。
合约更新时的值稳定性
在使用函数类型的值时,需要考虑一个重要的方面,即如果底层代码发生变化,值是否仍然有效。
区块链的状态并非完全不可变,并且有多种方法可以在同一地址下放置不同的代码:
- 通过加盐合约创建直接部署不同的代码。
- 通过 DELEGATECALL 委托到不同的合约(代理合约背后的可升级代码是一个常见的例子)。
- EIP-7702 定义的账户抽象。
外部函数类型可以被视为与合约的 ABI 一样稳定,这使得它们非常可移植。它们的 ABI 表示始终由合约地址和函数选择器组成,长期存储或在合约之间传递是完全安全的。虽然被引用的函数可能会发生变化或消失,但直接的外部调用将受到相同的影响,因此在这种使用方式下没有额外的风险。
然而,对于内部函数,值是一个与合约字节码紧密相关的标识符。标识符的实际表示是实现细节,并且可能在不同的编译器版本之间,甚至在不同的后端之间发生变化。以给定表示分配的值是确定性的(即,只要源代码相同,值就会保持不变),但容易受到变化的影响,例如添加、删除或重新排序函数。编译器还可以自由地删除从未使用的内部函数,这可能会影响其他标识符。一些表示方式,例如将标识符仅仅作为跳转目标的方式,可能会受到几乎任何变化的影响,即使这些变化与内部函数完全无关。
为了应对这种情况,Solidity 限制了在有效上下文之外使用内部函数类型。这就是为什么内部函数类型不能作为外部函数的参数(或以其他方式在合约 ABI 中暴露)使用的原因。然而,仍然有一些情况下,用户需要自行判断是否安全使用这些值。例如,虽然不推荐将这些值长期存储在状态变量中,但如果合约代码不会更新,那么这种做法可能是安全的。此外,使用内联汇编可以绕过这些安全限制,但这种做法需要格外小心。
示例
1、使用成员的代码示例:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.4 <0.9.0; // 设置 Solidity 编译器版本范围contract Example {// 定义一个公共的、可支付的函数 ffunction f() public payable returns (bytes4) {// 使用 assert 检查函数 f 的地址是否与当前合约的地址相同assert(this.f.address == address(this)); // 检查 f 函数的地址是否等于当前合约地址// 返回 f 函数的 ABI 选择器(前 4 字节的哈希值)return this.f.selector;}// 定义一个公共函数 g,用来调用函数 ffunction g() public {// 使用 .f{gas: 10, value: 800}() 调用 f 函数,指定发送 800 wei 并且限制 gas 为 10this.f{gas: 10, value: 800}();}
}
2、使用内部函数类型的代码示例:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0; // 设置 Solidity 编译器版本范围// 定义一个库 ArrayUtils,提供对数组进行操作的函数
library ArrayUtils {// 内部函数 map:接受一个 uint 数组和一个函数 f,将 f 应用于数组中的每个元素,并返回新的数组function map(uint[] memory self, function (uint) pure returns (uint) f)internalpurereturns (uint[] memory r){r = new uint[](self.length); // 创建一个与输入数组大小相同的新数组for (uint i = 0; i < self.length; i++) {r[i] = f(self[i]); // 对每个元素应用函数 f}}// 内部函数 reduce:接受一个 uint 数组和一个二元函数 f,对数组进行归约操作(折叠)function reduce(uint[] memory self,function (uint, uint) pure returns (uint) f)internalpurereturns (uint r){r = self[0]; // 初始化结果为数组的第一个元素for (uint i = 1; i < self.length; i++) {r = f(r, self[i]); // 对数组的每个元素应用函数 f}}// 内部函数 range:生成一个从 0 到 length-1 的 uint 数组function range(uint length) internal pure returns (uint[] memory r) {r = new uint[](length); // 创建一个指定长度的数组for (uint i = 0; i < r.length; i++) {r[i] = i; // 填充数组,元素为 0 到 length-1}}
}contract Pyramid {using ArrayUtils for *; // 使用 ArrayUtils 库中的函数// 函数 pyramid:创建一个范围为 l 的数组,应用 square 函数进行变换,再使用 reduce 函数对数组进行求和function pyramid(uint l) public pure returns (uint) {return ArrayUtils.range(l).map(square).reduce(sum); // 生成数组,映射每个元素为平方值,然后进行求和}// 内部函数 square:返回输入值的平方function square(uint x) internal pure returns (uint) {return x * x;}// 内部函数 sum:返回两个输入值的和function sum(uint x, uint y) internal pure returns (uint) {return x + y;}
}}contract Pyramid {using ArrayUtils for *;function pyramid(uint l) public pure returns (uint) {return ArrayUtils.range(l).map(square).reduce(sum);}function square(uint x) internal pure returns (uint) {return x * x;}function sum(uint x, uint y) internal pure returns (uint) {return x + y;}
}
map 和 reduce 函数的定义使用了内部函数类型作为参数。
在 Pyramid 合约中,传递的 square 和 sum 函数正是符合这些内部函数类型的内部函数。
3、使用外部函数类型的代码示例:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.9.0; // 设置 Solidity 编译器版本范围// 定义一个 Oracle 合约,用于请求数据并通过回调返回结果
contract Oracle {// 定义一个 Request 结构体,包含请求的数据和回调函数struct Request {bytes data; // 请求的数据,存储为字节数组function(uint) external callback; // 回调函数,接收一个 uint 参数并是外部可见的}Request[] private requests; // 存储所有请求的数组event NewRequest(uint); // 事件,用于记录新请求的 ID// query 函数:接受请求数据和一个外部回调函数function query(bytes memory data, function(uint) external callback) public {// 将请求添加到 requests 数组中requests.push(Request(data, callback));// 触发 NewRequest 事件,通知有新请求emit NewRequest(requests.length - 1);}// reply 函数:用于处理来自外部的响应,并触发回调function reply(uint requestID, uint response) public {// 这里进行检查,确保回复来自可信源requests[requestID].callback(response); // 调用请求中的回调函数,将响应传递给它}
}// 定义一个 OracleUser 合约,模拟从 Oracle 获取汇率并处理响应
contract OracleUser {// 定义一个常量,Oracle 合约的地址,指向已知的 Oracle 合约Oracle constant private ORACLE_CONST = Oracle(address(0x00000000219ab540356cBB839Cbe05303d7705Fa)); // 已知合约uint private exchangeRate; // 存储汇率的状态变量// buySomething 函数:向 Oracle 请求数据(例如汇率)function buySomething() public {// 调用 Oracle 的 query 函数,传递数据和回调函数ORACLE_CONST.query("USD", this.oracleResponse);}// oracleResponse 函数:接收 Oracle 返回的响应,并更新汇率function oracleResponse(uint response) public {// 确保只有 Oracle 合约可以调用该函数require(msg.sender == address(ORACLE_CONST),"Only oracle can call this.");exchangeRate = response; // 更新汇率}
}
callback 作为外部函数类型,可以允许外部合约通过 Oracle 合约进行回调。
通过传递外部函数类型,Oracle 合约能够调用 OracleUser 合约中的 oracleResponse 函数,这样响应就可以被处理并且进行合约间交互。
相关文章:
【区块链安全 | 第十六篇】类型之值类型(三)
文章目录 函数类型声明语法转换成员合约更新时的值稳定性示例 函数类型 函数类型是函数的类型。函数类型的变量可以通过函数进行赋值,函数类型的参数可以用来传递函数并返回函数。 函数类型有两种类型:内部函数和外部函数。 内部函数只能在当前合约内调…...
设计模式——设计模式理念
文章目录 参考:[设计模式——设计模式理念](https://mp.weixin.qq.com/s/IEduZFF6SaeAthWFFV6zKQ)参考:[设计模式——工厂方法模式](https://mp.weixin.qq.com/s/7tKIPtjvDxDJm4uFnqGsgQ)参考:[设计模式——抽象工厂模式](https://mp.weixin.…...
Kubernetes对象基础操作
基础操作 文章目录 基础操作一、创建Kubernetes对象1.使用指令式命令创建Deployment2.使用指令式对象配置创建Deployment3.使用声明式对象配置创建Deployment 二、操作对象的标签1.为对象添加标签2.修改对象的标签3.删除对象标签4.操作具有指定标签的对象 三、操作名称空间四、…...
Java与代码审计-Java基础语法
Java基础语法 package com.woniuxy.basic;public class HelloWorld {//入口函数public static void main(String[] args){System.out.println("Hello World");for(int i0;i< args.length;i){System.out.println(args[i]);}} }运行结果如下: 但是下面那个没有参数…...
Xenium | 细胞邻域(Cellular Neighborhood)分析(fixed radius)
上节我们介绍了空间转录组数据分析中常见的细胞邻域分析,CN计算过程中定义是否为细胞邻居的方法有两种,一种是上节我们使用固定K最近邻方法(fixed k-nearest neighbors)定义细胞Neighborhood,今天我们介绍另外一种固定半径范围内(fixed radiu…...
Python:爬虫概念与分类
网络请求: https://www.baidu.com url——统一资源定位符 请求过程: 客户端,指web浏览器向服务器发送请求 请求:请求网址(request url);请求方法(request methods);请求头(request header)&…...
[Linux实战] Linux设备树原理与应用详解
Linux设备树原理与应用详解 一、设备树概述 1.1 什么是设备树 设备树(Device Tree,简称DT)是一种描述硬件资源的数据结构,它通过一种树状结构来描述系统硬件配置,包括CPU、内存、总线、外设等硬件信息。设备树最初在…...
用Nginx实现负载均衡与高可用架构(整合Keepalived)
前言 在分布式架构中,负载均衡和高可用是保障系统稳定性的两大核心能力。本文将深入讲解如何通过Nginx实现七层负载均衡,并结合Keepalived构建无单点故障的高可用架构。文末附完整配置模板! 一、Nginx负载均衡实现方案 1. 核心原理 Nginx通…...
SQLMesh调度系统深度解析:内置调度与Airflow集成实践
本文系统解析SQLMesh的两种核心调度方案:内置调度器与Apache Airflow集成。通过对比两者的适用场景、架构设计和操作流程,为企业构建可靠的数据分析流水线提供技术参考。重点内容包括: 内置调度器的轻量级部署与性能优化策略Airflow集成的端到…...
Excel 中 INDEX 和 VLOOKUP 的对比
INDEX 和 VLOOKUP 都是 Excel 中常用的查找函数,但它们的用途和灵活性有所不同。 1. 相同点 均可用于查找数据:都能根据某个条件返回目标值。 支持精确匹配:均可使用 0 或 FALSE 进行精确匹配。 2. 不同点 特性VLOOKUPINDEX MATCH查找方向…...
Multism TL494仿真异常
仿真模型如下:开关频率少了一半,而且带不动负载,有兄弟知道为什么吗 这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码…...
基于 Trae 的超轻量级前端架构设计与性能优化实践
一、技术背景与选型动因 在单页应用(SPA)复杂度指数级增长的今天,传统框架在千级列表渲染场景下普遍存在首屏延迟(>1.5s)、内存占用过高(>200MB)等问题。基于对 Webpack Bundle Analyzer 的长期观察,我们发现核心问题集中在: • 类组件…...
算法练习(队列)
队列 单向队列 1. 定义一个队列 Queue<Integer> q new LinkedList<>(); Queue<Character> q new LinkedList<>();2. 入队列 q.offer(1); q.offer(2); // 从队尾入队列 q.add();3. 出队列 q.poll() // 从队头出队列,并将删除的元素…...
HarmonyOS NEXT开发进阶(十五):日志打印 hilog 与 console.log 的区别
文章目录 一、前言二、两者区别对比三、HiLog 详解四、拓展阅读 一、前言 在日常开发阶段,日志打印是调试程序非常常用的操作,在鸿蒙的官方文档中介绍了hilog这种方式,前端转过来的开发者发现console.log也可以进行日志打印,而且…...
【差分隐私相关概念】差分隐私中的稀疏向量技术
差分隐私中的稀疏向量技术(Sparse Vector Technique, SVT) 稀疏向量技术(SVT)是差分隐私中的一种高效机制,专用于处理稀疏高影响查询的场景。其核心思想是:当面对大量查询时,仅对其中“显著超过…...
快速幂算法还有用吗?——从内置函数到高性能计算的深度解析
博主在学习过程中遇到了一个疑问,既然C语言中有内置函数pow,那为什么还需要算法思想中的快速幂算法呢?下面将会讲解快速幂算法在特定场景下依然非常有用,具体原因如下: 目录 1. 精度与整数运算 2. 性能对比 3. 应用场…...
开源测试用例管理平台
不可错过的10个开源测试用例管理平台: PingCode、TestLink、Kiwi TCMS、Squash TM、FitNesse、Tuleap、Robot Framework、SpecFlow、TestMaster、Nitrate。 开源测试用例管理工具提供了一种透明、灵活的解决方案,使团队能够在不受限的情况下适应具体的测…...
vue 权限应用
目录 一、系统菜单栏权限 二、系统页面按钮权限 在企业开发中,不同的用户所扮演的角色不一样,角色拥有权限,所以用户拥有角色,就会有角色对应的权限。例如,张三是系统管理员角色,登录后就拥有整个系统的…...
鸿蒙HarmonyOS NEXT设备升级应用数据迁移流程
数据迁移是什么 什么是数据迁移,对用户来讲就是本地数据的迁移,终端设备从HarmonyOS 3.1 Release API 9及之前版本(单框架)迁移到HarmonyOS NEXT(双框架)后保证本地数据不丢失。例如,我在某APP…...
利用 PCI-Express 交换机实现面向未来的推理服务器
在数据中心系统的历史上,没有比被 Nvidia 选为其 AI 系统的组件供应商更高的赞誉了。 这就是为什么新兴的互连芯片制造商 Astera Labs 感到十分高兴,因为该公司正在 PCI-Express 交换机、PCI-Express 重定时器和 CXL 内存控制器方面与 Broadcom 和 Marv…...
Python调用手机摄像头检测火焰烟雾的三种方法
方法1:使用IP摄像头应用 OpenCV 1. 在手机上安装IP摄像头应用(如IP Webcam for Android) 2. 配置应用并启动服务器 3. 在Python中使用OpenCV连接 import cv2 import numpy as np # 手机IP摄像头URL(替换为你的手机IP和端口…...
Python if else while for 学习笔记
一.if,else if语句用于根据条件执行代码块 else语句可与if语句结合,当if判断为假时执行else语句 x10 if x>5:print("x大于5") y3 if y>5:print("y大于5") else:print("y小于等于5")结果: 二.while循环…...
正则化是什么?
正则化(Regularization)是机器学习中用于防止模型过拟合(Overfitting)的一种技术,通过在模型训练过程中引入额外的约束或惩罚项,降低模型的复杂度,从而提高其泛化能力(即在未见数据上…...
搜索-BFS
马上蓝桥杯了,最近刷了广搜,感觉挺有意思的,广搜题类型都差不多,模板也一样,大家写的时候可以直接套模板 这里给大家讲一个比较经典的广搜题-迷宫 题目问问能否走到 (n,m) 位置,假设最后一个点是我们的&…...
《边缘计算风云录:FPGA与MCU的算力之争》
点击下面图片带您领略全新的嵌入式学习路线 🔥爆款热榜 88万阅读 1.6万收藏 文章目录 **第一章:边城烽烟——数据洪流压境****第二章:寒铁剑匣——FPGA的千机变****第三章:枯木禅杖——MCU的至简道****第四章:双生契…...
R-GCN-Modeling Relational Data with GraphConvolutional Networks(论文笔记)
CCF等级:B 发布时间:2018年6月 25年3月31日交 目录 一、简介 二、原理 1.整体 2.信息交换与更新 2.1基分解 2.2块对角矩阵 3.实体分类或链接预测 3.1实体分类 3.2链接预测 三、结论和未来工作 一、简介 RGCN通过允许不同关系类型之间的信息…...
蓝桥杯第十六届模拟赛——基础细节考频分析
文章目录 前言一、STL函数二、日期问题三、质数与约数四、基本常识总结 前言 一、STL函数 #include< cmath > 详解floor函数、ceil函数和round函数 1.floor() 功能:把一个小数向下取整如果数是2.2 ,那向下取整的结果就为2.000000如果数是-2.2 &…...
【C++初阶】----模板初阶
1.泛型函数 泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。 2.函数模板 2.1函数模板的概念 函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型…...
PyCharm操作基础指南
一、安装与配置 1. 版本选择 专业版:支持 Web 开发(Django/Flask)、数据库工具、科学计算等(需付费)。 社区版:免费,适合纯 Python 开发。 2. 安装步骤 访问 JetBrains 官网 下载对应版本。…...
Pycharm(七):几个简单案例
一.剪刀石头布 需求:和电脑玩剪刀石头布游戏 考察点:1.随机数;2.判断语句 import random # numrandom.randint(1,3) # print(num) # print(**30) #1.录入玩家手势 playerint(input(请输入手势:(1.剪刀 2.石头 3&…...
