Vyper智能合约编程语言
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 | 编译合约文件 |
在python中编译:
1 | from vyper import compiler |
数据类型
值类型
数据类型 | 关键字 |
---|---|
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 | b: bool = True |
引用类型
1 | # Fixed-size Lists |
环境变量和常量
环境变量始终存在于命名空间中,主要用于提供有关区块链或当前交易的信息
以下是区块和交易的属性
名称 | 数据类型 | 含义 |
---|---|---|
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 | assert x > 5, "value too low" |
-
所有函数必须明确包含一个可见性装饰器
- 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 | @external |
- __default__函数
- 合约也可以有一个默认函数,如果没有其他函数匹配给定的函数标识符(或者如果根本没有提供,例如通过发送Eth的人),该函数将在调用合约时执行。它与Solidity中的fallback函数结构相同。
- 此函数始终命名为__default__,它必须用@external注释,并且它不能期望任何输入参数,但它仍然可以访问msg对象
- 如果该函数被注解为@payable,则只要向合约发送以太币(无数据),就会执行该函数。这就是默认函数不能接受参数的原因——以太坊的设计决定不区分向合约或用户地址发送以太币
1 | event Payment: |
以太坊指定如果合约在执行中耗尽gas将回滚操作,用send
向合约发送调用可获得2300gas的免费津贴,如果发件人通过call
而不是send
来包含更高的气体量,则可以运行更复杂的功能
- __init__函数
- __init__是一个特殊的初始化函数,只能在部署合约时调用
- 它可用于设置存储变量的初始值。一个常见的用例是为合约的创建者设置一个
owner
变量 - 不能从初始化函数调用其他合约函数
1 | owner: address |
循环
- for循环的限制
- 不能遍历多维数组,i必须始终是基本类型
- 不能在迭代数组时修改数组中的值,也不能调用可能修改正在迭代的数组的函数
变量
- 是否能在变量声明时赋值
- 存储变量(全局变量)不可以
- 内存变量(函数内声明的变量)必须赋值
- calldata变量(函数输入参数)可以给出默认值
编译器会自动给公共变量创建get方法。对于公共数组,只能通过生成的getter检索单个元素,这种机制的存在是为了避免在返回整个阵列时产生高昂的gas成本,getter将接受一个参数来指定要返回的元素,例如data(0)
变量可以声明为不可变变量,如:DATA: immutable(uint256)
,不可变变量仅能在构造器中赋值,后面就不能改变
函数可以返回多个变量,示例如下:
1 | @internal |
接口
接口可以通过内联定义或从单独的文件导入来添加到合约中
1 | # interface关键字用于定义内联外部接口 |
事件
Vyper可以记录要被用户界面捕获和显示的事件,示例如下:
1 | # 声明事件 |
记录事件不占用状态存储,因此不消耗gas,缺点是事件对合同不可用,只对客户可用
NatSpec元数据
Vyper合约可以使用一种特殊形式的文档字符串来为函数、返回变量等提供丰富的文档。这种特殊形式被命名为以太坊自然语言规范格式(NatSpec)。本文档分为以开发人员为中心的消息和面向最终用户的消息。这些消息可能会在最终用户(人类)与合同交互(即签署交易)时显示给他们
编译器不解析内部函数的文档字符串。可以在内部函数的注释中使用NatSpec,但它们不会被处理或包含在编译器输出中,示例如下:
1 | """ |