类型¶
Vyper 是一种静态类型语言。每个变量(状态和局部变量)的类型必须在编译时指定或至少已知。Vyper 提供了几种基本类型,可以组合起来形成复杂类型。
此外,类型可以在包含运算符的表达式中相互交互。
值类型¶
以下类型也称为值类型,因为这些类型的变量始终按值传递,即在用作函数参数或在赋值中时始终被复制。
布尔值¶
关键字: bool
布尔值是一种用于存储逻辑/真值的类型。
值¶
唯一可能的值是常量 True
和 False
。
运算符¶
运算符 |
描述 |
---|---|
|
逻辑否定 |
|
逻辑合取 |
|
逻辑析取 |
|
相等 |
|
不相等 |
布尔运算符 (or
和 and
) 的短路与 Python 的行为一致。
带符号整数 (N 位)¶
关键字: intN
(例如,int128
)
一个带符号整数,可以存储正负整数。 N
必须是 8 到 256(含)之间的 8 的倍数。
运算符¶
比较运算¶
比较运算返回布尔值。
运算符 |
描述 |
---|---|
|
小于 |
|
小于或等于 |
|
等于 |
|
不等于 |
|
大于或等于 |
|
大于 |
x
和 y
必须是相同的类型。
移位¶
运算符 |
描述 |
---|---|
|
左移 |
|
右移 |
移位仅适用于 256 位宽类型。也就是说,x
必须是 int256
,而 y
可以是任何无符号整数。 int256
的右移编译为带符号右移(EVM SAR
指令)。
注意
虽然在运行时移位不受检查(即,它们可以是任意数量的位),但为了防止常见的错误,编译器在编译时更加严格,并将阻止越界移位。例如,在运行时,1 << 257
将评估为 0
,而在编译时,该表达式将引发 OverflowException
。
无符号整数 (N 位)¶
关键字: uintN
(例如,uint8
)
一个无符号整数,可以存储正整数。 N
必须是 8 到 256(含)之间的 8 的倍数。
值¶
从 0 到 (2N-1) 的整数值。
即使小数部分为零,整数字面量也不能带小数点。例如,2.0
不能解释为整数。
注意
整数字面量默认解释为 int256
。在 uint8
更合适的情况下,例如赋值,字面量可能会被解释为 uint8
。示例:_variable: uint8 = _literal
。为了将字面量显式转换为 uint8
,请使用 convert(_literal, uint8)
。
运算符¶
比较运算¶
比较运算返回布尔值。
运算符 |
描述 |
---|---|
|
小于 |
|
小于或等于 |
|
等于 |
|
不等于 |
|
大于或等于 |
|
大于 |
x
和 y
必须是相同的类型。
移位¶
运算符 |
描述 |
---|---|
|
左移 |
|
右移 |
移位仅适用于 256 位宽类型。也就是说,x
必须是 uint256
,而 y
可以是任何无符号整数。 uint256
的右移编译为带符号右移(EVM SHR
指令)。
注意
虽然在运行时移位不受检查(即,它们可以是任意数量的位),但为了防止常见的错误,编译器在编译时更加严格,并将阻止越界移位。例如,在运行时,1 << 257
将评估为 0
,而在编译时,该表达式将引发 OverflowException
。
小数¶
关键字: decimal
小数是一种用于存储十进制定点值的类型。
值¶
精度为 10 位小数的数值,介于 -18707220957835557353007165858768422651595.9365500928 (-2167 / 1010) 和 18707220957835557353007165858768422651595.9365500927 ((2167 - 1) / 1010) 之间。
为了使字面量被解释为 decimal
,它必须包含小数点。
decimal
的 ABI 类型(用于计算方法标识符)是 fixed168x10
。
地址¶
关键字: address
地址类型保存一个以太坊地址。
值¶
地址类型可以保存一个以太坊地址,相当于 20 个字节或 160 位。地址字面量必须用十六进制表示法编写,以 0x
开头,并且必须是校验和。
成员¶
成员 |
类型 |
描述 |
---|---|---|
|
|
地址的余额 |
|
|
地址处代码的 Keccak,如果未部署合约,则为 |
|
|
部署在地址处的代码大小(以字节为单位) |
|
|
指示是否在地址处部署了合约的布尔值 |
|
|
合约字节码 |
语法如下:_address.<member>
,其中 _address
是 address
类型,而 <member>
是上述关键字之一。
注意
诸如 SELFDESTRUCT
和 CREATE2
之类的操作允许在地址处删除和替换字节码。你不应该假设地址成员的值在将来不会改变。
注意
_address.code
需要使用 slice
显式地提取合约字节码的一部分。如果提取的部分超过了字节码的范围,这将抛出异常。你可以使用 _address.codesize
检查 _address.code
的大小。
M 字节宽固定大小字节数组¶
关键字: bytesM
这是一个 M 字节宽的字节数组,与动态大小的字节数组类似。在 ABI 级别上,它被注释为 bytesM(例如,bytes32)。
示例
# Declaration
hash: bytes32
# Assignment
self.hash = _hash
some_method_id: bytes4 = 0x01abcdef
运算符¶
关键字 |
描述 |
---|---|
|
返回 keccak256 哈希值作为 bytes32。 |
|
连接多个输入。 |
|
返回从 |
其中 x
是一个字节数组,_start
和 _len
是整数值。
字节数组¶
关键字: Bytes
一个具有最大大小的字节数组。
语法为 Bytes[maxLen]
,其中 maxLen
是一个整数,表示字节的最大数量。在 ABI 级别上,固定大小的字节数组被注释为 bytes
。
字节字面量可以作为字节字符串给出。
bytes_string: Bytes[100] = b"\x01"
字符串¶
关键字: String
固定大小的字符串可以保存字符数量等于或少于字符串最大长度的字符串。在 ABI 级别上,固定大小的字节数组被注释为 string
。
example_str: String[100] = "Test String"
枚举¶
关键字: enum
枚举是自定义定义的类型。枚举必须至少有一个成员,并且最多可以包含 256 个成员。成员由 uint256
值表示,形式为 2n,其中 n
是成员在范围 0 <= n <= 255
中的索引。
# Defining an enum with two members
enum Roles:
ADMIN
USER
# Declaring an enum variable
role: Roles = Roles.ADMIN
# Returning a member
return Roles.ADMIN
运算符¶
按位运算符¶
运算符 |
描述 |
---|---|
|
按位与 |
|
按位或 |
|
按位异或 |
|
按位取反 |
枚举成员可以使用上面的按位运算符进行组合。虽然枚举成员的值是二的幂,但枚举成员组合可能不是。
in
和 not in
运算符可以与枚举成员组合一起使用来检查成员资格。
enum Roles:
MANAGER
ADMIN
USER
# Check for membership
@external
def foo(a: Roles) -> bool:
return a in (Roles.MANAGER | Roles.USER)
# Check not in
@external
def bar(a: Roles) -> bool:
return a not in (Roles.MANAGER | Roles.USER)
请注意,in
与严格相等 (==
) 不同。 in
检查两个枚举对象上的任何标志是否同时设置,而 ==
检查两个枚举对象是否按位相等。
以下代码使用按位运算从给定的 Roles
对象添加和撤销权限。
引用类型¶
引用类型是指其组件可以在不复制的情况下原地赋值的类型。例如,数组和结构成员可以单独赋值,而不覆盖整个数据结构。
注意
在调用约定方面,引用类型按值传递,而不是按引用传递。这意味着,调用函数不需要担心被调用函数修改传递的结构的数据。
固定大小列表¶
固定大小列表保存有限数量的元素,这些元素属于指定的类型。
列表可以用 _name: _ValueType[_Integer]
声明,除了 Bytes[N]
、String[N]
和枚举。
# Defining a list
exampleList: int128[3]
# Setting values
exampleList = [10, 11, 12]
exampleList[2] = 42
# Returning a value
return exampleList[0]
多维列表也是可能的。声明的符号与其他一些语言相比是相反的,但访问符号没有反转。
二维列表可以用 _name: _ValueType[inner_size][outer_size]
声明。元素可以用 _name[outer_index][inner_index]
访问。
# Defining a list with 2 rows and 5 columns and set all values to 0
exampleList2D: int128[5][2] = empty(int128[5][2])
# Setting a value for row the first row (0) and last column (4)
exampleList2D[0][4] = 42
# Setting values
exampleList2D = [[10, 11, 12, 13, 14], [16, 17, 18, 19, 20]]
# Returning the value in row 0 column 4 (in this case 14)
return exampleList2D[0][4]
注意
在存储中定义一个大小明显大于 2**64
的数组会导致安全漏洞,因为存在溢出风险。
动态数组¶
动态数组表示有界数组,其长度可以在运行时修改,直到类型中指定的边界为止。它们可以用 _name: DynArray[_Type, _Integer]
声明,其中 _Type
可以是值类型或引用类型(映射除外)。
# Defining a list
exampleList: DynArray[int128, 3]
# Setting values
exampleList = []
# exampleList.pop() # would revert!
exampleList.append(42) # exampleList now has length 1
exampleList.append(120) # exampleList now has length 2
exampleList.append(356) # exampleList now has length 3
# exampleList.append(1) # would revert!
myValue: int128 = exampleList.pop() # myValue == 356, exampleList now has length 2
# myValue = exampleList[2] # would revert!
# Returning a value
return exampleList[0]
注意
尝试访问数组运行时长度之外的数据、从空数组中 pop()
或向满数组 append()
将导致运行时 REVERT
。尝试将大于数组边界的数组传递到 calldata 中将导致运行时 REVERT
。
注意
为了使代码易于理解,在使用数组作为迭代器时不允许修改数组。例如,以下用法不允许
for item in self.my_array:
self.my_array[0] = item
在 ABI 中,它们表示为 _Type[]
。例如,DynArray[int128, 3]
表示为 int128[]
,DynArray[DynArray[int128, 3], 3]
表示为 int128[][]
。
注意
在存储中定义一个大小明显大于 2**64
的动态数组会导致安全漏洞,因为存在溢出风险。
结构体¶
结构体是自定义定义的类型,可以对多个变量进行分组。
结构体类型可以在映射和数组中使用。结构体可以包含数组和其他结构体,但不能包含映射。
结构体成员可以通过 struct.argname
访问。
# Defining a struct
struct MyStruct:
value1: int128
value2: decimal
# Declaring a struct variable
exampleStruct: MyStruct = MyStruct({value1: 1, value2: 2.0})
# Accessing a value
exampleStruct.value1 = 1
映射¶
映射是 哈希表,它们是虚拟初始化的,这样每个可能的键都存在并映射到一个值,该值的字节表示都是零:类型的 默认值。
键数据没有存储在映射中。相反,它的 keccak256
哈希值用于查找值。因此,映射没有长度,也没有“设置”键或值的的概念。
映射类型声明为 HashMap[_KeyType, _ValueType]
。
_KeyType
可以是任何基本类型或字节类型。映射、数组或结构体不支持作为键类型。_ValueType
实际上可以是任何类型,包括映射。
注意
映射只允许作为状态变量。
# Defining a mapping
exampleMapping: HashMap[int128, decimal]
# Accessing a value
exampleMapping[0] = 10.1
注意
映射没有长度的概念,因此无法迭代。
初始值¶
与大多数编程语言不同,Vyper 没有 null
的概念。相反,每个变量类型都有一个默认值。要检查变量是否为空,必须将其与给定类型的默认值进行比较。
要将变量重置为其默认值,请将其赋值给内置的 empty()
函数,该函数为该类型构造一个零值。
注意
内存变量必须在声明时分配一个值。
在这里你可以找到所有类型和默认值的列表
类型 |
默认值 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
注意
在 Bytes
中,数组以所有字节都设置为 '\x00'
开始。
注意
在引用类型中,所有类型的成员都设置为其初始值。
类型转换¶
Vyper 中的所有类型转换都必须使用内置的 convert(a: atype, btype)
函数显式地进行。Vyper 中的类型转换旨在安全直观。所有类型转换都将检查输入是否在输出类型的范围内。一般原则如下
除了涉及十进制和布尔值的转换外,输入按位保存。
转换为布尔值将所有非零输入映射为 1。
从十进制转换为整数时,输入被截断为零。
address
类型被视为uint160
,但与带符号整数和十进制数的转换不允许。将右填充类型(
bytes
、Bytes
、String
)转换为左填充类型,会进行旋转以转换填充。例如,从bytes20
转换为address
将导致输入向右旋转 12 个字节。在有符号整数和无符号整数之间转换,如果输入为负数,则会反转。
缩窄转换(例如,
int256 -> int128
)会检查输入是否在输出类型的范围内。在字节和整数类型之间转换,如果输出类型是有符号的,则会进行符号扩展。例如,将
0xff
(bytes1
) 转换为int8
将返回-1
。在大小不同的字节和整数类型之间转换,遵循先经过最接近的整数类型规则。例如,
bytes1 -> int16
等同于bytes1 -> int8 -> int16
(符号扩展,然后加宽)。uint8 -> bytes20
等同于uint8 -> uint160 -> bytes20
(向左旋转 12 个字节)。枚举只能转换为和从
uint256
转换。
一个小的 Python 参考实现作为 Vyper 测试套件的一部分维护,可以在 这里 找到。规则的动机和更详细的讨论可以在 这里 找到。