【区块链安全 | 第十四篇】类型之值类型(一)

文章目录

Solidity 是一种静态类型语言,这意味着每个变量(无论是状态变量还是局部变量)都需要指定其类型。Solidity 提供了几种基本类型,这些类型可以组合形成复杂类型。

此外,不同类型可以在包含运算符的表达式中相互作用,并且具有优先级的区分。

Solidity 没有"未定义"或"空"值的概念,但新声明的变量总是具有默认值,该默认值取决于其类型。为了处理任何意外的值,应该使用 revert 函数回滚整个交易,或者返回一个带有布尔值的元组,其中第二个 bool 值表示操作是否成功。

值类型

值类型的变量始终按值传递,即在作为函数参数或赋值时总是被复制。

与引用类型不同,值类型的声明不指定数据位置,因为它们足够小,可以存储在栈中。唯一的例外是状态变量,状态变量默认存储在存储中,但也可以标记为 transient、constant 或 immutable。

布尔值

bool:值是 truefalse

整数

int / uint:各种大小的有符号和无符号整数。

关键字 uint8uint256(以 8 为步长,表示 8 位到 256 位的无符号整数),以及 int8int256

uintint 分别是 uint256int256 的别名。

运算符

比较运算符:<=<==!=>=>(结果为 bool

位运算符:&|^(按位异或),~(按位取反)

移位运算符:<<(左移),>>(右移)

算术运算符:+-,一元 -(仅适用于有符号整数),*/%(取模),**(指数运算)

对于整数类型 X,可以使用 type(X).mintype(X).max 来访问该类型可表示的最小值和最大值。

运算符 ||&& 遵循短路规则。这意味着在表达式 f(x) || g(y) 中,如果 f(x) 计算结果为 true,则 g(y) 将不会被计算。

注意

Solidity 中的整数受到特定范围的限制。例如,对于 uint32,其范围为 02**32 - 1

在 Solidity 中,整数运算有两种模式:"溢出"模式(wrapping/unchecked mode)"检查"模式(checked mode)

默认情况下,运算始终处于 "检查"模式,这意味着如果运算结果超出了类型的值范围,则调用会因失败的断言而回滚。

可以使用 unchecked { ... } 切换到 "溢出"模式,但应谨慎使用。

取模运算

% 的结果是操作数 a 除以操作数 n 后的余数 r,其中 q = int(a / n),并且 r = a - (n * q)

这意味着取模运算的结果与其左操作数的符号相同(或为零),并且对于负数 aa % n == -(-a % n) 恒成立:

复制代码
int256(5) % int256(2) == int256(1)

int256(5) % int256(-2) == int256(1)

int256(-5) % int256(2) == int256(-1)

int256(-5) % int256(-2) == int256(-1)

注意:使用 0 作为取模运算的除数会导致 Panic 错误。此检查无法通过 unchecked { ... } 关闭。

指数运算

指数运算 ** 仅适用于无符号类型作为指数(幂)。指数运算的结果类型始终与底数的类型相同。请确保底数足够大以容纳结果,并预防潜在的断言失败或溢出行为。

注意:在 检查模式 (checked mode)下,对于较小的底数,指数运算仅使用相对廉价的 EXP 操作码。

例如,在计算 x**3 时,使用 x*x*x 可能更便宜。因此,建议进行 Gas 成本测试 并使用优化器。此外,EVM 规定 0**0 的结果为 1

定点数

警告:定点数在 Solidity 中尚不完全支持。它们可以被声明,但不能进行赋值操作。

fixed / ufixed:带符号和无符号定点数,具有不同的大小。关键字 ufixedMxNfixedMxN,其中 M 代表类型所占用的位数,N 代表可用的小数位数。M 必须是 8 的倍数,范围从 8 到 256 位。N 必须在 0 到 80 之间(包含 0 和 80)。ufixedfixedufixed128x18fixed128x18 的别名。

操作符

  • 比较操作符:<=<==!=>=>(结果为布尔值)

  • 算术操作符:+-,一元 -(仅对带符号数),*/%(取模)

注意:浮动点数(许多语言中的 floatdouble,更精确地说是 IEEE 754 数字)和定点数的主要区别在于,浮动点数用于表示整数和小数部分的位数是灵活的,而定点数则严格定义了每部分所占的位数。通常,在浮动点数中,几乎整个空间用于表示数字,而只有少数位数用于定义小数点的位置。

地址(Address)

地址类型有两种主要相似的变体:

  • address:持有一个 20 字节的值(以太坊地址的大小)。
  • address payable:与 address 相同,但具有额外的 transfersend 成员。

这种区分的想法是,address payable 是一个可以接收以太币的地址,而普通的 address 不能接收以太币,例如,它可能是一个不支持接收以太币的智能合约。

类型转换

1.允许从 address payableaddress 的隐式转换,而从 addressaddress payable 必须通过显式转换 payable(<address>)

2.允许显式转换到 address 类型并返回 uint160、整数字面量、bytes20 和合约类型。

3.只有 address 类型和合约类型的表达式可以通过显式转换 payable(...) 转换为 address payable。对于合约类型,只有在合约可以接收以太币的情况下(即合约有 receivepayable 回退函数)才能进行这种转换。注意,payable(0) 是有效的,且是这一规则的例外。

注意

如果我们需要一个 address 类型的变量,并计划向其发送以太币,那么应将其声明为 address payable 以使该要求更加明显。此外,尽量在最早阶段做出这种区分或转换。

从 0.5.0 版本开始,addressaddress payable 之间的区分被引入。自那时起,合约不能隐式转换为 address 类型,但如果它们有 receivepayable 回退函数,仍然可以显式地转换为 addressaddress payable

地址的成员

balance 和 transfer

可以使用 balance 属性查询地址的余额,并使用 transfer 函数将以太币(以 wei 为单位)发送到可支付的地址:

solidity 复制代码
address payable x = payable(0x123);
address myAddress = address(this);
if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);

