Vyper is a contract-oriented, pythonic programming language that targets the Ethereum Virtual Machine (EVM).
Vyper的Github地址
Vyper文档

简介

  • Vyper是一种运行在以太坊虚拟机上的智能合约编程语言,它的目标是安全、简单、易审计
  • 与Solidity的区别
Solidity Vyper
类似 JavaScript Python
gas费 更多 更少
函数修饰符 支持 不支持
无限数组 支持 不支持
无限循环 支持 不支持
内联汇编 支持 不支持
递归调用 支持 不支持
继承 支持 不支持

编译合约

终端编译:

1
2
3
4
# 编译合约文件
vyper yourFileName.vy
# 编译合约文件并,指定要返回的输出格式
vyper -f abi,bytecode yourFileName.vy

在python中编译:

1
2
from vyper import compiler
compiled_vy = compiler.compile_code(contract_source=contract_file,output_formats=["abi","bytecode"])

数据类型

值类型

数据类型 关键字
Boolean bool
Signed Integer intN
Unsigned Integer uintN
Decimals decimal
Address address
M-byte-wide Fixed Size Byte Array bytesM
Byte Arrays Bytes
Strings String
Enums enum
1
2
3
4
5
6
7
8
9
10
11
12
b: bool = True
i: int128 = -1
u: uint256 = 123
d: decimal = 3.1415900000
addr: address = 0x0000000000000000000000000000000000000000
b32: bytes32 = 0x0000000000000000000000000000000000000000000000000000000000000000
ba: Bytes[100] = b"\x01"
s: String[100] = "Test String"
enum Roles:
ADMIN
USER
e: Roles = Roles.ADMIN

引用类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Fixed-size Lists
exampleList: int128[3] = [10, 11, 12]
exampleList2D: int128[5][2] = empty(int128[5][2])
exampleList2D = [[10, 11, 12, 13, 14], [16, 17, 18, 19, 20]]
# Dynamic Arrays
exampleList: DynArray[int128, 3]
exampleList = []
exampleList.append(42)
myValue: int128 = exampleList.pop()
# Structs
struct MyStruct:
value1: int128
value2: decimal
exampleStruct: MyStruct = MyStruct({value1: 1, value2: 2.0})
exampleStruct.value1 = 1
# Mappings
exampleMapping: HashMap[int128, decimal]
exampleMapping[0] = 10.1

环境变量和常量

环境变量始终存在于命名空间中,主要用于提供有关区块链或当前交易的信息

以下是区块和交易的属性

名称 数据类型 含义
block.coinbase address 当前区块的矿工地址
block.difficulty uint256 当前区块难度
block.prevrandao uint256 信标链提供的当前随机数信标
block.number uint256 当前区块号
block.prevhash bytes32 前区块哈希
block.timestamp uint256 当前区块纪元时间戳
chain.id uint256 Chain ID
msg.data Bytes 消息数据
msg.gas uint256 剩余gas
msg.sender address 当前消息的发送者
msg.value uint256 随消息发送的wei的数量
tx.origin address 整个交易的发送者
tx.gasprice uint256 当前交易的gas price(单位是wei)

block.prevrandao是block.difficulty的别名,在以太坊合并之后,推荐使用block.prevrandao

在使用msg.data前,最好使用len()来检查一下其长度

self是一个环境变量,用于从自身内部引用合约,self允许您读取和改写状态变量并调用合约中的私有函数

名称 数据类型 含义
self address 当前合约地址
self.balance uint256 当前合约余额

定义全局常量,需要使用constant关键字,如:TOTAL_SUPPLY: constant(uint256) = 10000000

语法

函数

所有函数必须以return结束或者有类似raise的终止动作
assert后的判断语句如果错误,就会回滚交易
raise和assert判断语句后面可以加字符出也可以不加

下面两个语句有一样的效果:

1
2
3
assert x > 5, "value too low"
if not cond:
raise "reason"
  • 所有函数必须明确包含一个可见性装饰器

    • external:外部函数是合约接口的一部分,只能通过交易或其他合约调用,Vyper合约不能在两个外部函数之间直接调用。如果必须这样做,可以使用接口
    • internal:内部函数只能从同一合约中的其他函数访问。它们通过self对象调用
  • 四个可变性装饰器

    • pure:函数不读取合约状态或环境变量。标有@pure的函数不能调用没有标有@pure的函数
    • view:可以读取合约状态,但不会改变合约状态。标有@view的函数不能调用可变函数(payable or nonpayable)
    • nonpayable:可以读取和写入合约状态,但不能接收以太币。当不使用可变性装饰器时,函数默认为nonpayable
    • payable:可以读取和写入合约状态,并且可以接收以太币

