学习一门语言,最好的方法就是动手去实现一个案例。
本文通过一个官方的公开拍卖合约示例来介绍Vyper编程。
当我们开始用Vyper进行智能合约编程时,要记住一个重要的原则:
所有Vyper语法都是有效的Python3语法,但并不是所有的Python3语法都可以在Vyper中使用。
在这个合约中,我们将实现一个简单的公开拍卖功能,通过该合约参与者可以在有限的时间内提交出价,直至拍卖结束,最高的出价者竞拍成功,合约自动将竞拍款支付给受益人。
以下是代码:
# 公开拍卖
# 拍卖参数
# beneficiary是受益人的以太坊地址,将获得出价最高者的竞拍款
# auction_start是竞拍开始时间,auction_end是结束时间
beneficiary: public(address)
auction_start: public(timestamp)
auction_end: public(timestamp)
# 保存当前最高出价的信息
# highest_bidder是最高出价人以太坊地址
# highest_bid是当前最高出价
highest_bidder: public(address)
highest_bid: public(wei_value)
# 用于标识拍卖是否结束,为True时拍卖结束
ended: public(bool)
# 构造函数,进行初始化
# '_beneficiary'参数代表受益人地址
# '_bidding_time'参数代表拍卖持续的时长
@public
def __init__(_beneficiary: address, _bidding_time: timedelta):
self.beneficiary = _beneficiary
self.auction_start = block.timestamp
self.auction_end = self.auction_start + _bidding_time
# 竞价函数,通过调用该函数发送以太币参与竞拍
# 当竞拍结束时,你没有赢得拍卖,以太币将被退还
@public
@payable
def bid():
# 检查拍卖是否结束了
assert block.timestamp < self.auction_end
# 检查出价是否比当前最高价高
assert msg.value > self.highest_bid
if not self.highest_bid == 0:
# 退还之前最高出价者的资金
send(self.highest_bidder, self.highest_bid)
self.highest_bidder = msg.sender
self.highest_bid = msg.value
# 结束竞拍,将最高出价发送给受益人
@public
def end_auction():
# 1. 判断合约是否结束
# 检查是否超出拍卖的最后时间
assert block.timestamp >= self.auction_end
# 检查该合约是否已经被调用过
assert not self.ended
# 2. 标记为竞拍结束
self.ended = True
# 3. 发送以太币给受益人
send(self.beneficiary, self.highest_bid)
该拍卖合约非常简单,仅包含一个构造函数,两个方法,以及一些状态变量。下面我们来详细讨论这个合约。
一、合约名称
我们会发现这个代码没有类似Solidity的“contract OpenAuction”的表述,这是因为Vyper的语法跟Python的模块命名类似,合约名就是文件名,即这个合约叫OpenAuction的话,就把上面这段代码保存为一个名叫OpenAuction.vy的文件。
二、变量声明
Vyper是静态类型语言,要事先声明所有变量的数据类型。
放在文件开头定义的变量为全局变量。
beneficiary: public(address)
auction_start: public(timestamp)
auction_end: public(timestamp)
highest_bidder: public(address)
highest_bid: public(wei_value)
ended: public(bool)
beneficiary
被定义为一个公开类型的地址变量。这个beneficiary
地址就是拍卖受益人的地址,拍卖完成后,拍卖款就会转入这个地址。auction_start
和auction_end
是公开类型的时间戳变量。这两个变量用来记录拍卖的起始和结束时间。highest_bidder
和highest_bid
表示当前最高出价人的地址和出价金额,分别是一个地址变量和一个以太币金额变量。ended
是一个布尔值变量,用于标记拍卖是否结束。
关键字 | 说明 |
---|---|
public |
几乎在所有的语言中表示的意义都相同,就是可以被外部合约访问。没有声明为public 的变量只能在合约内被访问。public 还为变量创建了一个“getter ”函数,可通过外部调用访问contract.beneficiary() 。 |
address |
是以太坊特有的一种数据类型,表示该变量被定义为存储一个以太坊地址,即一个长度为20个字节的十六进制钱包地址,比如像这样ca35b7d915458ef540ade6068dfe2f44e8fa733c 。 |
timestamp |
是以太坊特有的一种数据类型,代表的是当前块的Unix时间戳(从1970/1/1 00:00:00 UTC开始所经过的秒数)。 |
wei_value |
以太币金额,以wei 为单位。 |
bool |
跟其他语言的布尔值变量一样,有True 和False 两个值。 |
三、构造函数
@public
def __init__(_beneficiary: address, _bidding_time: timedelta):
self.beneficiary = _beneficiary
self.auction_start = block.timestamp
self.auction_end = self.auction_start + _bidding_time
Vyper的构造函数定义跟Python的类构造函数一样,用__init__
表示。
在函数的前面有一个装饰器@public
,这表示该函数可以被外部调用。Vyper语言里的其他装饰器还包括:
装饰器 | 说明 |
---|---|
@private |
表示只能在合约内部调用 |
@constant |
表示合约状态不能被改变 |
@payable |
表示可以接收以太币转账 |
@nonrentant |
表示只能被调用一次,防止重入攻击 |
其中@public
和@private
是强制二选一的,其他的装饰器视情况可选。
构造函数包含两个参数:1、_beneficiary
是address
类型,代表受益人地址;2、_bidding_time
是timedelta
类型,代表拍卖持续的时长。
关键字 | 单位 | 说明 |
---|---|---|
timestamp |
1秒 | 表示一个时刻 |
timedelta |
1秒 | 表示一段时间长度,以秒为单位 |
注:两个
timedelta
类型的变量可以相加,一个timestamp
类型的变量与一个timedelta
类型的时间变量也可相加,但是两个timestamp
类型的变量不能相加。
构造函数通过self
关键字来指代合约本身,将函数参数赋值给合约的变量。在构造函数里,我们看到有一个block
对象,通过它获得了当前区块的时间。block
是一个以太坊默认对象,在任何的Vyper合约中都能调用,不用声明。与block
对象类似的还有msg
对象。
四、功能函数
这个智能合约里包含两个功能函数,竞价函数与结束函数。
这两个函数的功能都已经在上面的代码注释中进行了比较详细的解释,这里主要解说一下代码里的一些知识点。
@payable
装饰器说明bid()
函数可接收以太币,想要进行投标的用户可以调用该方法并发送一定数量的以太币。合约通过内置对象msg
的msg.sender
方法获取访问用户的以太坊地址,通过msg.value
方法获取访问用户发送的以太币数量。
注意!下方高能!
msg.sender
将在内部函数调用之间发生变化。当从外部调用函数时,它对于第一个函数调用是正确的,就是调用函数的人的地址。但是,在合约内部函数互相调用之后,msg.sender
将会引用合同本身而不是事务的发送者。这是一个容易造成安全隐患的大坑!在设计智能合约的时候,要牢记安全性,保持代码简单,可读性强,安全第一!
在bid()
和end_auction()
这两个函数里,最主要的是使用了Vyper语言内置函数里面的断言函数assert
和发送函数send
。
断言函数
assert
相当于传统编程语言里的if
,当判断条件为False
时,将终止后面语句的执行,并把剩余的gas
返回给合约调用方。发送函数
send
顾名思义,就是从当前地址转账以太币到指定地址。
好了,这个例子到此就解释完毕了,其实还是挺简单的。