transfer 函数在当前合约的余额不足或接收方帐户拒绝接收以太币时失败,并会回滚操作。

注意

如果 x 是一个合约地址,它的代码(更具体地说:其 Receive Ether 函数如果存在,或其他回退函数如果存在)将在 transfer 调用时一起执行,这是 EVM 的特性,无法阻止。如果该执行耗尽了 gas 或以任何方式失败,Ether 转账将回滚,当前合约将停止并抛出异常。

send

sendtransfer 的低级对等函数。如果执行失败,当前合约不会停止并抛出异常,而是返回 false

使用 send 时存在一些危险:如果调用堆栈深度达到 1024(调用者可以强制此情况),或者接收方耗尽 gas,则转账将失败。因此,为了安全地转账以太币,始终检查 send 的返回值,使用 transfer 或更好的方式是使用一个模式,其中接收方自己提取以太币。

call,delegatecall 和 staticcall

为了与不符合 ABI 的合约交互,或为了更直接地控制编码,可以使用 calldelegatecallstaticcall 函数。它们都接受一个字节内存参数,并返回成功条件(布尔值)和返回的数据(字节内存)。abi.encodeabi.encodePackedabi.encodeWithSelectorabi.encodeWithSignature 可以用来编码结构化数据。

示例:

solidity 复制代码
bytes memory payload = abi.encodeWithSignature("register(string)", "MyName");
(bool success, bytes memory returnData) = address(nameReg).call(payload);
require(success);

注意,这些都是低级函数,应该小心使用。特别是,任何未知的合约可能是恶意的,如果调用它,你将把控制权交给该合约,而该合约可能会再次调用你的合约,因此当调用返回时,需要做好准备处理可能会更改的状态变量。与其他合约交互的常规方式是调用合约对象上的函数(例如 x.f())。

可以使用 gas 修饰符调整提供的 gas:

solidity 复制代码
address(nameReg).call{gas: 1000000}(abi.encodeWithSignature("register(string)", "MyName"));

类似地,也可以控制提供的 Ether 数量:

solidity 复制代码
address(nameReg).call{value: 1 ether}(abi.encodeWithSignature("register(string)", "MyName"));

最后,这些修饰符可以结合使用,它们的顺序无关紧要:

solidity 复制代码
address(nameReg).call{gas: 1000000, value: 1 ether}(abi.encodeWithSignature("register(string)", "MyName"));

类似地,可以使用 delegatecall 函数,不同之处在于,只有给定地址的代码会被使用,所有其他方面(存储、余额等)都来自当前合约。delegatecall 的目的是使用存储在另一个合约中的库代码。用户必须确保两个合约的存储布局适合使用 delegatecall

code 和 codehash

