Vyper logo

yper

语言基础Vyper 中文文档

类型系统

掌握 Vyper 的静态类型、数值类型、数组、映射和显式转换规则。

Vyper 是静态类型语言。变量、参数和返回值的类型必须在编译期明确, 这使得很多模糊行为在进入链上之前就会被拦住。值在赋值和传参时总是按值复制, 调用方永远不需要担心被调用方修改了传入的数据结构。

静态类型约束

Vyper 没有"模糊子类型层级"。类型之间不存在隐式继承关系, 每个值都必须通过显式的 convert() 来进行类型转换。

核心原则:

  • 类型越明确,调用边界越清晰。
  • 编译器知道得越多,运行时越不容易出现意外行为。
  • 显式转换优先于隐式魔法。

关于可变性

内部函数的参数和局部变量可以被重新赋值(同类型),数组和结构体也支持就地修改成员。 但外部函数的参数是不可变的——既不能重新赋值,也不能修改成员。

Boolean

关键字: bool

布尔值只有 TrueFalse 两个取值。

运算符说明
not x逻辑取反
x and y逻辑与
x or y逻辑或
x == y相等
x != y不等

andor 遵循短路求值,与 Python 行为一致。

有符号整数(intN)

关键字: intN(例如 int128

可存储正数和负数。N 是 8 到 256 之间的 8 的倍数。 取值范围为 -2^(N-1) 到 2^(N-1) - 1。

vyper

delta: int128 = -5
big: int256 = -999999999

比较运算符: <<===!=>=>(两端必须类型相同)

算术运算符:

运算符说明
x + y加法
x - y减法
-x取负
x * y乘法
x // y整数除法
x ** y指数运算
x % y取模

位运算符: &|^(两端必须类型相同)

移位运算符: <<>>(仅对 int256 可用,y 为无符号整数。int256 的右移会编译为 EVM 的 SAR 有符号右移指令)

整数除法的舍入方向

Vyper 的整数除法向零舍入,这与 Python 不同(Python 向负无穷舍入)。 例如 -1 // 2 在 Vyper 中返回 0,在 Python 中返回 -1。 这一设计保证了 (x // y) * y + (x % y) == x 恒成立。

无符号整数(uintN)

关键字: uintN(例如 uint256uint8

只能存储非负整数。N 是 8 到 256 之间的 8 的倍数。 取值范围为 0 到 2^N - 1。

vyper

counter: uint256 = 0
small: uint8 = 255

运算符与有符号整数相同,额外支持 ~x(按位取反,目前仅 uint256 可用)。

移位运算仅对 uint256 可用,右移编译为 EVM 的 SHR 无符号右移指令。

字面量的默认类型

整数字面量默认被解释为 int256。当赋值目标类型明确时(例如 x: uint8 = 1), 编译器会自动适配。如需显式指定,使用 convert(literal, uint8)

Decimal(十进制定点数)

关键字: decimal

从 v0.4.0 起,使用 decimal 需要通过 CLI 标志 --enable-decimals 显式启用。

精度为 10 位小数。ABI 类型为 int168。 字面量必须包含小数点才能被解释为 decimal

vyper

price: decimal = 0.1
rate: decimal = 3.14

算术运算符: +--x(取负)、*/(注意是十进制除法,不是整数除法)、%

比较运算符与整数类型一致。

Address

关键字: address

存储一个 20 字节的以太坊地址。地址字面量必须使用 0x 前缀的十六进制格式,并通过 EIP-55 校验和验证。

vyper

owner: address = 0x1234567890123456789012345678901234567890

地址成员

成员类型说明
balanceuint256地址余额
codehashbytes32地址上代码的 keccak 哈希(无合约时返回特定常量值)
codesizeuint256部署代码的字节大小
is_contractbool地址上是否部署了合约
codeBytes合约字节码

访问方式:_address.balance_address.codesize 等。

注意

SELFDESTRUCTCREATE2 可以移除或替换某个地址上的字节码。 不要假设地址成员值永远不变。_address.code 需要配合 slice() 使用来截取特定片段。

固定字节数组(bytesM)

关键字: bytesM(例如 bytes32bytes4

M 字节宽的固定大小字节数组。在 ABI 层表示为 bytesM

vyper

hash: bytes32
some_method_id: bytes4 = 0x01abcdef

常用操作包括 keccak256(x)concat(x, ...)slice(x, start, length)

动态字节数组(Bytes)

关键字: Bytes

语法为 Bytes[maxLen],其中 maxLen 是最大字节数。ABI 层表示为 bytes

vyper

bytes_string: Bytes[100] = b"\x01"
hex_bytes: Bytes[100] = x"01"

字符串(String)

关键字: String

固定最大长度的字符串类型。实际内容可以短于最大长度。ABI 层表示为 string

vyper

example_str: String[100] = "Test String"

Flag(标志枚举)

关键字: flag

自定义枚举类型,至少 1 个成员,最多 256 个。 成员值为 uint256,形式为 2^n,其中 n 为成员在 0 到 255 范围内的索引。

vyper

flag Roles:
    ADMIN
    USER

role: Roles = Roles.ADMIN

比较运算符: ==!=innot in

位运算符: &|^~

成员组合可以通过位运算操作。innot in 可以检查成员是否存在于某个组合中:

vyper

flag Roles:
    MANAGER
    ADMIN
    USER

@external
def foo(a: Roles) -> bool:
    return a in (Roles.MANAGER | Roles.USER)

in 与 == 的区别

in 检查两个 flag 对象是否有任何共同设置的位,而 == 检查两个 flag 对象是否逐位完全相同。

位运算还可用于添加和撤销权限:

vyper

@external
def add_user(a: Roles) -> Roles:
    ret: Roles = a
    ret |= Roles.USER     # 设置 USER 位为 1
    return ret

@external
def revoke_user(a: Roles) -> Roles:
    ret: Roles = a
    ret &= ~Roles.USER    # 设置 USER 位为 0
    return ret

标量类型速查

以下是所有基础标量类型的快速参考:

类型说明默认值
bool布尔值,TrueFalseFalse
intN有符号整数,N 为 8~256 的 8 倍数0
uintN无符号整数,N 为 8~256 的 8 倍数0
decimal十进制定点数,10 位精度0.0
address20 字节以太坊地址0x000...000
bytesMM 字节固定字节数组(M 为 1~32)全零
Bytes[N]最大 N 字节的动态字节数组全零
String[N]最大 N 字符的字符串

集合类型

固定长度数组

语法为 _name: _ValueType[_Integer](不支持 Bytes[N]String[N] 和 flag 作为元素类型)。

vyper

exampleList: int128[3]

# 赋值
exampleList = [10, 11, 12]
exampleList[2] = 42

# 访问
return exampleList[0]

多维数组的声明顺序与访问顺序是反过来的:

vyper

# 声明:2 行 5 列
exampleList2D: int128[5][2] = empty(int128[5][2])

# 访问:[行索引][列索引]
exampleList2D[0][4] = 42

安全提示

在存储中定义大小远超 2^64 的数组可能因溢出风险导致安全漏洞。

动态数组(DynArray)

运行时可变长度的有界数组,声明语法为 DynArray[_Type, _Integer]

vyper

exampleList: DynArray[int128, 3]

exampleList = []
exampleList.append(42)     # 长度变为 1
exampleList.append(120)    # 长度变为 2
exampleList.append(356)    # 长度变为 3
# exampleList.append(1)   # 会 revert!已满

myValue: int128 = exampleList.pop()  # myValue == 356,长度变为 2

关键限制:

  • 越界访问、对空数组 pop() 或对满数组 append() 都会触发 REVERT
  • 迭代数组时不能修改数组内容。
  • ABI 表示为 _Type[],例如 DynArray[int128, 3] 表示为 int128[]

Struct(结构体)

自定义复合类型,可组合多个字段。结构体可嵌套数组和其他结构体,但不能包含映射。

vyper

struct MyStruct:
    value1: int128
    value2: decimal

exampleStruct: MyStruct = MyStruct(value1=1, value2=2.0)
exampleStruct.value1 = 1

HashMap(映射)

哈希表类型,虚拟初始化为所有可能的键都映射到类型的零值。键的数据本身不存储, 只用其 keccak256 哈希来查找值。

vyper

exampleMapping: HashMap[int128, decimal]
exampleMapping[0] = 10.1
  • _KeyType 可以是任何基础类型或字节类型,不支持映射、数组或结构体作为键。
  • _ValueType 可以是任何类型,包括映射(嵌套映射)。
  • 映射只能作为状态变量声明。
  • 映射没有"长度"概念,不能被迭代。

初始值

Vyper 没有 null 概念。每种类型都有默认的零值。 检查变量是否为空需要与对应类型的默认值比较。 使用内建的 empty() 函数可以将变量重置为默认值。

类型默认值
address0x0000000000000000000000000000000000000000
boolFalse
bytes320x00...00(64 个零)
decimal0.0
uint80
int1280
int2560
uint2560

内存变量必须初始化

内存变量在声明时必须赋初始值。引用类型的所有成员会被递归初始化为各自的默认值。

转换规则

Vyper 的所有类型转换必须通过 convert(a, btype) 显式完成。 转换被设计为安全且直观的——所有转换都会检查输入是否在输出类型的有效范围内。

vyper

x: uint256 = 100
y: int256 = convert(x, int256)

who: address = 0x1234567890123456789012345678901234567890
who_as_num: uint160 = convert(who, uint160)

转换原则

核心规则总结:

规则说明
位保留除涉及 decimal 和 bool 的转换外,输入的位表示被原样保留
Bool 转换所有非零输入映射为 True(1)
Decimal → 整数向零截断
Address 处理地址被视为 uint160,但不允许与有符号整数或 decimal 互转
右填充 ↔ 左填充bytes/Bytes/String(右填充)与左填充类型之间的转换会旋转字节
有符号 ↔ 无符号输入为负数时会 revert
窄化转换例如 int256 → int128 会检查输入是否在目标范围内
字节 → 有符号整数会进行符号扩展,例如 bytes10xff 转为 int8 返回 -1
跨宽度字节/整数转换先经过最近的整数类型,例如 bytes1 → int16 等同于 bytes1 → int8 → int16
Flag 转换只能与 uint256 互相转换

实践建议

对不会损失精度的放宽转换(例如 uint8 → uint256)通常可以自动完成。 只要涉及地址、符号位、精度或截断风险,就显式写 convert()。 这虽然多敲几个字,但能显著降低审计时的歧义。