范围和声明

变量声明

第一次引用变量时,必须声明其类型

data: int128

在上面的示例中,我们声明了变量 data,其类型为 int128

根据活动作用域,可能或可能不会分配初始值

  • 对于存储变量(在模块作用域中声明),不能设置初始值

  • 对于内存变量(在函数内声明),必须设置初始值

  • 对于 calldata 变量(函数输入参数),可以提供默认值

声明公共变量

存储变量可以在声明期间标记为 public

data: public(int128)

编译器会自动为所有公共存储变量创建 getter 函数。对于上面的示例,编译器将生成一个名为 data 的函数,该函数不接受任何参数,并返回一个 int128,即状态变量 data 的值。

对于公共数组,您只能通过生成的 getter 检索单个元素。此机制旨在避免在返回整个数组时产生高昂的 Gas 成本。getter 将接受一个参数来指定要返回的元素,例如 data(0)

声明不可变变量

变量可以在声明期间标记为 immutable

DATA: immutable(uint256)

@external
def __init__(_data: uint256):
    DATA = _data

声明为不可变的变量类似于常量,不同之处在于它们在合约的构造函数中被赋值。不可变值必须在构造时被赋值,并且在构造之后不能被赋值。

编译器生成的合约创建代码将在合约的运行时代码被返回之前对其进行修改,方法是在由构造函数返回的运行时代码中追加所有分配给不可变变量的值。如果您要比较编译器生成的运行时代码与实际存储在区块链中的运行时代码,这一点很重要。

元组赋值

您不能直接声明元组类型。但是,在某些情况下,您可以在赋值期间使用字面元组。例如,当函数返回多个值时

@internal
def foo() -> (int128, int128):
    return 2, 3

@external
def bar():
    a: int128 = 0
    b: int128 = 0

    # the return value of `foo` is assigned using a tuple
    (a, b) = self.foo()

    # Can also skip the parenthesis
    a, b = self.foo()

存储布局

存储变量位于智能合约中特定存储槽位。默认情况下,编译器将第一个要存储的变量分配到 slot 0;后续变量按顺序存储在其之后。

在某些情况下,有必要覆盖这种模式,并将存储变量分配到自定义槽位。这种行为通常对于可升级合约是必需的,以确保两个合约(旧合约和新合约)在同一个槽位中存储同一个变量。

这可以通过包含 --storage-layout-file 标志来在通过 vyper 编译时执行。

例如,考虑升级以下合约

# old_contract.vy
owner: public(address)
balanceOf: public(HashMap[address, uint256])
# new_contract.vy
owner: public(address)
minter: public(address)
balanceOf: public(HashMap[address, uint256])

这会在升级时造成问题,因为 balanceOf 映射在旧合约中位于 slot1,在新合约中位于 slot2

可以通过使用存储布局覆盖将 balanceOf 分配到 slot1 来避免此问题。可以使用 vyper new_contract.vy --storage-layout-file new_contract_storage.json 编译合约,其中 new_contract_storage.json 包含以下内容

{
    "owner": {"type": "address", "slot": 0},
    "minter": {"type": "address", "slot": 2},
    "balanceOf": {"type": "HashMap[address, uint256]", "slot": 1}
}

有关生成存储布局的更多信息,请参见存储布局.

作用域规则

Vyper 遵循 C99 作用域规则。变量从其声明后的位置开始一直到包含该声明的最小块的末尾都是可见的。

模块作用域

在代码块之外声明的变量和其他项(函数、常量、事件和结构定义等),在声明之前也是可见的。这意味着您可以在声明之前使用模块级项。

此规则的一个例外是,您只能调用已经声明的函数。

从函数访问模块作用域

在合约的模块作用域中声明的值,例如存储变量和函数,可以通过 self 对象访问

a: int128

@internal
def foo() -> int128
    return 42

@external
def foo() -> int128:
    b: int128 = self.foo()
    return self.a  + b

名称遮蔽

不允许内存或 calldata 变量遮蔽不可变或常量值的名称。以下示例将无法编译

a: constant(bool) = True

@external
def foo() -> bool:
    # memory variable cannot have the same name as a constant or immutable variable
    a: bool = False
    return a
a: immutable(bool)

@external
def __init__():
    a = True
@external
def foo(a:bool) -> bool:
    # input argument cannot have the same name as a constant or immutable variable
    return a

函数作用域

在函数内声明或作为函数输入参数给出的变量在该函数的函数体中可见。例如,以下合约有效,因为 a 的每个声明都只存在于一个函数的函数体中。

@external
def foo(a: int128):
    pass

@external
def bar(a: uint256):
    pass

@external
def baz():
    a: bool = True

以下示例将无法编译

@external
def foo(a: int128):
    # `a` has already been declared as an input argument
    a: int128 = 21
@external
def foo(a: int128):
    a = 4

@external
def bar():
    # `a` has not been declared within this function
    a += 12

块作用域

forif 语句创建的逻辑块有自己的作用域。例如,以下合约有效,因为 x 只存在于 if 语句的每个分支的块作用域中

@external
def foo(a: bool) -> int128:
    if a:
        x: int128 = 3
    else:
        x: bool = False

for 语句中,目标变量存在于循环的作用域内。例如,以下合约有效,因为 i 在退出循环后不再可用

@external
def foo(a: bool) -> int128:
    for i in [1, 2, 3]:
        pass
    i: bool = False

以下合约无法编译,因为 a 尚未在循环之外声明。

@external
def foo(a: bool) -> int128:
    for i in [1, 2, 3]:
        a: int128 = i
    a += 3