你可以查询任何智能合约的已部署代码。使用 .code 获取 EVM 字节码作为字节内存(可能为空)。使用 .codehash 获取该代码的 Keccak-256 哈希(作为 bytes32)。注意,addr.codehash 比使用 keccak256(addr.code) 更便宜。

如果与 addr 相关联的帐户为空或不存在(即它没有代码、零余额和零 nonce,如 EIP-161 所定义),则 addr.codehash 的输出可能为 0。如果该帐户没有代码,但有非零余额或 nonce,则 addr.codehash 将输出空数据的 Keccak-256 哈希(即 keccak256(""),其结果为 c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470)。

注意,所有合约都可以转换为 address 类型,因此可以使用 address(this).balance 查询当前合约的余额。

合约类型(Contract Types)

每个合约都定义了自己的类型。你可以将合约隐式转换为它继承的合约类型。合约类型可以显式地转换为 address 类型,反之亦然。

显式转换到 address payable 类型仅在合约类型有 receivepayable 回退函数时才可能。转换仍然通过 address(x) 进行。如果合约类型没有 receivepayable 回退函数,可以使用 payable(address(x)) 进行转换。

注意

1.如果你声明一个合约类型的局部变量(例如 MyContract c),你可以在该合约上调用函数。需要注意的是,必须从与之相同的合约类型赋值给该变量。

2.你也可以实例化合约(这意味着它们是新创建的)。你可以在"通过 new 创建合约"部分找到更多的细节。

3.合约的数据显示方式与 address 类型相同,并且这种类型也用于 ABI 中。

4.合约不支持任何操作符。

5.合约类型的成员是该合约的外部函数,包括任何标记为 public 的状态变量。

6.对于合约 C,你可以使用 type(C) 来访问有关该合约的类型信息。

固定大小字节数组(Fixed-size byte arrays)

值类型 bytes1, bytes2, bytes3, ..., bytes32 用于存储从 1 到 32 字节的字节序列。

操作符:

  • 比较操作符:<=, <, ==, !=, >=, >(返回布尔值)
  • 位操作符:&, |, ^(按位异或),~(按位取反)
  • 移位操作符:<<(左移),>>(右移)
  • 索引访问:如果 x 是类型 bytesI,则 x[k](0 <= k < I)返回第 k 个字节(只读)。

移位操作符与无符号整数类型作为右操作数一起工作(但返回左操作数的类型),表示要移位的位数。使用有符号类型进行移位会导致编译错误。

成员.length 可以返回字节数组的固定长度(只读)。

注意

类型 bytes1[] 是字节的数组,但由于填充规则,对于每个元素,它浪费 31 字节的空间(在存储中除外)。最好使用 bytes 类型。

地址字面量(Address Literals)

地址字面量是通过地址校验和测试的十六进制字面量,例如 0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AFaddress 类型。长度在 39 到 41 位之间且未通过校验和测试的十六进制字面量会产生错误。

我们可以通过在前面(对于整数类型)或后面(对于 bytesNN 类型)加零来去除错误。

相关推荐
阳光普照世界和平9 分钟前
您使用的开源软件许可证是否存在冲突呢?
安全
恒拓高科WorkPlus31 分钟前
局域网视频软件BeeWorks Meet,企业内部安全会议不断线
网络·安全·音视频
AORO_BEIDOU3 小时前
卫星电话究竟是“锦上添花”?还是“刚需之选”?
科技·安全·智能手机·信息与通信
Doris Liu.8 小时前
如何检测代码注入(Part 2)
windows·python·安全·网络安全·网络攻击模型
秋说10 小时前
【区块链安全 | 第八篇】多签机制及恶意多签
安全·区块链
68岁扶墙肾透11 小时前
Java安全-FastJson反序列化分析
java·安全·web安全·网络安全·网络攻击模型·安全架构·fastjson
CryptoPP13 小时前
基于WebSocket的金融数据实时推送系统架构设计对接多国金融数据API
websocket·网络协议·金融·系统架构·区块链
nington0114 小时前
为Splunk登录开启OTP二次验证,增强访问安全
安全
浪子小院14 小时前
区块链技术之分布式数字身份:构建数字世界的信任基石
区块链
智联视频超融合平台16 小时前
视频联网平台智慧运维系统:智能时代的城市视觉中枢
运维·网络协议·安全·音视频·智慧城市·视频编解码