接口¶
接口是一组用于实现智能合约之间通信的函数定义。合约接口定义了该合约所有对外可用的函数。通过导入接口,您的合约现在知道如何调用其他合约中的这些函数。
声明和使用接口¶
接口可以通过内联定义或从单独的文件导入添加到合约中。
interface
关键字用于定义内联外部接口
interface FooBar:
def calculate() -> uint256: view
def test1(): nonpayable
然后可以使用定义的接口来进行外部调用,前提是给定合约地址
@external
def test(foobar: FooBar):
foobar.calculate()
接口名称也可以用作存储变量的类型注释。然后,您将地址值分配给变量以访问该接口。请注意,可以将地址强制转换为接口,例如 FooBar(<address_var>)
foobar_contract: FooBar
@external
def __init__(foobar_address: address):
self.foobar_contract = FooBar(foobar_address)
@external
def test():
self.foobar_contract.calculate()
指定 payable
或 nonpayable
注释表示对外部合约进行的调用能够更改存储,而 view
pure
调用将使用 STATICCALL
,确保在执行期间不会更改存储。此外,payable
允许在调用时发送非零值。
interface FooBar:
def calculate() -> uint256: pure
def query() -> uint256: view
def update(): nonpayable
def pay(): payable
@external
def test(foobar: FooBar):
foobar.calculate() # cannot change storage
foobar.query() # cannot change storage, but reads itself
foobar.update() # storage can be altered
foobar.pay(value=1) # storage can be altered, and value can be sent
Vyper 提供了在进行外部调用时设置以下附加关键字参数的选项
关键字 |
描述 |
---|---|
|
指定调用的 gas 值 |
|
指定与调用一起发送的以太坊数量 |
|
放弃 |
|
如果未返回值,则指定默认返回值 |
default_return_value
参数可用于处理受 Solidity 中缺少返回值错误影响的 ERC20 代币,方法类似于 OpenZeppelin 的 safeTransfer
ERC20(USDT).transfer(msg.sender, 1, default_return_value=True) # returns True
ERC20(USDT).transfer(msg.sender, 1) # reverts because nothing returned
警告
当使用 skip_contract_check=True
并且被调用的函数返回数据时(例如:x: uint256 = SomeContract.foo(skip_contract_check=True)
),编译器不提供关于返回值有效性的保证。换句话说,如果被调用的合约不存在,会发生什么是不确定的行为。特别是,返回值可能指向垃圾内存。因此,建议仅在已手动确保被调用合约在调用时存在的调用中使用 skip_contract_check=True
。
导入接口¶
接口通过 import
或 from ... import
语句导入。
导入的接口使用标准 Vyper 语法编写。导入接口时,每个函数的主体都会被忽略。如果您正在定义一个独立的接口,通常通过使用 pass
语句来指定它
@external
def test1():
pass
@external
def calculate() -> uint256:
pass
您还可以导入一个完全实现的合约,Vyper 会自动将其转换为接口。合约甚至可以导入自身以访问其自身的接口。
import greeter as Greeter
name: public(String[10])
@external
def __init__(_name: String[10]):
self.name = _name
@view
@external
def greet() -> String[16]:
return concat("Hello ", Greeter(msg.sender).name())
通过 import
导入¶
使用绝对 import
语句,您 **必须** 包含一个别名作为导入包的名称。在以下示例中,如果不包含 as Foo
,将引发编译错误
import contract.foo as Foo
通过 from ... import
导入¶
使用 from
,您可以执行绝对和相对导入。您可以选择性地包含一个别名 - 如果您不包含,接口的名称将与文件相同。
# without an alias
from contract import foo
# with an alias
from contract import foo as Foo
通过在合约名称前添加点可以进行相对导入。一个前导点表示从当前包开始的相对导入。两个前导点表示从当前包的父级开始的相对导入
from . import foo
from ..interfaces import baz
搜索接口文件¶
查找要导入的文件时,Vyper 将首先搜索与正在编译的合约相同的文件夹。对于绝对导入,它还会搜索相对于项目的根路径。Vyper 首先检查带有 .vy
后缀的文件名,然后检查 .json
。
使用命令行编译器时,根路径默认为当前工作目录。您可以使用 -p
标志更改它
$ vyper my_project/contracts/my_contract.vy -p my_project
在上面的示例中,my_project
文件夹被设置为根路径。合约不能执行超出顶层文件夹的相对导入。
内置接口¶
Vyper 包含常见的内置接口,例如 ERC20 和 ERC721。这些从 vyper.interfaces
导入
from vyper.interfaces import ERC20
implements: ERC20
您可以在 Vyper GitHub 存储库中查看所有可用的内置接口。
实现接口¶
您可以使用 implements
语句为您的合约定义接口
import an_interface as FooBarInterface
implements: FooBarInterface
这会从 an_interface.vy
(或如果使用 ABI json 接口类型,则为 an_interface.json
)中的 vyper 文件导入定义的接口,并确保您当前的合约实现了所有必要的外部函数。如果合约中不包含任何接口函数,它将无法编译。这在开发围绕 ERC20 等定义良好的标准的合约时特别有用。
注意
实现具有需要上限的返回值的函数(例如 Bytes
、DynArray
或 String
)的接口,接口中定义的上限表示实现的下限。假设函数 my_func
在接口中返回一个值 String[1]
,这意味着对于 my_func
的实现函数,返回值必须至少具有长度 1。这种行为在未来可能会改变。
提取接口¶
Vyper 具有内置的格式选项,允许您轻松创建自己的 Vyper 接口。
$ vyper -f interface examples/voting/ballot.vy
# Functions
@view
@external
def delegated(addr: address) -> bool:
pass
# ...
如果您想对另一个合约进行外部调用,Vyper 还提供了一个外部接口提取实用程序。
$ vyper -f external_interface examples/voting/ballot.vy
# External Contracts
interface Ballot:
def delegated(addr: address) -> bool: view
def directlyVoted(addr: address) -> bool: view
def giveRightToVote(voter: address): nonpayable
def forwardWeight(delegate_with_weight_to_forward: address): nonpayable
# ...
然后可以轻松地将输出复制粘贴到目标位置。