@nonreentrant(key)装饰器在函数上放置一个锁,所有函数都具有相同的key值。外部合约试图回调这些函数中的任何一个都会导致交易回滚
不能将@nonreentrant装饰器放在pure函数上。可以把它放在view函数上,但它只检查函数不在回调中(存储槽不在locked状态),因为视图函数只能读取状态,不能改变它
可变函数可以保护view函数不被回调,但是视图函数不能保护自己不被回调。
不可重入锁的unlocked值为3,locked值为2

1
2
3
4
5
@external
@nonreentrant("lock")
def make_a_call(_addr: address):
# this function is protected from re-entrancy
...
  • __default__函数
    • 合约也可以有一个默认函数,如果没有其他函数匹配给定的函数标识符(或者如果根本没有提供,例如通过发送Eth的人),该函数将在调用合约时执行。它与Solidity中的fallback函数结构相同。
    • 此函数始终命名为__default__,它必须用@external注释,并且它不能期望任何输入参数,但它仍然可以访问msg对象
    • 如果该函数被注解为@payable,则只要向合约发送以太币(无数据),就会执行该函数。这就是默认函数不能接受参数的原因——以太坊的设计决定不区分向合约或用户地址发送以太币
1
2
3
4
5
6
7
8
event Payment:
amount: uint256
sender: indexed(address)

@external
@payable
def __default__():
log Payment(msg.value, msg.sender)

以太坊指定如果合约在执行中耗尽gas将回滚操作,用send向合约发送调用可获得2300gas的免费津贴,如果发件人通过call而不是send来包含更高的气体量,则可以运行更复杂的功能

  • __init__函数
    • __init__是一个特殊的初始化函数,只能在部署合约时调用
    • 它可用于设置存储变量的初始值。一个常见的用例是为合约的创建者设置一个owner变量
    • 不能从初始化函数调用其他合约函数
1
2
3
4
owner: address

def __init__():
self.owner = msg.sender

循环

  • for循环的限制
    • 不能遍历多维数组,i必须始终是基本类型
    • 不能在迭代数组时修改数组中的值,也不能调用可能修改正在迭代的数组的函数

变量

  • 是否能在变量声明时赋值
    • 存储变量(全局变量)不可以
    • 内存变量(函数内声明的变量)必须赋值
    • calldata变量(函数输入参数)可以给出默认值

编译器会自动给公共变量创建get方法。对于公共数组,只能通过生成的getter检索单个元素,这种机制的存在是为了避免在返回整个阵列时产生高昂的gas成本,getter将接受一个参数来指定要返回的元素,例如data(0)

变量可以声明为不可变变量,如:DATA: immutable(uint256),不可变变量仅能在构造器中赋值,后面就不能改变

函数可以返回多个变量,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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()

接口

接口可以通过内联定义或从单独的文件导入来添加到合约中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# interface关键字用于定义内联外部接口
interface FooBar:
def calculate() -> uint256: view
def test1(): nonpayable

# 接口名称也可以用作存储变量的类型
foobar_contract: FooBar

@external
def __init__(foobar_address: address):
self.foobar_contract = FooBar(foobar_address)

# 导入接口用import或from...import...
# 可以把一个合约当作接口导入,也可以导入自己作为接口
import greeter as Greeter

name: public(String[10])

@external
def __init__(_name: String[10]):
self.name = _name

@view
@external
def greet(addr: addresss) -> String[16]:
return concat("Hello ", Greeter(addr).name())

事件

Vyper可以记录要被用户界面捕获和显示的事件,示例如下:

1
2
3
4
5
6
7
8
# 声明事件
event Transfer:
sender: indexed(address)
receiver: indexed(address)
value: uint256

# 记录事件
log Transfer(msg.sender, _to, _amount)

记录事件不占用状态存储,因此不消耗gas,缺点是事件对合同不可用,只对客户可用

NatSpec元数据

Vyper合约可以使用一种特殊形式的文档字符串来为函数、返回变量等提供丰富的文档。这种特殊形式被命名为以太坊自然语言规范格式(NatSpec)。本文档分为以开发人员为中心的消息和面向最终用户的消息。这些消息可能会在最终用户(人类)与合同交互(即签署交易)时显示给他们

编译器不解析内部函数的文档字符串。可以在内部函数的注释中使用NatSpec,但它们不会被处理或包含在编译器输出中,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
"""
@title A simulator for Bug Bunny, the most famous Rabbit
@license MIT
@author Warned Bros
@notice You can use this contract for only the most basic simulation
@dev
Simply chewing a carrot does not count, carrots must pass
the throat to be considered eaten
"""

@external
@payable
def doesEat(food: string[30], qty: uint256) -> bool:
"""
@notice Determine if Bugs will accept `qty` of `food` to eat
@dev Compares the entire string and does not rely on a hash
@param food The name of a food to evaluate (in English)
@param qty The number of food items to evaluate
@return True if Bugs will eat it, False otherwise
"""