通過例子學Vyper(一)

學習一門語言,最好的方法就是動手去實現一個案例。

本文通過一個官方的公開拍賣合約示例來介紹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` ——表示一個時刻 `timedelta`——表示一段時間長度,以秒為單位 注:兩個`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`。

1、斷言函數`assert`相當於傳統編程語言裡的`if`,當判斷條件為`False`時,將終止後面語句的執行,並把剩餘的`gas`返回給合約調用方。

2、發送函數`send`顧名思義,就是從當前地址轉賬以太幣到指定地址。

好了,這個例子到此就解釋完畢了,其實還是挺簡單的。