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费 | 更多 | 更少 |
函数修饰符 | 支持 | 不支持 |
无限数组 | 支持 | 不支持 |
无限循环 | 支持 | 不支持 |
内联汇编 | 支持 | 不支持 |
递归调用 | 支持 | 不支持 |
继承 | 支持 | 不支持 |
编译合约
终端编译:
# 编译合约文件
vyper yourFileName.vy
# 编译合约文件并,指定要返回的输出格式
vyper -f abi,bytecode yourFileName.vy
在python中编译:
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 |
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
引用类型
# 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判断语句后面可以加字符出也可以不加
下面两个语句有一样的效果:
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
@external
@nonreentrant("lock")
def make_a_call(_addr: address):
# this function is protected from re-entrancy
...
- __default__函数
- 合约也可以有一个默认函数,如果没有其他函数匹配给定的函数标识符(或者如果根本没有提供,例如通过发送Eth的人),该函数将在调用合约时执行。它与Solidity中的fallback函数结构相同。
- 此函数始终命名为__default__,它必须用@external注释,并且它不能期望任何输入参数,但它仍然可以访问msg对象
- 如果该函数被注解为@payable,则只要向合约发送以太币(无数据),就会执行该函数。这就是默认函数不能接受参数的原因——以太坊的设计决定不区分向合约或用户地址发送以太币
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
变量 - 不能从初始化函数调用其他合约函数
owner: address
def __init__():
self.owner = msg.sender
循环
- for循环的限制
- 不能遍历多维数组,i必须始终是基本类型
- 不能在迭代数组时修改数组中的值,也不能调用可能修改正在迭代的数组的函数
变量
- 是否能在变量声明时赋值
- 存储变量(全局变量)不可以
- 内存变量(函数内声明的变量)必须赋值
- calldata变量(函数输入参数)可以给出默认值
编译器会自动给公共变量创建get方法。对于公共数组,只能通过生成的getter检索单个元素,这种机制的存在是为了避免在返回整个阵列时产生高昂的gas成本,getter将接受一个参数来指定要返回的元素,例如data(0)
变量可以声明为不可变变量,如:DATA: immutable(uint256)
,不可变变量仅能在构造器中赋值,后面就不能改变
函数可以返回多个变量,示例如下:
@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()
接口
接口可以通过内联定义或从单独的文件导入来添加到合约中
# 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可以记录要被用户界面捕获和显示的事件,示例如下:
# 声明事件
event Transfer:
sender: indexed(address)
receiver: indexed(address)
value: uint256
# 记录事件
log Transfer(msg.sender, _to, _amount)
记录事件不占用状态存储,因此不消耗gas,缺点是事件对合同不可用,只对客户可用
NatSpec元数据
Vyper合约可以使用一种特殊形式的文档字符串来为函数、返回变量等提供丰富的文档。这种特殊形式被命名为以太坊自然语言规范格式(NatSpec)。本文档分为以开发人员为中心的消息和面向最终用户的消息。这些消息可能会在最终用户(人类)与合同交互(即签署交易)时显示给他们
编译器不解析内部函数的文档字符串。可以在内部函数的注释中使用NatSpec,但它们不会被处理或包含在编译器输出中,示例如下:
"""
@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
"""